결합도와 SRP에 대한 요약
Source: Dev.to
응집된 클래스란 무엇인가?
응집된 클래스는 단 하나의 책임만을 갖는 클래스입니다.
응집된 클래스는 더 작고, 더 체계적이며, 유지보수와 재사용이 용이합니다.
낮은 응집도를 가진 클래스 예시
class CalculadoraDeSalario {
calcula(funcionario) {
if (funcionario.cargo === "DESENVOLVEDOR") {
return this.dezOuVintePorcento(funcionario);
}
if (funcionario.cargo === "DBA" || funcionario.cargo === "TESTER") {
return this.quinzeOuVinteCincoPorcento(funcionario);
}
throw new Error("Funcionário inválido");
}
dezOuVintePorcento(funcionario) {
if (funcionario.salarioBase > 3000) {
return funcionario.salarioBase * 0.8;
}
return funcionario.salarioBase * 0.9;
}
quinzeOuVinteCincoPorcento(funcionario) {
if (funcionario.salarioBase > 2000) {
return funcionario.salarioBase * 0.75;
}
return funcionario.salarioBase * 0.85;
}
}
이 클래스의 문제점
- 클래스가 모든 직책을 알아야 함.
- 많은
ifs → 새로운 직책이 생길 때마다 클래스를 수정해야 함. - 계산 규칙을 쉽게 재사용할 수 없음.
- 클래스가 하나 이상의 책임을 가짐 (직책 결정 + 급여 계산).
즉, 낮은 응집도와 높은 결합도. 각 계산 규칙을 별도의 클래스로 분리하면 응집도가 높아지고 재사용 및 유지보수가 쉬워집니다.
왜 이 클래스는 재사용하기 어려운가?
시스템의 다른 곳에서 dezOuVintePorcento() 규칙만 재사용하고 싶다고 상상해 보세요. 이 메서드가 CalculadoraDeSalario 클래스 안에 있기 때문에 메서드만 사용할 수 없습니다. 따라서 다음을 해야 합니다:
- 클래스를 인스턴스화한다
- 직원 객체를 만든다
- 직원을 전달한다
- 그때서야 메서드를 사용한다
계산 규칙을 재사용하려면 전체 클래스를 함께 가져와야 합니다. 이는 클래스가 너무 많은 일을 하고 있어 낮은 응집도를 나타냅니다.
높은 응집도를 가진 리팩터링된 클래스 예시
class Cargo {
calculaSalario(salarioBase) {
throw new Error("Método deve ser implementado pela subclasse");
}
}
class Desenvolvedor extends Cargo {
calculaSalario(salarioBase) {
return salarioBase > 3000
? salarioBase * 0.8
: salarioBase * 0.9;
}
}
class Dba extends Cargo {
calculaSalario(salarioBase) {
return salarioBase > 2000
? salarioBase * 0.75
: salarioBase * 0.85;
}
}
class Tester extends Cargo {
calculaSalario(salarioBase) {
return salarioBase > 2000
? salarioBase * 0.75
: salarioBase * 0.85;
}
}
const RegrasPorCargo = {
DESENVOLVEDOR: new Desenvolvedor(),
DBA: new Dba(),
TESTER: new Tester()
};
class CalculadoraDeSalarioRefatorada {
calcula(funcionario) {
const regra = RegrasPorCargo[funcionario.cargo];
if (!regra) {
throw new Error("Funcionário inválido");
}
return regra.calculaSalario(funcionario.salarioBase);
}
}
const funcionario2 = {
cargo: "DESENVOLVEDOR",
salarioBase: 3500
};
const calculadora2 = new CalculadoraDeSalarioRefatorada();
console.log(calculadora2.calcula(funcionario2));
리팩터링을 이해하기 위한 첫 번째 단계
리팩터링 전, CalculadoraDeSalario 클래스는 두 가지 이유로 변경되었습니다:
- 새로운 직책이 생길 때
- 새로운 계산 규칙이 생길 때
즉, 클래스는 변경 이유가 하나 이상이었으며, SRP (Single Responsibility Principle, 단일 책임 원칙)를 위반했습니다.
계산 메서드를 살펴보면 모두 동일한 형식을 따릅니다:
- 급여를 받는다
- 규칙을 적용한다
- 계산된 급여를 반환한다
리팩터링 아이디어는 각 계산 규칙마다 클래스를 만들고, 모두 동일한 메서드 calculaSalario()를 갖게 하는 것입니다.
이를 통해 얻는 이점
- 각 규칙이 격리됩니다
- 한 규칙의 변경이 다른 규칙에 영향을 주지 않습니다
- 클래스가 더 작고 응집력이 높아집니다
- 시스템 유지보수가 쉬워집니다
- 기존 코드를 변경하지 않고 새로운 직책을 추가할 수 있습니다
이제 각 클래스는 하나의 변경 이유만 가집니다: 바로 자신의 계산 규칙입니다.
왜 작은 메서드로 나누지 않을까?
큰 메서드를 작은 메서드로 나누는 것이 독립적인 재사용을 보장하지는 않습니다; 로직은 여전히 원래 클래스에 결합된 상태로 남아 있습니다.
참고
이 예제와 리팩터링 아이디어는 다음 책에서 영감을 받았습니다: Orientação a Objetos e SOLID para Ninjas - Projetando classes flexíveis – Maurício Aniche.