'디자인패턴'에 해당되는 글 11건

  1. 2014.11.11 템플릿 메소드 패턴(Template Method Pattern)
  2. 2014.11.11 스트래티지 패턴(Strategy Pattern)
  3. 2014.11.11 퍼사드 패턴(Facade Pattern)
  4. 2014.11.11 커맨드 패턴(Command Pattern)
  5. 2014.11.11 싱글턴 패턴(Singleton Pattern)
  6. 2014.11.11 팩토리 패턴(Factory Pattern)
  7. 2014.11.11 옵저버 패턴(Observer Pattern)
  8. 2014.11.11 데코레이터 패턴(Decorator Pattern)
  9. 2014.11.11 어댑터 패턴(Adapter Pattern)
  10. 2012.11.02 싱글톤 패턴

템플릿 메소드 패턴(Template Method Pattern)

디자인패턴 2014. 11. 11. 14:26

1. 템플릿 메소드 패턴이란?


 템플릿 메소드 패턴에서는 메소드에서 알고리즘의 골격을 정의한다. 알고리즘의 여러 단계 중 일부는 추상(Abstract) 메소드로 정의하여 subclass에서 특정 알고리즘을 제공할 수 있다.
 템플릿 메소드를 이용하면 알고리즘의 구조는 그대로 유지하면서 subclass에서 알고리즘의 특정 단계를 재정의할 수 있다.



2. 템플릿 메소드 패턴 예시

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
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
// 템플릿 메소드가 들어있는 추상(Abstract) 클래스
public abstract class CaffeineBeverage {
 
 // 템플릿 메소드
 // 카페인이 들어있는 음료를 만들기 위한 알고리즘에 대한 템플릿.
 // Subclass에서 이 메소드를 override해서 아무렇게나 음료를 만들지 못하도록 final로 선언함.
 final void prepareRecipe() {
  boilWater();
  brew();
  pourInCup();
   
  // 손님이 첨가물을 넣어달라고 했을때만, addCondiments()를 호출함.
  if (customerWantsCondiments()) {
   addCondiments();
  }
 }
  
 // 두 메소드는 subclass에서 알아서 처리하게 추상(Abstract) 메소드로 선언함.
 // Subclass에서 알고리즘의 특정 단계를 제공해야만 하는 경우에 사용.
 abstract void brew();
 abstract void addCondiments();
  
 // 물을 끓이고 컵에 물을 따르는 행동은 같으므로 구상(Concrete) 메소드로 구현함.
 void boilWater() {
  System.out.println("물 끓이는 중");
 }
  
 void pourInCup() {
  System.out.println("컵에 따르는 중");
 }
  
 // Hook 메소드
 // Subclass에서 필요에 따라 override할 수 있는 메소드이다.
 // 알고리즘의 특정 부분이 선택적으로 적용되야 하는 경우에 사용함.
 boolean customerWantsCondiments() {
  return true;
 }
}
 
 
 
public class Coffee extends CaffeineBeverage {
 
 @Override
 void brew() {
  System.out.println("필터로 커피를 우려내는 중");
 }
 
 @Override
 void addCondiments() {
  System.out.println("설탕과 커피를 추가하는 중");
 }
  
 public boolean customerWantsCondiments() {
  String answer = getUserInput();
   
  if (answer.toLowerCase().startsWith("y")) {
   return true;
  } else {
   return false;
  }
 }
  
 private String getUserInput() {
  String answer = null;
   
  System.out.print("커피에 우유와 설탕을 넣어 드릴까요? (y/n) ");
   
  BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
  try {
   answer = in.readLine();
  } catch (IOException ioe) {
   System.err.println("IO 오류");
  }
   
  if (answer == null) {
   return "no";
  }
   
  return answer;
 }
}
 
 
 
public class Tea extends CaffeineBeverage {
 
 @Override
 void brew() {
  System.out.println("차를 우려내는 중");
 }
 
 @Override
 void addCondiments() {
  System.out.println("레몬을 추가하는 중");
 }
  
 public boolean customerWantsCondiments() {
  String answer = getUserInput();
   
  if (answer.toLowerCase().startsWith("y")) {
   return true;
  } else {
   return false;
  }
 }
  
 private String getUserInput() {
  String answer = null;
   
  System.out.print("차에 레몬을 넣어 드릴까요? (y/n) ");
   
  BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
  try {
   answer = in.readLine();
  } catch (IOException ioe) {
   System.err.println("IO 오류");
  }
   
  if (answer == null) {
   return "no";
  }
   
  return answer;
 }
}
 
 
public class BeverageTestDrive {
 
 /**
  * @param args
  */
 public static void main(String[] args) {
  Tea tea = new Tea();
  Coffee coffee = new Coffee();
   
  System.out.println("\n차 준비중...");
  tea.prepareRecipe();
   
  System.out.println("\n커피 준비중...");
  coffee.prepareRecipe();
 
 }
 
}





참고
Head FIrst Design Pattern : 스토리가 있는 패턴 학습법
http://www.gurubee.net/pages/viewpage.action?pageId=1507401



출처 - http://digitanomad.blogspot.kr/2013/04/template-method-pattern.html

'디자인패턴' 카테고리의 다른 글

스트래티지 패턴(Strategy Pattern)  (0) 2014.11.11
퍼사드 패턴(Facade Pattern)  (0) 2014.11.11
커맨드 패턴(Command Pattern)  (0) 2014.11.11
싱글턴 패턴(Singleton Pattern)  (0) 2014.11.11
팩토리 패턴(Factory Pattern)  (0) 2014.11.11
:

스트래티지 패턴(Strategy Pattern)

디자인패턴 2014. 11. 11. 14:25

1. 스트래티지 패턴이란?


 알고리즘군을 정의하고 각각을 캡슐화(Encapsulte)하여 교환해서 사용할 수 있도록 만든다. 스트래티지를 활용하면 알고리즘을 사용하는 클라이언트와는 독립적으로 알고리즘을 변경할 수 있다.



2. 스트래티지 패턴 예시

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
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
// 나는 행동에 대한 클래스에서 이 인터페이스를 구현(implement)하게 된다.
// 이것을 알고리즘군이라고 할 수 있다.
public interface FlyBehavior {
    public void fly();
}
 
 
public class FlyNoWay implements FlyBehavior {
 
    @Override
    public void fly() {
        System.out.println("저는 못 날아요.");
    }
 
}
 
 
public class FlyRocketPowered implements FlyBehavior {
 
    @Override
    public void fly() {
        System.out.println("로켓추진으로 날아갑니다.");
    }
 
}
 
 
public class FlyWithWings implements FlyBehavior {
 
    @Override
    public void fly() {
        System.out.println("날고 있어요!");
    }
 
}
 
 
public interface QuackBehavior {
    public void quack();
}
 
 
public class Quack implements QuackBehavior {
 
    @Override
    public void quack() {
        System.out.println("꽥");
    }
 
}
 
 
public class Squeak implements QuackBehavior {
 
    @Override
    public void quack() {
        System.out.println("삑");
    }
 
}
 
 
public class MuteQuack implements QuackBehavior {
 
    @Override
    public void quack() {
        System.out.println("<<조용~>>");
    }
 
}
 
 
public abstract class Duck {
     
    // 행동 인터페이스 형식의 레퍼런스 변수 두 개를 선언한다.
    // 모든 subclass에서 이 변수를 상속받는다.
    FlyBehavior flyBehavior;
    QuackBehavior quackBehavior;
     
    public Duck(){
         
    }
     
    public abstract void display();
     
    // 행동 클래스에 위임(Delegate)한다.
    public void performFly(){
        flyBehavior.fly();
    }
     
    // 행동 클래스에 위임(Delegate)한다.
    public void performQuack(){
        quackBehavior.quack();
    }
     
    public void swim(){
        System.out.println("모든 오리는 물에 뜹니다. 가짜 오리도 뜨죠");
    }
     
    // 오리의 행동을 동적으로 지정하는 방법
    public void setFlyBehavior(FlyBehavior fb){
        flyBehavior=fb;
    }
     
    public void setQuackBehavior(QuackBehavior qb){
        quackBehavior=qb;
    }
}
 
 
public class MallardDuck extends Duck {
 
     
    public MallardDuck() {
        // 꽥꽥거리는 소리를 처리할 때는 Quack 클래스를 사용하기 때문에
        // performQuack()이 호출되면 꽥꽥거리는 행동은 Quack 객체에게 위임된다.
        quackBehavior=new Quack();
         
        // FlyBehavior 형식으로는 FlyWithWings를 사용한다.
        flyBehavior=new FlyWithWings();
    }
    @Override
    public void display() {
        System.out.println("저는 물오리입니다.");
    }
 
}
 
 
public class ModelDuck extends Duck {
 
    public ModelDuck() {
        flyBehavior=new FlyNoWay();
        quackBehavior=new Quack();
    }
     
    @Override
    public void display() {
        System.out.println("저는 모형오리입니다.");
    }
 
}
 
 
public class MiniDuckSimulator {
 
    /**
     * @param args
     */
    public static void main(String[] args) {
        Duck mallard=new MallardDuck();
        // MallardDuck에서 상속받은 performQuack() 메소드가 호출된다.
        // 이 메소드에서 QuackBehavior에게 할 일을 위임(Delegate)한다.
        mallard.performQuack();
        mallard.performFly();
         
        Duck model=new ModelDuck();
        model.performFly();
        model.setFlyBehavior(new FlyRocketPowered());
        model.performFly();
    }
 
}









참고
Head FIrst Design Pattern : 스토리가 있는 패턴 학습법
http://www.gurubee.net/pages/viewpage.action?pageId=1507401




출처 - http://digitanomad.blogspot.kr/2013/03/strategy-pattern.html

:

퍼사드 패턴(Facade Pattern)

디자인패턴 2014. 11. 11. 14:25

1. 퍼사드 패턴이란?


 퍼사드 패턴은 복잡하게 구성된 서브시스템을 단순화된 인터페이스(Interface)를 통해 클라이언트에서 더 쉽게 사용하기 위한 용도로 쓰인다.
 이 패턴을 이용하면 클라이언트와 서브시스템이 서로 긴밀하게 연결되지 않아도 되고, 여러 클래스들이 복잡하게 얽혀서 시스템의 한 부분을 변경했을 때 다른 부분까지 줄줄이 고쳐야 되는 상황을 미리 방지하는 데 도움이 된다.



2. 퍼사드 패턴 예시

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
// 복잡한 서브시스템의 기능을 단순화한 인터페이스
public class HomeTheaterFacade {
 // 사용하고자 하는 서브시스템의 모든 구성요소들이 Instance 변수 형태로 저장된다.
 Amplifier amp;
 Tuner tuner;
 DvdPlayer dvd;
 CdPlayer cd;
 Projector projector;
 TheaterLights lights;
 Screen screen;
 PopcornPopper popper;
  
 public HomeTheaterFacade(Amplifier amp, Tuner tuner, DvdPlayer dvd, CdPlayer cd, Projector projector, Screen screen, TheaterLights lights, PopcornPopper popper) {
  this.amp = amp;
  this.tuner = tuner;
  this.dvd = dvd;
  this.cd = cd;
  this.projector = projector;
  this.screen = screen;
  this.lights = lights;
  this.popper = popper;
 }
  
 // 복잡한 모든 일들을 하나의 메소드로 간단하게 처리할 수 있다.
 // 각 작업은 서브시스템에 들어있는 구성요소(Component)들에게 위임(Delegate)된다.
 public void watchMovie(String movie) {
  System.out.println("Get ready to watch a movie...");
  popper.on();
  popper.pop();
  lights.dim(10);
  screen.down();
  projector.on();
  projector.wideScreenMode();
  amp.on();
  amp.setDvd(dvd);
  amp.setSurroundSound();
  amp.setVolume(5);
  dvd.on();
  dvd.play(movie);
 }
  
 public void endMovie() {
  System.out.println("Shutting movie theater down...");
  popper.off();
  lights.on();
  screen.up();
  projector.off();
  amp.off();
  dvd.stop();
  dvd.eject();
  dvd.off();
 }
}
 
 
public class HomeTheaterTestDrive {
 
 /**
  * @param args
  */
 public static void main(String[] args) {
  // 여기에 Components를 초기화함.
   
  HomeTheaterFacade homeTheater = new HomeTheaterFacade(amp, tuner, dvd, cd, projector, screen, lights, popper);
  homeTheater.watchMovie("Raiders of the Lost Ark");
  homeTheater.endMovie();
 }
 
}










참고
Head FIrst Design Pattern : 스토리가 있는 패턴 학습법
http://www.gurubee.net/pages/viewpage.action?pageId=1507401




출처 - http://digitanomad.blogspot.kr/2013/02/facade-pattern.html

:

커맨드 패턴(Command Pattern)

디자인패턴 2014. 11. 11. 14:24

1. 커맨드 패턴이란?


 커맨드 패턴에서는 요구 사항을 객체로 캡슐화 할 수 있으며, 매개변수를 써서 여러 가지 다른 요구 사항을 집어 넣을 수 있다. 그리고 작업을 요청하는 쪽과 작업을 처리하는 쪽을 분리할 수 있다. 또한 요청 내역을 에 저장하거나 로그로 기록할 수 있으며, 작업취소 기능도 지원 가능하다.

(1) Client
 클라이언트는 ConcreteCommand를 생성하고 Receiver를 설정한다.

(2) Receiver
 Receiver는 요구 사항을 수행하기 위해 어떤 일을 처리해야 하는지 알고 있는 객체

(3) Invoker
 Invoker에는 명령(Command)이 들어있으며, execute() 메소드를 호출함으로써 커맨드 객체에게 특정 작업을 수행해 달라는 요구를 한다.

(4) Command
 Command는 모든 커맨드 객체에서 구현해야 하는 인터페이스(Interface)이다. Receiver에게 시킬 모든 명령은 execute() 메소드 호출을 통해 수행되며, 이 메소드에서는 Receiver에 특정 작업을 처리하라는 지시를 전달한다.

(5) ConcreteCommand
 ConcreteCommand는 특정 행동과 Receiver 사이를 연결(bind)한다. Invoker에서 excute() 호출을 통해 요청을 하면 ConcreteCommand 객체에서 Receiever에 있는 메소드를 호출함으로써 그 작업을 처리한다.

 행동과 Receiver를 Command 객체에 집어넣고, execute()라는 메소드 하나만 외부에 공개하여 요구 사항이 들어있는 Receiver 객체를 캡슐화한다.



2. 커맨드 패턴 예시 

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
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
// 모든 커맨드 객체에서 구현해야 하는 인터페이스(Interface)
// 모든 명령은 execute() 메소드 호출을 통해 수행되며, 이 메소드에서는 Receiver에 특정 작업을 처리하라는 지시를 전달한다.
public interface Command {
 public void execute();
 public void undo();
}
 
// ConcreteCommand
// 이 클래스는 천장팬 속도를 높이는 행동과 Receiver인 CeilingFan 사이를 bind한다.
public class CeilingFanHighCommand implements Command {
 CeilingFan ceilingFan;
 int prevSpeed;
  
 public CeilingFanHighCommand(CeilingFan ceilingFan) {
  this.ceilingFan = ceilingFan;
 }
  
 // Invoker에서 이 메소드를 호출하면 천장팬 속도를 높인다.
 // 이 때, undo 기능을 구현하기 위해 이전에 있던 속도 값을 저장해둔다.
 @Override
 public void execute() {
  prevSpeed = ceilingFan.getSpeed();
  ceilingFan.high();
 }
 
 // 이전 속도를 바탕으로 속도를 재조정한다.
 @Override
 public void undo() {
  if (prevSpeed == CeilingFan.HIGH) {
   ceilingFan.high();
  } else if (prevSpeed == CeilingFan.MEDIUM) {
   ceilingFan.medium();
  } else if (prevSpeed == CeilingFan.LOW) {
   ceilingFan.low();
  } else if (prevSpeed == ceilingFan.OFF) {
   ceilingFan.off();
  }
 }
 
}
 
public class CeilingFanOffCommand implements Command {
 CeilingFan ceilingFan;
 int prevSpeed;
  
 public CeilingFanOffCommand(CeilingFan ceilingFan) {
  this.ceilingFan = ceilingFan;
 }
  
 @Override
 public void execute() {
  prevSpeed = ceilingFan.getSpeed();
  ceilingFan.off();
 }
 
 @Override
 public void undo() {
  if (prevSpeed == CeilingFan.HIGH) {
   ceilingFan.high();
  } else if (prevSpeed == CeilingFan.MEDIUM) {
   ceilingFan.medium();
  } else if (prevSpeed == CeilingFan.LOW) {
   ceilingFan.low();
  } else if (prevSpeed == CeilingFan.OFF) {
   ceilingFan.off();
  }
 }
 
}
 
// NoCommand 객체는 일종의 Null Object(널 객체)이다.
// 리턴할 객체는 없지만 클라이언트 쪽에서 null을 처리하지 않아도 되게 하고 싶을 때 활용하면 좋다.
public class NoCommand implements Command {
 
 @Override
 public void execute() {
   
 }
 
 @Override
 public void undo() {
   
 }
 
}
 
// Receiver
// 천장팬을 작동시키는 클래스
public class CeilingFan {
 public static final int HIGH = 3;
 public static final int MEDIUM = 2;
 public static final int LOW = 1;
 public static final int OFF = 0;
 String location;
 int speed;
  
 public CeilingFan(String location) {
  this.location = location;
  speed = OFF;
 }
  
 public void high() {
  speed = HIGH;
 }
  
 public void medium() {
  speed = MEDIUM;
 }
  
 public void low() {
  speed = LOW;
 }
  
 public void off() {
  speed = OFF;
 }
  
 public int getSpeed() {
  return speed;
 }
}
 
// Invoker
public class RemoteControl {
 Command[] onCommands;
 Command[] offCommands;
 Command undoCommand;
  
 public RemoteControl() {
  onCommands = new Command[7];
  offCommands = new Command[7];
   
  Command noCommand = new NoCommand();
  for (int i = 0; i < 7; i++) {
   onCommands[i] = noCommand;
   offCommands[i] = noCommand;
  }
  undoCommand = noCommand;
 }
  
 // setCommand() 메소드로 리모컨의 각 슬롯에 특정 명령을 지정해놓는다.
 public void setCommand(int slot, Command onCommand, Command offCommand) {
  onCommands[slot] = onCommand;
  offCommands[slot] = offCommand;
 }
  
 // 특정 슬롯의 ON 버튼이 눌러지면, 해당 슬롯의 ONCommand의 execute() 메소드를 호출한다.
 public void onButtonWasPushed(int slot) {
  onCommands[slot].execute();
  undoCommand = onCommands[slot];
 }
  
 public void offbuttonWasPushed(int slot) {
  offCommands[slot].execute();
  undoCommand = offCommands[slot];
 }
  
 public String toString() {
  StringBuffer stringBuff = new StringBuffer();
  stringBuff.append("\n-------Remote Control--------\n");
  for (int i = 0; i < onCommands.length; i++) {
   stringBuff.append("[slot " + i + "] " + onCommands[i].getClass().getName() + "   "+offCommands[i].getClass().getName() + "\n");
  }
  return stringBuff.toString();
 }
  
 public void undoButtonWasPushed() {
  undoCommand.undo();
 }
}
 
// Client
public class RemoteLoader {
 
 /**
  * @param args
  */
 public static void main(String[] args) {
  RemoteControl remoteControl = new RemoteControl();
   
  CeilingFan ceilingFan = new CeilingFan("Living Room");
 
  CeilingFanHighCommand ceilingFanHigh = new CeilingFanHighCommand(ceilingFan);
  CeilingFanOffCommand ceilingFanOff = new CeilingFanOffCommand(ceilingFan);
   
  remoteControl.setCommand(2, ceilingFanHigh, ceilingFanOff);
   
  System.out.println(remoteControl);
  remoteControl.onButtonWasPushed(0);
  remoteControl.offbuttonWasPushed(0);
  remoteControl.undoButtonWasPushed();
   
  remoteControl.onButtonWasPushed(2);
  remoteControl.offbuttonWasPushed(2);
  remoteControl.undoButtonWasPushed();
 }
 
}









참고
Head FIrst Design Pattern : 스토리가 있는 패턴 학습법
http://www.gurubee.net/pages/viewpage.action?pageId=1507401


출처 - http://digitanomad.blogspot.kr/2013/02/command-pattern.html




출처 - http://digitanomad.blogspot.kr/2013/02/command-pattern.html

'디자인패턴' 카테고리의 다른 글

스트래티지 패턴(Strategy Pattern)  (0) 2014.11.11
퍼사드 패턴(Facade Pattern)  (0) 2014.11.11
싱글턴 패턴(Singleton Pattern)  (0) 2014.11.11
팩토리 패턴(Factory Pattern)  (0) 2014.11.11
옵저버 패턴(Observer Pattern)  (0) 2014.11.11
:

싱글턴 패턴(Singleton Pattern)

디자인패턴 2014. 11. 11. 14:23

1. 싱글턴 패턴이란?


 싱글턴 패턴은 해당 클래스의 Instance가 하나만 만들어지고, 어디서든지 그 Instance  접근할 수 있도록 하기 위한 패턴이다. 보통 스레드 풀이나 캐시, 대화상자, 사용자 설정, 레지스트리 설정, 로그 기록용 객체, 디바이스 드라이버 등 객체가 여러 개 있는 것보다 하나만 있어야 하는 경우에 싱글턴 패턴을 유용하게 사용할 수 있다.



2. 싱글턴 패턴 예시


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 ChocolateBoiler {
 private boolean empty;
 private boolean boiled;
 // ChocolateBoiler 클래스의 유일한 Instance를 저장하기 위한 정적 변수
 private static ChocolateBoiler uniqueInstance;
  
 // 생성자(Constructor)를 private으로 선언했기 때문에 ChocolateBoiler에서만 클래스의 Instance를 만들 수 있다.
 private ChocolateBoiler() {
  empty = true;
  boiled = false;
 }
  
 // 클래스의 Instance를 만들어서 리턴해준다.
 public static ChocolateBoiler getInstance() {
  // uniqueInstance가 null이면 아직 Instance가 생성되지 않았다는 것을 알 수 있다.
  // 이렇게 해두면, Instance가 필요한 상황이 닥치기 전에는 아예 Instance를 생성하지 않을 수 있다. 이를 Lazy Instantiation이라고 한다.
  if (null == uniqueInstance) {
   uniqueInstance = new ChocolateBoiler();
  }
  return uniqueInstance;
 }
  
 public void fill() {
  if (isEmpty()) {
   empty = false;
   boiled = false;
   // 보일러에 우유/초콜릿을 혼합한 재료를 집어넣음.
  }
 }
  
 public void drain() {
  if (!isEmpty() && isBoiled()) {
   // 끓인 재료를 다음 단계로 넘김.
   empty = true;
  }
 }
  
 public void boil() {
  if (!isEmpty() && !isBoiled()) {
   // 재료를 끓임
   boiled = true;
  }
 }
  
 public boolean isEmpty() {
  return empty;
 }
  
 public boolean isBoiled() {
  return empty;
 }
}



3. 멀티 스레딩 문제 해결 방법


 다중 스레드에서는 getInstance()를 할 때, 싱글턴 클래스에서 약간의 시간 차이로 서로 다른 객체가 생성될 수 있다. 이를 해결하기 위해서는 다음과 같은 방법이 있다.

(1) getInstance()를 동기화(Synchronize) 시키기
 getInstance() 메소드가 애플리케이션에 큰 부담을 주지 않는다면 synchronized 키워드를 사용해서 한 스레드가 메소드 사용을 끝내기 전까지 다른 스레드가 기다리게 할 수 있다. 하지만 메소드를 동기화시키면 성능이 100배 정도 저하된다고 한다.

1
2
3
4
5
6
public static synchronized ChocolateBoiler getInstance() {
 if (null == uniqueInstance) {
  uniqueInstance = new ChocolateBoiler();
 }
 return uniqueInstance;
}


(2) 처음부터 Instance를 생성하기
 애플리케이션에서 반드시 싱글턴을 사용한 클래스의 Instance를 생성하고, 그 Instance를 항상 사용하거나 또는 Instance를 실행중에 수시로 만들고 관리하기가 성가시다면 처음부터 싱글턴 Instance를 만들어버리는 것도 좋은 방법이다.

1
2
3
4
5
6
7
8
9
public class ChocolateBoiler {
 
 // 정적 초기화 부분(Static Initializer)에서 ChocolateBoiler의 Instance를 생성한다.
 private static ChocolateBoiler uniqueInstance = new ChocolateBoiler();
 
 public static ChocolateBoiler getInstance() {
  return uniqueInstance;
 }
}


(3) DCL(Double-checking Locking)을 사용해서 getInstance()에서 동기화 시키기
 DCL을 사용하면, 일단 Instance가 생성되어 있는지 확인한 다음, 생성되어 있지 않았을 때만 동기화를 할 수 있다. 이렇게 하면 처음에만 동기화를 하고, 나중에는 동기화를 하지 않아도 되서 getInstance() 메소드를 모두 동기화 해서 발생하는 오버헤드를 줄일 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class ChocolateBoiler {
 
 private volatile static ChocolateBoiler uniqueInstance = new ChocolateBoiler();
 
 public static ChocolateBoiler getInstance() {
  // Instance가 있는 지 확인하고, 없으면 동기화된 블럭으로 들어간다.
  if (null == uniqueInstance) {
   synchronized (ChocolateBoiler.class) {
    if (null == uniqueInstance) {
     uniqueInstance = new ChocolateBoiler();
    }
   }
  }
  return uniqueInstance;
 }
}


 하지만 DCL은 자바 1.4 버전 및 이전 JVM 중에는 volatile 키워드를 사용하더라도 동기화가 잘 안되는 것이 많다고 하니 자바 1.5 버전부터 사용하도록 하자.









참고
Head FIrst Design Pattern : 스토리가 있는 패턴 학습법
http://www.gurubee.net/pages/viewpage.action?pageId=1507401


출처 - http://digitanomad.blogspot.kr/2013/02/singleton-pattern.html

'디자인패턴' 카테고리의 다른 글

퍼사드 패턴(Facade Pattern)  (0) 2014.11.11
커맨드 패턴(Command Pattern)  (0) 2014.11.11
팩토리 패턴(Factory Pattern)  (0) 2014.11.11
옵저버 패턴(Observer Pattern)  (0) 2014.11.11
데코레이터 패턴(Decorator Pattern)  (0) 2014.11.11
:

팩토리 패턴(Factory Pattern)

디자인패턴 2014. 11. 11. 14:22

1. 팩토리 패턴이란?


 팩토리는 구상(Concrete) 클래스가 아닌 추상(Abstract) 클래스/인터페이스에 맞춰서 코딩할 수 있게 해 주는 강력한 기법이다. 모든 팩토리 패턴에서는 애플리케이션의 구상(Concrete) 클래스에 대한 의존성을 줄여줌으로써 느슨한 결합(Loose Coupling)을 도와준다. 그리고 객체 생성을 캡슐화(Encapsulation)할 수 있다.

(1) 팩토리 메소드 패턴
 팩토리 메소드 패턴은 클라이언트 코드와 Instance를 만들어야 할 구상(Concrete) 클래스를 분리시켜야 할 때 사용한다. 또한 어떤 구상(Concrete) 클래스를 필요로 하게 될지 미리 알 수 없는 경우에도 매우 유용하다.
 팩토리 메소드 패턴에서는 객체를 생성하기 위한 인터페이스를 만들고, 어떤 클래스의 Instance를 만들지는 Subclass에게 위임(Delegate)한다. Subclass에서는 팩토리 메소드를 구현하여 객체를 생성한다.

(2) 추상(Abstract) 팩토리 패턴
 추상(Abstract) 팩토리 패턴은 클라이언트에서 구상(Concrete) 클래스에 직접 의존하지 않고도 서로 연관된 일련의 제품들(제품군)을 만들어야 할 때 사용한다.
 추상(Abstract) 팩토리 패턴에서는 객체 구성(Composition)을 활용한다. 객체 생성이 팩토리 인터페이스에서 선언한 메소드들에서 구현된다.



2. 팩토리 패턴 예시

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
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
// 추상(Abstract) 팩토리
// 추상(Abstract) 팩토리를 통해서 제품군(원재료들)을 생성하기 위한 인터페이스를 제공할 수 있다.
// 각 Subclass에서는 지역별 사정에 맞는 재료들을 구현한다.
public interface PizzaIngredientFactory {
 // 추상(Abstract) 팩토리에서 제품을 생산하기 위한 메소드를 팩토리 메소드 패턴으로 구현하는 경우가 있다.
 public Dough createDough();
 public Sauce createSauce();
 public Cheese createCheese();
 public Veggies[] createVeggies();
 public Pepperoni createPepperoni();
 public Clams createClam();
}
 
 
public class NYPizzaIngredientFactory implements PizzaIngredientFactory {
 
 @Override
 public Dough createDough() {
  return new ThinCrustDough();
 }
 
 @Override
 public Sauce createSauce() {
  return new MarinaraSauce();
 }
 
 @Override
 public Cheese createCheese() {
  return new ReggianoCheese();
 }
 
 @Override
 public Veggies[] createVeggies() {
  Veggies veggies[] = { new Garlic(), new Onion(), new Mushroom(), new RedPepper() };
  return veggies;
 }
 
 @Override
 public Pepperoni createPepperoni() {
  return new SlicedPepperoni();
 }
 
 @Override
 public Clams createClam() {
  return new FreshClams();
 }
}
 
 
public class ChicagoPizzaIngredientFactory implements PizzaIngredientFactory {
 
 @Override
 public Dough createDough() {
  return new ThickCrustDough();
 }
 
 @Override
 public Sauce createSauce() {
  return new PlumTomatoSauce();
 }
 
 @Override
 public Cheese createCheese() {
  return new Mozzarella();
 }
 
 @Override
 public Veggies[] createVeggies() {
  Veggies veggies[] = { new EggPlant(), new Spinach(), new BlackOlives() };
  return veggies;
 }
 
 @Override
 public Pepperoni createPepperoni() {
  return new SlicedPepperoni();
 }
 
 @Override
 public Clams createClam() {
  return new FrozenClam();
 }
 
}
 
 
import java.util.*;
 
public abstract class Pizza {
 String name;
 String dough;
 String sauce;
 Veggies veggies[];
 Cheese cheese;
 Pepperoni pepperoni;
 Clams clam;
  
 // 피자를 만드는 데 필요한 재료들을 정돈
 // 피자에는 여러 종류가 있고, 지역마다 사용하는 재료가 다르므로 팩토리 메소드를 사용함.
 abstract void prepare();
  
 void bake() {
  System.out.println("Bake for 25 minutes at 350");
 }
  
 void cut() {
  System.out.println("Cutting the pizza into diagonal slices");
 }
  
 void box() {
  System.out.println("Place pizza in oficial PizzaStore box");
 }
  
 public String getName() {
  return name;
 }
  
 public void setName(String name) {
  this.name = name;
 }
  
}
 
 
public class CheesePizza extends Pizza {
 PizzaIngredientFactory ingredientFactory;
  
 public CheesePizza(PizzaIngredientFactory ingredientFactory) {
  this.ingredientFactory = ingredientFactory;
 }
  
 @Override
 void prepare() {
  System.out.println("Preparing " + name);
  // 사용하는 재료에 따라 피자 종류가 달라진다. 그래서 IngredientFactory에 따라 피자가 다양하게 만들어질 수 있다.
  dough = ingredientFactory.createDough();
  cheese = ingredientFactory.createCheese();
  sauce = ingredientFactory.createSauce();
 }
 
}
 
 
public class ClamPizza extends Pizza {
 PizzaIngredientFactory ingredientFactory;
  
 public ClamPizza(PizzaIngredientFactory ingredientFactory) {
  this.ingredientFactory = ingredientFactory;
 }
  
 @Override
 void prepare() {
  System.out.println("Preparing " + name);
  dough = ingredientFactory.createDough();
  sauce = ingredientFactory.createSauce();
  cheese = ingredientFactory.createCheese();
  clam = ingredientFactory.createClam();
 }
 
}
 
 
public abstract class PizzaStore {
 
 public Pizza orderPizza(String type){
  Pizza pizza;
   
  // createPizza 메소드를 호출하면 PizzaStore를 구현한 Subclass에서 처리가 된다.
  // Pizza 객체의 Instance를 만드는 일은 PizzaStore의 Subclass에 있는 createPizza() 메소드에서 처리한다.
  // 실제로 어떤 구상(Concrete) 클래스에서 작업을 처리되고 있는지 알 수 없기 때문에, PizzaStore와 Pizza 클래스는 서로 분리되어 있다고 할 수 있다.
  pizza = createPizza(type);
   
  pizza.prepare();
  pizza.bake();
  pizza.cut();
  pizza.box();
   
  return pizza;
 }
  
 
 // 팩토리 메소드는 클라이언트(Superclass에 있는 orderPizza()와 같은 코드)에서 실제로 생성되는 Concrete 객체(Pizza 객체)가 무엇인지 알 수 없게 만드는 역할도 한다.
 protected abstract Pizza createPizza(String type);
}
 
 
public class NYPizzaStore extends PizzaStore {
 
 @Override
 protected Pizza createPizza(String item) {
  Pizza pizza = null;
  PizzaIngredientFactory ingredientFactory = new NYPizzaIngredientFactory();
   
  if (item.equals("cheese")) {
   pizza = new CheesePizza(ingredientFactory);
   pizza.setName("New York Style Cheese Pizza");
  } else if (item.equals("veggie")) {
   pizza = new VeggiePizza(ingredientFactory);
   pizza.setName("New York Style Veggie Pizza");
  } else if (item.equals("clam")) {
   pizza = new ClamPizza(ingredientFactory);
   pizza.setName("New York Style Clam Pizza");
  } else if (item.equals("pepperoni")) {
   pizza = new PepperoniPizza(ingredientFactory);
   pizza.setName("New York Style Pepperoni Pizza");
  }
  return pizza;
 }
 
}
 
 
public class ChicagoPizzaStore extends PizzaStore {
 
 @Override
 protected Pizza createPizza(String item) {
  Pizza pizza = null;
  PizzaIngredientFactory ingredientFactory = new ChicagoPizzaIngredientFactory();
   
  if (item.equals("cheese")) {
   pizza = new CheesePizza(ingredientFactory);
   pizza.setName("Chicago Style Cheese Pizza");
  } else if (item.equals("veggie")) {
   pizza = new VeggiePizza(ingredientFactory);
   pizza.setName("Chicago Style Veggie Pizza");
  } else if (item.equals("clam")) {
   pizza = new ClamPizza(ingredientFactory);
   pizza.setName("Chicago Style Clam Pizza");
  } else if (item.equals("pepperoni")) {
   pizza = new PepperoniPizza(ingredientFactory);
   pizza.setName("Chicago Style Pepperoni Pizza");
  }
   
  return pizza;
 }
}
 
 
public class PizzaTestDrive {
 
 /**
  * @param args
  */
 public static void main(String[] args) {
  PizzaStore nyStore = new NYPizzaStore();
  PizzaStore chicagoStore = new ChicagoPizzaStore();
   
  Pizza pizza = nyStore.orderPizza("cheese");
  System.out.println("Ethan ordered a " + pizza.getName() + "\n");
   
  pizza = chicagoStore.orderPizza("cheese");
  System.out.println("Joel ordered a " + pizza.getName() + "\n");
 }
 
}







참고
Head FIrst Design Pattern : 스토리가 있는 패턴 학습법
http://www.gurubee.net/pages/viewpage.action?pageId=1507401


출처 - http://digitanomad.blogspot.kr/2013/02/1.html

:

옵저버 패턴(Observer Pattern)

디자인패턴 2014. 11. 11. 14:22

1. 옵저버 패턴이란?


 옵저버 패턴(Observer Pattern)에서는 한 객체(Subject)의 상태가 바뀌면 그 객체에 의존하는 다른 객체들(Observer)한테 연락이 가고 자동으로 내용이 갱신되는 방식으로 일대다(one-to-many) 의존성을 정의한다.



2. 옵저버 패턴 예시


(1) 소스 코드

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
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
public interface Subject {
 // 옵저버를 관리하기 위한 인터페이스
 public void registerObserver(Observer o);
 public void removeObserver(Observer o);
 // 등록된 옵저버들에게 데이터를 전달
 public void notifyObservers();
}
 
 
public interface Observer {
 // Subject가 보낸 자료를 처리
 public void update(float temp, float humidity, float pressure);
}
 
 
public interface DisplayElement {
 public void display();
}
 
 
// 기상 정보를 옵저버들에게 나누어 줄 것이므로, WeatherData가 Subject가 된다.
// 그러므로, Subject 인터페이스를 implements 한다.
public class WeatherData implements Subject{
 private ArrayList<observer> observers;
 private float temperature;
 private float humidity;
 private float pressure;
 
  
 public WeatherData() {
  observers = new ArrayList<observer>();
 }
  
 
 @Override
 public void registerObserver(Observer o) {
  observers.add(o);
 }
 
 
 @Override
 public void removeObserver(Observer o) {
  int i = observers.indexOf(o);
  if (i >= 0) {
   observers.remove(i);
  }
 }
 
 
 @Override
 public void notifyObservers() {
  for (int i = 0; i < observers.size(); i++) {
   Observer observer = (Observer)observers.get(i);
   observer.update(getTemperature(), getHumidity(), getPressure());
  }
 }
  
 
 public void measurementsChanged() {
  this.notifyObservers();
 }
  
 
 public void setMeasurements(float temperature, float humidity, float pressure) {
  // 기상정보 측정 값이 변경되면 measurementsChanged 메소드가 호출되고,
  // 이어서 notifyObservers 메소드를 통해 옵저버들에게 기상정보가 보내진다.
  this.temperature = temperature;
  this.humidity = humidity;
  this.pressure = pressure;
  this.measurementsChanged();
 }
 
 
 private float getTemperature() {
  return temperature;
 }
 
 
 private float getHumidity() {
  return humidity;
 }
 
 
 private float getPressure() {
  return pressure;
 }
}
 
 
// 현재 기상 상태를 표시하는 장치이므로, 기상 정보를 구독하는 Observer가 된다.
// 그러므로 Observer 인터페이스를 implements 한다.
public class CurrentConditionDisplay implements Observer, DisplayElement {
 private float temperature;
 private float humidity;
 private Subject weatherData;
  
 
 public CurrentConditionDisplay(Subject weatherData) {
  // 현재 기상 상태 표시하는 장치 생성과 동시에 WeatherData 클래스에 옵저버로 등록한다.
  this.weatherData = weatherData;
  weatherData.registerObserver(this);
 }
 
 
 @Override
 public void display() {
  System.out.println("Current conditions : " + temperature + "F degrees and " + humidity + "% humidity");
 }
 
 
 @Override
 public void update(float temperature, float humidity, float pressure) {
  // Subject로부터 받은 데이터를 update 메소드에서 처리한다.
  this.temperature = temperature;
  this.humidity = humidity;
  this.display();
 }
 
}
 
 
// 옵저버 패턴을 적용한 가상 기상 정보 시스템이다.
public class WeatherStation {
 
 
 /**
 
  * @param args
 
  */
 public static void main(String[] args) {
  // Subject 객체를 생성한다.
  WeatherData weatherData = new WeatherData();
   
  // WeatherData 클래스의 옵저버로 등록을 한다.
  CurrentConditionDisplay currentDisplay = new CurrentConditionDisplay(weatherData);
  HeatIndexDisplay heatIndexDisplay = new HeatIndexDisplay(weatherData);
  weatherData.setMeasurements(80, 65, 30.4f);
 }
 
}
</observer></observer>


(2) 결과






3. 자바 내장 라이브러리를 이용한 옵저버 패턴 예시


(1) 소스 코드

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
// java.util.Observable 클래스를 상속받아 Subject를 구현한다.
public class WeatherData extends Observable {
 private float temperature;
 private float humidity;
 private float pressure;
  
 public WeatherData() {
  // TODO Auto-generated constructor stub
 }
  
 public void measurementsChanged() {
  // setChanged 메소드는 데이터가 변경되었다는 것을 밝히기 위한 메소드로 변경되었다면 true, 그렇지 않다면 false를 리턴한다.
  // setChanged 메소드를 통해 옵저버들에게 데이터를 전달하기 위한 조건을 줘서 필요할 때만 전달되어지도록 제한을 할 수 있다.
  setChanged();
  notifyObservers();
 }
  
 public void setMeasurements(float temperature, float humidity, float pressure) {
  this.temperature = temperature;
  this.humidity = humidity;
  this.pressure = pressure;
  measurementsChanged();
 }
  
 public float getTemperature() {
  return temperature;
 }
  
 public float getHumidity() {
  return humidity;
 }
  
 public float getPressure() {
  return pressure;
 }
}
 
 
public interface DisplayElement {
 public void display();
}
 
 
public class CurrentConditionsDisplay implements Observer, DisplayElement{
 Observable observable;
 private float temperature;
 private float humidity;
  
 public CurrentConditionsDisplay(Observable observable) {
  this.observable = observable;
  observable.addObserver(this);
 }
  
 @Override
 public void display() {
  System.out.println("Current conditions: " + temperature + "F degrees and " + humidity + "% humidity");
 }
 
 @Override
 public void update(Observable o, Object arg) {
  if (o instanceof WeatherData) {
   WeatherData weahterData = (WeatherData)o;
   this.temperature = weahterData.getTemperature();
   this.humidity = weahterData.getHumidity();
   display();
  }
 }
 
}
 
 
public class WeatherStation {
 
 /**
  * @param args
  */
 public static void main(String[] args) {
  WeatherData weatherData = new WeatherData();
   
  CurrentConditionsDisplay currentDisplay = new CurrentConditionsDisplay(weatherData);
 }
 
}


(2) 결과






4. java.util.Observable의 단점


(1) Observable은 클래스이다.
1) Observable이 클래스기 때문에 서브 클래스를 만들어야 한다는 점이 문제가 된다. 이미 다른 Superclass를 상속하고 있는 클래스는 Observable 클래스를 상속할 수 없기 때문이다. 그래서 재사용성에 제약이 생기게 된다.
2) Observable 인터페이스라는 것이 없기 때문에 자바에 내장된 Observer API하고 잘 맞는 클래스를 직접 구현하는 것이 불가능하다. java.util 구현을 멀티스레드로 구현한다거나 하는 일이 불가능하다.

(2) Observable 클래스의 핵심 메소드를 외부에서 호출할 수 없다.
 Observable API에서 setChanged 메소드가 protected로 선언되어 있다. 그러므로, Observable을 상속한 Subclass에서만 setChanged 메소드를 호출할 수 있다. 결국 어떤 클래스를 만들고, Observable의 Subclass를 Instance 변수로 사용하는 방법을 쓸 수가 없다.




출처 - http://digitanomad.blogspot.kr/2013/01/observer-pattern.html

'디자인패턴' 카테고리의 다른 글

싱글턴 패턴(Singleton Pattern)  (0) 2014.11.11
팩토리 패턴(Factory Pattern)  (0) 2014.11.11
데코레이터 패턴(Decorator Pattern)  (0) 2014.11.11
어댑터 패턴(Adapter Pattern)  (0) 2014.11.11
싱글톤 패턴  (0) 2012.11.02
:

데코레이터 패턴(Decorator Pattern)

디자인패턴 2014. 11. 11. 14:19

1. 데코레이터 패턴이란?


 데코레이터 패턴에서는 객체에 추가 요소를 구성(Composition)과 위임(Delegate)를 통해 동적으로 더할 수 있다. Concrete Component를 감싸는 데코레이터(Decorator)를 사용하면 Subclass를 만드는 경우에 비해 훨씬 유연하게 기능을 확장할 수 있다.



2. 데코레이터 패턴 예시

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
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
public abstract class Beverage {
 String description = "";
  
 public String getDescription() {
  return description;
 }
  
 // SubClass에서 구현해야 함.
 public abstract double cost();
}
 
 
// 음료를 구현하기 위해 Beverage 클래스를 상속한다.
public class DarkRoast extends Beverage{
 public DarkRoast() {
  description = "DarkRoast Coffee";
 }
  
 @Override
 public double cost() {
  // 음료 클래스에서는 첨가물 가격을 걱정할 필요가 없다.
  // 그냥 DarkRoast의 가격을 리턴하면 된다.
  return .99;
 }
 
}
 
 
public class Decaf extends Beverage {
 public Decaf() {
  description = "Decaf Coffee";
 }
  
 @Override
 public double cost() {
  return 1.05;
 }
 
}
 
 
public class Espresso extends Beverage{
 
 public Espresso() {
  description = "Espresso";
 }
  
 @Override
 public double cost() {
  return 1.99;
 }
 
}
 
 
public class HouseBlend extends Beverage{
 public HouseBlend() {
  description = "HouseBlend Coffee";
 }
 
 @Override
 public double cost() {
  return .89;
 }
}
 
 
// 커피의 첨가물(Condiment)을 나타내는 Abstract 클래스(Decorator 클래스)
// 데코레이터 클래스의 형식은 그 클래스가 감싸고 있는 클래스의 형식을 반영한다.
// 그러므로, Beverage 객체가 들어갈 자리에 들어갈 수 있어야 하므로 Beverage 클래스를 상속한다.
public abstract class CondimentDecorator extends Beverage {
 public abstract String getDescription();
}
 
 
// Mocha는 Decorator이기 때문에 CondimentDecorator를 상속한다.
public class Mocha extends CondimentDecorator {
 // Wrapping 하고자 하는 음료를 저장하기 위한 Instance 변수
 Beverage beverage;
  
 public Mocha(Beverage beverage) {
  // Instance 변수를 감싸고자 하는 객체를 설정하기 위한 생성자.
  this.beverage = beverage;
 }
 
// 데코레이터에서는 자기가 감싸고 있는 Component의 메소드를 호출한 결과에 새로운 기능을 더함으로써 행동을 확장한다.
 @Override
 public String getDescription() {
  // Decorate 하고 있는 객체에 작업을 Delegate한 다음, 그 결과에 ", Mocha"를 추가한 결과를 return한다.
  return beverage.getDescription() + ", Mocha";
 }
 
 @Override
 public double cost() {
  // Decorate 하고 있는 객체에 가격을 구하는 작업을 Delegate해서 음료 자체의 값을 구하고, Mocha 가격을 더해서 합을 return한다.
  return .20 + beverage.cost();
 }
  
}
 
 
public class Soy extends CondimentDecorator {
 Beverage beverage;
  
 public Soy(Beverage beverage) {
  this.beverage = beverage;
 }
  
 @Override
 public String getDescription() {
  return beverage.getDescription() + ", Soy";
 }
 
 @Override
 public double cost() {
  return beverage.cost() + .15;
 }
 
}
 
 
public class SteamMilk extends CondimentDecorator {
 Beverage beverage;
  
 public SteamMilk(Beverage beverage) {
  this.beverage = beverage;
 }
  
 @Override
 public String getDescription() {
  return beverage.getDescription() + ", SteamMilk";
 }
 
 @Override
 public double cost() {
  return .10 + beverage.cost();
 }
 
}
 
 
public class Whip extends CondimentDecorator {
 Beverage beverage;
  
 public Whip(Beverage beverage) {
  this.beverage = beverage;
 }
  
 @Override
 public String getDescription() {
  return beverage.getDescription() + ", Whip";
 }
 
 @Override
 public double cost() {
  return .10 + beverage.cost();
 }
 
}
 
 
public class StarbuzzCoffee {
 
 /**
  * @param args
  */
 public static void main(String[] args) {
  Beverage beverage = new Espresso();
  System.out.println(beverage.getDescription() + " $" + beverage.cost());
   
  // 아래와 같은 식으로 기본 음료에 첨가물들을 Wrapping해서 가격과 설명을 추가할 수 있다.
  Beverage beverage2 = new DarkRoast();
  beverage2 = new Mocha(beverage2);
  beverage2 = new Mocha(beverage2);
  beverage2 = new Whip(beverage2);
  System.out.println(beverage2.getDescription() + " $" + beverage2.cost());
   
  Beverage beverage3 = new HouseBlend();
  beverage3 = new Soy(beverage3);
  beverage3 = new Mocha(beverage3);
  beverage3 = new Whip(beverage3);
  System.out.println(beverage3.getDescription() + " $" + beverage3.cost());
 }
 
}




3. JAVA I/O


(1) 데코레이터 패턴을 이용한 java.io 패키지
1) InputStream은 추상 구성요소(Abstract Component)이며, FileInputStreamStringBufferInputStream,ByteArrayInputStreamObjectInputStream 등이 데코레이터로 Wrapping될 Concrete Component 역할을 하며 InputStream을 상속하고 있다. 그리고 FilterInputStream이 바로 추상 데코레이터(Abstract Decorator)이며 구상 구성요소(Concrete Component)들을 Wrapping 하기 위해 InputStream과 형식을 동일하게 해야 하기 때문에 InputStream을 상속한다.
 PushbackInputStream, BufferdInputStreamDataInputStreamLineNumberInputStream 등은 구상 데코레이터(Concrete Decorator)들이다.
2) OutputStream의 InputStream의 디자인도 똑같고, Reader/Writer Stream(문자 기반의 데이터를 처리하기 위한 Stream)도 거의 유사하게 디자인 되어 있다.

(2) 자바 I/O 데코레이터 예시

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
import java.io.*;
 
// InputStream을 상속한 Abstract Decorator인 FilterInputStream을 상속한다.
public class LowercaseInputStream extends FilterInputStream {
 
 protected LowercaseInputStream(InputStream in) {
  super(in);
 }
  
 @Override
 public int read() throws IOException {
  int c = super.read();
  return (c == -1 ? c : Character.toLowerCase((char)c));
 }
  
 @Override
 public int read(byte[] b, int off, int len) throws IOException {
  int result = super.read(b, off, len);
  for (int i = off; i < off + result; i++) {
   b[i] = (byte) Character.toLowerCase((char)b[i]);
  }
  return result;
 }
 
}
 
 
public class InputTest {
 
 /**
  * @param args
  */
 public static void main(String[] args) {
  int c;
  try {
   // FileInputStream을 만들고 BufferedInputStream과 새로 만든 LowercaseInputStream으로 감싼다.
   InputStream in = new LowercaseInputStream(new BufferedInputStream(new FileInputStream("test.txt")));
    
   while((c = in.read()) >= 0) {
    System.out.print((char)c);
   }
    
   in.close();
  } catch (Exception e) {
   e.printStackTrace();
  }
 }
 
}



4. 데코레이터 패턴의 단점


(1) 데코레이터 패턴을 사용하면 자잘한 객체들이 매우 많이 추가될 수 있고, 데코레이터를 너무 많이 사용하면 코드가 필요 이상으로 복잡해질 수도 있다. 

(2) 구성요소(Component)를 초기화하는 데 필요한 코드가 훨씬 복잡해진다.
 구성요소(Component) Instance만 만든다고 해서 일이 끝나는게 아니라 많은 데코레이터로 Wrapping 해야 하는 경우가 있다. 그래서 데코레이터 패턴은 팩토리 패턴이나 빌더 패턴과 함께 사용된다.

(3) 데코레이터 패턴은 구상 구성요소(Concrete Component)_의 형식을 알아내서 그 결과를 바탕으로 어떤 작업을 처리하는 코드(특정 형식에 의존하는 클라이언트 코드)에는 적용할 수 없다. 
 위에 있는 데코레이터 패턴 예시에서 HouseBlend를 데코레이터로 감싸게 되면, 그 커피가 HouseBlend인지 아닌지를 알 수가 없게 된다. 그러므로, 구상 구성요소(Concrete Component)를 바탕으로 돌아가는 코드를 만들어야 한다면, 애플리케이션 디자인 자체 및 데코레이터 패턴을 사용하는 것에 대해서 다시 한번 생각해 볼 필요가 있다.






참고
Head First Design Pattern : 스토리가 있는 패턴 학습법
http://www.gurubee.net/pages/viewpage.action?pageId=1507398




출처 - http://digitanomad.blogspot.kr/2013/01/decorator-pattern.html

'디자인패턴' 카테고리의 다른 글

팩토리 패턴(Factory Pattern)  (0) 2014.11.11
옵저버 패턴(Observer Pattern)  (0) 2014.11.11
어댑터 패턴(Adapter Pattern)  (0) 2014.11.11
싱글톤 패턴  (0) 2012.11.02
데코레이터 패턴  (0) 2012.11.02
:

어댑터 패턴(Adapter Pattern)

디자인패턴 2014. 11. 11. 14:18

1. 어댑터 패턴이란?


 어댑터 패턴에서는 한 클래스의 인터페이스(Interface)를 클라이언트에서 사용하고자 하는 다른 인터페이스로 변환(Convert)한다. 어댑터를 이용하여 인터페이스 호환성 문제 때문에 같이 쓸 수 없는 클래스들을 연결해서 사용할 수 있다.
 이렇게 하여, 클라이언트와 구현된 인터페이스를 분리시킬 수 있으며, 나중에 인터페이스가  바뀌더라도 그 변경 내역은 어댑터에 캡슐화(Encapsulate)되기 때문에 클라이언트는 바뀔 필요가 없어진다.



2. 어댑터 패턴 예시

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
public interface Duck {
 public void quack();
 public void fly();
}
 
 
public class MallardDuck implements Duck {
 
 @Override
 public void quack() {
  System.out.println("Quack");
 }
 
 @Override
 public void fly() {
  System.out.println("I'm flying");
 }
 
}
 
 
public interface Turkey {
 public void gobble();
 public void fly();
}
 
 
public class WildTurkey implements Turkey {
 
 @Override
 public void gobble() {
  System.out.println("Gobble gobble");
 }
 
 @Override
 public void fly() {
  System.out.println("I'm flying a short distance");
 }
 
}
 
 
// 변환시킬 형식의 인터페이스를 구현해야 한다.
public class TurkeyAdapter implements Duck {
 Turkey turkey;
  
 // 원래 형식의 객체에 대한 레퍼런스가 필요하다. 여기에서는 생성자에서 레퍼런스를 받아온다.
 public TurkeyAdapter(Turkey turkey) {
  this.turkey = turkey;
 }
  
  
 // 인터페이스에 있는 메소드들을 모두 구현해야 한다.
 @Override
 public void quack() {
  turkey.gobble();
 }
 
 @Override
 public void fly() {
  for (int i = 0; i < 5; i++) {
   turkey.fly();
  }
 }
 
}
 
 
public class DuckTestDrive {
 
 /**
  * @param args
  */
 public static void main(String[] args) {
  MallardDuck duck = new MallardDuck();
   
  WildTurkey turkey = new WildTurkey();
  Duck turkeyAdapter = new TurkeyAdapter(turkey);
   
  System.out.println("The turkey says...");
  turkey.gobble();
  turkey.fly();
   
  System.out.println("\nThe Duck says...");
  testDuck(duck);
   
  System.out.println("\nThe TurkeyAdapter says...");
  // 오리 대신에 어댑터로 변환시킨 칠면조를 넘김.
  testDuck(turkeyAdapter);
 }
  
 static void testDuck(Duck duck) {
  duck.quack();
  duck.fly();
 }
 
}




참고
Head FIrst Design Pattern : 스토리가 있는 패턴 학습법
http://www.gurubee.net/pages/viewpage.action?pageId=1507401


출처 - http://digitanomad.blogspot.kr/2013/02/adapter-pattern.html

'디자인패턴' 카테고리의 다른 글

팩토리 패턴(Factory Pattern)  (0) 2014.11.11
옵저버 패턴(Observer Pattern)  (0) 2014.11.11
데코레이터 패턴(Decorator Pattern)  (0) 2014.11.11
싱글톤 패턴  (0) 2012.11.02
데코레이터 패턴  (0) 2012.11.02
:

싱글톤 패턴

디자인패턴 2012. 11. 2. 15:08

추가

싱글톤을 안전(Thread Safe)하게 생성하는 방법 몇가지를 소개합니다.

1. 이른 초기화 방법

싱글톤 객체를 미리 생성해 놓는 방법입니다. 항상 싱글톤 객체가 필요하거나 객체 생성 비용이 크지 않을 경우 사용합니다.

1
2
3
4
5
6
7
8
9
public class Singleton {
    private static final Singleton instance = new Singleton();
  
    private Singleton() {}
  
    public static Singleton getInstance() {
        return instance;
    }
}

싱글톤 객체가 static으로 선언 되었기 때문에 해당 클래스가 클래스 로더에 의해 로딩 될 때 객체가 생성 됩니다. 클래스 로더에 의해 클래스가 처음 로딩 될 때 수행 되므로 thread safe합니다. 단점으로는 싱글톤 객체를 사용하든 안하든 해당 클래스가 로딩 되는 시점에 항상 싱글톤 객체가 생성되고 리소스(메모리)를 차지하고 있으니 상황에 따라 비효율적일 수 있겠죠.

2. 늦은 초기화 방법

싱글톤 객체를 처음 사용하는 시점에 생성합니다. 처음 getInstance() 호출시에 싱글톤 객체를 생성 시키는 방법입니다. 하지만 concurrent한 상황에서 getInstance() 메소드에 동시 접근이 가능하므로 싱글톤 객체의 특성인 ‘한개의 인스턴스만 생성’ 룰이 깨질 수 있습니다.
이 문제를 해결 하기 위해서 synchronized 키워드를 사용해서 동시성 문제를 해결하고 객체 생성이 안되어 있을 경우에만 생성 하도록 처리합니다. getInstance() 메소드에 synchronized를 걸어주거나 주로 double-checked locking 이라는 기법을 사용합니다. 전자는 메소드 전체에 synchnorized 를 걸어주기 때문에 성능상 이슈가 있고요. 후자는 아래 코드와 같이 해당 싱글톤 객체가 null 일 경우에만 해당 싱글톤 객체에 synchronized를 거는 방법으로 성능 이슈를 해결한 방법입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class SingletonDemo {
        private static volatile SingletonDemo instance = null;
  
        private SingletonDemo() {       }
  
        public static SingletonDemo getInstance() {
                if (instance == null) {
                        synchronized (SingletonDemo .class){
                                if (instance == null) {
                                        instance = new SingletonDemo ();
                                }
                      }
                }
                return instance;
        }
}

장점으로는 이른 초기화와 반대겠지요? 사용시점까지 싱글톤 객체 생성을 미루기 때문에 사용하기 전까진 자원(메모리)을 점유하지 않는다는 점이네요. (double-checked locking 기법은 J2SE 5.0 이전 버전에서는 문제가 있다고 하니 주의 바랍니다.)

3. Initialization on demand holder idiom (holder에 의한 초기화 용법)

이것도 늦은 초기화인데 별도의 synchronized 키워드를 사용하지 않고 중첩 클래스(Holder)를 이용하는 방법으로 모든 자바 버젼에서 동작합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Singleton {
        // Private constructor prevents instantiation from other classes
        private Singleton() { }
  
        /**
        * SingletonHolder is loaded on the first execution of Singleton.getInstance()
        * or the first access to SingletonHolder.INSTANCE, not before.
        */
        private static class SingletonHolder {
                public static final Singleton INSTANCE = new Singleton();
        }
  
        public static Singleton getInstance() {
                return SingletonHolder.INSTANCE;
        }
}

중첩 클래스(SingletonHolder)는 getInstance 메소드가 호출되기 이전에는 참조 되지 않으며, 처음 getInstance 메소드 호출시 SingletonHolder 클래스가 클래스 로더에 의해 로딩 되면서 싱글톤 객체 생성이 이루어집니다. (물론 static 이므로 클래스 로딩 시점에 한번만 호출 되겠죠?)

늦은 초기화의 장점 + 중첩 클래스를 이용해야하지만 synchronized의 부담이 없다는 장점이 있겠죠?

4. Enum을 이용한 방법

이펙티브 자바 2nd Edition에서 나온 방법입니다. 모든 Enum type들은 프로그램 내에서 한번 초기화 되는 점을 이용합니다.

1
2
3
4
5
6
public enum Singleton {
        INSTANCE;
        public void execute (String arg) {
                //... perform operation here ...
        }
}

실제 위 코드의 컴파일 된 클래스 파일을 javap 명령어로 확인하면 다음과 같습니다.

1
2
3
4
5
6
7
public final class Singleton extends java.lang.Enum{
    public static final Singleton INSTANCE;
    public static Singleton[] values();
    public static Singleton valueOf(java.lang.String);
    public void execute(java.lang.String);
    static {};
}

위에서 본 코드들과 비슷하지요? 클래스 로딩 시점에 static 블럭에서 싱글톤 객체인 INSTANCE에 대한 초기화가 이루어집니다. 물론 이른 초기화의 장점과 동일하겠죠?

[참고]
http://en.wikipedia.org/wiki/Singleton_pattern

출처 - http://unabated.tistory.com/entry/%EC%8B%B1%EA%B8%80%ED%86%A4-%ED%8C%A8%ED%84%B4


싱글턴 패턴

유일무이한 객체

  • 싱글턴패턴은 인스턴스가 하나 뿐인 특별한 객체를 만들 수 있게 해주는 패턴이다.
  • 어떤 용도로 쓰는 건가?
    • 스레드 풀이라던가, 캐시, 대화상자, 사용자설정, 디바이스드라이버 등등 객체가 전체프로그램에서 오직 하나만 생성되어야 하는 경우에 사용한다.
  • 그럼 전역변수로 static 으로 선언해서 사용하면 되지 않느냐?
    전역 변수로 객체를 생성하면 어플리케이션이 실행 될 때 객체가 생성될 것이다.(P208 맨밑줄)
    그 객체가 자원을 많이 차지 한다면 사용도 되기전에, 괜히 자원만 차지한다. 사용하지 않는다면 아무 쓸 데 없는 객체가 되겠지.

고전적인 싱글턴 패턴 구현법

  • 조심하세요.. 이 코드에 문제가 있다는 것을 알게 될 것입니다.
public class Singleton {
  
  //private으로 Sinleton클래스의 유일한 인스턴스를 저장하기 위한 정적 변수를 선언
  private static Singleton uniqueInstance;

  //생성자를 private로 선언했기 때문에 Singleton에서만 클래스를 만들 수 있다.
  private Singleton() {}

  //클래스의 인스턴스를 만들어서 리턴해 준다.
  public static Singleton getInstance() {
    if(uniqueInstance == null) {
      uniqueInstance = new Singleton();
    }
    return uniqueInstance;
  }

}

초콜릿 공장

  • 만약 애플리케이션에서 ChocolateBoiler 인스턴스가 두 개 이상 만들어지게 되면 어떤 문제가 생길까?
Non-SingletonSingleton
public class ChocolateBoiler {
  private boolean empty;
  private boolean boiled;
  
  private ChocolateBoiler() {
    //이 코드는 보일러가 비어있을 때만 돌아갑니다
    empty = true;
    boiled = false;
  }
  
  public void fill() {
    if (isEmpty()) {
      //보일러가 비어있을 때만 재료를 집어 넣습니다. 
      //원료를 가득 채우고 나면 empty와 boiled 플래그를 false로 설정합니다.
      empty = false;
      boiled = false;
      // 보일러에 우유/초콜릿을 혼합한 재료를 집어넣음
    }
  }
  
  public void drain() {
    //보일러가 가득 차 있고(비어있지 않고), 다 끓여진 상태에서만 
     //보일러에 들어있는 재료를 다음 단계로 넘깁니다. 
    //보일러를 다 비우고 나면 empty 플래그를 다시 true로 설정합니다. 
    if (!isEmpty() && isBoiled()) {
      // 끓인 재료를 다음 단계로 넘김
      empty = true;
    }
  }

 //보일러가 가득 차 있고 아직 끓지 않은 상태에서만 
 //초콜릿과 우유가 혼합된 재료를 끓입니다. 
 //재료가 다 끓고 나면 boiled 플래그를 true로 설정합니다   
  public void boil() {
    if (!isEmpty() && !isBoiled()) {
      // 재료를 끓임
      boiled = true;
    }
  }

  public boolean isEmpty() {
    return empty;
  }
  
  public boolean isBoiled() {
    return boiled;
  }
}
public class ChocolateBoiler {
  private static ChocolateBoilerSingleton cb;
  private boolean empty;
  private boolean boiled;
  
  private ChocolateBoiler() {
    empty = true;
    boiled = false;
  }

  public static ChocolateBoilerSingleton getInstance(){
    if(cb == null)
      cb = new ChocolateBoilerSingleton();

    return cb;
  }  
  
  public void fill() {
    if (isEmpty()) {
      empty = false;
      boiled = false;
    }
  }
  
  public void drain() {
    if (!isEmpty() && isBoiled()) {
      empty = true;
    }
  }


  public void boil() {
    if (!isEmpty() && !isBoiled()) {
      boiled = true;
    }
  }

  public boolean isEmpty() {
    return empty;
  }
  
  public boolean isBoiled() {
    return boiled;
  }
}

싱글턴 패턴의 정의

  • 싱글턴 패턴은 해당 클래스의 인스턴스가 하나만 만들어진다,
  • 어디서든지 그 인스턴스에 접근할 수 있도록 한다.
  • 클래스에서 자신의 단 하나뿐인 인스턴스를 관리하도록 만들면 된다.

문제가 생겼다.

도대체 무슨 일이 일어난 거지? 새로 만든 싱글턴 코드가 얼마 전까지만 해도 문제 없이 잘 돌아가고 있었는데. 
조금 전에 다중 스레드를 사용하도록 초콜릿 보일러 컨트롤러를 최적화시킨 걸 빼면 이런 문제를 일으킬 만한 게없는데...

두 개의 스레드에서 여기에 있는 코드를 실행시킨다고 가정해 보면.
두 스레드가 다른 보일러 객체를 사용하게 될 가능성은 없는지 따져 보자.

ChocolateBoiler boiler = ChocolateBoiler.getInstance();
 fill();
 boil();
 drain();
  • page 226 참고

멀티스레딩 문제 해결 방법

  • getInstance()를 동기화시키기만 하면 멀티스레딩과 관련된 문제가 간단하게 해결된다.
public class Singleton {
  private static Singleton uniqueInstance;
  // 기타 인스턴스 변수
  private Singleton() {}
  
  //synchronized 키워드만 추가하면 두 스레드가 이 메소드를 동시에 실행시키는 일은 일어나지 않게 된다.
  public static synchronized Singleton getInstance() {
    if (uniqueInstance == null) {
       uniqueInstance = new Singleton();
    }
    return uniqueInstance;
  }
// 기타 메소드
}
  • 이렇게 하면 문제가 해결되긴 하겠지만, 동기화를하면 속도 문제가 생기지 않나?
    동기화는 불필요한 오버헤드만 증가시킬 수 있다.

더 효율적인 방법은 없을까요?

1. getInstance()의 속도가 그리 중요하지 않다면 그냥 내비 둔다.

  • 메소드를 동기화하면 성능이 100배 정도 저하된다는 것은 기억해 두자
  • 만약 getInstance( )가 애플리케이션에서 병목으로 작용한다면 다른 방법을 생각해봐야 한다.

2. 인스턴스를 필요할 때 생성하지 말고, 처음부터 만들어 버린다.

public class Singleton {
  private static Singleton uniqueInstance = new Singleton();

  private Singleton() {}

  public static Singleton getInstance() {
    return uniqueInstance;
  }
}
  • 이런 접근법을 사용하면 클래스가 로딩될 때 JVM에서 Singleton의 유일한 인스턴스를 생성해 준다.

3. DCL(Double-Checking Locking)을 써서 getInstance()에서 동기화되는 부분을 줄인다.

  • DCL(Double-Checking Locking)을 사용하면, 일단 인스턴스가 생성되어 있는지 확인한 다음, 생성되어 있지 않았을 때만 동기화를 할 수 있다.
  • volatile 키워드를 사용하여 멀티스레딩을 쓰더라도 uniqueInstance 변수가 Singleton 인스턴스로 초기화 되는 과정이 올바르게 할 수 있다.
  • DCL은 자바 1.4 이전 버전에서는 쓸 수 없다
public class Singleton {
  private volatile static Singleton uniqueInstance;

  private Singleton() {}

  public static Singleton getInstance() {
    if (uniqueInstance == null) {
      //이렇게 하면 처음에만 동기화 된다
      synchronized (Singleton.class) {
        if (uniqueInstance == null) {
          uniqueInstance = new Singleton();
        }
      }
    }
    return uniqueInstance;
  }
}

핵심 정리

  • 어떤 클래스에 싱글턴 패턴을 적용하면 애플리케이션에 그 클래스의 인스턴스가 최대 한 개 까지만 있도록 할 수 있다.
  • 싱글턴 패턴을 이용하면 유일한 인스턴스를 어디서든지 접근할 수 있도록 할 수 있다.
  • 자바에서 싱글턴 패턴을 구현 할 때는 private 생성자와 정적 메소드, 정적 변수를 사용 한다.
  • 다중 스레드를 사용하는 애플리케이션에서는 속도와 자원 문제를 파악해보고 적절한 구현법을 사용해야 한다.
  • DCL을 사용하는 방법은 자바2 버전 5(자바 1.5)보다 전에 나온 버전에서는 쓸 수 없다는 점에 주의.
  • 클래스 로더가 여러 개 있으면 싱글턴이 제대로 작동하지 않고, 여러 개의 인스턴스가 생길 수 있다.

문서에 대하여

'디자인패턴' 카테고리의 다른 글

팩토리 패턴(Factory Pattern)  (0) 2014.11.11
옵저버 패턴(Observer Pattern)  (0) 2014.11.11
데코레이터 패턴(Decorator Pattern)  (0) 2014.11.11
어댑터 패턴(Adapter Pattern)  (0) 2014.11.11
데코레이터 패턴  (0) 2012.11.02
: