본문 바로가기
Computer Science

[CS] 결합도와 응집도

by DuncanKim 2022. 7. 12.
728x90

[CS] 결합도와 응집도

 

좋은 도구를 가지고 요리를 하는 방법은 너무나도 많다. 직접 화로에 고기를 굽는 방법도 있고, 스테인리스 팬에 고기를 튀기듯 굽는 방법도 있다. 그런데 멀쩡히 가스레인지가 있음에도, 화로를 피워서 그 위에 스테인리스 팬을 두고 고기를 굽는 것은 정말 비효율적인 방법이라고 할 수 있다. 도구를 올바르게 사용하는 법이 있듯, 객체 지향의 특성을 올바르게 사용하는 방법, 즉 객체 지향 언어를 이용해 객체 지향 프로그램을 올바르게 설계해나가는 방법이 있다.

 

많이 알려진 원칙은 SOLID(객체 지향 설계 5원칙)이다. 이것을 알아보기 이전에 결합도와 응집도를 먼저 알아보고자 한다. 다섯 가지의 원칙들도 결합도를 낮추고 응집도를 높이는 과정에서 모두 만들어진 것이라고 볼 수 있다. 그렇기 때문에, 그 뿌리, 결합도와 응집도를 먼저 알아본다.

 

 

1. 코드를 구현할 때, 가장 중요한 것들.

 

가독성이 제일 중요하다.
응집도는 높아야 하고, 결합도는 낮아야 한다.
중복을 제거하라
변화하는 것을 변화하지 않는 것으로부터 분리하라.

 

개발자가 코드라인을 써내려 갈 때, 가독성을 생각하면서 짜내려 갈 것이다. 만약 그 개발자가 객체 지향적 프로그래밍을 하고 있다면, 그와 동시에 응집도를 높이고 결합도를 낮추는 선택을 할 것이다. 그렇게 해야 모듈 간의 의존성이 줄어서 하나의 모듈을 수정하는 것에 다른 모듈이 영향을 받지 않고 쉽게 유지보수를 할 수 있으니까 말이다.

 

2. 결합도와 응집도

 

: 결합도는 낮을 수록, 응집도는 높을 수록 좋은 프로그램이다.

 

<결합도가 높은 좋지 않은 사례>

// 문제 : 아래와 같이 출력되도록 해주세요.
// 조건 : 조건문을 사용하지 않고 처리해주세요.
// 조건 : 전사 클래스만 만들어주세요.

public class Attack {
    public static void main(String[] args) {
        전사 a전사 = new 전사();

        a전사.공격();
        // 칼(으)로 공격합니다.
        // 데미지 : 78

        a전사.창_모드로_변경();
        a전사.공격();
        // 창(으)로 공격합니다.
        // 데미지 : 80

        a전사.지팡이_모드로_변경();
        a전사.공격();
        // 지팡이(으)로 공격합니다.
        // 데미지 : 12
    }
}

// <결합도가 높아진다 하나의 클래스에 기능이 여러 가지가 혼재되어 있다.>
class 전사 {
    String 무기이름;
    int 데미지;
    전사() {
        무기이름 = "칼";
        데미지 = 78;
    }

    void 공격() {
        System.out.println(무기이름 + "(으)로 공격합니다.");
        System.out.println("데미지 : " + 데미지);
    }

    void 창_모드로_변경() {
        this.무기이름 = "창";
        this.데미지 = 80;
    }

    void 지팡이_모드로_변경() {
        this.무기이름 = "지팡이";
        this.데미지 = 12;
    }
}

 

 

<결합도를 낮추고 응집성을 올린 버전>

 

상위클래스에서 어떻게 할 지를 계획해놓고, 자식클래스에서는 데이터를 넣고 바꾸고 하는 기능만 담당한다.

// 문제 : 아래와 같이 출력되도록 해주세요.
// 조건 : 조건문을 사용하지 않고 처리해주세요.
// 조건 : `a무기` 인스턴스 변수를 이용해서 풀어주세요.
// 조건 : 칼, 창, 지팡이 클래스를 만들어주세요.

class Main {
  public static void main(String[] args) {
    전사 a전사 = new 전사();

    a전사.공격();
    // 칼(으)로 공격합니다.
    // 데미지 : 78

    a전사.창_모드로_변경();

    a전사.공격();
    // 창(으)로 공격합니다.
    // 데미지 : 80

    a전사.지팡이_모드로_변경();

    a전사.공격();
    // 지팡이(으)로 공격합니다.
    // 데미지 : 12
  }
}

class 전사 {
  무기 a무기;

  전사() {
    a무기 = new 칼();
  }

  void 공격() {
    a무기.사용();
  }

  void 창_모드로_변경() {
    a무기 = new 창();
  }

  void 지팡이_모드로_변경() {
    a무기 = new 지팡이();
  }
}

class 무기 {
  String 이름;
  int 데미지;

  void 사용() {
    System.out.println(이름 + "(으)로 공격합니다.");
    System.out.println("데미지 : " + 데미지);
  }
}

class 칼 extends 무기 {
  칼() {
    이름 = "칼";
    데미지 = 78;
  }
}

class 창 extends 무기 {
  창() {
    이름 = "창";
    데미지 = 80;
  }
}

class 지팡이 extends 무기 {
  지팡이() {
    이름 = "지팡이";
    데미지 = 12;
  }
}

 

코드가 길어져서 좋지 않은 것처럼 보일 수도 있다. 그렇지만, 코드는 확장되고 더 개발되기 때문에, 나중을 고려한다면 개선 버전처럼 만드는 것이 필요하다.

 

 

<기능 별로 확실히 분리시킨 클래스>

// 문제 : 온라인 게임을 구현해주세요.
// 조건 : 전사객체를 만들고 순서대로 칼, 창, 지팡이로 공격하게 해주세요.
// 단 : 전사는 무기라는 것이 존재한다는 것은 알지만 칼, 창, 지팡이에 대해선 몰라야 합니다.

public class Game {
    public static void main(String[] args) {
        전사3 멋있는_전사 = new 전사3();
        멋있는_전사.칼_공격();
        멋있는_전사.창_공격();
        멋있는_전사.지팡이_공격();
        멋있는_전사.전설지팡_공격();
    }
}
/* ==출력==    
칼(으)로 공격합니다.
데미지 : 78
창(으)로 공격합니다.
데미지 : 80
지팡이(으)로 공격합니다.
데미지 : 12
전설의_지팡이(으)로 공격합니다.
데미지 : 120
*/

class 전사3 {
    무기2 전사의_무기;

    void 칼_공격(){
        전사의_무기 = new 칼2();
        전사의_무기.공격();
    }

    void 창_공격(){
        전사의_무기 = new 창2();
        전사의_무기.공격();
    }
    void 지팡이_공격(){
        전사의_무기 = new 지팡이2();
        전사의_무기.공격();
    }

    void 전설지팡_공격(){
        전사의_무기 = new 전설의_지팡이();
        전사의_무기.공격();
    }
}

abstract class 무기2{
    int 데미지;
    String 이름;

    void 공격() {
        System.out.printf("전사가 %s(으)로 공격합니다.\n", 이름);
        System.out.printf("데미지 : %d\n", 데미지);
    }
}

class 칼2 extends 무기2{
    칼2(){
        이름 = "칼";
        데미지 = 78;
    }
}

class 지팡이2 extends 무기2{
    지팡이2(){
        이름 = "그냥 지팡이";
        데미지 = 12;
    }
}

class 창2 extends 무기2{
    창2(){
        이름 = "창";
        데미지 = 80;
    }
}

class 전설의_지팡이 extends 무기2{
    전설의_지팡이(){
        이름 = "전설의 지팡이";
        데미지 = 120;
    }
}

 

 

 

리팩토링의 과정

 

<원문>

// 문제 : 구성(전사는 무기로 구성된다.)을 사용하여 중복을 제거해주세요.
// Main 클래스는 수정불가능

class Main {
    public static void main(String[] args) {
        전사타입A a전사타입A = new 전사타입A();
        a전사타입A.공격();

        전사타입B a전사타입B = new 전사타입B();
        a전사타입B.공격();

        전사타입C a전사타입C = new 전사타입C();
        a전사타입C.공격();

        전사타입D a전사타입D = new 전사타입D();
        a전사타입D.공격();
    }
}

class 전사타입A extends 전사 {
    void 공격() {
        System.out.println("칼로 공격");
    }
}
class 전사타입B extends 전사 {
    void 공격() {
        System.out.println("칼로 공격");
    }
}
class 전사타입C extends 전사 {
    void 공격() {
        System.out.println("활로 공격");
    }
}
class 전사타입D extends 전사 {
    void 공격() {
        System.out.println("활로 공격");
    }
}

sout이 ‘중복’으로 쓰이고 있다. 공격() 메소드도 하나만 존재하면 충분하다. 타입별로 칼을 들고 있는 지 활을 들고 있는지만 설정을 해주고, 공격은 다른 클래스를 두고 각각의 타입별로도 클래스를 분리시키는 것이 필요하다.

 

 

<개선코드>

// 문제 : 구성(전사는 무기로 구성된다.)을 사용하여 중복을 제거해주세요.
// Main 클래스는 수정불가능

public class Game2 {
    public static void main(String[] args) {
        전사타입A a전사타입A = new 전사타입A();
        a전사타입A.공격();

        전사타입B a전사타입B = new 전사타입B();
        a전사타입B.공격();

        전사타입C a전사타입C = new 전사타입C();
        a전사타입C.공격();

        전사타입D a전사타입D = new 전사타입D();
        a전사타입D.공격();
    }
}

abstract class 전사4 {
    무기4 a무기;
    void 공격() {
        a무기.사용();
    }
}

class 전사타입A extends 전사4 {
    전사타입A() {
        a무기 = new 칼4();
    }
}
class 전사타입B extends 전사4 {
    전사타입B() {
        a무기 = new 칼4();
    }
}
class 전사타입C extends 전사4 {
    전사타입C() {
        a무기 = new 활4();
    }
}
class 전사타입D extends 전사4 {
    전사타입D() {
        a무기 = new 활4();
    }
}

// abstract => 무기 클래스는 아쉽게도 `new 무기();` 될 일은 없고 리모콘 제작용으로만 쓰인다.
abstract class 무기4 {
    // abstract => 사용 메서드는 슬프게도 리모콘 버튼용으로만 쓰인다.
    abstract void 사용();
}

class 칼4 extends 무기4 {
    void 사용() {
        System.out.println("칼로 공격");
    }
}

class 활4 extends 무기4 {
    void 사용() {
        System.out.println("활로 공격");
    }
}

 

728x90

'Computer Science' 카테고리의 다른 글

[C언어] 문자열을 활용한 여러 함수 만들기  (0) 2022.07.13
[C언어] 문자열 활용하기  (0) 2022.07.12
[CS] 테스트와 TDD  (0) 2022.07.09
[CS] 객체지향 디자인패턴  (0) 2022.07.07
[CS] 프로세스와 스레드  (0) 2022.07.06

댓글