결합도와 의존성 주입 (011-001~005)
서술 개념(의존성 vs 결합도·판정 기준·DI 정의)은 ANKI 참고: 24-의존성과 결합도는 다른 개념 이후 카드들
다음 코드의 결과는? 두 클래스의 결합도 판정은? (부품 클래스의 생성자 변경)
public class Head {
private float fovAngle;
// 원래는 매개변수 없는 기본 생성자였는데, fovAngle 변수가 추가되며 교체됨
public Head(float fovAngle) {
this.fovAngle = fovAngle;
}
}
public class Robot {
private Head head;
public Robot() {
this.head = new Head();
}
}컴파일 오류, tight coupling
Head클래스의 기본 생성자가 사라져서Robot클래스의new Head()에서 컴파일 오류 →Robot클래스의 코드를 수정해야만 함- 결합도 판정법: ==의존 대상(
Head)을 실제로 수정해보고== 내(Robot) 코드를 고쳐야 하는지 확인- 고쳐야 함 → tight coupling / 안 고쳐도 됨 → loose coupling
- 주의:
Robot클래스가Head클래스에 의존하는 것 자체는 문제가 아님 — 기능 분리·캡슐화·재사용성이 좋은 설계. 나쁜 건 의존이 아니라 높은 결합도
위 Robot 클래스를 loose coupling으로 고쳐라 (코드 작성)
public class Robot {
private Head head;
public Robot(Head head) {
this.head = head;
}
}
// 사용하는 쪽
Head head = new Head(90.0f);
Robot robot = new Robot(head);Head개체를 외부에서 생성해 생성자의 매개변수로 전달 — 의존성 주입(DI) 중 생성자 주입- 효과:
Robot클래스가Head클래스의 생성(내부 구현)에 관여하지 않음 → 이후Head클래스의 생성자가 또 바뀌어도Robot클래스는 무변경 (loose coupling) - 의존성이 제거된 게 아님 —
Robot클래스는 여전히Head클래스에 의존. 결합도만 줄인 것
다음 코드에서 모든 로봇이 SmartHead를 쓰게 하려면 Robot 클래스를 수정해야 하나? 결합도를 더 줄이는 방법은? (상속 관계에서의 결합도)
public abstract class Head {
public abstract void pickEnemy();
}
public class SimpleHead extends Head {
@Override
public void pickEnemy() {
}
}
public class SmartHead extends Head {
@Override
public void pickEnemy() {
}
}
public class Robot {
private SimpleHead head;
public Robot(SimpleHead head) {
this.head = head;
}
}수정해야 함 — 멤버 변수·생성자 매개변수가 SimpleHead 타입이라 SmartHead로 바꿀 때마다 Robot 클래스 코드 변경 필요
public class Robot {
private Head head;
public Robot(Head head) {
this.head = head;
}
}- 부모(일반화된) 타입으로 멤버 변수·매개변수를 선언 → 어떤 자식 개체를 주입해도
Robot클래스는 무변경 — 다형성으로 결합도를 줄임 - 추상화를 잘했다는 가정하에 부모 타입은 바뀔 일이 적음 — 자식 교체가 잦을수록 효과 큼
- 부모가 인터페이스여도 똑같음 — 자식이 아니라 부모 인터페이스에 의존
- 이때도 의존성은
Head클래스에 여전히 있음 — 없앤 게 아니라 줄인 것
서술형 (공식 연습 9번 그대로)
a) 결합도가 높은 코드란 무엇을 의미하는지 다섯 문장 이내로 설명하세요. b) 두 클래스 간의 결합도를 줄이는 방법을 최소 2개 설명하세요.
모범답안
a) A가 B에 의존하는 상황에서, B를 변경했을 때 A의 내부 코드까지 수정해야만 프로그램이 제대로 동작하는 상태를 결합도가 높다(tight coupling)고 함. 즉 결합도는 의존 대상의 변경이 나에게 전파되는 정도임. 의존성 자체는 기능이 잘 분리됐다는 뜻이라 좋은 설계이고, 나쁜 것은 높은 결합도임. 예를 들어 A가 내부에서 new B()로 개체를 직접 생성하면, B의 생성자가 바뀔 때 A의 코드도 고쳐야 하므로 결합도가 높음.
b) 최소 2개:
- 의존성 주입(생성자 주입) — 의존하는 개체를 외부에서 생성해 생성자의 매개변수로 전달. 내가 상대의 생성(내부 구현)에 관여하지 않게 되어 상대의 생성자·구현 변경에 영향을 안 받음
- 부모(일반화된) 타입·인터페이스에 의존 — 멤버 변수·매개변수를 구체 클래스 대신 부모 타입으로 선언하고 자식 개체를 주입. 다형성 덕분에 구체 클래스를 교체해도 내 코드는 무변경
다음 각 명제의 O/X는?
-
의존성이 있는 설계는 나쁜 설계다
-
loose coupling은 의존성이 없는 상태를 의미한다
-
의존성 주입을 사용하면 두 클래스 사이의 의존성이 제거된다
-
setter 주입은 생성자 주입과 달리 캡슐화 원칙에 위배될 수 있다
-
X — 의존성이 있다는 건 기능이 잘 분리됐다는 뜻 (목적 뚜렷, 캡슐화, 재사용성) → 좋은 설계. 나쁘다는 인식은 결합도와 의존성을 같은 개념으로 착각한 오해
-
X — 의존성은 있지만 상대의 변경에 영향을 적게 받는 상태. 애초에 의존은 할 수밖에 없음
-
X — 개체 생성을 외부로 옮길 뿐 의존성은 그대로. 제거(remove)가 아니라 감소(reduce)가 정확한 표현 (decoupling은 OO에 없는 개념 — loose/tight 두 가지뿐)
-
O — setter 주입은 개체 생성 이후에 의존 개체를 넣으므로 생성 시 개체의 상태를 유효하게 하는 캡슐화 원칙에 위배됨