-
[헤드 퍼스트 디자인 패턴] 1장. 디자인 패턴 소개와 전략 패턴Book/헤드 퍼스트 디자인 패턴 2023. 3. 31. 21:27
1장. 디자인 패턴 소개와 전략 패턴
본 글은 '헤드 퍼스트 디자인 패턴' 도서를 읽고 정리한 글입니다.
1장에서는 오리 시뮬레이션 게임에 대하여 나온다.
조는 이 게임에서 헤엄도 치고 꽥꽥 소리도 내는 매우 다양한 오리를 구현해야한다.
이 시스템을 처음 디자인한 사람은 표준 객체지향 기법을 사용하여 Duck이라는 슈퍼클래스를 만든 다음, 그 클래스를 확장해서 서로 다른 종류의 오리를 만들었다.
이 때 조는 여태 없었던 새로운 오리의 행동인 '날기'를 할 수 있는 오리가 등장해야한다는 임무를 받았다.
조는 처음 개발되어 있던데로 fly() 라는 메소드를 Duck(상위 클래스)에 추가해서 모든 오리가 그 fly를 상속받게끔 구현하려고 했다.
❗️여기서 문제가 발생했다❗️
고무 오리 마저 fly 클래스를 상속받아버렸다는 것이다.
❓왜 이런 문제가 발생했을까❓
조는 Duck의 몇몇 서브 클래스만 날아야한다는 사실을 깜빡했기 때문이다.
그래서 슈퍼클래스에 행동을 추가한 결과, 일부 서브클래스에 적합하지 않는 행동이 추가되어버렸다.
결국 코드를 재사용하려고 상속을 했는데 유지보수 측면에서 비효율적인 코드가 나와버렸다.여기서 우리는 Duck의 행동을 상속할 때 발생할 수 있는 문제를 다음과 같이 정의 내릴 수 있다.
1. 모든 오리의 행동을 알기 힘들다.
2. 코드를 변경했을 때에 다른 오리들에게 원치 않은 영향을 끼칠 수 있다.
3. 서브 클래스에서 코드가 중복된다.
4. 실행 시에 특징을 바꾸기 힘들다.현재까지 구현된 오리는 우는 소리가 다 달라서 quack() 클래스를 서로 다르게 오버라이드 하고 있었습니다. 그렇다면 역시 상속을 하고 fly()를 다르게 오버라이드를 하면 되는 것일까요?
- 상속을 하는 것이 옳은 프로그래밍일까?
위의 과정을 통해 조는 상속이 옳은 방법이 아니라는 사실을 깨달았습니다.
조는 임원진이 앞으로 6개월마다 제품을 업데이트하기로 결정했다는 소식을 들었습니다. 그러면 규격이 바뀔 때 마다 프로그램에 추가했던 Duck의 서브 클래스 fly() 메소드를 일일이 살펴보고 상황에 따라 오버라이드 해야하기 때문입니다.
여기서 코드 중복이 일어납니다. 메소드 몇 개를 오버라이드 하는 게 싫어서 상속이라는 아이디어를 생각해냈는데, 만일 날아다니는 동장이 아주 조금씩 다르다면 다른 클래스들에 있는 코드를 모두 고쳐야하는 상황이 발생할 수 있습니다.
그렇다면 코드를 재사용하는 효율성은 높이되, 규격이 변경되었을 때 기존 코드에 미치는 영향을 줄일 수 있는 방법은 무엇일까요?
여기서 우리는 바로 디자인 패턴을 생각하지 않고 좋은 디자인 원칙을 적용해보기로 했습니다.
💡여기서 디자인 원칙은 무엇일까요?
다음은 여러 디자인 원칙 중 첫 번째 원칙입니다. 애플리케이션에서 달라지는 부분을 찾아내고 달라지지 않는 부분과 분리한다.
다시 말하면 아래 문장으로 말할 수 있습니다.
바뀌는 부분은 따로 뽑아서 캡슐화한다. 그러면 나중에 바뀌지 않는 부분에는 여향을 미치지 않고 그 부분만 고치거나 확장할 수 있다.
- 바뀌는 부분과 그렇지 않은 부분 분리하기
Duck 클래스는 fly()와 quack()을 제외하면 잘 작동하고 있고 나머지 부분은 자주 달라지거나 바뀌지 않습니다. 그래서 Duck클래스는 그대로 두기로 합니다.
그러면 변화하는 부분과 그대로 있는 부분을 분리하려면 어떻게 해야할까요?
2개의 클래스 집합(set)을 만들어야 합니다. 하나는 나는 것과 관련된 부분이고, 다른 하나는 꽥꽥거리는 것과 관련된 부분이겠죠.
각 클래스 집합에는 각각의 행동을 구현한 것을 전부 넣습니다 예를 들어 꽥꽥거리는 행동을 구현하는 클래스와 삑삑거리는 행동을 구현하는 클래스, 그리고 아무 소리도 내지 않는 행동을 구현하는 클래스를 만드는 식으로 말이죠
나는 행동과 우는 행동을 구현하는 클래스 집합은 어떻게 디자인해야 할까?
일단 최대한 유연하게 만들고, Duck 인스턴스에 행동을 할당할 수 있어야 합니다.
그리고 오리의 행동을 동적으로 바꿀 수 있으면 더 좋겠죠. 동적으로 바꾼다는 말은, Setter를 통해 프로그램 실행 중에도 MallardDuck 클래스가 나는 행동을 바꿀 수 있었으면 좋겠어요.
💡여기서 살펴볼 두번째 디자인 원칙은 바로 '구현보다는 인터페이스에 맞춰서 프로그래밍한다'입니다.
- 인터페이스에 맞춰서 프로그래밍하자
각 행동은 인터페이스로 표현하고 이런 인터페이스를 사용해서 행동을 구현해야 합니다.
특정 행동만을 목적으로 하는 클래스들의 집합을 만듭니다.
행동 인터페이스는 Duck 클래스가 아니라 방금 설명한 행동 클래스에서 구현합니다.
전에 썼던 방법은 항상 특정 구현에 의존했기에 행동을 변경할 여지가 없었습니다. 지금 이 방법(새로운 디자인)을 사용하면 Duck 서브 클래스는 인터페이스로 표현되는 행동을 사용합니다. 따라서 실제 행동 구현은 Duck 서브 클래스에 국한되지 않습니다.
"인터페이스에 맞춰서 프로그래밍한다."라는 말이 뭔가요?
"상위 형식에 맞춰서 프로그래밍한다."라는 말입니다. 여기서 핵심은 실행 시에 쓰이는 객체가 코드에 고정되지 않도록 상위 형식에 맞춰 프로그래밍해서 다형성을 활용해야한다는 점에 있습니다.
다시 자세히 설명하자면, "변수를 선언할 때 보통 추상 클래스나 인터페이스 같은 상위 형식으로 선언해야 하고, 객체를 변수에 대입할 때 상위 형식을 구체적으로 구현한 형식이라면 어떤 객체든 넣을 수 있기 때문이다. 그러면 변수는 선언하는 클래스에서 실제 객체의 형식은 몰라도 된다." 로 설명할 수 있습니다.
정확히 와닿지 않으니 코드를 살펴보자.
Dog d= new Dog(); d.bark(); //변수 d를 Dog형식(Animal을 확장한 구상 클래스)로 선언하면 구체적인 구현에 맞춰서 코딩해야한다. //** 하지만 인터페이스와 상위 형식에 맞춰서 프로그래밍을 한다면 다음과 같이 할 수 있따. Animal animal = new Dog(); //Dog라는 것을 알고 있긴 하지만 다형성을 활용해서 Animal의 레퍼런스를 써도 된다. animal.makesound(); //더 바람직한 방법은 상위 형식의 인스턴스를 만드는 과정을 직접 코드로 만드는 대신 구체적으로 구현된 객체를 실행 시 대입하는 것이다. a = getAnimal(); a.makeSound();
- 위임하기
가장 중요한 점은 나는 행동과 꽥꽥거리는 행동을 Duck 클래스(또는 그 서브클래스)에서 정의한 메소드를 써서 구현하지 않고 다른 클래스에 위임한다는 것입니다.
- 동적으로 행동 지정하기
우리는 위의 방법으로 구현하면 Setter Method를 통해 동적으로 활용할 수 있습니다.
만일 실행 중에 오리의 행동을 바꾸고 싶으면 원하는 행동에 해당하는 Duck의 Setter 메소드를 호출합니다.
- 두 클래스 합치기 : "구성"을 이용하기
A에는 B가 있다 관계를 생각해보자. 각 오리에는 FlyBehavior와 QuackBehavior가 있으며 각각 나는 행동과 우는 행동을 위임받는다. 이런 식으로 두 클래스를 합치는 것을 '구성'을 이용한다라고 부릅니다.
여기서 오리 클래스에서는 행동을 상속 받지 않고, 올바른 행동 객체로 구성되어 행동을 부여받습니다.
💡 구성은 매우 중요한 테크닉이자 세번째 디자인 원칙이기도 합니다. : 상속보다는 구성을 활용한다.
지금까지 봐 왔던 것처럼 구성을 활용해서 시스템을 만들면 유연성을 크게 향상시킬 수 있습니다. 단순히 알고리즘 군을 별도의 클래스 집합으로 캡슐화할 수 있으며 구성 요소로 사용하는 객체에서 올바른 행동 인터페이스를 구현하기만하면 실행 시에 행동을 바꿀 수도 있다.
오리 클래스만 주구장창 본 것 같은데우리는 방금 처음으로 전략 패턴이라는 디자인 패턴을 적용했습니다.- 그래서 지금까지 살펴본 전략패턴이란?
전략 패턴은 알고리즘군을 정의하고 캡슐화해서 각각의 알고리즘군을 수정해서 쓸 수 있게 해줍니다. 전략 패턴을 사용하면 클라이언트로 부터 알고리즘을 분리해서 독립적으로 변경할 수 있습니다.
다음엔 2장 정리로 찾아오겠습니다! ^___^
'Book > 헤드 퍼스트 디자인 패턴' 카테고리의 다른 글
[헤드 퍼스트 디자인 패턴] 8장. 템플릿 메소드 패턴 (0) 2023.06.19 [헤드 퍼스트 디자인 패턴] 5장 하나뿐인 특별한 객체 만들기: 싱글턴 패턴 (2) 2023.05.13 [헤드퍼스트 디자인패턴] 4장. 객체지향 빵 굽기: 팩토리 패턴 (0) 2023.05.05 [헤드퍼스트 디자인패턴] 3장. 객체 꾸미기 : 데코레이터 패턴 (0) 2023.04.29 [헤드퍼스트 디자인 패턴] 2장. 객체들에게 연락 돌리기, 옵저버 패턴 (0) 2023.04.14