의존성과 결합도에 대한 정확한 의미를 이해해본다.

의존성(Dependency)

  • A 모듈이 동작하려면 B 모듈이 필요한 경우
    • OO에서 모듈 == 클래스
  • 클래스 A가 클래스 B에 의존

의존성이 있으면 잘못된 OO 설계다? (code smell)

  • 잘못된 말이다.
  • 의존성이 있어야 좋은 설계
  • 각 클래스의 목적이 뚜렷하다는 말
  • 캡슐화가 잘 되어 있다는 말
  • 클래스 재사용이 가능하다는 말
  • 의존성을 완전히 없애려면 프로그램 전체를 함수하나에 작성하면 됨

왜 의존성이 나쁘다고 생각할까?

  1. 결합도라는 용어와 혼용해서 사용
  2. 용어 자체의 의미를 생략해서 잘못 사용

즉, 특정 상황을 설명하는 다른 단어가 있음에도 불구하고 퉁치고 넘어가는 경향이 있다. 제대로된 용어에 대한 정의를 알아보고 제대로 사용해보자.

결합도(Coupling)

  • 두 소프트웨어 모듈 간에 상호 의존성 정도
    • 상호 참조
    • 각각이 독자 생존이 불가

OO에서 논하는 결합도

A가 B를 의존하는 상황에서 B변경시 동작하는가?

  1. A의 내부를 변경안해도 됨
    • A가 B에 의존하나 정도가 높지 않음 (A depends lightly on B)
    • 결합도가 낮음 (loose coupling)
  2. A의 내부를 변경해야 함
    • 의존도가 높음 (A depends heavily on B)
    • 결합도가 높음 (tight coupling)

정리: B 코드 변경시, A 코드도 변경해야 하면 결합도가 높다.

높은 결합도를 의미하는 표현들

  1. A와 B가 결합되어 있다. (A and B are coupled.)
    • "결합" 자체는 나쁜의미를 지니고 있지 않음
    • "결합 == 의존"의 의미를 필연적으로 내포하기 있기 때문
      • 의존 자체도 나쁜 것이 아님
    • 높은 결합도는 나쁜 것.
    • 하지만, 결합되어 있다는 말로 사용하는 것이 보통 높은 결합도를 생각하고 말하는 경우가 많음
  2. A가 B에 의존한다. (A depends on B.)
  3. A와 B사이에 의존성이 있다. (There is a dependency between A and B.)

이렇게 변경되어야 한다.

  1. A가 B에 심하게 의존한다. (A depends heavily on B.)
  2. A와 B의 결합도가 높다. (A and B are tightly coupled.)

낮은 결합도를 의미하는 표현들

  1. A와 B가 결합되어 있지 않다. (A and B are decoupled.)

이렇게 바꿔야 한다.

  1. A와 B의 결합도가 낮다. (A and B are loosely coupled.)
  2. A가 B에 미미하게 의존한다. (A depends lightly on B.)

결합도를 줄이는 것을 의미하는 표현들

  1. A와 B의 결합관계를 제거한다. (Decouple A and B.)
  2. A와 B의 의존성을 제거한다. (Break dependencies between A and B.)

이렇게 바꿔야 한다.

  1. A와 B의 결합도를 줄인다. (Reduce coupling between A and B.)

의존성 주입 (Dependency Injection)

  • 클래스 간에 의존성은 있는 것이 좋다.
  • 그런데, 이 의존성이 강하게 엮여 있는 경우 좋지 않다.
  • A가 B에 의존할 때, B코드를 변경할 경우 A도 변경해야 한다면 좋지 않다.
  • 보통 이런 경우 A가 B에 의존할 때, B자체를 생성해서 사용하는 경우 발생한다.
  • 즉, 의존성을 자체적으로 해결하고 있는 것.
  • 이런 경우 이 의존성 자체를 바깥에서 받아올 수는 없을까?
  • 그것이 의존성 주입이다.

DI 용어 주의

다음과 같은 것들을 의미하기 위해 사용할 수도 있다.

  1. 의존성 주입 (Dependency Injection)
  2. 의존성 주입 컨테이너 (DI Container)
  3. 의존성 역전 (Dependency Inversion)

의존성 주입 종류

  1. 생성자 주입
  2. setter 주입
    • setter 함수하나 만들고 그 함수를 통해 넣어주는 방식

setter 주입의 문제

  • 생성자 주입을 없앨 수는 있으나..
  • 개체의 유효한 상태에 해가 될 수 있다.
  • 개체는 생성시 부터 유효한 상태를 가져야 한다.
  • setter가 마구 추가된다면? 캡슐화가 깨질 수도..

의존성 주입의 불편함

  • 로봇을 만드는데, 머리를 전달해줘야만 만들어지는 것이 이상하다.
  • 즉, 하나의 개체로 생각하고 로봇() 이렇게 쓰고 싶은데,
  • 머리는 뭐고, 다리는 뭐고 이런식으로 전달하는 것이 불편하다.
  • 하나의 완제품으로 생성해서 쓰고 싶다!

DI를 통해 얻은 것과 잃은 것

  • 얻은 것
    • 결합도를 낮춤
    • Head의 생성자가 바뀌어도 Robot을 바꿀 필요가 없음
    • Head가 바뀌면, 이 클래스만 따로 컴파일하여 배포 가능
  • 잃은 것
    • 편의성 - 로봇을 생성하려면 머리도 알아야 함
    • 프로그래머의 원래 의도를 잘 보여주는 클래스
      • 머리 넣을 필요없이 완제품이야~ 라는 의도를 보여줄 수 없음

장단점이 존재한다. 무조건적으로 결합도를 낮춰야 한다는 주장은 있을 수 없다. 한 방식이 무조건 옳다고 할 수 없다.

복잡한 시스템에서 문제인 커플링

  • 간단한 구조에서는 DI의 실익이 크지 않다.
  • 하지만 복잡한 시스템에서는 다르다.

디커플링은 유연성/재사용성을 높임

  • 추상화는 유연성/재사용성을 높임
  • 디커플링도 마찬가지
  • 미래의 변화에 대비되어 있다는 의미
  • 하지만 그로 인한 단점도 있다.

단점

  1. 직관적이지 못하다.
  2. 내부를 알아야 좋은 경우도 있다.
import java.util.Collection;

public final class DataSource {
    public void mergeTo(Collection<Data> dataset) {
        // source로 부터 모든 데이터를 가져와 "중복 없이" dataset에 반영
    }
}
  • 위와 같은 상황이라면, 이 dataset이 어떤 클래스냐에 따라 차이가 생긴다.
    • Set: 중복 체크 필요없음, add만 호출
    • ArrayList: contains로 중복검사, 아닌 경우만 add
    • 정렬된 ArrayList: 이진 탐색으로 중복 검사, 아닌 경우만 add
  • 이런 경우들을 모두 아우르기 위해서는 가장 느리고 일반적인 방법으로 처리해야 한다.
    • contains로 중복검사, 아닌 경우만 add
  • 즉, 성능이 중요한 경우에는 일반화/추상화가 비효율적일 수 있다.
    • 구체 타입이 들어오면 최적화가 가능
  1. 구현을 알아야 문제해결이 가능
    • 될 것이라 생각하고 작성했는데, 알고보니 내부 구현사항에 문제가 있는 경우

Reference

profile
Goal, Plan, Execute.

0개의 댓글