반응형
(본 강의 노트는 한빛 미디어의 <Head First Design Patterns>책을 기반으로 하고 있습니다)
01 디자인 패턴 소개
디자인 패턴의 분류
- GoF가 디자인 패턴을 23가지로 정리하고 세 가지로 크게 분류( bold 처리 한 부분이 앞으로 자세히 다룰 패턴)
생성 패턴 (Creation Patterns)
- 객체의 생성 과정과 연관된 패턴
- 추상 팩토리 (Abstaact Factory)
- 빌더 (Builder)
- 팩토리 메소드 (Factory Method)
- 프로토 타입 (Prototype)
- 싱글턴 (Singleton)
구조 패턴 (Structual Patterns)
: 클래스나 객체의 합성/ 집약에 관련된 패턴
- 어댑터 (Adapter)
- 브리지 (Bridge)
- 컴포지트 (Composite)
- 데코레이터 (Decorator)
- 퍼사드 (Facade)
- 플라이웨이트 (Flyweight)
- 프록시 (Proxy)
행위 패턴(Behavioral Patterns)
: 클래스나 객체들이 상호작용하는 방법과 책임을 분산시키는 방법을 정의하는 패턴
- 책임 연쇄 (Chain of Responsibility)
- 커맨드 (Command)
- 인터프리터 (Interpreter)
- 반복자 (Iterator)
- 미디에이터 (Mediator)
- 메멘토 (Memento)
- 옵서버 (Observer)
- 스테이트 (State)
- 스트래티지 (Strategy)
- 템플릿 메소드 (Template Method)
- 비지터 (Visitor)
Strategy Pattern : 스트래티지 패턴
목적
- 같은 종류의 작업을 하는 알고리즘을 정의, 캡슐화, 또한 서로 바꿔 알고리즘을 사용할 수 있도록 함
- 알고리즘을 사용하는 클라이언트로부터 독립적으로 알고리즘을 바꿔서 적용시킬 수 있도록 함
Policy Pattern이라고 부르기도 함
- 여러 정책(policy)이 존재하고, 상황에 따라 적합한 정책을 적용시킴
서로 다른 알고리즘들이 존재하고, 실행 중 적합한 알고리즘을 선택해서 적용
- 클라이언트에 모든 알고리즘을 포함시킨다면 코드의 양이 늘어나고 복잡해짐
- 즉 유지 보수가 어려워짐
- 모든 알고리즘이 동시에 사용되는 것이 아니라면 굳이 같이 넣을 이유 없음
- 새로운 알고리즘 추가가 어려움. 기존 코드를 수정해야 함
예
- 조리법이 다른 경우
- 파일의 압축 방법이 다른 경우
- 영화를 보는 방식이 다른 경우
- 자바의 정렬
- Comparator Interface를 이용하는 경우
- 서로 다른 비교 방법을 구현하고 실행 시점에 적절한 방법을 선택
사례1 - Duck(HFDP Ch.1)
Version 1
- simUDuck 이라는 오리 연못 시뮬레이션 게임 개발
- 헤엄치고 꽥꽥 거리는 소리를 내는 다양한 오리가 있음
- Duck 클래스를 구성하고 이로부터 상속받아 다른 클래스를 만듦
코드
class Duck{
void swim(){
System.out.println("swimming");
}
void quack(){
System.out.println("quack");
}
void display(){
System.out.println("Duck");
}
}
class MallardDuck extends class{
@Override
void display(){
System.out.println("MallardDuck");
}
}
class RedheadDuck extends class{
@Override
void display(){
System.out.println("RedheadDuck");
}
}
public class Main {
public static void main(String[] args) {
// write your code here
Duck d1 = new Duck();
Duck d2 = new MallardDuck();
Duck d3 = new RedheadDuck();
d1.display();
d2.display();
d3.display();
d1.quack();
d2.quack();
d3.quack();
}
}
Version 2: 오리를 날게 하고 싶음
- 상위 클래스인 Duck에 fly() 기능 추가
- 단 부모 클래스에 기능이 추가되었으므로 부모,자식 클래스 모두 다시 컴파일 과정이 필요
Duck class code
class Duck{ void swim(){ System.out.println("swimming"); } void quack(){ System.out.println("quack"); } void display(){ System.out.println("Duck"); } // version 2. new code : Duck can fly void fly(){ System.out.println("fly"); } }
장난감 오리 클래스를 추가함 : RubberDuck
class RubberDuck extends class{ @Override void display(){ System.out.println("RubberDuc"); } @Override void quck(){ System.out.println("squeck"); } }
문제점! 원하지 않는 자식 클래스에 fly() 기능이 추가됨.
해결방법 : fly() 함수를 Rubber Duck클래스내에서 오버라이드 하자
//version 3. new code : rubberDuck cannot fly @Override void fly(){ System.out.println("cannot fly"); }
Version 3 & 4
- 인터페이스를 이용한다면? (혹은 추상클래스)
- 인터페이스에 코드를 넣을 수 없으므로 같은 코드를 반복해서 구현하는 경우가 발생 할 수 있음
- 자바8 에서는 디폴트 메소드를 이용하면 일부 해결이 가능하긴 함(Version 4)
스트래티지 패턴 이용하기 : 바뀌는 부분과 그렇지 않은 부분 분리하기
- Duck 클래스에서 fly(), quack() 부분이 자주 바뀜
- 나머지 코드는 변함없음
- 변화하는 부분과 그대로 있는 부분을 분리하려면 두 개의 클래스 집합을 만들어야 함
- 각 클래스 집합에는 각각의 행동을 구현한 것을 넣을 것
- 특정 행동을 Duck 클래스에서 구현하는 것이 아니라, 독립적으로 새로운 클래스를 만들어 구현
- Duck 클래스 또는 서브 클래스에서는 행동을 실제로 구현한 인터페이스를 사용
Version 5
- ModelDuck의 생성자에서 생성한 FlyBehavior을 사용하고 있다.
: flyBehavior = new FlyNoWay(); - 또한 set 함수를 통해서 이후 FlyBehavior를 수정할 수 있다.
: model.setFlyBehavior(new FlyRocketPowered());
사례2 -라면 조리
조리 방법
- 기본 조리
- 볶음 라면
- 치즈 라면
- 식초 라면
- 우유 라면
Version 1
- 클라이언트에 모든 조리법을 넣고 조건문으로 조리법 선택
- 문제점
- 새로운 조리 방법 추가 어려움
- 클라이언트 클래스 코드가 너무 복잡해짐
- Ramen 코드
class Ramen {
public static enum CookingMode {
GENERAL,
WITHOUT_BROTH,
WITH_CHEESE,
WITH_VINEGAR,
WITH_MILK
}
private CookingMode mode;
Ramen() {
mode = CookingMode.GENERAL;
}
public void setCookMode(CookingMode mode) {
this.mode = mode;
}
public void cook() {
switch (mode) {
case GENERAL:
cookWithGeneralRecipe();
break;
case WITHOUT_BROTH:
cookWithoutBroth();
break;
case WITH_CHEESE:
cookWithCheese();
break;
case WITH_VINEGAR:
cookWithVinegar();
break;
case WITH_MILK:
cookWithMilk();
break;
}
private void cookWithGeneralRecipe() {
System.out.println("일반 조리법으로 끓이기");
}
private void cookWithoutBroth() {
System.out.println("물을 적게 넣고 라면을 익힌 뒤에 라면 스프에 볶듯이 끓임");
}
private void cookWithCheese() {
System.out.println("라면을 끓인 후에 치즈 넣기");
}
private void cookWithVinegar() {
System.out.println("라면을 끓인 후에 식초 약간 넣기");
}
private void cookWithMilk() {
System.out.println("우유를 넣고 끓이기");
}
}
}
- Main 코드
public class Main {
public static void main(String[] args) {
Ramen cook = new Ramen();
cook.cook();
cook.setCookMode(
Ramen.CookingMode.WITH_CHEESE);
cook.cook();
}
}
Version 2
- 상속 사용
- 문제점
- 음식 모형을 추가한다면? cook()을 오버라이드해서 실제 요리하지 않도록 해야함
- 새로운 클래스가 추가될때마다 cook() 함수를 확인해야함
Version 3
- 인터페이스를 이용해서 변화하는 부분을 캡슐화시킴
- Ramen 클래스에서는 변화하는 부분을 바꿔서 사용할 수 있도록 처리
- cook() 멤버 함수에서 Recipe의 cook()함수를 호출
- CookRecipe 코드
interface CookRecipe {
public void cook();
}
- Ramen 코드
class Ramen {
CookRecipe recipe = new GeneralRamenRecipe();
public void setRecipe(Recipe recipe) {
this.recipe = recipe;
}
public void cook() {
recipe.cook();
}
}
6. CookRecipe를 구현하는 클래스 코드
class GeneralRamenRecipe implements Recipe {
public void cook() {
System.out.println("일반 조리법으로 끓이기");
}
}
class RamenWithoutBrothRecipe implements Recipe {
public void cook() {
System.out.println("물을 적게 넣고 라면을 익힌 뒤에 라면 스프에 볶듯이 끓임");
}
}
class CheeseRamenRecipe implements Recipe {
public void cook() {
System.out.println("라면을 끓인 후에 치즈 넣기");
}
}
class VinegarRamenRecipe implements Recipe {
public void cook() {
System.out.println("라면을 끓인 후에 식초 약간 넣기");
}
}
class MilkRamenRecipe implements Recipe {
public void cook() {
System.out.println("우유를 넣고 끓이기");
}
}
7. Main 코드
public class Main {
public static void main(String[] args) {
Ramen cook = new Ramen();
cook.cook();
cook.setRecipe(new CheeseRamenRecipe());
cook.cook();
}
}
디자인 패턴 요소
패턴이 필요한 경우
- 경우에 따라 서로 다른 여러 알고리즘이 존재
- 알고리즘이 실행 시점에 결정되어져서 조건문 등을 이용해서 다른 알고리즘을 선택하는 경우
요소 |
설명 |
이름 |
스트래티지(Strategy) |
문제 |
알고리즘의 다른 버전이 존재해서, 중복으로 존재하거나 if문을 이용해서 선택해야 함. OCP 위반 |
해결방안 |
중복을 공통화시키고, 실행 시점에 맞는 알고리즘을 호출하도록 함 (상속 또는 인터페이스 활용) |
결과 |
OCP. 수정할 경우 Strategy를 추가하고, 나머지는 변경하지 않아도 됨 |
Strategy Pattern
Context class
- 캡슐화된 알고리즘을 멤버 변수로 포함
- 캡슐화된 알고리즘을 교환해서 적용시킬 수 있음
Strategy class(인터페이스)
- 컴파일 시점에서 사용하는 캡슐화된 알고리즘을 나타냄
- 실제 구현은 하위 Strategy_n 클래스에 위임
- 인터페이스 또는 클래스(추상)로 구현 가능
Strategy_n
- 실행 시점에 적용될 알고리즘을 캡슐화
- Context에서 실행될 알고리즘을 구현
반응형
'Computer Science > 디자인패턴' 카테고리의 다른 글
[Head First Design Patterns] 05 싱글톤 패턴 (0) | 2020.10.20 |
---|---|
[Head First Design Patterns] 04 팩토리 패턴 (0) | 2020.10.20 |
Introduction Advanced OOP (0) | 2020.10.18 |
[Head First Design Patterns] 03 데코레이터 패턴 (0) | 2020.10.07 |
[Head First Design Patterns] 02 옵저버 패턴 (0) | 2020.09.25 |