下面總結(jié)設(shè)計模式中的行為型模式:
1.責(zé)任鏈模式
顧名思義慨灭,責(zé)任鏈模式(Chain of Responsibility Pattern)為請求創(chuàng)建了一個接收者對象的鏈桶略。這種模式給予請求的類型惶翻,對請求的發(fā)送者和接收者進行解耦纺荧。這種類型的設(shè)計模式屬于行為型模式议泵。
在這種模式中型奥,通常每個接收者都包含對另一個接收者的引用谐宙。如果一個對象不能處理該請求搭综,那么它會把相同的請求傳給下一個接收者茅姜,依此類推。
介紹
意圖:避免請求發(fā)送者與接收者耦合在一起,讓多個對象都有可能接收請求寓免,將這些對象連接成一條鏈鲫惶,并且沿著這條鏈傳遞請求欢策,直到有對象處理它為止猜敢。
主要解決:職責(zé)鏈上的處理者負責(zé)處理請求鼠冕,客戶只需要將請求發(fā)送到職責(zé)鏈上即可博脑,無須關(guān)心請求的處理細節(jié)和請求的傳遞泞边,所以職責(zé)鏈將請求的發(fā)送者和請求的處理者解耦了。
何時使用:在處理消息的時候以過濾很多道。
如何解決:攔截的類都實現(xiàn)統(tǒng)一接口。
關(guān)鍵代碼:Handler里面聚合它自己狸演,在 HandlerRequest 里判斷是否合適中姜,如果沒達到條件則向下傳遞翩瓜,向誰傳遞之前 set 進去峡蟋。
應(yīng)用實例: 1、紅樓夢中的"擊鼓傳花"。 2、JS 中的事件冒泡。 3、JAVA WEB 中 Apache Tomcat 對 Encoding 的處理花椭,Struts2 的攔截器郭厌,jsp servlet 的 Filter批狐。 4.JAVA 的異常鏈機制
優(yōu)點: 1、降低耦合度。它將請求的發(fā)送者和接收者解耦。 2、簡化了對象。使得對象不需要知道鏈的結(jié)構(gòu)。 3、增強給對象指派職責(zé)的靈活性。通過改變鏈內(nèi)的成員或者調(diào)動它們的次序,允許動態(tài)地新增或者刪除責(zé)任。 4管怠、增加新的請求處理類很方便甚带。
缺點: 1砾莱、不能保證請求一定被接收。 2、系統(tǒng)性能將受到一定影響,而且在進行代碼調(diào)試時不太方便,可能會造成循環(huán)調(diào)用。 3、可能不容易觀察運行時的特征情竹,有礙于除錯。
使用場景: 1棉安、有多個對象可以處理同一個請求底扳,具體哪個對象處理該請求由運行時刻自動確定盒件。 2、在不明確指定接收者的情況下朋凉,向多個對象中的一個提交一個請求蒲赂。 3阱冶、可動態(tài)指定一組對象處理請求。
注意事項:在 JAVA WEB 中遇到很多應(yīng)用滥嘴。
實現(xiàn)
我們以銷售樓盤為例木蹬,客戶是Customer類,發(fā)送一個折扣請求給責(zé)任鏈若皱。責(zé)任鏈由樓盤各級人員組成镊叁,都繼承自PriceHandler抽象類,自底向上為Sales(銷售)走触、Lead(銷售小組長)晦譬、Manager(銷售經(jīng)理)、Director(銷售總監(jiān))互广、VicePresident(銷售副總裁)敛腌、CEO(首席執(zhí)行官)。沿著責(zé)任鏈惫皱,能批準折扣的力度依次上升像樊。
步驟 1 創(chuàng)建PriceHandler抽象類
PriceHandler.java
package com.imooc.pattern.cor.handler;
/*
* 價格處理人,負責(zé)處理客戶折扣申請
*/
public abstract class PriceHandler {
/*
* 直接后繼旅敷,用于傳遞請求
*/
protected PriceHandler successor;
public void setSuccessor(PriceHandler successor) {
this.successor = successor;
}
/*
* 處理折扣申請
*/
public abstract void processDiscount(float discount);
}
步驟 2 創(chuàng)建PriceHandler的具體類
Sales.java
package com.imooc.pattern.cor.handler;
/*
* 銷售凶硅, 可以批準5%以內(nèi)的折扣
*/
public class Sales extends PriceHandler {
@Override
public void processDiscount(float discount) {
if(discount <= 0.05){
System.out.format("%s批準了折扣:%.2f%n", this.getClass().getName(), discount);
}else{
successor.processDiscount(discount);
}
}
}
Lead.java
package com.imooc.pattern.cor.handler;
/*
* 銷售小組長, 可以批準15%以內(nèi)的折扣
*/
public class Lead extends PriceHandler {
@Override
public void processDiscount(float discount) {
if(discount<=0.15){
System.out.format("%s批準了折扣:%.2f%n",this.getClass().getName(),discount);
}else{
successor.processDiscount(discount);
}
}
}
Manager.java
package com.imooc.pattern.cor.handler;
/*
* 銷售經(jīng)理扫皱, 可以批準30%以內(nèi)的折扣
*/
public class Manager extends PriceHandler {
@Override
public void processDiscount(float discount) {
if(discount<=0.3){
System.out.format("%s批準了折扣:%.2f%n",this.getClass().getName(),discount);
}else{
successor.processDiscount(discount);
}
}
}
Director.java
package com.imooc.pattern.cor.handler;
/*
* 銷售總監(jiān)足绅, 可以批準40%以內(nèi)的折扣
*/
public class Director extends PriceHandler {
@Override
public void processDiscount(float discount) {
if(discount<=0.4){
System.out.format("%s批準了折扣:%.2f%n",this.getClass().getName(),discount);
}else{
successor.processDiscount(discount);
}
}
}
VicePresident.java
package com.imooc.pattern.cor.handler;
/*
* 銷售副總裁, 可以批準50%以內(nèi)的折扣
*/
public class VicePresident extends PriceHandler {
@Override
public void processDiscount(float discount) {
if(discount<=0.5){
System.out.format("%s批準了折扣:%.2f%n",this.getClass().getName(),discount);
}else{
successor.processDiscount(discount);
}
}
}
CEO.java
package com.imooc.pattern.cor.handler;
/*
* CEO韩脑, 可以批準55%以內(nèi)的折扣
* 折扣超出55%氢妈, 就拒絕申請
*/
public class CEO extends PriceHandler {
@Override
public void processDiscount(float discount) {
if(discount<=0.55){
System.out.format("%s批準了折扣:%.2f%n",this.getClass().getName(),discount);
}else{
System.out.format("%s拒絕了折扣:%.2f%n", this.getClass().getName(),discount);
}
}
}
步驟 3 創(chuàng)建PriceHandlerFactory類
package com.imooc.pattern.cor.handler;
public class PriceHandlerFactory {
/*
* 創(chuàng)建PriceHandler的工廠方法,類似于構(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類
package com.imooc.pattern.cor;
import java.util.Random;
import com.imooc.pattern.cor.handler.PriceHandler;
import com.imooc.pattern.cor.handler.PriceHandlerFactory;
/*
* 客戶段多,請求折扣
*/
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ū)動的設(shè)計模式首量,它屬于行為型模式。請求以命令的形式包裹在對象中,并傳給調(diào)用對象加缘。調(diào)用對象尋找可以處理該命令的合適的對象鸭叙,并把該命令傳給相應(yīng)的對象,該對象執(zhí)行命令拣宏。
介紹
意圖:將一個請求封裝成一個對象沈贝,從而使您可以用不同的請求對客戶進行參數(shù)化。
主要解決:在軟件系統(tǒng)中勋乾,行為請求者與行為實現(xiàn)者通常是一種緊耦合的關(guān)系宋下,但某些場合,比如需要對行為進行記錄辑莫、撤銷或重做学歧、事務(wù)等處理時,這種無法抵御變化的緊耦合的設(shè)計就不太合適各吨。
何時使用:在某些場合枝笨,比如要對行為進行"記錄、撤銷/重做揭蜒、事務(wù)"等處理伺帘,這種無法抵御變化的緊耦合是不合適的。在這種情況下忌锯,如何將"行為請求者"與"行為實現(xiàn)者"解耦伪嫁?將一組行為抽象為對象,可以實現(xiàn)二者之間的松耦合偶垮。
如何解決:通過調(diào)用者調(diào)用接受者執(zhí)行命令张咳,順序:調(diào)用者→接受者→命令。
關(guān)鍵代碼:定義三個角色:1似舵、received 真正的命令執(zhí)行對象 2脚猾、Command 3、invoker 使用命令對象的入口
應(yīng)用實例:struts 1 中的 action 核心控制器 ActionServlet 只有一個砚哗,相當于 Invoker龙助,而模型層的類會隨著不同的應(yīng)用有不同的模型類,相當于具體的 Command蛛芥。
優(yōu)點:1提鸟、降低了系統(tǒng)耦合度。 2仅淑、新的命令可以很容易添加到系統(tǒng)中去称勋。
缺點:使用命令模式可能會導(dǎo)致某些系統(tǒng)有過多的具體命令類。
使用場景:認為是命令的地方都可以使用命令模式涯竟,比如: 1赡鲜、GUI 中每一個按鈕都是一條命令空厌。 2、模擬 CMD银酬。
注意事項:系統(tǒng)需要支持命令的撤銷(Undo)操作和恢復(fù)(Redo)操作嘲更,也可以考慮使用命令模式,見命令模式的擴展揩瞪。
實現(xiàn)
我們首先創(chuàng)建作為命令的接口 Order赋朦,然后創(chuàng)建作為請求的 Stock 類。實體命令類 BuyStock 和 SellStock壮韭,實現(xiàn)了 Order 接口北发,將執(zhí)行實際的命令處理纹因。創(chuàng)建作為調(diào)用對象的類 Broker喷屋,它接受訂單并能下訂單。
Broker 對象使用命令模式瞭恰,基于命令的類型確定哪個對象執(zhí)行哪個命令屯曹。CommandPatternDemo,我們的演示類使用 Broker 類來演示命令模式惊畏。
步驟 1
創(chuàng)建一個命令接口恶耽。
Order.java
public interface Order {
void execute();
}
步驟 2
創(chuà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)建實現(xiàn)了 Order 接口的實體類颜启。
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();
}
}
步驟 6
執(zhí)行程序缰盏,輸出結(jié)果:
Stock [ Name: ABC, Quantity: 10 ] bought
Stock [ Name: ABC, Quantity: 10 ] sold
3.解釋器模式
解釋器模式(Interpreter Pattern)提供了評估語言的語法或表達式的方式涌萤,它屬于行為型模式。這種模式實現(xiàn)了一個表達式接口口猜,該接口解釋一個特定的上下文负溪。這種模式被用在 SQL 解析、符號處理引擎等济炎。
介紹
意圖:給定一個語言川抡,定義它的文法表示,并定義一個解釋器须尚,這個解釋器使用該標識來解釋語言中的句子崖堤。
主要解決:對于一些固定文法構(gòu)建一個解釋句子的解釋器。
何時使用:如果一種特定類型的問題發(fā)生的頻率足夠高耐床,那么可能就值得將該問題的各個實例表述為一個簡單語言中的句子倘感。這樣就可以構(gòu)建一個解釋器,該解釋器通過解釋這些句子來解決該問題咙咽。
如何解決:構(gòu)建語法樹老玛,定義終結(jié)符與非終結(jié)符。
關(guān)鍵代碼:構(gòu)件環(huán)境類,包含解釋器之外的一些全局信息蜡豹,一般是 HashMap麸粮。
應(yīng)用實例:編譯器、運算表達式計算镜廉。
優(yōu)點:1弄诲、可擴展性比較好,靈活娇唯。 2齐遵、增加了新的解釋表達式的方式。 3塔插、易于實現(xiàn)簡單文法梗摇。
缺點:1、可利用場景比較少想许。 2伶授、對于復(fù)雜的文法比較難維護。 3流纹、解釋器模式會引起類膨脹糜烹。 4、解釋器模式采用遞歸調(diào)用方法漱凝。
使用場景:1疮蹦、可以將一個需要解釋執(zhí)行的語言中的句子表示為一個抽象語法樹。 2茸炒、一些重復(fù)出現(xiàn)的問題可以用一種簡單的語言來進行表達愕乎。 3、一個簡單語法需要解釋的場景扣典。
注意事項:可利用場景比較少妆毕,JAVA 中如果碰到可以用 expression4J 代替。
實現(xiàn)
我們將創(chuàng)建一個接口 Expression 和實現(xiàn)了 Expression 接口的實體類贮尖。定義作為上下文中主要解釋器的 TerminalExpression 類笛粘。其他的類 OrExpression、AndExpression 用于創(chuàng)建組合式表達式湿硝。
InterpreterPatternDemo薪前,我們的演示類使用 Expression 類創(chuàng)建規(guī)則和演示表達式的解析。
步驟 1
創(chuàng)建一個表達式接口关斜。
Expression.java
public interface Expression {
public boolean interpret(String context);
}
步驟 2
創(chuàng)建實現(xiàn)了上述接口的實體類示括。
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 是一個已婚的女性
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è)計模式鳍侣。這種模式用于順序訪問集合對象的元素,不需要知道集合對象的底層表示吼拥。
迭代器模式屬于行為型模式倚聚。
介紹
意圖:提供一種方法順序訪問一個聚合對象中各個元素, 而又無須暴露該對象的內(nèi)部表示。
主要解決:不同的方式來遍歷整個整合對象凿可。
何時使用:遍歷一個聚合對象惑折。
如何解決:把在元素之間游走的責(zé)任交給迭代器,而不是聚合對象枯跑。
關(guān)鍵代碼:定義接口:hasNext, next惨驶。
應(yīng)用實例:JAVA 中的 iterator。
優(yōu)點:1敛助、它支持以不同的方式遍歷一個聚合對象粗卜。 2、迭代器簡化了聚合類辜腺。 3休建、在同一個聚合上可以有多個遍歷乍恐。 4评疗、在迭代器模式中,增加新的聚合類和迭代器類都很方便茵烈,無須修改原有代碼百匆。
缺點:由于迭代器模式將存儲數(shù)據(jù)和遍歷數(shù)據(jù)的職責(zé)分離,增加新的聚合類需要對應(yīng)增加新的迭代器類呜投,類的個數(shù)成對增加加匈,這在一定程度上增加了系統(tǒng)的復(fù)雜性。
使用場景:1仑荐、訪問一個聚合對象的內(nèi)容而無須暴露它的內(nèi)部表示雕拼。 2、需要為聚合對象提供多種遍歷方式粘招。 3啥寇、為遍歷不同的聚合結(jié)構(gòu)提供一個統(tǒng)一的接口。
注意事項:迭代器模式就是分離了集合對象的遍歷行為洒扎,抽象出一個迭代器類來負責(zé)辑甜,這樣既可以做到不暴露集合的內(nèi)部結(jié)構(gòu),又可讓外部代碼透明地訪問集合內(nèi)部的數(shù)據(jù)袍冷。
實現(xiàn)
我們將創(chuàng)建一個敘述導(dǎo)航方法的 Iterator 接口和一個返回迭代器的 Container 接口磷醋。實現(xiàn)了 Container 接口的實體類將負責(zé)實現(xiàn) Iterator 接口。
IteratorPatternDemo胡诗,我們的演示類使用實體類 NamesRepository 來打印 NamesRepository 中存儲為集合的 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)建實現(xiàn)了 Container 接口的實體類淌友。該類有實現(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);
}
}
}
步驟 4
執(zhí)行程序,輸出結(jié)果:
Name : Robert
Name : John
Name : Julie
Name : Lora
5.中介者模式
中介者模式(Mediator Pattern)是用來降低多個對象和類之間的通信復(fù)雜性缩歪。這種模式提供了一個中介類归薛,該類通常處理不同類之間的通信,并支持松耦合匪蝙,使代碼易于維護主籍。中介者模式屬于行為型模式。
介紹
意圖:用一個中介對象來封裝一系列的對象交互逛球,中介者使各對象不需要顯式地相互引用千元,從而使其耦合松散,而且可以獨立地改變它們之間的交互颤绕。
主要解決:對象與對象之間存在大量的關(guān)聯(lián)關(guān)系幸海,這樣勢必會導(dǎo)致系統(tǒng)的結(jié)構(gòu)變得很復(fù)雜,同時若一個對象發(fā)生改變奥务,我們也需要跟蹤與之相關(guān)聯(lián)的對象物独,同時做出相應(yīng)的處理。
何時使用:多個類相互耦合氯葬,形成了網(wǎng)狀結(jié)構(gòu)挡篓。
如何解決:將上述網(wǎng)狀結(jié)構(gòu)分離為星型結(jié)構(gòu)。
關(guān)鍵代碼:對象 Colleague 之間的通信封裝到一個類中單獨處理帚称。
應(yīng)用實例:1官研、中國加入 WTO 之前是各個國家相互貿(mào)易,結(jié)構(gòu)復(fù)雜闯睹,現(xiàn)在是各個國家通過 WTO 來互相貿(mào)易戏羽。 2、機場調(diào)度系統(tǒng)楼吃。 3始花、MVC 框架,其中C(控制器)就是 M(模型)和 V(視圖)的中介者所刀。
優(yōu)點:1衙荐、降低了類的復(fù)雜度,將一對多轉(zhuǎn)化成了一對一浮创。 2忧吟、各個類之間的解耦。 3斩披、符合迪米特原則溜族。
缺點:中介者會龐大讹俊,變得復(fù)雜難以維護。
使用場景:1煌抒、系統(tǒng)中對象之間存在比較復(fù)雜的引用關(guān)系仍劈,導(dǎo)致它們之間的依賴關(guān)系結(jié)構(gòu)混亂而且難以復(fù)用該對象。 2寡壮、想通過一個中間類來封裝多個類中的行為贩疙,而又不想生成太多的子類。
注意事項:不應(yīng)當在職責(zé)混亂的時候使用况既。
實現(xiàn)
我們通過聊天室實例來演示中介者模式这溅。實例中,多個用戶可以向聊天室發(fā)送消息棒仍,聊天室向所有的用戶顯示消息悲靴。我們將創(chuàng)建兩個類 ChatRoom 和 User。User 對象使用 ChatRoom 方法來分享他們的消息莫其。
MediatorPatternDemo癞尚,我們的演示類使用 User 對象來顯示他們之間的通信。
步驟 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 對象來顯示他們之間的通信。
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)保存一個對象的某個狀態(tài)临燃,以便在適當?shù)臅r候恢復(fù)對象睛驳。備忘錄模式屬于行為型模式烙心。
介紹
意圖:在不破壞封裝性的前提下叁怪,捕獲一個對象的內(nèi)部狀態(tài)袱结,并在該對象之外保存這個狀態(tài)。
主要解決:所謂備忘錄模式就是在不破壞封裝的前提下页眯,捕獲一個對象的內(nèi)部狀態(tài)蹬跃,并在該對象之外保存這個狀態(tài)匙瘪,這樣可以在以后將對象恢復(fù)到原先保存的狀態(tài)。
何時使用:很多時候我們總是需要記錄一個對象的內(nèi)部狀態(tài)蝶缀,這樣做的目的就是為了允許用戶取消不確定或者錯誤的操作丹喻,能夠恢復(fù)到他原先的狀態(tài),使得他有"后悔藥"可吃翁都。
如何解決:通過一個備忘錄類專門存儲對象狀態(tài)碍论。
關(guān)鍵代碼:客戶不與備忘錄類耦合,與備忘錄管理類耦合柄慰。
應(yīng)用實例:1鳍悠、后悔藥税娜。 2、打游戲時的存檔藏研。 3敬矩、Windows 里的 ctri + z。 4蠢挡、IE 中的后退弧岳。 4、數(shù)據(jù)庫的事務(wù)管理业踏。
優(yōu)點:1缩筛、給用戶提供了一種可以恢復(fù)狀態(tài)的機制,可以使用戶能夠比較方便地回到某個歷史的狀態(tài)堡称。 2瞎抛、實現(xiàn)了信息的封裝,使得用戶不需要關(guān)心狀態(tài)的保存細節(jié)却紧。
缺點:消耗資源桐臊。如果類的成員變量過多,勢必會占用比較大的資源晓殊,而且每一次保存都會消耗一定的內(nèi)存断凶。
使用場景:1、需要保存/恢復(fù)數(shù)據(jù)的相關(guān)狀態(tài)場景巫俺。 2认烁、提供一個可回滾的操作。
注意事項:1介汹、為了符合迪米特原則却嗡,還要增加一個管理備忘錄的類。 2嘹承、為了節(jié)約內(nèi)存窗价,可使用原型模式+備忘錄模式。
實現(xiàn)
備忘錄模式使用三個類 Memento叹卷、Originator 和 CareTaker撼港。Memento 包含了要被恢復(fù)的對象的狀態(tài)。Originator 創(chuàng)建并在 Memento 對象中存儲狀態(tài)骤竹。Caretaker 對象負責(zé)從 Memento 中恢復(fù)對象的狀態(tài)帝牡。
MementoPatternDemo,我們的演示類使用 CareTaker 和 Originator 對象來顯示對象的狀態(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 對象墨技。
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
驗證輸出惩阶。
Current State: State #4
First saved State: State #2
Second saved State: State #3
7.觀察者模式
當對象間存在一對多關(guān)系時,則使用觀察者模式(Observer Pattern)扣汪。比如断楷,當一個對象被修改時,則會自動通知它的依賴對象崭别,被通知的對象會做出各自的反應(yīng)冬筒。觀察者模式屬于行為型模式。
介紹
意圖:定義對象間的一種一對多的依賴關(guān)系茅主,當一個對象的狀態(tài)發(fā)生改變時舞痰,所有依賴于它的對象都得到通知并被自動更新。
主要解決:一個對象狀態(tài)改變給其他對象通知的問題诀姚,而且要考慮到易用和低耦合响牛,保證高度的協(xié)作。
何時使用:一個對象(目標對象)的狀態(tài)發(fā)生改變赫段,進行廣播通知呀打,所有的依賴對象(觀察者對象)都將得到通知并做出各自的反應(yīng)。
如何解決:使用面向?qū)ο蠹夹g(shù)糯笙,可以將這種依賴關(guān)系弱化贬丛。
關(guān)鍵代碼:在抽象類里有一個 ArrayList 存放觀察者們。
應(yīng)用實例: 1给涕、拍賣的時候豺憔,拍賣師觀察最高標價,然后通知給其他競價者競價够庙。 2恭应、西游記里面悟空請求菩薩降服紅孩兒,菩薩灑了一地水招來一個老烏龜首启,這個烏龜就是觀察者暮屡,他觀察菩薩灑水這個動作。
優(yōu)點: 1毅桃、觀察者和被觀察者是抽象耦合的,被觀察者只知道觀察者接口准夷,不知道具體的觀察者類钥飞,實現(xiàn)了被觀察者類和具體觀察者類的解耦。 2衫嵌、建立一套觸發(fā)機制读宙,實現(xiàn)了動態(tài)聯(lián)動。
3楔绞、支持廣播通信结闸。
缺點: 1唇兑、如果一個被觀察者對象有很多的直接和間接的觀察者的話,將所有的觀察者都通知到會花費很多時間桦锄。 2扎附、如果在觀察者和觀察目標之間有循環(huán)依賴的話,觀察目標會觸發(fā)它們之間進行循環(huán)調(diào)用结耀,可能導(dǎo)致系統(tǒng)崩潰留夜。 3、觀察者模式?jīng)]有相應(yīng)的機制讓觀察者知道所觀察的目標對象是怎么發(fā)生變化的图甜,而僅僅只是知道觀察目標發(fā)生了變化碍粥。
使用場景: 1、一個抽象模型有兩個方面黑毅,其中一個方面的操作(觀察者)依賴于另一個方面狀態(tài)的變化(被觀察者)嚼摩。 2、如果在更改一個對象的時候矿瘦,需要同時連帶改變其他對象低斋,而且不知道究竟有多少對象需要被連帶改變。 3匪凡、當一個對象必須通知其他對象膊畴,而又希望這個對象和其他被通知的對象是松散耦合的。
注意事項: 1病游、JAVA 中已經(jīng)有了對觀察者模式的支持類唇跨。 2、避免循環(huán)引用衬衬。 3买猖、如果順序執(zhí)行,某一觀察者錯誤會導(dǎo)致系統(tǒng)卡殼滋尉,一般采用異步方式玉控。
實現(xiàn)
觀察者模式使用三個類 Subject、Observer 和 Client狮惜。Subject 對象帶有綁定觀察者到 Client 對象和從 Client 對象解綁觀察者的方法高诺。我們創(chuàng)建 Subject 類、Observer 抽象類和擴展了抽象類 Observer 的實體類碾篡。
ObserverPatternDemo虱而,我們的演示類使用 Subject 和實體類對象來演示觀察者模式。
觀察者模式實現(xiàn)的兩種方式
1) 推模型
目標對象主動向觀察者推送目標的詳細信息开泽,推送的信息通常是目標或目標對象的全部數(shù)據(jù)牡拇。一般這種模型的實現(xiàn)中,會把目標對象想要推送的信息通過update方法傳遞給觀察者。
步驟 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(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 類
Observer.java
public abstract class Observer {
public abstract void update(int state); //傳遞要推送的信息导俘,觀察者只能接收到目標推送的數(shù)據(jù)
}
步驟 3
創(chuàng)建實體觀察者類
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 和實體觀察者對象
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
驗證輸出
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) 拉模型
目標對象在通知觀察者的時候,只傳遞少量信息剔蹋。如果觀察者需要更具體的信息旅薄,由觀察者主動到目標對象中獲取,相當于觀察者從目標對象中拉數(shù)據(jù)滩租。一般這種模型的實現(xiàn)中赋秀,會把目標對象自身的引用通過update方法傳遞給觀察者。
步驟 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 detach(Observer observer){
observers.remove(observer);
}
public void notifyAllObservers(){
for (Observer observer : observers) {
observer.update(this);
}
}
}
步驟 2
創(chuàng)建 Observer 類
Observer.java
public abstract class Observer {
/*傳遞目標對象自身的引用猎莲,觀察者可自己選擇
從目標對象中拉哪些數(shù)據(jù)*/
public abstract void update(Subject message);
}
步驟 3
創(chuàng)建實體觀察者類
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 和實體觀察者對象
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
驗證輸出
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ū)別:
- 推模型由目標對象決定推送的信息,觀察者不能獲取推送信息之外目標對象的其他信息技即,較為被動著洼。拉模型雖然仍是目標對象主動推送信息,但推送的是整個目標對象的引用而叼,觀察者可以選擇性接收目標對象的信息身笤。
- 推模型一般用于目標對象知道觀察者需要的數(shù)據(jù);而拉模型則用于目標對象不知道觀察者需要的數(shù)據(jù),因此把自身傳遞給觀察者葵陵,由觀察者來取值液荸。
- 推模型會使觀察者對象難以復(fù)用。拉模型下脱篙,update方法的參數(shù)是目標對象本身娇钱,基本上可以適應(yīng)各種情況的需要。
利用Java提供的觀察者實現(xiàn)
Java提供了觀察者模式的實現(xiàn)绊困,有關(guān)類和接口是java.util包的Observable類和Observer接口文搂。
和自己實現(xiàn)對比:
1.不需要自己定義觀察者和目標接口了,JDK幫忙定義了
2.具體的目標實現(xiàn)里面不需要再維護觀察者的注冊信息了秤朗,這個在Java中的Observable類里面已經(jīng)幫忙實現(xiàn)好了煤蹭。
3.觸發(fā)通知的方式有一點變化,要先調(diào)用setChanged方法,這個是Java為了幫助實現(xiàn)更精確的觸發(fā)控制而實現(xiàn)的功能取视。
4.具體觀察者的實現(xiàn)里面硝皂,update方法其實能同時支持推模型和拉模型,這個是Java在定義的時候贫途,就已經(jīng)考慮進去的了吧彪。
實現(xiàn)方法:
1、讓具體Subject實現(xiàn)類繼承Observable目標父類丢早,Observable意為可被觀察的,所以讓具體目標類繼承它。
2怨酝、讓具體觀察者實現(xiàn)類實現(xiàn)Observer接口傀缩,Observer意為觀察者,所以讓具體觀察者實現(xiàn)類實現(xiàn)它农猬。
步驟 1
創(chuàng)建具體目標對象實現(xiàn)類繼承Observable類
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;
//通知觀察者之前必須調(diào)用setChanged()
this.setChanged();
/*通知所有觀察者赡艰,既傳遞目標對象引用給觀察者,
* 也傳遞參數(shù)給觀察者update的第二個參數(shù)*/
this.notifyObservers(Integer.valueOf(state));
/* 重載方法的無參方法notifyObservers()
* 通知所有觀察者斤葱,但只是傳遞目標對象引用給觀察者慷垮,
* 觀察者update的第二個參數(shù)為null
* */
}
}
步驟 2
創(chuàng)建觀察者的具體實現(xiàn)類實現(xiàn)Observer接口
BinaryObserver.java
import java.util.Observable;
import java.util.Observer;
public class BinaryObserver implements Observer {
/**
* Observable o是目標對象傳遞的引用,用于拉模型
* Object arg是目標對象主動推送的信息揍堕,用于推模型
* 如果目標對象使用帶參的notifyObservers方法料身,
* 則即可推也可拉;如果使用無參的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)建測試類
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); //注冊觀察者
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
驗證輸出
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ū)別對待觀察者模式
之前的觀察者模式是目標對象無條件通知所有觀察者對象衩茸,然而有時需要在特定條件下對特定的觀察者進行通知芹血。這是就需要觀察者模式的變形 —— 區(qū)別對待觀察者模式。
具體實現(xiàn)只要修改Subject類的notifyAllObservers方法,對Observer的身份做特定判斷楞慈,然后有條件的推送信息即可幔烛。
8.狀態(tài)模式
在狀態(tài)模式(State Pattern)中,類的行為是基于它的狀態(tài)改變的囊蓝。這種類型的設(shè)計模式屬于行為型模式饿悬。
在狀態(tài)模式中,我們創(chuàng)建表示各種狀態(tài)的對象和一個行為隨著狀態(tài)對象改變而改變的 context 對象聚霜。
介紹
意圖:允許對象在內(nèi)部狀態(tài)發(fā)生改變時改變它的行為狡恬,對象看起來好像修改了它的類。
主要解決:對象的行為依賴于它的狀態(tài)(屬性)俯萎,并且可以根據(jù)它的狀態(tài)改變而改變它的相關(guān)行為傲宜。
何時使用:代碼中包含大量與對象狀態(tài)有關(guān)的條件語句。
如何解決:將各種具體的狀態(tài)類抽象出來夫啊。
關(guān)鍵代碼:通常命令模式的接口中只有一個方法函卒。而狀態(tài)模式的接口中有一個或者多個方法。而且撇眯,狀態(tài)模式的實現(xiàn)類的方法报嵌,一般返回值,或者是改變實例變量的值熊榛。也就是說锚国,狀態(tài)模式一般和對象的狀態(tài)有關(guān)。實現(xiàn)類的方法有不同的功能玄坦,覆蓋接口中的方法血筑。狀態(tài)模式和命令模式一樣绘沉,也可以用于消除 if...else 等條件選擇語句。
應(yīng)用實例:1豺总、打籃球的時候運動員可以有正常狀態(tài)车伞、不正常狀態(tài)和超常狀態(tài)。 2喻喳、曾侯乙編鐘中另玖,'鐘是抽象接口','鐘A'等是具體狀態(tài),'曾侯乙編鐘'是具體環(huán)境(Context)表伦。
優(yōu)點:1谦去、封裝了轉(zhuǎn)換規(guī)則。 2蹦哼、枚舉可能的狀態(tài)鳄哭,在枚舉狀態(tài)之前需要確定狀態(tài)種類。 3翔怎、將所有與某個狀態(tài)有關(guān)的行為放到一個類中窃诉,并且可以方便地增加新的狀態(tài),只需要改變對象狀態(tài)即可改變對象的行為赤套。 4飘痛、允許狀態(tài)轉(zhuǎn)換邏輯與狀態(tài)對象合成一體,而不是某一個巨大的條件語句塊容握。 5宣脉、可以讓多個環(huán)境對象共享一個狀態(tài)對象,從而減少系統(tǒng)中對象的個數(shù)剔氏。
缺點:1塑猖、狀態(tài)模式的使用必然會增加系統(tǒng)類和對象的個數(shù)。 2谈跛、狀態(tài)模式的結(jié)構(gòu)與實現(xiàn)都較為復(fù)雜羊苟,如果使用不當將導(dǎo)致程序結(jié)構(gòu)和代碼的混亂。 3感憾、狀態(tài)模式對"開閉原則"的支持并不太好蜡励,對于可以切換狀態(tài)的狀態(tài)模式,增加新的狀態(tài)類需要修改那些負責(zé)狀態(tài)轉(zhuǎn)換的源代碼阻桅,否則無法切換到新增狀態(tài)凉倚,而且修改某個狀態(tài)類的行為也需修改對應(yīng)類的源代碼。
使用場景:1嫂沉、行為隨狀態(tài)改變而改變的場景稽寒。 2、條件趟章、分支語句的代替者杏糙。
注意事項:在行為受狀態(tài)約束的時候使用狀態(tài)模式慎王,而且狀態(tài)不超過 5 個。
實現(xiàn)
我們將創(chuàng)建一個 State 接口和實現(xiàn)了 State 接口的實體狀態(tài)類搔啊。Context 是一個帶有某個狀態(tài)的類柬祠。
StatePatternDemo北戏,我們的演示類使用 Context 和狀態(tài)對象來演示 Context 在狀態(tài)改變時的行為變化负芋。
步驟 1
創(chuàng)建一個接口。
State.java
public interface State {
public void doAction(Context context);
}
步驟 2
創(chuàng)建實現(xiàn)接口的實體類嗜愈。
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 來查看當狀態(tài) State 改變時的行為變化。
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.空對象模式
在空對象模式(Null Object Pattern)中锨天,一個空對象取代 NULL 對象實例的檢查。Null 對象不是檢查空值剃毒,而是反應(yīng)一個不做任何動作的關(guān)系病袄。這樣的 Null 對象也可以在數(shù)據(jù)不可用的時候提供默認的行為。
在空對象模式中赘阀,我們創(chuàng)建一個指定各種要執(zhí)行的操作的抽象類和擴展該類的實體類益缠,還創(chuàng)建一個未對該類做任何實現(xiàn)的空對象類,該空對象類將無縫地使用在需要檢查空值的地方基公。
實現(xiàn)
我們將創(chuàng)建一個定義操作(在這里幅慌,是客戶的名稱)的 AbstractCustomer 抽象類,和擴展了 AbstractCustomer 類的實體類轰豆。工廠類 CustomerFactory 基于客戶傳遞的名字來返回 RealCustomer 或 NullCustomer 對象胰伍。
NullPatternDemo,我們的演示類使用 CustomerFactory 來演示空對象模式的用法酸休。
步驟 1
創(chuàng)建一個抽象類骂租。
AbstractCustomer.java
public abstract class AbstractCustomer {
protected String name;
public abstract boolean isNil();
public abstract String getName();
}
步驟 2
創(chuàng)建擴展了上述類的實體類。
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 類斑司。
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渗饮,基于客戶傳遞的名字,來獲取 RealCustomer 或 NullCustomer 對象陡厘。
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)中,一個類的行為或其算法可以在運行時更改糙置。這種類型的設(shè)計模式屬于行為型模式云茸。
在策略模式中,我們創(chuàng)建表示各種策略的對象和一個行為隨著策略對象改變而改變的 context 對象谤饭。策略對象改變 context 對象的執(zhí)行算法标捺。
介紹
意圖:定義一系列的算法,把它們一個個封裝起來, 并且使它們可相互替換懊纳。
主要解決:在有多種算法相似的情況下箫锤,使用 if...else 所帶來的復(fù)雜和難以維護插勤。
何時使用:一個系統(tǒng)有許多許多類答毫,而區(qū)分它們的只是他們直接的行為年鸳。
如何解決:將這些算法封裝成一個一個的類绕沈,任意地替換卤妒。
關(guān)鍵代碼:抽象出行為的共性作為一個策略接口驯击,各種策略類實現(xiàn)這個接口硝清。在調(diào)用這個行為的類中通過組合持有這個接口的對象屋谭,通過這個策略接口對象代理具體的行為脚囊。
應(yīng)用實例: 1、諸葛亮的錦囊妙計桐磁,每一個錦囊就是一個策略悔耘。 2、旅行的出游方式我擂,選擇騎自行車衬以、坐汽車,每一種旅行方式都是一個策略校摩。 3看峻、JAVA AWT 中的 LayoutManager。
優(yōu)點: 1秧耗、算法可以自由切換备籽。 2、避免使用多重條件判斷分井。 3车猬、擴展性良好。
缺點: 1尺锚、策略類會增多珠闰。 2、所有策略類都需要對外暴露瘫辩。
使用場景: 1伏嗜、如果在一個系統(tǒng)里面有許多類,它們之間的區(qū)別僅在于它們的行為伐厌,那么使用策略模式可以動態(tài)地讓一個對象在許多行為中選擇一種行為承绸。 2、一個系統(tǒng)需要動態(tài)地在幾種算法中選擇一種挣轨。 3军熏、如果一個對象有很多的行為,如果不用恰當?shù)哪J骄戆纾@些行為就只好使用多重的條件選擇語句來實現(xiàn)荡澎。
注意事項:如果一個系統(tǒng)的策略多于四個均践,就需要考慮使用混合模式,解決策略類膨脹的問題摩幔。
實現(xiàn)
我們將創(chuàng)建一個定義活動的 Strategy 接口和實現(xiàn)了 Strategy 接口的實體策略類彤委。Context 是一個使用了某種策略的類。
StrategyPatternDemo或衡,我們的演示類使用 Context 和策略對象來演示 Context 在它所配置或使用的策略改變時的行為變化焦影。
步驟 1
創(chuàng)建一個接口。
Strategy.java
public interface Strategy {
public int doOperation(int num1, int num2);
}
步驟 2
創(chuàng)建實現(xiàn)接口的實體類薇宠。
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 類偷办。
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);
//用策略接口對象代理具體實現(xiàn)
}
}
步驟 4
使用 Context 來查看當它改變策略 Strategy 時的行為變化。
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
驗證輸出澄港。
10 + 5 = 15
10 - 5 = 5
10 * 5 = 50
11.模板模式
在模板模式(Template Pattern)中,一個抽象類公開定義了執(zhí)行它的方法的方式/模板柄沮。它的子類可以按需要重寫方法實現(xiàn)回梧,但調(diào)用將以抽象類中定義的方式進行。這種類型的設(shè)計模式屬于行為型模式祖搓。
介紹
意圖:定義一個操作中的算法的骨架狱意,而將一些步驟延遲到子類中。模板方法使得子類可以不改變一個算法的結(jié)構(gòu)即可重定義該算法的某些特定步驟拯欧。
主要解決:一些方法通用详囤,卻在每一個子類都重新寫了這一方法。
何時使用:有一些通用的方法镐作。
如何解決:將這些通用算法抽象出來藏姐。
關(guān)鍵代碼:準備一個抽象類,將部分邏輯以具體方法的形式實現(xiàn)该贾,然后聲明一些抽象方法交由子類實現(xiàn)剩余邏輯羔杨,用鉤子方法給予子類更大的靈活性。最后將方法匯總為一個final的模板方法杨蛋。
應(yīng)用實例: 1兜材、在造房子的時候,地基逞力、走線曙寡、水管都一樣,只有在建筑的后期才有加壁櫥加?xùn)艡诘炔町悺?2寇荧、西游記里面菩薩定好的 81 難举庶,這就是一個頂層的邏輯骨架。 3砚亭、spring 中對 Hibernate 的支持灯变,將一些已經(jīng)定好的方法封裝起來殴玛,比如開啟事務(wù)、獲取 Session添祸、關(guān)閉 Session 等滚粟,程序員不重復(fù)寫那些已經(jīng)規(guī)范好的代碼,直接丟一個實體就可以保存刃泌。
優(yōu)點: 1凡壤、封裝性好,封裝不變部分耙替,擴展可變部分亚侠。 2、復(fù)用性好俗扇,提取公共代碼硝烂,便于維護。 3铜幽、屏蔽細節(jié)滞谢,行為由父類控制,子類實現(xiàn)除抛。
缺點:1狮杨、每一個不同的實現(xiàn)都需要一個子類來實現(xiàn),導(dǎo)致類的個數(shù)增加到忽,使得系統(tǒng)更加龐大橄教。2、Java的單繼承使得繼承了其他父類子類難以實現(xiàn)對模板基類的繼承喘漏。
使用場景: 1护蝶、有多個子類共有的方法,且邏輯相同陷遮。 2滓走、重要的、復(fù)雜的方法帽馋,可以考慮作為模板方法搅方。
注意事項:為防止惡意操作,一般模板方法都加上 final 關(guān)鍵詞绽族。在模板方法內(nèi)的步驟中姨涡,通用的方法在抽象基類里提供實現(xiàn),特定的方法定義為抽象方法吧慢,延遲到子類中實現(xiàn)涛漂。
實現(xiàn)
我們將創(chuàng)建一個定義操作的 Game 抽象類,其中,模板方法設(shè)置為 final匈仗,這樣它就不會被重寫瓢剿。Cricket 和 Football 是擴展了 Game 的實體類,它們重寫了抽象類的方法悠轩。
TemplatePatternDemo间狂,我們的演示類使用 Game 來演示模板模式的用法。
步驟 1
創(chuàng)建一個抽象類火架,它的模板方法被設(shè)置為 final鉴象。
Game.java
public abstract class Game {
abstract void initialize();
//如果子類通用,可以在抽象基類實現(xiàn)何鸡,不必定義為抽象方法
abstract void startPlay();
abstract void endPlay();
//模板,定義為final纺弊,防止被子類重寫
public final void play(){
//初始化游戲
initialize();
//開始游戲
startPlay();
//結(jié)束游戲
endPlay();
}
}
步驟 2
創(chuàng)建擴展了上述類的實體類。
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();
}
}
步驟 4
驗證輸出淆游。
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),可以添加一個鉤子方法:比如在泡飲品的模板方法中洞翩,燒水——倒入飲品沖劑——加水——加調(diào)料稽犁。如果有的飲品不想要調(diào)料,可以在模板方法中把加調(diào)料放在if語句中骚亿,if的條件是一個返回值為boolean類型的方法,比如isCustomerWantsCondiments(),提供一個空的或者默認返回true的實現(xiàn)熊赖,稱為鉤子方法来屠。子類可以根據(jù)需要重寫該鉤子方法選擇要不要加調(diào)料。
12.訪問者模式
在訪問者模式(Visitor Pattern)中震鹉,我們使用了一個訪問者類俱笛,它改變了元素類的執(zhí)行算法。通過這種方式传趾,元素的執(zhí)行算法可以隨著訪問者改變而改變迎膜。這種類型的設(shè)計模式屬于行為型模式。根據(jù)模式浆兰,元素對象已接受訪問者對象磕仅,這樣訪問者對象就可以處理元素對象上的操作。
介紹
意圖:主要將數(shù)據(jù)結(jié)構(gòu)與數(shù)據(jù)操作分離簸呈。
主要解決:穩(wěn)定的數(shù)據(jù)結(jié)構(gòu)和易變的操作耦合問題榕订。
何時使用:需要對一個對象結(jié)構(gòu)中的對象進行很多不同的并且不相關(guān)的操作,而需要避免讓這些操作"污染"這些對象的類蜕便,使用訪問者模式將這些封裝到類中劫恒。
如何解決:在被訪問的類里面加一個對外提供接待訪問者的接口。
關(guān)鍵代碼:在數(shù)據(jù)基礎(chǔ)類里面有一個方法接受訪問者,將自身引用傳入訪問者两嘴。
應(yīng)用實例:您在朋友家做客丛楚,您是訪問者,朋友接受您的訪問憔辫,您通過朋友的描述趣些,然后對朋友的描述做出一個判斷,這就是訪問者模式螺垢。
優(yōu)點:1喧务、符合單一職責(zé)原則。 2枉圃、優(yōu)秀的擴展性功茴。 3、靈活性孽亲。
缺點:1坎穿、具體元素對訪問者公布細節(jié),違反了迪米特原則返劲。 2玲昧、具體元素變更比較困難。 3篮绿、違反了依賴倒置原則孵延,依賴了具體類,沒有依賴抽象亲配。
使用場景:1尘应、對象結(jié)構(gòu)中對象對應(yīng)的類很少改變,但經(jīng)常需要在此對象結(jié)構(gòu)上定義新的操作吼虎。 2犬钢、需要對一個對象結(jié)構(gòu)中的對象進行很多不同的并且不相關(guān)的操作,而需要避免讓這些操作"污染"這些對象的類思灰,也不希望在增加新操作時修改這些類玷犹。
注意事項:訪問者可以對功能進行統(tǒng)一,可以做報表洒疚、UI歹颓、攔截器與過濾器。
實現(xiàn)
我們將創(chuàng)建一個定義接受操作的 ComputerPart 接口拳亿。Keyboard晴股、Mouse、Monitor 和 Computer 是實現(xiàn)了 ComputerPart 接口的實體類肺魁。我們將定義另一個接口 ComputerPartVisitor电湘,它定義了訪問者類的操作。Computer 使用實體訪問者來執(zhí)行相應(yīng)的動作。
VisitorPatternDemo寂呛,我們的演示類使用 Computer怎诫、ComputerPartVisitor 類來演示訪問者模式的用法。
步驟 1
定義一個表示元素的接口贷痪。
ComputerPart.java
public interface ComputerPart {
public void accept(ComputerPartVisitor computerPartVisitor);
}
步驟 2
創(chuàng)建擴展了上述類的實體類幻妓。
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
定義一個表示訪問者的接口。
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)建實現(xià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 來顯示 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.