本系列文章共分爲五篇:
設計模式的分類與原則
創建型模式
結構型模式
行爲型模式
設計模式的區別
上篇文件介紹了結構型模式,本篇將介紹行爲型模式。行爲型模式的主要關注點是“描述類或對象之間怎樣通信、協作共同完成任務,以及怎樣分配職責”。
一、模板模式
1.1 模板模式定義
定義一個操作中的算法骨架,而將算法的一些步驟延遲到子類中,使得子類可以不改變該算法結構的情況下重定義該算法的某些特定步驟。
1.2 模板模式特點
優點:
1>它封裝了不變部分,擴展可變部分。它把認爲是不變部分的算法封裝到父類中實現,而把可變部分算法由子類繼承實現,便於子類繼續擴展。
2>它在父類中提取了公共的部分代碼,便於代碼複用。
3>部分方法是由子類實現的,因此子類可以通過擴展方式增加相應的功能,符合開閉原則。
缺點:
1>對每個不同的實現都需要定義一個子類,這會導致類的個數增加,系統更加龐大,設計也更加抽象。
2>父類中的抽象方法由子類實現,子類執行的結果會影響父類的結果,這導致一種反向的控制結構,它提高了代碼閱讀的難度。
1.3 模板模式主要角色
抽象類:負責給出一個算法的輪廓和骨架。它由一個模板方法和若干個基本方法構成。這些方法的定義如下。
模板方法:定義了算法的骨架,按某種順序調用其包含的基本方法。
基本方法:是整個算法中的一個步驟,包含以下幾種類型。
1>抽象方法:在抽象類中申明,由具體子類實現。
2>具體方法:在抽象類中已經實現,在具體子類中可以繼承或重寫它。
3>鉤子方法:在抽象類中已經實現,包括用於判斷的邏輯方法和需要子類重寫的空方法兩種。
具體子類:實現抽象類中所定義的抽象方法和鉤子方法,它們是一個頂級邏輯的一個組成步驟。
1.4 模板模式實現方式
此處,以兩個人張三和李四上班爲例,主要涉及三個部分:起牀、喫早飯、乘坐交通工具去上班,起牀是公共方法,喫早飯可以推遲到之類,乘什麼樣的交通工具可以用鉤子方法來判別。示例代碼如下:
/*抽象類*/
abstract class Worker {
public void WorkerWay()
{
getUp();
haveBreakfast();
if(haveCar())
{
takeBus();
}else{
drive();
}
}
/*具體方法*/
public void getUp()
{
System.out.println("起牀");
}
public void takeBus(){
System.out.println("坐公交上班");
}
public void drive(){
System.out.println("開車上班");
}
/*鉤子方法*/
public boolean haveCar()
{
return true;
}
/*抽象方法*/
public abstract void haveBreakfast();
}
/*具體子類:張三*/
public class Zhangsan extends Worker{
@Override
public void haveBreakfast() {
System.out.println("喫三明治、喝牛奶");
}
public boolean haveCar()
{
return false;
}
}
/*具體子類:李四*/
public class Lisi extends Worker{
@Override
public void haveBreakfast() {
System.out.println("喫包子、喝豆漿");
}
public boolean haveCar()
{
return true;
}
}
/*測試類*/
public class TemplateTest {
public static void main(String[] args) {
Zhangsan zhangsan =new Zhangsan();
System.out.println("張三的上班方式:");
zhangsan.WorkerWay();
Lisi lisi =new Lisi();
System.out.println("李四的上班方式:");
lisi.WorkerWay();
}
}
結果如下:
張三的上班方式:
起牀
喫三明治、喝牛奶
開車上班
李四的上班方式:
起牀
喫包子、喝豆漿
坐公交上班
1.5 模板模式應用場景
模板模式通常適用的場景:
1)算法的整體步驟很固定,但其中個別部分易變時,這時候可以使用模板方法模式,將容易變的部分抽象出來,供子類實現。
2)當多個子類存在公共的行爲時,可以將其提取出來並集中到一個公共父類中以避免代碼重複。首先,要識別現有代碼中的不同之處,並且將不同之處分離爲新的操作。最後,用一個調用這些新的操作的模板方法來替換這些不同的代碼。
3)當需要控制子類的擴展時,模板方法只在特定點調用鉤子操作,這樣就只允許在這些點進行擴展。
二、策略模式
2.1 策略模式定義
該模式定義了一系列算法,並將每個算法封裝起來,使它們可以相互替換,且算法的變化不會影響使用算法的客戶。
2.2 策略模式特點
優點:
1>多重條件語句不易維護,而使用策略模式可以避免使用多重條件語句。
策略模式提供了一系列的可供重用的算法族,恰當使用繼承可以把算法族的公共代碼轉移到父類裏面,從而避免重複的代碼。
2>可以提供相同行爲的不同實現,客戶可以根據不同時間或空間要求選擇不同的。
3>提供了對開閉原則的完美支持,可以在不修改原代碼的情況下,靈活增加新算法。
4>把算法的使用放到環境類中,而算法的實現移到具體策略類中,實現了二者的分離。
缺點:
1>客戶端必須理解所有策略算法的區別,以便適時選擇恰當的算法類。
2>策略模式造成很多的策略類。
2.3 策略模式主要角色
抽象策略類:定義了一個公共接口,各種不同的算法以不同的方式實現這個接口,環境角色使用這個接口調用不同的算法,一般使用接口或抽象類實現。
具體策略類:實現了抽象策略定義的接口,提供具體的算法實現。
環境類:持有一個策略類的引用,最終給客戶端調用。
2.4 策略模式實現方式
此處,以小明做飯爲例,當他想吃麪時,做了一份臊子面;想喫飯時,做了一份炒飯。示例代碼如下:
/*抽象策略*/
public interface Cook {
public void cooking();
}
/*具體策略:做面*/
public class NoodleCook implements Cook{
public void cooking() {
System.out.println("做了一份臊子面");
}
}
/*具體策略:做飯*/
public class RiceCook implements Cook{
public void cooking() {
System.out.println("做了一份炒飯");
}
}
/*環境類*/
public class Context {
private Cook cook;
public Cook getCook()
{
return cook;
}
public void setCook(Cook cook)
{
this.cook=cook;
}
public void cooking()
{
cook.cooking();
}
}
/*測試類*/
public class StrategyTest {
public static void main(String[] args) {
Context context = new Context();
System.out.println("小明要吃麪");
Cook cook = new NoodleCook();
context.setCook(cook);
context.cooking();
System.out.println("小明要喫飯");
cook = new RiceCook();
context.setCook(cook);
context.cooking();
}
}
結果如下:
小明要吃麪
做了一份臊子面
小明要喫飯
做了一份炒飯
2.5 策略模式應用場景
模板模式通常適用的場景:
1)一個系統需要動態地在幾種算法中選擇一種時,可將每個算法封裝到策略類中。
2)一個類定義了多種行爲,並且這些行爲在這個類的操作中以多個條件語句的形式出現,可將每個條件分支移入它們各自的策略類中以代替這些條件語句。
3)系統中各算法彼此完全獨立,且要求對客戶隱藏具體算法的實現細節時。
4)系統要求使用算法的客戶不應該知道其操作的數據時,可使用策略模式來隱藏與算法相關的數據結構。
5)多個類只區別在表現行爲不同,可以使用策略模式,在運行時動態選擇具體要執行的行爲。
三、狀態模式
3.1 狀態模式定義
對有狀態的對象,把複雜的“判斷邏輯”提取到不同的狀態對象中,允許狀態對象在其內部狀態發生改變時改變其行爲。
3.2 狀態模式特點
優點:
1>狀態模式將與特定狀態相關的行爲局部化到一個狀態中,並且將不同狀態的行爲分割開來,滿足“單一職責原則”。
2>減少對象間的相互依賴。將不同的狀態引入獨立的對象中會使得狀態轉換變得更加明確,且減少對象間的相互依賴。
3>有利於程序的擴展。通過定義新的子類很容易地增加新的狀態和轉換。
缺點:
1>狀態模式的使用必然會增加系統的類與對象的個數。
2>狀態模式的結構與實現都較爲複雜,如果使用不當會導致程序結構和代碼的混亂。
3.3 狀態模式主要角色
環境角色:也稱爲上下文,它定義了客戶感興趣的接口,維護一個當前狀態,並將與狀態相關的操作委託給當前狀態對象來處理。
抽象狀態角色:定義一個接口,用以封裝環境對象中的特定狀態所對應的行爲。
具體狀態角色:實現抽象狀態所對應的行爲。
3.4 狀態模式實現方式
此處,以檔位切換爲例,某個開關有3個檔位,且遵從高 --> 中 --> 低 --> 高的切換方式。示例代碼如下:
/*抽象狀態*/
public abstract class Switch {
public abstract void setPre(Context context);
public abstract void setNext(Context context);
}
/*具體狀態1:低檔位*/
public class LowSwitch extends Switch{
@Override
public void setPre(Context context) {
System.out.println("當前是低檔位,向前切換爲中檔位");
context.setSwitch(new MiddSwitch());
}
@Override
public void setNext(Context context) {
System.out.println("當前是低檔位,向後切換爲高檔位");
context.setSwitch(new HighSwitch());
}
@Override
public String toString() {
return "低檔位";
}
}
/*具體狀態2:中檔位*/
public class MiddSwitch extends Switch{
@Override
public void setPre(Context context) {
System.out.println("當前是中檔位,向前切換爲高檔位");
context.setSwitch(new HighSwitch());
}
@Override
public void setNext(Context context) {
System.out.println("當前是中檔位,向後切換爲低檔位");
context.setSwitch(new LowSwitch());
}
}
/*具體狀態3:高檔位*/
public class HighSwitch extends Switch{
@Override
public void setPre(Context context) {
System.out.println("當前是高檔位,向前切換爲低檔位");
context.setSwitch(new LowSwitch());
}
@Override
public void setNext(Context context) {
System.out.println("當前是高檔位,,向後切換爲中檔位");
context.setSwitch(new MiddSwitch());
}
}
/*環境類*/
public class Context {
private Switch switch1;
//定義環境類的初始狀態
public Context()
{
this.switch1 = new LowSwitch();
}
//設置檔位
public void setSwitch(Switch sw)
{
switch1 = sw;
}
//讀取檔位
public Switch getSwitch()
{
System.out.println("當前檔位是:"+switch1.toString());
return(switch1);
}
//往前設置一個檔位
public void setPre()
{
switch1.setPre(this);
}
//往後設置一個檔位
public void setNext()
{
switch1.setNext(this);
}
}
/*測試類*/
public class SwtichTest {
public static void main(String[] args)
{
Context context=new Context(); //創建環境
context.getSwitch(); //處理請求
context.setPre();
context.setPre();
context.setNext();
}
}
結果如下:
當前檔位是:低檔位
當前是低檔位,向前切換爲中檔位
當前是中檔位,向前切換爲高檔位
當前是高檔位,,向後切換爲中檔位
3.5 狀態模式應用場景
狀態模式通常適用的場景:
1)當一個對象的行爲取決於它的狀態,並且它必須在運行時根據狀態改變它的行爲時,就可以考慮使用狀態模式。
2)一個操作中含有龐大的分支結構,並且這些分支決定於對象的狀態時。
四、觀察者模式
4.1 觀察者模式定義
指多個對象間存在一對多的依賴關係,當一個對象的狀態發生改變時,所有依賴於它的對象都得到通知並被自動更新。這種模式有時又稱作發佈-訂閱模式、模型-視圖模式,它是對象行爲型模式。
4.2 觀察者模式特點
優點:
1>降低了目標與觀察者之間的耦合關係,兩者之間是抽象耦合關係。
2>目標與觀察者之間建立了一套觸發機制。
缺點:
1>目標與觀察者之間的依賴關係並沒有完全解除,而且有可能出現循環引用。
2>當觀察者對象很多時,通知的發佈會花費很多時間,影響程序的效率。
4.3 觀察者模式主要角色
抽象主題角色:也叫抽象目標類,它提供了一個用於保存觀察者對象的聚集類和增加、刪除觀察者對象的方法,以及通知所有觀察者的抽象方法。
具體主題角色:也叫具體目標類,它實現抽象目標中的通知方法,當具體主題的內部狀態發生改變時,通知所有註冊過的觀察者對象。
抽象觀察者角色:它是一個抽象類或接口,它包含了一個更新自己的抽象方法,當接到具體主題的更改通知時被調用。
具體觀察者角色:實現抽象觀察者中定義的抽象方法,以便在得到目標的更改通知時更新自身的狀態。
4.4 觀察者模式實現方式
此處,以小明追小說爲例,有兩本小說《劍來》和《英雄志》都未完結,他關注了這兩部小說,當則兩部小說更新時,他都會收到對應的消息。代碼示例如下:
/*抽象主題:小說*/
public abstract class Story {
protected List<Observer> observers=new ArrayList<Observer>();
//增加觀察者方法
public void add(Observer observer)
{
observers.add(observer);
}
//刪除觀察者方法
public void remove(Observer observer)
{
observers.remove(observer);
}
public abstract void notifyObserver(int chapterNum); //通知觀察者方法
}
/*抽象主題1:劍來*/
public class Story1 extends Story{
private String name = "jianlai";
public void notifyObserver(int chapterNum)
{
for(Object obs:observers)
{
((Observer)obs).response(this.name,chapterNum);
}
}
}
/*抽象主題2:英雄志*/
public class Story2 extends Story{
private String name = "yingxiongzhi";
public void notifyObserver(int chapterNum)
{
for(Object obs:observers)
{
((Observer)obs).response(this.name,chapterNum);
}
}
}
/*抽象觀察者*/
public interface Observer {
void response(String storyName,int number);
}
/*具體觀察者:小明*/
public class ObserverXiaoming implements Observer{
private String name = "小明";
public void response(String storyName,int chapterNum) {
if("jianlai".equals(storyName)){
System.out.println("《劍來》更新了"+chapterNum+"章,"+this.name+"等養肥了在看");
}else{
System.out.println("《英雄志》更新了"+chapterNum+"章,"+this.name+"立馬去看");
}
}
}
/*測試類*/
public class ObserverTest {
public static void main(String[] args)
{
Story story1=new Story1();
Story story2=new Story2();
Observer observer=new ObserverXiaoming();
story1.add(observer);
story2.add(observer);
story1.notifyObserver(20);
story2.notifyObserver(10);
}
}
示例結果如下:
《劍來》更新了20章,小明等養肥了在看
《英雄志》更新了10章,小明立馬去看
4.5 觀察者模式應用場景
觀察者模式通常適用的場景:
1)對象間存在一對多關係,一個對象的狀態發生改變會影響其他對象。
2)當一個抽象模型有兩個方面,其中一個方面依賴於另一方面時,可將這二者封裝在獨立的對象中以使它們可以各自獨立地改變和複用。
五、備忘錄模式
5.1 備忘錄模式定義
在不破壞封裝性的前提下,捕獲一個對象的內部狀態,並在該對象之外保存這個狀態,以便以後當需要時能將該對象恢復到原先保存的狀態。該模式又叫快照模式。
5.2 備忘錄模式特點
優點:
1>提供了一種可以恢復狀態的機制。當用戶需要時能夠比較方便地將數據恢復到某個歷史的狀態。
2>實現了內部狀態的封裝。除了創建它的發起人之外,其他對象都不能夠訪問這些狀態信息。
3>簡化了發起人類。發起人不需要管理和保存其內部狀態的各個備份,所有狀態信息都保存在備忘錄中,並由管理者進行管理,這符合單一職責原則。
缺點:
1>資源消耗大。如果要保存的內部狀態信息過多或者特別頻繁,將會佔用比較大的內存資源。
5.3 備忘錄模式主要角色
發起人角色:記錄當前時刻的內部狀態信息,提供創建備忘錄和恢復備忘錄數據的功能,實現其他業務功能,它可以訪問備忘錄裏的所有信息。
備忘錄角色:負責存儲發起人的內部狀態,在需要的時候提供這些內部狀態給發起人。
管理者角色:對備忘錄進行管理,提供保存與獲取備忘錄的功能,但其不能對備忘錄的內容進行訪問與修改。
5.4 備忘錄模式實現方式
此處,以小亮週末做的一些事情爲例,如:上午健身、下午和朋友聚餐、晚上看小說爲例,用備忘錄來記住他週末一天做的事,示例代碼如下:
/*備忘錄*/
public class Memento {
private String state;
public Memento(String state) {
this.state = state;
}
public String getState() {
return state;
}
}
/*發起人:小亮*/
public class OriginatorXiaoliang {
private String state;
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
public Memento saveStateToMemento(){
return new Memento(state);
}
public void getStateFromMemento(Memento memento){
state = memento.getState();
}
}
/*管理者*/
public class CareTaker {
private List<Memento> mementoList = new ArrayList<Memento>();
public void add(Memento memento){
mementoList.add(memento);
}
public Memento get(int index){
return mementoList.get(index);
}
}
/*測試類*/
public class MementoTest {
public static void main(String[] args) {
OriginatorXiaoliang xiaoliang = new OriginatorXiaoliang();
CareTaker careTaker = new CareTaker();
xiaoliang.setState("健身");
careTaker.add(xiaoliang.saveStateToMemento());
xiaoliang.setState("和朋友聚會");
careTaker.add(xiaoliang.saveStateToMemento());
xiaoliang.setState("看小說");
System.out.println("小亮晚上做的事: " + xiaoliang.getState());
xiaoliang.getStateFromMemento(careTaker.get(0));
System.out.println("小亮上午做的事: " + xiaoliang.getState());
xiaoliang.getStateFromMemento(careTaker.get(1));
System.out.println("小亮下午做的事: " + xiaoliang.getState());
}
}
測試結果如下:
小亮晚上做的事: 看小說
小亮上午做的事: 健身
小亮下午做的事: 和朋友聚會
5.5 備忘錄模式應用場景
備忘錄模式通常適用的場景:
1)需要保存與恢復數據的場景,如玩遊戲時的中間結果的存檔功能。
1)需要提供一個可回滾操作的場景,如 Word、記事本、Photoshop,Eclipse 等軟件在編輯時按 Ctrl+Z 組合鍵,還有數據庫中事務操作。
六、中介者模式
6.1 中介者模式定義
定義一箇中介對象來封裝一系列對象之間的交互,使原有對象之間的耦合鬆散,且可以獨立地改變它們之間的交互。
6.2 中介者模式特點
優點:
1>降低了對象之間的耦合性,使得對象易於獨立地被複用。
2>將對象間的一對多關聯轉變爲一對一的關聯,提高系統的靈活性,使得系統易於維護和擴展。
缺點:
1>當同事類太多時,中介者的職責將很大,它會變得複雜而龐大,以至於系統難以維護。
6.3 中介者模式主要角色
抽象中介者角色:它是中介者的接口,提供了同事對象註冊與轉發同事對象信息的抽象方法。
具體中介者角色:實現中介者接口,定義一個 List 來管理同事對象,協調各個同事角色之間的交互關係,因此它依賴於同事角色。
抽象同事類角色:定義同事類的接口,保存中介者對象,提供同事對象交互的抽象方法,實現所有相互影響的同事類的公共功能。
具體同事類角色:是抽象同事類的實現者,當需要與其他同事對象交互時,由中介者對象負責後續的交互。
6.4 中介者模式實現方式
此處以在班級裏收作業爲例,一個班級裏有不同的課代表,他們會發起收作業的動作,同時,他們又要交作業,這恰好符合中介者模式。示例代碼如下:
/*抽象中介者*/
public abstract class Mediator {
public abstract void register(Student stu); //將stu加入通知列表
public abstract void relay(Student stu); //轉發消息
}
/*具體中介者*/
public class RealMediator extends Mediator{
private List<Student> stus=new ArrayList<Student>();
public void register(Student stu)
{
if(!stus.contains(stu))
{
stus.add(stu);
stu.setMedium(this);
}
}
public void relay(Student stu1)
{
for(Student stu:stus)
{
if(!stu.equals(stu1))
{
((Student)stu).receive();
}
}
}
}
/*抽象同事類*/
public abstract class Student {
protected Mediator mediator;
public void setMedium(Mediator mediator)
{
this.mediator=mediator;
}
public abstract void receive();
public abstract void send();
}
/*具體同事類:物理課代表*/
public class PhysicsClassrepresentative extends Student{
@Override
public void receive() {
System.out.println("物理課代表說:收到,馬上交化學作業");
}
@Override
public void send() {
System.out.println("物理課代表說:收物理作業了");
mediator.relay(this); //請中介者轉發
}
}
/*具體同事類:化學課代表*/
public class ChemistryClassrepresentative extends Student{
@Override
public void receive() {
System.out.println("化學課代表說:收到,馬上交物理作業");
}
@Override
public void send() {
System.out.println("化學課代表說:收化學作業了");
mediator.relay(this); //請中介者轉發
}
}
/*測試類*/
public class MediatorTest {
public static void main(String[] args) {
Mediator md=new RealMediator();
Student stu1,stu2;
stu1=new PhysicsClassrepresentative();
stu2=new ChemistryClassrepresentative();
md.register(stu1);
md.register(stu2);
stu1.send();
System.out.println("-------------");
stu2.send();
}
}
結果如下:
物理課代表說:收物理作業了
化學課代表說:收到,馬上交物理作業
化學課代表說:收化學作業了
物理課代表說:收到,馬上交化學作業
6.5 中介者模式應用場景
中介者模式通常適用的場景:
1)當對象之間存在複雜的網狀結構關係而導致依賴關係混亂且難以複用時。
2)當想創建一個運行於多個類之間的對象,又不想生成新的子類時。
七、迭代器模式
7.1 迭代器模式定義
提供一個對象來順序訪問聚合對象中的一系列數據,而不暴露聚合對象的內部表示。
7.2 迭代器模式特點
優點:
1>訪問一個聚合對象的內容而無須暴露它的內部表示。
2>遍歷任務交由迭代器完成,這簡化了聚合類。
3>它支持以不同方式遍歷一個聚合,甚至可以自定義迭代器的子類以支持新的遍歷。
4>增加新的聚合類和迭代器類都很方便,無須修改原有代碼。
5>封裝性良好,爲遍歷不同的聚合結構提供一個統一的接口。
缺點:
1>增加了類的個數,這在一定程度上增加了系統的複雜性。
7.3 迭代器模式主要角色
抽象聚合角色:定義存儲、添加、刪除聚合對象以及創建迭代器對象的接口。
具體聚合角色:實現抽象聚合類,返回一個具體迭代器的實例。
抽象迭代器角色:定義訪問和遍歷聚合元素的接口,通常包含 hasNext()、first()、next() 等方法。
具體迭代器角色:實現抽象迭代器接口中所定義的方法,完成對聚合對象的遍歷,記錄遍歷的當前位置。
7.4 迭代器模式實現方式
此處以中國知名的手機品牌爲例,這些品牌有華爲、小米、OPPO、VIVO、錘子等。示例代碼如下:
/*抽象聚合*/
public interface Aggregate {
public void add(Phone phone);
public void remove(Phone phone);
public Iterator getIterator();
}
/*具體聚合*/
public class ConcreteAggregate implements Aggregate{
private List<Phone> phoneList=new ArrayList<Phone>();
public void add(Phone phone)
{
phoneList.add(phone);
}
public void remove(Phone phone)
{
phoneList.remove(phone);
}
public Iterator getIterator()
{
return(new ConcreteIterator(phoneList));
}
}
/*抽象迭代器*/
public interface Iterator {
Phone first();
Phone next();
boolean hasNext();
}
/*具體迭代器*/
public class ConcreteIterator implements Iterator
{
private List<Phone> list=null;
private int index=-1;
public ConcreteIterator(List<Phone> list)
{
this.list=list;
}
public boolean hasNext()
{
if(index<list.size()-1)
{
return true;
}
else
{
return false;
}
}
public Phone first()
{
index=0;
Phone phone=list.get(index);;
return phone;
}
public Phone next()
{
Phone phone=null;
if(this.hasNext())
{
phone=list.get(++index);
}
return phone;
}
}
/*實體類:手機*/
public class Phone {
private String name;
public Phone(String name){
this.name = name;
}
public String getName(){
return this.name;
}
}
/*測試類*/
public class IteratorTest {
public static void main(String[] args) {
Aggregate ag=new ConcreteAggregate();
ag.add(new Phone("華爲"));
ag.add(new Phone("小米"));
ag.add(new Phone("OPPO"));
ag.add(new Phone("VIVO"));
ag.add(new Phone("錘子"));
System.out.print("聚合的手機品牌有:\n");
Iterator it=ag.getIterator();
while(it.hasNext())
{
Phone phone=it.next();
System.out.print(phone.getName()+"\n");
}
Phone firstPhone=it.first();
System.out.println("\n第一是:"+firstPhone.getName());
}
}
測試結果如下:
聚合的手機品牌有:
華爲
小米
OPPO
VIVO
錘子
第一是:華爲
7.5 迭代器模式應用場景
迭代器模式通常適用的場景:
1)當需要爲聚合對象提供多種遍歷方式時。
2)當需要爲遍歷不同的聚合結構提供一個統一的接口時。
3)當訪問一個聚合對象的內容而無須暴露其內部細節的表示時。
八、解釋器模式
8.1 解釋器模式定義
給分析對象定義一個語言,並定義該語言的文法表示,再設計一個解析器來解釋語言中的句子。
8.2 解釋器模式特點
優點:
1>擴展性好。由於在解釋器模式中使用類來表示語言的文法規則,因此可以通過繼承等機制來改變或擴展文法。
2>容易實現。在語法樹中的每個表達式節點類都是相似的,所以實現其文法較爲容易。
缺點:
1>執行效率較低。解釋器模式中通常使用大量的循環和遞歸調用,當要解釋的句子較複雜時,其運行速度很慢,且代碼的調試過程也比較麻煩。
2>會引起類膨脹。解釋器模式中的每條規則至少需要定義一個類,當包含的文法規則很多時,類的個數將急劇增加,導致系統難以管理與維護。
3>可應用的場景比較少。在軟件開發中,需要定義語言文法的應用實例非常少,所以這種模式很少被使用到。
8.3 解釋器模式主要角色
抽象表達式角色:定義解釋器的接口,約定解釋器的解釋操作,主要包含解釋方法 interpret()。
終結符表達式角色:是抽象表達式的子類,用來實現文法中與終結符相關的操作,文法中的每一個終結符都有一個具體終結表達式與之相對應。
非終結符表達式角色:也是抽象表達式的子類,用來實現文法中與非終結符相關的操作,文法中的每條規則都對應於一個非終結符表達式。
環境角色:通常包含各個解釋器需要的數據或是公共的功能,一般用來傳遞被所有解釋器共享的數據,後面的解釋器可以從這裏獲取這些值。
客戶端:主要任務是將需要分析的句子或表達式轉換成使用解釋器對象描述的抽象語法樹,然後調用解釋器的解釋方法,當然也可以通過環境角色間接訪問解釋器的解釋方法。
8.4 解釋器模式實現方式
此處以員工是否需要參加新人培訓爲例,在無錫和北京兩地工作的3年以下的員工需要參加,其他的則不需要。代碼示例如下:
/*抽象表達式*/
public interface Expression {
public boolean interpret(String info);
}
/*終結表達式*/
public class TerminalExpression implements Expression{
private Set<String> set= new HashSet<String>();
public TerminalExpression(String[] data)
{
for(int i=0;i<data.length;i++)set.add(data[i]);
}
public boolean interpret(String info)
{
if(set.contains(info))
{
return true;
}
return false;
}
}
/*非終結符表達式*/
public class AndExpression implements Expression{
private Expression city=null;
private Expression person=null;
public AndExpression(Expression city,Expression person)
{
this.city=city;
this.person=person;
}
public boolean interpret(String info)
{
String s[]=info.split(",");
return city.interpret(s[0])&&person.interpret(s[1]);
}
}
/*環境*/
public class Context {
private String[] citys={"無錫","北京"};
private String[] persons={"1年","2年","3年"};
private Expression cityPerson;
public Context()
{
Expression city=new TerminalExpression(citys);
Expression person=new TerminalExpression(persons);
cityPerson=new AndExpression(city,person);
}
public void train(String info)
{
boolean ok=cityPerson.interpret(info);
if(ok) System.out.println("您入職年份較少,需要參加新人培訓");
else System.out.println("您入職年份較多或工作地不在無錫、北京,不用再參加新人培訓");
}
}
/*測試類*/
public class InterpreterTest {
public static void main(String[] args) {
Context personnel=new Context();
personnel.train("無錫,2年");
personnel.train("北京,5年");
personnel.train("無錫,3年");
personnel.train("廣州,2年");
personnel.train("北京,2年");
}
}
測試結果如下:
您入職年份較少,需要參加新人培訓
您入職年份較多或工作地不在無錫、北京,不用再參加新人培訓
您入職年份較少,需要參加新人培訓
您入職年份較多或工作地不在無錫、北京,不用再參加新人培訓
您入職年份較少,需要參加新人培訓
8.5 解釋器模式應用場景
解釋器模式通常適用的場景:
1)當語言的文法較爲簡單,且執行效率不是關鍵問題時。
2)當問題重複出現,且可以用一種簡單的語言來進行表達時。
3)當一個語言需要解釋執行,並且語言中的句子可以表示爲一個抽象語法樹的時候,如 XML 文檔解釋。
九、命令模式
9.1 命令模式定義
將一個請求封裝爲一個對象,使發出請求的責任和執行請求的責任分割開。
9.2 命令模式特點
優點:
1>降低系統的耦合度。命令模式能將調用操作的對象與實現該操作的對象解耦。
2>增加或刪除命令非常方便。採用命令模式增加與刪除命令不會影響其他類,它滿足“開閉原則”,對擴展比較靈活。
3>可以實現宏命令。命令模式可以與組合模式結合,將多個命令裝配成一個組合命令,即宏命令。
4>方便實現 Undo 和 Redo 操作。命令模式可以與後面介紹的備忘錄模式結合,實現命令的撤銷與恢復。
缺點:
可能產生大量具體命令類。因爲計對每一個具體操作都需要設計一個具體命令類,這將增加系統的複雜性。
9.3 命令模式主要角色
抽象命令類角色:聲明執行命令的接口,擁有執行命令的抽象方法 execute()。
具體命令角色角色:是抽象命令類的具體實現類,它擁有接收者對象,並通過調用接收者的功能來完成命令要執行的操作。
實現者/接收者角色:執行命令功能的相關操作,是具體命令對象業務的真正實現者。
調用者/請求者角色:是請求的發送者,它通常擁有很多的命令對象,並通過訪問命令對象來執行相關請求,它不直接訪問接收者。
9.4 命令模式實現方式
此處以項目經理追問各研發小組進度爲例,項目經理爲調用者,查詢項目進度爲命令,各研發小組Leader爲接收者。示例代碼如下:
/*抽象命令*/
public abstract class Command {
public abstract void execute();
}
/*具體命令:查詢項目進度*/
public class QueryProjectCommand extends Command{
private GroupLeaderA groupLeaderA;
private GroupLeaderB groupLeaderB;
QueryProjectCommand()
{
groupLeaderA = new GroupLeaderA();
groupLeaderB = new GroupLeaderB();
}
public void execute()
{
groupLeaderA.action();
groupLeaderB.action();
}
}
/*接收者A:A研發組Leader*/
public class GroupLeaderA {
public void action()
{
System.out.println("A項目組進度爲:已完成70%功能開發");
}
}
/*接收者B:B研發組Leader*/
public class GroupLeaderB {
public void action()
{
System.out.println("B項目組進度爲:已完成50%功能開發");
}
}
/*調用者:項目經理*/
public class ProjectManager {
private Command command;
public ProjectManager(Command command)
{
this.command=command;
}
public void setCommand(Command command)
{
this.command=command;
}
public void call()
{
System.out.println("項目經理查詢各小組研發進度");
command.execute();
}
}
/*測試類*/
public class CommandTest {
public static void main(String[] args)
{
Command cmd = new QueryProjectCommand();
ProjectManager projectManager = new ProjectManager(cmd);
System.out.println("客戶訪問調用者(項目經理)的call()方法...");
projectManager.call();
}
}
輸出結果如下:
客戶訪問調用者(項目經理)的call()方法…
項目經理查詢各小組研發進度
A項目組進度爲:已完成70%功能開發
B項目組進度爲:已完成50%功能開發
9.5 命令模式應用場景
命令模式通常適用的場景:
1)當系統需要將請求調用者與請求接收者解耦時,命令模式使得調用者和接收者不直接交互。
2)當系統需要隨機請求命令或經常增加或刪除命令時,命令模式比較方便實現這些功能。
3)當系統需要執行一組操作時,命令模式可以定義宏命令來實現該功能。
4)當系統需要支持命令的撤銷(Undo)操作和恢復(Redo)操作時,可以將命令對象存儲起來,採用備忘錄模式來實現。
十、責任鏈模式
10.1 責任鏈模式定義
爲了避免請求發送者與多個請求處理者耦合在一起,將所有請求的處理者通過前一對象記住其下一個對象的引用而連成一條鏈;當有請求發生時,可將請求沿着這條鏈傳遞,直到有對象處理它爲止。
10.2 責任鏈模式特點
優點:
1>降低了對象之間的耦合度。該模式使得一個對象無須知道到底是哪一個對象處理其請求以及鏈的結構,發送者和接收者也無須擁有對方的明確信息。
2>增強了系統的可擴展性。可以根據需要增加新的請求處理類,滿足開閉原則。
3>增強了給對象指派職責的靈活性。當工作流程發生變化,可以動態地改變鏈內的成員或者調動它們的次序,也可動態地新增或者刪除責任。
4>責任鏈簡化了對象之間的連接。每個對象只需保持一個指向其後繼者的引用,不需保持其他所有處理者的引用,這避免了使用衆多的 if 或者 if···else 語句。
5>責任分擔。每個類只需要處理自己該處理的工作,不該處理的傳遞給下一個對象完成,明確各類的責任範圍,符合類的單一職責原則。
缺點:
1>不能保證每個請求一定被處理。由於一個請求沒有明確的接收者,所以不能保證它一定會被處理,該請求可能一直傳到鏈的末端都得不到處理。
2>對比較長的職責鏈,請求的處理可能涉及多個處理對象,系統性能將受到一定影響。
3>職責鏈建立的合理性要靠客戶端來保證,增加了客戶端的複雜性,可能會由於職責鏈的錯誤設置而導致系統出錯,如可能會造成循環調用。
10.3 責任鏈模式主要角色
抽象處理者角色:定義一個處理請求的接口,包含抽象處理方法和一個後繼連接。
具體處理者角色:實現抽象處理者的處理方法,判斷能否處理本次請求,如果可以處理請求則處理,否則將該請求轉給它的後繼者。
客戶類角色:創建處理鏈,並向鏈頭的具體處理者對象提交請求,它不關心處理細節和請求的傳遞過程。
10.4 責任鏈模式實現方式
此處以代購爲例,不同的代購者可以購買不同價位的產品,代購者A可以購買0-300元的產品,代購者B可以購買300-600元的產品,代購者C可以購買600元以上的產品。示例代碼如下:
/*抽象處理者:代購者*/
public abstract class AgentBuyer {
private AgentBuyer next;
public void setNext(AgentBuyer next)
{
this.next=next;
}
public AgentBuyer getNext()
{
return next;
}
//處理請求的方法
public abstract void buyGoods(int money);
}
/*具體處理者:代購者A*/
public class AgentBuyerA extends AgentBuyer{
@Override
public void buyGoods(int money) {
if(money>0 && money <= 300)
{
System.out.println("您可以通過代購者A購買"+money+"元的商品");
}
else
{
if(getNext() != null)
{
getNext().buyGoods(money);
}
}
}
}
/*具體處理者:代購者B*/
public class AgentBuyerB extends AgentBuyer{
@Override
public void buyGoods(int money) {
if(money>300 && money <= 600)
{
System.out.println("您可以通過代購者B購買"+money+"元的商品");
}
else
{
if(getNext() != null)
{
getNext().buyGoods(money);
}
}
}
}
/*具體處理者:代購者C*/
public class AgentBuyerC extends AgentBuyer{
@Override
public void buyGoods(int money) {
System.out.println("您可以通過代購者C購買"+money+"元的商品");
}
}
/*測試類*/
public class ResponsibilityTest {
public static void main(String[] args)
{
//組裝責任鏈
AgentBuyer gentBuyerA =new AgentBuyerA();
AgentBuyer gentBuyerB =new AgentBuyerB();
AgentBuyer gentBuyerC =new AgentBuyerC();
gentBuyerA.setNext(gentBuyerB);
gentBuyerB.setNext(gentBuyerC);
gentBuyerA.buyGoods(500);
gentBuyerA.buyGoods(1000);
}
}
輸出結果如下:
您可以通過代購者B購買500元的商品
您可以通過代購者C購買1000元的商品
10.5 責任鏈模式應用場景
責任鏈模式通常適用的場景:
1)有多個對象可以處理一個請求,哪個對象處理該請求由運行時刻自動確定。
2)可動態指定一組對象處理請求,或添加新的處理者。
3)在不明確指定請求處理者的情況下,向多個處理者中的一個提交請求。
十一、訪問者模式
11.1 訪問者模式定義
將作用於某種數據結構中的各元素的操作分離出來封裝成獨立的類,使其在不改變數據結構的前提下可以添加作用於這些元素的新的操作,爲數據結構中的每個元素提供多種訪問方式。它將對數據的操作與數據結構進行分離,是行爲類模式中最複雜的一種模式。
11.2 訪問者模式特點
優點:
1>擴展性好。能夠在不修改對象結構中的元素的情況下,爲對象結構中的元素添加新的功能。
2>複用性好。可以通過訪問者來定義整個對象結構通用的功能,從而提高系統的複用程度。
3>靈活性好。訪問者模式將數據結構與作用於結構上的操作解耦,使得操作集合可相對自由地演化而不影響系統的數據結構。
4>符合單一職責原則。訪問者模式把相關的行爲封裝在一起,構成一個訪問者,使每一個訪問者的功能都比較單一。
缺點:
1>增加新的元素類很困難。在訪問者模式中,每增加一個新的元素類,都要在每一個具體訪問者類中增加相應的具體操作,這違背了“開閉原則”。
2>破壞封裝。訪問者模式中具體元素對訪問者公佈細節,這破壞了對象的封裝性。
3>違反了依賴倒置原則。訪問者模式依賴了具體類,而沒有依賴抽象類。
11.3 訪問者模式主要角色
抽象訪問者角色:定義一個訪問具體元素的接口,爲每個具體元素類對應一個訪問操作 visit() ,該操作中的參數類型標識了被訪問的具體元素。
具體訪問者角色:實現抽象訪問者角色中聲明的各個訪問操作,確定訪問者訪問一個元素時該做什麼。
抽象元素角色:聲明一個包含接受操作 accept() 的接口,被接受的訪問者對象作爲 accept() 方法的參數。
具體元素角色:實現抽象元素角色提供的 accept() 操作,其方法體通常都是 visitor.visit(this) ,另外具體元素中可能還包含本身業務邏輯的相關操作。
對象結構角色:是一個包含元素角色的容器,提供讓訪問者對象遍歷容器中的所有元素的方法,通常由 List、Set、Map 等聚合類實現。
11.4 訪問者模式實現方式
此處以老師查詢學生成績爲例,一個學生有在不同科目上有不同的成績,老師除了詢問本科目的成績之外,有時也會詢問一下其他科目的成績,來看下學生是否在其他科目投入精力過多,導致自己教授的科目成績下降。示例代碼如下:
/*抽象訪問者:老師*/
public interface Teacher {
void query(PhysicsAchievement physicsAchievement);
void query(ChemistryAchievement chemistryAchievement);
}
/*具體訪問者1:物理老師*/
public class PhysicsTeacher implements Teacher{
public void query(PhysicsAchievement element)
{
System.out.println("物理老師查詢物理課成績,"+element.operationA());
}
public void query(ChemistryAchievement element)
{
System.out.println("物理老師查詢化學課成績,"+element.operationB());
}
}
/*具體訪問者2:化學老師*/
public class ChemistryTeacher implements Teacher{
public void query(PhysicsAchievement element)
{
System.out.println("化學老師查詢物理課成績,"+element.operationA());
}
public void query(ChemistryAchievement element)
{
System.out.println("化學老師查詢化學課成績,"+element.operationB());
}
}
/*抽象元素:成績*/
public interface Achievement {
void accept(Teacher teacher);
}
/*具體元素1:物理成績*/
public class PhysicsAchievement implements Achievement
{
public void accept(Teacher teacher)
{
teacher.query(this);
}
public String operationA()
{
return "物理成績85分";
}
}
/*具體成績2:化學成績*/
public class ChemistryAchievement implements Achievement
{
public void accept(Teacher teacher)
{
teacher.query(this);
}
public String operationB()
{
return "化學成績90分";
}
}
/*對象結構*/
public class ObjectStructure {
private List<Achievement> list=new ArrayList<Achievement>();
public void accept(Teacher visitor)
{
Iterator<Achievement> i=list.iterator();
while(i.hasNext())
{
((Achievement) i.next()).accept(visitor);
}
}
public void add(Achievement element)
{
list.add(element);
}
public void remove(Achievement element)
{
list.remove(element);
}
}
/*測試類*/
public class VisitorTest {
public static void main(String[] args)
{
ObjectStructure os=new ObjectStructure();
os.add(new PhysicsAchievement());
os.add(new ChemistryAchievement());
Teacher visitor=new PhysicsTeacher();
os.accept(visitor);
visitor=new ChemistryTeacher();
os.accept(visitor);
}
}
結果如下:
物理老師查詢物理課成績,物理成績85分
物理老師查詢化學課成績,化學成績90分
化學老師查詢物理課成績,物理成績85分
化學老師查詢化學課成績,化學成績90分
11.5 訪問者模式應用場景
訪問者模式通常適用的場景:
1)對象結構相對穩定,但其操作算法經常變化的程序。
2)對象結構中的對象需要提供多種不同且不相關的操作,而且要避免讓這些操作的變化影響對象的結構。
3)對象結構包含很多類型的對象,希望對這些對象實施一些依賴於其具體類型的操作。
參考資料:行爲型模式概述