行為型模式用于描述程序在運(yùn)行時(shí)復(fù)雜的流程控制,即描述多個(gè)類或?qū)ο笾g怎樣相互協(xié)作共同完成單個(gè)對(duì)象都無法單獨(dú)完成的任務(wù),它涉及算法與對(duì)象間職責(zé)的分配。
是設(shè)計(jì)模式分類當(dāng)中種類最多的。
1、責(zé)任鏈模式
顧名思義,責(zé)任鏈模式(Chain of Responsibility Pattern)為請(qǐng)求創(chuàng)建了一個(gè)接收者對(duì)象的鏈。這種模式給予請(qǐng)求的類型,對(duì)請(qǐng)求的發(fā)送者和接收者進(jìn)行解耦。這種類型的設(shè)計(jì)模式屬于行為型模式。
在這種模式中,通常每個(gè)接收者都包含對(duì)另一個(gè)接收者的引用。如果一個(gè)對(duì)象不能處理該請(qǐng)求,那么它會(huì)把相同的請(qǐng)求傳給下一個(gè)接收者,依此類推。
介紹
意圖:避免請(qǐng)求發(fā)送者與接收者耦合在一起,讓多個(gè)對(duì)象都有可能接收請(qǐng)求,將這些對(duì)象連接成一條鏈,并且沿著這條鏈傳遞請(qǐng)求,直到有對(duì)象處理它為止。
主要解決:職責(zé)鏈上的處理者負(fù)責(zé)處理請(qǐng)求,客戶只需要將請(qǐng)求發(fā)送到職責(zé)鏈上即可,無須關(guān)心請(qǐng)求的處理細(xì)節(jié)和請(qǐng)求的傳遞,所以職責(zé)鏈將請(qǐng)求的發(fā)送者和請(qǐng)求的處理者解耦了。
何時(shí)使用:在處理消息的時(shí)候以過濾很多道。
如何解決:攔截的類都實(shí)現(xiàn)統(tǒng)一接口。
關(guān)鍵代碼:Handler 里面聚合它自己,在 HandlerRequest 里判斷是否合適,如果沒達(dá)到條件則向下傳遞,向誰傳遞之前 set 進(jìn)去。
應(yīng)用實(shí)例: 1、紅樓夢中的"擊鼓傳花"。 2、JS 中的事件冒泡。 3、JAVA WEB 中 Apache Tomcat 對(duì) Encoding 的處理,Struts2 的攔截器,jsp servlet 的 Filter。
優(yōu)點(diǎn): 1、降低耦合度。它將請(qǐng)求的發(fā)送者和接收者解耦。 2、簡化了對(duì)象。使得對(duì)象不需要知道鏈的結(jié)構(gòu)。 3、增強(qiáng)給對(duì)象指派職責(zé)的靈活性。通過改變鏈內(nèi)的成員或者調(diào)動(dòng)它們的次序,允許動(dòng)態(tài)地新增或者刪除責(zé)任。 4、增加新的請(qǐng)求處理類很方便。
缺點(diǎn): 1、不能保證請(qǐng)求一定被接收。 2、系統(tǒng)性能將受到一定影響,而且在進(jìn)行代碼調(diào)試時(shí)不太方便,可能會(huì)造成循環(huán)調(diào)用。 3、可能不容易觀察運(yùn)行時(shí)的特征,有礙于除錯(cuò)。
使用場景: 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)
我們創(chuàng)建抽象類 AbstractLogger,帶有詳細(xì)的日志記錄級(jí)別。然后我們創(chuàng)建三種類型的記錄器,都擴(kuò)展了 AbstractLogger。每個(gè)記錄器消息的級(jí)別是否屬于自己的級(jí)別,如果是則相應(yīng)地打印出來,否則將不打印并把消息傳給下一個(gè)記錄器。

步驟 1
創(chuàng)建抽象的記錄器類。
AbstractLogger.java
public abstract class AbstractLogger {
public static int INFO = 1;
public static int DEBUG = 2;
public static int ERROR = 3;
protected int level;
//責(zé)任鏈中的下一個(gè)元素
protected AbstractLogger nextLogger;
public void setNextLogger(AbstractLogger nextLogger){
this.nextLogger = nextLogger;
}
public void logMessage(int level, String message){
if(this.level <= level){
write(message);
}
if(nextLogger !=null){
nextLogger.logMessage(level, message);
}
}
abstract protected void write(String message);
}
步驟 2
創(chuàng)建擴(kuò)展了該記錄器類的實(shí)體類。
ConsoleLogger.java
public class ConsoleLogger extends AbstractLogger {
public ConsoleLogger(int level){
this.level = level;
}
@Override
protected void write(String message) {
System.out.println("Standard Console::Logger: " + message);
}
}
ErrorLogger.java
public class ErrorLogger extends AbstractLogger {
public ErrorLogger(int level){
this.level = level;
}
@Override
protected void write(String message) {
System.out.println("Error Console::Logger: " + message);
}
}
FileLogger.java
public class FileLogger extends AbstractLogger {
public FileLogger(int level){
this.level = level;
}
@Override
protected void write(String message) {
System.out.println("File::Logger: " + message);
}
}
步驟 3
創(chuàng)建不同類型的記錄器。賦予它們不同的錯(cuò)誤級(jí)別,并在每個(gè)記錄器中設(shè)置下一個(gè)記錄器。每個(gè)記錄器中的下一個(gè)記錄器代表的是鏈的一部分。
ChainPatternDemo.java
public class ChainPatternDemo {
private static AbstractLogger getChainOfLoggers(){
AbstractLogger errorLogger = new ErrorLogger(AbstractLogger.ERROR);
AbstractLogger fileLogger = new FileLogger(AbstractLogger.DEBUG);
AbstractLogger consoleLogger = new ConsoleLogger(AbstractLogger.INFO);
errorLogger.setNextLogger(fileLogger);
fileLogger.setNextLogger(consoleLogger);
return errorLogger;
}
public static void main(String[] args) {
AbstractLogger loggerChain = getChainOfLoggers();
loggerChain.logMessage(AbstractLogger.INFO, "This is an information.");
loggerChain.logMessage(AbstractLogger.DEBUG,
"This is a debug level information.");
loggerChain.logMessage(AbstractLogger.ERROR,
"This is an error information.");
}
}
責(zé)任鏈模式其實(shí)非常多的應(yīng)用在流當(dāng)中,在范例中的代碼里運(yùn)用了鏈表的數(shù)據(jù)結(jié)構(gòu),一次次的遍歷到結(jié)尾,每次都會(huì)查看是否能夠處理本次遍歷的結(jié)果。如果不能處理就往后繼續(xù)看是否能處理,這當(dāng)然也可以用流來書寫,如果用流的話,那最典型的應(yīng)用就是Filter了。這種處理方式能夠大大減少你的代碼量,如果你要自己書寫的話你可能會(huì)需要很多次的判斷,而這里只需要采用這種設(shè)計(jì)模式就可以解決。
注意的是,采用這種設(shè)計(jì)模式,通常都是能夠無需在意給你的數(shù)據(jù)類型的順序結(jié)構(gòu),只需要遍歷完畢就可以了。
2、模板模式
在模板模式(Template Pattern)中,一個(gè)抽象類公開定義了執(zhí)行它的方法的方式/模板。它的子類可以按需要重寫方法實(shí)現(xiàn),但調(diào)用將以抽象類中定義的方式進(jìn)行。這種類型的設(shè)計(jì)模式屬于行為型模式。
介紹
意圖:定義一個(gè)操作中的算法的骨架,而將一些步驟延遲到子類中。模板方法使得子類可以不改變一個(gè)算法的結(jié)構(gòu)即可重定義該算法的某些特定步驟。
主要解決:一些方法通用,卻在每一個(gè)子類都重新寫了這一方法。
何時(shí)使用:有一些通用的方法。
如何解決:將這些通用算法抽象出來。
關(guān)鍵代碼:在抽象類實(shí)現(xiàn),其他步驟在子類實(shí)現(xiàn)。
應(yīng)用實(shí)例: 1、在造房子的時(shí)候,地基、走線、水管都一樣,只有在建筑的后期才有加壁櫥加?xùn)艡诘炔町悺?2、西游記里面菩薩定好的 81 難,這就是一個(gè)頂層的邏輯骨架。 3、spring 中對(duì) Hibernate 的支持,將一些已經(jīng)定好的方法封裝起來,比如開啟事務(wù)、獲取 Session、關(guān)閉 Session 等,程序員不重復(fù)寫那些已經(jīng)規(guī)范好的代碼,直接丟一個(gè)實(shí)體就可以保存。
優(yōu)點(diǎn): 1、封裝不變部分,擴(kuò)展可變部分。 2、提取公共代碼,便于維護(hù)。 3、行為由父類控制,子類實(shí)現(xiàn)。
缺點(diǎn):每一個(gè)不同的實(shí)現(xiàn)都需要一個(gè)子類來實(shí)現(xiàn),導(dǎo)致類的個(gè)數(shù)增加,使得系統(tǒng)更加龐大。
使用場景: 1、有多個(gè)子類共有的方法,且邏輯相同。 2、重要的、復(fù)雜的方法,可以考慮作為模板方法。
注意事項(xiàng):為防止惡意操作,一般模板方法都加上 final 關(guān)鍵詞。
實(shí)現(xiàn)
我們將創(chuàng)建一個(gè)定義操作的 Game 抽象類,其中,模板方法設(shè)置為 final,這樣它就不會(huì)被重寫。Cricket 和 Football 是擴(kuò)展了 Game 的實(shí)體類,它們重寫了抽象類的方法。
TemplatePatternDemo,我們的演示類使用 Game 來演示模板模式的用法。

步驟 1
創(chuàng)建一個(gè)抽象類,它的模板方法被設(shè)置為 final。
Game.java
public abstract class Game {
abstract void initialize();
abstract void startPlay();
abstract void endPlay();
//模板
public final void play(){
//初始化游戲
initialize();
//開始游戲
startPlay();
//結(jié)束游戲
endPlay();
}
}
步驟 2
創(chuàng)建擴(kuò)展了上述類的實(shí)體類。
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() 來演示游戲的定義方式。
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();
}
}
模板模式,正如他的名字,就是提供一個(gè)模板,在頂層類中提供一個(gè)默認(rèn)方法去調(diào)用抽象方法。實(shí)體類去實(shí)現(xiàn)抽象方法,然后使用的時(shí)候就直接調(diào)用默認(rèn)方法就可以了。思想很簡單,也很容易實(shí)現(xiàn)。
3.命令模式
命令模式(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ì)客戶進(jìn)行參數(shù)化。
主要解決:在軟件系統(tǒng)中,行為請(qǐng)求者與行為實(shí)現(xiàn)者通常是一種緊耦合的關(guān)系,但某些場合,比如需要對(duì)行為進(jìn)行記錄、撤銷或重做、事務(wù)等處理時(shí),這種無法抵御變化的緊耦合的設(shè)計(jì)就不太合適。
何時(shí)使用:在某些場合,比如要對(duì)行為進(jìn)行"記錄、撤銷/重做、事務(wù)"等處理,這種無法抵御變化的緊耦合是不合適的。在這種情況下,如何將"行為請(qǐng)求者"與"行為實(shí)現(xiàn)者"解耦?將一組行為抽象為對(duì)象,可以實(shí)現(xiàn)二者之間的松耦合。
如何解決:通過調(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,而模型層的類會(huì)隨著不同的應(yīng)用有不同的模型類,相當(dāng)于具體的 Command。
優(yōu)點(diǎn): 1、降低了系統(tǒng)耦合度。 2、新的命令可以很容易添加到系統(tǒng)中去。
缺點(diǎn):使用命令模式可能會(huì)導(dǎo)致某些系統(tǒng)有過多的具體命令類。
使用場景:認(rèn)為是命令的地方都可以使用命令模式,比如: 1、GUI 中每一個(gè)按鈕都是一條命令。 2、模擬 CMD。
注意事項(xiàng):系統(tǒng)需要支持命令的撤銷(Undo)操作和恢復(fù)(Redo)操作,也可以考慮使用命令模式,見命令模式的擴(kuò)展。
實(shí)現(xiàn)
我們首先創(chuàng)建作為命令的接口 Order,然后創(chuàng)建作為請(qǐng)求的 Stock 類。實(shí)體命令類 BuyStock 和 SellStock,實(shí)現(xiàn)了 Order 接口,將執(zhí)行實(shí)際的命令處理。創(chuàng)建作為調(diào)用對(duì)象的類 Broker,它接受訂單并能下訂單。
Broker 對(duì)象使用命令模式,基于命令的類型確定哪個(gè)對(duì)象執(zhí)行哪個(gè)命令。CommandPatternDemo,我們的演示類使用 Broker 類來演示命令模式。

步驟 1
創(chuàng)建一個(gè)命令接口。
Order.java
public interface Order {
void execute();
}
步驟 2
創(chuàng)建一個(gè)請(qǐng)求類。
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í)體類。
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)用類。
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 類來接受并執(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();
}
}
命令模式一個(gè)將命令存儲(chǔ)在對(duì)象中的模式,他可以將命令對(duì)象傳遞給調(diào)用者,然后我們?nèi)ナ褂谜{(diào)用者來調(diào)用。這個(gè)模式結(jié)構(gòu)是比較清晰的,目的就是為了解耦,其實(shí)設(shè)計(jì)模式學(xué)了這么久,百分之50的設(shè)計(jì)都是為了解耦。
4、解釋器模式
解釋器模式(Interpreter Pattern)提供了評(píng)估語言的語法或表達(dá)式的方式,它屬于行為型模式。這種模式實(shí)現(xiàn)了一個(gè)表達(dá)式接口,該接口解釋一個(gè)特定的上下文。這種模式被用在 SQL 解析、符號(hào)處理引擎等。
介紹
意圖:給定一個(gè)語言,定義它的文法表示,并定義一個(gè)解釋器,這個(gè)解釋器使用該標(biāo)識(shí)來解釋語言中的句子。
主要解決:對(duì)于一些固定文法構(gòu)建一個(gè)解釋句子的解釋器。
何時(shí)使用:如果一種特定類型的問題發(fā)生的頻率足夠高,那么可能就值得將該問題的各個(gè)實(shí)例表述為一個(gè)簡單語言中的句子。這樣就可以構(gòu)建一個(gè)解釋器,該解釋器通過解釋這些句子來解決該問題。
如何解決:構(gòu)建語法樹,定義終結(jié)符與非終結(jié)符。
關(guān)鍵代碼:構(gòu)建環(huán)境類,包含解釋器之外的一些全局信息,一般是 HashMap。
應(yīng)用實(shí)例:編譯器、運(yùn)算表達(dá)式計(jì)算。
優(yōu)點(diǎn): 1、可擴(kuò)展性比較好,靈活。 2、增加了新的解釋表達(dá)式的方式。 3、易于實(shí)現(xiàn)簡單文法。
缺點(diǎn): 1、可利用場景比較少。 2、對(duì)于復(fù)雜的文法比較難維護(hù)。 3、解釋器模式會(huì)引起類膨脹。 4、解釋器模式采用遞歸調(diào)用方法。
使用場景: 1、可以將一個(gè)需要解釋執(zhí)行的語言中的句子表示為一個(gè)抽象語法樹。 2、一些重復(fù)出現(xiàn)的問題可以用一種簡單的語言來進(jìn)行表達(dá)。 3、一個(gè)簡單語法需要解釋的場景。
注意事項(xiàng):可利用場景比較少,JAVA 中如果碰到可以用 expression4J 代替。
實(shí)現(xiàn)
我們將創(chuàng)建一個(gè)接口 Expression 和實(shí)現(xiàn)了 Expression 接口的實(shí)體類。定義作為上下文中主要解釋器的 TerminalExpression 類。其他的類 OrExpression、AndExpression 用于創(chuàng)建組合式表達(dá)式。
InterpreterPatternDemo,我們的演示類使用 Expression 類創(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í)體類。
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 類來創(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"));
}
}
解釋器模式是一個(gè)使用的比較少的模式,它首先先創(chuàng)建了一個(gè)接口,這是一個(gè)抽象表達(dá)式,里面有interpret方法,然后會(huì)有兩種實(shí)現(xiàn)類,一種是終結(jié)符表達(dá)式,另外一種是非終結(jié)符表達(dá)式。然后便可以使用了,例子其實(shí)舉的一般,意思理解即可。主要是確實(shí)也很難找到一個(gè)很好的例子,畢竟這種模式使用的頻率較少。
5、迭代器模式
迭代器模式(Iterator Pattern)是 Java 和 .Net 編程環(huán)境中非常常用的設(shè)計(jì)模式。這種模式用于順序訪問集合對(duì)象的元素,不需要知道集合對(duì)象的底層表示。
迭代器模式屬于行為型模式。
介紹
意圖:提供一種方法順序訪問一個(gè)聚合對(duì)象中各個(gè)元素, 而又無須暴露該對(duì)象的內(nè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、迭代器簡化了聚合類。 3、在同一個(gè)聚合上可以有多個(gè)遍歷。 4、在迭代器模式中,增加新的聚合類和迭代器類都很方便,無須修改原有代碼。
缺點(diǎn):由于迭代器模式將存儲(chǔ)數(shù)據(jù)和遍歷數(shù)據(jù)的職責(zé)分離,增加新的聚合類需要對(duì)應(yīng)增加新的迭代器類,類的個(gè)數(shù)成對(duì)增加,這在一定程度上增加了系統(tǒng)的復(fù)雜性。
使用場景: 1、訪問一個(gè)聚合對(duì)象的內(nèi)容而無須暴露它的內(nèi)部表示。 2、需要為聚合對(duì)象提供多種遍歷方式。 3、為遍歷不同的聚合結(jié)構(gòu)提供一個(gè)統(tǒng)一的接口。
注意事項(xiàng):迭代器模式就是分離了集合對(duì)象的遍歷行為,抽象出一個(gè)迭代器類來負(fù)責(zé),這樣既可以做到不暴露集合的內(nèi)部結(jié)構(gòu),又可讓外部代碼透明地訪問集合內(nèi)部的數(shù)據(jù)。
實(shí)現(xiàn)
我們將創(chuàng)建一個(gè)敘述導(dǎo)航方法的 Iterator 接口和一個(gè)返回迭代器的 Container 接口。實(shí)現(xiàn)了 Container 接口的實(shí)體類將負(fù)責(zé)實(shí)現(xiàn) Iterator 接口。
IteratorPatternDemo,我們的演示類使用實(shí)體類 NamesRepository 來打印 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í)體類。該類有實(shí)現(xiàn)了 Iterator 接口的內(nè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 來獲取迭代器,并打印名字。
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);
}
}
}
迭代器模式,是一個(gè)你沒學(xué)過設(shè)計(jì)模式也聽說過,知道的一個(gè)模式,這個(gè)設(shè)計(jì)模式太有名了,因?yàn)槲覀兯褂玫娜萜鳎际亲裱髂J蕉O(shè)計(jì)的,我們用迭代器模式一般都是不用在意如何去遍歷一個(gè)容器,我們更加去在意遍歷的結(jié)果,而不需要在意如何去遍歷,因此我們可以用一個(gè)迭代器去幫我們遍歷,我們不需要在意如何去遍歷的, 也許這個(gè)遍歷過程很復(fù)雜比如一個(gè)二叉樹,我們只需要調(diào)用next方法就可以完成遍歷,這就是我們用迭代器的意義所在。
6、 中介者模式
中介者模式(Mediator Pattern)是用來降低多個(gè)對(duì)象和類之間的通信復(fù)雜性。這種模式提供了一個(gè)中介類,該類通常處理不同類之間的通信,并支持松耦合,使代碼易于維護(hù)。中介者模式屬于行為型模式。
介紹
意圖:用一個(gè)中介對(duì)象來封裝一系列的對(duì)象交互,中介者使各對(duì)象不需要顯式地相互引用,從而使其耦合松散,而且可以獨(dú)立地改變它們之間的交互。
主要解決:對(duì)象與對(duì)象之間存在大量的關(guān)聯(lián)關(guān)系,這樣勢必會(huì)導(dǎo)致系統(tǒng)的結(jié)構(gòu)變得很復(fù)雜,同時(shí)若一個(gè)對(duì)象發(fā)生改變,我們也需要跟蹤與之相關(guān)聯(lián)的對(duì)象,同時(shí)做出相應(yīng)的處理。
何時(shí)使用:多個(gè)類相互耦合,形成了網(wǎng)狀結(jié)構(gòu)。
如何解決:將上述網(wǎng)狀結(jié)構(gòu)分離為星型結(jié)構(gòu)。
關(guān)鍵代碼:對(duì)象 Colleague 之間的通信封裝到一個(gè)類中單獨(dú)處理。
應(yīng)用實(shí)例: 1、中國加入 WTO 之前是各個(gè)國家相互貿(mào)易,結(jié)構(gòu)復(fù)雜,現(xiàn)在是各個(gè)國家通過 WTO 來互相貿(mào)易。 2、機(jī)場調(diào)度系統(tǒng)。 3、MVC 框架,其中C(控制器)就是 M(模型)和 V(視圖)的中介者。
優(yōu)點(diǎn): 1、降低了類的復(fù)雜度,將一對(duì)多轉(zhuǎn)化成了一對(duì)一。 2、各個(gè)類之間的解耦。 3、符合迪米特原則。
缺點(diǎn):中介者會(huì)龐大,變得復(fù)雜難以維護(hù)。
使用場景: 1、系統(tǒng)中對(duì)象之間存在比較復(fù)雜的引用關(guān)系,導(dǎo)致它們之間的依賴關(guān)系結(jié)構(gòu)混亂而且難以復(fù)用該對(duì)象。 2、想通過一個(gè)中間類來封裝多個(gè)類中的行為,而又不想生成太多的子類。
注意事項(xiàng):不應(yīng)當(dāng)在職責(zé)混亂的時(shí)候使用。
實(shí)現(xiàn)
我們通過聊天室實(shí)例來演示中介者模式。實(shí)例中,多個(gè)用戶可以向聊天室發(fā)送消息,聊天室向所有的用戶顯示消息。我們將創(chuàng)建兩個(gè)類 ChatRoom 和 User。User 對(duì)象使用 ChatRoom 方法來分享他們的消息。
MediatorPatternDemo,我們的演示類使用 User 對(duì)象來顯示他們之間的通信。

步驟 1
創(chuàng)建中介類。
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 類。
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ì)象來顯示他們之間的通信。
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!");
}
}
中介者模式同樣是一個(gè)比較容易理解的模式,做web項(xiàng)目我們都知道需要用三層架構(gòu),service層,dao層,web層,目的究竟是什么呢?其實(shí)很簡單,降低成本。
我們可以舉一個(gè)現(xiàn)實(shí)生活中的模型,生產(chǎn)商,代理商,和客戶。生產(chǎn)商如果既做生產(chǎn),他如果也要做售出商品的服務(wù),那成本無疑會(huì)上漲很多,因?yàn)樯a(chǎn)商他只是單純擅長生產(chǎn),他不擅長銷售,他就要把任務(wù)給代理銷售的人,比如淘寶,京東,讓他們這些平臺(tái)代理銷售,處理售后服務(wù),這樣各司其職,效率能夠上升,客戶端不和生產(chǎn)商打交道,只用和代理商打交道就可以了。
而在程序中也是這樣,service層把各種dao層的基礎(chǔ)查詢更新操作融合在一起進(jìn)行處理,這樣可以維護(hù)起來更方便,結(jié)構(gòu)更清晰的同時(shí),效率也有保障。
因此在程序設(shè)計(jì)中,中介者模式也是一個(gè)非常重要的模式。
7、備忘錄模式
備忘錄模式(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),這樣做的目的就是為了允許用戶取消不確定或者錯(cuò)誤的操作,能夠恢復(fù)到他原先的狀態(tài),使得他有"后悔藥"可吃。
如何解決:通過一個(gè)備忘錄類專門存儲(chǔ)對(duì)象狀態(tài)。
關(guān)鍵代碼:客戶不與備忘錄類耦合,與備忘錄管理類耦合。
應(yīng)用實(shí)例: 1、后悔藥。 2、打游戲時(shí)的存檔。 3、Windows 里的 ctri + z。 4、IE 中的后退。 4、數(shù)據(jù)庫的事務(wù)管理。
優(yōu)點(diǎn): 1、給用戶提供了一種可以恢復(fù)狀態(tài)的機(jī)制,可以使用戶能夠比較方便地回到某個(gè)歷史的狀態(tài)。 2、實(shí)現(xiàn)了信息的封裝,使得用戶不需要關(guān)心狀態(tài)的保存細(xì)節(jié)。
缺點(diǎn):消耗資源。如果類的成員變量過多,勢必會(huì)占用比較大的資源,而且每一次保存都會(huì)消耗一定的內(nèi)存。
使用場景: 1、需要保存/恢復(fù)數(shù)據(jù)的相關(guān)狀態(tài)場景。 2、提供一個(gè)可回滾的操作。
注意事項(xiàng): 1、為了符合迪米特原則,還要增加一個(gè)管理備忘錄的類。 2、為了節(jié)約內(nèi)存,可使用原型模式+備忘錄模式。
實(shí)現(xiàn)
備忘錄模式使用三個(gè)類 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,我們的演示類使用 CareTaker 和 Originator 對(duì)象來顯示對(duì)象的狀態(tài)恢復(fù)。

步驟 1
創(chuàng)建 Memento 類。
Memento.java
public class Memento {
private String state;
public Memento(String state){
this.state = state;
}
public String getState(){
return state;
}
}
步驟 2
創(chuàng)建 Originator 類。
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 類。
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());
}
}
以前一直好奇ctrl+z是怎么完成撤銷的, 理解了這個(gè)模式,也就理解撤銷的過程。它實(shí)際上就是將之前的狀態(tài)統(tǒng)統(tǒng)保存了下來,然后可以通過操作進(jìn)行返回之前狀態(tài)。這個(gè)模式,在實(shí)際的操作中應(yīng)該是比較重要的,但是自己實(shí)現(xiàn)起來好像有點(diǎn)復(fù)雜。
8、觀察者模式
當(dāng)對(duì)象間存在一對(duì)多關(guān)系時(shí),則使用觀察者模式(Observer Pattern)。比如,當(dāng)一個(gè)對(duì)象被修改時(shí),則會(huì)自動(dòng)通知依賴它的對(duì)象。觀察者模式屬于行為型模式。
介紹
意圖:定義對(duì)象間的一種一對(duì)多的依賴關(guān)系,當(dāng)一個(gè)對(duì)象的狀態(tài)發(fā)生改變時(shí),所有依賴于它的對(duì)象都得到通知并被自動(dòng)更新。
主要解決:一個(gè)對(duì)象狀態(tài)改變給其他對(duì)象通知的問題,而且要考慮到易用和低耦合,保證高度的協(xié)作。
何時(shí)使用:一個(gè)對(duì)象(目標(biāo)對(duì)象)的狀態(tài)發(fā)生改變,所有的依賴對(duì)象(觀察者對(duì)象)都將得到通知,進(jìn)行廣播通知。
如何解決:使用面向?qū)ο蠹夹g(shù),可以將這種依賴關(guān)系弱化。
關(guān)鍵代碼:在抽象類里有一個(gè) ArrayList 存放觀察者們。
應(yīng)用實(shí)例: 1、拍賣的時(shí)候,拍賣師觀察最高標(biāo)價(jià),然后通知給其他競價(jià)者競價(jià)。 2、西游記里面悟空請(qǐng)求菩薩降服紅孩兒,菩薩灑了一地水招來一個(gè)老烏龜,這個(gè)烏龜就是觀察者,他觀察菩薩灑水這個(gè)動(dòng)作。
優(yōu)點(diǎn): 1、觀察者和被觀察者是抽象耦合的。 2、建立一套觸發(fā)機(jī)制。
缺點(diǎn): 1、如果一個(gè)被觀察者對(duì)象有很多的直接和間接的觀察者的話,將所有的觀察者都通知到會(huì)花費(fèi)很多時(shí)間。 2、如果在觀察者和觀察目標(biāo)之間有循環(huán)依賴的話,觀察目標(biāo)會(huì)觸發(fā)它們之間進(jìn)行循環(huán)調(diào)用,可能導(dǎo)致系統(tǒng)崩潰。 3、觀察者模式?jīng)]有相應(yīng)的機(jī)制讓觀察者知道所觀察的目標(biāo)對(duì)象是怎么發(fā)生變化的,而僅僅只是知道觀察目標(biāo)發(fā)生了變化。
使用場景:
- 一個(gè)抽象模型有兩個(gè)方面,其中一個(gè)方面依賴于另一個(gè)方面。將這些方面封裝在獨(dú)立的對(duì)象中使它們可以各自獨(dú)立地改變和復(fù)用。
- 一個(gè)對(duì)象的改變將導(dǎo)致其他一個(gè)或多個(gè)對(duì)象也發(fā)生改變,而不知道具體有多少對(duì)象將發(fā)生改變,可以降低對(duì)象之間的耦合度。
- 一個(gè)對(duì)象必須通知其他對(duì)象,而并不知道這些對(duì)象是誰。
- 需要在系統(tǒng)中創(chuàng)建一個(gè)觸發(fā)鏈,A對(duì)象的行為將影響B(tài)對(duì)象,B對(duì)象的行為將影響C對(duì)象……,可以使用觀察者模式創(chuàng)建一種鏈?zhǔn)接|發(fā)機(jī)制。
注意事項(xiàng): 1、JAVA 中已經(jīng)有了對(duì)觀察者模式的支持類。 2、避免循環(huán)引用。 3、如果順序執(zhí)行,某一觀察者錯(cuò)誤會(huì)導(dǎo)致系統(tǒng)卡殼,一般采用異步方式。
實(shí)現(xiàn)
觀察者模式使用三個(gè)類 Subject、Observer 和 Client。Subject 對(duì)象帶有綁定觀察者到 Client 對(duì)象和從 Client 對(duì)象解綁觀察者的方法。我們創(chuàng)建 Subject 類、Observer 抽象類和擴(kuò)展了抽象類 Observer 的實(shí)體類。
ObserverPatternDemo,我們的演示類使用 Subject 和實(shí)體類對(duì)象來演示觀察者模式。

步驟 1
創(chuàng)建 Subject 類。
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 notifyAllObservers(){
for (Observer observer : observers) {
observer.update();
}
}
}
步驟 2
創(chuàng)建 Observer 類。
Observer.java
public abstract class Observer {
protected Subject subject;
public abstract void update();
}
步驟 3
創(chuàng)建實(shí)體觀察者類。
BinaryObserver.java
public class BinaryObserver extends Observer{
public BinaryObserver(Subject subject){
this.subject = subject;
this.subject.attach(this);
}
@Override
public void update() {
System.out.println( "Binary String: "
+ Integer.toBinaryString( subject.getState() ) );
}
}
OctalObserver.java
public class OctalObserver extends Observer{
public OctalObserver(Subject subject){
this.subject = subject;
this.subject.attach(this);
}
@Override
public void update() {
System.out.println( "Octal String: "
+ Integer.toOctalString( subject.getState() ) );
}
}
HexaObserver.java
public class HexaObserver extends Observer{
public HexaObserver(Subject subject){
this.subject = subject;
this.subject.attach(this);
}
@Override
public void update() {
System.out.println( "Hex String: "
+ Integer.toHexString( subject.getState() ).toUpperCase() );
}
}
步驟 4
使用 Subject 和實(shí)體觀察者對(duì)象。
ObserverPatternDemo.java
public class ObserverPatternDemo {
public static void main(String[] args) {
Subject subject = new Subject();
new HexaObserver(subject);
new OctalObserver(subject);
new BinaryObserver(subject);
System.out.println("First state change: 15");
subject.setState(15);
System.out.println("Second state change: 10");
subject.setState(10);
}
}
觀察者模式其實(shí)思想和實(shí)現(xiàn)方式都不算復(fù)雜,就是通過一個(gè)類保存所有對(duì)象用集合方式,然后每次更新對(duì)象就把集合內(nèi)的所有內(nèi)容全部更新一次,需要注意的是,這些對(duì)象是不必互相認(rèn)識(shí)的,只要繼承相同類就可以保存在一個(gè)集合當(dāng)中,是非常方便的。
9、 狀態(tài)模式
在狀態(tài)模式(State Pattern)中,類的行為是基于它的狀態(tài)改變的。這種類型的設(shè)計(jì)模式屬于行為型模式。
在狀態(tài)模式中,我們創(chuàng)建表示各種狀態(tài)的對(duì)象和一個(gè)行為隨著狀態(tài)對(duì)象改變而改變的 context 對(duì)象。
介紹
意圖:允許對(duì)象在內(nèi)部狀態(tài)發(fā)生改變時(shí)改變它的行為,對(duì)象看起來好像修改了它的類。
主要解決:對(duì)象的行為依賴于它的狀態(tài)(屬性),并且可以根據(jù)它的狀態(tài)改變而改變它的相關(guān)行為。
何時(shí)使用:代碼中包含大量與對(duì)象狀態(tài)有關(guān)的條件語句。
如何解決:將各種具體的狀態(tài)類抽象出來。
關(guān)鍵代碼:通常命令模式的接口中只有一個(gè)方法。而狀態(tài)模式的接口中有一個(gè)或者多個(gè)方法。而且,狀態(tài)模式的實(shí)現(xiàn)類的方法,一般返回值,或者是改變實(shí)例變量的值。也就是說,狀態(tài)模式一般和對(duì)象的狀態(tài)有關(guān)。實(shí)現(xiàn)類的方法有不同的功能,覆蓋接口中的方法。狀態(tài)模式和命令模式一樣,也可以用于消除 if...else 等條件選擇語句。
應(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)種類。 3、將所有與某個(gè)狀態(tài)有關(guān)的行為放到一個(gè)類中,并且可以方便地增加新的狀態(tài),只需要改變對(duì)象狀態(tài)即可改變對(duì)象的行為。 4、允許狀態(tài)轉(zhuǎn)換邏輯與狀態(tài)對(duì)象合成一體,而不是某一個(gè)巨大的條件語句塊。 5、可以讓多個(gè)環(huán)境對(duì)象共享一個(gè)狀態(tài)對(duì)象,從而減少系統(tǒng)中對(duì)象的個(gè)數(shù)。
缺點(diǎn): 1、狀態(tài)模式的使用必然會(huì)增加系統(tǒng)類和對(duì)象的個(gè)數(shù)。 2、狀態(tài)模式的結(jié)構(gòu)與實(shí)現(xiàn)都較為復(fù)雜,如果使用不當(dāng)將導(dǎo)致程序結(jié)構(gòu)和代碼的混亂。 3、狀態(tài)模式對(duì)"開閉原則"的支持并不太好,對(duì)于可以切換狀態(tài)的狀態(tài)模式,增加新的狀態(tài)類需要修改那些負(fù)責(zé)狀態(tài)轉(zhuǎn)換的源代碼,否則無法切換到新增狀態(tài),而且修改某個(gè)狀態(tài)類的行為也需修改對(duì)應(yīng)類的源代碼。
使用場景: 1、行為隨狀態(tài)改變而改變的場景。 2、條件、分支語句的代替者。
注意事項(xiàng):在行為受狀態(tài)約束的時(shí)候使用狀態(tài)模式,而且狀態(tài)不超過 5 個(gè)。
實(shí)現(xiàn)
我們將創(chuàng)建一個(gè) State 接口和實(shí)現(xiàn)了 State 接口的實(shí)體狀態(tài)類。Context 是一個(gè)帶有某個(gè)狀態(tài)的類。
StatePatternDemo,我們的演示類使用 Context 和狀態(tài)對(duì)象來演示 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í)體類。
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 類。
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 來查看當(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());
}
}
狀態(tài)模式同樣是一個(gè)非常容易理解的設(shè)計(jì)模式,就是根據(jù)不同的狀態(tài)行為也不一樣,似乎和命令模式是有點(diǎn)類似的,根據(jù)不同命令來做不同的事。但實(shí)際上一般情況下,狀態(tài)模式需要一個(gè)抽象接口,然后有多個(gè)不同的實(shí)例去實(shí)現(xiàn)狀態(tài)接口,就是所謂的狀態(tài),與命令模式還是有些區(qū)別,但是兩者都是實(shí)現(xiàn)了if...else...的功能。
10、 訪問者模式
在訪問者模式(Visitor Pattern)中,我們使用了一個(gè)訪問者類,它改變了元素類的執(zhí)行算法。通過這種方式,元素的執(zhí)行算法可以隨著訪問者改變而改變。這種類型的設(shè)計(jì)模式屬于行為型模式。根據(jù)模式,元素對(duì)象已接受訪問者對(duì)象,這樣訪問者對(duì)象就可以處理元素對(duì)象上的操作。
介紹
意圖:主要將數(shù)據(jù)結(jié)構(gòu)與數(shù)據(jù)操作分離。
主要解決:穩(wěn)定的數(shù)據(jù)結(jié)構(gòu)和易變的操作耦合問題。
何時(shí)使用:需要對(duì)一個(gè)對(duì)象結(jié)構(gòu)中的對(duì)象進(jìn)行很多不同的并且不相關(guān)的操作,而需要避免讓這些操作"污染"這些對(duì)象的類,使用訪問者模式將這些封裝到類中。
如何解決:在被訪問的類里面加一個(gè)對(duì)外提供接待訪問者的接口。
關(guān)鍵代碼:在數(shù)據(jù)基礎(chǔ)類里面有一個(gè)方法接受訪問者,將自身引用傳入訪問者。
應(yīng)用實(shí)例:您在朋友家做客,您是訪問者,朋友接受您的訪問,您通過朋友的描述,然后對(duì)朋友的描述做出一個(gè)判斷,這就是訪問者模式。
優(yōu)點(diǎn): 1、符合單一職責(zé)原則。 2、優(yōu)秀的擴(kuò)展性。 3、靈活性。
缺點(diǎn): 1、具體元素對(duì)訪問者公布細(xì)節(jié),違反了迪米特原則。 2、具體元素變更比較困難。 3、違反了依賴倒置原則,依賴了具體類,沒有依賴抽象。
使用場景: 1、對(duì)象結(jié)構(gòu)中對(duì)象對(duì)應(yīng)的類很少改變,但經(jīng)常需要在此對(duì)象結(jié)構(gòu)上定義新的操作。 2、需要對(duì)一個(gè)對(duì)象結(jié)構(gòu)中的對(duì)象進(jìn)行很多不同的并且不相關(guān)的操作,而需要避免讓這些操作"污染"這些對(duì)象的類,也不希望在增加新操作時(shí)修改這些類。
注意事項(xiàng):訪問者可以對(duì)功能進(jìn)行統(tǒng)一,可以做報(bào)表、UI、攔截器與過濾器。
實(shí)現(xiàn)
我們將創(chuàng)建一個(gè)定義接受操作的 ComputerPart 接口。Keyboard、Mouse、Monitor 和 Computer 是實(shí)現(xiàn)了 ComputerPart 接口的實(shí)體類。我們將定義另一個(gè)接口 ComputerPartVisitor,它定義了訪問者類的操作。Computer 使用實(shí)體訪問者來執(zhí)行相應(yīng)的動(dòng)作。
VisitorPatternDemo,我們的演示類使用 Computer、ComputerPartVisitor 類來演示訪問者模式的用法。

步驟 1
定義一個(gè)表示元素的接口。
ComputerPart.java
public interface ComputerPart {
public void accept(ComputerPartVisitor computerPartVisitor);
}
步驟 2
創(chuàng)建擴(kuò)展了上述類的實(shí)體類。
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è)表示訪問者的接口。
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)了上述類的實(shí)體訪問者。
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 來顯示 Computer 的組成部分。
VisitorPatternDemo.java
public class VisitorPatternDemo {
public static void main(String[] args) {
ComputerPart computer = new Computer();
computer.accept(new ComputerPartDisplayVisitor());
}
}
訪問者模式是一種非常復(fù)雜的模式,當(dāng)我們需要對(duì)一個(gè)對(duì)象訪問,而不同人關(guān)注的點(diǎn)不一樣的時(shí)候,就可以使用這個(gè)設(shè)計(jì)模式,我們首先有一個(gè)抽象類或者抽象接口接受一個(gè)對(duì)象,而通過不同的對(duì)象,我們展示的內(nèi)容也是不一樣的, 但是需要知道的與狀態(tài)模式是有明顯區(qū)別的,一個(gè)是相同對(duì)象的不同狀態(tài),一個(gè)是不同的對(duì)象。看似差不多的模式,但是實(shí)際應(yīng)用會(huì)完全不一樣,只能說思想是類似的,畢竟都是行為型設(shè)計(jì)模式。
11、策略模式
在策略模式(Strategy Pattern)中,一個(gè)類的行為或其算法可以在運(yùn)行時(shí)更改。這種類型的設(shè)計(jì)模式屬于行為型模式。
在策略模式中,我們創(chuàng)建表示各種策略的對(duì)象和一個(gè)行為隨著策略對(duì)象改變而改變的 context 對(duì)象。策略對(duì)象改變 context 對(duì)象的執(zhí)行算法。
介紹
意圖:定義一系列的算法,把它們一個(gè)個(gè)封裝起來, 并且使它們可相互替換。
主要解決:在有多種算法相似的情況下,使用 if...else 所帶來的復(fù)雜和難以維護(hù)。
何時(shí)使用:一個(gè)系統(tǒng)有許多許多類,而區(qū)分它們的只是他們直接的行為。
如何解決:將這些算法封裝成一個(gè)一個(gè)的類,任意地替換。
關(guān)鍵代碼:實(shí)現(xiàn)同一個(gè)接口。
應(yīng)用實(shí)例: 1、諸葛亮的錦囊妙計(jì),每一個(gè)錦囊就是一個(gè)策略。 2、旅行的出游方式,選擇騎自行車、坐汽車,每一種旅行方式都是一個(gè)策略。 3、JAVA AWT 中的 LayoutManager。
優(yōu)點(diǎn): 1、算法可以自由切換。 2、避免使用多重條件判斷。 3、擴(kuò)展性良好。
缺點(diǎn): 1、策略類會(huì)增多。 2、所有策略類都需要對(duì)外暴露。
使用場景: 1、如果在一個(gè)系統(tǒng)里面有許多類,它們之間的區(qū)別僅在于它們的行為,那么使用策略模式可以動(dòng)態(tài)地讓一個(gè)對(duì)象在許多行為中選擇一種行為。 2、一個(gè)系統(tǒng)需要?jiǎng)討B(tài)地在幾種算法中選擇一種。 3、如果一個(gè)對(duì)象有很多的行為,如果不用恰當(dāng)?shù)哪J?,這些行為就只好使用多重的條件選擇語句來實(shí)現(xiàn)。
注意事項(xiàng):如果一個(gè)系統(tǒng)的策略多于四個(gè),就需要考慮使用混合模式,解決策略類膨脹的問題。
實(shí)現(xiàn)
我們將創(chuàng)建一個(gè)定義活動(dòng)的 Strategy 接口和實(shí)現(xiàn)了 Strategy 接口的實(shí)體策略類。Context 是一個(gè)使用了某種策略的類。
StrategyPatternDemo,我們的演示類使用 Context 和策略對(duì)象來演示 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í)體類。
OperationAdd.java
public class OperationAdd implements Strategy{
@Override
public int doOperation(int num1, int num2) {
return num1 + num2;
}
}
OperationSubtract.java
public class OperationSubtract 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 類。
Context.java
public class Context {
private Strategy strategy;
public Context(Strategy strategy){
this.strategy = strategy;
}
public int executeStrategy(int num1, int num2){
return strategy.doOperation(num1, num2);
}
}
步驟 4
使用 Context 來查看當(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 OperationSubtract());
System.out.println("10 - 5 = " + context.executeStrategy(10, 5));
context = new Context(new OperationMultiply());
System.out.println("10 * 5 = " + context.executeStrategy(10, 5));
}
}
與之前幾種設(shè)計(jì)模式是類似的,就是為了解決if...else...的方式,一個(gè)抽象類中有一個(gè)操作方法,這個(gè)抽象類可以被多種類實(shí)例化,然后可以通過選擇不同的對(duì)象來執(zhí)行不同的方法。而這種設(shè)計(jì)模式與之前幾種的著重點(diǎn)不同,更加著重的是策略,或者說是方法本身。