스트래티지 패턴 (Strategy Pattern)

JAVA/Design Pattern|2018. 5. 28. 22:39

공통적인 특징을 가진 객체를 

만들 경우 우리는 대게 다음과 같이 

추상클래스나 인터페이스를 만들어 놓고, 그 것을 상속받아 객체를 구현한다.

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
// 추상 클래스
package com.wedul.study.strategy;
 
public abstract class Animals {
 
    public Animals() {}
 
    private String name;
    
    public Animals(String name) {
        this.name = name;
    }
 
    public void eat() {
     System.out.println("a gu a gu");
    }
    
    abstract void attck();
}
 
// 고양이
public class Cat extends Animals {
    public Cat() {}
 
    public Cat(String name) {
        super(name);
    }
 
    @Override
    void attck() {
        System.out.println("scretch and bite");
    }
 
}
 
// 강아지
public class Dog extends Animals {
    public Dog() {}    
 
    public Dog(String name) {
        super(name);
    }
 
    @Override
    void attck() {
        System.out.println("bark and bite");
    }
 
}
 
// main
public class Main {
    public static void main(String args[]) {
        Animals dog = new Dog();
        Animals cat = new Cat();
        
        dog.attck();
        cat.attck();
    }
}
 
 
// 결과
bark and bite
scretch and bite
cs



하지만 이런 유형의 경우 

몇가지 문제점이 발생할 수 있다.

문제점
1. 기존 추가된 객체의 동작을 변경하려는 경우.

강아지는 물지 않고, 고양이는 할퀴지 않는경우에 

기존의 코드를 수정을 해주어야 한다.

하지만 그런 경우 새로운 기능을 위해 기존의 속성을 변경해야 하기에 

OCP를 위반하는 행위이다.

2. 새로운 객체가 추가되었을 경우

새로운 rabbit이라는 객체가 추가되었을 때 고양이의 attack 메소드를 동일하게 사용하고 싶은 경우에도

별도로 만들어 주어야 한다.

이는 중복된 코드를 만드는 좋지 않은 코드 습관이 될 수 있다.

또한 attack의 의 성격이 변경될 경우 토끼와 고양이의 메소드를 모두 수정해 주어야 한다.

해결방법

이렇게 자주 변경되는 속성에 관해서는 

그 속성에 대한 인터페이스를 구분하는 것이 좋다.

위의 예제에서 보면 공격이라는 속성이 자주 변경 되는 경우

공격이라는 인터페이스를 만들고 

공격유형에 따른 객체를 인터페이스를 구현하여 만들고 동물에게 해당 속성을 부여해 주는 방식으로 진행해야 한다.


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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
// 공격 타입 인터페이스
public interface AttactType {
    void attack();
}
 
// bite 타입
public class BiteAttackType implements AttactType {
 
    @Override
    public void attack() {
        System.out.println("bite you");
    }
 
}
 
// scretch 타입
public class ScretchType implements AttactType {
 
    @Override
    public void attack() {
        System.out.println("scretch use");
    }
 
}
 
// 공격 속성 부여
public abstract class Animals {
    private String name;
    private AttactType attactType;
    
    public Animals() {}
    
    public Animals(String name) {
        this.name = name;
    }
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    public AttactType getAttactType() {
        return attactType;
    }
 
    public void setAttactType(AttactType attactType) {
        this.attactType = attactType;
    }
    
}
 
// main (동적으로 속성 부여)
public class Main {
    public static void main(String args[]) {
        Animals dog = new Dog();
        Animals cat = new Cat();
 
        // 속성 부여
        dog.setAttactType(new BiteAttackType());
        cat.setAttactType(new ScretchType());
        
        dog.getAttactType().attack();
        cat.getAttactType().attack();
    }
}
cs




위와 같이 스트래티지 패턴은 전략을 쉽게 바꿀 수 있도록 해주는 디자인 패턴이다.

스트래트지 패턴은 

역할에 따라 3가지 요소로 구분된다.

1) Strategy 
- 인터페이스나 추상화로 외부에서 동일한 방식으로 알고리즘을 호출하는 방법을 명시 ( 위의 예에서 Attack Type 인터페이스)
2) ConcreteStrategy
- 인터페이스를 구현한 실제 전략 객체들 (위의 예에서 bite Attack Type, Scretech Attack Type)
3) Context 
- 스트래티지 패턴을 이용하는 역할을 수행한다.  (위의 예에서 Animals)
- 위에서 생성된 전략을 setter을 통해서 매핑한다.



정리하면 

어떤 주체의 행위는 동일하나,
그 도구가 계속 변경된다면 그 도구를 인터페이스로 정의하고
인터페이스 메서드로 그 행위를 기재 한후.

주체에게 그 도구를 주입해서 동적으로 사용하는 방식을 고려하라.

댓글()

싱글톤 패턴 (Singleton Pattern)

JAVA/Design Pattern|2018. 5. 28. 22:37

대게 

공용으로 사용하는 유틸성 클래스의 경우

하나의 공용 인스턴스 객체를 생성하고 

필요로 할 때마다, 해당 이스턴스 객체에 접근 하여 사용한다.

이를 싱글톤 패턴이라고 한다.

싱글톤 패턴
- 하나의 인스턴스만을 생성하는 책임이 있으며, getInstance 메소드를 통해 모든 클라이언트에게 동일한 인스턴스를 반환한다.


1
2
3
4
5
6
7
8
9
10
11
12
13
// 유틸성 클래스의 경우 인스턴스 객체를 만들거나 상속을 할 필요가 없으므로 final과 private 생성자를 만든다.
public final class Util {
  private Utill () {}
  private Util util = null;
 
  public static Util getUtil() {
   if (util == null ) {
     util = new Util();
   }
 
   return util;
  } 
}
cs



하지만 이경우 동시에 여러 스레드가 Util 클래스의 접근하였을 때

경합 조건이 발생하여 두 개 이상의 스레드가 인스턴스 객체를 만들려고 시도 할 수 있다. 

이런경우에 문제가 싱글톤의 장점이 사라진다.

또한 단순히 정보만을 전달해주는 클래스일 경우에는 큰 문제가 없을 수 있으나,

스태틱 변수가 아닌 멤벼 변수 값의 경우 여러 인스턴스 객체가 만들어지면 

잘못된 데이터가 출력되기 때문에 문제가 발생할 수 있다.

ex)
int count = 10;

public void upCount() {
count++;
}

해결방법
1. 정적 변수에 인스턴스를 만들어 초기화 하기 




1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 방법 1 
public class Util {
 private static Util = new Util();
 private Util() {}
 
}
 
 
// 방법 2. (Lazy 방식)
public class Util {
  private Util() {}
  public static Util getInstance() {
    return LazyHolder.INSTANCE;
  }
  
  private static class LazyHolder {
    private static final Util INSTANCE = new Util();  
  }
}
cs



클래스가 로드 되는 경우에는 초기화가 한번만 이루어 지기 때문에 인스턴스 객체가 여러개 발생할 수 있는 문제가 야기되지 않는다.

2. Enum 클래스로 사용하기
 Enum은 인스턴스가 여러 개 생기지 않도록 확실하게 보장해주고 복잡한 직렬화나 리플렉션 상황에서도 직렬화가 자동으로 지원된다는 장점이 있어서 

Enum 을 사용해서 싱글톤 패턴을 사용하면 좋다.



1
2
3
4
5
6
7
8
9
10
11
12
13
// Enum
public enum Util {
    INSTANCE;
    
    public void getData() {
        System.out.println("db");
    }
}
 
// Main 사용
public void main(String args[]) {
  Util.INSTANCE.getData();
}
cs



3. 인스턴스 생성 메서드 동기화 하기

1
2
3
4
5
6
7
8
9
10
11
12
public final class Util {
  private Utill () {}
  private Util util = null;
 
  public synchronized static Util getUtil() {
   if (util == null ) {
     util = new Util();
   }
 
   return util;
  } 
}
cs



정적 클래스 VS 싱글톤 객체

정적 클래스는 굳이 인스턴스 객체를 공유해서 사용하지 않고, 정적 클래스를 이용해서 유틸성 클래스를 사용할 수 있다.

또한 정적 메서드를 사용하므로 인스턴스를 사용하는것 보다 성능적으로 우수하다. (컴파일 바인딩 부분에서)

하지만 이런 정적 클래스가 장점이 있어도 사용하지 못하는 경우가 있었다.

항상 모든 메서드가 정적 메서드이어야 하는 정적 클래스의 경우 
공통으로 사용하는 인터페이스의 메서드 또한 정적 메서드여야 한다.

하지만 인터페이스에서 구현된 static 메서드는 상속받아서 구현할 수 없기 때문에 문제가 발생할 수 있다.

하지만 

싱글톤 패턴의 경우 스태틱 메서드를 사용하지 않으므로 인터페이스의 내용을 구현하여 
인스턴스 메서드를 사용할 수 있다.


1
2
3
4
5
6
7
8
9
10
11
12
public interface Utils {
    static void getData() {
            System.out.println("dbs");
    }
}
 
public class UtilClass implements Utils {
    @Override
    public static void getData() { // 오류 발생
        
    }
}
cs



위와 같은 문제가 아니라면
정적 클래스를 사용하는 것이 싱글톤보다 더욱 편해보인다.



댓글()
  1. wedul 2019.03.07 15:01 댓글주소  수정/삭제  댓글쓰기

    thread safe single tone 생성법 정리
    https://medium.com/@joongwon/multi-thread-%ED%99%98%EA%B2%BD%EC%97%90%EC%84%9C%EC%9D%98-%EC%98%AC%EB%B0%94%EB%A5%B8-singleton-578d9511fd42

스테이트 패턴 (state pattern)

JAVA/Design Pattern|2018. 5. 28. 22:35

스테이트 패턴

객체의 상태를 효율적으로 관리 할 수 있도록 

스테이트 패턴을 알아보자

많은 개체들은 상태값이 변경됨에 따라 다른 일을 수행하게 된다.

이를 객체에 어떤 동작이 수행될 때마다 상태를 확인하고 그에 맞는 동작이 수행되도록 하는 경우

코드가 지저분해지고, 많은 switch, if문을 가지게 될 것이다.

이것을 스테이트 패턴을 이용하여 공통의 상태 인터페이스를 만든 후

각 상태에 따른 객체를 생성한 후 상세한 동작을 상태 객체에서 수행하도록 설정하는 것이다.

문제의 상황의 예를 살펴보자

기존의 객체의 상태값이 있는 경우 다음의 자동차 객체처럼 하는 경우가 있다.



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Car {
    private int ON = 1;
    private int OFF = 0;
    private int state;
    
    public Car() {
        // 시동이 꺼있는 상태 
        this.state = OFF;
    }
    
    public void onCar() {
        if (state != ON) {
            state = ON;
        }
    }
    
    public void offCar() {
        if (state != OFF) {
            state = OFF;
        }
    }
     
}
cs



하지만 만약에 시동이 켜진상태에서 다시한번 시동 버튼을 누를 경우
파워모드로 변경되고, 다시 시동버튼을 누르면 일반 모드로 넘어가도록 수정을 한다고 해보자.


다음과 같이 상태값 POWER_MODE_ON을 추가해야 하며,
onCar 메소드도 그에 따라 계속 변경되어야 한다.



1
2
3
4
5
6
7
8
9
10
11
private int POWER_MODE_ON = 2;
 
public void onCar() {
    if (state == ON) {
        this.state = POWER_MODE_ON;
    } else if (state == POWER_MODE_ON ) {
        this.state = ON; 
    } else if (state != ON) {
        state = ON;
    }
}

cs



상태값이 추가되거나 할 때마다 계속 수정된다면 매우 비효율 적이다.

해결방법

이를 해결하기 위해서는 현재 객체(Car)의 상태가 어떠하든지 상관없게 구성하고 
상태 변화에도 독립적 이도록 코드를 수정해야한다.

구성
- Car 객체는 별도의 상태를 나타내는 State를 구현한 객체를 보유한다.
- Car 객체에서 onCar, offCar 동작을 수행할 때마다 state에게 자신을 전달하여 상태를 변경한다.



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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
public class Car {
    // 자동차의 상태를 가지고 있는다.
    private CarState state;
    
    public Car() {
        // 시동이 꺼있는 상태를 기본으로 가지고 있는다.
        this.state = CarPowerOff.getInstance();
    }
    
    public CarState getState() {
        return state;
    }
 
    public void setState(CarState state) {
        this.state = state;
    }
 
    public void onCar() {
        this.state.on_buutton_pushed(this);
    }
    
    public void offCar() {
        this.state.off_button_pushed(this);
    }    
}
 
// 상태 객체를 구현하기 위한 인터페이스
public interface CarState {
    void on_buutton_pushed(Car car);
    void off_button_pushed(Car car);
}
 
// 시동 파워모드 On
public class CarPowerModeState implements CarState {
    
    private static CarPowerModeState instance = new CarPowerModeState();
    
    public static CarPowerModeState getInstance() {
        return instance;
    }
 
    @Override
    public void on_buutton_pushed(Car car) {
        car.setState(CarOneState.getInstance());
    }
 
    @Override
    public void off_button_pushed(Car car) {
        car.setState(CarPowerOff.getInstance());
    }
 
}
 
// 시동 On
public class CarOneState implements CarState {
    
    private static CarOneState instance = new CarOneState();
    
    public static CarOneState getInstance() {
        return instance;
    }
 
    @Override
    public void on_buutton_pushed(Car car) {
        car.setState(CarPowerModeState.getInstance());
    }
 
    @Override
    public void off_button_pushed(Car car) {
        car.setState(CarPowerOff.getInstance());
    }
 
}
 
// 시동 off
public class CarPowerOff implements CarState {
    private static CarPowerOff instance = new CarPowerOff();
    
    public static CarPowerOff getInstance() {
        return instance;
    }
    
    @Override
    public void on_buutton_pushed(Car car) {
        car.setState(CarOneState.getInstance());
    }
 
    @Override
    public void off_button_pushed(Car car) {
        // Not Work
    }
 
}
 
cs



결과적으로 위에 코드를 보면 알겠지만

결론
상태에 필요 부분을 인터페이스로 명세하고, 
상태별로 각자 필요 메소드를 Override하여 구현한다.

자동차는 현재 상태값을 객체 형태로 가지고 있으며, on 또는 off 요청이 들어왔을 때
현재 자신의 상태 객체에게 자신을 넘겨서 동작을 본인이 하는 것이아니라 현재 상태 객체에게 넘겨서
진행되도록 한다.



댓글()

커맨드 패턴 (command pattern)

JAVA/Design Pattern|2018. 5. 28. 22:34

커맨드 패턴은 이벤트가 발생되었을 때

실행될 기능이 다양하면서도 변경이 필요한 경우에 이벤트를 발생시키는 클래스는 변경하지 않고 재사용하고자 할 때 유용하다.

다음 예를 살펴보자

자동차의 
시동버튼이 눌러졌을 때 시동이 켜지도록 설계해보자.



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
// 버튼 클래스
public class Button {
    private Engine engine;
    
    public Button(Engine engine) {
        this.engine = engine;
    }
    
    public void on() {
        engine.execute();
    }
    
}
 
// execute 인터페이스
public interface Execute {
    void execute();
}
 
// 엔진 
public class Engine implements Execute {
 
    @Override
    public void execute() {
        System.out.println("Engine Start");
    }
 
}
cs




하지만 이런 구조에서
버튼을 눌렀을 때, 시동이 아닌 램프를 키고 싶은 경우에는 다음과 같이 변경작업을 진행해야한다.



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 버튼 클래스
public class Button {
    private Engine engine;
    
    public Button(Engine engine) {
        this.engine = engine;
    }
    
    public void on() {
        engine.execute();
    }
    
}
 
// 엔진 
public class Lamp implements Execute {
 
    @Override
    public void execute() {
        System.out.println("Lamp On);
    }
}
cs



이런경우 동작이 변경될 때 
기존의 코드가 변경되면 안된다는 OCP를 위반하게 된다.

이를 다음과 같이 변경하여
이 문제를 해결하는 것이 command pattern이다.



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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
// 버튼 클래스 (동작이되는 Command 인터페이스를 가지고 있는다.)
public class Button {
    private Command executeEle;
    
    public Button(Command executeEle) {
        this.executeEle = executeEle;
    }
    
    public void setCommand(Command executeEle) {
        this.executeEle = executeEle;
    }
    
    public void press() {
        executeEle.execute();
    }
    
}
 
// command 인터페이스
public interface Command {
    void execute();
}
 
// 엔진 객체
public class Engine {
 
    public void on() {
        System.out.println("Engine Start");
    }
 
}
 
// 엔진의 실행동작을 가지고 있는 command
public class EngineOnCommand implements Command {
    private Engine engine;
    
    public EngineOnCommand(Engine engine) {
        this.engine = engine;
    }
    
    @Override
    public void execute() {
        this.engine.on();
    }
    
}
 
// Lamp
public class Lamp {
    
    public void on() {
        System.out.println("Lamp on");
    }
}
 
// Lamp를 실행시키는 command
public class LampOnCommand implements Command {
 
    private Lamp lamp;
    
    public LampOnCommand(Lamp lamp) {
        this.lamp = lamp;
    }
    
    @Override
    public void execute() {
        this.lamp.on();
    }
    
}
 
public class Main {
    public static void main(String[] args) {
        // 엔진 키기
        Button button = new Button(new EngineOnCommand(new Engine()));
        button.press();
        
        button.setCommand(new LampOnCommand(new Lamp()));
        button.press();
    }
 
}
cs





실행을 요구하는 Button클래스와 
각 동작을 구현하는 Command 클래스, 그 클래스 내부에 동작이되는 대상을 삽입한다.





한가지 적용 가능한 예를 보면
화면에 메뉴를 구성하는 MenuItem 객체를
공용해서 사용하는데 동작만 변경해서 사용하고 싶을 경우 

1. MenuItem 클래스에 command라는 인터페이스를 가지고 있게 설계한다.
2. 객체의 동작을 호출하는 클래스를 command를 구현하여 작성한다.
3. 2번의 클래스는 각자 자신이 호출할 객체를 가지고 있다.

이렇게 설계하면 MenuItem 클래스에서 동작을 
변경하여 사용할 수 있다.


결론
기능의 실행을 요구하는 호출자(invoke)와 실제 기능을 수행하는 수진자(Receiver)사이의 의존성을 제거한다. 따라서 실행될 기능의 변경에도 호출자 클래스를 수정없이 사용할 수 있어야 한다.

결국 서로간의 의존성 및 커플링을 줄일 수 있도록 설계해야 한다.



댓글()

커맨드 패턴 (command pattern)

JAVA/Design Pattern|2018. 5. 27. 22:14

커맨드 패턴은 이벤트가 발생되었을 때

실행될 기능이 다양하면서도 변경이 필요한 경우에 이벤트를 발생시키는 클래스는 변경하지 않고 재사용하고자 할 때 유용하다.

다음 예를 살펴보자

자동차의 
시동버튼이 눌러졌을 때 시동이 켜지도록 설계해보자.



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
// 버튼 클래스
public class Button {
    private Engine engine;
    
    public Button(Engine engine) {
        this.engine = engine;
    }
    
    public void on() {
        engine.execute();
    }
    
}
 
// execute 인터페이스
public interface Execute {
    void execute();
}
 
// 엔진 
public class Engine implements Execute {
 
    @Override
    public void execute() {
        System.out.println("Engine Start");
    }
 
}
cs




하지만 이런 구조에서
버튼을 눌렀을 때, 시동이 아닌 램프를 키고 싶은 경우에는 다음과 같이 변경작업을 진행해야한다.



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 버튼 클래스
public class Button {
    private Engine engine;
    
    public Button(Engine engine) {
        this.engine = engine;
    }
    
    public void on() {
        engine.execute();
    }
    
}
 
// 엔진 
public class Lamp implements Execute {
 
    @Override
    public void execute() {
        System.out.println("Lamp On);
    }
}
cs



이런경우 동작이 변경될 때 
기존의 코드가 변경되면 안된다는 OCP를 위반하게 된다.

이를 다음과 같이 변경하여
이 문제를 해결하는 것이 command pattern이다.



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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
// 버튼 클래스 (동작이되는 Command 인터페이스를 가지고 있는다.)
public class Button {
    private Command executeEle;
    
    public Button(Command executeEle) {
        this.executeEle = executeEle;
    }
    
    public void setCommand(Command executeEle) {
        this.executeEle = executeEle;
    }
    
    public void press() {
        executeEle.execute();
    }
    
}
 
// command 인터페이스
public interface Command {
    void execute();
}
 
// 엔진 객체
public class Engine {
 
    public void on() {
        System.out.println("Engine Start");
    }
 
}
 
// 엔진의 실행동작을 가지고 있는 command
public class EngineOnCommand implements Command {
    private Engine engine;
    
    public EngineOnCommand(Engine engine) {
        this.engine = engine;
    }
    
    @Override
    public void execute() {
        this.engine.on();
    }
    
}
 
// Lamp
public class Lamp {
    
    public void on() {
        System.out.println("Lamp on");
    }
}
 
// Lamp를 실행시키는 command
public class LampOnCommand implements Command {
 
    private Lamp lamp;
    
    public LampOnCommand(Lamp lamp) {
        this.lamp = lamp;
    }
    
    @Override
    public void execute() {
        this.lamp.on();
    }
    
}
 
public class Main {
    public static void main(String[] args) {
        // 엔진 키기
        Button button = new Button(new EngineOnCommand(new Engine()));
        button.press();
        
        button.setCommand(new LampOnCommand(new Lamp()));
        button.press();
    }
 
}
cs



실행을 요구하는 Button클래스와 
각 동작을 구현하는 Command 클래스, 그 클래스 내부에 동작이되는 대상을 삽입한다.





한가지 적용 가능한 예를 보면
화면에 메뉴를 구성하는 MenuItem 객체를
공용해서 사용하는데 동작만 변경해서 사용하고 싶을 경우 

1. MenuItem 클래스에 command라는 인터페이스를 가지고 있게 설계한다.
2. 객체의 동작을 호출하는 클래스를 command를 구현하여 작성한다.
3. 2번의 클래스는 각자 자신이 호출할 객체를 가지고 있다.

이렇게 설계하면 MenuItem 클래스에서 동작을 
변경하여 사용할 수 있다.


결론
기능의 실행을 요구하는 호출자(invoke)와 실제 기능을 수행하는 수진자(Receiver)사이의 의존성을 제거한다. 따라서 실행될 기능의 변경에도 호출자 클래스를 수정없이 사용할 수 있어야 한다.

결국 서로간의 의존성 및 커플링을 줄일 수 있도록 설계해야 한다.



댓글()

옵저버 패턴 (Observer Pattern)

JAVA/Design Pattern|2018. 5. 27. 22:12

옵서버 패턴은 데이터의 변경이 발생되었을 경우 

상대 클래스나 객체에 의존하지 않으면서 데이터 변경을 통보하고자 할 때 유용하다.

예를 들면 새로운 파일이 추가되거나 기존 파일이 삭제되었을 때

여러 프로그램에게 동시에 알려주어야 모든 프로그램이 그 최신 내역을 반영할 수 있다.

예를 들어보자

만약 회원들의 정보를 보관하는

Member 클래스와 
Member들의 리스트를 출력해주는 Member View 클래스가 존재한다고 하였을 때,
다음과 같이 Member 객체가 추가 될 때 마다 Member View를 업데이트 해줄 수 있다.



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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
// 멤버클래스
public class Member {
    private List<String> memberNames = new ArrayList<>();
    private MemberView memberView;
 
    public List<String> getMemberNames() {
        return memberNames;
    }
 
    public void addMemberName(String memberName) {
        this.memberNames.add(memberName);
        memberView.update();
    }
 
    public void setMemberView(MemberView memberView) {
        this.memberView = memberView;
    }
 
}
 
// 멤버 뷰 클래스
public class MemberView {
    private Member member;
    
    public MemberView(Member member) {
        this.member = member;
    }
    
    public void update() {
        displayMember(member.getMemberNames());
    }
    
    private void displayMember(List<String> members) {
        Stream.of(members).forEach((data) -> {
            System.out.println(data);
        });
    }
}
 
// Main 클래스
public class Main {
    public static void main(String args[]) {
        Member member = new Member();
        
        MemberView view = new MemberView(member);
        member.setMemberView(view);
        
        member.addMemberName("babo");
        member.addMemberName("chun jae");
        member.addMemberName("korea");
    }
}
cs




하지만 위에 경우에는 다음과 같은 문제가 있다. 

현재는 하나의 뷰만 받을 수 있어서 Member의 값이 변경되어도 

리스트를 출력하는 MemberView 클래스에게만 업데이트 통보를하는 구조로 되어있어 새로운 뷰를 추가할 수 없다. 

핵심적인 문제는 멤버의 변경여부를 통보하는 클래스가 변경된다 하더라도
멤버의 내용은 그대로 사용할 수 있어야 한다는 점이다.

다음과 같이 변경해보자



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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
// 관리대상을 바라보고 있는 Observer 인터페이스
public interface Observer {
    void update();
    
    void display(List<Person> members);
}
 
// 멤버 객체의 변화를 지켜보고 있다가 변경시 팀원의 리스트를 출력하는 Observer
public class MemeberLeaderView implements Observer {
    private Member member;
 
    public MemeberLeaderView(Member member) {
        this.member = member;
    }
 
    @Override
    public void update() {
        display(member.getMemberNames());
    }
 
    @Override
    public void display(List<Person> members) {
        members.stream().forEach((data) -> {
            if (data.isLeader()) {
                System.out.println("leader is : " + data.getName());
            }
        });
    }
 
}
 
// 멤버 객체의 변화를 지켜보고 있다가 변경시 팀장을 출력하는 Observer
public class MemberView implements Observer {
    private Member member;
    
    public MemberView(Member member) {
        this.member = member;
    }
    
    @Override
    public void update() {
        display(member.getMemberNames());
    }
 
    @Override
    public void display(List<Person> members) {
        members.stream().forEach((data) -> {
            if (!data.isLeader()) {
                System.out.println("teamone name : " + data.getName());
            }
        });
        System.out.println();
    }
 
}
 
// 객체 대상이 되는 객체의 subject 클래스
public abstract class Subject {
    private List<Observer> observers = new ArrayList<>();
 
    public void attach(Observer view) {
        observers.add(view);
    }
    
    public void detach(Observer view) {
        observers.remove(view);
    }
    
    public void notifiyObservers() {
        observers.stream().forEach(data -> {
            data.update();
        });
    }
 
}
 
// 멤버 클래스
public class Member extends Subject {
    private List<Person> member = new ArrayList<>();
 
    public List<Person> getMemberNames() {
        return member;
    }
 
    public void addMemberName(Person person) {
        this.member.add(person);
        notifiyObservers();
    }
}
 
// person
public class Person {
    private boolean isLeader;
    private String name;
    
    public Person(String name, boolean isLeader) {
        this.isLeader = isLeader;
        this.name = name;
    }
 
    public boolean isLeader() {
        return isLeader;
    }
 
    public String getName() {
        return name;
    }
}
 
// main 클래스
public class Main {
    public static void main(String args[]) {
        Member member = new Member();
        
        member.attach(new MemeberLeaderView(member));
        member.attach(new MemberView(member));
        
        member.addMemberName(new Person("babo"true));
        member.addMemberName(new Person("chun jae"false));
        member.addMemberName(new Person("korea"false));
    }
}
cs




관찰대상 (subject)는 관찰자 observer들의 항목을 가지고 있으며
subject가 변경되었을 때 observer들에게 자신의 변경 사실을 통보하는 구조로 변경하였다.

하나의 관리대상이 아닌 여러 관리대상을 가지고 관리할 수 있도록 
observer를 인터페이스를 구현하였다.

실제로 

쿼리박스-S 프로젝트를 진행하면서
여러 쿼리박스-S 프로젝트를 진행하면서 
API를 subject, 각 사용자들의 쿼리박스-S를 observer로서 지정하여서 사용하였다.

api의 값이 변경될 경우 각 쿼리박스들에게 변경사실을 통보해줌으로써 쿼리박스-S들은 자신에 변경된 정책을 갱신할 수 있도록 설계한 적이 있었다.

잘만 활용하면 좋은 패턴인 것 같다.

댓글()

데코레이트 패턴 (Decorator Pattern)

JAVA/Design Pattern|2018. 5. 27. 22:11

데코레이트 패턴은 기본 기능에 추가할 수 있는 기능의 종류가 
많은 경우에 각 추가 기능을 Decorator 클래스로 정의한 후 필요한 Decorator 객체를 조합함으로써

추가 기능을 설계하는 방식이다.

문제가 되는 예를 확인해보자.

만약 

음식을 할 때, 준비를 해는 클래스 ReadyDish가 있다고 해보자.

생선 음식을 준비해야할 때는 ReadyDish 클래스에서 하는 ready() 메소드가 필요하며, 생선클래스를 위한 메소드 readyForFish()메소드가 필요하다.

그럴경우 기본적인 기능은 ReadyDish 클래스에 정의하고
추가적인 기능은 다음과 같이 하위클래스로서 상속받아서 진행할 수도 있다.



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package decorator;
 
public class ReadyDish {
    public void ready() {
        System.out.println("ready");
    }
}
 
 
package decorator;
 
public class RedayForFish extends ReadyDish {
    @Override
    public void ready() {
        super.ready();
        readyForFish();
    }
    
    public void readyForFish() {
        System.out.println("ready For Fish");
    }
}
cs




그런데 이런건 문제의 소지가 있다.

만약 고기요리 클래스가 늘어났을경우에는 RedayForMeat 클래스를 만들어야 하고 
그에 상응하는 readyForMeat() 메소드도 만들어야한다. 

이는 되게 불편하기에 데코레이터 패턴을 사용하여 정리하여야 한다.


1. 기본적으로 음식 가공을 준비하는 ready메소드를 제공하는 추상클래스 dish 생성한다.
2. 기본음식 준비를 하는 readyDish 클래스를 dish 클래스를 상속받아 구현한다.
3. 추가적인 생선요리, 고기요리 등에 사용될 클래스들을 따로 생성하고 기본적인 음식 준비를 위한 기능을 할 수 있도록 해주는 ReadyDishDecorator 클래스를 생성한다.





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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
package decorator;
 
public abstract class Dish {
    public abstract void ready();
}
 
package decorator;
 
public class ReadyDish extends Dish {
    @Override
    public void ready() {
        System.out.println("ready");
    }
}
 
package decorator;
 
public abstract class DishDecorator extends Dish {
    private Dish decoratedDish;
    
    public DishDecorator(Dish decoratedDish) {
        this.decoratedDish = decoratedDish;
    }
    
    @Override
    public void ready() {
        decoratedDish.ready();
    }
}
 
package decorator;
 
public class ReadyDishForFish extends DishDecorator {
 
    public ReadyDishForFish(Dish decoratedDish) {
        super(decoratedDish);
    }
    
    public void ready() {
        super.ready();
        readyForFish();
    }
    
    public void readyForFish() {
        System.out.println("ready For fish");
    }
 
}
 
package decorator;
 
public class ReadyDishForMeat extends DishDecorator {
 
    public ReadyDishForMeat(Dish decoratedDish) {
        super(decoratedDish);
    }
    
    public void ready() {
        super.ready();
        readyForMeat();
    }
    
    public void readyForMeat() {
        System.out.println("ready For meat");
    }
 
}
cs



이런 방식의 설계를 이용하면 추가 기능 조합별로 별도의 클래스를 구현하는 대신 
각 추가 기능에 해당하는 클래스의 객체를 조합해 추가 기능을 조합을 구현할 수 있다.

예를 들어보면 
생선도 준비하고 고기 음식을 준비해야 한다 했을 때
다음과 같이 하면 여러가지 항목들을 조합하여 필요에 따라 여기저기 합쳐서 사용할 수 있다.



1
2
3
4
5
6
7
Dish dishForFishMeat = new ReadyDishForMeat(new ReadyDishForFish(new ReadyDish()));
dishForFishMeat.ready();
 
// 출력결과 
ready
ready For fish
ready For meat
cs



이와 같은 설계는 추가 기능이 많을 수록 효과가 커서
여러가지 기능들이 자주 조합되어야 할때 섞여서 사용되어야 할때 사용하면 좋다.



댓글()

템플릿 메서드 패턴 (Template method)

JAVA/Design Pattern|2018. 5. 27. 22:09

템플릿 메서드 패턴에 대해 알아보기 위해
먼저 문제의 소지가 있는 상황을 알아보자.

[예시]
만약 특정 제품의 모터를 동작시키고자 할 때 
현재 모터가 동작중인지, 작업장에 문이 열려 있는지 확인하고 동작하도록 하는
로직을 다음과 같이 만들어보자



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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
package template;
 
public class Door {
    private DoorStatus doorStatus;
 
    public DoorStatus getDoorStatus() {
        return doorStatus;
    }
 
    public void setDoorStatus(DoorStatus doorStatus) {
        this.doorStatus = doorStatus;
    }
    
    public void open() {
        this.doorStatus = DoorStatus.OPENED;
    }
    
    public void close() {
        this.doorStatus = DoorStatus.CLOSED;
    }
}
 
package template;
 
public enum DoorStatus {
    CLOSED,
    OPENED
}
 
package template;
 
public enum MotorStatus {
    MOVING,
    STOPPED
}
 
package template;
 
public enum Direction {
    UP,
    DOWN
}
 
package template;
 
public class TrailMotor {
    private Door door;
    private MotorStatus mortorStatus;
    
    public TrailMotor(Door door, MotorStatus status) {
        this.door = door;
        this.mortorStatus = status; 
    }
 
    public Door getDoor() {
        return door;
    }
 
    public void setDoor(Door door) {
        this.door = door;
    }
 
    public MotorStatus getMortorStatus() {
        return mortorStatus;
    }
 
    public void setMortorStatus(MotorStatus mortorStatus) {
        this.mortorStatus = mortorStatus;
    }
    
    public void Move(Direction direction) {
        // 이미 모터가 동작중인 경우 움직이지 않는다.
        if (mortorStatus == MotorStatus.MOVING) {
            return;
        }
        
        // 문이 열려있는 경우 닫는다,
        if (door.getDoorStatus() == DoorStatus.OPENED) {
            door.close();
        }
 
        readyTrail();
        
        // 동작 시킨 후 모터상태 변경
        startMotor(direction);
        setMortorStatus(MotorStatus.MOVING);
    }
    
    public void startMotor(Direction direction) {
        
    }
    
}
cs



위와 같은 코드에는 어떤 문제가 있을까?

[문제]
만약 TrailMortor가 아니 자동차 모터를 동작해야 한다고 가정했을 때,
자동차 모터는 트레일 모터와 다른 부분이 존재하기 때문에 트레일 모터와 동일한 클래스에서 
일부가 추가/수정된 클래스를 추가로 또 만들어야 할 것이다.

만약 이 중복되는 코드들을 없애기 위해서
상속을 사용한다면 어떨까?





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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
package template;
 
public abstract class Motor {
    
    protected Door door;
    protected MotorStatus mortorStatus;
    
    public Motor(Door door) {
        this.door = door;
        this.mortorStatus = MotorStatus.STOPPED; 
    }
 
    public Door getDoor() {
        return door;
    }
 
    public void setDoor(Door door) {
        this.door = door;
    }
 
    public MotorStatus getMortorStatus() {
        return mortorStatus;
    }
 
    public void setMortorStatus(MotorStatus mortorStatus) {
        this.mortorStatus = mortorStatus;
    }
    
    public abstract void Move(Direction direction);
 
    public abstract void startMotor(Direction direction);
 
}
 
package template;
 
public class TrailMotor extends Motor {
    
    public TrailMotor(Door door) {
        super(door);
    }
    
    public void Move(Direction direction) {
        // 이미 모터가 동작중인 경우 움직이지 않는다.
        if (mortorStatus == MotorStatus.MOVING) {
            return;
        }
        
        // 문이 열려있는 경우 닫는다,
        if (door.getDoorStatus() == DoorStatus.OPENED) {
            door.close();
        }
 
        // 트레일 운전 시 특정 메서드 호출
        readyTrail();
        
        // 동작 시킨 후 모터상태 변경
        startMotor(direction);
        setMortorStatus(MotorStatus.MOVING);
    }
    
    public void startMotor(Direction direction) {
        
    }
 
    public void readyTrail() {}
    
}
 
package template;
 
public class CarMotor extends Motor {
    public CarMotor(Door door) {
        super(door);
    }
    
    public void Move(Direction direction) {
        // 이미 모터가 동작중인 경우 움직이지 않는다.
        if (mortorStatus == MotorStatus.MOVING) {
            return;
        }
        
        // 문이 열려있는 경우 닫는다,
        if (door.getDoorStatus() == DoorStatus.OPENED) {
            door.close();
        }
 
        // 트레일 운전 시 특정 메서드 호출
        readyCar();
        
        // 도
        startMotor(direction);
        setMortorStatus(MotorStatus.MOVING);
    }
    
    public void startMotor(Direction direction) {
        
    }
    
}
cs




위와같이 하면 어느정도 중복되는 코드를 제거할 수는 있으나,
Move, startMotor 메서드 부분에서 상당한 부분이 중복되는 것은 여전하다.

그래서 더욱 줄이기 위해서 템플릿 메서드 패턴을 적용해 보자.

템플릿 메서드 패턴

템플릿 메서드 패턴은 전체적으로 동일하면서 부분적으로는 다른 구문으로 구성된 메서드의 코드 중복을 최소화할 때 유용하다.

위의 코드를 템플릿 메서드 패턴을 적용할 경우

move 메소드를 공통 부분을 모두 모아놓아서 템플릿 메서드로 사용하고
트레일, 카 모터 등등에서 개별적인 움직이이 필요한 부분인 startMotor에서는 오버라이드하여 사용한다.

startMotor 메서드를 primitive 또는 hook 메서드라고 한다.



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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
package template;
 
public abstract class Motor {
    
    protected Door door;
    protected MotorStatus mortorStatus;
    
    public Motor(Door door) {
        this.door = door;
        this.mortorStatus = MotorStatus.STOPPED; 
    }
 
    public Door getDoor() {
        return door;
    }
 
    public void setDoor(Door door) {
        this.door = door;
    }
 
    public MotorStatus getMortorStatus() {
        return mortorStatus;
    }
 
    public void setMortorStatus(MotorStatus mortorStatus) {
        this.mortorStatus = mortorStatus;
    }
    
    public abstract void Move(Direction direction);
 
    public abstract void startMotor(Direction direction);
 
}
 
package template;
 
public class TrailMotor extends Motor {
    
    public TrailMotor(Door door) {
        super(door);
    }
    
    public void Move(Direction direction) {
        // 이미 모터가 동작중인 경우 움직이지 않는다.
        if (mortorStatus == MotorStatus.MOVING) {
            return;
        }
        
        // 문이 열려있는 경우 닫는다,
        if (door.getDoorStatus() == DoorStatus.OPENED) {
            door.close();
        }
 
        // 동작 시킨 후 모터상태 변경
        setMortorStatus(MotorStatus.MOVING);
 
        // hook 메서드
        startMotor(direction);
    }
    
    public void startMotor(Direction direction) {
        // 트레일 운전 시 특정 메서드 호출
        readyTrail();
        
    }
 
    public void readyTrail() {}
    
}
 
package template;
 
public class CarMotor extends Motor {
    public CarMotor(Door door) {
        super(door);
    }
    
    public void Move(Direction direction) {
        // 이미 모터가 동작중인 경우 움직이지 않는다.
        if (mortorStatus == MotorStatus.MOVING) {
            return;
        }
        
        // 문이 열려있는 경우 닫는다,
        if (door.getDoorStatus() == DoorStatus.OPENED) {
            door.close();
        }
 
        setMortorStatus(MotorStatus.MOVING);
        //hook 메서드
        startMotor(direction);
    }
    
    public void startMotor(Direction direction) {
        // 트레일 운전 시 특정 메서드 호출
        readyCar();
        
    }
    
}
 
cs


댓글()