디자인 패턴(구)/헤드 퍼스트 디자인 패턴

[디자인 패턴] 1장 - 디자인 패턴은 왜 필요할까? + 디자인 원칙

공대키메라 2023. 2. 12. 10:12

최근들어 필자는 더욱 더 기본에 충실한 개발자가 되기로 하였다.

 

그래서 다시 디자인 패턴을 공부하기로 했다.

 

사실 필자는 디자인 패턴 섹션이 있는데, 또 다시 공부하는 이유는

 

  1. 그때는 수준이 낮은 상태에서 공부하려니 기본적인 이해자체가 힘들었다.
  2. 보여주기식 공부를 한 것 같다. 
  3. 다시 글을 보니 내가 보기에 이해가 너무 힘들고 딱딱하다.

고로, 공부를 다시 진행하고, 이를 정확하고 이해가 쉬운 방식으로 정리하기로 결심했다.

 

이를 진행하면서 참고하는 책은...

 

헤드퍼스트 디자인 패턴!

 

GoF책은 디자인 패턴의 고전이고 정말 좋은 책이라지만... 2005년 이후로는 이 책이 훨씬 더 많이 팔렷고 부동의 1위라는 것이 헤드퍼스트 디자인패턴의 설명! (아기다리고기다리던책!) 이미 나는 영업당하고 말았다...

 

하여간 이 책을 기점으로, 학습한 내용을 토대로 나만의 예제를 생성할 것이다.

 

그리고 도움이 될 만한 것들은 살을 더 붙여서 내가 다시 보았을 때 아따 참 정리 잘했다~ 

 

하는 정도? 까지 욕심을 부려볼 생각이다.

 

그리고 디자인 패턴을 위한 원칙은 공부를 진행하면서 추가할 것이다. (밑에 읽어보면 알 것이다!@)

 

 

인간의 맥락(Context) 기억은 오랜 기간 생물이 진화하면서, 그리고 인간이라는 종으로 진화하면서 생긴 기억법이다.

 

특히, 어떤 것을 떠올리고자 할 때 전후 문맥을 살피고 하나 하나 기억하다보면 기억을 떠올리기가 쉽다고 한다.

 

내가 공부하는 책인 '헤드 퍼스트 디자인 패턴' 에서는 학습 효과를 극대화하고자 재미있는 이야기와 상황을 부여한다. 

 

필자도 이를 위해 이야기로 만들어서 학습을 스스로 진행하고자 한다. 

 

참고한 내용은 다음과 같다.

https://m.hanbit.co.kr/media/books/book_view.html?p_code=B6113501223 

https://refactoring.guru/ko/design-patterns

https://tecoble.techcourse.co.kr/post/2020-05-18-inheritance-vs-composition/


1. 디자인 패턴이란?

간단하게 디자인 패턴의 뜻에 대해 알고가자.

디자인 패턴들은 소프트웨어 디자인 과정에서 자주 발생하는 문제들에 대한 일반적인 해결책들입니다. 이는 당신의 코드에서 반복적으로 되풀이되는 디자인 문제들을 해결하기 위하여 맞춤화할 수 있는 청사진들을 미리 만들어 놓은 것과 같습니다.

출처 : https://refactoring.guru/ko/design-patterns

2. 디자인 패턴이 필요한 이유 - 문제제기

노동자 시뮬레이션을 통해 예를 들도록 하겠다.

 

다음 필자가 그린 그림을 보자.

 

 

여러 나라의 노동자가 한국에서 온다고 한다면, 자국 노동자, 동남아 출신 노동자 말고도 많은 노동자가 있을 것이다.

 

만약 임금이 모종의 이유로 다르게 산정이 된다면, 오버라이드를 해서 새롭게 정의하면 된다.

 

고로, 동일한 기능을 노동자들이 하기에 슈퍼클래스인 Worker 를 각각 상속하고 있다.

 

 

그런데 말이다...

 

여기서 새로운 기능을 Worker 클래스에 추가하고자 한다.

 

모든 노동자에게 쉴 수 있는 기능을 부여하는 것이다! (오예! 우리는 일하는 기계가 아니라고!)

 

 

 

그런데... 이런!

 

과거에는 몰랐는데 현대 사회에서는 쉬지 않는 노동자도 있을수가 있다.

 

기계가 그렇다! (잉 치킹 치킹)

 

일을 하는 주체가 다르니 사람처럼 쉬는게 아니라 가동을 중단하는것을 쉰다고 표현하기도 그렇다.

 

그렇게 되면 Robot 클래스에 적합하지 않은 행동이 추가되고 만다.

 

심지어 임금도 필요가 없다! (이것도 따져보면 투자금이라는것이 임금과 비슷한 개념일 수는 있지만...)

 

이것을 코드로 보면 다음과 같다.

 

// Worker.java

public abstract class Worker {

    void work() {
        System.out.println("일한다!");
    }

    void rest() {
        System.out.println("쉰다!");
    }

    void payment() {
        System.out.println("임금 일당 20만원 받는다!");
    }

}

// EastSouthAsianWorker.java
public class EastSouthAsianWorker extends Worker{
}

// KoreanWorker.java
public class KoreanWorker extends Worker{
}

// Robot.java
public class Robot extends Worker{
    @Override
    void rest() {
        System.out.println("기계는 쉬지 않는다.");
    }

    @Override
    void payment() {
        System.out.println("기계는 임금이 필요없다.");
    }
}

 

분명 처음에는 코드를 재사용한다는 점에서 상속을 잘 한거 같은데 유지보수를 생각하면 별로 안좋은 것 같다.

 

Robot과 같이 예외사항이 많이 생길것 같기 때문이다.

 

그러면 특정 형식의 Worker에만 임금을 지불하거나 쉴 수 있도록 수정했다.

 

 

그런데 이렇게 하면 도대체 편한게 없다. 중복코드가 너무 많기 때문이다!

 

이러한 문제를 해결하기 위해서 

 

=> 소프트웨어를 고칠 때 기존 코드에 미치는 영향을 최소한으로 줄이면서 작업할 수 있는 방법에 대한 고민!

 

3. 소프트웨어는 늘 변한다.

위의 상황처럼 소프트웨어는 요구사항에 맞게 변경을 해야한다.

 

만약 변경에 취약한 프로그램이라면 유지보수하는데 정말로 힘들것이다!

 

그러면 어떤 원칙을 지켜켜야지 더 나은 설계가 될까?

 

디자인 패턴 원칙 1 - 애플리케이션에서 달리지는 부분을 찾아내고, 달라지지 않는 부분과 분리한다. 

많은 사람들이 캡슐화에 대해 많이 들어봤을 것이다.

 

* 캡슐화: 만일의 상황(타인이 외부에서 조작)에 대비해 외부에서 특정 속성이나 메서드를 사용할 수 없도록 숨겨놓는 것.

 

 애플리케이션에서 달라지는 부분, 즉 바뀌는 부분은 따로 뽑아 캡슐화하여 변하지 않는 부분과 분리한한다.

 

그렇게 되면 코드 변경 과정에서 의도치 않게 발생하는 일을 줄여 유연성 향상된다!

 

디자인 패턴 원칙 2 - 구현보다는 인터페이스에 맞춰서 프로그래밍한다.

인터페이스를 적극 활용하게 된다면 얻는 이점은 매우 많다.

 

우선, 변수를 선언하는 클래스에서 실제 객체의 형식을 몰라도 된다는 점이다.

 

실제 실행 시에 쓰이는 객체가 코드가 고정되지 않도록 상위 형식에 맞춰 프로그래밍해서 다형성을 활용해야 한다는 것은

 

객체지향에서 역할에만 충실하게 설계한것과 같다고 볼 수 있다.

 

또 다른 장점은 상속을 쓸 때 떠앉게 되는 부담(e.g : 필요없는 변수나 기능들도 상속하게 됨)을 없애고 재사용의 장점을 누릴 수 있다. 

 

마지막으로 행동을 다른 클래스에 위임하게 되므로 동적으로도 행동을 지정할 수 있다.

 

그러면 위의 코드들을 다시 이렇게 끔 수정이 가능하다.

 

 

디자인 패턴 원칙 3 - 상속보다는 구성을 활용한다.

위의 이야기를 빌리자면, 현재 일한다는 부분은 모든 노동자라면 공통적으로 가지고 있으니 이것을 상속하는것은 합리적이다.(변경이 없으니 크게 걱정을 안해도 된다!)

 

허나 임금을 지불받거나, 쉰다는 것은 일하는 주체에 따라서 다르게 작동할 수 있다.

 

고로, 각 일꾼들은 임금을 받는 방식과 쉬는 방식에 대한 행동들을 위임받게끔 작성한다.

 

그렇게 되면 변경이 없는 work 부분과 변경이 잦은 rest, pay 부분으로 나뉘는데 이 두 무리를 합치는 것을 구성이라고 한다.

 

다르게 설명하자면, 기존 클래스가 새로운 클래스의 구성요소로 쓰인다는 것이다.

 

예시에서 행동을 상속받는 대신, 올바른 행동 객체로 구성(composition)되어 행동을 부여받도록 작성하면 된다. 

(rest 와 pay를 상속하는게 아닌 interface를 사용하면 된다.)

 

이를 정리하면 다음과 같이 된다.

 

 

 

최종적으로 위의 다이어그램을 바탕으로 코드를 구성한 후 

 

실행해본 것은 다음과 같다.

 

public class WorkerSimulator {
    public static void main(String[] args) {
        Worker koreanWorker = new KoreanWorker();
        koreanWorker.getPay();
        koreanWorker.takeARest(10);

        Worker robotWorker = new Robot();
        robotWorker.getPay();
        robotWorker.takeARest(0);
        robotWorker.setPayPolicy(new PaySalary());
        robotWorker.getPay();
        /*
        	출력 결과
                일당 20만원 지급
                10분 휴식
                임금 없음
                휴식 없음
                일당 20만원 지급
        */
    }
}

 


 

이번 시간에는 디자인 패턴이 무엇인지, 그리고 기본적인 설계 원칙에 대해 공부했다.

 

필자가 공부하는 내용은 github에 공유중이니 궁금한 분은 찾아보면 될 것이다.

 

정리하자면, 

 

오늘의 핵심은 디자인 패턴 원칙이다.

  1. 애플리케이션에서 달리지는 부분을 찾아내고, 달라지지 않는 부분과 분리한다. 
  2. 구현보다는 인터페이스에 맞춰 구현한다.
  3. 상속보다는 구현을 활용한다.

다음 시간에는 옵저버 패턴에 대해 공부할 것이다.