클린코드
객체 간 의존관계 연결, 인자 수를 최소화하기
메소드(함수)에 인자 수를 최소화한다 - 클린코드 책 중에서
- 메소드에서 이상적인 인자 개수는 0개이다. 다음은 1개이고, 다음은 2개이다.
- 3개는 가능한 피하는 편이 좋다. 4개 이상은 특별한 이유가 있어도 사용하면 안 된다.
인자를 줄이는 방법
- 관련이 있는 인자를 묶어서 하나의 클래스로 만든다.
- 얘시) List winningLotto + int bonusNumber => WinningLotto 로 묶음
클래스간의 의존간계 연결 방식
클래스를 분리할 때 고민해야할 부분은 클래스 간의 의존관계를 어떻게 연결할 것인가? 이다.
1. 상속(is-a)관계, 조합(has-a)관계
- 일급 Collection를 구현할 때 접근 방법으로 상속과 조합 방법으로 구현할 수 있다.
- 객체의 중복을 제거할 때 상속과 조합 방법으로 구현할 수 있다.
- 상속: 하위 클래스가 상위 클래스의 모든 기능을 상속받아 의존 관계를 형성한다.
- 조합: 하나의 클래스가 다른 클래스의 인스턴스를 포함하여 의존 관계를 형성한다.
상속(is-a) 관계
- 상속은 부모 클래스의 모든 속성과 메소드를 자식 클래스가 물려받는 관계다.
- 즉, 자식 클래스는 부모 클래스의 특징을 그대로 가지면서 추가적인 기능을 제공할 수 있습니다.
상속을 사용하는 이유:
- 코드 재사용: 부모 클래스에 정의된 코드를 자식 클래스에서 공유하여 코드 중복을 줄일 수 있습니다.
- 계층 구조 형성: 클래스 간의 계층 구조를 만들어 코드를 체계적으로 관리할 수 있습니다.
- 다형성: 상속 관계를 통해 다형성을 구현하여 유연한 코드를 작성할 수 있습니다.
예시)
1
2
3
4
5
6
7
8
9
10
11
| class Animal {
public void move() {
System.out.println("움직입니다.");
}
}
class Dog extends Animal {
public void bark() {
System.out.println("멍멍");
}
}
|
조합(has-a) 관계
- 조합은 한 클래스가 다른 클래스의 인스턴스를 포함하는 관계다.
- 즉, 한 객체가 다른 객체를 구성 요소로 가지고 있는 것.
조합을 사용하는 이유:
- 유연성: 상속보다 유연하게 객체를 구성할 수 있다.
- 재사용성: 독립적인 클래스를 조합하여 다양한 기능을 구현할 수 있다.
- 책임 분리: 각 클래스가 명확한 책임을 가지도록 분리하여 시스템의 복잡도를 줄일 수 있다.
예시)
1
2
3
4
5
6
7
8
9
10
|
class Car {
private Engine engine;
// ...
}
class Engine {
// ...
}
|
- 일반적으로 상속보다는 조합을 사용하는 것이 더 유연하고 확장 가능한 시스템을 만들 수 있다.
- 상속은 강한 결합을 야기할 수 있고, 상위 클래스에 변경이 발생하면 하위 클래스에도 영향을 미칠 수 있기 때문.
2. 인터페이스를 통한 의존
- 가장 일반적이고 유연한 방법
- 추상적인 인터페이스를 정의하고, 구체적인 클래스들이 이 인터페이스를 구현하여 의존 관계를 형성한다.
1
2
3
4
5
6
7
8
9
10
11
| interface Logger {
void log(String message);
}
class FileLogger implements Logger {
// 파일로 로그를 기록하는 로직
}
class ConsoleLogger implements Logger {
// 콘솔에 로그를 출력하는 로직
}
|
3. 생성자 주입을 통한 의존
- 객체 생성 시 의존하는 객체를 생성자를 통해 주입한다.
- 장점: 객체 생성 시 의존성을 명확하게 드러난다.
1
2
3
4
5
6
7
| class MyClass {
private Logger logger;
public MyClass(Logger logger) {
this.logger = logger;
}
}
|
3. Setter 주입:
- 객체 생성 후 setter 메소드를 통해 의존하는 객체를 주입
- 장점: 생성자 주입보다 유연하지만, 의존성이 명확하지 않을 수 있다.
- 사양되는 방식
4. 인터페이스 메소드 파라미터:
- 메소드 호출 시 필요한 의존 객체를 파라미터로 전달합니다.
- 필드 주입: 필드에 직접 의존 객체를 할당한다.
- 단점: 결합도가 높아져 유지보수가 어려울 수 있다.
디미터 법칙
- 객체 지향 프로그래밍에서 객체 간의 결합도를 낮추고 유지보수성을 높이기 위한 설계 원칙 중 하나다.
- “최소 지식의 원칙(Principle of Least Knowledge)”이라고도 불리며, 간단히 말해 “한 객체는 자신과 직접적인 관계가 있는 객체에 대해서만 알아야 한다”는 것을 의미한다.
디미터 법칙 위반 예시
1
2
3
4
5
6
7
8
| class Customer {
private Order order;
// ...
public String getShippingAddress() {
return order.getShippingAddress().getCity();
}
}
|
왜 알아야 하는가?
- 낮은 결합도: 객체 간의 의존성을 줄여 하나의 객체에 변경이 발생했을 때 다른 객체에 미치는 영향을 최소화한다.
- 높은 유지보수성: 코드 변경 시 발생할 수 있는 문제를 줄이고 시스템의 안정성을 높인다.
- 테스트 용이성: 각 객체를 독립적으로 테스트하기 쉽도록 만들어준다.
디미터 법칙을 지키려면
- 메소드 체이닝 줄이기: . 연산자를 남발하여 여러 객체를 연속적으로 호출하는 것을 피한다.
- 중간 객체를 통해 값 전달: 필요한 값을 직접 가져오는 대신, 해당 값을 제공하는 메소드를 호출하여 값을 전달받는다.
- 객체의 책임 분리: 각 객체가 명확한 책임을 가지도록 설계하여 객체 간의 의존성을 줄인다.
reference