設計模式學習筆記整理手冊


目錄

一、GoF設計模式的分類

  • 創建型
    主要用於創建對象
  • 結構型
    主要用於處理類與對象的組合
  • 行為型
    主要用於描述類與對象怎么交互和分配職責的

1.1 創建型

  • 抽象工廠模式(Abstract Factory)
  • 建造者模式(Builder)
  • 工廠方法模式(Factory Method)
  • 原型模式(Prototype)
  • 單例模式(Singleton)

1.2 結構型

  • 適配器模式(Adapter)
  • 橋接模式(Bridge)
  • 組合模式(Composite)
  • 裝飾模式(Decorator)
  • 外觀模式(Facade)
  • 享元模式(Flyweight)
  • 代理模式(Proxy)

1.3 行為型

  • 職責鏈模式(Chain of Responsibility)
  • 命令模式(Command)
  • 解釋器模式(Interpreter)
  • 迭代器模式(Iterator)
  • 中介者模式(Mediator)
  • 備忘錄模式(Memento)
  • 觀察者模式(Observer)
  • 狀態模式(State)
  • 策略模式(Strategy)
  • 模板方法模式(Template Method)
  • 訪問者模式(Visitor)

二、設計原則概述

2.1 面向對象設計原則概述:

  • 單一職責原則(Single Responsibility Principle, SRP)
  • 開閉原則(Open-Closed Principle, OCP)
  • 里氏代換原則(Liskov Substitution Principle, LSP)
  • 依賴倒轉原則(Dependency Inversion Principle, DIP)
  • 接口隔離原則(Interface Segregation Principle, ISP)
  • 合成復用原則(Composite Reuse Principle, CRP)
  • 迪米特法則(Law of Demeter, LoD)
設計原則名稱 設計原則簡介
單一職責原則 類的職責要單一,不要將太多的職責放在一個類中
開閉原則 軟件實體對拓展是開放的,但對修改是關閉的,即在不修改一個軟件實體的基礎上拓展其功能
里氏代換原則 在軟件系統中,一個可以接受基類對象的地方必然可以一個子類對象
依賴倒轉原則 要針對抽象層編程,而不針對具體類編程
接口隔離原則 使用多個接口來替代一個統一的接口
合成復用原則 在系統中盡量多使用組合和聚合關聯關系,盡量少使用甚至不使用繼承關系
迪米特法則 一個軟件實體對其他實體的引用越少越好,或者說如果兩個類不必彼此直接通信,那么這兩個類就不應當發生直接的相互作用,而是通過引入一個第三者發生間接交互

2.2 單一職責原則

單一職責原則定義:一個對象應該只包含單一的職責,並且該職責被完整地封裝在一個類中。

單一職責原則是實現高內聚、低耦合的指導方針

2.3 開閉原則

開閉原則定義:一個軟件實體應當對擴展開放,對修改關閉。也就是說在設計一個模塊的時候,應當使這個模塊可以在不被修改的前提下被擴展,即實現在不修改源代碼的情況下改變這個模塊的行為。

在開閉原則的定義中,軟件實體可以指一個軟件模塊、一個由多個類組成的局部結構或一個獨立的類。

2.4 里氏代換原則

里氏代換原則嚴格的定義:如果對每一個類型為S的對象o1,都有類型為T的對象o2,使得以T定義的所有程序P在所有的對象o1都代換成o2時,程序P的行為沒有變化,那么類型S是類型T的子類型。

更容易理解的定義:所有引用基類(父類)的地方必須能透明地使用其子類的對象。也可以說在軟件系統中,一個可以接受基類對象的地方必然可以一個子類對象

里氏代換原則是實現開閉原則的重要方式之一,由於使用基類對象的地方都可以使用子類對象,因此在程序中盡量使用基類類型來對對象進行定義,而在運行時再確定其子類類型,用子類對象來替換父類對象。

2.5 依賴倒轉原則

依賴倒轉原則定義:高層模塊不應該依賴低層模塊,它們都應該依賴抽象。抽象不應該依賴於細節,細節應該依賴於抽象。也就是說要針對接口編程,不要針對實現編程

依賴倒轉原則的常用實現方式之一是在代碼中使用抽象類,而將具體類放在配置文件中。

2.6 接口隔離原則

接口隔離原則定義:一旦一個接口太大,則需要將它分割成一些更細小的接口,使用該接口的客戶端僅需知道與之相關的方法即可。也就是說使用多個接口來替代一個統一的接口

2.7 合成復用原則

合成復用原則定義:又稱為組合/聚合復用原則(Composition/Aggregate Reuse Principle,CARP),其定義如下:盡量使用對象組合,而不是繼承來達到復用的目的。簡言之:要盡量使用組合/聚合關系,少用繼承。

2.8 迪米特法則

迪米特法則(Law of Demeter, LoD)又稱為最少知識原則(Least Knowledge Principle, LKP),它有多種定義方法,其中幾種典型定義如下:

  • (1) 不要和“陌生人”說話。英文定義為:Don't talk to strangers.
  • (2) 只與你的直接朋友通信。英文定義為:Talk only to your immediate friends.
  • (3) 每一個軟件單位對其他的單位都只有最少的知識,而且局限於那些與本單位密切相關的軟件單位。英文定義為:Each unit should have only limited knowledge about other units: only units "closely" related to the current unit.

迪米特法則可分為狹義法則和廣義法則。在狹義的迪米特法則中,如果兩個類之間不必彼此直接通信,那么這兩個類就不應當發生直接的相互作用,如果其中的一個類需要調用另一個類的某一個方法的話,可以通過第三者轉發這個調用。

三、創建型設計模式

3.1 抽象工廠模式

3.1.1 模式定義

抽象工廠模式(Abstract Factory Pattern):提供一個創建一系列相關或相互依賴對象的接口,而無須指定它們具體的類。抽象工廠模式又稱為Kit模式,屬於對象創建型模式。

3.1.2 模式角色

抽象工廠模式包含如下角色:

AbstractFactory:抽象工廠

ConcreteFactory:具體工廠

Product:具體產品

AbstractProduct:抽象產品

3.1.3 簡單例子

抽象工廠類:

public abstract class AbstractFactory
{
    public abstract AbstractProductA createProductA();
    public abstract AbstractProductB createProductB();
}

具體工廠類:

public class ConcreteFactory1 extends AbstractFactory
{
    public AbstractProductA createProductA()
    {
        return new ConcreteProductA1();
    }
    public AbstractProductB createProductB()
    {
        return new ConcreteProductB1();
    } 
}

3.1.4 抽象工廠模式和工廠模式的區別

抽象工廠模式是所有形式的工廠模式中最為抽象和最具一般性的一種形態。抽象工廠模式與工廠方法模式最大的區別在於,工廠方法模式針對的是一個產品等級結構,而抽象工廠模式則需要面對多個產品等級結構。

3.2 建造者模式

3.2.1 模式定義

建造者模式屬於23種設計模式中的創建型模式,可以理解為創建對象的一種很好的方法。

所謂建造者模式就是將組件和組件的組件過程分開,然后一步一步建造一個復雜的對象。所以建造者模式又叫生成器模式。

3.2.2 模式結構

建造者模式包含如下角色
Builder:抽象建造者
ConcreteBuilder:具體建造者
Director:指揮者
Product:產品角色

如果系統只需要一個具體的建造者類的時候,可以省略抽象建造者,有時候指揮者類也可以省略,讓建造者類同時充當指揮者和建造者

3.2.3 簡單實例

下面給出一個簡單例子

產品角色類

public class Product
{
    private String partA;
    private String partB;
    private String partC;
    //...省略set、get方法
}

抽象建造者類定義了產品的創建方法和返回方法

public abstract class Builder
{
	protected Product product=new Product();
	
	public abstract void buildPartA();
	public abstract void buildPartB();
	public abstract void buildPartC();
	
	public Product getResult()
	{
		return product;
	}

具體建造者類,實現抽象建造者類接口

public class ConcreteBuilder implements Builder {

  Part partA, partB, partC; 
  public void buildPartA() {
    //這里是具體如何構建partA的代碼

  }; 
  public void buildPartB() { 
    //這里是具體如何構建partB的代碼
  }; 
   public void buildPartC() { 
    //這里是具體如何構建partB的代碼
  }; 
   public Product getResult() { 
    //返回最后組裝成品結果
  }; 

}

指揮者類,一方面它隔離了客戶與生產過程;另一方面它負責控制產品的生成過程

public class Director
{
	private Builder builder;
	
	public Director(Builder builder)
	{
		this.builder=builder;
	}
	
	public void setBuilder(Builder builder)
	{
		this.builder=builer;
	}
	
	public Product construct()
	{
		builder.buildPartA();
		builder.buildPartB();
		builder.buildPartC();
		return builder.getResult();
	}
} 

然后客戶端調用,在客戶端代碼中,無須關心產品對象的具體組裝過程,只需確定具體建造者的類型即可

Builder builder = new ConcreteBuilder();
Director director = new Director(builder);
Product product = director.construct();

3.2.4 模式應用

最常見的就是StringBuilder;

JDBC的PreparedStatement類

螞蟻金服的螞蟻庄園小雞的裝扮實現可以通過建造者模式設計

優缺點
優點:
客戶端不必知道產品內部組成的細節,將產品本身與產品的創建過程解耦。

增加新的具體建造者無須修改原有類庫的代碼,指揮者類針對抽象建造者類編程,系統擴展方便,符合“開閉原則”。

缺點:
如果內部建造組件的方法經常變動,這種情況就不適合建造者模式了

建造者模式雖然很好的解耦,但是和單例模式比起來,可能造成過多的創建類對象,給JVM造成負載,當然在適當的場景應用也是可以提高性能的,比如StringBuilder的應用

3.2.5 模式比較

通過學習,我們發現建造模式和抽象工廠模式似乎有點類似,所以我們對比一下兩種模式

抽象工廠模式:在客戶端調用時,只是實例工廠類,然后調用工廠類對應的方法
建造者模式:在客戶端調用時,可以通過指揮者指揮生成對象,返回的是一個完整的對象

3.3 工廠方法模式

3.3.1 模式定義

工廠方法模式:又稱工廠模式,也叫虛擬構造器模式,屬於構建型設計模式,工廠方法模式是在簡單工廠模式上進行拓展,生產產品的過程由具體工廠類實現,基類只實現接口,這使得工廠方法模式可以在不修改工廠角色的情況下,引進新的產品。

工作方法模式也符合”開閉原則“。工廠方法模式也稱虛擬構造器(Virtual Constructor)模式或者多態工廠(Polymorphic Factory)模式

3.3.2 模式結構

工廠方法模式包含如下結構:

Product:抽象產品

ConcreteProduct:具體產品

Factory:抽象工廠

ConcreteFactory:具體工廠

3.3.3 簡單實例

抽象工廠類:

public abstract class PayMethodFactory
{
    public abstract AbstractPay getPayMethod();
}

具體工廠類:

public class CashPayFactory extends PayMethodFactory
{
    public AbstractPay getPayMethod()
    {
        return new CashPay();
    }
} 


客戶端調用:

PayMethodFactory factory;
AbstractPay payMethod;
factory=new CashPayFactory();
payMethod =factory.getPayMethod();
payMethod.pay(); 

三種工廠方法對比:https://blog.csdn.net/u014427391/article/details/80067882

3.4 原型模式

3.4.1 模式定義

原型模式(Prototype Pattern):原型模式是提供一個原型接口,提供原型的克隆,創建新的對象,是一種對象創建型模式。

3.4.2 模式結構

原型模式包括如下角色

  • Prototype :抽象原型類
  • ConcretePrototype:具體原型類
  • Client:客戶類

3.4.3 原型模式類別

一個類包括另外一個成員變量,在使用原型模式進行對象克隆時,如果直接是通過super Cloneable接口的的clone方法,這種情況其實並不支持類中另外一些成員變量的克隆的,這種方法稱之為淺克隆,所以淺克隆和深克隆的本質區別就是看其是否支持類中的成員變量的克隆。

綜上,原型模式可以淺克隆和深克隆兩種情況,其區別是是否支持類中的成員變量的克隆。

原型模式的淺克隆
原型模式在Java里的常用實現是通過類繼承 JDK提供的Cloneable接口,重寫 clone(),這種方法其實也可以稱之為原型模式的淺克隆

public class A implements Cloneable 
{

	
	public Object clone()
	{
		A clone=null;
		try
		{
			clone=(A)super.clone();		
		}
        catch(CloneNotSupportedException e)
        {
        	System.out.println("Clone failure!");
        }
		return clone;
	}
}

一般來說,clone方法符合:

  • 類型相同:對於任何對象a,a.clone().getClass() = a.getClass()
  • 內存地址不同:也可以說對於任何對象a,a.clone()!=a,克隆對象和原對象不是同一個對象
  • a對象的equals方法:對於任何對象a,a.clone().equals(a)

淺克隆的例子,例子來自《設計模式》一書的郵件復制

由於郵件對象包含的內容較多(如發送者、接收者、標題、內容、日期、附件等),某系統中現需要提供一個郵件復制功能,對於已經創建好的郵件對象,可以通過復制的方式創建一個新的郵件對象,如果需要改變某部分內容,無須修改原始的郵件對象,只需要修改復制后得到的郵件對象即可。使用原型模式設計該系統。在本實例中使用淺克隆實現郵件復制,即復制郵件(Email)的同時不復制附件(Attachment)。

附件類:

public class Attachment
{
    public void download()
    {
    	System.out.println("下載附件");	
    }
}

郵件類,淺克隆:

public class Email implements Cloneable 
{
	private Attachment attachment=null;
	
	public Email()
	{
		this.attachment=new Attachment();
	}
	
	public Object clone()
	{
		Email clone=null;
		try
		{
			clone=(Email)super.clone();		
		}
        catch(CloneNotSupportedException e)
        {
        	System.out.println("Clone failure!");
        }
		return clone;
	}
	
	public Attachment getAttachment()
	{
		return this.attachment;
	}
	
	public void display()
	{
		System.out.println("查看郵件");	
	}
	
}

客戶端類:

public class Client
{
	public static void main(String a[])
	{
		Email email,copyEmail;
		
		email=new Email();
		
		copyEmail=(Email)email.clone();
		
		System.out.println("email==copyEmail?");
		System.out.println(email==copyEmail);
		
		System.out.println("email.getAttachment==copyEmail.getAttachment?"); 
		System.out.println(email.getAttachment()==copyEmail.getAttachment());			
	}
}

編譯返回,第一個是false,第二個是true,由前面的理論可以知道,淺克隆對於成員變量是不支持克隆的,因為對象地址還是一樣的

原型模式的深克隆

上面是淺克隆的實現,對於原型模式深克隆的實現一般是提供類的序列化來實現

附件類,注意要implements Serializable

import java.io.*;

public class Attachment implements Serializable
{
    public void download()
    {
        System.out.println("下載附件");
    }
}

郵件類,同樣要實現Serializable接口

import java.io.*;

public class Email implements Serializable
{
	private Attachment attachment=null;

	public Email()
	{
		this.attachment=new Attachment();
	}

	public Object deepClone() throws IOException, ClassNotFoundException, OptionalDataException
	{
		//將對象寫入流中
		ByteArrayOutputStream bao=new ByteArrayOutputStream();
		ObjectOutputStream oos=new ObjectOutputStream(bao);
		oos.writeObject(this);

		//將對象從流中取出
		ByteArrayInputStream bis=new ByteArrayInputStream(bao.toByteArray());
		ObjectInputStream ois=new ObjectInputStream(bis);
		return(ois.readObject());
	}

	public Attachment getAttachment()
	{
		return this.attachment;
	}

	public void display()
	{
		System.out.println("查看郵件");
	}

}

客戶端類:


public class Client
{
	public static void main(String a[])
	{
		Email email,copyEmail=null;

		email=new Email();

		try{
			copyEmail=(Email)email.deepClone();
		}
		catch(Exception e)
		{
			e.printStackTrace();
		}


		System.out.println("email==copyEmail?");
		System.out.println(email==copyEmail);

		System.out.println("email.getAttachment==copyEmail.getAttachment?");
		System.out.println(email.getAttachment()==copyEmail.getAttachment());
	}
}

編譯返回,第一個是false,第二個是flase,由前面的理論可以知道,深克隆對於成員變量是支持克隆的,因為對象地址是一樣的

原型管理器
原型管理器是原型模式的拓展
例子同樣來自《設計模式》一書

import java.util.*;

interface MyColor extends Cloneable
{
	public Object clone();
	public void display();
}

class Red implements MyColor
{
   public Object clone()
   {
     Red r=null;
     try
     {
       r=(Red)super.clone();
     }
     catch(CloneNotSupportedException e)
     {  
  
     }
     return r;
   }
   public void display()
   {
     System.out.println("This is Red!");
   }
}

class Blue implements MyColor
{
   public Object clone()
   {
     Blue b=null;
     try
     {
       b=(Blue)super.clone();
     }
     catch(CloneNotSupportedException e)
     {  
  
     }
     return b;
   }
   public void display()
   {
     System.out.println("This is Blue!");
   }
}

class PrototypeManager 
{
   private Hashtable ht=new Hashtable();
   
   public PrototypeManager()
   {
   	  ht.put("red",new Red());
   	  ht.put("blue",new Blue());
   }
   
   public void addColor(String key,MyColor obj)
   {
      ht.put(key,obj);
   }
   
   public MyColor getColor(String key)
   {
      return (MyColor)((MyColor)ht.get(key)).clone();
   }
}

class Client
{
   public static void main(String args[])
   {
      PrototypeManager pm=new PrototypeManager();  
      
      MyColor obj1=(MyColor)pm.getColor("red");
      obj1.display();
      
      MyColor obj2=(MyColor)pm.getColor("red");
      obj2.display();
      
      System.out.println(obj1==obj2);
   }
}

3.4.4 模式應用

原型模式適用的場景

  • 保存對象的狀態:對於要保存的狀態不是很占內存的情況,可以適用原型模式和備忘錄模式保存對象狀態,如果對象占用太多內存,那就還是狀態模式比較好

  • 創建新對象成本很大的情況:比如創建一個對象是需要查詢很慢的SQL才能給對象賦值,這種情況就和適合用原型模式克隆對象,減少對象創建和查詢

原型模式應用的場景

  • 對於很多軟件的復制和粘貼實現其實也是原型模式的應用
  • Spring框架提供BeanUtils.copyProperties方法也是原型模式的應用

3.5 單例模式

3.5.1 前言

本博客介紹一種創建型模式:單例模式
這是一種比較容易理解的設計模式,可以理解為創建對象的一種很好的做法。可以盡量避免創建過多的對象,給JVM造成很大的負載。

3.5.2 應用場景

單例模式的一些應用場景:
1、比如數據連接類,這是需要經常調用的
2、網站訪問量統計的服務類,需要多次調用
3、導出導入Excel表,一些業務復雜的系統需要多次調用
...

總結起來就是需要經常調用的通用類,我們可以用單例模式進行很好的設計。
編程思想
單例模式涉及了兩種重要的編程思想:懶加載思想和緩存思想

緩存思想:

	private static Singleton instance = null;//先放內存緩存
   
    public static Singleton getInstance() {
        if (instance == null) {//內存加載不到,創建對象
            instance = new Singleton();
        }
        return instance;//內存緩存有,直接調用
    }

懶加載思想:
下面例子就是懶加載的簡單應用,創建一個對象都是需要用的時候實例,盡量不要在加載類的時候就實例了,這種方法可以很好的避免給JVM增加負載。這是一種很好的編程習慣。

public static Singleton getInstance() {
        if (instance == null) {//對象需要用時才實例
            instance = new Singleton();
        }
        return instance;
    }

3.5.3 單例模式實例

下面介紹幾種常用的單例模式實例

1、懶漢模式
這是一種線程不安全,懶加載的方式

public class Singleton {
    private static Singleton instance;
    //定義private構造函數,使類不可以被實例
    private Singleton (){}

    /**
     * 懶漢模式,線程不安全,懶加載
     * @return
     */
    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

上面例子線程不安全,要線程安全可以加個同步鎖,不過加了同步鎖性能又不好了,加載慢

public class Singleton {
    private static Singleton instance;
    //定義private構造函數,使類不可以被實例
    private Singleton (){}

    /**
     * 懶漢模式,線程安全,懶加載
     * @return
     */
    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

2、餓漢模式
下面介紹一下單例模式的另外一種實現方式,餓漢模式
其實現原理就是在類內部全局new一個對象,利用Java虛擬機的類加載機制,保證了線程安全,不過很明顯,一創建了,就實例了單例類,會給JVM增加負載

public class Singleton {
    //定義private構造函數,使類不可以被實例
    private Singleton (){}


    //加載類的時候,利用JVM的類加載機制創建對象,保證了線程安全,但是效率不好
    private static Singleton instance = new Singleton();

    /**
     * 餓漢模式,線程安全,非懶加載
     * @return
     */
    public static Singleton getInstance() {
        return instance;
    }
}

3、雙檢鎖/雙重校驗鎖(DCL,即 double-checked locking)
下面介紹一種雙檢鎖的實現方式,這種方式看起來稍稍比較復雜了點,不過可以實現線程安全,同時雙檢鎖的方式可以保證性能比較高

public class Singleton {
 
    //定義private構造函數,使類不可以被實例
    private Singleton (){}

    private volatile static Singleton instance;

    /**
     * 雙檢鎖/雙重校驗鎖(DCL,即 double-checked locking)線程安全,懶加載
     * @return
     */
    public static Singleton getInstance(){
        if(instance == null){
            synchronized (Singleton.class){
                if(instance == null){
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

4、登記式/內部類
下面用內部類的方式來實現單例模式,這種方式可以和餓漢模式來對比一下
這種方式和剛才介紹的餓漢模式類似,不過區別就是做到了懶加載,我們可以分析例子。方法就是在單例類里加個內部類,這樣做就不會像餓漢模式一樣,單例類一加載就實例對象。當調用getInstance方法的時候,才會調用,創建對象。這樣就做到了懶加載,同時也是利用JVM保證了線程安全

public class Singleton {
   
    //定義private構造函數,使類不可以被實例
    private Singleton (){}

    public static class SingletonHolder{
        private final static Singleton INSTANCE = new Singleton();
    }

    /**
     * 登記式/靜態內部類,線程安全,懶加載
     * @return
     */
    public static Singleton getInstance(){
        return SingletonHolder.INSTANCE;
    }
}

5、枚舉模式
這種方式是 Effective Java 作者 Josh Bloch 提倡的方式,它不僅能避免多線程同步問題,而且還自動支持序列化機制,防止反序列化重新創建新的對象,絕對防止多次實例化。不過,由於 JDK1.5 之后才加入 enum 特性,用這種方式寫不免讓人感覺生疏,在實際工作中,也很少用。

/**
     * 枚舉方式
     */
    public enum Singleton {
        INSTANCE;
        public void whateverMethod() {
        }
    }

四、結構型設計模式

4.1 適配器模式

4.1.1 模式定義

適配器模式(Adapter Pattern):將一個接口轉換成客戶希望的接口,適配器模式使接口不兼容的那些類可以一起工作,其別名為包裝器(Wrapper)。適配器模式既可以作為類結構型模式,也可以作為對象結構型模式。

4.1.2 模式角色

適配器模式包括如下角色

  • Traget(目標抽象類)
  • Adapter(適配器類)
  • Adaptee(適配者類)
  • Client(客戶類)

4.1.3 模式分析

適配器模式將目標類和適配者類解耦,引入一個適配器類來重用適配者類,具體的實現是在適配者類的,通過適配器模式,客戶端類就不知道適配者類的具體實現了,

典型的類適配器代碼:

public class Adapter extends Adaptee implements Target
{
	public void request()
	{
		specificRequest();
	}
}

典型的對象適配器代碼:

public class Adapter extends Adaptee implements Target
{
	public void request()
	{
		specificRequest();
	}
}

4.1.4 模式例子

某系統需要提供一個加密模塊,將用戶信息(如密碼等機密信息)加密之后再存儲在數據庫中,系統已經定義好了數據庫操作類。為了提高開發效率,現需要重用已有的加密算法,這些算法封裝在一些由第三方提供的類中,有些甚至沒有源代碼。使用適配器模式設計該加密模塊,實現在不修改現有類的基礎上重用第三方加密方法

本例子來自《設計模式》一書

目標類:

public abstract class DataOperation
{
	private String password;
	
	public void setPassword(String password)
	{
		this.password=password;
	}
		
	public String getPassword()
	{
		return this.password;
	}
	
	public abstract String doEncrypt(int key,String ps);
}

適配者類:

public final class Caesar 
{
	public String doEncrypt(int key,String ps)
	{   
		String es="";
		for(int i=0;i<ps.length();i++)
		{
			char c=ps.charAt(i);
			if(c>='a'&&c<='z')
			{
				c+=key%26;
			if(c>'z') c-=26;
			if(c<'a') c+=26;
			}
			if(c>='A'&&c<='Z')
			{
				c+=key%26;
			if(c>'Z') c-=26;
			if(c<'A') c+=26;
			}
			es+=c;
		}
		return es;
	}
}

適配器類:

public class CipherAdapter extends DataOperation
{
	private Caesar cipher;
	
	public CipherAdapter()
	{
		cipher=new Caesar();
	}
	
	public String doEncrypt(int key,String ps)
	{
		return cipher.doEncrypt(key,ps);
	}
}
public class NewCipherAdapter extends DataOperation
{
	private NewCipher cipher;
	
	public NewCipherAdapter()
	{
		cipher=new NewCipher();
	}
	
	public String doEncrypt(int key,String ps)
	{
		return cipher.doEncrypt(key,ps);
	}
}



4.1.5 模式分類

適配器模式可以分為默認適配器和雙向適配器

默認適配器
在適配器中同時包含對目標類和適配者類的引用,適配者可以通過它調用目標類中的方法,目標類也可以通過它調用適配者類中的方法,它適用於一個接口不想使用其所有的方法的情況。因此也稱為單接口適配器模式。

雙向適配器
如果在對象適配器中,在適配器中同時包含對目標類和適配者類的引用,適配者可以通過它調用目標類中的方法,目標類也可以通過它調用適配者類中的方法,那么這個適配器就是一個雙向適配器

4.1.6 模式應用

  • JDBC驅動軟件都是一個介於jdbc接口和數據庫引擎接口之間的適配器軟件。JDBC驅動軟件使java程序可以適配各種數據庫引擎
  • Spring AOP框架中,對BeforeAdvice、AfterAdvice、ThrowsAdvice三種通知類型借助適配器模式來實現。

4.2 橋接模式

4.2.1 模式定義

橋接模式(Bridge Pattern)是將抽象部分和實現部分分離,使它們可以獨立地改變,是一種對象結構型模式。

4.2.2 模式角色

橋接模式包含如下角色:

  • Abstraction(抽象類)
  • RefinedAbstraction(擴充抽象類)
  • Implementor(實現類接口)
  • ConcreteImplementor(具體實現類)

4.2.3 模式分析

橋接模式關鍵在於如何將抽象化與實現化解耦,使得兩者可以獨立改變。

抽象化:抽象就是忽略一些信息,將不同的實體當作同樣的實體對待。在面向對象中將對象的共同性質抽取出來形成類的過程稱之為抽象化的過程

實現化:針對抽象話給出的具體實現,就是實現化,抽象化與實現化是互逆的過程

解耦:解耦就是將抽象化和實現化直接的耦合解脫開,或者說將兩者之間的強關聯變成弱關聯,將兩個角色由繼承改成關聯關系(組合或者聚合)

典型代碼:

public interface Implementor
{
	public void operationImpl();
} 

public abstract class Abstraction
{
	protected Implementor impl;
	
	public void setImpl(Implementor impl)
	{
		this.impl=impl;
	}
	
	public abstract void operation();
} 

public class RefinedAbstraction extends Abstraction
{
	public void operation()
	{
		//代碼
		impl.operationImpl();
		//代碼
	}
} 

4.2.4 模式例子

畫出不同顏色的圓,DrawAPI 接口的實體類 RedCircle、GreenCircle。Shape 是一個抽象類,例子來自:http://www.runoob.com/design-pattern/bridge-pattern.html

創建橋接接口:

public interface DrawAPI {
   public void drawCircle(int radius, int x, int y);
}

接口具體實現類:

public class RedCircle implements DrawAPI {
   @Override
   public void drawCircle(int radius, int x, int y) {
      System.out.println("Drawing Circle[ color: red, radius: "
         + radius +", x: " +x+", "+ y +"]");
   }
}
public class GreenCircle implements DrawAPI {
   @Override
   public void drawCircle(int radius, int x, int y) {
      System.out.println("Drawing Circle[ color: green, radius: "
         + radius +", x: " +x+", "+ y +"]");
   }
}

抽象類關聯方式實現接口:

public abstract class Shape {
   protected DrawAPI drawAPI;
   protected Shape(DrawAPI drawAPI){
      this.drawAPI = drawAPI;
   }
   public abstract void draw();  
}

具體類實現抽象類:

public class Circle extends Shape {
   private int x, y, radius;
 
   public Circle(int x, int y, int radius, DrawAPI drawAPI) {
      super(drawAPI);
      this.x = x;  
      this.y = y;  
      this.radius = radius;
   }
 
   public void draw() {
      drawAPI.drawCircle(radius,x,y);
   }
}

public class BridgePatternDemo {
   public static void main(String[] args) {
      Shape redCircle = new Circle(100,100, 10, new RedCircle());
      Shape greenCircle = new Circle(100,100, 10, new GreenCircle());
 
      redCircle.draw();
      greenCircle.draw();
   }
}

打印到控制台:

Drawing Circle[ color: red, radius: 10, x: 100, 100]
Drawing Circle[  color: green, radius: 10, x: 100, 100]

4.2.5 模式應用

  • 一些軟件的跨平台設計有時候也是應用了橋接模式
  • JDBC的驅動程序,實現了將不同類型的數據庫與Java程序的綁定
  • Java虛擬機實現了平台的無關性,Java虛擬機設計就是通過橋接模式

4.3 組合模式

4.3.1 模式意圖

介紹模式定義定義之前先介紹一下組合模式的意圖。其實就是將對象組合成整體-部分層次的樹形結構,客戶端調用時,對於調用容器對象或者說組合對象("樹枝")和單個對象("樹葉")是一致的。

4.3.2 模式定義

組合模式(Composite Pattern):組合多個對象形成樹形結構來表示“整體-部分”的結構層次。

組合模式又稱“整體-部分”(Part-Whole)模式,屬於對象結構型的設計模式。將對象組合在組件類里,用於描述整體和部分的關系。組合模式對單個對象和組合對象或者說容器對象的使用具有一致性。

4.3.3 模式角色

組合模式包括如下角色:

  • Component:抽象構件
  • Leaf:葉子構件
  • Composite:容器構件
  • Client:客戶類

4.3.4 模式分析

組合模式定義一個抽象的構件類,主要用於被客戶端調用,客戶調調用時就不需要關心是單個對象還是容器對象了。

容器對象和抽象構件類是一種聚合關系,容器對象里即可以包含葉子,也可以包含容器,遞歸組合,從而形成一個樹形結構。

實際例子
例子來自:《設計模式》一書

抽象構件類:

public abstract class Component
{
	public abstract void add(Component c);
	public abstract void remove(Component c);
	public abstract Component getChild(int i);
	public abstract void operation(); 
} 

葉子類:

public class Leaf extends Component
{
	public void add(Component c)
	{ //異常處理或錯誤提示 }	
		
	public void remove(Component c)
	{ //異常處理或錯誤提示 }
	
	public Component getChild(int i)
	{ //異常處理或錯誤提示 }
	
	public void operation()
	{
		//實現代碼
	} 
} 

容器類:

public class Composite extends Component
{
	private ArrayList list = new ArrayList();
	
	public void add(Component c)
	{
		list.add(c);
	}
	
	public void remove(Component c)
	{
		list.remove(c);
	}
	
	public Component getChild(int i)
	{
		(Component)list.get(i);
	}
	
	public void operation()
	{
		for(Object obj:list)
		{
			((Component)obj).operation();
		}
	} 	
} 

4.3.5 模式應用

組合模式應用

  • XML文檔解析
  • JDK的AWT/Swing
    ...

4.4 裝飾模式

4.4.1 模式定義

裝飾模式:裝飾模式就是允許向一個現有的對象添加新的功能,同時又不改變其結構,裝飾模式是一種對象結構型設計模式。

4.4.2 模式角色

對於裝飾模式可以分為如下角色

  • Component:抽象構件
  • ConcreteComponent:具體構件
  • Decorator:抽象裝飾類
  • ConcreteDecorator:具體裝飾類

4.4.3 模式分析

對於裝飾模式進行解釋,更易於理解。要給一個類或對象新增行為,一般有兩種方法,一種是繼承方法,通過類繼承的方法可以使Z子類擁有自身方法的同時,擁有父類的方法這就是一種新增類行為的方法;對於另外一種新增類行為的方法就是關聯方法,即將一個類嵌入另外一個類,對於這個類,我們稱之為裝飾器(Decorator)

上面說了繼承機制和關聯機制,對於關聯機制與繼承機制相比,關聯優勢在於不會破壞類的封裝性,繼承的耦合度還是比關聯要大的,所以應用關聯機制的裝飾模式偶爾度還是比較小的,這個就是裝飾模式的優點了,不過裝飾模式需要創建比較多的對象,這種缺點或許可以用享元模式減少類的創建。
下面給出裝飾模式的經典代碼:
繼承抽象構件接口

public class Decorator extends Component
{
	private Component component;
	public Decorator(Component component)
	{
		this.component=component;
	}
	public void operation()
	{
		component.operation();
	}
} 

具體裝飾類型實現抽象裝飾接口

public class ConcreteDecorator extends Decorator
{
	public ConcreteDecorator(Component component)
	{
		super(component);
	}
	public void operation()
	{
		super.operation();
		addedBehavior();
	}
	public void addedBehavior()
	{
                  //新增方法	
        }
} 

4.4.4 模式例子

給出《設計模式》一書的多重加密例子:

某系統提供了一個數據加密功能,可以對字符串進行加密。最簡單的加密算法通過對字母進行移位來實現,同時還提供了稍復雜的逆向輸出加密,還提供了更為高級的求模加密。用戶先使用最簡單的加密算法對字符串進行加密,如果覺得還不夠可以對加密之后的結果使用其他加密算法進行二次加密,當然也可以進行第三次加密。現使用裝飾模式設計該多重加密系統。

抽象構件接口:

public interface Cipher
{
	public String encrypt(String plainText);
}

具體構件類:

public final class SimpleCipher implements Cipher
{
	public String encrypt(String plainText)
	{
		String str="";
		for(int i=0;i<plainText.length();i++)
		{
			char c=plainText.charAt(i);
			if(c>='a'&&c<='z')
			{
				c+=6;
			if(c>'z') c-=26;
			if(c<'a') c+=26;
			}
			if(c>='A'&&c<='Z')
			{
				c+=6;
			if(c>'Z') c-=26;
			if(c<'A') c+=26;
			}
			str+=c;
		}
		return str;
	}
}

抽象裝飾類:

public abstract class CipherDecorator implements Cipher
{
	private Cipher cipher;
	
	public CipherDecorator(Cipher cipher)
	{
		this.cipher=cipher;
	}
	
	public String encrypt(String plainText)
	{
		return cipher.encrypt(plainText);
	}
}

具體裝飾類:

public class AdvancedCipher extends CipherDecorator
{
	public AdvancedCipher(Cipher cipher)
	{
		super(cipher);
	}
	
	public String encrypt(String plainText)
	{
		String result=super.encrypt(plainText);
		result=mod(result);
		return result;
	}
	
	public String mod(String text)
	{
		String str="";
		for(int i=0;i<text.length();i++)
		{
			String c=String.valueOf(text.charAt(i)%6);
			str+=c;
		}
		return str;
	}
}

public class ComplexCipher extends CipherDecorator
{
	public ComplexCipher(Cipher cipher)
	{
		super(cipher);
	}
	
	public String encrypt(String plainText)
	{
		String result=super.encrypt(plainText);
		result= this.reverse(result);
		return result;
	}
	
	public String reverse(String text)
	{
		String str="";
		for(int i=text.length();i>0;i--)
		{
			str+=text.substring(i-1,i);
		}
		return str;
	}
}

客戶端類進行調用:

public class Client
{
	public static void main(String args[])
	{
		String password="sunnyLiu";  //明文
		String cpassword;       //密文
		Cipher sc,ac,cc;
		
		sc=new SimpleCipher();
		cpassword=sc.encrypt(password);
		System.out.println(cpassword);
		
		cc=new ComplexCipher(sc);
		cpassword=cc.encrypt(password);
	    System.out.println(cpassword);
		
		ac=new AdvancedCipher(cc);
		cpassword=ac.encrypt(password);
	    System.out.println(cpassword);
	}
}

模式應用
裝飾模式應用最常見的就是JDK提供的Java IO操作

  • 抽象構件類:InputStream
  • 具體構件類:FileInputStream、ByteArrayInputStream等
  • 抽象裝飾類:FilterInputStream
  • 具體裝飾類:BufferedInputStream、DataInputStream等
    ...

4.4.5 模式分類

裝飾模式可以分為透明裝飾模式和半透明裝飾模式。

透明裝飾模式
透明裝飾模式要求客戶端面向抽象編程,裝飾模式的透明性要求客戶端程序不應該聲明具體構件類型和具體裝飾類型,而應該全部聲明為抽象構件類型。

Cipher sc,cc,ac;
sc=new SimpleCipher();
cc=new ComplexCipher(sc);	
ac=new AdvancedCipher(cc);

半透明裝飾模式
半透明裝飾模式是比較常見的,大多數裝飾模式都是半透明(semi-transparent)的裝飾模式,而不是完全透明(transparent)的,即允許用戶在客戶端聲明具體裝飾者類型的對象,調用在具體裝飾者中新增的方法。

Transform camaro;
camaro=new Car();
camaro.move();
Robot bumblebee=new Robot(camaro);
bumblebee.move();
bumblebee.say(); 

4.5 外觀模式

4.5.1 模式定義

外觀模式:外觀模式就是提供一個統一的接口,用來訪問子系統的一群接口。外觀模式定義了一個高層接口,讓子系統更容易使用。,外觀模式也稱門面模式,是一種對象結構型設計模式。

在這里插入圖片描述

4.5.2 模式角色

從模式定義可以知道,外觀模式應該包含如下角色:

  • Frcade:外觀角色
  • SubSystem:子系統角色
  • Client:客戶端角色

經典例子:

public class Facade
{
    private SubSystemA obj1 = new SubSystemA();
    private SubSystemB obj2 = new SubSystemB();
    private SubSystemC obj3 = new SubSystemC();
    public void method()
    {
        obj1.method();
        obj2.method();
        obj3.method();
    }
} 

4.5.3 模式簡單分析

外觀模式為客戶端類提供了便捷,客戶端類不需要關注子系統的設計,直接提供外觀類訪問就好

外觀模式符合“迪米特法則”,引入一個單一簡單的接口,給客戶端調用,從而降低了客戶端和子系統的耦合度

不過外觀模式也有一些缺點,每一種設計模式都是有缺點和優點的,需要根據復雜的業務場景進行選用。加入沒引用一個抽象的外觀類的話,一旦業務改變就需要進行外觀類和客戶端類代碼的調整了

在這里插入圖片描述

對於一些很復雜的業務系統來說,有時候可以設計多個外觀類進行系統解耦

4.5.4 簡單例子實踐

JDBC數據庫操作的例子,本例子來自《設計模式》一書

import java.sql.*;

public class JDBCFacade {
    
    private Connection conn=null;
    private Statement statement=null;

    public void open(String driver,String jdbcUrl,String userName,String userPwd) {
        try {
            Class.forName(driver).newInstance();
            conn = DriverManager.getConnection(jdbcUrl,userName,userPwd);
            statement = conn.createStatement();
        } 
        catch (Exception e) {
            e.printStackTrace();
        }
    }

    public int executeUpdate(String sql) {
        try {
            return statement.executeUpdate(sql);
        } 
        catch (SQLException e) {
            e.printStackTrace();
            return -1;
        }
    }

    public ResultSet executeQuery(String sql) {
        try {
            return statement.executeQuery(sql);
        } catch (SQLException e) {
            e.printStackTrace();
            return null;
        }
    }

    public void close() {
        try {
            conn.close();
            statement.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

4.5.5 模式應用

外觀模式適用於復雜的系統,可以用於系統解耦。下面簡單列舉一下外觀模式的一些應用場景

  • JavaEE框架里的Session就是用了外觀模式

  • 學JSP的JDBC數據庫操作也是經常用外觀模式的

4.6 享元模式

4.6.1 模式定義

享元模式(Flyweight Pattern)就是通過共享技術實現大量細粒度對象的復用。享元模式是通過細粒度對象的共享,所以也可以說享元模式是一種輕量級模式。按照Gof模式分類,享元模式屬於對象結構型模式。

4.6.2 模式解釋

  • 可以共享的內容稱為內部狀態(Intrinsic State),需要外部環境設置的不能共享的內容稱為外部狀態(Extrinsic State)。享元模式需要創建一個享元工廠負責維護享元池(Flyweight Pool),享元池用於存儲具有相同內部狀態的享元對象。

  • 享元模式中共享的僅僅是享元對象,外部狀態是需要通過環境類設置的,在實際使用中,能共享的內部狀態不是很多的,所以設計享元對象是比較小的,也就是細粒度對象,所以說享元模式就是通過共享技術實現大量細粒度對象的復用

  • 創建大量對象會一定程度影響系統性能,不方便程序閱讀,使用享元模式可以減少對象使用。

4.6.3 模式角色

享元模式包括下面角色

  • Flyweight:抽象享元類

  • ConcreteFlyweight:具體享元類

  • UnsharedConcreteFlyweight:非分享具體享元類

  • FlyweightFactory:享元工廠類

享元模式的核心在享元工廠類,享元工廠類的作用在與維護享元池,需要什么對象,可以從享元池獲取

4.6.4 典型例子

例子來自:《設計模式》一書

public class Flyweight
{
        //內部狀態作為成員屬性
	private String intrinsicState;
	
	public Flyweight(String intrinsicState)
	{
		this.intrinsicState = intrinsicState;
	}
	
	public void operation(String extrinsicState)
	{
		......
	}	
}

public class FlyweightFactory
{
	private HashMap flyweights = new HashMap();
	
	public Flyweight getFlyweight(String key)
	{
		if(flyweights.containsKey(key))
		{
		   //享元池有對象,直接獲取
			return (Flyweight)flyweights.get(key);
		}
		else
		{
		    //創建具體的享元對象,存儲在享元池
			Flyweight fw = new ConcreteFlyweight();
			flyweights.put(key,fw);
			return fw;
		}
	}
} 

4.6.5 模式應用

  • JDK類庫中的String類使用了享元模式
    ...

4.6.6 模式分類

享元模式分為單存享元模式和復合享元模式

  • 單純享元模式:在單純享元模式中不存在非共享具體單元,所有的具體享元類對象都是可以共享的。
  • 復合享元模式:通過復合模式將單純享元模式進行組合,形成復合享元對象

4.7 代理模式

4.7.1 模式定義

代理模式:代理模式就是引入一個代理對象,通過代理對象實現對原對象的引用。代理模式是一種對象結構型。

4.7.2 代理模式包含如下角色

  • Subject:抽象主題角色
  • Proxy:代理主題角色
  • RealSubject:真實主題角色

這里寫圖片描述

4.7.3 模式例子

public class Proxy implements Subject
{
    private RealSubject realSubject = new RealSubject();
    public void preRequest()
    {…...}
    public void request()
    {
        preRequest();
        realSubject.request();
        postRequest();
    }
    public void postRequest()
    {……}
} 

4.7.4 模式類型

來自:《設計模式》一書歸納分類

  • 遠程(Remote)代理:為一個位於不同的地址空間的對象提供一個本地的代理對象,這個不同的地址空間可以是在同一台主機中,也可是在另一台主機中,遠程代理又叫做大使(Ambassador)。
  • 虛擬(Virtual)代理:如果需要創建一個資源消耗較大的對象,先創建一個消耗相對較小的對象來表示,真實對象只在需要時才會被真正創建。
  • Copy-on-Write代理:它是虛擬代理的一種,把復制(克隆)操作延遲到只有在客戶端真正需要時才執行。一般來說,對象的深克隆是一個開銷較大的操作,Copy-on-Write代理可以讓這個操作延遲,只有對象被用到的時候才被克隆。
  • 保護(Protect or Access)代理:控制對一個對象的訪問,可以給不同的用戶提供不同級別的使用權限。
  • 緩沖(Cache)代理:為某一個目標操作的結果提供臨時的存儲空間,以便多個客戶端可以共享這些結果。
  • 防火牆(Firewall)代理:保護目標不讓惡意用戶接近。
  • 同步化(Synchronization)代理:使幾個用戶能夠同時使用一個對象而沒有沖突。
  • 智能引用(Smart Reference)代理:當一個對象被引用時,提供一些額外的操作,如將此對象被調用的次數記錄下來等。

下面介紹一下靜態代理和動態代理

代理模式分為靜態代理和動態代理 • 靜態代理:靜態代理就是編譯階段就生成代理類來完成對代理對象的一系列操作。
• 動態代理:動態代理是指在運行時動態生成代理類。即,代理類的字節碼將在運行時生成並載入當前代理的 ClassLoader。

4.7.5 靜態代理

靜態代理:靜態代理就是編譯階段就生成代理類來完成對代理對象的一系列操作。
主題接口:

public   interface Subject  {    
	abstract   public   void  request(); 
}   

目標對象:

public   class  RealSubject  implements Subject  {    		  	       
   public   void  request()  { 
	   System.out.println( " From real subject. " );     
   }  
}  

代理對象:

public   class  StaticProxySubject  implements Subject  { 
    private  RealSubject  realSubject;  // 以真實角色作為代理角色的屬性  
    public  ProxySubject()  { }  
    public  void  request()  {  // 該方法封裝了真實對象的request方法        
    //懶加載,用的時候才加載
	if ( realSubject  ==   null  )  { 
		realSubject  =   new  RealSubject();        
	}   
	realSubject.request();  // 此處執行真實對象的request方法   
   } 
}

編寫客戶端類:

public class Client{
	StaticProxySubject sps = new StaticProxySubject();
	sps.request();
}

4.7.6 動態代理

動態代理:動態代理是指在運行時動態生成代理類。即,代理類的字節碼將在運行時生成並載入當前代理的 ClassLoader。
生成動態代理的方法有很多: JDK中自帶動態代理,CGlib, javassist等。
JDK動態代理
Proxy類。該類即為動態代理類,該類最常用的方法為:public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException

newProxyInstance()方法用於根據傳入的接口類型interfaces返回一個動態創建的代理類的實例,方法中第一個參數loader表示代理類的類加載器,第二個參數interfaces表示被代理類實現的接口列表,第三個參數h表示所指派的調用處理程序類。

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class MyInvocationHandler implements InvocationHandler {
    private Class<?> target;//委托類
    public MyInvocationHandler(Class<?> target){
        this.target=target;
    }
	//實際執行類bind
    public  Object bind(Class<?> target){
        //利用JDK提供的Proxy實現動態代理
        return  Proxy.newProxyInstance(target.getClassLoader(),
        		new Class[]{target},this);
    }
    
    @Override
    public Object invoke(Object o, Method method, Object[] args) throws Throwable {
		/**代理環繞**/
        //執行實際的方法
        Object invoke = method.invoke(target, args);
        return invoke;
    }
}

CGLIB動態代理

CGLIB動態代理實現相關類需要在項目中導入 cglib-nodep-2.1_3.jar ,主要涉及兩個類:
MethodInterceptor接口。它是代理實例的調用處理程序實現的接口,該接口中定義了如下方法:public Object intercept(Object proxy, Method method, Object[] arg2, MethodProxy mp);

intercept()方法中第一個參數proxy表示代理類,第二個參數method表示需要代理的方法,第三個參數args表示代理方法的參數數組,第四個參數mp用 來去調用被代理對象方法

package com.demo;

import java.lang.reflect.Method;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

public class MyInterceptor implements MethodInterceptor{	
    private Object target; ;//代理的目標對象
    public MyInterceptor(Object target) {
        this.target = target;
    } 
//proxy 在其上調用方法的代理實例    method攔截的方法    args  攔截的參數
 //invocation 用來去調用被代理對象方法
    @Override
    public Object intercept(Object proxy, Method method, Object[] args, 
                                         MethodProxy invocation) throws Throwable {
        //1.記錄日志 2.時間統計開始   3.安全檢查
        Object retVal = invocation.invoke(target, args);  
        //4.時間統計結束
        return retVal;   
    }
//創建代理對象的方法
    public Object proxy(Object target) {
		this.target = target;
		Enhancer enhancer = new Enhancer();//該類用於生成代理類		
		enhancer.setSuperclass(this.target.getClass());//設置父類
		enhancer.setCallback(this);//設置回調用對象為本身
		return enhancer.create();

   }
}

五、行為型設計模式

5.1 職責鏈模式

5.1.1 行為型模式

介紹職責鏈模式之前先介紹一下行為型設計模式,因為按照GoF模式分類,職責鏈就是一種行為型設計模式。行為型設計模式就是主要表示類或者對象之間的關聯關系,分為類行為型和對象行為型。類行為型一般都是通過類的繼承或者多態等等方式實現。對象行為型就是通過對象的聚合等等關聯實現。

5.1.2 職責鏈模式定義

職責鏈模式是一種對象行為型模式。根據“合成復用”原則,盡量使用關聯來取代類繼承,對象行為型可以說是一種不錯的行為型模式。

職責鏈模式是一種將請求的發送者和請求處理者分離的一種模式。職責鏈可以是線型、環型或者樹形的,不需要關系請求處理的細節,只要將請求沿着路徑發送就好,做到了請求發送者和請求處理者解耦。

引用一下

Chain of Responsibility Pattern: Avoid coupling the sender of a request to its receiver by giving more than one object a chance to handle the request. Chain the receiving objects and pass the request along the chain until an object handles it.

翻譯過來就是:

職責鏈模式:通過給予多個對象處理請求的機會,避免將請求的發送方與接收方耦合。將接收對象鏈接起來,並沿着鏈傳遞請求,直到對象處理它。

5.1.3 職責鏈模式角色

職責鏈模式包括下面幾種角色:

  • Handler:抽象處理者
  • ConcreteHandler:具體處理者
  • Client:客戶端

上面已經說了請求發送者和請求處理者,其實請求發送者就是客戶端,請求處理者就是ConcreteHandler,也就是說,Client只是需要什么業務請求的就發送而已,完全可以對ConcreteHandler請求處理者改造,而不影響到Client,也就是前面所說的做到了請求發送者和請求處理者的解耦。

5.1.4 簡單實例

例子參考:《設計模式》一書

抽象類:

public abstract class Handler
{
	protected Handler nextHandler;
	
	public void setNextHandler(Handler nextHandler)
	{
		this.nextHandler=nextHandler;
	}
	
	public abstract void handleRequest(String request);
} 

具體實現者:

public class ConcreteHandler extends Handler
{
	public void handleRequest(String request)
	{
		if(請求request滿足條件)
		{
			......  //處理請求;
		}
		else
		{
			this.nextHandler.handleRequest(request); //轉發請求
		}
	}
} 

客戶端調用:

public class Client
{
	public static void main(String args[])
	{
		Handler handler1 = new ConcreteHandler();
		handler handler2 = new ConcreteHandler();
		handler1.setNextHandler(handler2);
		handler1.handleRequest("test");
	}
}

網上這個例子也寫的還可以,可以參考學習
http://www.runoob.com/design-pattern/chain-of-responsibility-pattern.html

5.1.5 模式應用

在Java中的異常處理機制

try
{
    …… 			
}catch(IOException e3){
    ……
}finally{
    ……
}

Mybatis Plugin 插件(攔截器)的應用,也是用動態代理和職責鏈模式進行設計和實現的:
可以看一下博客:https://www.jianshu.com/p/b82d0a95b2f3

5.2 命令模式

5.2.1 模式定義

命令模式(Command Pattern):將一個請求封裝為一個對象,使發出請求的責任和執行請求的責任分離,兩者之間通過命令對象進行溝通,方便將命令對象進行儲存、傳遞、調用、增加與管理。命令模式別名為動作(Action)模式或事務(Transaction)模式,屬於對象行為型模式。

5.2.2 模式角色

命令模式包括如下角色:

  • Client:客戶類,負責調用
  • Command:抽象命令類,聲明執行命令的接口,擁有執行命令的抽象方法 execute()。
  • ConcreteCommand:具體命令類,是抽象命令類的具體實現類,它擁有接收者對象,並通過調用接收者的功能來完成命令要執行的操作。
  • Invoker:調用者,請求的發送者,通常擁有很多的命令對象,並通過訪問命令對象來執行相關請求,它不直接訪問接收者。
  • Receiver:接收者,執行命令功能的相關操作,是具體命令對象業務的真正實現者。

5.2.3 模式分析

命令模式的本質:是對命令進行封裝,將發出命令的責任和執行命令的責任分離。

命令模式的實際執行者是接收者(Receiver),調用者和接收者兩者之間通過命令對象進行溝通。

命令模式允許請求的一方和接收的一方獨立開來,使得請求的一方不必知道接收請求的一方的接口,更不必知道請求是怎么被接收,以及操作是否被執行、何時被執行,以及是怎么被執行的。

典型的命令模式代碼

抽象命令類:

public abstract class Command
{
	public abstract void execute();
} 

具體命令類:

public class ConcreteCommand extends Command
{
	private Receiver receiver;
	public void execute()
	{
		receiver.action();
	}
} 

調用者Invoker類:

public class Invoker
{
	private Command command;
	
	public Invoker(Command command)
	{
		this.command=command;
	}
	
	public void setCommand(Command command)
	{
		this.command=command;
	}
	
	//業務方法,用於調用命令類的方法
	public void call()
	{
		command.execute();
	}
} 

接收者(Receiver)類:

public class Receiver
{
	public void action()
	{
		//具體操作
	}
} 

5.2.4 典型例子

例子來自《設計模式》一書

電視機是請求的接收者,遙控器是請求的發送者,遙控器上有一些按鈕,不同的按鈕對應電視機的不同操作。抽象命令角色由一個命令接口來扮演,有三個具體的命令類實現了抽象命令接口,這三個具體命令類分別代表三種操作:打開電視機、關閉電視機和切換頻道。顯然,電視機遙控器就是一個典型的命令模式應用實例。

抽象命令類:

public interface AbstractCommand
{
	public void execute();
}

具體的命令類:

換台

public class TVChangeCommand implements AbstractCommand
{
	private Television tv;
	public TVChangeCommand()
	{
		tv = new Television();
	}
	public void execute()
	{
		tv.changeChannel();
	}
}

關機

public class TVCloseCommand implements AbstractCommand
{
	private Television tv;
	public TVCloseCommand()
	{
		tv = new Television();
	}
	public void execute()
	{
		tv.close();
	}
}

開機

public class TVOpenCommand implements AbstractCommand
{
	private Television tv;
	public TVOpenCommand()
	{
		tv = new Television();
	}
	public void execute()
	{
		tv.open();
	}
}

接收者Receiver類:

public class Television
{
	public void open()
	{
		System.out.println("打開電視機!");
	}
	
	public void close()
	{
		System.out.println("關閉電視機!");		
	}
	
	public void changeChannel()
	{
		System.out.println("切換電視頻道!");
	}
}

調用者(Invoker)類

public class Controller
{
	private AbstractCommand openCommand,closeCommand,changeCommand;
	
	public Controller(AbstractCommand openCommand,AbstractCommand closeCommand,AbstractCommand changeCommand)
	{
		this.openCommand=openCommand;
		this.closeCommand=closeCommand;
		this.changeCommand=changeCommand;
	}
	
	public void open()
	{
		openCommand.execute();
	}
	
	public void change()
	{
		changeCommand.execute();
	}	

	public void close()
	{
		 closeCommand.execute();	
	}
}

5.2.5 適用場景

  • 系統需要支持命令的撤銷(Undo)操作和恢復(Redo)操作。
  • 系統需要將一組操作組合在一起,即支持宏命令。
  • 系統需要在不同的時間指定請求、將請求排隊和執行請求。
  • 系統需要將請求調用者和請求接收者解耦,使得調用者和接收者不直接交互。

5.3 解釋器模式

5.3.1 模式定義

解釋器模式(Interpreter Pattern):定義語言的文法,並且建立一個解釋器來解釋改語言中的句子,這里的“語言”意思是規定格式和語法的代碼,所以解釋器模式是一種類行為型模式

5.3.2 模式角色

  • Context: 環境類
  • Client: 客戶類
  • AbstractExpression: 抽象表達式
  • TerminalExpression: 終結符表達式
  • NonterminalExpression: 非終結符表達式

5.3.3 模式分析

模式表示,可以使用文法規則或抽象語法樹來表示語言

文法規則實例:

  • expression ::= value | symbol
  • symbol ::= expression '+' expression | expression '-' expression
  • value ::= an integer //一個整數值

在文法規則定義中可以使用一些符號來表示不同的含義,如使用“|”表示或,使用“{”和“}”表示組合,使用“*”表示出現0次或多次等,其中使用頻率最高的符號是表示或關系的“|” 。

除了使用文法規則來定義一個語言,在解釋器模式中還可以通過一種稱之為抽象語法樹(Abstract Syntax Tree, AST)的圖形方式來直觀地表示語言的構成,每一棵抽象語法樹對應一個語言實例。
在這里插入圖片描述

5.3.4 典型例子

典型的解釋器模式例子:

抽象表達式類:

public abstract class AbstractExpression
{
	public abstract void interpret(Context ctx);
} 

終結符表達式類:

public class TerminalExpression extends AbstractExpression
{
	public void interpret(Context ctx)
	{
		//對於終結符表達式的解釋操作
	}
} 

非終結符表達式類:

public class NonterminalExpression extends AbstractExpression
{
	private AbstractExpression left;
	private AbstractExpression right;
	
	public NonterminalExpression(AbstractExpression left,AbstractExpression right)
	{
		this.left=left;
		this.right=right;
	}
	
	public void interpret(Context ctx)
	{
		//遞歸調用每一個組成部分的interpret()方法
		//在遞歸調用時指定組成部分的連接方式,即非終結符的功能
	}	
} 

環境類代碼:

public class Context
{
    private HashMap map = new HashMap();
    public void assign(String key, String value)
    {
        //往環境類中設值
    }
public String lookup(String key)    
{
        //獲取存儲在環境類中的值
    }
} 

例子來自《設計模式》一書

5.4 迭代器模式

5.4.1 模式定義

迭代器模式(Iterator Pattern):提供一種方法來訪問聚合對象,而不用暴露這個對象的內部表示,其別名為游標(Cursor),所以迭代器模式是一種對象行為型。

5.4.2 模式角色

  • Iterator:抽象迭代器
  • ConcreteIterator:具體迭代器
  • Aggregate:抽象聚合類
  • ConcreteAggregate:具體聚合類

5.4.3 模式分析

對於迭代器模式來說,一個聚合可以有多個遍歷。在迭代器模式中,提供了一個外部的迭代器對聚合對象進行訪問和遍歷,迭代器定義了一個訪問聚合對象的接口,可以跟蹤遍歷元素,了解哪些元素已經遍歷過而哪些沒有。

迭代器模式中應用了工廠方法模式,聚合類充當工廠類,而迭代器充當產品類

迭代器模式本質

迭代器模式本質:將聚合對象存儲的內部數據提取出來,封裝到一個迭代器中,通過專門的迭代器來遍歷聚合對象的內部數據,這就是迭代器模式的本質

聚合對象主要職責

聚合對象主要有兩個職責:一是存儲內部數據;二是遍歷內部數據;最基本的職責還是存儲內部數據

5.4.4 典型例子

例子來自:《設計模式》一書

自定義迭代器

自定義迭代器
Client:客戶端調用
MyIterator:抽象迭代器
MyCollection:抽象聚合類
NewCollection:具體聚合類
NewIterator:具體迭代器

interface MyCollection
{
	MyIterator createIterator();
}
interface MyIterator
{
	void first();
	void next();
	boolean isLast();
	Object currentItem();
}

class NewCollection implements MyCollection
{
   private Object[] obj={"dog","pig","cat","monkey","pig"};
   public MyIterator createIterator()
   {
  	  return new NewIterator();
   }
   
   private class NewIterator implements MyIterator
   {
   	private int currentIndex=0;
   	
   	public void first()
   	{
   	  	currentIndex=0;
   	}
   	
	public void next()
	{
		if(currentIndex<obj.length)
		{
			currentIndex++;
		}
	}
	
	public void previous()
	{
		if(currentIndex>0)
		{
			currentIndex--;
		}
	}	
	
	public boolean isLast()
	{
		return currentIndex==obj.length;
	}
	
	public boolean isFirst()
	{
		return currentIndex==0;
	}
	
	public Object currentItem()
	{
		return obj[currentIndex];
	}
	
   }
}

class Client
{
	public static void process(MyCollection collection)
	{
		MyIterator i=collection.createIterator();
		
		while(!i.isLast())
		{
			System.out.println(i.currentItem().toString());
			i.next();
		}
	}
	
	public static void main(String a[])
	{
		MyCollection collection=new NewCollection();
		process(collection);
	}
}

5.4.5 適用場景

在以下的情況可以使用迭代器模式:

  • 訪問一個聚合對象的內容而無須暴露它的內部表示。
  • 需要為聚合對象提供多種遍歷方式。
  • 為遍歷不同的聚合結構提供一個統一的接口。

5.5 中介者模式

5.5.1 模式定義

中介者模式(Mediator Pattern):中介者模式就是用一個中介對象來封裝一系列的對象的交互,使各對象之間不需要顯式地相互作用,降低對象之間的耦合度,中介者是一種對象行為型模式。

所以中介者模式適用於對象之間存在大量的關聯的情況,假如一個對象改變了,我們就需要跟蹤其關聯對象,做出對於調整,耦合度是很大的,所以就可以用中介者模式來降低耦合度。

5.5.2 模式角色

中介者模式包括如下角色:

  • Mediator:抽象中介者
  • ConcreteMediator:具體中介者
  • Colleague:抽象同事類
  • ConcreteColleague:具體同事類

5.5.3 模式分析

模式作用
中介者模式起到中轉的作用,當同事類需要調用時,調用中介者就好,不需要調用同事類,中介者模式將同事對象之間的關系行為進行封裝,起到了協調的作用

模式優缺點
中介者模式優點:

  • 簡化了對象之間的交互
  • 減少子類生成
  • 解耦各同事類
  • 簡化各同事類的設計和實現

中介者模式缺點:

  • 由於對象之間的交互細節處理都放在中介者這里,所以具體中介者類就會隨着對象的增多而變得越來越復雜,使中介者類維護起來很困難

模式經典代碼
抽象中介者類:

public abstract class Mediator
{
	protected ArrayList colleagues;
	public void register(Colleague colleague)
	{
		colleagues.add(colleague);
	}
	
	public abstract void operation();
}

具體中介者類:

public class ConcreteMediator extends Mediator
{
	public void operation()
	{
		......
		((Colleague)(colleagues.get(0))).method1();
		......
	}
} 

抽象同事類:

public abstract class Colleague
{
	protected Mediator mediator;
	
	public Colleague(Mediator mediator)
	{
		this.mediator=mediator;
	}
	
	public abstract void method1();
	
	public abstract void method2();
} 

具體同事類:

public class ConcreteColleague extends Colleague
{
	public ConcreteColleague(Mediator mediator)
	{
		super(mediator);
	}
	
	public void method1()
	{
		......
	}
	
	public void method2()
	{
		mediator.operation1();
	}
} 

5.5.4 典型例子

例子來自:《設計模式》一書

實例:虛擬聊天室
某論壇系統欲增加一個虛擬聊天室,允許論壇會員通過該聊天室進行信息交流,普通會員(CommonMember)可以給其他會員發送文本信息,鑽石會員(DiamondMember)既可以給其他會員發送文本信息,還可以發送圖片信息。該聊天室可以對不雅字符進行過濾,還可以對發送的圖片大小進行控制。用中介者模式設計該虛擬聊天室。

抽象同事類
定義一個Member類,屬於抽象同事類:

public abstract class Member
{
	protected AbstractChatroom chatroom;
	protected String name;
	
	public Member(String name)
	{
		this.name=name;
	}
	
	public String getName()
	{
		return name;
	}
	
	public void setName(String name)
	{
		this.name=name;
	}
	
	public AbstractChatroom getChatroom()
	{
		return chatroom;
	}
	
    public void setChatroom(AbstractChatroom chatroom)
    {
    	this.chatroom=chatroom;
    }
	
	public abstract void sendText(String to,String message);
	public abstract void sendImage(String to,String image);

    public void receiveText(String from,String message)
    {
    	System.out.println(from + "發送文本給" + this.name + ",內容為:" + message);
    }
    
    public void receiveImage(String from,String image)
    {
    	System.out.println(from + "發送圖片給" + this.name + ",內容為:" + image);
    }	
}

具體同事類
具體同事類,繼承抽象同事類Member:

普通會員

public class CommonMember extends Member
{
	public CommonMember(String name)
	{
		super(name);
	}
	
	public void sendText(String to,String message)
	{
	    System.out.println("普通會員發送信息:");
	    chatroom.sendText(name,to,message);  //發送
	}
	
	public void sendImage(String to,String image)
	{
		System.out.println("普通會員不能發送圖片!");
	}
}

磚石會員

public class DiamondMember extends Member
{
	public DiamondMember(String name)
	{
		super(name);
	}
	
	public void sendText(String to,String message)
	{
	    System.out.println("鑽石會員發送信息:");
	    chatroom.sendText(name,to,message);  //發送
	}
	
	public void sendImage(String to,String image)
	{
		System.out.println("鑽石會員發送圖片:");
	    chatroom.sendImage(name,to,image);  //發送
	}
}

抽象中介者類

抽象的中介者類,定義聊天室具體有功能方法

public abstract class AbstractChatroom
{
	public abstract void register(Member member);
	public abstract void sendText(String from,String to,String message);
	public abstract void sendImage(String from,String to,String message);
}

具體中介者類

聊天室功能實現,不需要同事類之間相互調用

import java.util.*;

public class ChatGroup extends AbstractChatroom
{
	private Hashtable members=new Hashtable();
	
	public void register(Member member)
	{
		if(!members.contains(member))
		{
			members.put(member.getName(),member);
			member.setChatroom(this);
		}
	}
	
   public void sendText(String from,String to,String message)
   {
   	  Member member=(Member)members.get(to);
   	  String newMessage=message;
   	  newMessage=message.replaceAll("不雅字符","*");
	  member.receiveText(from,newMessage);
   }
   
   public void sendImage(String from,String to,String image)
   {
   	  Member member=(Member)members.get(to);
   	  //模擬圖片大小判斷
   	  if(image.length()>5)
   	  {
   	  	  System.out.println("圖片太大,發送失敗!");
   	  }
   	  else
   	  {
   	  	  member.receiveImage(from,image);
   	  }
   }
}

5.5.5 模式應用

  • 中介者模式是事件驅動類軟件中應用比較多,中介者模式充當組件之間調用的中介,對組件調用進行協調
  • MVC是JavaEE的一個基本模式,此時控制器Controller作為一個中介者,負責視圖對象View和模型對象Model之間的交互,

5.6 備忘錄模式

5.6.1 模式定義

備忘錄模式(Memento Pattern):備忘錄模式的定義是在不破壞封裝的前提下,捕獲一個對象的內部狀態,並將該對象之外保存這個狀態,這樣可以在以后將對象恢復到原先保存的狀態。所以備忘錄模式就是一種對象行為型模式。

5.6.2 模式角色

備忘錄模式包括下面角色

  • Originator(原發器)
  • Memento(備忘錄)
  • Caretaker(負責人)

在這里插入圖片描述

備忘錄模式包括原發器類,備忘錄類和負責人類。原發器可以創建一個備忘錄,備忘錄類存儲原發器類的內部狀態,根據原發器來決定保存哪些內部狀態,負責人類負責保存備忘錄

5.6.3 模式分析

備忘錄模式主要應用於備份或者回退操作,為了使軟件使用更友好,通常都有回退功能,軟件一般也要提供回退機制,而要實現回退,就必須事先備份好狀態信息,所以有了備忘錄模式就有實現系統回退到某一個特定的歷史狀態。

備忘錄對象用於存儲另外一個對象內部狀態的快照對象,所以備忘錄模式又可以稱之為快照模式(Snapshot Pattern)或Token模式

典型代碼:

原發器類:

public class Originator {
  private String state;
  public Originator(){}
  // 創建一個備忘錄對象
  public Memento createMemento(){
    return new Memento(this);
  }
  // 根據備忘錄對象恢復原發器狀態
  public void restoreMemento(Memento m){
     state = m.state;
    }
    public void setState(String state)
    {
        this.state=state;
    }
    public String getState()
    {
        return this.state;
    }
}

備忘錄類:

public class Memento {
  private String state;
  public Memento(Originator o){
    state = o.state;
    }
    public void setState(String state)
    {
          this.state=state;
    }
    public String getState()
    {
           return this.state;
     }
} 

負責人類:

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);
   }
}

5.6.4 模式例子

實例:用戶信息操作撤銷
某系統提供了用戶信息操作模塊,用戶可以修改自己的各項信息。為了使操作過程更加人性化,現使用備忘錄模式對系統進行改進,使得用戶在進行了錯誤操作之后可以恢復到操作之前的狀態。

本例子來自《設計模式》一書

原發器類,創建備忘錄類

package dp.memento;

public class UserInfoDTO
{
	private String account;
	private String password;
	private String telNo;
	
	public String getAccount()
	{
		return account;
	}
	
	public void setAccount(String account)
	{
		this.account=account;
	}

	public String getPassword()
	{
		return password;
	}
	
	public void setPassword(String password)
	{
		this.password=password;
	}
	
	public String getTelNo()
	{
		return telNo;
	}
	
	public void setTelNo(String telNo)
	{
		this.telNo=telNo;
	}
		
	public Memento saveMemento()
	{
		return new Memento(account,password,telNo);
	}
	
	public void restoreMemento(Memento memento)
	{
		this.account=memento.getAccount();
		this.password=memento.getPassword();
		this.telNo=memento.getTelNo();
	}
	
	public void show()
	{
		System.out.println("Account:" + this.account);
		System.out.println("Password:" + this.password);
		System.out.println("TelNo:" + this.telNo);		
	}
}

備忘錄類,保存原發器類狀態:

package dp.memento;

class Memento
{
	private String account;
	private String password;
	private String telNo;
	
	public Memento(String account,String password,String telNo)
    {
    	this.account=account;
    	this.password=password;
    	this.telNo=telNo;
    }
	public String getAccount()
	{
		return account;
	}
	
	public void setAccount(String account)
	{
		this.account=account;
	}

	public String getPassword()
	{
		return password;
	}
	
	public void setPassword(String password)
	{
		this.password=password;
	}
	
	public String getTelNo()
	{
		return telNo;
	}
		
	public void setTelNo(String telNo)
	{
		this.telNo=telNo;
	}
}

負責人類,創建備忘錄:

package dp.memento;

public class Caretaker
{
	private Memento memento;
	public Memento getMemento()
	{
		return memento;
	}
	public void setMemento(Memento memento)
	{
		this.memento=memento;
	}
}

5.6.5 模式應用

  • 軟件里的存檔操作
  • Windows 里的 ctri + z。
  • IE 中的后退操作
  • 數據庫的事務管理
    ....

5.7 觀察者模式

5.7.1 模式定義

觀察者模式(Observer Pattern):觀察者模式定義對象間的一種一對多依賴關系,使得每當一個對象狀態發生改變時,其相關依賴的對象皆得到通知並且被自動更新。不過觀察者只能知道目標發送了改變,而不能知道具體怎么改變的。

5.7.2 觀察者角色

觀察者模式包含如下角色:

Subject:目標

ConcreteSubject:具體目標

Observer:觀察者

ConcreteObserver:具體觀察者

5.7.3 Observer模式”push”和”pull”數據

具體Subject可以通過兩種方式通知具體觀察者更新數據:

①push數據方式:具體Subject將變化后的數據全部交給具體觀察者;

②pull數據方式:具體Subject提供獲得數據的方法,具體觀察者調用具體主題提供的方法獲得數據。

5.7.4 典型列子

PS:代碼例子來自《圖說設計模式》

抽象目標類

import java.util.*;
public abstract class Subject
{
            protected ArrayList observers = new ArrayList();
	public abstract void attach(Observer observer);
	public abstract void detach(Observer observer);
	public abstract void notify();
} 

具體目標類

public class ConcreteSubject extends Subject
{
	public void attach(Observer observer)
	{
		observers.add(observer);
	}
	
	public void detach(Observer observer)
	{
		observers.remove(observer);
	}
	
	public void notify()
	{
		for(Object obs:observers)
		{
			((Observer)obs).update();
		}
	}	
} 

抽象觀察者類

public interface Observer
{
	public void update();
} 

具體觀察者類

public class ConcreteObserver implements Observer
{
	public void update()
	{
		//具體更新代碼
	}
} 

還有一個不錯的例子可以參考《Head First 設計模式》里的氣象局例子

5.7.5 模式優缺點

觀察者優點:下面簡要描述一下,觀察者可以實現表現層和數據邏輯層的分離;觀察者模式在觀察目標和觀察者之間建立一個抽象的耦合

觀察者缺點:如果觀察者類和目標類之間有循環關聯,很容易導致系統奔潰;如果觀察者太多的話,通知所有的觀察者將花費很多時間

5.7.6 模式應用

Swing、RMI、JDK內置的java.util.Observer接口和java.util.Observable類都是觀察者模式的應用

5.7.7 經典氣象局例子

PS:本列子來自《Head First 設計模式》
自己看《Head First設計模式》,本博客僅僅是自己做做筆記
下面的例子來自《Head First設計模式》一書,推薦讀者去學習
這里寫圖片描述

主題接口,定義一個主題接口


public interface Subject {
	//注冊觀察者
	public void registerObserver(Observer o);
	//remove觀察者
	public void removeObserver(Observer o);
	//通知觀察者
	public void notifyObservers();
}

weatherDate類,其實就是主題接口的實現類,weatherData類實現Subject接口,主要用來注冊觀察者,通知觀察者等等,當數據變化時,即時通知注冊的觀察者,代碼實現是通過循環遍歷,觀察者再調用更新數據接口來實現,《Head First設計模式》一書提供了基於JDK的內置類來實現的列子,讀者可以去看看


import java.util.ArrayList;

public class WeatherData implements Subject {
	private ArrayList observers;
	private float temperature;
	private float humidity;
	private float pressure;

	public WeatherData() {
		observers = new ArrayList();//實例對象,通過數組列表來存在觀察者對象
	}

	public void registerObserver(Observer o) {
		observers.add(o);//注冊觀察者
	}

	public void removeObserver(Observer o) {
		int i = observers.indexOf(o);
		if (i >= 0) {
			observers.remove(i);//remove觀察者對象
		}
	}

	public void notifyObservers() {//循環遍歷,通知所有注冊的觀察者
		for (int i = 0; i < observers.size(); i++) {
			Observer observer = (Observer)observers.get(i);
			observer.update(temperature, humidity, pressure);
		}
	}

	public void measurementsChanged() {
		notifyObservers();
	}

	public void setMeasurements(float temperature, float humidity, float pressure) {
		this.temperature = temperature;
		this.humidity = humidity;
		this.pressure = pressure;
		measurementsChanged();
	}

	// other WeatherData methods here

	public float getTemperature() {
		return temperature;
	}

	public float getHumidity() {
		return humidity;
	}

	public float getPressure() {
		return pressure;
	}
}

觀察者接口類

public interface Observer {
	public void update(float temp, float humidity, float pressure);
}

觀察者接口實現類

public interface DisplayElement {
	public void display();
}

public class CurrentConditionsDisplay implements Observer, DisplayElement {
	private float temperature;
	private float humidity;
	public CurrentConditionsDisplay(Subject weatherData) {
		weatherData.registerObserver(this);//注冊觀察者
	}
	/更新數據
	public void update(float temperature, float humidity, float pressure) {
		this.temperature = temperature;
		this.humidity = humidity;
		display();
	}

	public void display() {
		System.out.println("Current conditions: " + temperature
			+ "F degrees and " + humidity + "% humidity");
	}
}

控制台打印一下

public class WeatherStation {

	public static void main(String[] args) {
		WeatherData weatherData = new WeatherData();

		CurrentConditionsDisplay currentDisplay =
			new CurrentConditionsDisplay(weatherData);
		StatisticsDisplay statisticsDisplay = new StatisticsDisplay(weatherData);
		ForecastDisplay forecastDisplay = new ForecastDisplay(weatherData);

		weatherData.setMeasurements(80, 65, 30.4f);
		weatherData.setMeasurements(82, 70, 29.2f);
		weatherData.setMeasurements(78, 90, 29.2f);
	}
}

5.8 狀態模式

5.8.1 模式定義

一個對象在其內部狀態改變時改變其行為,這個對象我們可以稱為狀態對象,所以狀態模式是一種對象行為型模式。

5.8.2 模式結構

  • Context:環境類
    Context類也可以稱之為上下文類,實際上就是擁有狀態的對象,可以理解為狀態管理器。

  • State:抽象狀態類
    抽象狀態類可以是一個接口類或者抽象類,反正實現的話都是通過具體狀態類。抽象狀態類,封裝環境類不同狀態下的所有動作方法。

  • ConcreteState:具體狀態類
    具體實現類就比較容易理解了,就是繼承抽象狀態類,實現具體的方法,不一定所有的抽象方法都有重寫,根據環境類狀態的改變進行重寫就好,其實也是根據狀態改變改變動作方法。

5.8.3 模式適用場景

  • 狀態模式適用於行為隨狀態改變的業務場景,比如狀態改變了,行為也會做成改變。
  • 業務代碼中很多條件的情況,加入一些代碼有很多的if...else,並且經常改變,這種情況就可以使用狀態模式進行編寫。

5.8.4 業務應用場景:

  • 比如OA的審批就可以應用狀態模式,發起申請之后,審批狀態可能有受理,批准等等狀態,每個狀態具有不一樣的動作;

  • 游戲的角色扮演,每次游戲版本升級都是會出現狀態動作的改變,用狀態模式進行設計,可以提高程序可拓展性;

5.8.5 簡單實例

上下文類:

public class Context {
   private State state;
 
   public Context(){
      state = null;
   }
 
   public void setState(State state){
      this.state = state;     
   }
 
   public State getState(){
      return state;
   }
}

抽象狀態類:

public abstract class State {
   public void doAction(Context context);
}

具體狀態類:

public class ConcreteState implements State {
 
   public void doAction(Context context) {
      System.out.println("Player is in start state");
      context.setState(this); 
   }
 
   public String toString(){
      return "Start State";
   }
}

調用代碼:

public class StatePatternDemo {
   public static void main(String[] args) {
      Context context = new Context();
 
      ConcreteState concreteState = new ConcreteState();
      concreteState.doAction(context);
 
      System.out.println(context.getState().toString());
 
   }
}

5.8.6 狀態模式分類

狀態模式分為簡單狀態模式和可切換狀態模式。

  • 簡單狀態模式
    簡單狀態模式就是指狀態相對獨立,具體狀態類可以根據抽象狀態類進行編程,也就是不需要用環境類中的setState方法改變狀態

  • 可切換狀態的狀態模式
    可切換狀態模式是狀態可以變換的,狀態變換了,具體狀態類在調用時就要使用環境類的setState改變狀態

5.9 策略模式

5.9.1 模式定義

策略模式:定義一系列算法,然后將每一個算法封裝起來,並將它們可以互相替換。也就是將一系列算法封裝到一系列策略類里面。策略模式是一種對象行為型模式。策略模式符合“開閉原則“

Strategy Pattern: Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it.

5.9.2 模式角色

  • Context :環境類
  • Strategy:抽象策略類
  • ConcreteStrategy:具體策略類

5.9.3 簡單實例

不用策略模式的情況:

public class Context
{
    ……
    public void method(String type)  
    {
        ......
        if(type == "strategyA")
        {
            //算法A
        }
        else if(type == "strategyB")
        {
            //算法B
        }
        else if(type == "strategyC")
        {
            //算法C
        }
        ......
    }
    ……
} 

抽象策略類:

public abstract class AbstractStrategy
{
    public abstract void method();  
} 

具體策略類:

public class ConcreteStrategyA extends AbstractStrategy
{
    public void method()
    {
        //算法A
    }
} 

環境類:

public class Context
{
    private AbstractStrategy strategy;
    public void setStrategy(AbstractStrategy strategy)
    {
        this.strategy= strategy;
    }
    public void method()
    {
        strategy.method();
    }
} 

客戶端調用:

Context context = new Context();
AbstractStrategy strategy;
strategy = new ConcreteStrategyA();
context.setStrategy(strategy);
context.method();

5.9.4 策略模式和狀態模式對比

相同點:

  • 策略模式和狀態模式都是屬於行為型設計模式,也同樣是對象行為型設計模式,非類行為型設計模式。

  • 策略模式和狀態模式有點類似,都符合”閉合原則“

  • 兩個設計模式都可以用於減少代碼大量的if...else

不同點:

  • 具體使用策略模式還是狀態模式,可以通過環境類的狀態而定,有很多狀態的話,就使用狀態模式。

  • 使用策略模式時,環境類需要選擇一個確定的策略類,也就是客戶端調時需要關心具體狀態,根據需要調用;而狀態模式是不需要的,在狀態模式里,環境類是要放在一個具體狀態中的,也就是根據環境類的狀態改變進行調用狀態類的算法

對狀態模式不是很熟悉,可以參考我以前寫的一篇博客
https://blog.csdn.net/u014427391/article/details/85219470

5.10 模板方法模式

5.10.1 模式定義

模板方法模式就是在一個抽象類中定義一些骨架方法,然后通過類繼承的方法,將一些方法延遲到繼承類里。模板方法模式是一種類行為型模式,是一種比較常用的方法。不屬於對象行為型模式,因為只是通過類繼承實現。

Template Method Pattern: Define the skeleton of an algorithm in an operation, deferring some steps to subclasses. Template Method lets subclasses redefine certain steps of an algorithm without changing the algorithm's structure.

5.10.2 模式角色

  • AbstractClass(抽象類)
  • ConcreteClass(實現類)

5.10.3 模式分析

模板方法,將基本方法封裝組合在一個抽象類中形成一個總算法或者說一個總行為的方法。

模板方法的組成部分:

  • 抽象方法(Abstract Method)
  • 具體方法(Concrete Method)
  • 鈎子方法(HookMethod)

抽象類代碼:

public abstract class AbstractClass
{
    public void templateMethod()  //模板方法
    {
        primitiveOperation1();
        primitiveOperation2();
        primitiveOperation3();
    }
    public void operation1()    //基本方法-具體方法
    {
        //實現代碼
    }
    public abstract void operation2();    //基本方法-抽象方法
    public void operation3()    //基本方法-鈎子方法
    {
    }
} 

具體實現類代碼:

public abstract class ConcreteClass
{
   /**
    * 基本方法-抽象方法
    */
    public abstract void operation2(){
        //具體實現
    }    
    
    /**
    * 基本方法-鈎子方法
    */
    public void operation3(){
        //具體實現
    }
} 

子類不顯性調用父類的方法,而是通過繼承的方法來實現具體的業務方法,也就是說父類控制子類的調用,這種機制叫好萊塢原則。好萊塢原則的定義為:“不要給我們打電話,我們會給你打電話(Don‘t call us, we’ll call you)”。

5.10.4 具體例子

數據庫操作的例子。數據庫操作分為連接、打開、使用、關閉步驟。現在要使用mysql、oracle、db2等等關系型數據庫進行數據庫操作工具類的編寫。而對於使用這幾種不同的數據庫,其實只是連接的代碼不同,而其它操作的代碼都是差不多的,所以可以使用模板方法進行代碼復用。

ps:這個例子來自《設計模式》一書,稍微改了一點

模板方法

public abstract class DBOperator
{   
//抽象方法
	public abstract void connDB();
    public void openDB()
	{
		System.out.println("打開數據庫");
	}
	public void useDB()
	{
		System.out.println("使用數據庫");
	}
	public void closeDB()
	{
		System.out.println("關閉數據庫");	
	}
	//模板方法
   public void process()
   {
    connDB();
   	openDB();
   	useDB();
   	closeDB();
   }
}

mysql數據庫

public class DBOperatorMysql extends DBOperator
{
	public void connDB()
	{
		System.out.println("使用JDBC-ODBC橋接連接Mysql數據庫");		
	}
}

Oracle數據庫

public class DBOperatorOracle extends DBOperator
{
	public void connDB()
	{
		System.out.println("使用JDBC-ODBC橋接連接Oracle數據庫");		
	}
}

調用


class Client
{
	public static void main(String a[])
	{
		DBOperator db1;
		
		db1=new DBOperatorMysql();
		db1.process();
		db1=new DBOperatorOracle();
		db1.process();
	}
}

5.10.5 模式應用場景

  • Spring、Struts2框架的應用,比如框架的初始化就有應用
    ...

5.11 訪問者模式

5.11.1 模式定義

訪問者模式:表示一個作用於某對象結構中的各元素的操作,它使我們可以在不改變各元素的類的前提下定義作用於這些元素的新操作。所以訪問者模式是一種對象行為型模式。

5.11.2 模式角色

訪問者模式包括如下角色:

  • Vistor(抽象訪問者)
  • ConcreteVisitor(具體訪問者)
  • Element(抽象元素)
  • ConcreteElement(具體元素)
  • ObjectStructure(對象結構)

5.11.3 模式分析

訪問者模式的對象結構存儲了不同類型的元素對象,以供不同的訪問者訪問

訪問者模式包括了兩個層次結構,一個是訪問者層次結構,提供了抽象訪問者和具體訪問者,一個是元素層級結構,提供了抽象元素和具體元素

相同的訪問者可以以不同的方式訪問不同的元素,相同的元素可以接受不同的訪問者以不同訪問方式訪問。

典型代碼:

抽象訪問者類

public abstract class Visitor
{
	public abstract void visit(ConcreteElementA elementA);
	public abstract void visit(ConcreteElementB elementB);
	public void visit(ConcreteElementC elementC)
	{
		//元素ConcreteElementC操作代碼
	}
} 

具體訪問者類

public class ConcreteVisitor extends Visitor
{
	public void visit(ConcreteElementA elementA)
	{
		//元素ConcreteElementA操作代碼
	}
	public void visit(ConcreteElementB elementB)
	{
		//元素ConcreteElementB操作代碼
	}
} 

抽象元素類

public interface Element
{
	public void accept(Visitor visitor);
} 

具體元素類

public class ConcreteElementA implements Element
{
	public void accept(Visitor visitor)
	{
		visitor.visit(this);
	}
	
	public void operationA()
	{
		//業務方法
	}
} 

對象結構類

public class ObjectStructure
{
	private ArrayList list=new ArrayList();
	public void accept(Visitor visitor)
	{
		Iterator i=list.iterator();
		
		while(i.hasNext())
		{
			((Element)i.next()).accept(visitor);	
		}
	}
	public void addElement(Element element)
	{
		list.add(element);
	}
	public void removeElement(Element element)
	{
		list.remove(element);
	}
} 

5.11.4 模式例子

本例子來自《設計模式》一書

實例一:購物車
顧客在超市中將選擇的商品,如蘋果、圖書等放在購物車中,然后到收銀員處付款。在購物過程中,顧客需要對這些商品進行訪問,以便確認這些商品的質量,之后收銀員計算價格時也需要訪問購物車內顧客所選擇的商品。此時,購物車作為一個ObjectStructure(對象結構)用於存儲各種類型的商品,而顧客和收銀員作為訪問這些商品的訪問者,他們需要對商品進行檢查和計價。不同類型的商品其訪問形式也可能不同,如蘋果需要過秤之后再計價,而圖書不需要。使用訪問者模式來設計該購物過程。

抽象的訪問者類

public abstract class Visitor
{
	protected String name;
	
	public void setName(String name)
	{
		this.name=name;
	}
	
	public abstract void visit(Apple apple);
	
	public abstract void visit(Book book);
}

具體的訪問者類:

public class Saler extends Visitor
{
	public void visit(Apple apple)
	{
		System.out.println("收銀員" + name + "給蘋果過秤,然后計算其價格。");
	}
	
	public void visit(Book book)
	{
		System.out.println("收銀員" + name + "直接計算書的價格。");
	}
}
public class Customer extends Visitor
{
	public void visit(Apple apple)
	{
		System.out.println("顧客" + name + "選蘋果。");
	}
	
	public void visit(Book book)
	{
		System.out.println("顧客" + name + "買書。");
	}
}

元素接口類:

public interface Product
{
	void accept(Visitor visitor);
}

具體的元素類:

public class Apple implements Product
{
  public void accept(Visitor visitor)
  {
      visitor.visit(this);
  }	
}
public class Book implements Product
{
  public void accept(Visitor visitor)
  {
      visitor.visit(this);
  }	
}

對象結構類:

import java.util.*;

public class BuyBasket
{
	private ArrayList list=new ArrayList();
	
	public void accept(Visitor visitor)
	{
		Iterator i=list.iterator();
		
		while(i.hasNext())
		{
			((Product)i.next()).accept(visitor);	
		}
	}
	
	public void addProduct(Product product)
	{
		list.add(product);
	}
	
	public void removeProduct(Product product)
	{
		list.remove(product);
	}
}

客戶端類:

public class Client
{
	public static void main(String a[])
	{
		Product b1=new Book();
		Product b2=new Book();
		Product a1=new Apple();
		Visitor visitor;
		
        BuyBasket basket=new BuyBasket();
        basket.addProduct(b1);
        basket.addProduct(b2);
        basket.addProduct(a1);
        
        visitor=(Visitor)XMLUtil.getBean();
        
        visitor.setName("張三");
        	
        basket.accept(visitor);
	}
}
import javax.xml.parsers.*;
import org.w3c.dom.*;
import org.xml.sax.SAXException;
import java.io.*;
public class XMLUtil
{
//該方法用於從XML配置文件中提取具體類類名,並返回一個實例對象
	public static Object getBean()
	{
		try
		{
			//創建文檔對象
			DocumentBuilderFactory dFactory = DocumentBuilderFactory.newInstance();
			DocumentBuilder builder = dFactory.newDocumentBuilder();
			Document doc;							
			doc = builder.parse(new File("config.xml")); 
		
			//獲取包含類名的文本節點
			NodeList nl = doc.getElementsByTagName("className");
            Node classNode=nl.item(0).getFirstChild();
            String cName=classNode.getNodeValue();
            
            //通過類名生成實例對象並將其返回
            Class c=Class.forName(cName);
	  	    Object obj=c.newInstance();
            return obj;
           }   
           	catch(Exception e)
           	{
           		e.printStackTrace();
           		return null;
           	}
		}
}

config.xml

<?xml version="1.0"?>
<config>
    <className>Saler</className>
</config>

5.11.5 模式應用

  • java xml處理的DOM4j,通過訪問者模式的方式來讀取並解析XML文檔,VisitorSupport是DOM4J提供的Visitor接口的默認適配器
public class MyVisitor extends VisitorSupport 
{
    public void visit(Element element)
    {
        System.out.println(element.getName());
    }
    public void visit(Attribute attr)
    {
        System.out.println(attr.getName());
    }
},

....

附錄: 參考教程


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM