간단한 관찰자 패턴 구현
다음과 같이 코드 코드를 복사합니다.
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
java.util.List 가져오기;
/**
* 콜백은 관찰자 모드에서 사용됩니다:
* A. 관찰자는 관찰자의 리스너 목록에 자신을 등록하며, 관찰자 클래스 자체에서 콜백 함수를 제공합니다.
* B. Observable(Observable 또는 Subject)은 관찰자 목록을 유지하며 관찰자를 등록 및 등록 취소할 수 있습니다.
* C. 관찰자의 상태가 변경되면 통지Observers()를 호출할 수 있습니다. 이 메소드는 관찰자 목록을 순회하여 하나씩 호출합니다.
관찰자가 제공하는 콜백 함수
* @author는
*
*/
공개 클래스 SimpleObserverPattern {
공개 정적 무효 메인(String[] args) {
SimpleObserverPattern sop = new SimpleObserverPattern();
List<IObserver> 관찰자 = new ArrayList<IObserver> ();
IObserver 관찰자A = sop.new Observer("ObserverA");
IObserver 관찰자B = sop.new Observer("ObserverB");
관찰자.add(observerA);
관찰자.add(observerB);
IObservable observable = sop.new Observable(관찰자);
observable.registerObserver(sop.new Observer("ObserverC"));
관찰 가능.changeState();
관찰 가능.닫기();
}
// 관찰되고 있는 사람을 어떤 곳에서는 피험자라고 부릅니다.
인터페이스 IObservable {
void RegisterObserver(IObserver 관찰자);
void unregisterObserver(IObserver 관찰자);
무효 통지Observers();
문자열 getState();
무효 변경상태();
무효 닫기();
}
Observable 클래스는 IObservable을 구현합니다.
private static final String NEW = "새로 만들기";
private static final String CHANGED = "변경됨";
private static final String CLOSED = "닫힘";
개인 문자열 상태;
private List<IObserver> 관찰자;
공개 관찰 가능() {
이(널);
}
public Observable(List<IObserver> 관찰자) {
if(관찰자 == null) {
관찰자 = 새로운 ArrayList<IObserver> ();
}
this.observers = Collections.synchronizedList(관찰자);
this.state = 신규;
}
@보수
공공 무효 RegisterObserver(IObserver 관찰자) {
관찰자.add(관찰자);
}
@보수
공공 무효 unregisterObserver(IObserver 관찰자) {
관찰자.제거(관찰자);
}
@보수
공공 무효 통지Observers() {
Iterator<IObserver> iter = 관찰자.iterator();
동안(iter.hasNext()) {
iter.next().update(this);
}
}
@보수
공개 문자열 getState() {
반환 상태;
}
@보수
공개 무효 변경 상태() {
this.state = 변경됨;
통지관찰자();
}
@보수
공개 무효 닫기() {
this.state = 닫힘;
통지관찰자();
}
}
인터페이스 IObserver {
void update(IObservable 관찰 가능);
}
클래스 Observer는 IObserver를 구현합니다.
개인 문자열 이름;
public Observer(문자열 이름) {
this.name = 이름;
}
@보수
공개 무효 업데이트(IObservable observalbe) {
System.out.println(
String.format("%s는 observalbe의 변경 사항을 수신합니다. 현재 observalbe의 상태는 %s입니다.",
이름, observalbe.getState()));
}
}
}
위의 구현은 관찰된 개체를 콜백 함수 매개변수로 직접 사용합니다. 이는 매우 우아하지 않으며 간단한 시나리오에서 작동할 수 있습니다.
그러나 실제로는 관찰된 사람이 많은 사건이나 상태를 갖고 있고, 각 관찰자가 서로 다른 사건이나 상태에 관심을 가질 수도 있고, 정보 은폐를 위해 모든 관찰자가 모든 상태에 접근할 수 있는 것을 원하지 않는 경우도 많습니다. Observable 내부.
이런 식으로 코드를 다음 버전으로 계속 발전시켰습니다. 여기서는 동시성 문제를 자세히 고려하지 않았습니다.
다음과 같이 코드 코드를 복사합니다.
import java.util.Collections;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Set;
공개 클래스 MultiEventObserverPattern {
공개 정적 무효 메인(String[] args) {
MultiEventObserverPattern meop = new MultiEventObserverPattern();
IObservable observable = meop.new Observable();
IObserver 관찰자A = meop.new Observer("ObserverA");
IObserver 관찰자B = meop.new Observer("ObserverB");
//관심 이벤트 등록
observable.registerObserver(observable.getEventA(), 관찰자A);
observable.registerObserver(observable.getEventB(), 관찰자B);
//관찰된 상태를 변경합니다.
관찰 가능.changeStateA();
관찰 가능.changeStateB();
}
인터페이스 IEvent {
무효 이벤트변경();
문자열 getState();
}
EventA 클래스는 IEvent를 구현합니다.
private static final String INITIALIZED = "초기화됨";
private static final String PENDING = "보류 중";
개인 문자열 상태;
공개 EventA() {
this.state = 초기화됨;
}
@보수
공공 무효 eventChange() {
System.out.println("이벤트A 변경");
this.state = 보류 중;
}
@보수
공개 문자열 toString() {
"이벤트A"를 반환합니다.
}
@보수
공개 문자열 getState() {
반환 상태;
}
}
클래스 EventB는 IEvent를 구현합니다.
private static final String NEW = "새로 만들기";
private static final String IDLE = "Idle";
개인 문자열 상태;
공개 이벤트B() {
this.state = 신규;
}
@보수
공공 무효 eventChange() {
System.out.println("이벤트B 변경");
this.state = 유휴 상태;
}
@보수
공개 문자열 toString() {
"이벤트B"를 반환합니다.
}
@보수
공개 문자열 getState() {
반환 상태;
}
}
// 관찰 가능, 때로는 주제라고도 함
인터페이스 IObservable {
void RegisterObserver(IEvent 이벤트, IObserver 관찰자);
void unregisterObserver(IEvent 이벤트, IObserver 관찰자);
// 이벤트가 발생했음을 관찰자에게 알립니다.
void informObservers(IEvent 이벤트);
무효 변경StateA();
무효 변경StateB();
IEvent getEventA();
IEvent getEventB();
}
Observable 클래스는 IObservable을 구현합니다.
개인 IEvent 이벤트A;
개인 IEvent 이벤트B;
개인 Hashtable<IEvent, Set<IObserver>> eventObserverMapping;
공개 관찰 가능() {
이(널);
}
// evenObserverMapping에 의해 전달된 Set<IObserver> 중 일부가 동기적으로 수정되지 않은 경우 이에 대해 취할 수 있는 조치가 없습니다.
공개 Observable(Hashtable<IEvent, Set<IObserver>> eventObserverMapping) {
if(eventObserverMapping == null) {
eventObserverMapping = new Hashtable<IEvent, Set<IObserver>> ();
}
this.eventObserverMapping = new Hashtable<IEvent, Set<IObserver>> ();
this.eventA = 새로운 이벤트A();
this.eventB = 새로운 EventB();
}
@보수
공공 무효 RegisterObserver(IEvent 이벤트, IObserver 관찰자) {
Set<IObserver> 관찰자 = eventObserverMapping.get(event);
if(관찰자 == null) {
관찰자 = Collections.synchronizedSet(new HashSet<IObserver> ());
관찰자.add(관찰자);
eventObserverMapping.put(이벤트, 관찰자);
}
또 다른 {
관찰자.add(관찰자);
}
}
@보수
공공 무효 unregisterObserver(IEvent 이벤트, IObserver 관찰자) {
Set<IObserver> 관찰자 = eventObserverMapping.get(event);
if(관찰자 != null) {
관찰자.제거(관찰자);
}
}
@보수
공공 무효 통지Observers(IEvent 이벤트) {
Set<IObserver> 관찰자 = eventObserverMapping.get(event);
if(관찰자 != null && 관찰자.size() > 0) {
Iterator<IObserver> iter = 관찰자.iterator();
동안(iter.hasNext()) {
iter.next().update(이벤트);
}
}
}
@보수
공공 무효 변경StateA() {
// 상태 A를 변경하면 이벤트 A가 트리거됩니다.
eventA.eventChange();
통지Observers(eventA);
}
@보수
공공 무효 변경StateB() {
// 상태 B를 변경하면 이벤트 B가 트리거됩니다.
eventB.eventChange();
통지Observers(eventB);
}
@보수
공개 IEvent getEventA() {
이벤트A 반환;
}
@보수
공개 IEvent getEventB() {
이벤트B를 반환합니다.
}
}
인터페이스 IObserver {
무효 업데이트(IEvent 이벤트);
}
클래스 Observer는 IObserver를 구현합니다.
개인 문자열 이름;
public Observer(문자열 이름) {
this.name = 이름;
}
@보수
공개 무효 업데이트(IEvent 이벤트) {
System.out.println(
String.format("%s는 %s의 변경 사항을 수신합니다. 현재 관찰자의 상태는 %s입니다.",
이름, 이벤트, event.getState()));
}
}
}
완벽한 것 같지만 아직 완벽하지는 않습니다. 이벤트는 Observer 클래스의 속성으로 하드코딩되어 있기 때문입니다. 이런 방식으로 이벤트 유형은 컴파일 타임에 결정됩니다. 새로운 이벤트 유형을 추가하려면 IObservable 인터페이스와 Observable 클래스를 수정해야 하므로 유연성이 크게 떨어집니다.
이는 관찰자가 이러한 특정 이벤트에 결합되는 것과 동일합니다. 그렇다면 이 제한을 어떻게 깨뜨릴 수 있을까요?
대답은 새로운 구성 요소를 도입하고 해당 구성 요소가 이벤트, 관찰자 및 관찰된 개체 간의 관계를 관리하도록 하는 것입니다. 이벤트가 발생하면 해당 구성 요소는 관찰자의 콜백 함수도 호출합니다. 이는 Spring의 IOC 컨테이너와 다소 유사한 일종의 분리이기도 합니다.
구체적인 구현에 대해서는 Guava EventBus가 꽤 잘 수행되었다고 생각합니다. 앞서 언급한 링크를 참조하세요.
추신: 이 게시물은 Guava EventBus에 대한 광고가 아니지만 내 아이디어가 단계별로 발전하고 있으며 점차 Guava EventBus의 디자인 아이디어와 일치하고 있습니다.
관찰자 패턴을 구현하는 JDK 표준 클래스의 예를 계속 살펴본 다음 해당 소스 코드 구현을 분석해 보겠습니다. 우리가 살펴봐야 할 것은 Observable 클래스와 Observer 인터페이스뿐입니다.
JDK 표준 클래스는 관찰자 패턴을 구현합니다.
다음과 같이 코드 코드를 복사합니다.
import java.util.Observable;
java.util.Observer 가져오기;
/**
* java.util 패키지의 표준 클래스를 사용하여 관찰자 패턴 구현
* @author는
*
*/
공개 클래스 JDKOBserverDemo {
공개 정적 무효 메인(String[] args) {
JDKObserverDemo jod = new JDKObserverDemo();
// 관찰자
MyObservable myObservable = jod.new MyObservable("hello");
// 관찰자
관찰자 myObserver = jod.new MyObserver();
// 등록하다
myObservable.addObserver(myObserver);
//관찰된 상태를 변경하고 관찰자 콜백 함수를 트리거합니다.
myObservable.setValue("will");
}
MyObservable 클래스는 Observable을 확장합니다.
private String watchValue; // 관찰된 값
공개 MyObservable(StringwatchedValue) {
this.watchedValue = watchedValue;
}
공공 무효 setValue(문자열 newValue) {
if(!watchedValue.equals(newValue)) {
watchValue = newValue;
setChanged();
통지Observers(newValue);
}
}
@보수
공개 문자열 toString() {
"MyObservable"을 반환합니다.
}
}
MyObserver 클래스는 Observer를 구현합니다.
@보수
public void update(Observable o, Object arg) {
System.out.println(o + "의 상태가 변경되었습니다. 인수는 " + arg);
}
}
}
JDK 표준 라이브러리에서 Observer와 Observable의 구현을 살펴보니 매우 간단하므로 더 이상 설명하고 싶지 않습니다.
다음은 Quartz의 리스너 구현입니다.
QuartzScheduler 리스너
다음과 같이 코드 코드를 복사합니다.
import java.util.ArrayList;
java.util.Date 가져오기;
java.util.List 가져오기;
/**
* Observable(관찰 가능)과 동등한 Quartz 코어 클래스
* @author는
*
*/
공개 클래스 QuartzScheduler {
private ArrayList<SchedulerListener> 내부SchedulerListeners = new ArrayList<SchedulerListener>(10);
//private ArrayList<JobListener> interanlJobListeners = new ArrayList<JobListener>() // Observable에는 여러 리스너 세트가 포함될 수 있습니다.
공개 날짜 ScheduleJob(트리거 트리거) {
if(트리거 == null) {
null을 반환;
}
System.out.println("작업 예약, 트리거: " + 트리거);
informSchedulerListenersScheduled(trigger);
새로운 날짜()를 반환합니다.
}
공공 무효 unScheduleJob(트리거 트리거) {
if(트리거 == null) {
반품;
}
System.out.println("작업 예약 취소, 트리거: " + 트리거);
informShedulerListenerUnScheduled(trigger);
}
//스케줄러리스너 등록
공공 무효 addInternalSchedulerListener(SchedulerListener SchedulerListener) {
동기화됨(internalSchedulerListeners) {
내부SchedulerListeners.add(schedulerListener);
}
}
// SchedulerListener 제거
공개 부울 제거InternalSchedulerListener(SchedulerListener SchedulerListener) {
동기화됨(internalSchedulerListeners) {
return InternalSchedulerListeners.remove(schedulerListener);
}
}
공개 목록<SchedulerListener> getInternalSchedulerListeners() {
동기화됨(internalSchedulerListeners) {
return java.util.Collections.unmodifyingList(new ArrayList<SchedulerListener>(internalSchedulerListeners));
}
}
공공 무효 통지SchedulerListenersScheduled(트리거 트리거) {
for(SchedulerListener 리스너: getInternalSchedulerListeners()) {
리스너.jobScheduled(trigger);
}
}
공공 무효 informShedulerListenerUnScheduled(트리거 트리거) {
for(SchedulerListener 리스너: getInternalSchedulerListeners()) {
리스너.jobUnScheduled(trigger);
}
}
}
스케줄러리스너
다음과 같이 코드 코드를 복사합니다.
// Listening 인터페이스, 콜백 함수, 모니터링 등록 시 클라이언트가 콜백 함수 구현을 제공해야 함
공용 인터페이스 SchedulerListener {
void jobScheduled(트리거 트리거);
void jobUnScheduled(트리거 트리거);
}
방아쇠
다음과 같이 코드 코드를 복사합니다.
//방아쇠
공개 클래스 트리거 {
개인 문자열 TriggerKey;
개인 문자열 TriggerName;
공개 Trigger(문자열 TriggerKey, 문자열 TriggerName) {
this.triggerKey = 트리거키;
this.triggerName = 트리거이름;
}
공개 문자열 getTriggerKey() {
TriggerKey를 반환합니다.
}
공개 무효 setTriggerKey(String TriggerKey) {
this.triggerKey = 트리거키;
}
공개 문자열 getTriggerName() {
트리거 이름을 반환합니다.
}
공개 무효 setTriggerName(String TriggerName) {
this.triggerName = 트리거이름;
}
공개 문자열 toString() {
return String.format("{triggerKey: %s, TriggerName: %s}", TriggerKey, TriggerName);
}
}
시험
다음과 같이 코드 코드를 복사합니다.
공개 클래스 테스트 {
공개 정적 무효 메인(String[] args) {
QuartzScheduler qs = new QuartzScheduler();
SchedulerListener 리스너A = 새로운 SchedulerListener() {
@보수
공공 무효 jobUnScheduled(트리거 트리거) {
System.out.println("listenerA 작업이 예약되지 않았습니다: " + Trigger.getTriggerName());
}
@보수
공공 무효 jobScheduled(트리거 트리거) {
System.out.println("listenerA 작업 예약됨: " + Trigger.getTriggerName());
}
};
SchedulerListener 리스너B = 새로운 SchedulerListener() {
@보수
공공 무효 jobUnScheduled(트리거 트리거) {
System.out.println("listenerB 작업이 예약되지 않았습니다: " + Trigger.getTriggerName());
}
@보수
공공 무효 jobScheduled(트리거 트리거) {
System.out.println("listenerB 작업 예약됨: " + Trigger.getTriggerName());
}
};
//스케줄러 리스너 등록
qs.addInternalSchedulerListener(listenerA);
qs.addInternalSchedulerListener(listenerB);
트리거 TriggerA = new Trigger("Key1", "triggerA");
트리거 TriggerB = new Trigger("Key2", "triggerB");
qs.scheduleJob(triggerA);
qs.scheduleJob(triggerB);
qs.unScheduleJob(triggerA);
}
}