Skip to content

Commit 7df982b

Browse files
authored
Create 김경록_정리.md
1 parent 3b1103a commit 7df982b

File tree

1 file changed

+243
-0
lines changed

1 file changed

+243
-0
lines changed
+243
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
# 옵저버(Observer) 패턴
2+
3+
## 옵저버 패턴이란..
4+
한 객체의 상태 변화를 정해지지 않은 여러 다른 객체에 통지하고 싶을 때 사용되는 패턴
5+
6+
## 옵저버 패턴의 구조
7+
옵저버 패턴은 크게 주제(subject) 객체, 옵저버(observer) 객체로 나뉜다. <br><br>
8+
9+
주제 객체는 아래와 같은 두 가지 책임을 갖는다.<br>
10+
<ul>
11+
<li> 옵저버 목록을 관리하고, 옵저버를 등록하고 제거할 수 있는 메서드를 제공한다. </li>
12+
<li> 상태의 변경이 발생하면 등록된 옵저버에 변경 낸역을 알린다. </li>
13+
</ul>
14+
15+
> 예를 들자면 아래와 같은 구조를 띄고있다!
16+
17+
![image](https://user-images.githubusercontent.com/47850258/80787928-ca2bb600-8bc2-11ea-9d39-8bdf8eec1361.png)
18+
19+
20+
StatusSubject 클래스는 아래와 같이 옵저버 목록을 List와 같은 타입으로 보관할 수 있다.<br>
21+
22+
### StatusSubject (추상)클래스
23+
~~~
24+
public abstract class StatusSubject {
25+
private List<StatusObserver> observers = new ArrayList<StatusObserver>();
26+
27+
public void add(StatusObserver observer) {
28+
observers.add(observer);
29+
}
30+
31+
public void remove(StatusObserver observer) {
32+
observers.remove(observer);
33+
}
34+
35+
public void notifyStatus(Status status) {
36+
for (StatusObserver observer : observers)
37+
observer.onAbnormalStatus(status);
38+
}
39+
}
40+
~~~
41+
<br>
42+
43+
notifyStatus() 메서드는 observers List에 등록된 각 StatusObserver 객체의 onAbnormalStatus() 메서드를 호출하는데,<br>
44+
이렇게 옵저버 객체의 메서드를 호출하는 방식으로 상태에 변화가 생겼음을 옵저버 객체에게 알린다. <br>
45+
46+
Status의 상태 변경을 알려야 하는 StatusChecker 클래스는 아래와 같이 StatusSubject 클래스를 상속받아 구현한다.
47+
48+
### StatusChecker 클래스
49+
~~~
50+
public class StatusChecker extends StatusSubject {
51+
52+
public void check() {
53+
Status status = loadStatus();
54+
55+
if (status.isNotNormal())
56+
super.notifyStatus(status);
57+
}
58+
59+
private Status loadStatus() {
60+
....
61+
}
62+
}
63+
~~~
64+
<br>
65+
StatusChecker 클래스는 비정상 상태가 감지되면 상위 클래스의 notifyStatus() 메서드를 호출해서 등록된 옵저버 객체들에 상태 값을 전달한다. <br>
66+
옵저버 객체를 구현한 클래스는 주제 객체가 호출하는 메서드에서 필요한 기능을 구현하면된다. 앞의 예제의 경우, StatusSubject 타입 객체에 등록되는 옵저버 인터페이스인 <br>
67+
StatusObserver는 주제 객체로부터 상태 변화를 전달 받을 수 있는 메서드 onAbnormalStatus() 메서드를 정의하고 있다. <br>
68+
옵저버 구현 클래스인 StatusEmailSender 클래스는 StatusObserver 인터페이스를 상속받아 상태 변화를 통지 받을 때 수행해야 할 기능을 구현하게 된다. <br>
69+
70+
### StatusObserver
71+
~~~
72+
public interface StatusObserver {
73+
void onAbnormalStatus(Status status);
74+
}
75+
~~~
76+
77+
### StatusEmailSender
78+
~~~
79+
public class StatusEmailSender implements StatusObserver {
80+
81+
@Override
82+
public void onAbnormalStatus(Status status) {
83+
sendEmail(status);
84+
}
85+
86+
private void sendEmail(Status status) {
87+
... // 이메일 전송 코드
88+
}
89+
}
90+
~~~
91+
<br>
92+
주제 객체의 상태에 변화가 생길 때 그 내용을 통지받도록 하려면, 옵저버 객체를 주제 객체에 등록해 주어야 한다. <br><br>
93+
94+
예를 들어, 시스템의 상태가 비정상이 될 때 StatusChecker 객체가 StatusEmailSender 객체에 통지하게 하려면 다음 코드처럼 StatusEmailSender 객체를 <br>
95+
StatusChecker 객체에 옵저버로 등록해 주어야 한다. <br><br>
96+
97+
~~~
98+
StatusChecker checker = new StatusChecker();
99+
checker.add(new StatusEmailSender()); // 옵저버로 등록
100+
~~~
101+
<br>
102+
103+
위와같이 옵저버로 등록되면, 시스템이 비정상 상태가 될 때마다 StatusChecker 객체가 StatusEmailSender 객체의 onAbnormalStatus() 메서드를 <br>
104+
호출해서 상태 정보를 통지해준다. 따라서 StatusEmailSender 객체는 시스템이 비정상 상태가 될 때 담당자에게 이메일로 통보해 줄 수 있게 된다. <br><br>
105+
106+
옵저버 패턴을 적용할 때의 장점은 주제 클래스 변경 없이 상태 변경을 통지 받을 옵저버를 추가할 수 있다는 점이다. <br>
107+
예를 들어, 장애가 발생할 때 SMS를 이용해서 문자를 전송한다면, 해당 기능을 구현한 옵저버 객체를 StatusChecker 객체에 등록해 주기만 하면 된다. <br>
108+
109+
~~~
110+
StatusChecker checker = ...;
111+
112+
//새로운 타입의 옵저버가 추가되어도 StatusChecker 코드는 바뀌지 않는다.
113+
StatusObserver faultObserver = new FaultStatusSMSSender();
114+
checker.add(faultObserver);
115+
checker.add(new StatusEmailSender());
116+
~~~
117+
118+
## 옵저버 객체에게 상태 전달 방법
119+
옵저버 객체가 기능을 수행하기 위해 주제 객체의 상태가 필요할 수 있다. <br>
120+
예를 들어, FaultStatusSMSSender 클래스는 장애 상태인 경우에만 SMS를 전송하고, 응답 속도가 느려진 상태처럼 장애 이외의 비정상 상태인 경우에는 <br>
121+
메시지를 전송하지 않도록 구현할 수 있을 것이다. 이 경우 FaultStatusSMSSender 클래스는 상태 값을 확인해야 한다. 지금까지 작성한 예에서는 아래 코드에서 보듯이 <br>
122+
주제 객체에서 옵저버 객체에 상태 값을 전달했다. 위 코드에서 FaultStatusSMSSender 클래스는 onAbnormalStatus() 메서드를 통해서 전달받은 status 객체만으로<br>
123+
원하는 기능을 구현하는데 부족함이 없다. 하지만, 경우에 따라서 옵저버 객체의 메서드를 호출할 때 전달한 객체만으로는 옵저버의 기능을 구현할 수 없을 수도 있다. <br><br>
124+
125+
이런 경우에는 옵저버 객체에서 콘크리트 주제 객체에 직접 접근하는 방법을 사용하기도 한다. 아래 코드는 옵저버 객체에서 특정 타입의 주제 객체를 사용하는 코드의 예를 보여준다.
126+
127+
~~~
128+
public class SpecialStatusObserver implements StatusObserver {
129+
private StatusChecker statusChecker;
130+
private Siren siren;
131+
132+
public SpecialStatusObserver(StatusChecker statusChecker) {
133+
this.statusChecker = statusChecker;
134+
}
135+
136+
public void onAbnormalStatus(Status status) {
137+
// 특정 타입의 주제 객체에 접근
138+
if (status.isFault() && statusChecker.isContinuousFault())
139+
siren.begin();
140+
}
141+
}
142+
~~~
143+
<br>
144+
SpecialStatusObserver 클래스의 onAbnormalStatus() 메서드는 status 파라미터와 statusChecker 필드를 이용해서 사이렌의 실행 조건을 판단하고 있다. <br><br>
145+
146+
이 코드를 보면 SpecialStatusObserver 클래스에서 StatusChecker 클래스로의 의존이 발생하게 되는데, 이렇게 콘크리트 옵저버 클래스(SpecialStatusObserver)는 <br>
147+
필요에 따라 특정한 콘크리트 주제 클래스(StatusChecker)에 의존하게 된다. <br>
148+
149+
![image](https://user-images.githubusercontent.com/47850258/80853522-8b523a80-8c6c-11ea-8912-9eebb344e185.png)
150+
151+
## 옵저버에서 주제 객체 구분
152+
옵저버 패턴이 가장 많이 사용되는 영역을 꼽으라면 GUI 프로그래밍 영역일 것이다. 버튼이 눌릴 때 로그인 기능을 호출한다고 할 때, 버튼이 주제 객체가 되고 로그인 <br>
153+
모듈을 호출하는 객체가 옵저버가 된다. <br><br>
154+
155+
예를 들어, 안드로이드에서는 다음과 같이 OnClickListener 타입의 객체를 Button 객체에 등록하는데, 이때 OnClickListener 인터페이스가 옵저버 인터페이스가 된다. <br>
156+
157+
~~~
158+
public class MyActivity extends Activity implements View.OnClickListener {
159+
public void onCreate(Bundle savedInstanceState) {
160+
super.onCreate(savedInstanceState);
161+
setContentView(R.layout.main);
162+
...
163+
Button loginButton = getViewById(R.id.main_loginbtn);
164+
loginButton.setOnClickListener(this);
165+
}
166+
167+
@Override
168+
public void onClick(View v) { // OnclickListener의 메서드
169+
login(id, password);
170+
}
171+
172+
...
173+
~~~
174+
<br>
175+
한 객체의 옵저버 객체를 여러 주제 객체에 등록할 수도 있을 것이다. GUI 프로그래밍을 하면 이런 상황이 흔하게 발생한다. <br>
176+
177+
예를 들어, 아래 코드처럼 로그인 버튼과 로그아웃 버튼에 동일한 OnclickListener 객체를 등록할 수 있다. <br>
178+
179+
~~~
180+
public class MyActivity extends Activity implements View.OnclickListener {
181+
public void onCreate(Bundle savedInstanceState) {
182+
super.onCreate(savedInstanceState);
183+
setContentView(R.layout.main);
184+
...
185+
// 두 개의 버튼에 동일한 OnclickListener 객체 등록
186+
Button loginButton = (Button) findViewById(R.id.main_loginbtn);
187+
loginButton.setOnClickListener(this);
188+
Button logoutButton = (Button) findViewById(R.id.main_logoutbtn);
189+
logoutButton.setOnClickListener(this);
190+
}
191+
192+
@Override
193+
public void onClick(View v) { // OnClickListener의 메서드
194+
// 주제 객체를 구분할 수 있는 방법 필요
195+
if (v.getId() == R.id.main_loginbtn) {
196+
login(id, password);
197+
} else if (v.getId() == R.id.main_logoutbtn) {
198+
logout(_;
199+
}
200+
}
201+
...
202+
~~~
203+
<br>
204+
한 옵저버 객체를 여러 주제 객체에 등록하면, 옵저버 객체에서 각 주제 객체를 구분할 수 있는 방법이 필요하다. 위 코드에서 옵저버 객체의 메서드인 onClick() 메서드에서<br>
205+
주제 객체인 Button 객체를 구분하기 위해 ID값을 사용하였다. ID값 외에 아래 코드처럼 객체 레퍼런스를 사용할 수도 있을 것이다. <br>
206+
207+
~~~
208+
@Override
209+
public void onClick(View v) { // OnClickListener의 메서드
210+
if (v == loginButton) {
211+
login(id, password);
212+
} else if (v == logoutButton) {
213+
logout();
214+
}
215+
~~~
216+
<br>
217+
앞서 StatusChecker 예제나 안드로이드의 예제는 모두 주체 객체를 위한 추상 타입을 제공하고 있다. 예를 들어, StatusChecker는 상위 타입인 StatusSubject <br>
218+
추상 클래스가 존재하고, 안드로이드의 Button 클래스는 상위 타입은 View가 존재한다. StatusSubject 클래스와 View 클래스는 모두 옵저버 객체를 관리하기 위한 기능을 <br>
219+
제공한다는 공통점이 있다. <br>
220+
221+
~~~
222+
// StatusChecker 클래스
223+
public void add(StatusObserver observer) {... }
224+
225+
// View 클래스:
226+
public void setOnClickListener(OnClickListener o) {... }
227+
~~~
228+
<br>
229+
한 주제에 대한 다양한 구현 클래스가 존재한다면, 위 코드처럼 옵저버 객체 관리 및 통지 기능을 제공하는 추상 클래스를 제공함으로써 불필요하게 동일한 코드가 <br>
230+
여러 주제 클래스에서 중복되는 것을 방지할 수 있을 것이다. 하지만, 해당 주제 클래스가 한 개뿐이라면 옵저버 관리를 위한 추상 클래스를 따로 만들 필요는 없을 것이다. <br><br>
231+
232+
## 옵저버 패턴 구현의 고려 사항
233+
옵저버 패턴을 구현할 때에는 다음 내용을 고려해야 한다.<br>
234+
235+
<ul>
236+
<li>주제 객체의 통지 기능 실행 주체</li>
237+
<li>옵저버 인터페이스의 분리</li>
238+
<li>통지 시점에서의 주제 객체 상태</li>
239+
<li>옵저버 객체의 실행 제약 조건</li>
240+
</ul>
241+
242+
이 외에 생각해 볼만한 고려 사항들이 있다. 예를 들면, 옵저버 객체에서 주제 객체의 상태를 다시 변경하면 어떻게 구현할 것인가에 대한 문제나 <br>
243+
옵저버 자체를 비동기로 실행하는 문제 등을 생각해 볼 수 있다. 이런 문제는 주어진 상황에 따라 대답이 달라질 수 있으므로, 실제 옵저버 패턴을 적용할 때 고민해봐야 한다.

0 commit comments

Comments
 (0)