데코레이터 패턴 시간입니당!!!!!!!!!!!!!!!!


책에는 "상속맨, 디자인에 눈을 뜨다"라고 써있네요!

상속을 남용하는 전형적인 예를 살펴보고 객체 작성이라는 형식으로 실행중에 클래스를 꾸미는(데코레이터하는) 패턴을 배워볼거에욧


데코레이터 패턴을 배우면!!

원래 클래스의 코드는 전혀 바꾸지 않고도 기존의 객체에 새로운 임무를 부여할 수 있어요!


기존 코드는 건드리지 않은 채로 확장을 통해서 새로운 행동을 간단하게 추가할 수 있도록 하는게 바 로 데코레이터 패턴이죠!

새로운 기능을 추가하는 데 있어서 매우 유연해서 급변하는 주변 환경에 잘 적응할 수 있으면서도 강하고 튼튼한 디자인 !!


OCP라는 디자인 원칙이 있는데요

클래스는 확장에 대해서는 열려있어야 하며, 코드 변경에 있어서는 닫혀있어야 한다는 뜻입니다.


이제부터 제대로 시작합니당!


데코레이터 패턴!!!!!

정의 : 객체에 추가적인 요건을 동적으로 첨가한다.

데코레이터는 서브클래스를 만드는 것을 통해서 기능을 유연하게 확장할 수 있는 방법을 제공한다.


까페를 예로 들겠습니다.

상속을 써서 음료 가격과 첨가물(샷, 시럽, 우유, 휘핑 크림 등) 가격을 합한 총 가격을 계산하는 방법은 그리 좋은 방법이 아닙니다.

그 이유는

1. 클래스가 어마어마하게 많아짐.

2. 일부 서브클래스에는 적합하지 않은 기능을 베이스 클래스에 추가하게 됨.(아이스티에 휘핑크림을 추가할 필요는 없음)


그렇기 때문에 어떤 특정 음료에서 시작해서! 그 음료를 장식할겁니다.!!!!!!!!

하나의 예로 모카하고 휘핑 크림을 추가한 다크 로스트 커피를 들게요.


1. 다크로스트 객체를 가져온다.

2. 모카 객체로 장식한다.

3. 휘핑 객체로 장식한다.

4. 코스트 메소드를 호출한다. 이 때 첨가물의 가격을 계산하는 일은 해당 객체들에게 위임된다.



예제

추상클래스들을 먼저 보여드릴게용


음료 클래스

1
2
3
4
5
6
7
8
9
10
11
12
package abstract_package;
 
public abstract class Beverage {
    protected String description = "제목 없음";
    
    public String getDescription() {
        return description;
    }
    
    public abstract double cost();
}
 
cs


첨가물 클래스

1
2
3
4
5
6
package abstract_package;
 
public abstract class CondimentDecorator extends Beverage {
    public abstract String getDescription();
}
 
cs



이제 음료 클래스들과, 첨과물 클래스들을 보여드릴게요.


먼저 음료클래스

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package beverage;
 
import abstract_package.Beverage;
 
public class DarkRoast extends Beverage {
    public DarkRoast() {
        description = "다크로스트";
    }
    
    @Override
    public double cost() {
        return 0.99;
    }
}
 
cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package beverage;
 
import abstract_package.Beverage;
 
public class Decaf extends Beverage {
    public Decaf() {
        description = "디카페인";
    }
 
    @Override
    public double cost() {
        return 1.05;
    }
 
}
 
cs


더 있는데 너무 많으므로 두개씩만 할게요 풀코드 보고싶은 분은 코드 다운로드해서 봐주세요


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package condiment;
import abstract_package.Beverage;
import abstract_package.CondimentDecorator;
 
public class Mocha extends CondimentDecorator {
    Beverage beverage;
    
    public Mocha (Beverage beverage) {    
        this.beverage = beverage;
    }
    
    public String getDescription() {
        return beverage.getDescription() + ", 모카";
    }
    
    public double cost() {
        return 0.20 + beverage.cost();
    }
 
}
 
cs


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package condiment;
 
import abstract_package.Beverage;
import abstract_package.CondimentDecorator;
 
public class Whip extends CondimentDecorator{
    Beverage beverage;
    
    public Whip(Beverage beverage) {
        this.beverage = beverage;
    }
    
    public String getDescription() {
        return beverage.getDescription() + ", 휘핑";
    }
    
    public double cost() {
        return beverage.cost() + 0.10;
    }
}
 
cs



이제 메인클래스입니당.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import abstract_package.Beverage;
import beverage.DarkRoast;
import beverage.Espresso;
import beverage.HouseBlend;
import condiment.Mocha;
import condiment.Soy;
import condiment.Whip;
 
public class StarbuzzCoffee {
 
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Beverage beverage[] = new Beverage[3];
 
        beverage[0= new Espresso();
        beverage[0= new Mocha(beverage[0]);
        beverage[0= new Mocha(beverage[0]);
        beverage[0= new Whip(beverage[0]);
 
        beverage[1= new DarkRoast();
        beverage[1= new Soy(beverage[1]);
        beverage[1= new Mocha(beverage[1]);
 
        beverage[2= new HouseBlend();
 
        for (int i = 0; i < beverage.length; i++) {
            System.out.println(beverage[i].getDescription() + " $" + beverage[i].cost());
        }
        
    }
}
 
cs



[출력결과]


에스프레소, 모카, 모카, 휘핑 $2.49

다크로스트, 두유, 모카 $1.3399999999999999

하우스 블렌드 커피 $0.89

1.3399999999999999



다 보고 싶으신 분은 풀 코드 다운로드하세요~~

Decorator_Pattern.jar



'CS기본지식 > 디자인 패턴' 카테고리의 다른 글

옵저버 패턴(Observer Pattern)  (0) 2017.08.25
스트래티지 패턴(Strategy Pattern)  (0) 2017.08.23


제가 보는 책입니다.

블로그는 참조만 해주시고 진짜 공부하려면

책 사는 것을 권장합니다.

진짜 좋은 책입니다.

Head First - Design Patterns 입니다.


오늘은 옵저버 패턴에 대해서 알려드릴게요!!!!


옵저버 패턴은 어떠한 이벤트가 발생했을 때 객체들에게 새소식을 전해줄 수 있는 패턴입니다.

객체 쪽에서는 계속해서 정보를 받을지 여부를 실행중에 결정할 수 있습니다.

옵저버 패턴은 JDK에서 가장 많이 쓰이는 패턴이라고 합니다.

일대다 관계 그리고 느슨한 결합 키워드를 항상 기억하세요~


옵저버 패턴 = 출판사 + 구독자

라고 생각하면 이해하기 쉬울 것 같습니다.

출판사는 subject, 구독자를 observer라고 부릅니다.


옵저버 패턴의 정의

한 객체의 상태가 바뀌면 그 객체에 의존하는 다른 객체들한테 연락이 가고 자동으로 내용이 갱신되는 방식

일대다 의존성(subject에 observer들이 일대다로 연결되며, 의존함)을 정의함.

이미지 출처 클릭


옵저버 패턴을 구현하는 방법은 여러 가지가 있지만, 대부분 주제(subject) 인터페이스와 옵저버(observer) 인터페이스가 들어있는 클래스 디자인을 바탕.

옵저버 패턴은 느슨한 결합을 한다고 했는데

그럼 느슨한 결합은 무엇일까요??

두 객체가 느슨하게 결합되어 있다는 것은, 그 둘이 상호작용을 하긴 하지만 서로에 대해 서로 잘 모른다는 것을 의미합니다.

옵저버 패턴에서는 주제와 옵저버가 느슨하게 결합되어 있는 객체 디자인을 제공합니다.


왜일까요??


주제가 옵저버에 대해서 아는 것은 옵저버가 특정 인터페이스를 구현한다는 것 뿐입니다. 옵저버의 구상 클래스가 무엇인지, 옵저버가 무엇을 하는지 등에 대해서 알 필요가 없습니다.


- 옵저버는 언제든지 새로 추가할 수 있습니다. 주제는 Observer 인터페이스를 구현하는 객체의 목록에만 의존하기 때문에 언제든지 추가/삭제를 할 수 있습니다.


- 새로운 형식의 옵저버를 추가하려고 해도 주제를 전혀 변경할 필요가 없습니다. 옵저버가 되어야 하는 새로운 구상 클래스가 생겼다고 가정하면, 새로운 클래스 형식을 받아들일 수 있도록 주제를 바꿔야할 필요는 없습니다. 새로운 클래스에서 Observer 인터페이스를 구현하고 옵저버로 등록하기만 하면 됩니다. 주제 객체는 전혀 신경도 쓰지 않습니다. subject는 Observer 인터페이스만 구현한다면 어떤 객체에든지 연락을 하기 때문입니다.


- 주제와 옵저버는 서로 독립적으로 재사용할 수 있음. 주제나 옵저버를 다른 용도로 활용할 일이 있다고 해도 손쉽게 재사용할 수 있음. 그 둘이 서로 단단하게 결합되어 있지않기 때문(느슨한 결합)


- 주제나 옵저버가 바뀌더라도 서로한테 영향을 미치지는 않습니다. 둘이 서로 느슨하게 결합되어 있기 때문에 주제 혹은 옵저버 인터페이스를 구현한다는 조건만 만족된다면 어떻게 바꿔도 문제가 없습니다.


서로 상호작용을 하는 객체 사이에서는 가능하면 느슨하게 결합하는 디자인을 사용해야 한다.

느슨하게 해야하는 이유는 느슨한 결합을 사용하면 변경 사항이 생겨도 무난히 처리할 수 있는 유연한 객체지향 시스템을 구축할 수 있기 때문입니다. 객체 사이의 상호의존성을 최소화해야 합니다.



옵저버 패턴 코드 다운로드

코드에는 푸시와 풀 방식이 들어있습니다.

푸시는 subject와 observer를 인터페이스로 구현했고, 풀은 java.util의 observer와 observable을 import했습니다.


푸시방식에서는 notify를 하면 직접 subject 쪽에서 observer들을 update하지만

풀방식에서는 setChanged() 함수를 이용하여 데이터가 변했음을 알려주고,

notifyObservers()를 호출하는데 이는 데이터를 푸시하는 것이 아닌

옵저버 측에서 get함수를 이용하여 데이터를 가져다 쓰는 것입니다.


java.util.observable의 단점

- 이는 인터페이스가 아닌 클래스입니다. 그러므로 구현과 재사용성에 있어서 제약조건이 많습니다.

클래스이기 때문에 서브클래스를 만들어야하는데 이미 다른 클래스를 상속하고 있다면 다중상속이 되므로

Observable기능을 사용할 수 없습니다.


- 또한 인터페이스가 아니기 때문에 직접 구현하는 것이 불가능합니다. java.util 구현을 다른 구현으로 바꾸는 것은

불가능합니다.


- Observable 클래스의 핵심 메소드를 외부에서 호출할 수 없습니다. setChanged() 메소드는 protected로 되어있기 때문에 서브클래스에서만 호출이 가능합니다.


제가 보는 책입니다.

블로그는 참조만 해주시고 진짜 공부하려면

책 사는 것을 권장합니다.

진짜 좋은 책입니다.

Head First - Design Patterns 입니다.


Strategy_Pattern.jar

아래에서 설명할 스트래티지 패턴의 예제입니다.

다운로드 받으시고 실행하시면 좀 더 이해하기 수월하실 겁니다.

자바파일입니다.


문제의 시작

어떤 팀은 성공적인 오리 연못 시뮬레이션 게임을 만들었다.

이 게임에서의 오리는 헤엄도 치고 꽥꽥거리는 소리도 내는 매우 다양한 오리 종류를 보여줄 수 있다.

처음으로 이 오리를 디자인한 사람들은 Duck이라는 수퍼클래스를 만들었고,

Duck 클래스를 확장하여 다른 종류의 오리들을 만들었다.


Duck클래스에는 quack(), swim(), display()등의 메소드가 존재한다.


이제 오리들을 날아다닐 수 있게 하고 싶다.

그렇다면!


어떻게 해결해볼까??

수퍼클래스인 Duck클래스에 fly() 메소드를 추가하면 되지 않을까??

정말 간단하다.


하지만 문제가 발생했다. 날 수 있는 오리도 있지만, 날지 못하는 오리 인형 같은 것들이 날라다니기 시작한 것이다.

수퍼클래스에 fly() 메소드가 추가되면서 일부 서브클래스에는 적합하지 않은 행동이 전부 추가된 것이다.


상속을 활용하여 코드를 재사용할 수 도 있지만 이런 부작용이 발생할 수 있다.


오리 종류마다 quack()메소드(울음 소리)를 오버라이드 한 것처럼 fly() 메소드 또한 오버라이드하면 되지않을까!?

오리 인형은 아무 동작도 하지않게 오버라이드하면 되겠다!


하지만 이 방법 또한 문제가 많다.

오리의 종류마다 오버라이드를 한다면 모든 오리의 행동을 알기 힘들다는 것이다.

또한, 런타임 도중에 오리의 특징을 바꾸기도 힘들다.


인터페이스는 어떨까???

fly() 메소드를 Duck 수퍼클래스에서 빼고 fly() 메소드가 들어가있는 Flyable 인터페이스를 만든다면!

하늘을 나는 오리에 대해서만 이 인터페이스를 구현하여 fly() 메소드를 집어넣으면 되겠다!!!!


모든 오리들이 꽥꽥 우는 것도 아니니 Quackable이라는 인터페이스를 같이 만들어보자!


으악.. 이 방법도 문제가 있다.

서브클래스에서 이 인터페이스들(Flyable, Quackable)을 구현한다면 날거나 울음 소리에 대해 전부 오버라이딩을 해야하므로

코드의 재사용을 전혀 할 수 없다.

100가지의 오리 종류가 있다면 100가지 모두 일일이 다 구현해야하는 것이다.


진짜 어떻게 해결해야하지??ㅠ_ㅠ

이 상황에 가장 어울리는 디자인 원칙이 있다.


애플리케이션에서 달라지는 부분을 찾아내고, 달라지지 않는 부분으로부터 분리하라.


코드에 새로운 요구사항이 있을 때마다 바뀌는 부분이 있다면, 바뀌지 않는 다른 부분으로부터 골라내서 분리해야한다는 말이다.

바뀌는 부분은 따로 뽑아서 캡슐화한다면, 바뀌지 않는 부분에는 영향을 미치지 않은 채로 그 부분만 고치거나 확장할 수 있다.

모든 패턴은 시스템의 일부분을 다른 부분과 독립적으로 변화시킬 수 있는 방법을 제공하기 위한 것이다.


수퍼클래스 Duck에서 fly()와 quack()을 제외하면 다른 부분은 잘 작동한다.

그렇다면 달라지는 부분인 fly()와 quack()을 분리하여 새로운 집합을 만들어보자.


그리고 Duck()의 행동을 동적으로 혹은 유연하게 바꿀 수 있도록, fly와 quack부분에 대해 setter 메소드를 포함하자.


두번째 디자인 원칙!


구현이 아닌 인터페이스에 맞춰서 프로그래밍한다.


여기서 말하는 인터페이스는 자바의 인터페이스라기 보다는 객체가 코드에 의해서 고정되지 않도록, 어떤 상위 형식에 맞춰서 프로그래밍함으로써

다형성을 활용해야한다는 것이다.

그리고 상위 형식에 맞춰서 프로그래밍하라는 원칙은 변수를 선언할 때는 보통 추상 클래스나 인터페이스 같은 상위 형식으로 선언해야 한다는 말이다.

이렇게 하는 이유는 객체를 변수에 대입할 때 상위 형식을 구체적으로 구현한 형식이라면 어떤 객체든 집어넣을 수 있기 때문이다.

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


예를 들어..


Dog d = new Dog();

d.bark();


이렇게 한다면 구체적인 구현에 맞춰서 코딩을 해야한다.

하지만 상위 형식에 맞춰 코딩한다면??


Animal animal = new Dog();

animal.makeSound();


Dog라는 걸 알고 있긴 하지만 다형성을 활용하여 Animal에 대한 레퍼런스를 써도 된다는 말이다.


더 바람직한 방법은 인스턴스를 만드는 과정을 new Dog()처럼 직접만드는 대신 구체적으로 구현된 객체를 실행시에 대입하는 것이다.


a = getAnimal();

a.makeSound();


Animal의 하위 형식 가운데 어떤 형식인지 몰라도 makeSound()에 대해 올바른 반응을 할 수 있다.


어쨋든!!

위에서 분리한 fly와 quack을 인터페이스로 FlyBehavior 와 QuackBehavior로 구현을 하고 

그 인터페이스 안에 FlyWithWings, FlyNoway 그리고 Quack, Squeak, MuteQuack으로 구체적인 행동을 하는 클래스를 만든다.


이런 식으로 디자인하면 다른 형식의 객체에서도 나는 행동과 꽥꽥거리는 행동을 재사용할 수 있다.

그런 행동이 더 이상 Duck클래스 안에 숨겨져 있지 않기 때문이다.


그리고 기존의 행동 클래스를 수정하거나 날아다니는 행동을 사용하는 클래스는

Duck클래스를 전혀 건드리지 않고도 새로운 행동을 추가할 수 있다.


이렇게 한다면 상속을 쓸 때 떠안는 부담을 전부 떨쳐 버리고 코드 재사용의 장점을 그대로 누릴 수 있다.


이 패턴이 바로 스트래티지 패턴이다!


스트래티지 패턴의 정의는! 바로바로!


알고리즘군을 정의하고 각각을 캡슐화하여 교환해서 사용할 수 있도록 만든다.

스트래티지 패턴을 활용하면 알고리즘을 사용하는 클라이언트와는 독립적으로 알고리즘을 변경할 수 있다!

'CS기본지식 > 디자인 패턴' 카테고리의 다른 글

데코레이터 패턴(Decorator Pattern)  (0) 2017.08.28
옵저버 패턴(Observer Pattern)  (0) 2017.08.25

+ Recent posts