下面總結(jié)設(shè)計(jì)模式中的行為型模式:
1.責(zé)任鏈模式
顧名思義,責(zé)任鏈模式(Chain of Responsibility Pattern)為請(qǐng)求創(chuàng)建了一個(gè)接收者對(duì)象的鏈。這種模式給予請(qǐng)求的類(lèi)型,對(duì)請(qǐng)求的發(fā)送者和接收者進(jìn)行解耦。這種類(lèi)型的設(shè)計(jì)模式屬于行為型模式。
在這種模式中,通常每個(gè)接收者都包含對(duì)另一個(gè)接收者的引用。如果一個(gè)對(duì)象不能處理該請(qǐng)求,那么它會(huì)把相同的請(qǐng)求傳給下一個(gè)接收者,依此類(lèi)推。
介紹
意圖:避免請(qǐng)求發(fā)送者與接收者耦合在一起,讓多個(gè)對(duì)象都有可能接收請(qǐng)求,將這些對(duì)象連接成一條鏈,并且沿著這條鏈傳遞請(qǐng)求,直到有對(duì)象處理它為止。
主要解決:職責(zé)鏈上的處理者負(fù)責(zé)處理請(qǐng)求,客戶(hù)只需要將請(qǐng)求發(fā)送到職責(zé)鏈上即可,無(wú)須關(guān)心請(qǐng)求的處理細(xì)節(jié)和請(qǐng)求的傳遞,所以職責(zé)鏈將請(qǐng)求的發(fā)送者和請(qǐng)求的處理者解耦了。
何時(shí)使用:在處理消息的時(shí)候以過(guò)濾很多道。
如何解決:攔截的類(lèi)都實(shí)現(xiàn)統(tǒng)一接口。
關(guān)鍵代碼:Handler里面聚合它自己,在 HandlerRequest 里判斷是否合適,如果沒(méi)達(dá)到條件則向下傳遞,向誰(shuí)傳遞之前 set 進(jìn)去。
應(yīng)用實(shí)例: 1、紅樓夢(mèng)中的"擊鼓傳花"。 2、JS 中的事件冒泡。 3、JAVA WEB 中 Apache Tomcat 對(duì) Encoding 的處理,Struts2 的攔截器,jsp servlet 的 Filter。 4.JAVA 的異常鏈機(jī)制
優(yōu)點(diǎn): 1、降低耦合度。它將請(qǐng)求的發(fā)送者和接收者解耦。 2、簡(jiǎn)化了對(duì)象。使得對(duì)象不需要知道鏈的結(jié)構(gòu)。 3、增強(qiáng)給對(duì)象指派職責(zé)的靈活性。通過(guò)改變鏈內(nèi)的成員或者調(diào)動(dòng)它們的次序,允許動(dòng)態(tài)地新增或者刪除責(zé)任。 4、增加新的請(qǐng)求處理類(lèi)很方便。
缺點(diǎn): 1、不能保證請(qǐng)求一定被接收。 2、系統(tǒng)性能將受到一定影響,而且在進(jìn)行代碼調(diào)試時(shí)不太方便,可能會(huì)造成循環(huán)調(diào)用。 3、可能不容易觀(guān)察運(yùn)行時(shí)的特征,有礙于除錯(cuò)。
使用場(chǎng)景: 1、有多個(gè)對(duì)象可以處理同一個(gè)請(qǐng)求,具體哪個(gè)對(duì)象處理該請(qǐng)求由運(yùn)行時(shí)刻自動(dòng)確定。 2、在不明確指定接收者的情況下,向多個(gè)對(duì)象中的一個(gè)提交一個(gè)請(qǐng)求。 3、可動(dòng)態(tài)指定一組對(duì)象處理請(qǐng)求。
注意事項(xiàng):在 JAVA WEB 中遇到很多應(yīng)用。

實(shí)現(xiàn)
我們以銷(xiāo)售樓盤(pán)為例,客戶(hù)是Customer類(lèi),發(fā)送一個(gè)折扣請(qǐng)求給責(zé)任鏈。責(zé)任鏈由樓盤(pán)各級(jí)人員組成,都繼承自PriceHandler抽象類(lèi),自底向上為Sales(銷(xiāo)售)、Lead(銷(xiāo)售小組長(zhǎng))、Manager(銷(xiāo)售經(jīng)理)、Director(銷(xiāo)售總監(jiān))、VicePresident(銷(xiāo)售副總裁)、CEO(首席執(zhí)行官)。沿著責(zé)任鏈,能批準(zhǔn)折扣的力度依次上升。
步驟 1 創(chuàng)建PriceHandler抽象類(lèi)
PriceHandler.java
package com.imooc.pattern.cor.handler;
/*
* 價(jià)格處理人,負(fù)責(zé)處理客戶(hù)折扣申請(qǐng)
*/
public abstract class PriceHandler {
/*
* 直接后繼,用于傳遞請(qǐng)求
*/
protected PriceHandler successor;
public void setSuccessor(PriceHandler successor) {
this.successor = successor;
}
/*
* 處理折扣申請(qǐng)
*/
public abstract void processDiscount(float discount);
}
步驟 2 創(chuàng)建PriceHandler的具體類(lèi)
Sales.java
package com.imooc.pattern.cor.handler;
/*
* 銷(xiāo)售, 可以批準(zhǔn)5%以?xún)?nèi)的折扣
*/
public class Sales extends PriceHandler {
@Override
public void processDiscount(float discount) {
if(discount <= 0.05){
System.out.format("%s批準(zhǔn)了折扣:%.2f%n", this.getClass().getName(), discount);
}else{
successor.processDiscount(discount);
}
}
}
Lead.java
package com.imooc.pattern.cor.handler;
/*
* 銷(xiāo)售小組長(zhǎng), 可以批準(zhǔn)15%以?xún)?nèi)的折扣
*/
public class Lead extends PriceHandler {
@Override
public void processDiscount(float discount) {
if(discount<=0.15){
System.out.format("%s批準(zhǔn)了折扣:%.2f%n",this.getClass().getName(),discount);
}else{
successor.processDiscount(discount);
}
}
}
Manager.java
package com.imooc.pattern.cor.handler;
/*
* 銷(xiāo)售經(jīng)理, 可以批準(zhǔn)30%以?xún)?nèi)的折扣
*/
public class Manager extends PriceHandler {
@Override
public void processDiscount(float discount) {
if(discount<=0.3){
System.out.format("%s批準(zhǔn)了折扣:%.2f%n",this.getClass().getName(),discount);
}else{
successor.processDiscount(discount);
}
}
}
Director.java
package com.imooc.pattern.cor.handler;
/*
* 銷(xiāo)售總監(jiān), 可以批準(zhǔn)40%以?xún)?nèi)的折扣
*/
public class Director extends PriceHandler {
@Override
public void processDiscount(float discount) {
if(discount<=0.4){
System.out.format("%s批準(zhǔn)了折扣:%.2f%n",this.getClass().getName(),discount);
}else{
successor.processDiscount(discount);
}
}
}
VicePresident.java
package com.imooc.pattern.cor.handler;
/*
* 銷(xiāo)售副總裁, 可以批準(zhǔn)50%以?xún)?nèi)的折扣
*/
public class VicePresident extends PriceHandler {
@Override
public void processDiscount(float discount) {
if(discount<=0.5){
System.out.format("%s批準(zhǔn)了折扣:%.2f%n",this.getClass().getName(),discount);
}else{
successor.processDiscount(discount);
}
}
}
CEO.java
package com.imooc.pattern.cor.handler;
/*
* CEO, 可以批準(zhǔn)55%以?xún)?nèi)的折扣
* 折扣超出55%, 就拒絕申請(qǐng)
*/
public class CEO extends PriceHandler {
@Override
public void processDiscount(float discount) {
if(discount<=0.55){
System.out.format("%s批準(zhǔn)了折扣:%.2f%n",this.getClass().getName(),discount);
}else{
System.out.format("%s拒絕了折扣:%.2f%n", this.getClass().getName(),discount);
}
}
}
步驟 3 創(chuàng)建PriceHandlerFactory類(lèi)
package com.imooc.pattern.cor.handler;
public class PriceHandlerFactory {
/*
* 創(chuàng)建PriceHandler的工廠(chǎng)方法,類(lèi)似于構(gòu)建鏈表并返回表頭
*/
public static PriceHandler createPriceHandler() {
PriceHandler sales = new Sales();
PriceHandler lead = new Lead();
PriceHandler man = new Manager();
PriceHandler dir = new Director();
PriceHandler vp = new VicePresident();
PriceHandler ceo = new CEO();
sales.setSuccessor(lead);
lead.setSuccessor(man);
man.setSuccessor(dir);
dir.setSuccessor(vp);
vp.setSuccessor(ceo);
return sales;
}
}
步驟 4 創(chuàng)建Customer類(lèi)
package com.imooc.pattern.cor;
import java.util.Random;
import com.imooc.pattern.cor.handler.PriceHandler;
import com.imooc.pattern.cor.handler.PriceHandlerFactory;
/*
* 客戶(hù),請(qǐng)求折扣
*/
public class Customer {
private PriceHandler priceHandler;
public void setPriceHandler(PriceHandler priceHandler) {
this.priceHandler = priceHandler;
}
public void requestDiscount(float discount){
priceHandler.processDiscount(discount);
}
public static void main(String[] args){
Customer customer = new Customer();
customer.setPriceHandler(PriceHandlerFactory.createPriceHandler());
Random rand = new Random();
for(int i=1;i<=100;i++){
System.out.print(i+":");
customer.requestDiscount(rand.nextFloat());
}
}
}
2.命令模式
命令模式(Command Pattern)是一種數(shù)據(jù)驅(qū)動(dòng)的設(shè)計(jì)模式,它屬于行為型模式。請(qǐng)求以命令的形式包裹在對(duì)象中,并傳給調(diào)用對(duì)象。調(diào)用對(duì)象尋找可以處理該命令的合適的對(duì)象,并把該命令傳給相應(yīng)的對(duì)象,該對(duì)象執(zhí)行命令。
介紹
意圖:將一個(gè)請(qǐng)求封裝成一個(gè)對(duì)象,從而使您可以用不同的請(qǐng)求對(duì)客戶(hù)進(jìn)行參數(shù)化。
主要解決:在軟件系統(tǒng)中,行為請(qǐng)求者與行為實(shí)現(xiàn)者通常是一種緊耦合的關(guān)系,但某些場(chǎng)合,比如需要對(duì)行為進(jìn)行記錄、撤銷(xiāo)或重做、事務(wù)等處理時(shí),這種無(wú)法抵御變化的緊耦合的設(shè)計(jì)就不太合適。
何時(shí)使用:在某些場(chǎng)合,比如要對(duì)行為進(jìn)行"記錄、撤銷(xiāo)/重做、事務(wù)"等處理,這種無(wú)法抵御變化的緊耦合是不合適的。在這種情況下,如何將"行為請(qǐng)求者"與"行為實(shí)現(xiàn)者"解耦?將一組行為抽象為對(duì)象,可以實(shí)現(xiàn)二者之間的松耦合。
如何解決:通過(guò)調(diào)用者調(diào)用接受者執(zhí)行命令,順序:調(diào)用者→接受者→命令。
關(guān)鍵代碼:定義三個(gè)角色:1、received 真正的命令執(zhí)行對(duì)象 2、Command 3、invoker 使用命令對(duì)象的入口
應(yīng)用實(shí)例:struts 1 中的 action 核心控制器 ActionServlet 只有一個(gè),相當(dāng)于 Invoker,而模型層的類(lèi)會(huì)隨著不同的應(yīng)用有不同的模型類(lèi),相當(dāng)于具體的 Command。
優(yōu)點(diǎn):1、降低了系統(tǒng)耦合度。 2、新的命令可以很容易添加到系統(tǒng)中去。
缺點(diǎn):使用命令模式可能會(huì)導(dǎo)致某些系統(tǒng)有過(guò)多的具體命令類(lèi)。
使用場(chǎng)景:認(rèn)為是命令的地方都可以使用命令模式,比如: 1、GUI 中每一個(gè)按鈕都是一條命令。 2、模擬 CMD。
注意事項(xiàng):系統(tǒng)需要支持命令的撤銷(xiāo)(Undo)操作和恢復(fù)(Redo)操作,也可以考慮使用命令模式,見(jiàn)命令模式的擴(kuò)展。
實(shí)現(xiàn)
我們首先創(chuàng)建作為命令的接口 Order,然后創(chuàng)建作為請(qǐng)求的 Stock 類(lèi)。實(shí)體命令類(lèi) BuyStock 和 SellStock,實(shí)現(xiàn)了 Order 接口,將執(zhí)行實(shí)際的命令處理。創(chuàng)建作為調(diào)用對(duì)象的類(lèi) Broker,它接受訂單并能下訂單。
Broker 對(duì)象使用命令模式,基于命令的類(lèi)型確定哪個(gè)對(duì)象執(zhí)行哪個(gè)命令。CommandPatternDemo,我們的演示類(lèi)使用 Broker 類(lèi)來(lái)演示命令模式。

步驟 1
創(chuàng)建一個(gè)命令接口。
Order.java
public interface Order {
void execute();
}
步驟 2
創(chuàng)建一個(gè)請(qǐng)求類(lèi)。
Stock.java
public class Stock {
private String name = "ABC";
private int quantity = 10;
public void buy(){
System.out.println("Stock [ Name: "+name+",
Quantity: " + quantity +" ] bought");
}
public void sell(){
System.out.println("Stock [ Name: "+name+",
Quantity: " + quantity +" ] sold");
}
}
步驟 3
創(chuàng)建實(shí)現(xiàn)了 Order 接口的實(shí)體類(lèi)。
BuyStock.java
public class BuyStock implements Order {
private Stock abcStock;
public BuyStock(Stock abcStock){
this.abcStock = abcStock;
}
public void execute() {
abcStock.buy();
}
}
SellStock.java
public class SellStock implements Order {
private Stock abcStock;
public SellStock(Stock abcStock){
this.abcStock = abcStock;
}
public void execute() {
abcStock.sell();
}
}
步驟 4
創(chuàng)建命令調(diào)用類(lèi)。
Broker.java
import java.util.ArrayList;
import java.util.List;
public class Broker {
private List<Order> orderList = new ArrayList<Order>();
public void takeOrder(Order order){
orderList.add(order);
}
public void placeOrders(){
for (Order order : orderList) {
order.execute();
}
orderList.clear();
}
}
步驟 5
使用 Broker 類(lèi)來(lái)接受并執(zhí)行命令。
CommandPatternDemo.java
public class CommandPatternDemo {
public static void main(String[] args) {
Stock abcStock = new Stock();
BuyStock buyStockOrder = new BuyStock(abcStock);
SellStock sellStockOrder = new SellStock(abcStock);
Broker broker = new Broker();
broker.takeOrder(buyStockOrder);
broker.takeOrder(sellStockOrder);
broker.placeOrders();
}
}
步驟 6
執(zhí)行程序,輸出結(jié)果:
Stock [ Name: ABC, Quantity: 10 ] bought
Stock [ Name: ABC, Quantity: 10 ] sold
3.解釋器模式
解釋器模式(Interpreter Pattern)提供了評(píng)估語(yǔ)言的語(yǔ)法或表達(dá)式的方式,它屬于行為型模式。這種模式實(shí)現(xiàn)了一個(gè)表達(dá)式接口,該接口解釋一個(gè)特定的上下文。這種模式被用在 SQL 解析、符號(hào)處理引擎等。
介紹
意圖:給定一個(gè)語(yǔ)言,定義它的文法表示,并定義一個(gè)解釋器,這個(gè)解釋器使用該標(biāo)識(shí)來(lái)解釋語(yǔ)言中的句子。
主要解決:對(duì)于一些固定文法構(gòu)建一個(gè)解釋句子的解釋器。
何時(shí)使用:如果一種特定類(lèi)型的問(wèn)題發(fā)生的頻率足夠高,那么可能就值得將該問(wèn)題的各個(gè)實(shí)例表述為一個(gè)簡(jiǎn)單語(yǔ)言中的句子。這樣就可以構(gòu)建一個(gè)解釋器,該解釋器通過(guò)解釋這些句子來(lái)解決該問(wèn)題。
如何解決:構(gòu)建語(yǔ)法樹(shù),定義終結(jié)符與非終結(jié)符。
關(guān)鍵代碼:構(gòu)件環(huán)境類(lèi),包含解釋器之外的一些全局信息,一般是 HashMap。
應(yīng)用實(shí)例:編譯器、運(yùn)算表達(dá)式計(jì)算。
優(yōu)點(diǎn):1、可擴(kuò)展性比較好,靈活。 2、增加了新的解釋表達(dá)式的方式。 3、易于實(shí)現(xiàn)簡(jiǎn)單文法。
缺點(diǎn):1、可利用場(chǎng)景比較少。 2、對(duì)于復(fù)雜的文法比較難維護(hù)。 3、解釋器模式會(huì)引起類(lèi)膨脹。 4、解釋器模式采用遞歸調(diào)用方法。
使用場(chǎng)景:1、可以將一個(gè)需要解釋執(zhí)行的語(yǔ)言中的句子表示為一個(gè)抽象語(yǔ)法樹(shù)。 2、一些重復(fù)出現(xiàn)的問(wèn)題可以用一種簡(jiǎn)單的語(yǔ)言來(lái)進(jìn)行表達(dá)。 3、一個(gè)簡(jiǎn)單語(yǔ)法需要解釋的場(chǎng)景。
注意事項(xiàng):可利用場(chǎng)景比較少,JAVA 中如果碰到可以用 expression4J 代替。
實(shí)現(xiàn)
我們將創(chuàng)建一個(gè)接口 Expression 和實(shí)現(xiàn)了 Expression 接口的實(shí)體類(lèi)。定義作為上下文中主要解釋器的 TerminalExpression 類(lèi)。其他的類(lèi) OrExpression、AndExpression 用于創(chuàng)建組合式表達(dá)式。
InterpreterPatternDemo,我們的演示類(lèi)使用 Expression 類(lèi)創(chuàng)建規(guī)則和演示表達(dá)式的解析。

步驟 1
創(chuàng)建一個(gè)表達(dá)式接口。
Expression.java
public interface Expression {
public boolean interpret(String context);
}
步驟 2
創(chuàng)建實(shí)現(xiàn)了上述接口的實(shí)體類(lèi)。
TerminalExpression.java
public class TerminalExpression implements Expression {
private String data;
public TerminalExpression(String data){
this.data = data;
}
@Override
public boolean interpret(String context) {
if(context.contains(data)){
return true;
}
return false;
}
}
OrExpression.java
public class OrExpression implements Expression {
private Expression expr1 = null;
private Expression expr2 = null;
public OrExpression(Expression expr1, Expression expr2) {
this.expr1 = expr1;
this.expr2 = expr2;
}
@Override
public boolean interpret(String context) {
return expr1.interpret(context) || expr2.interpret(context);
}
}
AndExpression.java
public class AndExpression implements Expression {
private Expression expr1 = null;
private Expression expr2 = null;
public AndExpression(Expression expr1, Expression expr2) {
this.expr1 = expr1;
this.expr2 = expr2;
}
@Override
public boolean interpret(String context) {
return expr1.interpret(context) && expr2.interpret(context);
}
}
步驟 3
InterpreterPatternDemo 使用 Expression 類(lèi)來(lái)創(chuàng)建規(guī)則,并解析它們。
InterpreterPatternDemo.java
public class InterpreterPatternDemo {
//規(guī)則:Robert 和 John 是男性
public static Expression getMaleExpression(){
Expression robert = new TerminalExpression("Robert");
Expression john = new TerminalExpression("John");
return new OrExpression(robert, john);
}
//規(guī)則:Julie 是一個(gè)已婚的女性
public static Expression getMarriedWomanExpression(){
Expression julie = new TerminalExpression("Julie");
Expression married = new TerminalExpression("Married");
return new AndExpression(julie, married);
}
public static void main(String[] args) {
Expression isMale = getMaleExpression();
Expression isMarriedWoman = getMarriedWomanExpression();
System.out.println("John is male? " + isMale.interpret("John"));
System.out.println("Julie is a married women? "
+ isMarriedWoman.interpret("Married Julie"));
}
}
步驟 4
執(zhí)行程序,輸出結(jié)果:
John is male? true
Julie is a married women? true
4.迭代器模式
迭代器模式(Iterator Pattern)是 Java 和 .Net 編程環(huán)境中非常常用的設(shè)計(jì)模式。這種模式用于順序訪(fǎng)問(wèn)集合對(duì)象的元素,不需要知道集合對(duì)象的底層表示。
迭代器模式屬于行為型模式。
介紹
意圖:提供一種方法順序訪(fǎng)問(wèn)一個(gè)聚合對(duì)象中各個(gè)元素, 而又無(wú)須暴露該對(duì)象的內(nèi)部表示。
主要解決:不同的方式來(lái)遍歷整個(gè)整合對(duì)象。
何時(shí)使用:遍歷一個(gè)聚合對(duì)象。
如何解決:把在元素之間游走的責(zé)任交給迭代器,而不是聚合對(duì)象。
關(guān)鍵代碼:定義接口:hasNext, next。
應(yīng)用實(shí)例:JAVA 中的 iterator。
優(yōu)點(diǎn):1、它支持以不同的方式遍歷一個(gè)聚合對(duì)象。 2、迭代器簡(jiǎn)化了聚合類(lèi)。 3、在同一個(gè)聚合上可以有多個(gè)遍歷。 4、在迭代器模式中,增加新的聚合類(lèi)和迭代器類(lèi)都很方便,無(wú)須修改原有代碼。
缺點(diǎn):由于迭代器模式將存儲(chǔ)數(shù)據(jù)和遍歷數(shù)據(jù)的職責(zé)分離,增加新的聚合類(lèi)需要對(duì)應(yīng)增加新的迭代器類(lèi),類(lèi)的個(gè)數(shù)成對(duì)增加,這在一定程度上增加了系統(tǒng)的復(fù)雜性。
使用場(chǎng)景:1、訪(fǎng)問(wèn)一個(gè)聚合對(duì)象的內(nèi)容而無(wú)須暴露它的內(nèi)部表示。 2、需要為聚合對(duì)象提供多種遍歷方式。 3、為遍歷不同的聚合結(jié)構(gòu)提供一個(gè)統(tǒng)一的接口。
注意事項(xiàng):迭代器模式就是分離了集合對(duì)象的遍歷行為,抽象出一個(gè)迭代器類(lèi)來(lái)負(fù)責(zé),這樣既可以做到不暴露集合的內(nèi)部結(jié)構(gòu),又可讓外部代碼透明地訪(fǎng)問(wèn)集合內(nèi)部的數(shù)據(jù)。
實(shí)現(xiàn)
我們將創(chuàng)建一個(gè)敘述導(dǎo)航方法的 Iterator 接口和一個(gè)返回迭代器的 Container 接口。實(shí)現(xiàn)了 Container 接口的實(shí)體類(lèi)將負(fù)責(zé)實(shí)現(xiàn) Iterator 接口。
IteratorPatternDemo,我們的演示類(lèi)使用實(shí)體類(lèi) NamesRepository 來(lái)打印 NamesRepository 中存儲(chǔ)為集合的 Names。

步驟 1
創(chuàng)建接口:
Iterator.java
public interface Iterator {
public boolean hasNext();
public Object next();
}
Container.java
public interface Container {
public Iterator getIterator();
}
步驟 2
創(chuàng)建實(shí)現(xiàn)了 Container 接口的實(shí)體類(lèi)。該類(lèi)有實(shí)現(xiàn)了 Iterator 接口的內(nèi)部類(lèi) NameIterator。
NameRepository.java
public class NameRepository implements Container {
public String names[] = {"Robert" , "John" ,"Julie" , "Lora"};
@Override
public Iterator getIterator() {
return new NameIterator();
}
private class NameIterator implements Iterator {
int index;
@Override
public boolean hasNext() {
if(index < names.length){
return true;
}
return false;
}
@Override
public Object next() {
if(this.hasNext()){
return names[index++];
}
return null;
}
}
}
步驟 3
使用 NameRepository 來(lái)獲取迭代器,并打印名字。
IteratorPatternDemo.java
public class IteratorPatternDemo {
public static void main(String[] args) {
NameRepository namesRepository = new NameRepository();
for(Iterator iter = namesRepository.getIterator(); iter.hasNext();){
String name = (String)iter.next();
System.out.println("Name : " + name);
}
}
}
步驟 4
執(zhí)行程序,輸出結(jié)果:
Name : Robert
Name : John
Name : Julie
Name : Lora
5.中介者模式
中介者模式(Mediator Pattern)是用來(lái)降低多個(gè)對(duì)象和類(lèi)之間的通信復(fù)雜性。這種模式提供了一個(gè)中介類(lèi),該類(lèi)通常處理不同類(lèi)之間的通信,并支持松耦合,使代碼易于維護(hù)。中介者模式屬于行為型模式。
介紹
意圖:用一個(gè)中介對(duì)象來(lái)封裝一系列的對(duì)象交互,中介者使各對(duì)象不需要顯式地相互引用,從而使其耦合松散,而且可以獨(dú)立地改變它們之間的交互。
主要解決:對(duì)象與對(duì)象之間存在大量的關(guān)聯(lián)關(guān)系,這樣勢(shì)必會(huì)導(dǎo)致系統(tǒng)的結(jié)構(gòu)變得很復(fù)雜,同時(shí)若一個(gè)對(duì)象發(fā)生改變,我們也需要跟蹤與之相關(guān)聯(lián)的對(duì)象,同時(shí)做出相應(yīng)的處理。
何時(shí)使用:多個(gè)類(lèi)相互耦合,形成了網(wǎng)狀結(jié)構(gòu)。
如何解決:將上述網(wǎng)狀結(jié)構(gòu)分離為星型結(jié)構(gòu)。
關(guān)鍵代碼:對(duì)象 Colleague 之間的通信封裝到一個(gè)類(lèi)中單獨(dú)處理。
應(yīng)用實(shí)例:1、中國(guó)加入 WTO 之前是各個(gè)國(guó)家相互貿(mào)易,結(jié)構(gòu)復(fù)雜,現(xiàn)在是各個(gè)國(guó)家通過(guò) WTO 來(lái)互相貿(mào)易。 2、機(jī)場(chǎng)調(diào)度系統(tǒng)。 3、MVC 框架,其中C(控制器)就是 M(模型)和 V(視圖)的中介者。
優(yōu)點(diǎn):1、降低了類(lèi)的復(fù)雜度,將一對(duì)多轉(zhuǎn)化成了一對(duì)一。 2、各個(gè)類(lèi)之間的解耦。 3、符合迪米特原則。
缺點(diǎn):中介者會(huì)龐大,變得復(fù)雜難以維護(hù)。
使用場(chǎng)景:1、系統(tǒng)中對(duì)象之間存在比較復(fù)雜的引用關(guān)系,導(dǎo)致它們之間的依賴(lài)關(guān)系結(jié)構(gòu)混亂而且難以復(fù)用該對(duì)象。 2、想通過(guò)一個(gè)中間類(lèi)來(lái)封裝多個(gè)類(lèi)中的行為,而又不想生成太多的子類(lèi)。
注意事項(xiàng):不應(yīng)當(dāng)在職責(zé)混亂的時(shí)候使用。
實(shí)現(xiàn)
我們通過(guò)聊天室實(shí)例來(lái)演示中介者模式。實(shí)例中,多個(gè)用戶(hù)可以向聊天室發(fā)送消息,聊天室向所有的用戶(hù)顯示消息。我們將創(chuàng)建兩個(gè)類(lèi) ChatRoom 和 User。User 對(duì)象使用 ChatRoom 方法來(lái)分享他們的消息。
MediatorPatternDemo,我們的演示類(lèi)使用 User 對(duì)象來(lái)顯示他們之間的通信。

步驟 1
創(chuàng)建中介類(lèi)。
ChatRoom.java
import java.util.Date;
public class ChatRoom {
public static void showMessage(User user, String message){
System.out.println(new Date().toString()
+ " [" + user.getName() +"] : " + message);
}
}
步驟 2
創(chuàng)建 user 類(lèi)。
User.java
public class User {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public User(String name){
this.name = name;
}
public void sendMessage(String message){
ChatRoom.showMessage(this,message);
}
}
步驟 3
使用 User 對(duì)象來(lái)顯示他們之間的通信。
MediatorPatternDemo.java
public class MediatorPatternDemo {
public static void main(String[] args) {
User robert = new User("Robert");
User john = new User("John");
robert.sendMessage("Hi! John!");
john.sendMessage("Hello! Robert!");
}
}
步驟 4
執(zhí)行程序,輸出結(jié)果:
Thu Jan 31 16:05:46 IST 2013 [Robert] : Hi! John!
Thu Jan 31 16:05:46 IST 2013 [John] : Hello! Robert!
6.備忘錄模式
備忘錄模式(Memento Pattern)保存一個(gè)對(duì)象的某個(gè)狀態(tài),以便在適當(dāng)?shù)臅r(shí)候恢復(fù)對(duì)象。備忘錄模式屬于行為型模式。
介紹
意圖:在不破壞封裝性的前提下,捕獲一個(gè)對(duì)象的內(nèi)部狀態(tài),并在該對(duì)象之外保存這個(gè)狀態(tài)。
主要解決:所謂備忘錄模式就是在不破壞封裝的前提下,捕獲一個(gè)對(duì)象的內(nèi)部狀態(tài),并在該對(duì)象之外保存這個(gè)狀態(tài),這樣可以在以后將對(duì)象恢復(fù)到原先保存的狀態(tài)。
何時(shí)使用:很多時(shí)候我們總是需要記錄一個(gè)對(duì)象的內(nèi)部狀態(tài),這樣做的目的就是為了允許用戶(hù)取消不確定或者錯(cuò)誤的操作,能夠恢復(fù)到他原先的狀態(tài),使得他有"后悔藥"可吃。
如何解決:通過(guò)一個(gè)備忘錄類(lèi)專(zhuān)門(mén)存儲(chǔ)對(duì)象狀態(tài)。
關(guān)鍵代碼:客戶(hù)不與備忘錄類(lèi)耦合,與備忘錄管理類(lèi)耦合。
應(yīng)用實(shí)例:1、后悔藥。 2、打游戲時(shí)的存檔。 3、Windows 里的 ctri + z。 4、IE 中的后退。 4、數(shù)據(jù)庫(kù)的事務(wù)管理。
優(yōu)點(diǎn):1、給用戶(hù)提供了一種可以恢復(fù)狀態(tài)的機(jī)制,可以使用戶(hù)能夠比較方便地回到某個(gè)歷史的狀態(tài)。 2、實(shí)現(xiàn)了信息的封裝,使得用戶(hù)不需要關(guān)心狀態(tài)的保存細(xì)節(jié)。
缺點(diǎn):消耗資源。如果類(lèi)的成員變量過(guò)多,勢(shì)必會(huì)占用比較大的資源,而且每一次保存都會(huì)消耗一定的內(nèi)存。
使用場(chǎng)景:1、需要保存/恢復(fù)數(shù)據(jù)的相關(guān)狀態(tài)場(chǎng)景。 2、提供一個(gè)可回滾的操作。
注意事項(xiàng):1、為了符合迪米特原則,還要增加一個(gè)管理備忘錄的類(lèi)。 2、為了節(jié)約內(nèi)存,可使用原型模式+備忘錄模式。
實(shí)現(xiàn)
備忘錄模式使用三個(gè)類(lèi) Memento、Originator 和 CareTaker。Memento 包含了要被恢復(fù)的對(duì)象的狀態(tài)。Originator 創(chuàng)建并在 Memento 對(duì)象中存儲(chǔ)狀態(tài)。Caretaker 對(duì)象負(fù)責(zé)從 Memento 中恢復(fù)對(duì)象的狀態(tài)。
MementoPatternDemo,我們的演示類(lèi)使用 CareTaker 和 Originator 對(duì)象來(lái)顯示對(duì)象的狀態(tài)恢復(fù)。

步驟 1
創(chuàng)建 Memento 類(lèi)。
Memento.java
public class Memento {
private String state;
public Memento(String state){
this.state = state;
}
public String getState(){
return state;
}
}
步驟 2
創(chuàng)建 Originator 類(lèi)。
Originator.java
public class Originator {
private String state;
public void setState(String state){
this.state = state;
}
public String getState(){
return state;
}
public Memento saveStateToMemento(){
return new Memento(state);
}
public void getStateFromMemento(Memento Memento){
state = Memento.getState();
}
}
步驟 3
創(chuàng)建 CareTaker 類(lèi)。
CareTaker.java
import java.util.ArrayList;
import java.util.List;
public class CareTaker {
private List<Memento> mementoList = new ArrayList<Memento>();
public void add(Memento state){
mementoList.add(state);
}
public Memento get(int index){
return mementoList.get(index);
}
}
步驟 4
使用 CareTaker 和 Originator 對(duì)象。
MementoPatternDemo.java
public class MementoPatternDemo {
public static void main(String[] args) {
Originator originator = new Originator();
CareTaker careTaker = new CareTaker();
originator.setState("State #1");
originator.setState("State #2");
careTaker.add(originator.saveStateToMemento());
originator.setState("State #3");
careTaker.add(originator.saveStateToMemento());
originator.setState("State #4");
System.out.println("Current State: " + originator.getState());
originator.getStateFromMemento(careTaker.get(0));
System.out.println("First saved State: " + originator.getState());
originator.getStateFromMemento(careTaker.get(1));
System.out.println("Second saved State: " + originator.getState());
}
}
步驟 5
驗(yàn)證輸出。
Current State: State #4
First saved State: State #2
Second saved State: State #3
7.觀(guān)察者模式
當(dāng)對(duì)象間存在一對(duì)多關(guān)系時(shí),則使用觀(guān)察者模式(Observer Pattern)。比如,當(dāng)一個(gè)對(duì)象被修改時(shí),則會(huì)自動(dòng)通知它的依賴(lài)對(duì)象,被通知的對(duì)象會(huì)做出各自的反應(yīng)。觀(guān)察者模式屬于行為型模式。
介紹
意圖:定義對(duì)象間的一種一對(duì)多的依賴(lài)關(guān)系,當(dāng)一個(gè)對(duì)象的狀態(tài)發(fā)生改變時(shí),所有依賴(lài)于它的對(duì)象都得到通知并被自動(dòng)更新。
主要解決:一個(gè)對(duì)象狀態(tài)改變給其他對(duì)象通知的問(wèn)題,而且要考慮到易用和低耦合,保證高度的協(xié)作。
何時(shí)使用:一個(gè)對(duì)象(目標(biāo)對(duì)象)的狀態(tài)發(fā)生改變,進(jìn)行廣播通知,所有的依賴(lài)對(duì)象(觀(guān)察者對(duì)象)都將得到通知并做出各自的反應(yīng)。
如何解決:使用面向?qū)ο蠹夹g(shù),可以將這種依賴(lài)關(guān)系弱化。
關(guān)鍵代碼:在抽象類(lèi)里有一個(gè) ArrayList 存放觀(guān)察者們。
應(yīng)用實(shí)例: 1、拍賣(mài)的時(shí)候,拍賣(mài)師觀(guān)察最高標(biāo)價(jià),然后通知給其他競(jìng)價(jià)者競(jìng)價(jià)。 2、西游記里面悟空請(qǐng)求菩薩降服紅孩兒,菩薩灑了一地水招來(lái)一個(gè)老烏龜,這個(gè)烏龜就是觀(guān)察者,他觀(guān)察菩薩灑水這個(gè)動(dòng)作。
優(yōu)點(diǎn): 1、觀(guān)察者和被觀(guān)察者是抽象耦合的,被觀(guān)察者只知道觀(guān)察者接口,不知道具體的觀(guān)察者類(lèi),實(shí)現(xiàn)了被觀(guān)察者類(lèi)和具體觀(guān)察者類(lèi)的解耦。 2、建立一套觸發(fā)機(jī)制,實(shí)現(xiàn)了動(dòng)態(tài)聯(lián)動(dòng)。
3、支持廣播通信。
缺點(diǎn): 1、如果一個(gè)被觀(guān)察者對(duì)象有很多的直接和間接的觀(guān)察者的話(huà),將所有的觀(guān)察者都通知到會(huì)花費(fèi)很多時(shí)間。 2、如果在觀(guān)察者和觀(guān)察目標(biāo)之間有循環(huán)依賴(lài)的話(huà),觀(guān)察目標(biāo)會(huì)觸發(fā)它們之間進(jìn)行循環(huán)調(diào)用,可能導(dǎo)致系統(tǒng)崩潰。 3、觀(guān)察者模式?jīng)]有相應(yīng)的機(jī)制讓觀(guān)察者知道所觀(guān)察的目標(biāo)對(duì)象是怎么發(fā)生變化的,而僅僅只是知道觀(guān)察目標(biāo)發(fā)生了變化。
使用場(chǎng)景: 1、一個(gè)抽象模型有兩個(gè)方面,其中一個(gè)方面的操作(觀(guān)察者)依賴(lài)于另一個(gè)方面狀態(tài)的變化(被觀(guān)察者)。 2、如果在更改一個(gè)對(duì)象的時(shí)候,需要同時(shí)連帶改變其他對(duì)象,而且不知道究竟有多少對(duì)象需要被連帶改變。 3、當(dāng)一個(gè)對(duì)象必須通知其他對(duì)象,而又希望這個(gè)對(duì)象和其他被通知的對(duì)象是松散耦合的。
注意事項(xiàng): 1、JAVA 中已經(jīng)有了對(duì)觀(guān)察者模式的支持類(lèi)。 2、避免循環(huán)引用。 3、如果順序執(zhí)行,某一觀(guān)察者錯(cuò)誤會(huì)導(dǎo)致系統(tǒng)卡殼,一般采用異步方式。
實(shí)現(xiàn)
觀(guān)察者模式使用三個(gè)類(lèi) Subject、Observer 和 Client。Subject 對(duì)象帶有綁定觀(guān)察者到 Client 對(duì)象和從 Client 對(duì)象解綁觀(guān)察者的方法。我們創(chuàng)建 Subject 類(lèi)、Observer 抽象類(lèi)和擴(kuò)展了抽象類(lèi) Observer 的實(shí)體類(lèi)。
ObserverPatternDemo,我們的演示類(lèi)使用 Subject 和實(shí)體類(lèi)對(duì)象來(lái)演示觀(guān)察者模式。

觀(guān)察者模式實(shí)現(xiàn)的兩種方式
1) 推模型
目標(biāo)對(duì)象主動(dòng)向觀(guān)察者推送目標(biāo)的詳細(xì)信息,推送的信息通常是目標(biāo)或目標(biāo)對(duì)象的全部數(shù)據(jù)。一般這種模型的實(shí)現(xiàn)中,會(huì)把目標(biāo)對(duì)象想要推送的信息通過(guò)update方法傳遞給觀(guān)察者。
步驟 1
創(chuàng)建 Subject 類(lèi)(目標(biāo)類(lèi)、被觀(guān)察者類(lèi))
Subject.java
import java.util.ArrayList;
import java.util.List;
public class Subject {
private List<Observer> observers
= new ArrayList<Observer>();
private int state;
public int getState() {
return state;
}
public void setState(int state) {
this.state = state;
notifyAllObservers(state);
}
public void attach(Observer observer){
observers.add(observer);
}
public void detach(Observer observer){
observers.remove(observer);
}
public void notifyAllObservers(int state){
for (Observer observer : observers) {
observer.update(state);
}
}
}
步驟 2
創(chuàng)建 Observer 類(lèi)
Observer.java
public abstract class Observer {
public abstract void update(int state); //傳遞要推送的信息,觀(guān)察者只能接收到目標(biāo)推送的數(shù)據(jù)
}
步驟 3
創(chuàng)建實(shí)體觀(guān)察者類(lèi)
BinaryObserver.java
public class BinaryObserver extends Observer{
@Override
public void update(int state) {
System.out.println( "Binary String: "
+ Integer.toBinaryString(state));
}
}
OctalObserver.java
public class OctalObserver extends Observer{
@Override
public void update(int state) {
System.out.println( "Octal String: "
+ Integer.toOctalString(state));
}
}
HexaObserver.java
public class HexaObserver extends Observer{
@Override
public void update(int state) {
System.out.println( "Hex String: "
+ Integer.toHexString(state).toUpperCase());
}
}
步驟 4
使用 Subject 和實(shí)體觀(guān)察者對(duì)象
ObserverPatternDemo.java
public class ObserverPatternDemo {
public static void main(String[] args) {
Subject subject = new Subject();
Observer o1 = new HexaObserver();
Observer o2 = new OctalObserver();
Observer o3 = new BinaryObserver();
subject.attach(o1);
subject.attach(o2);
subject.attach(o3);
System.out.println("First state change: 15");
subject.setState(15);
System.out.println("Second state change: 10");
subject.setState(10);
}
}
步驟 5
驗(yàn)證輸出
First state change: 15
Hex String: F
Octal String: 17
Binary String: 1111
Second state change: 10
Hex String: A
Octal String: 12
Binary String: 1010
2) 拉模型
目標(biāo)對(duì)象在通知觀(guān)察者的時(shí)候,只傳遞少量信息。如果觀(guān)察者需要更具體的信息,由觀(guān)察者主動(dòng)到目標(biāo)對(duì)象中獲取,相當(dāng)于觀(guān)察者從目標(biāo)對(duì)象中拉數(shù)據(jù)。一般這種模型的實(shí)現(xiàn)中,會(huì)把目標(biāo)對(duì)象自身的引用通過(guò)update方法傳遞給觀(guān)察者。
步驟 1
創(chuàng)建 Subject 類(lèi)(目標(biāo)類(lèi)、被觀(guān)察者類(lèi))
Subject.java
import java.util.ArrayList;
import java.util.List;
public class Subject {
private List<Observer> observers
= new ArrayList<Observer>();
private int state;
public int getState() {
return state;
}
public void setState(int state) {
this.state = state;
notifyAllObservers();
}
public void attach(Observer observer){
observers.add(observer);
}
public void detach(Observer observer){
observers.remove(observer);
}
public void notifyAllObservers(){
for (Observer observer : observers) {
observer.update(this);
}
}
}
步驟 2
創(chuàng)建 Observer 類(lèi)
Observer.java
public abstract class Observer {
/*傳遞目標(biāo)對(duì)象自身的引用,觀(guān)察者可自己選擇
從目標(biāo)對(duì)象中拉哪些數(shù)據(jù)*/
public abstract void update(Subject message);
}
步驟 3
創(chuàng)建實(shí)體觀(guān)察者類(lèi)
BinaryObserver.java
public class BinaryObserver extends Observer{
@Override
public void update(Subject message) {
int state = message.getState();
System.out.println( "Binary String: "
+ Integer.toBinaryString(state));
}
}
OctalObserver.java
public class OctalObserver extends Observer{
@Override
public void update(Subject message) {
int state = message.getState();
System.out.println( "Octal String: "
+ Integer.toOctalString(state));
}
}
HexaObserver.java
public class HexaObserver extends Observer{
@Override
public void update(Subject message) {
int state = message.getState();
System.out.println( "Hex String: "
+ Integer.toHexString(state).toUpperCase());
}
}
步驟 4
使用 Subject 和實(shí)體觀(guān)察者對(duì)象
ObserverPatternDemo.java
public class ObserverPatternDemo {
public static void main(String[] args) {
Subject subject = new Subject();
Observer o1 = new HexaObserver();
Observer o2 = new OctalObserver();
Observer o3 = new BinaryObserver();
subject.attach(o1);
subject.attach(o2);
subject.attach(o3);
System.out.println("First state change: 15");
subject.setState(15);
System.out.println("Second state change: 10");
subject.setState(10);
}
}
步驟 5
驗(yàn)證輸出
First state change: 15
Hex String: F
Octal String: 17
Binary String: 1111
Second state change: 10
Hex String: A
Octal String: 12
Binary String: 1010
兩種模型的區(qū)別:
- 推模型由目標(biāo)對(duì)象決定推送的信息,觀(guān)察者不能獲取推送信息之外目標(biāo)對(duì)象的其他信息,較為被動(dòng)。拉模型雖然仍是目標(biāo)對(duì)象主動(dòng)推送信息,但推送的是整個(gè)目標(biāo)對(duì)象的引用,觀(guān)察者可以選擇性接收目標(biāo)對(duì)象的信息。
- 推模型一般用于目標(biāo)對(duì)象知道觀(guān)察者需要的數(shù)據(jù);而拉模型則用于目標(biāo)對(duì)象不知道觀(guān)察者需要的數(shù)據(jù),因此把自身傳遞給觀(guān)察者,由觀(guān)察者來(lái)取值。
- 推模型會(huì)使觀(guān)察者對(duì)象難以復(fù)用。拉模型下,update方法的參數(shù)是目標(biāo)對(duì)象本身,基本上可以適應(yīng)各種情況的需要。
利用Java提供的觀(guān)察者實(shí)現(xiàn)
Java提供了觀(guān)察者模式的實(shí)現(xiàn),有關(guān)類(lèi)和接口是java.util包的Observable類(lèi)和Observer接口。
和自己實(shí)現(xiàn)對(duì)比:
1.不需要自己定義觀(guān)察者和目標(biāo)接口了,JDK幫忙定義了
2.具體的目標(biāo)實(shí)現(xiàn)里面不需要再維護(hù)觀(guān)察者的注冊(cè)信息了,這個(gè)在Java中的Observable類(lèi)里面已經(jīng)幫忙實(shí)現(xiàn)好了。
3.觸發(fā)通知的方式有一點(diǎn)變化,要先調(diào)用setChanged方法,這個(gè)是Java為了幫助實(shí)現(xiàn)更精確的觸發(fā)控制而實(shí)現(xiàn)的功能。
4.具體觀(guān)察者的實(shí)現(xiàn)里面,update方法其實(shí)能同時(shí)支持推模型和拉模型,這個(gè)是Java在定義的時(shí)候,就已經(jīng)考慮進(jìn)去的了。
實(shí)現(xiàn)方法:
1、讓具體Subject實(shí)現(xiàn)類(lèi)繼承Observable目標(biāo)父類(lèi),Observable意為可被觀(guān)察的,所以讓具體目標(biāo)類(lèi)繼承它。
2、讓具體觀(guān)察者實(shí)現(xiàn)類(lèi)實(shí)現(xiàn)Observer接口,Observer意為觀(guān)察者,所以讓具體觀(guān)察者實(shí)現(xiàn)類(lèi)實(shí)現(xiàn)它。
步驟 1
創(chuàng)建具體目標(biāo)對(duì)象實(shí)現(xiàn)類(lèi)繼承Observable類(lèi)
Subject.java
import java.util.Observable;
public class Subject extends Observable {
private int state;
public int getState() {
return state;
}
public void setState(int state) {
this.state = state;
//通知觀(guān)察者之前必須調(diào)用setChanged()
this.setChanged();
/*通知所有觀(guān)察者,既傳遞目標(biāo)對(duì)象引用給觀(guān)察者,
* 也傳遞參數(shù)給觀(guān)察者update的第二個(gè)參數(shù)*/
this.notifyObservers(Integer.valueOf(state));
/* 重載方法的無(wú)參方法notifyObservers()
* 通知所有觀(guān)察者,但只是傳遞目標(biāo)對(duì)象引用給觀(guān)察者,
* 觀(guān)察者update的第二個(gè)參數(shù)為null
* */
}
}
步驟 2
創(chuàng)建觀(guān)察者的具體實(shí)現(xiàn)類(lèi)實(shí)現(xiàn)Observer接口
BinaryObserver.java
import java.util.Observable;
import java.util.Observer;
public class BinaryObserver implements Observer {
/**
* Observable o是目標(biāo)對(duì)象傳遞的引用,用于拉模型
* Object arg是目標(biāo)對(duì)象主動(dòng)推送的信息,用于推模型
* 如果目標(biāo)對(duì)象使用帶參的notifyObservers方法,
* 則即可推也可拉;如果使用無(wú)參的notifyObservers方法,
* 則只能拉
*/
@Override
public void update(Observable o, Object arg) {
//1.推的方式
System.out.println( "推模型:Binary String: "
+ Integer.toBinaryString(((Integer)arg).intValue()));
//2.拉的方式
System.out.println( "拉模型:Binary String: "
+ Integer.toBinaryString(((Subject)o).getState()));
}
}
HexaObserver.java
import java.util.Observable;
import java.util.Observer;
public class HexaObserver implements Observer {
@Override
public void update(Observable o, Object arg) {
System.out.println( "推模型:Hex String: "
+ Integer.toHexString(((Integer)arg).intValue()).toUpperCase());
System.out.println( "拉模型:Hex String: "
+ Integer.toHexString(((Subject)o).getState()).toUpperCase());
}
}
OctalObserver.java
import java.util.Observable;
import java.util.Observer;
public class OctalObserver implements Observer {
@Override
public void update(Observable o, Object arg) {
System.out.println( "推模型:Octal String: "
+ Integer.toOctalString(((Integer)arg).intValue()));
System.out.println( "拉模型:Octal String: "
+ Integer.toOctalString(((Subject)o).getState()));
}
}
步驟 3
創(chuàng)建測(cè)試類(lèi)
ObserverPatternDemo.java
import java.util.Observer;
public class ObserverPatternDemo {
public static void main(String[] args) {
Subject subject = new Subject();
Observer o1 = new HexaObserver();
Observer o2 = new BinaryObserver();
Observer o3 = new OctalObserver();
subject.addObserver(o1); //注冊(cè)觀(guān)察者
subject.addObserver(o2);
subject.addObserver(o3);
System.out.println("First state change: 15");
subject.setState(15);
System.out.println("Second state change: 10");
subject.setState(10);
}
}
步驟 4
驗(yàn)證輸出
First state change: 15
推模型:Octal String: 17
拉模型:Octal String: 17
推模型:Binary String: 1111
拉模型:Binary String: 1111
推模型:Hex String: F
拉模型:Hex String: F
Second state change: 10
推模型:Octal String: 12
拉模型:Octal String: 12
推模型:Binary String: 1010
拉模型:Binary String: 1010
推模型:Hex String: A
拉模型:Hex String: A
區(qū)別對(duì)待觀(guān)察者模式
之前的觀(guān)察者模式是目標(biāo)對(duì)象無(wú)條件通知所有觀(guān)察者對(duì)象,然而有時(shí)需要在特定條件下對(duì)特定的觀(guān)察者進(jìn)行通知。這是就需要觀(guān)察者模式的變形 —— 區(qū)別對(duì)待觀(guān)察者模式。
具體實(shí)現(xiàn)只要修改Subject類(lèi)的notifyAllObservers方法,對(duì)Observer的身份做特定判斷,然后有條件的推送信息即可。
8.狀態(tài)模式
在狀態(tài)模式(State Pattern)中,類(lèi)的行為是基于它的狀態(tài)改變的。這種類(lèi)型的設(shè)計(jì)模式屬于行為型模式。
在狀態(tài)模式中,我們創(chuàng)建表示各種狀態(tài)的對(duì)象和一個(gè)行為隨著狀態(tài)對(duì)象改變而改變的 context 對(duì)象。
介紹
意圖:允許對(duì)象在內(nèi)部狀態(tài)發(fā)生改變時(shí)改變它的行為,對(duì)象看起來(lái)好像修改了它的類(lèi)。
主要解決:對(duì)象的行為依賴(lài)于它的狀態(tài)(屬性),并且可以根據(jù)它的狀態(tài)改變而改變它的相關(guān)行為。
何時(shí)使用:代碼中包含大量與對(duì)象狀態(tài)有關(guān)的條件語(yǔ)句。
如何解決:將各種具體的狀態(tài)類(lèi)抽象出來(lái)。
關(guān)鍵代碼:通常命令模式的接口中只有一個(gè)方法。而狀態(tài)模式的接口中有一個(gè)或者多個(gè)方法。而且,狀態(tài)模式的實(shí)現(xiàn)類(lèi)的方法,一般返回值,或者是改變實(shí)例變量的值。也就是說(shuō),狀態(tài)模式一般和對(duì)象的狀態(tài)有關(guān)。實(shí)現(xiàn)類(lèi)的方法有不同的功能,覆蓋接口中的方法。狀態(tài)模式和命令模式一樣,也可以用于消除 if...else 等條件選擇語(yǔ)句。
應(yīng)用實(shí)例:1、打籃球的時(shí)候運(yùn)動(dòng)員可以有正常狀態(tài)、不正常狀態(tài)和超常狀態(tài)。 2、曾侯乙編鐘中,'鐘是抽象接口','鐘A'等是具體狀態(tài),'曾侯乙編鐘'是具體環(huán)境(Context)。
優(yōu)點(diǎn):1、封裝了轉(zhuǎn)換規(guī)則。 2、枚舉可能的狀態(tài),在枚舉狀態(tài)之前需要確定狀態(tài)種類(lèi)。 3、將所有與某個(gè)狀態(tài)有關(guān)的行為放到一個(gè)類(lèi)中,并且可以方便地增加新的狀態(tài),只需要改變對(duì)象狀態(tài)即可改變對(duì)象的行為。 4、允許狀態(tài)轉(zhuǎn)換邏輯與狀態(tài)對(duì)象合成一體,而不是某一個(gè)巨大的條件語(yǔ)句塊。 5、可以讓多個(gè)環(huán)境對(duì)象共享一個(gè)狀態(tài)對(duì)象,從而減少系統(tǒng)中對(duì)象的個(gè)數(shù)。
缺點(diǎn):1、狀態(tài)模式的使用必然會(huì)增加系統(tǒng)類(lèi)和對(duì)象的個(gè)數(shù)。 2、狀態(tài)模式的結(jié)構(gòu)與實(shí)現(xiàn)都較為復(fù)雜,如果使用不當(dāng)將導(dǎo)致程序結(jié)構(gòu)和代碼的混亂。 3、狀態(tài)模式對(duì)"開(kāi)閉原則"的支持并不太好,對(duì)于可以切換狀態(tài)的狀態(tài)模式,增加新的狀態(tài)類(lèi)需要修改那些負(fù)責(zé)狀態(tài)轉(zhuǎn)換的源代碼,否則無(wú)法切換到新增狀態(tài),而且修改某個(gè)狀態(tài)類(lèi)的行為也需修改對(duì)應(yīng)類(lèi)的源代碼。
使用場(chǎng)景:1、行為隨狀態(tài)改變而改變的場(chǎng)景。 2、條件、分支語(yǔ)句的代替者。
注意事項(xiàng):在行為受狀態(tài)約束的時(shí)候使用狀態(tài)模式,而且狀態(tài)不超過(guò) 5 個(gè)。
實(shí)現(xiàn)
我們將創(chuàng)建一個(gè) State 接口和實(shí)現(xiàn)了 State 接口的實(shí)體狀態(tài)類(lèi)。Context 是一個(gè)帶有某個(gè)狀態(tài)的類(lèi)。
StatePatternDemo,我們的演示類(lèi)使用 Context 和狀態(tài)對(duì)象來(lái)演示 Context 在狀態(tài)改變時(shí)的行為變化。

步驟 1
創(chuàng)建一個(gè)接口。
State.java
public interface State {
public void doAction(Context context);
}
步驟 2
創(chuàng)建實(shí)現(xiàn)接口的實(shí)體類(lèi)。
StartState.java
public class StartState implements State {
public void doAction(Context context) {
System.out.println("Player is in start state");
context.setState(this);
}
public String toString(){
return "Start State";
}
}
StopState.java
public class StopState implements State {
public void doAction(Context context) {
System.out.println("Player is in stop state");
context.setState(this);
}
public String toString(){
return "Stop State";
}
}
步驟 3
創(chuàng)建 Context 類(lèi)。
Context.java
public class Context {
private State state;
public Context(){
state = null;
}
public void setState(State state){
this.state = state;
}
public State getState(){
return state;
}
}
步驟 4
使用 Context 來(lái)查看當(dāng)狀態(tài) State 改變時(shí)的行為變化。
StatePatternDemo.java
public class StatePatternDemo {
public static void main(String[] args) {
Context context = new Context();
StartState startState = new StartState();
startState.doAction(context);
System.out.println(context.getState().toString());
StopState stopState = new StopState();
stopState.doAction(context);
System.out.println(context.getState().toString());
}
}
步驟 5
執(zhí)行程序,輸出結(jié)果:
Player is in start state
Start State
Player is in stop state
Stop State
9.空對(duì)象模式
在空對(duì)象模式(Null Object Pattern)中,一個(gè)空對(duì)象取代 NULL 對(duì)象實(shí)例的檢查。Null 對(duì)象不是檢查空值,而是反應(yīng)一個(gè)不做任何動(dòng)作的關(guān)系。這樣的 Null 對(duì)象也可以在數(shù)據(jù)不可用的時(shí)候提供默認(rèn)的行為。
在空對(duì)象模式中,我們創(chuàng)建一個(gè)指定各種要執(zhí)行的操作的抽象類(lèi)和擴(kuò)展該類(lèi)的實(shí)體類(lèi),還創(chuàng)建一個(gè)未對(duì)該類(lèi)做任何實(shí)現(xiàn)的空對(duì)象類(lèi),該空對(duì)象類(lèi)將無(wú)縫地使用在需要檢查空值的地方。
實(shí)現(xiàn)
我們將創(chuàng)建一個(gè)定義操作(在這里,是客戶(hù)的名稱(chēng))的 AbstractCustomer 抽象類(lèi),和擴(kuò)展了 AbstractCustomer 類(lèi)的實(shí)體類(lèi)。工廠(chǎng)類(lèi) CustomerFactory 基于客戶(hù)傳遞的名字來(lái)返回 RealCustomer 或 NullCustomer 對(duì)象。
NullPatternDemo,我們的演示類(lèi)使用 CustomerFactory 來(lái)演示空對(duì)象模式的用法。

步驟 1
創(chuàng)建一個(gè)抽象類(lèi)。
AbstractCustomer.java
public abstract class AbstractCustomer {
protected String name;
public abstract boolean isNil();
public abstract String getName();
}
步驟 2
創(chuàng)建擴(kuò)展了上述類(lèi)的實(shí)體類(lèi)。
RealCustomer.java
public class RealCustomer extends AbstractCustomer {
public RealCustomer(String name) {
this.name = name;
}
@Override
public String getName() {
return name;
}
@Override
public boolean isNil() {
return false;
}
}
NullCustomer.java
public class NullCustomer extends AbstractCustomer {
@Override
public String getName() {
return "Not Available in Customer Database";
}
@Override
public boolean isNil() {
return true;
}
}
步驟 3
創(chuàng)建 CustomerFactory 類(lèi)。
CustomerFactory.java
public class CustomerFactory {
public static final String[] names = {"Rob", "Joe", "Julie"};
public static AbstractCustomer getCustomer(String name){
for (int i = 0; i < names.length; i++) {
if (names[i].equalsIgnoreCase(name)){
return new RealCustomer(name);
}
}
return new NullCustomer();
}
}
步驟 4
使用 CustomerFactory,基于客戶(hù)傳遞的名字,來(lái)獲取 RealCustomer 或 NullCustomer 對(duì)象。
NullPatternDemo.java
public class NullPatternDemo {
public static void main(String[] args) {
AbstractCustomer customer1 = CustomerFactory.getCustomer("Rob");
AbstractCustomer customer2 = CustomerFactory.getCustomer("Bob");
AbstractCustomer customer3 = CustomerFactory.getCustomer("Julie");
AbstractCustomer customer4 = CustomerFactory.getCustomer("Laura");
System.out.println("Customers");
System.out.println(customer1.getName());
System.out.println(customer2.getName());
System.out.println(customer3.getName());
System.out.println(customer4.getName());
}
}
步驟 5
執(zhí)行程序,輸出結(jié)果:
Customers
Rob
Not Available in Customer Database
Julie
Not Available in Customer Database
10.策略模式
在策略模式(Strategy Pattern)中,一個(gè)類(lèi)的行為或其算法可以在運(yùn)行時(shí)更改。這種類(lèi)型的設(shè)計(jì)模式屬于行為型模式。
在策略模式中,我們創(chuàng)建表示各種策略的對(duì)象和一個(gè)行為隨著策略對(duì)象改變而改變的 context 對(duì)象。策略對(duì)象改變 context 對(duì)象的執(zhí)行算法。
介紹
意圖:定義一系列的算法,把它們一個(gè)個(gè)封裝起來(lái), 并且使它們可相互替換。
主要解決:在有多種算法相似的情況下,使用 if...else 所帶來(lái)的復(fù)雜和難以維護(hù)。
何時(shí)使用:一個(gè)系統(tǒng)有許多許多類(lèi),而區(qū)分它們的只是他們直接的行為。
如何解決:將這些算法封裝成一個(gè)一個(gè)的類(lèi),任意地替換。
關(guān)鍵代碼:抽象出行為的共性作為一個(gè)策略接口,各種策略類(lèi)實(shí)現(xiàn)這個(gè)接口。在調(diào)用這個(gè)行為的類(lèi)中通過(guò)組合持有這個(gè)接口的對(duì)象,通過(guò)這個(gè)策略接口對(duì)象代理具體的行為。
應(yīng)用實(shí)例: 1、諸葛亮的錦囊妙計(jì),每一個(gè)錦囊就是一個(gè)策略。 2、旅行的出游方式,選擇騎自行車(chē)、坐汽車(chē),每一種旅行方式都是一個(gè)策略。 3、JAVA AWT 中的 LayoutManager。
優(yōu)點(diǎn): 1、算法可以自由切換。 2、避免使用多重條件判斷。 3、擴(kuò)展性良好。
缺點(diǎn): 1、策略類(lèi)會(huì)增多。 2、所有策略類(lèi)都需要對(duì)外暴露。
使用場(chǎng)景: 1、如果在一個(gè)系統(tǒng)里面有許多類(lèi),它們之間的區(qū)別僅在于它們的行為,那么使用策略模式可以動(dòng)態(tài)地讓一個(gè)對(duì)象在許多行為中選擇一種行為。 2、一個(gè)系統(tǒng)需要?jiǎng)討B(tài)地在幾種算法中選擇一種。 3、如果一個(gè)對(duì)象有很多的行為,如果不用恰當(dāng)?shù)哪J剑@些行為就只好使用多重的條件選擇語(yǔ)句來(lái)實(shí)現(xiàn)。
注意事項(xiàng):如果一個(gè)系統(tǒng)的策略多于四個(gè),就需要考慮使用混合模式,解決策略類(lèi)膨脹的問(wèn)題。
實(shí)現(xiàn)
我們將創(chuàng)建一個(gè)定義活動(dòng)的 Strategy 接口和實(shí)現(xiàn)了 Strategy 接口的實(shí)體策略類(lèi)。Context 是一個(gè)使用了某種策略的類(lèi)。
StrategyPatternDemo,我們的演示類(lèi)使用 Context 和策略對(duì)象來(lái)演示 Context 在它所配置或使用的策略改變時(shí)的行為變化。

步驟 1
創(chuàng)建一個(gè)接口。
Strategy.java
public interface Strategy {
public int doOperation(int num1, int num2);
}
步驟 2
創(chuàng)建實(shí)現(xiàn)接口的實(shí)體類(lèi)。
OperationAdd.java
public class OperationAdd implements Strategy{
@Override
public int doOperation(int num1, int num2) {
return num1 + num2;
}
}
OperationSubstract.java
public class OperationSubstract implements Strategy{
@Override
public int doOperation(int num1, int num2) {
return num1 - num2;
}
}
OperationMultiply.java
public class OperationMultiply implements Strategy{
@Override
public int doOperation(int num1, int num2) {
return num1 * num2;
}
}
步驟 3
創(chuàng)建 Context 類(lèi)。
Context.java
public class Context {
private Strategy strategy;//組合一個(gè)策略接口對(duì)象
public Context(Strategy strategy){
this.strategy = strategy;
}
public int executeStrategy(int num1, int num2){
return strategy.doOperation(num1, num2);
//用策略接口對(duì)象代理具體實(shí)現(xiàn)
}
}
步驟 4
使用 Context 來(lái)查看當(dāng)它改變策略 Strategy 時(shí)的行為變化。
StrategyPatternDemo.java
public class StrategyPatternDemo {
public static void main(String[] args) {
Context context = new Context(new OperationAdd());
System.out.println("10 + 5 = " + context.executeStrategy(10, 5));
context = new Context(new OperationSubstract());
System.out.println("10 - 5 = " + context.executeStrategy(10, 5));
context = new Context(new OperationMultiply());
System.out.println("10 * 5 = " + context.executeStrategy(10, 5));
}
}
步驟 5
驗(yàn)證輸出。
10 + 5 = 15
10 - 5 = 5
10 * 5 = 50
11.模板模式
在模板模式(Template Pattern)中,一個(gè)抽象類(lèi)公開(kāi)定義了執(zhí)行它的方法的方式/模板。它的子類(lèi)可以按需要重寫(xiě)方法實(shí)現(xiàn),但調(diào)用將以抽象類(lèi)中定義的方式進(jìn)行。這種類(lèi)型的設(shè)計(jì)模式屬于行為型模式。
介紹
意圖:定義一個(gè)操作中的算法的骨架,而將一些步驟延遲到子類(lèi)中。模板方法使得子類(lèi)可以不改變一個(gè)算法的結(jié)構(gòu)即可重定義該算法的某些特定步驟。
主要解決:一些方法通用,卻在每一個(gè)子類(lèi)都重新寫(xiě)了這一方法。
何時(shí)使用:有一些通用的方法。
如何解決:將這些通用算法抽象出來(lái)。
關(guān)鍵代碼:準(zhǔn)備一個(gè)抽象類(lèi),將部分邏輯以具體方法的形式實(shí)現(xiàn),然后聲明一些抽象方法交由子類(lèi)實(shí)現(xiàn)剩余邏輯,用鉤子方法給予子類(lèi)更大的靈活性。最后將方法匯總為一個(gè)final的模板方法。
應(yīng)用實(shí)例: 1、在造房子的時(shí)候,地基、走線(xiàn)、水管都一樣,只有在建筑的后期才有加壁櫥加?xùn)艡诘炔町悺?2、西游記里面菩薩定好的 81 難,這就是一個(gè)頂層的邏輯骨架。 3、spring 中對(duì) Hibernate 的支持,將一些已經(jīng)定好的方法封裝起來(lái),比如開(kāi)啟事務(wù)、獲取 Session、關(guān)閉 Session 等,程序員不重復(fù)寫(xiě)那些已經(jīng)規(guī)范好的代碼,直接丟一個(gè)實(shí)體就可以保存。
優(yōu)點(diǎn): 1、封裝性好,封裝不變部分,擴(kuò)展可變部分。 2、復(fù)用性好,提取公共代碼,便于維護(hù)。 3、屏蔽細(xì)節(jié),行為由父類(lèi)控制,子類(lèi)實(shí)現(xiàn)。
缺點(diǎn):1、每一個(gè)不同的實(shí)現(xiàn)都需要一個(gè)子類(lèi)來(lái)實(shí)現(xiàn),導(dǎo)致類(lèi)的個(gè)數(shù)增加,使得系統(tǒng)更加龐大。2、Java的單繼承使得繼承了其他父類(lèi)子類(lèi)難以實(shí)現(xiàn)對(duì)模板基類(lèi)的繼承。
使用場(chǎng)景: 1、有多個(gè)子類(lèi)共有的方法,且邏輯相同。 2、重要的、復(fù)雜的方法,可以考慮作為模板方法。
注意事項(xiàng):為防止惡意操作,一般模板方法都加上 final 關(guān)鍵詞。在模板方法內(nèi)的步驟中,通用的方法在抽象基類(lèi)里提供實(shí)現(xiàn),特定的方法定義為抽象方法,延遲到子類(lèi)中實(shí)現(xiàn)。
實(shí)現(xiàn)
我們將創(chuàng)建一個(gè)定義操作的 Game 抽象類(lèi),其中,模板方法設(shè)置為 final,這樣它就不會(huì)被重寫(xiě)。Cricket 和 Football 是擴(kuò)展了 Game 的實(shí)體類(lèi),它們重寫(xiě)了抽象類(lèi)的方法。
TemplatePatternDemo,我們的演示類(lèi)使用 Game 來(lái)演示模板模式的用法。

步驟 1
創(chuàng)建一個(gè)抽象類(lèi),它的模板方法被設(shè)置為 final。
Game.java
public abstract class Game {
abstract void initialize();
//如果子類(lèi)通用,可以在抽象基類(lèi)實(shí)現(xiàn),不必定義為抽象方法
abstract void startPlay();
abstract void endPlay();
//模板,定義為final,防止被子類(lèi)重寫(xiě)
public final void play(){
//初始化游戲
initialize();
//開(kāi)始游戲
startPlay();
//結(jié)束游戲
endPlay();
}
}
步驟 2
創(chuàng)建擴(kuò)展了上述類(lèi)的實(shí)體類(lèi)。
Cricket.java
public class Cricket extends Game {
@Override
void endPlay() {
System.out.println("Cricket Game Finished!");
}
@Override
void initialize() {
System.out.println("Cricket Game Initialized! Start playing.");
}
@Override
void startPlay() {
System.out.println("Cricket Game Started. Enjoy the game!");
}
}
Football.java
public class Football extends Game {
@Override
void endPlay() {
System.out.println("Football Game Finished!");
}
@Override
void initialize() {
System.out.println("Football Game Initialized! Start playing.");
}
@Override
void startPlay() {
System.out.println("Football Game Started. Enjoy the game!");
}
}
步驟 3
使用 Game 的模板方法 play() 來(lái)演示游戲的定義方式。
TemplatePatternDemo.java
public class TemplatePatternDemo {
public static void main(String[] args) {
Game game = new Cricket();
game.play();
System.out.println();
game = new Football();
game.play();
}
}
步驟 4
驗(yàn)證輸出。
Cricket Game Initialized! Start playing.
Cricket Game Started. Enjoy the game!
Cricket Game Finished!
Football Game Initialized! Start playing.
Football Game Started. Enjoy the game!
Football Game Finished!
如果想要靈活選擇模板中的某一步驟是否出現(xiàn),可以添加一個(gè)鉤子方法:比如在泡飲品的模板方法中,燒水——倒入飲品沖劑——加水——加調(diào)料。如果有的飲品不想要調(diào)料,可以在模板方法中把加調(diào)料放在if語(yǔ)句中,if的條件是一個(gè)返回值為boolean類(lèi)型的方法,比如isCustomerWantsCondiments(),提供一個(gè)空的或者默認(rèn)返回true的實(shí)現(xiàn),稱(chēng)為鉤子方法。子類(lèi)可以根據(jù)需要重寫(xiě)該鉤子方法選擇要不要加調(diào)料。
12.訪(fǎng)問(wèn)者模式
在訪(fǎng)問(wèn)者模式(Visitor Pattern)中,我們使用了一個(gè)訪(fǎng)問(wèn)者類(lèi),它改變了元素類(lèi)的執(zhí)行算法。通過(guò)這種方式,元素的執(zhí)行算法可以隨著訪(fǎng)問(wèn)者改變而改變。這種類(lèi)型的設(shè)計(jì)模式屬于行為型模式。根據(jù)模式,元素對(duì)象已接受訪(fǎng)問(wèn)者對(duì)象,這樣訪(fǎng)問(wèn)者對(duì)象就可以處理元素對(duì)象上的操作。
介紹
意圖:主要將數(shù)據(jù)結(jié)構(gòu)與數(shù)據(jù)操作分離。
主要解決:穩(wěn)定的數(shù)據(jù)結(jié)構(gòu)和易變的操作耦合問(wèn)題。
何時(shí)使用:需要對(duì)一個(gè)對(duì)象結(jié)構(gòu)中的對(duì)象進(jìn)行很多不同的并且不相關(guān)的操作,而需要避免讓這些操作"污染"這些對(duì)象的類(lèi),使用訪(fǎng)問(wèn)者模式將這些封裝到類(lèi)中。
如何解決:在被訪(fǎng)問(wèn)的類(lèi)里面加一個(gè)對(duì)外提供接待訪(fǎng)問(wèn)者的接口。
關(guān)鍵代碼:在數(shù)據(jù)基礎(chǔ)類(lèi)里面有一個(gè)方法接受訪(fǎng)問(wèn)者,將自身引用傳入訪(fǎng)問(wèn)者。
應(yīng)用實(shí)例:您在朋友家做客,您是訪(fǎng)問(wèn)者,朋友接受您的訪(fǎng)問(wèn),您通過(guò)朋友的描述,然后對(duì)朋友的描述做出一個(gè)判斷,這就是訪(fǎng)問(wèn)者模式。
優(yōu)點(diǎn):1、符合單一職責(zé)原則。 2、優(yōu)秀的擴(kuò)展性。 3、靈活性。
缺點(diǎn):1、具體元素對(duì)訪(fǎng)問(wèn)者公布細(xì)節(jié),違反了迪米特原則。 2、具體元素變更比較困難。 3、違反了依賴(lài)倒置原則,依賴(lài)了具體類(lèi),沒(méi)有依賴(lài)抽象。
使用場(chǎng)景:1、對(duì)象結(jié)構(gòu)中對(duì)象對(duì)應(yīng)的類(lèi)很少改變,但經(jīng)常需要在此對(duì)象結(jié)構(gòu)上定義新的操作。 2、需要對(duì)一個(gè)對(duì)象結(jié)構(gòu)中的對(duì)象進(jìn)行很多不同的并且不相關(guān)的操作,而需要避免讓這些操作"污染"這些對(duì)象的類(lèi),也不希望在增加新操作時(shí)修改這些類(lèi)。
注意事項(xiàng):訪(fǎng)問(wèn)者可以對(duì)功能進(jìn)行統(tǒng)一,可以做報(bào)表、UI、攔截器與過(guò)濾器。
實(shí)現(xiàn)
我們將創(chuàng)建一個(gè)定義接受操作的 ComputerPart 接口。Keyboard、Mouse、Monitor 和 Computer 是實(shí)現(xiàn)了 ComputerPart 接口的實(shí)體類(lèi)。我們將定義另一個(gè)接口 ComputerPartVisitor,它定義了訪(fǎng)問(wèn)者類(lèi)的操作。Computer 使用實(shí)體訪(fǎng)問(wèn)者來(lái)執(zhí)行相應(yīng)的動(dòng)作。
VisitorPatternDemo,我們的演示類(lèi)使用 Computer、ComputerPartVisitor 類(lèi)來(lái)演示訪(fǎng)問(wèn)者模式的用法。

步驟 1
定義一個(gè)表示元素的接口。
ComputerPart.java
public interface ComputerPart {
public void accept(ComputerPartVisitor computerPartVisitor);
}
步驟 2
創(chuàng)建擴(kuò)展了上述類(lèi)的實(shí)體類(lèi)。
Keyboard.java
public class Keyboard implements ComputerPart {
@Override
public void accept(ComputerPartVisitor computerPartVisitor) {
computerPartVisitor.visit(this);
}
}
Monitor.java
public class Monitor implements ComputerPart {
@Override
public void accept(ComputerPartVisitor computerPartVisitor) {
computerPartVisitor.visit(this);
}
}
Mouse.java
public class Mouse implements ComputerPart {
@Override
public void accept(ComputerPartVisitor computerPartVisitor) {
computerPartVisitor.visit(this);
}
}
Computer.java
public class Computer implements ComputerPart {
ComputerPart[] parts;
public Computer(){
parts = new ComputerPart[] {new Mouse(), new Keyboard(), new Monitor()};
}
@Override
public void accept(ComputerPartVisitor computerPartVisitor) {
for (int i = 0; i < parts.length; i++) {
parts[i].accept(computerPartVisitor);
}
computerPartVisitor.visit(this);
}
}
步驟 3
定義一個(gè)表示訪(fǎng)問(wèn)者的接口。
ComputerPartVisitor.java
public interface ComputerPartVisitor {
public void visit(Computer computer);
public void visit(Mouse mouse);
public void visit(Keyboard keyboard);
public void visit(Monitor monitor);
}
步驟 4
創(chuàng)建實(shí)現(xiàn)了上述類(lèi)的實(shí)體訪(fǎng)問(wèn)者。
ComputerPartDisplayVisitor.java
public class ComputerPartDisplayVisitor implements ComputerPartVisitor {
@Override
public void visit(Computer computer) {
System.out.println("Displaying Computer.");
}
@Override
public void visit(Mouse mouse) {
System.out.println("Displaying Mouse.");
}
@Override
public void visit(Keyboard keyboard) {
System.out.println("Displaying Keyboard.");
}
@Override
public void visit(Monitor monitor) {
System.out.println("Displaying Monitor.");
}
}
步驟 5
使用 ComputerPartDisplayVisitor 來(lái)顯示 Computer 的組成部分。
VisitorPatternDemo.java
public class VisitorPatternDemo {
public static void main(String[] args) {
ComputerPart computer = new Computer();
computer.accept(new ComputerPartDisplayVisitor());
}
}
步驟 6
執(zhí)行程序,輸出結(jié)果:
Displaying Mouse.
Displaying Keyboard.
Displaying Monitor.
Displaying Computer.
參考資料:
菜鳥(niǎo)教程之設(shè)計(jì)模式
CyC2018/CS-Notes/設(shè)計(jì)模式