* 본 포스팅은 한빛미디어의 헤드퍼스트 디자인 패턴을 공부한 내용을 정리한 글입니다.
저의 부족한 생각과 주관으로 틀린 내용이 있을 수 있으니,
자세한 내용이 궁금하시다면 해당 책을 읽어보시길 추천드립니다.
1장 : 디자인 패턴의 세계로 떠나기
디자인 패턴 소개와 전략 패턴
디자인 패턴소개에 앞서, 객체지향에 대한 기본적인 이해가 필요합니다.
필요한 객체지향 기초 개념
추상화 : 공통 속성 및 기능을 묶어 이름을 붙임
캡슐화 : 데이터 구조 및 다루는 방법들을 결합시켜 묶는 것
상속 : 상위 개념의 특징을 하위 개념이 물려받는 것
다형성 : 물려받은 상위 개념의 특징을 재정하는 것
디자인 패턴의 의의
디자인패턴은 코드가 아닌 경험을 재사용하는 것이다.
다른 개발자가 우리와 똑같은 문제를 경험하고 해결하면서 익혔던 지혜와 교훈을 살펴 활용하는 법을 배울 수 있다고 합니다.
장점
- 공통으로 아는 용어를 쓰기에 간단한 단어로 많은 얘기를 할 수 있다.
- 객체와 클래스 구현에 대해 시간을 버릴 필요가 없어 디자인에 포커싱할 수 있다.
- 전문용어를 통해 명확한 내용전달로 팀의 능력이 극대화될 수 있다.
비개발자나 신입개발자에게 굉장히 멋있어 보인다.
디자인원칙
- 캡슐화 : 애플리케이션이서 달라지는 부분을 찾아내여 달라지지 않은 부분과 분리한다.
→ 달라지는 부분을 찾아 나머지 코드에 영향을 주지 않도록 캡슐화 한다. - 인터페이스 지향 : 구현보다는 인터페이스에 맞춰서 프로그래밍 한다.
→ 상위 형식이 맞춰 프로그래밍 해 다형성을 활용한다. - 구성 지향 : 상속보다는 구성을 활용한다.
소프트웨어 개발 불변의 진리
절대로 바뀌지 않는건 아이러니하게도 ‘변화’이다.
디자인을 아무리 잘 해도 시간에 따라 변화하고 성장해야 한다.
그렇지 않다면 그 애플리케이션은 죽은것과 다름이 없다.
01 오리 시뮬레이션 게임 - sim u duck
여기서, 오리에 하늘은 나는 기능을 추가해야 한다고 쳐 봅시다.
우선, fly() 메서드를 duck class에 추가해 보도록 하겠습니다.
그 결과, 날 수 없는 RubberDuck도 Duck을 상속받으므로 고무오리도 날아버리게 되었습니다...!
어쨋든, 고무오리는 날면 안됩니다!
그래서 RubberDuck의 fly 함수를 재정의를 통해 고무 오리의 fly()는 아무 행동도 하지 않도록 '일단' 수정을 하였습니다.
하지만 이건 고무오리만의 특징적인 문제잖아요?
나무오리가 추가되면 fly()를 아무동작도 하지 못하도록 재정의 해야 합니다. 도날드 덕이 추가되면 또 재정의, 집오리가 추가되면 또 재정의 쇠오리 파오리 가짜오리….. 영원해 재정의하며, 재정의하는 코드는 재사용되지 않고 영원히 수정해야 합니다!
날 수 있는 오리도 마찬가지입니다. 만약, fly()를 수정해야 한다면, 오리마다 전부 재정의를 해 주어야 합니다!
이건 확실히 문제가 있죠!
수정시 코드에 미치는 영향을 최소한으로 줄여보자!
해결방법 고민하기 - 디자인 원칙 적용하기
0. 문제파악부터!
위의 경우에서, 상속은 바람직한 방법이 아니였다. 서브클래스마다 행동이 바뀔 수 있으나, 모든 서브클래스에서 한가지 행동만 사용하도록 하기 때문입니다. 자바 인터페이스는 구현된 코드가 없으니, 재활용 할 코드도 없다는게 문제입니다.
1. 분리하기
사실, fly() 말고도 분리해야할 기능이 있습니다.
바로 quack()입니다..
나무오리는 못 울고, 고무오리는 '삑삑' 울수도 있습니다.
그러므로 우리는 quack()도 fly()와 마찬가지로 분리해야 할 기능 입니다.
2. 분리한 행동 구현하기
위에서 문제는 모든 서브클래스에 한가지 행동만 사용하도록 한 ‘유연성’의 문제였습니다.
그러므로, 분리해 새로 만들 클래스 집합은 최대한 유연하게 만들어야겠죠? Duck의 인스턴스에 행동을 할당하며 행동을 동적으로 바꿀 수 있음 최고입니다!
예시로 동물이라는 인터페이스 구조를 보여드리겠습니다.
Animal animal = getAnimal();
animal.makeSound();
우린 animal이 어떤 하위 클래스인지 몰라도, makeSound()를 동작하는데 아무 문제가 없습니다!
이제 오리의 행동을 분리해보겠습니다.
이런식으로 구현하면 재사용이 가능하며, 수정 및 추가할때 Duck 클래스를 건드리지 않아도 됩니다!
만약에 로켓 추친력으로 날아가는 행동을 추가려면 FlyBehavior를 상속받는 클래스를 만들면 되고,
오리소리를 내는 장치 클래스를 새로 만든다면, Quack을 활용할 수도 있습니다.
상당히 유연하죠?
3. 분리한 행동 통합하기
이제 분리한 행동을 Duck클래스에 통합하면 됩니다.
Duck클래스는 꽥꽥거리는 행동이나 나는 행동을 직접 구현하지 않으며, FlyBehavior와 QuackBehavior를 구현한 클래스에 ‘위임’하게 됩니다.
이 방법은 Swift에서 자주 사용하게 되는 Delegate 패턴입니다.
이런식으로 구현이 되는데요, 추상클래스는 아래와 같이 구현하면 됩니다.
public Abstract class Duck(){
//...
QuackBehavior quack;
public void performQuack(){
quack.quack();
}
//...
}
이제 Duck을 상속받은 MallardDuck 클래스에 아래처럼 하면 됩니다.
public class MallardDuck extends Duck{
//...
public MallardDuck(){
quack = new Quack(); //performQuack()을 호출하면 꽥꽥!
fly = new FlyWithWings(); //performFly()를 호출하면 훨훨!
}
//...
public void display(){
system.out.println("저는 물오리 입니다");
}
//...
}
여기서 짚고 넘어갈게 있는데,
Quack()의 인스턴스를 사용하는건 디자인 원칙에서 특정 구현이 아닌 인터페이스에 맞춰 작성하라고 한것과는 좀 대비가 되죠.
책에서는 지금은 일단 이렇게 했지만 차후 알려준다고 했는데, 빌더 패턴이나 팩토리 패턴을 배우며 좀 더 자세히 알 수 있을 것 같습니다.
각설하고, 이제 동적으로 행동을 변경하고 싶다면? setter를 만들어 활용하면 된다.
이제 전체 클래스 구성도를 살펴보겠습니다.
오리에는 FlyBehavior가 있고 QuackBehavior가 있습니다. 각각 나는 행동과 꽥꽥거리는 행동을 위임받죠.이런식으로 클래스를 합치는것을 구성을 이용한다고 합니다.
세번째 디자인 원칙이 여기서 보입니다. ‘상속보다는 구성을 활용할 것.’
그래서, 지금 쓴게 뭐죠?
지금까지 한것이 전략 패턴(Strategy Pattern)이였습니다.
알고리즘군을 정의하고 캠슐화해 각각의 알고리즘군을 수정해서 쓸 수 있게 합니다.
장점으로는 클라이언트로부터 알고리즘을 분리해서 독립적으로 변경할 수 있기에,
코드 재사용성과 유연성이 뛰어나다는것이 있습니다.
이설명을 처음에 하고 시작했으면 진짜 지루해서 죽을려고 했었을 수도 있는데,
책에선 전개방식을 신선하게 예제를 먼저 보여주고 이후에 설명을 하니 보기도 쉽고 좋더라구요.
정리를 하며 포스팅하니 어쩔수없이 딱딱하게 되엇는데...본 저서 정말 추천드립니다 ㅎ
'개발공부 > Code design' 카테고리의 다른 글
헤드퍼스트 디자인 패턴 04.팩토리(Factory) 패턴 - 팩토리 메서드 (0) | 2022.09.25 |
---|---|
헤드퍼스트 디자인 패턴 04.팩토리(Factory) 패턴 - 심플 팩토리 (0) | 2022.09.25 |
헤드퍼스트 디자인 패턴 03.데코레이터(Decorator) 패턴 (0) | 2022.08.08 |
헤드퍼스트 디자인 패턴 2장. 옵저버(Observer)패턴 (0) | 2022.07.17 |
MVC 패턴 (0) | 2021.06.02 |