引言
一般來說,同事類之間的關(guān)系是比較復(fù)雜的,多個同事類之間互相關(guān)聯(lián)時,他們之間的關(guān)系會呈現(xiàn)為復(fù)雜的網(wǎng)狀結(jié)構(gòu),這是一種過度耦合的架構(gòu),即不利于類的復(fù)用,也不穩(wěn)定。例如在下圖中,有六個同事類對象,假如對象1發(fā)生變化,那么將會有4個對象受到影響。如果對象2發(fā)生變化,那么將會有5個對象受到影響。也就是說,同事類之間直接關(guān)聯(lián)的設(shè)計是不好的。
如果引入中介者模式,那么同事類之間的關(guān)系將變?yōu)樾切徒Y(jié)構(gòu),從圖中可以看到,任何一個類的變動,只會影響的類本身,以及中介者,這樣就減小了系統(tǒng)的耦合。一個好的設(shè)計,必定不會把所有的對象關(guān)系處理邏輯封裝在本類中,而是使用一個專門的類來管理那些不屬于自己的行為。
介紹
中介者模式(Mediator Pattern):用一個中介對象(中介者)來封裝一系列的對象交互,中介者使各對象不需要顯式地相互引用,從而使其耦合松散,而且可以獨立地改變它們之間的交互。中介者模式又稱為調(diào)停者模式,它是一種對象行為型模式。
角色
- Mediator(抽象中介者):它定義一個接口,該接口用于與各同事對象之間進(jìn)行通信。
- ConcreteMediator(具體中介者):它是抽象中介者的子類,通過協(xié)調(diào)各個同事對象來實現(xiàn)協(xié)作行為,它維持了對各個同事對象的引用。
- Colleague(抽象同事類):它定義各個同事類公有的方法,并聲明了一些抽象方法來供子類實現(xiàn),同時它維持了一個對抽象中介者類的引用,其子類可以通過該引用來與中介者通信。
- ConcreteColleague(具體同事類):它是抽象同事類的子類;每一個同事對象在需要和其他同事對象通信時,先與中介者通信,通過中介者來間接完成與其他同事類的通信;在具體同事類中實現(xiàn)了在抽象同事類中聲明的抽象方法。
中介者模式的核心在于中介者類的引入,在中介者模式中,中介者類承擔(dān)了兩方面的職責(zé):
- 中轉(zhuǎn)作用(結(jié)構(gòu)性):通過中介者提供的中轉(zhuǎn)作用,各個同事對象就不再需要顯式引用其他同事,當(dāng)需要和其他同事進(jìn)行通信時,可通過中介者來實現(xiàn)間接調(diào)用。該中轉(zhuǎn)作用屬于中介者在結(jié)構(gòu)上的支持。
- 協(xié)調(diào)作用(行為性):中介者可以更進(jìn)一步的對同事之間的關(guān)系進(jìn)行封裝,同事可以一致的和中介者進(jìn)行交互,而不需要指明中介者需要具體怎么做,中介者根據(jù)封裝在自身內(nèi)部的協(xié)調(diào)邏輯,對同事的請求進(jìn)行進(jìn)一步處理,將同事成員之間的關(guān)系行為進(jìn)行分離和封裝。
開發(fā)中常見的場景
- MVC模式(Controller 是中介者,根據(jù) View 層的請求來操作 Model 層)
- 窗口游戲程序,窗口軟件開發(fā)中窗口對象也是一個中介者對象
- 圖形界面開發(fā)GUI中,多個組件之間的交互,可以通過引入一個中介者對象來解決,可以是整體的窗口對象或者DOM對象
- Java.lang.reflect.Method#invoke()
數(shù)據(jù)庫同步數(shù)據(jù)案例
我們來實現(xiàn)一個簡化版的數(shù)據(jù)同步方案,有三種數(shù)據(jù)庫 Mysql
、Redis
、Elasticsearch
,
- 其中的 Mysql 作為主數(shù)據(jù)庫,當(dāng)增加一條數(shù)據(jù)時需要同步到另外兩個數(shù)據(jù)庫中;
- Redis 作為緩存數(shù)據(jù)庫,當(dāng)增加一條數(shù)據(jù)時不需要同步到另外另個數(shù)據(jù)庫;
- 而 Elasticsearch 作為大數(shù)據(jù)查詢數(shù)據(jù)庫,有一個統(tǒng)計功能,當(dāng)增加一條數(shù)據(jù)時只需要同步到 Mysql,
所以它們之間的關(guān)系圖如下所示。
不使用中介者模式的數(shù)據(jù)同步方案,各數(shù)據(jù)源維護(hù)各自的同步作業(yè)
抽象數(shù)據(jù)庫
public abstract class AbstractDatabase { //存儲數(shù)據(jù) protected LinkedList<String> datas=new LinkedList<>(); //向自己的數(shù)據(jù)庫中增加數(shù)據(jù)的方法 public abstract void addData(String data); //同步數(shù)據(jù)的方法--默認(rèn)空實現(xiàn) public void DataStore(String data){} //展示當(dāng)前數(shù)據(jù)庫所有數(shù)據(jù) public void display() { datas.forEach(x->System.out.println(x)); } }
具體數(shù)據(jù)庫 Mysql,維護(hù)同步到 Redis和Elasticsearch 的同步作業(yè)
public class MySql extends AbstractDatabase { @Setter private Elasticsearch elasticsearch; @Setter private Redis redis; //向自己的數(shù)據(jù)庫增加數(shù)據(jù) @Override public void addData(String data) { System.out.println("====向Mysql數(shù)據(jù)庫增加一條數(shù)據(jù)===="); System.out.println("增加的數(shù)據(jù)為:"+data); System.out.println("====================================="); datas.add(data); } //重寫父類數(shù)據(jù)同步的方法 @Override public void DataStore(String data) { addData(data); elasticsearch.addData(data); redis.addData(data); } }
Elasticsearch ,只需要同步到Mysql
public class Elasticsearch extends AbstractDatabase { @Setter private MySql mySql; //給自己增加數(shù)據(jù)的方法 @Override public void addData(String data) { System.out.println("====向Elasticsearch數(shù)據(jù)庫增加一條數(shù)據(jù)===="); System.out.println("增加的數(shù)據(jù)為:"+data); System.out.println("====================================="); datas.add(data); } //重寫父類數(shù)據(jù)同步的方法 @Override public void DataStore(String data) { addData(data); //數(shù)據(jù)同步 mySql.addData(data); } }
具體數(shù)據(jù)庫 Redis,不需要同步到其它數(shù)據(jù)庫
public class Redis extends AbstractDatabase { //給自己增加數(shù)據(jù)的方法 @Override public void addData(String data) { System.out.println("====向Redis數(shù)據(jù)庫增加一條數(shù)據(jù)===="); System.out.println("增加的數(shù)據(jù)為:"+data); System.out.println("====================================="); datas.add(data); } }
客戶端測試:
public class Client { public static void main(String[] args) { Elasticsearch elasticsearch=new Elasticsearch(); MySql mySql=new MySql(); Redis redis=new Redis(); elasticsearch.setMySql(mySql); mySql.setElasticsearch(elasticsearch); mySql.setRedis(redis); //增加數(shù)據(jù) mySql.DataStore("大忽悠"); elasticsearch.DataStore("李窈"); redis.addData("小朋友"); System.out.println("mysql數(shù)據(jù)庫中的數(shù)據(jù)如下:"); mySql.display(); System.out.println("elasticsearch數(shù)據(jù)庫中的數(shù)據(jù)如下:"); elasticsearch.display(); System.out.println("redis數(shù)據(jù)庫中數(shù)據(jù)如下:"); redis.display(); } }
其實這樣已經(jīng)實現(xiàn)了我們的需求,但是存在一些問題
- 系統(tǒng)結(jié)構(gòu)復(fù)雜且耦合度高。數(shù)據(jù)源需要維護(hù)目標(biāo)端數(shù)據(jù)庫的引用,以便完成數(shù)據(jù)同步
- 組件的可重用性差。由于每一個數(shù)據(jù)源和目標(biāo)端之間具有很強(qiáng)的關(guān)聯(lián),若沒有目標(biāo)端的支持,這個組件很難被另一個系統(tǒng)或模塊重用
- 系統(tǒng)的可擴(kuò)展性差:如果需要增加、修改或刪除其中一個數(shù)據(jù)庫、將導(dǎo)致多個類的源代碼需要修改,這違反了“開閉原則”,可擴(kuò)展性和靈活性欠佳。
中介者模式來重構(gòu),將數(shù)據(jù)同步的功能遷移到中介者中,由中介者來管理數(shù)據(jù)同步作業(yè)
抽象中介者:
//抽象中介者 @Data public abstract class AbstractMediator { protected MySql mySql; protected Elasticsearch elasticsearch; protected Redis redis; public abstract void sync(String databaseName, String data); }
首先還是抽象數(shù)據(jù)庫類(抽象同事類),維護(hù)了一個中介者
public abstract class AbstractDatabase { public static final String MYSQL="mysql"; public static final String Elasticsearch="elasticsearch"; public static final String REDIS="redis"; //保存一個中介者對象 @Setter protected AbstractMediator Mediator; //存儲數(shù)據(jù) protected LinkedList<String> datas=new LinkedList<>(); //向自己的數(shù)據(jù)庫中增加數(shù)據(jù)的方法 public abstract void addData(String data); //同步數(shù)據(jù)的方法--默認(rèn)空實現(xiàn) public void DataStore(String data){} //展示當(dāng)前數(shù)據(jù)庫所有數(shù)據(jù) public void display() { datas.forEach(x->System.out.println(x)); } }
Mysql 數(shù)據(jù)庫(具體同事類)
public class MySql extends AbstractDatabase { //向自己的數(shù)據(jù)庫增加數(shù)據(jù) @Override public void addData(String data) { System.out.println("====向Mysql數(shù)據(jù)庫增加一條數(shù)據(jù)===="); System.out.println("增加的數(shù)據(jù)為:"+data); System.out.println("====================================="); datas.add(data); } //重寫父類數(shù)據(jù)同步的方法 @Override public void DataStore(String data) { addData(data); //將數(shù)據(jù)同步到redis和elasticsearch的工作由中介完成 mediator.sync(AbstractDatabase.MYSQL,data); } }
Redis 數(shù)據(jù)庫(具體同事類)
public class Redis extends AbstractDatabase { //給自己增加數(shù)據(jù)的方法 @Override public void addData(String data) { System.out.println("====向Redis數(shù)據(jù)庫增加一條數(shù)據(jù)===="); System.out.println("增加的數(shù)據(jù)為:"+data); System.out.println("====================================="); datas.add(data); } //重新父類同步數(shù)據(jù)的方法 @Override public void DataStore(String data) { addData(data); //同步數(shù)據(jù)的工作交給中介 mediator.sync(AbstractDatabase.REDIS,data); } }
Elasticsearch(具體同事類)
public class Elasticsearch extends AbstractDatabase { //給自己增加數(shù)據(jù)的方法 @Override public void addData(String data) { System.out.println("====向Elasticsearch數(shù)據(jù)庫增加一條數(shù)據(jù)===="); System.out.println("增加的數(shù)據(jù)為:"+data); System.out.println("====================================="); datas.add(data); } //重寫父類數(shù)據(jù)同步的方法 @Override public void DataStore(String data) { addData(data); //數(shù)據(jù)同步 mediator.sync(AbstractDatabase.Elasticsearch,data); } }
具體中介者:
public class SyncMediator extends AbstractMediator { @Override public void sync(String databaseName, String data) { if (AbstractDatabase.MYSQL.equals(databaseName)) { // mysql 同步到 redis 和 Elasticsearch this.redis.addData(data); this.elasticsearch.addData(data); } else if (AbstractDatabase.REDIS.equals(databaseName)) { // redis 緩存同步,不需要同步到其他數(shù)據(jù)庫 } else if (AbstractDatabase.Elasticsearch.equals(databaseName)) { // Elasticsearch 同步到 Mysql this.mySql.addData(data); } } }
測試客戶端
public class Client { public static void main(String[] args) { AbstractMediator mediator=new SyncMediator(); Elasticsearch elasticsearch=new Elasticsearch(); MySql mySql=new MySql(); Redis redis=new Redis(); elasticsearch.setMediator(mediator); mySql.setMediator(mediator); redis.setMediator(mediator); mediator.setMySql(mySql); mediator.setElasticsearch(elasticsearch); mediator.setRedis(redis); //增加數(shù)據(jù) mySql.DataStore("大忽悠"); elasticsearch.DataStore("李窈"); redis.DataStore("小朋友"); System.out.println("mysql數(shù)據(jù)庫中的數(shù)據(jù)如下:"); mySql.display(); System.out.println("elasticsearch數(shù)據(jù)庫中的數(shù)據(jù)如下:"); elasticsearch.display(); System.out.println("redis數(shù)據(jù)庫中數(shù)據(jù)如下:"); redis.display(); } }
小結(jié)
主要優(yōu)點
- 中介者模式簡化了對象之間的交互,它用中介者和同事的一對多交互代替了原來同事之間的多對多交互,一對多關(guān)系更容易理解、維護(hù)和擴(kuò)展,將原本難以理解的網(wǎng)狀結(jié)構(gòu)轉(zhuǎn)換成相對簡單的星型結(jié)構(gòu)。
- 中介者模式可將各同事對象解耦。中介者有利于各同事之間的松耦合,我們可以獨立的改變和復(fù)用每一個同事和中介者,增加新的中介者和新的同事類都比較方便,更好地符合 “開閉原則”。
- 可以減少子類生成,中介者將原本分布于多個對象間的行為集中在一起,改變這些行為只需生成新的中介者子類即可,這使各個同事類可被重用,無須對同事類進(jìn)行擴(kuò)展。
中介者模式的主要缺點
- 在具體中介者類中包含了大量同事之間的交互細(xì)節(jié),可能會導(dǎo)致具體中介者類非常復(fù)雜,使得系統(tǒng)難以維護(hù)。(也就是把具體同事類之間的交互復(fù)雜性集中到了中介者類中,結(jié)果中介者成了最復(fù)雜的類)
適用場景
- 系統(tǒng)中對象之間存在復(fù)雜的引用關(guān)系,系統(tǒng)結(jié)構(gòu)混亂且難以理解。
- 一個對象由于引用了其他很多對象并且直接和這些對象通信,導(dǎo)致難以復(fù)用該對象。
- 想通過一個中間類來封裝多個類中的行為,而又不想生成太多的子類。可以通過引入中介者類來實現(xiàn),在中介者中定義對象交互的公共行為,如果需要改變行為則可以增加新的具體中介者類
具體應(yīng)用
Java Timer 中的中介者模式
敲一個 java.util.Timer 的Demo
兩個任務(wù)類
public class MyOneTask extends TimerTask { private static int num = 0; @Override public void run() { System.out.println("I"m MyOneTask " + ++num); } } public class MyTwoTask extends TimerTask { private static int num = 1000; @Override public void run() { System.out.println("I"m MyTwoTask " + num--); } }
客戶端測試,3秒后開始執(zhí)行,循環(huán)周期為 1秒
public class TimerTest { public static void main(String[] args) { // 注意:多線程并行處理定時任務(wù)時,Timer運行多個TimeTask時,只要其中之一沒有捕獲拋出的異常, // 其它任務(wù)便會自動終止運行,使用ScheduledExecutorService則沒有這個問題 Timer timer = new Timer(); timer.schedule(new MyOneTask(), 3000, 1000); // 3秒后開始運行,循環(huán)周期為 1秒 timer.schedule(new MyTwoTask(), 3000, 1000); } }
Timer 的部分關(guān)鍵源碼如下
public class Timer { private final TaskQueue queue = new TaskQueue(); private final TimerThread thread = new TimerThread(queue); public void schedule(TimerTask task, long delay) { if (delay < 0) throw new IllegalArgumentException("Negative delay."); sched(task, System.currentTimeMillis()+delay, 0); } public void schedule(TimerTask task, Date time) { sched(task, time.getTime(), 0); } private void sched(TimerTask task, long time, long period) { if (time < 0) throw new IllegalArgumentException("Illegal execution time."); if (Math.abs(period) > (Long.MAX_VALUE >> 1)) period >>= 1; // 獲取任務(wù)隊列的鎖(同一個線程多次獲取這個鎖并不會被阻塞,不同線程獲取時才可能被阻塞) synchronized(queue) { // 如果定時調(diào)度線程已經(jīng)終止了,則拋出異常結(jié)束 if (!thread.newTasksMayBeScheduled) throw new IllegalStateException("Timer already cancelled."); // 再獲取定時任務(wù)對象的鎖(為什么還要再加這個鎖呢?想不清) synchronized(task.lock) { // 判斷線程的狀態(tài),防止多線程同時調(diào)度到一個任務(wù)時多次被加入任務(wù)隊列 if (task.state != TimerTask.VIRGIN) throw new IllegalStateException( "Task already scheduled or cancelled"); // 初始化定時任務(wù)的下次執(zhí)行時間 task.nextExecutionTime = time; // 重復(fù)執(zhí)行的間隔時間 task.period = period; // 將定時任務(wù)的狀態(tài)由TimerTask.VIRGIN(一個定時任務(wù)的初始化狀態(tài))設(shè)置為TimerTask.SCHEDULED task.state = TimerTask.SCHEDULED; } // 將任務(wù)加入任務(wù)隊列 queue.add(task); // 如果當(dāng)前加入的任務(wù)是需要第一個被執(zhí)行的(也就是他的下一次執(zhí)行時間離現(xiàn)在最近) // 則喚醒等待queue的線程(對應(yīng)到上面提到的queue.wait()) if (queue.getMin() == task) queue.notify(); } } // cancel會等到所有定時任務(wù)執(zhí)行完后立刻終止定時線程 public void cancel() { synchronized(queue) { thread.newTasksMayBeScheduled = false; queue.clear(); queue.notify(); // In case queue was already empty. } } // ... }
Timer
中在 schedulexxx
方法中通過 TaskQueue
協(xié)調(diào)各種 TimerTask 定時任務(wù),Timer 是中介者,TimerTask 是抽象同事類,而我們自己寫的任務(wù)則是具體同事類
TimerThread
是 Timer
中定時調(diào)度線程類的定義,這個類會做為一個線程一直運行來執(zhí)行 Timer 中任務(wù)隊列中的任務(wù)。
Timer 這個中介者的功能就是定時調(diào)度我們寫的各種任務(wù),將任務(wù)添加到 TaskQueue 任務(wù)隊列中,給 TimerThread 執(zhí)行,讓任務(wù)與執(zhí)行線程解耦
參考文章
本篇文章就到這里了,希望能夠給你帶來幫助,也希望您能夠多多關(guān)注服務(wù)器之家的更多內(nèi)容!
原文鏈接:https://blog.csdn.net/m0_53157173/article/details/120212092