本文節選自《設計模式就該這樣學》
1 基於Java API實現通知機制
當小夥伴們在社群提問時,如果有設定指定使用者回答,則對應的使用者就會收到郵件通知,這就是觀察者模式的一種應用場景。有些小夥伴可能會想到MQ、非同步佇列等,其實JDK本身就提供這樣的API。我們用程式碼來還原這樣一個應用場景,首先建立GPer類。
/**
* JDK提供的一種觀察者的實現方式,被觀察者
*/
public class GPer extends Observable{
private String name = "GPer生態圈";
private static GPer gper = null;
private GPer(){}
public static GPer getInstance(){
if(null == gper){
gper = new GPer();
}
return gper;
}
public String getName() {
return name;
}
public void publishQuestion(Question question){
System.out.println(question.getUserName() + "在" + this.name + "上提交了一個問題。");
setChanged();
notifyObservers(question);
}
}
然後建立問題Question類。
public class Question {
private String userName;
private String content;
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}
接著建立老師Teacher類。
public class Teacher implements Observer {
private String name;
public Teacher(String name) {
this.name = name;
}
public void update(Observable o, Object arg) {
GPer gper = (GPer)o;
Question question = (Question)arg;
System.out.println("======================");
System.out.println(name + "老師,你好!\n" +
"您收到了一個來自" + gper.getName() + "的提問,希望您解答。問題內容如下:\n" +
question.getContent() + "\n" + "提問者:" + question.getUserName());
}
}
最後編寫客戶端測試程式碼。
public static void main(String[] args) {
GPer gper = GPer.getInstance();
Teacher tom = new Teacher("Tom");
Teacher jerry = new Teacher("Jerry");
gper.addObserver(tom);
gper.addObserver(jerry);
//使用者行為
Question question = new Question();
question.setUserName("張三");
question.setContent("觀察者模式適用於哪些場景?");
gper.publishQuestion(question);
}
執行結果如下圖所示。
2 基於Guava API輕鬆落地觀察者模式
筆者向大家推薦一個實現觀察者模式的非常好用的框架,API使用也非常簡單,舉個例子,首先引入Maven依賴包。
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>20.0</version>
</dependency>
然後建立偵聽事件GuavaEvent。
/**
* Created by Tom
*/
public class GuavaEvent {
@Subscribe
public void subscribe(String str){
//業務邏輯
System.out.println("執行subscribe方法,傳入的引數是:" + str);
}
}
最後編寫客戶端測試程式碼。
/**
* Created by Tom
*/
public class GuavaEventTest {
public static void main(String[] args) {
EventBus eventbus = new EventBus();
GuavaEvent guavaEvent = new GuavaEvent();
eventbus.register(guavaEvent);
eventbus.post("Tom");
}
}
3 使用觀察者模式設計滑鼠事件響應API
再來設計一個業務場景,幫助小夥伴們更好地理解觀察者模式。在JDK原始碼中,觀察者模式的應用也非常多。例如java.awt.Event就是觀察者模式的一種,只不過Java很少被用來寫桌面程式。我們用程式碼來實現一下,以幫助小夥伴們更深刻地瞭解觀察者模式的實現原理。首先,建立EventListener介面。
/**
* 觀察者抽象
* Created by Tom.
*/
public interface EventListener {
}
建立Event類。
/**
* 標準事件源格式的定義
* Created by Tom.
*/
public class Event {
//事件源,動作是由誰發出的
private Object source;
//事件觸發,要通知誰(觀察者)
private EventListener target;
//觀察者的回應
private Method callback;
//事件的名稱
private String trigger;
//事件的觸發事件
private long time;
public Event(EventListener target, Method callback) {
this.target = target;
this.callback = callback;
}
public Object getSource() {
return source;
}
public Event setSource(Object source) {
this.source = source;
return this;
}
public String getTrigger() {
return trigger;
}
public Event setTrigger(String trigger) {
this.trigger = trigger;
return this;
}
public long getTime() {
return time;
}
public Event setTime(long time) {
this.time = time;
return this;
}
public Method getCallback() {
return callback;
}
public EventListener getTarget() {
return target;
}
@Override
public String toString() {
return "Event{" +
"source=" + source +
", target=" + target +
", callback=" + callback +
", trigger='" + trigger + '\'' +
", time=" + time +
'}';
}
}
建立EventContext類。
/**
* 被觀察者的抽象
* Created by Tom.
*/
public abstract class EventContext {
protected Map<String,Event> events = new HashMap<String,Event>();
public void addListener(String eventType, EventListener target, Method callback){
events.put(eventType,new Event(target,callback));
}
public void addListener(String eventType, EventListener target){
try {
this.addListener(eventType, target,
target.getClass().getMethod("on"+toUpperFirstCase(eventType), Event.class));
}catch (NoSuchMethodException e){
return;
}
}
private String toUpperFirstCase(String eventType) {
char [] chars = eventType.toCharArray();
chars[0] -= 32;
return String.valueOf(chars);
}
private void trigger(Event event){
event.setSource(this);
event.setTime(System.currentTimeMillis());
try {
if (event.getCallback() != null) {
//用反射呼叫回撥函式
event.getCallback().invoke(event.getTarget(), event);
}
}catch (Exception e){
e.printStackTrace();
}
}
protected void trigger(String trigger){
if(!this.events.containsKey(trigger)){return;}
trigger(this.events.get(trigger).setTrigger(trigger));
}
}
然後建立MouseEventType介面。
/**
* Created by Tom.
*/
public interface MouseEventType {
//單擊
String ON_CLICK = "click";
//雙擊
String ON_DOUBLE_CLICK = "doubleClick";
//彈起
String ON_UP = "up";
//按下
String ON_DOWN = "down";
//移動
String ON_MOVE = "move";
//滾動
String ON_WHEEL = "wheel";
//懸停
String ON_OVER = "over";
//失去焦點
String ON_BLUR = "blur";
//獲得焦點
String ON_FOCUS = "focus";
}
建立Mouse類。
/**
* 具體的被觀察者
* Created by Tom.
*/
public class Mouse extends EventContext {
public void click(){
System.out.println("呼叫單擊方法");
this.trigger(MouseEventType.ON_CLICK);
}
public void doubleClick(){
System.out.println("呼叫雙擊方法");
this.trigger(MouseEventType.ON_DOUBLE_CLICK);
}
public void up(){
System.out.println("呼叫彈起方法");
this.trigger(MouseEventType.ON_UP);
}
public void down(){
System.out.println("呼叫按下方法");
this.trigger(MouseEventType.ON_DOWN);
}
public void move(){
System.out.println("呼叫移動方法");
this.trigger(MouseEventType.ON_MOVE);
}
public void wheel(){
System.out.println("呼叫滾動方法");
this.trigger(MouseEventType.ON_WHEEL);
}
public void over(){
System.out.println("呼叫懸停方法");
this.trigger(MouseEventType.ON_OVER);
}
public void blur(){
System.out.println("呼叫獲得焦點方法");
this.trigger(MouseEventType.ON_BLUR);
}
public void focus(){
System.out.println("呼叫失去焦點方法");
this.trigger(MouseEventType.ON_FOCUS);
}
}
建立回撥方法MouseEventLisenter類。
/**
* 觀察者
* Created by Tom.
*/
public class MouseEventListener implements EventListener {
public void onClick(Event e){
System.out.println("===========觸發滑鼠單擊事件==========" + "\n" + e);
}
public void onDoubleClick(Event e){
System.out.println("===========觸發滑鼠雙擊事件==========" + "\n" + e);
}
public void onUp(Event e){
System.out.println("===========觸發滑鼠彈起事件==========" + "\n" + e);
}
public void onDown(Event e){
System.out.println("===========觸發滑鼠按下事件==========" + "\n" + e);
}
public void onMove(Event e){
System.out.println("===========觸發滑鼠移動事件==========" + "\n" + e);
}
public void onWheel(Event e){
System.out.println("===========觸發滑鼠滾動事件==========" + "\n" + e);
}
public void onOver(Event e){
System.out.println("===========觸發滑鼠懸停事件==========" + "\n" + e);
}
public void onBlur(Event e){
System.out.println("===========觸發滑鼠失去焦點事件==========" + "\n" + e);
}
public void onFocus(Event e){
System.out.println("===========觸發滑鼠獲得焦點事件==========" + "\n" + e);
}
}
最後編寫客戶端測試程式碼。
public static void main(String[] args) {
EventListener listener = new MouseEventListener();
Mouse mouse = new Mouse();
mouse.addListener(MouseEventType.ON_CLICK,listener);
mouse.addListener(MouseEventType.ON_MOVE,listener);
mouse.click();
mouse.move();
}
關注微信公眾號『 Tom彈架構 』回覆“設計模式”可獲取完整原始碼。
本文為“Tom彈架構”原創,轉載請註明出處。技術在於分享,我分享我快樂!
如果本文對您有幫助,歡迎關注和點贊;如果您有任何建議也可留言評論或私信,您的支援是我堅持創作的動力。關注微信公眾號『 Tom彈架構 』可獲取更多技術乾貨!