객체에 대해 생각할 때, 생각 해야 되는 것은 '역할' 과 '책임' 이다. 하지만 이 말을 달리한다면, '어떻게 의존성을 관리를 하는가'라고 한다. 어떻게 의존성을 관리하냐에 따라서 객체의 역할과 책임이 달라진다고 한다.
그렇다면 '의존성'이라는 것 정확히 무엇일까?
객체지향에서 의존성은 크게 클래스(Calss) 사이의 의존성과, 패키지(Package) 사의의 의존성으로 나눌 수 있다.
우선 클래스 사이의 의존성에 대해 알아보자.
Class 사이의 의존성은 크게 네 가지로 분류가 된다.
▶️ 연관 관계 → 'A 에서 B에 대해 알 수가 있다.' 라는 의미 이다. 아래 코드와 같이 객체 참조가 직접 있는 경우.
Class A {
private B b;
}
▶️ 의존 관계 → 일시적으로 관계를 맺는 경우. 연관 관계에서는 A가 B에 대해 영구적으로 알 수 있지만, 의존 관계는 일시적으로 A가 B를 알고있다.
Class A {
pubilc method(B b){
return new B();
}
}
▶️ 상속 관계 → 말 그대로 상속을 받는 경우, 상속을 받아 B의 클래스의 필드와 메소드를 참조 함. 이때 B가 변경될 경우 A도 변경 될 수 있음.
Class A extends B{
}
▶️ 인터페이스를 implement하는 관계 → 상속관계과 비슷하지만, 상속은 클래스의 구현이 바뀌면 영향을 받는다. 하지만 실체화 관계는 인터페이스의 시그니처가 바뀌었을 경우에 영향을 받는다.
Class A implements B{
}
패키지 사이의 의존성은 패키지안에 포함된 클래스들 사이에서의 의존성을 말한다.
즉 클래스 파일에서 import를 이용하여 다른 클래스 파일에 의존을 할 수 있다.
import java.util.Scanner;
// 코드 상단에 이와같은 코드가 있는,
// 경우 그 코드는 Scanner 클래스에 의존 하는 코드가 된다.
의존성 관리를 위한 규칙들
양방향 의존성을 피하는 당연하다. 두 클래스가 서로 의존을 하고 있다면, 한 클래스만 수정하도 다른 클래스까지 영향이 끼치게 된다. 예를 들어보자
class A {
private B b;
public void setA(B b) {
this.b = b;
this.b.setA(this);
}
}
class B{
private A a;
public void setA(A a) {
this.a = a;
}
}
이와 같이 서로 다른 클래스가 서로를 양방향으로 의존하고 있는 경우가 있다.
A 클래스에서 set메서드를 호출할 때, B 클래스의 setA 메소드를 호출 해야하기 때문에, A와 B사이의 관계를 항상 동기화 해야 한다고 한다. 따라서 이런 방식은 지양해야한다고 한다. 이런 방식은 단방향 의존으로 바꿔줘야 한다.
class A {
private B b;
public void setA(B b) {
this.b = b;
}
}
class B{
}
이런 식으로 바꿔준다면, A를 클래스의 set을 호출해도, B 클래스의 set 메소드를 호출 하지 않아도 된다.
일대다 (One To Many) 보다 다대일(Many To One) 방식으로 설계를 해야한다.
//One To Many의 예시
class A {
private Collection<B> bs;
//Collection 이나 Set 같은 것들을 인스턴스 변수로 가지면,
//다양한 이슈가 발생 할 수 있음
}
class B {
}
//Many - To - One(다대일) 예시
class A {
}
class B {
private A a;
}
의존성이 없을 경우는 의존성을 제거하는 것이다.
//의존성이 있는 경우
class A {
private B b;
}
class B {
}
아래와 같은 코드는 의존성이 없다.
//의존성이 없는 경우
class A {
}
class B {
}
패키지 사의 양방향 의존성은 사이클이라고 한다. 3개의 패키지, A,B,C 가 있다고 하자. 이 패키지는 각각 A←B, B←C, C ← A 의 관계가 있다.
이런 경우가 사이클이다.
//A 패키지
import B
//B 패키지
import C
// C 패키지
import A