原創不易,轉載請註明出處:http://blog.csdn.net/anxpp/article/details/51224293,謝謝!php
文章比較長,讀者能夠經過頂端的目錄選擇要了解的模式,而後經過文章右邊的按鈕快速返回頂部從新選擇一個新的模式瀏覽java
博主精心準備了大量的示例代碼。文章儘可能提供與編程相關的例子,而不是像多數其餘介紹的文章同樣,提供一些感受挺滑稽的例子(那樣的例子可能看完以爲寫得很好,然而仍是不會用...)。node
本文耗費了做者大量時間,還請親們給個贊O(∩_∩)O~mysql
也能夠經過CTRL+F並輸入要了解的模式並跳到對應位置。android
文章中的示例源碼在github上:https://github.com/anxpp/JavaDesignPatternc++
文中未給出UML圖,若是須要請回復說明,本人也能夠畫出須要的設計模式對應的UML圖。git
設計模式是針對某一類問題的最優解決方案,是從許多優秀的軟件系統中總結出的。github
Java中設計模式(java design patterns)一般有23種。web
模式能夠分紅3類:建立型、行爲型和結構型。算法
建立型模式涉及對象的實例化,特色是不讓用戶代碼依賴於對象的建立或排列方式,避免用戶直接使用new建立對象。
建立型模式有如下5個:
工廠方法模式、抽象工廠方法模式、生成器模式、原型模式和單例模式。
行爲型模式涉及怎樣合理的設計對象之間的交互通訊,以及怎樣合理爲對象分配職責,讓設計富有彈性,易維護,易複用。
行爲型模式有如下11個:
責任鏈模式、命令模式、解釋器模式、迭代器模式、中介者模式、備忘錄模式、觀察者模式、狀態模式、策略模式、模板方法模式和訪問者模式。
結構型模式涉及如何組合類和對象以造成更大的結構,和類有關的結構型模式涉及如何合理使用繼承機制;和對象有關的結構型模式涉及如何合理的使用對象組合機制。
結構型模式有如下7個:
適配器模式、組合模式、代理模式、享元模式、外觀模式、橋接模式和裝飾模式。
模式中涉及的重要角色,會在描述中(加粗字體)介紹出來。下面就逐一介紹。
Ensure a class only has one instance,and provide a global point of access to it.
保證一個類僅有一個實例,並提供一個訪問它的全局訪問點。
什麼時候使用
優勢
單例模式概念很簡單,並且也比較經常使用。
在使用這個模式的時候,咱們要考慮是否會在多線程中使用,若是不會應用於多線程,那寫法就足夠簡單:
public class SimpleSingleton {
private static SimpleSingleton instance;
private SimpleSingleton(){}
public static SimpleSingleton getIntance(){
if(instance == null)
instance = new SimpleSingleton();
return instance;
}
}
上例就是一個簡單的單例模式實現,使用了懶加載模式。可是多線程中可能會建立多個實例。下面就介紹多線程中的使用。
若是直接將上面例子應用到多線程中,能夠直接把getInstance()設置爲同步的(synchronized),可是並不高效,任一以後,只能有一個線程能夠調用這個方法,其他的會排隊等待。
因此整個方法作同步不是優解,那就只同步代碼塊就行了。這就引出了雙重檢驗鎖,即在同步塊外檢查一次null,而後再在同步塊內檢查一次。可是最終這種方式也是會有問題的,使用靜態內部類是一種比較好的方式。
單例模式使用很頻繁,也很簡單,但不必定都能寫對,詳細的寫法請參考:如何正確地寫出單例模式。裏面詳細的分析的單例模式的各類寫法。
其餘模式中的示例代碼,有不少時候用到了單例模式,此處就不額外添加例子了。
這裏給出一個推薦的實現方式(枚舉):
別名:虛擬構造(Another Name:Virtual Constructor)。
Define an interface for creating an object,but let subclasses decide which class to instantiate.Factory Method lets a class defer instantiation to subclassess.
定義一個用於建立對象的接口,讓子類決定實例化哪個類。工廠方法使一個類的實例化延遲到其子類。
什麼時候使用
優勢
簡單工廠模式
介紹工廠方法模式前,先介紹一下簡單工廠模式,簡單工廠模式也是一種工廠方法模式。
簡單工廠模式又稱靜態工廠方法模式。從命名上就能夠看出這個模式必定很簡單。它存在的目的很簡單:定義一個用於建立對象的接口。
若是一個一些對象(產品),已經肯定了並不易改變和添加新的產品,那麼久能夠使用簡單工廠模式。下面就是簡單工廠的例子:
//演示簡單工廠
public class SimpleFactory {
public static void main(String args[]) throws Exception{
Factory factory = new Factory();
factory.produce("PRO5").run();
factory.produce("PRO6").run();
}
}
//抽象產品
interface MeizuPhone{
void run();
}
//具體產品X2
class PRO5 implements MeizuPhone{
@Override
public void run() {
System.out.println("我是一臺PRO5");
}
}
class PRO6 implements MeizuPhone{
@Override
public void run() {
System.out.println("我是一臺PRO6");
}
}
//工廠
class Factory{
MeizuPhone produce(String product) throws Exception{
if(product.equals("PRO5"))
return new PRO5();
else if(product.equals("PRO6"))
return new PRO6();
throw new Exception("No Such Class");
}
}
很容易看出,簡單工廠模式是不易維護的,若是須要添加新的產品,則整個系統都須要修改。若是咱們須要添加諸如PRO七、PRO8等產品,直接在工程類中添加便可。可是若是這時候根部不知道還有什麼產品,只有到子類實現時才知道,這時候就須要工廠方法模式。
而在實際應用中,極可能產品是一個多層次的樹狀結構。因爲簡單工廠模式中只有一個工廠類來對應這些產品,因此實現起來是比較麻煩的,那麼工廠方法模式正式解決這個問題的,下面就介紹工廠方法模式。
工廠方法模式
工廠方法模式去掉了簡單工廠模式中工廠方法的靜態屬性,使得它能夠被子類繼承。這樣在簡單工廠模式裏集中在工廠方法上的壓力能夠由工廠方法模式裏不一樣的工廠子類來分擔。
針對上面的例子,若是使用工廠方法模式,即將工廠定義爲一個接口,而後由具體的工廠來肯定須要生成什麼樣的產品,爲了與簡單工廠比較,這裏仍是貼上代碼:
若是瞭解Java的集合框架,那麼它就是一個很好的例子:
Java中的Collection接口的實現都能經過iterator()方法返回一個迭代器,而不一樣的實現的迭代器都在該實現中之內部類的方式對Iterator接口實現的,而後經過iterator()方法返回。那麼,這個iterator()方法就是一種工廠方法。
能夠看到,在這裏抽象產品是Iterator接口,具體產品就是Collection接口的實現中對Iterator接口的實現,構造者是Collection接口,其提供的工廠方法就是Iterator iterator();,具體構造者就是Collection的實現。而工廠方法模式的結構,也就是由前面加粗的4部分組成。
若是對Java容器不熟悉,下面再提供一個例子(模仿Iterator,其實順便也介紹了Iterator):
若是有多種數據結構要遍歷,咱們就須要一種用於遍歷不一樣結構的工具,首先咱們就須要爲這個工具定義一個接口(抽象產品),用於描述如何來遍歷:
//只是須要遍歷一堆數據,那麼只須要2個方法就能夠了
public interface Iterator<T> {
boolean hasNext(); //是否還有下一個元素
T next(); //獲得下一個元素
}
而後就是咱們要遍歷的目標,而這些目標此處咱們暫定爲列表,這就是構造者:
對於List可能有多種實現方式,好比數組和鏈表,此處就簡陋的介紹一下,而這些就是具體構造者,而裏面有遍歷器的具體實現(具體產品),此處之內部類的形式放到了List的實現(具體構造者)裏面,也徹底能夠修改代碼將遍歷器的實現(具體產品)獨立出來:
數組的實現:
package com.anxpp.designpattern.factorymethod;
//方便演示而實現的簡陋的數組list
public class ArrayList<T> implements List<T>{
private int size; //存放的元素個數,會默認初始化爲0
private Object[] defaultList; //使用數組存放元素
private static final int defaultLength = 10;//默認長度
public ArrayList(){ //默認構造函數
defaultList = new Object[defaultLength];
}
@Override
public Iterator<T> iterator() {
return new MyIterator();
}
//添加元素
@Override
public boolean add(T t) {
if(size<=defaultLength){
defaultList[size++] = t;
return true;
}
return false;
}
//遍歷器(具體產品)
private class MyIterator implements Iterator<T>{
private int next;
@Override
public boolean hasNext() {
return next<size;
}
@SuppressWarnings("unchecked")
@Override
public T next() {
return (T) defaultList[next++];
}
}
}
鏈表實現:
使用上述代碼(模式的使用):
package com.anxpp.designpattern.factorymethod;
public class TestUse {
public static void main(String args[]){
//分別定義兩種結構
List<Integer> array = new ArrayList<Integer>();
List<Integer> link = new LinkList<Integer>();
//添加數據
for(int i = 1;i < 8; i++){
array.add(i);
link.add(i);
}
//得到迭代器
Iterator<Integer> ai = array.iterator();
Iterator<Integer> li = link.iterator();
//遍歷並輸出
while(ai.hasNext())
System.out.print(ai.next());
System.out.println();
while(li.hasNext())
System.out.print(li.next());
}
}
控制檯會輸出:
這就是工廠方法模式,其中遍歷器也算是一種迭代器設計模式,後面會介紹。我不會跟你講什麼造車,造輪船,造人,我會給出實際應用。這裏只是其中一種應用的舉例,當一個接口的一系列實現須要另外的對象對其進行相同操做時,咱們就能夠這樣用:在這個接口中定義返回另一個對象的方法(工廠方法),而後再在這個接口的實現中,返回對其操做的對象。
上面這個例子會在迭代器模式中給出完整的實現代碼。
一抽象產品類派生出多個具體產品類;一抽象工廠類派生出多個具體工廠類;每一個具體工廠類只能建立一個具體產品類的實例。 即定義一個建立對象的接口(即抽象工廠類),讓其子類(具體工廠類)決定實例化哪個類(具體產品類)。「一對一」的關係。
與簡單工廠間的取捨:工廠方法模式和簡單工廠模式在定義上的不一樣是很明顯的。工廠方法模式的核心是一個抽象工廠類,而不像簡單工廠模式, 把核心放在一個實類上。工廠方法模式能夠容許不少實的工廠類從抽象工廠類繼承下來, 從而能夠在實際上成爲多個簡單工廠模式的綜合,從而推廣了簡單工廠模式。 反過來說,簡單工廠模式是由工廠方法模式退化而來。設想若是咱們很是肯定一個系統只須要一個實的工廠類, 那麼就不妨把抽象工廠類合併到實的工廠類中去。而這樣一來,咱們就退化到簡單工廠模式了。
能夠看出工廠方法的加入,使得對象的數量成倍增加。當產品種類很是多時,會出現大量的與之對應的工廠對象,這不是咱們所但願的。
若是再分得詳細一點,一個工廠可能不僅是生產手機(如小米除了手機,連電飯鍋都有),但有得工廠智能生成低端的產品,而大一點的工廠可能一般是生成更高端的產品。因此一個工廠是不夠用了,這時,就應該使用抽象工廠來解決這個問題。
別名:配套(Another Name:Kit)
Provide an interface for creating families of related or dependent objects without specifying their concrete classess.
提供一個建立一系列或相互依賴對象的接口,而無須指定他們的具體的類。
什麼時候使用:
優勢:
上述生成魅族產品的例子中,咱們只生產了手機,可是它不止有手機一種產品,可能還有其餘的,好比耳機,爲了還能夠生成耳機,咱們須要對上例進行擴展。
咱們先給出上面生成手機的例子的擴展後的抽象工廠模式代碼,以比較這幾種模式:
//抽象工廠模式
public class AbstractFactory {
public static void main(String args[]){
IFactory bigfactory = new BigFactory();
IFactory smallfactory = new BigFactory();
bigfactory.producePhone().run();
bigfactory.produceHeadset().play();
smallfactory.producePhone().run();
smallfactory.produceHeadset().play();
}
}
//抽象產品*2
interface Headset{
void play();
}
//抽象產品
interface MeizuPhone{
void run();
}
//具體產品*2*2
class PRO5 implements MeizuPhone{
@Override
public void run() {
System.out.println("我是一臺PRO5");
}
}
class MX5 implements MeizuPhone{
@Override
public void run() {
System.out.println("我是一臺MX5");
}
}
class EP21 implements Headset{
@Override
public void play() {
System.out.println("我是一副EP21");
}
}
class EP30 implements Headset{
@Override
public void play() {
System.out.println("我是一臺EP30");
}
}
//抽象工廠
interface IFactory{
MeizuPhone producePhone();
Headset produceHeadset();
}
//具體工廠*2
class BigFactory implements IFactory{
@Override
public MeizuPhone producePhone() {
return new PRO5();
}
@Override
public Headset produceHeadset() {
return new EP30();
}
}
//具體工廠*2
class SmallFactory implements IFactory{
@Override
public MeizuPhone producePhone() {
return new MX5();
}
@Override
public Headset produceHeadset() {
return new EP21();
}
}
在抽象工廠模式中,抽象產品 (AbstractProduct) 多是一個或多個,從而構成一個或多個產品族(Product Family)。 在只有一個產品族的狀況下,抽象工廠模式實際上退化到工廠方法模式(不如上例減去耳機這種產品,就回到工廠方法模式了)。
這樣舉例子其實很空洞,這裏只是爲了比較三種模式,給出抽象的例子才更容易看出區別。
那麼上例中實際應用就是生產迭代器的例子,這裏也對齊擴展來介紹抽象工廠模式。Iterator迭代器是Collection專屬的,可是如今咱們但願能生產Map的迭代器,咱們都知道,Map不是繼承自Collection的,遍歷的方式是不同的,這就至關於2個產品族,接下來咱們就要來實現它。
爲了演示咱們若是實現這個同時能生產Map和Collection的迭代器,我會將例子一步步貼出來:
首先是抽象產品,用來描述迭代器的公共接口:
而後是抽象工廠,用來返回不一樣迭代器:
//抽象工廠
public interface IIteratorFactory<T> {
IIterator<T> iteratorMap(Map<T,Object> m);
IIterator<T> iteratorCollection(Collection<T> c);
}
接下來是具體產品。
Collection的迭代器(具體產品):
Map的迭代器(具體產品):
//具體產品,Map迭代器(用到了代理模式)
public class IteratorMap<T> implements IIterator<T>{
Iterator<Map.Entry<T, Object>> iterator;
public IteratorMap(Map<T,Object> m){
iterator = m.entrySet().iterator();
}
@Override
public boolean hasNext() {
return iterator.hasNext();
}
@Override
public Object next() {
return iterator.next().getValue();
}
}
完成具體產品設計後,咱們就要實現具體的工廠了:
至此,這個小框架就完成了,咱們能夠使用它來遍歷Collection(List,Set,Queue都是集成自它)和Map:
//測試使用
public class TestUse {
public static void main(String args[]){
IIteratorFactory<Integer> factory = new IteratorFactory<>();
Collection<Integer> collection = new ArrayList<Integer>();
Map<Integer, Object> map = new LinkedHashMap<>();
for(int i=0;i<10;i++){
collection.add(i);
map.put(i, i);
}
IIterator<Integer> iteratorCollection = factory.iteratorCollection(collection);
IIterator<Integer> iteratorMap = factory.iteratorMap(map);
while(iteratorCollection.hasNext())
System.out.print(iteratorCollection.next());
System.out.println();
while(iteratorMap.hasNext())
System.out.print(iteratorMap.next());
}
}
輸出:
實際狀況下,咱們可能不該該這麼作,覺得Collection面向一種對象的容器,Map是面向兩種對象的關聯容器,可是此例使用抽象工廠模式確實實現了不一樣容器的 統一遍歷方式。
若是一個容器持有的大量對象,他們都直接或間接集成自某一個類,使用訪問者模式遍歷也是一種很好的方式,具體在後面的訪問者模式中會詳細介紹。
工廠模式主要就涉及上面介紹的三種:
Separate the construction of a complex object from its representation so that the same construction process can create different representations.
將一個複雜對象的構建與它的表示分離,使一樣的構建過程能夠建立不一樣的表示。
什麼時候使用:
優勢:
模式的重心在於分離構建算法和具體的構造實現,從而使構建算法能夠重用。
好比咱們要獲得一個日期,能夠有不一樣的格式,而後咱們就使用不一樣的生成器來實現。
首先是這個類(產品):
//產品
public class MyDate {
String date;
}
而後就是抽象生成器,描述生成器的行爲:
接下來是具體生成器,一個以「-」分割年月日,另外一個使用空格:
//具體生成器
public class DateBuilder1 implements IDateBuilder{
private MyDate myDate;
public DateBuilder1(MyDate myDate){
this.myDate = myDate;
}
@Override
public IDateBuilder buildDate(int y, int m, int d) {
myDate.date = y+"-"+m+"-"+d;
return this;
}
@Override
public String date() {
return myDate.date;
}
}
接下來是指揮官,向用戶提供具體的生成器:
//指揮者
public class Derector {
private IDateBuilder builder;
public Derector(IDateBuilder builder){
this.builder = builder;
}
public String getDate(int y,int m,int d){
builder.buildDate(y, m, d);
return builder.date();
}
}
使用以下:
輸出:
使用不一樣生成器,能夠使原有產品表現得有點不同。
每每在實際的應用中,生成器要作的工做不會這麼簡單,而是相對複雜的(由於其產品通常是比較複雜的),原有構建的維護會轉移到生成器的維護上。
Specify the kinds of objects to create using a prototypical instance,and create new objects by copying this prototype.
用原型實例指定建立對象的種類,而且經過複製這些原型建立新的對象。
什麼時候使用:
優勢:
原型模式要求對象實現一個能夠「克隆」自身的接口,這樣就能夠經過複製一個實例對象自己來建立一個新的實例。這樣一來,經過原型實例建立新的對象,就再也不須要關心這個實例自己的類型,只要實現了克隆自身的方法,就能夠經過這個方法來獲取新的對象,而無須再去經過new來建立。
例子中的抽象原型沒有使用方法名clone(),其緣由下面會介紹。
簡單形式的原型模式:
//具體原型
public class SimplePrototype implements Prototype,Cloneable {
int value;
//clone()實現
@Override
public Object cloneSelf() {
SimplePrototype self = new SimplePrototype();
self.value = value;
return self;
}
//使用
public static void main(String args[]){
SimplePrototype simplePrototype = new SimplePrototype();
simplePrototype.value = 500;
SimplePrototype simplePrototypeClone = (SimplePrototype) simplePrototype.cloneSelf();
System.out.println(simplePrototypeClone.value);
}
}
//抽象原型
interface Prototype{
Object cloneSelf();//克隆自身的方法
}
//客戶端使用
class Client{
SimplePrototype prototype;
public Client(SimplePrototype prototype){
this.prototype = prototype;
}
public Object getPrototype(){
return prototype.cloneSelf();
}
}
簡單的原型模式就是在clone()實現時,new一個新的實例,而後爲成員變量賦值後返回。
Java的原生支持
Java中全部類都直接或間接繼承自Object類,Object類中已有clone()方法:」protected native Object clone() throws CloneNotSupportedException;「,能夠看到權限是protected的,因此僅有子類能夠訪問這個方法,但咱們能夠在子類中重寫這個方法,將訪問權限上調到public,而後方法體裏面return super.clone()。
咱們能看到這個Object方法是可能會拋出異常的,咱們必須實現Cloneable接口,才能夠使用這個方法,不然會拋出「java.lang.CloneNotSupportedException」的異常。這個Cloneable接口實際上是空的,實現它的目的在於讓JVM知道這個對象是能夠可複製的,不然clone()時就會發生異常。下面看演示代碼:
調用這個方法時,成員變量會自動被複制。因此若是須要使用原型模式,Java原生支持就已經很好用了。
除了以上的原生支持,java中還有一種序列化,只須要對象實現Serializable接口。這樣,咱們能夠將對象寫入到流中,能夠保存到文件,也能夠經過網絡發送到其餘地方:
//使用Serializable支持克隆
public class SerializablePrototype implements Serializable{
private static final long serialVersionUID = 1L;
private int i;
private transient int notClone;//transient關鍵字的成員不會被序列化
public int getI() {
return i;
}
public void setI(int i) {
this.i = i;
}
public int getNotClone() {
return notClone;
}
public void setNotClone(int notClone) {
this.notClone = notClone;
}
public void writeToFile(String path) throws Exception{
FileOutputStream outStream = new FileOutputStream(path);
ObjectOutputStream objectOutputStream = new ObjectOutputStream(outStream);
objectOutputStream.writeObject(this);
outStream.close();
}
public SerializablePrototype ReadFromFile(String path) throws Exception{
File file = new File(path);
if(!file.exists())
file.createNewFile();
FileInputStream inStream = new FileInputStream(path);
ObjectInputStream objectOutputStream = new ObjectInputStream(inStream);
Object o= objectOutputStream.readObject();
inStream.close();
return (SerializablePrototype) o;
}
public static void main(String args[]) throws Exception{
String path = "D:/SerializablePrototype.instance";
SerializablePrototype prototype = new SerializablePrototype();
prototype.setI(123);
prototype.setNotClone(456);
prototype.writeToFile(path);
SerializablePrototype prototypeClone = new SerializablePrototype();
prototypeClone = prototype.ReadFromFile(path);
System.out.println(prototypeClone.getI() + " " + prototypeClone.getNotClone());
}
}//輸出:123 0
咱們來分析上例:這個對象有3個成員變量,而其中一個是有transient關鍵字申明的,一個是序列化id,一個是普通變量,在main方法中,想建立了對象,並設置值,而後寫入到文件,再從文件讀出來,最後輸出讀出來的對象的變量,普通變量是能夠正常輸出的(序列化id也能夠,只是此處沒有輸出來而已),而transient申明的變量爲0了,那就證實這個變量沒有被保存到文件中,由於這個關鍵字聲明的變量在序列化時會被忽略,而是後來建立時被自動初始化爲0了(java中類的成員變量是基本數據類型時,若是沒有初值,就會被自動初始化爲0)。
額...這裏是介紹模式,好像說得多了點,那原型模式就介紹到這兒了。
六、責任鏈模式(Chain of Responsibility Pattern)
使不少個對象都有機會處理請求,從而避免請求的發送者和接收者之間的耦合關係。將這些對象連成一條鏈,並沿着這條鏈傳遞該請求,直到有一個對象處理它爲止。
什麼時候使用
有許多對象能夠處理用戶請求,但願程序在運行期間自動肯定處理用戶的那個對象。
但願用戶沒必要明確指定接收者的狀況下,想多個接受者的一個提交請求
程序但願動態的指定可處理用戶請求的對象集合
優勢
低耦合
能夠動態的添加刪除處理者或從新指派處理者的職責
能夠動態改變處理者之間的前後順序
一般來講,一個純粹的責任鏈是先傳給第一個處理,若是處理過了,這個請求處理就此結束,若是沒有處理,再傳給下一個處理者。
好比咱們有一個數學公式,有一個整數輸入,要求小於0時返回絕對值,其次,小於10的時候返回他的二次冪,不然,返回他自己:
首先咱們要定義一個接口(處理者),來描述他們共有的行爲:
而後是具體的處理者(3個):
//第一個具體處理者,處理小於0的
public class Handler1 implements Handler {
private Handler next;
@Override
public int handleRequest(int n) {
if(n<0) return -n;
else{
if(next==null)
throw new NullPointerException("next 不能爲空");
return next.handleRequest(n);
}
}
@Override
public void setNextHandler(Handler next) {
this.next = next;
}
}
//第三個具體處理者,處理>=0但小於10的
public class Handler3 implements Handler {
private Handler next;
@Override
public int handleRequest(int n) {
if(n<=Integer.MAX_VALUE) return n;
else{
if(next==null)
throw new NullPointerException("next 不能爲空");
return next.handleRequest(n);
}
}
@Override
public void setNextHandler(Handler next) {
this.next = next;
}
}
使用:
此處責任鏈中的具體處理者的順序是不能重設的,不然可能會引起錯誤,但更多的狀況是徹底能夠隨意更改他們的位置的,就上例中,只要把if中的條件從新設置(各自獨立,不相互依賴),就能夠了。
咱們寫java web程序的時候,一般會編寫一些過濾器(Filter),而後配置到web.xml中,這其實就是責任鏈模式的一種實踐。而使用Log4j記錄日誌,配置級別的時候,也一樣用到了責任鏈模式。
咱們使用責任鏈模式的時候,不必定非得某一處理者處理後就得終止請求的傳遞,若是有其餘需求,咱們依然能夠繼續傳遞這個請求到下一個具體的處理者。
別名:動做,事物(Another Name:Action,Transaction)
Encapsulate a request as an object,thereby letting you parameterize clients with different reauests,queue or log requests,and support undoable operations.
將一個請求封裝爲一個對象,從而使用戶可用不一樣的請求對客戶進行參數化;對請求排隊或記錄請求日誌,以及支持可撤銷的操做。
什麼時候使用:
優勢:
一個對象有多種操做,可是咱們不但願調用者(請求者)直接使用,咱們就額外添加一個對象,而後讓調用者經過這個對象來使用那些操做。
好比,咱們有一個類能夠在磁盤上新建或是刪除文件(接收者),可是咱們不但願直接提供給別人(請求者)使用,因此咱們就爲它的各類操做建立對應的命令,下面咱們用代碼來實現這個需求:
接收者,能夠在磁盤刪除或新建文件:
//接收者
public class MakeFile {
//新建文件
public void createFile(String name) throws IOException{
File file = new File(name);
file.createNewFile();
}
//刪除文件
public boolean deleteFile(String name){
File file = new File(name);
if(file.exists()&&file.isFile()){
file.delete();
return true;
}
return false;
}
}
而後就是執行操做的接口:
咱們須要建立具體的命令,這裏就是2個,新建和刪除:
//新建文件命令
public class CommandCreate implements Command {
MakeFile makeFile;
public CommandCreate(MakeFile makeFile) {
this.makeFile = makeFile;
}
@Override
public void execute(String name) throws Exception {
makeFile.createFile(name);
}
}
最後就是請求者了:
//請求者
public class Client {
Command command;
public Client setCommand(Command command){
this.command = command;
return this;
}
public void executeCommand(String name) throws Exception{
if(command==null)
throw new Exception("命令不能爲空!");
command.execute(name);
}
}
這樣,咱們就能夠使用了,方式以下:
這裏只是簡單的實現,諸如CommandCreate命令的操做,若是咱們須要undo的,那麼就須要在命令接口中添加undo()方法並在具體命令中實現便可(將操做保存到棧裏便可,undo的時候出棧並撤銷操做)。
命令模式不宜濫用,好比:使用這種模式,會多出來不少對象(命令)。
命令模式中還有一種具體命令叫宏命令,它會包含一些其餘具體命令的引用,執行宏命令能夠執行這個宏命令所包含的引用的命令,概念清楚後實現起來也是容易的:
好比輸出文章的命令,有中文輸出命令、英文輸出命令和宏命令,宏命令包含了其餘兩個命令的引用(能夠使用列表保存這些命令),若是執行宏命令,宏命令會一次執行它所包含引用的其餘命令(迭代命令列表並執行便可)。
Given a language,define a representation for its grammar along with an interpreter that uses the representation to interpret sentences in the language.
給定一個語言,定義它的文法的一種表示,並定義一個解釋器,這個解釋器使用該表示來解釋語言中的句子。
什麼時候使用
優勢
概念其實很簡單。在有些問題上,咱們可能但願自定定義簡單的語言來描述,而後咱們本身能解釋它。
解釋器模式通常包括四種角色:
使用該模式設計程序通常須要三個步驟:
這種模式通常會應用到一些特殊的問題上,使用這種模式通常須要了解形式語言中的基本知識。js內核就是一個強大的解釋器。
簡單的解釋器模式,咱們須要解釋出來表達式的信息便可;而更深一層的,咱們須要把表達式中的內容,翻譯成咱們程序運行的一部分來執行。
本初不提供例子,理解概念便可。有需求的時候再深刻學習。若是確實須要舉例,請在回覆中說明,我會更新文章並添加一些內容。
別名:遊標(Another Name:Cursor)
提供一種方法順序訪問一個聚合對象中的各個元素,而由不須要暴露該對象的內部細節。
什麼時候使用
優勢
一般容器提供的迭代器時能夠高速遍歷它自己的,而使用其自己的機制(如LinkedList中使用get(i)方法遍歷)遍歷性能可能並很差。
其實這個在工廠方法模式給出的例子就足夠解釋這個模式的使用了,如需看具體代碼實現,請移步工廠方法模式中的例子查看。
其中主要的角色是集合、具體集合、迭代器、具體迭代器。
迭代器其實很簡單,下面咱們就繼續工廠方法模式中的例子,將它完善一下:
稍微加強的集合接口:
//集合接口
public interface MyList<T> {
MyIterator<T> iterator(); //返回一個遍歷器
boolean add(T t); //添加元素到列表
T get(int index); //獲得元素
T remove(); //刪除最後一個元素
boolean remove(T element); //刪除指定元素
T remove(int index); //刪除指定位置元素
boolean set(int index,T element); //修改指定位置值
int size();
}
容量能夠自動增加的數組實現的集合:
鏈表實現的集合:
public class MyLinkedList<T> implements MyList<T>{
private int size; //存放的元素個數,會默認初始化爲0
private Node<T> first; //首節點,默認初始化爲null
@Override
public MyIterator<T> iterator() {
return new Iterator();
}
@Override
public boolean add(T t) {
if(size==0){
first = new Node<T>(t,null);
size++;
return true;
}
Node<T> node = first;
while(node.next!=null)
node = node.next;
node.next = new Node<T>(t,null);
size++;
return true;
}
@Override
public T get(int index) {
Node<T> node = first;
while(--index>=0)
node = node.next;
return node.data;
}
@Override
public T remove() {
return remove(size-1);
}
@Override
public T remove(int index) {
if(index<0||index>=size) return null;
Node<T> node = first;
while(--index>0)
node = node.next;
T element = node.next.data;
node.next = node.next.next;
size--;
return element;
}
@Override
public boolean remove(T element) {
if(element == null){
if(first.data==null){
first = first.next;
size--;
return true;
}
Node<T> node = first;
do{
if(node.next.data==null){
node.next = node.next.next;
size--;
return true;
}
node = node.next;
}
while(node.next!=null);
}
else{
if(first.data.equals(element)){
first = first.next;
size--;
return true;
}
Node<T> node = first;
do{
if(node.next.data.equals(element)){
node.next = node.next.next;
size--;
return true;
}
node = node.next;
}
while(node.next!=null);
}
return false;
}
@Override
public boolean set(int index, T element) {
if(index<0||index>=size) return false;
Node<T> node = first;
while(--index>0)
node = node.next;
node.data = element;
return true;
}
@Override
public int size() {
return size;
}
//鏈表節點
private static class Node<T>{
T data;
Node<T> next;
Node(T data,Node<T> next){
this.data = data;
this.next = next;
}
}
//遍歷器
private class Iterator implements MyIterator<T>{
private Node<T> next; //下一個節點
Iterator(){
next = first;
}
@Override
public boolean hasNext() {
return next!=null;
}
@Override
public T next() {
T data = next.data;
next = next.next;
return data;
}
@Override
public T remove() {
// TODO Auto-generated method stub
return null;
}
}
}
迭代器接口:
具體的迭代器就是集合具體實現裏面的迭代器內部類,下面是使用 :
public class TestUse {
public static void main(String args[]){
//分別定義兩種結構
MyList<String> array = new MyArrayList<String>();
MyList<String> link = new MyLinkedList<String>();
//添加數據
for(int i = 1;i < 15; i++){
array.add(i+"");
link.add(i+"");
}
//數組操做
System.out.println(array.remove());
System.out.println(array.get(5));
System.out.println(array.remove(5));
System.out.println(array.get(5));
System.out.println(array.remove("7"));
System.out.println(array.set(0, "00"));
//使用迭代器
MyIterator<String> ai = array.iterator();
while(ai.hasNext())
System.out.print(ai.next()+" ");
System.out.println();
System.out.println(link.remove());
System.out.println(link.get(5));
System.out.println(link.remove(5));
System.out.println(link.get(5));
System.out.println(link.remove("7"));
System.out.println(link.set(0, "00"));
//使用迭代器
MyIterator<String> li = link.iterator();
while(li.hasNext())
System.out.print(li.next()+" ");
System.out.println();
System.out.println("a size=" + array.size());
System.out.println("l size=" + link.size());
}
}
輸出:
這裏的迭代器就是典型的迭代器模式的實現(不過此處的迭代器沒有實現remove()方法,查找集合的remove()方法實現也簡單),介紹得有點多了,把集合都給介紹了...
用一箇中介對象來封裝一系列的對象交互。中介者使各對象不須要顯示的相互引用,從而使其耦合鬆散,並且能夠獨立的改變他們以前的交互。
什麼時候使用
優勢
兩個類直接關聯,是很好實現的,但若是不但願兩個類直接發生交互,那麼就須要使用中介者模式了。
好比有兩個類,他們都是作持久化的,一個負責將數據寫入文件,一個負責將數據寫入數據庫。他們誰先接收到數據是不肯定的,可是要確保其中一個接收到數據後,另一個也必須完成這些數據的持久化。若是咱們直接將兩個類關聯在一塊兒,互相調用是能夠實現的,但不利於後期擴展或維護(好比再添加一個持久化組建,則原有的組建可能都須要修改),此時,若添加一箇中介者,來協調他們,事兒就好辦多了:
數據持久化的接口:
//同事(接口)
public interface IPersistent {
void getData(Object data);
void getData(Object data,Midiator midiator);
void saveData();
}
分別實現持久化到文件和持久化到數據庫的組件(具體同事):
//具體同事
public class PersistentDB implements IPersistent{
private Object data;
@Override
public void getData(Object data, Midiator midiator) {
getData(data);
midiator.notifyOther(this, data);
}
@Override
public void saveData() {
System.out.println(data + " 已保存到數據庫");
}
@Override
public void getData(Object data) {
this.data = data;
saveData();
}
}
中介者:
使用:
public class TestUse {
public static void main(String args[]){
Object data = "數據";
PersistentDB persistentDB = new PersistentDB();
PersistentFile persistentFile = new PersistentFile();
Midiator midiator = new Midiator();
midiator.setPersistentDB(persistentDB).setPersistentFile(persistentFile);
persistentDB.getData(data, midiator);
persistentFile.getData(data, midiator);
}
}//輸出(省略了換行符):數據 已保存到數據庫數據 已保存到文件數據 已保存到文件數據 已保存到數據庫
就上例,若是還有許多的持久化組件(具體同事),能夠在中介者中使用一個List來存放他們的引用,set的時候就添加。在通知其餘同事時,遍歷這個List,除了參數自己這個同事,其餘的依次通知,便可實現。
中介者消除了同事與同事間直接的關聯。
十一、備忘錄模式(Memento Pattern)
別名:標記(Another Name:Token)
Without violating encapsulation,captrue and externalize an object' orifianl state so that the object can be restored to this state later.
在不破壞封裝性的前提下,捕獲一個對象的內部狀態,並在該對象以外保存該狀態,這樣就能夠將該對象恢復到以前保存的狀態。
什麼時候使用:
必須保存一個對象在某一時刻的所有或部分狀態,以便在須要時恢復該對象先前的狀態。
一個對象不想經過提供public權限的,諸如getXXX()的方法讓其餘對象獲得本身IDE內部狀態。
優勢:
備忘錄模式使用備忘錄能夠吧原先者的內部狀態所有保存起來,使是有很「親密」的對象能夠訪問備忘錄中的數據。
備忘錄模式強調了類設計單一責任的原則,即將狀態的刻畫和保存分開。
備忘錄模式又叫作快照模式(Snapshot Pattern)或Token模式,是對象的行爲模式。 備忘錄對象是一個用來存儲另一個對象內部狀態的快照的對象。
備忘錄模式中有三種角色:
備忘錄(Memento)角色:將發起人(Originator)對象的內戰狀態存儲起來。備忘錄能夠根據發起人對象的判斷來決定存儲多少發起人(Originator)對象的內部狀態。備忘錄能夠保護其內容不被髮起人(Originator)對象以外的任何對象所讀取。
發起人(Originator)角色:建立一個含有當前的內部狀態的備忘錄對象。使用備忘錄對象存儲其內部狀態。
負責人(Caretaker)角色:負責保存備忘錄對象。不檢查備忘錄對象的內容。
先看一個簡單的實現方式:
備忘錄角色對任何對象都提供一個接口,備忘錄角色的內部所存儲的狀態就對全部對象公開,所以是破壞封裝性的。
按照定義中的要求,備忘錄角色要保持完整的封裝。最好的狀況即是:備忘錄角色只應該暴露操做內部存儲屬性的的接口給「備忘發起角色」。
若是上例中,咱們把備忘錄以發起人的私有內部類的方式實現的話,那它就只能被髮起人訪問了,這正好就符合備忘錄模式的要求,可是咱們的負責人是須要存放備忘錄的引用的,因而,咱們提供一個公共的接口,他是空的,咱們用備忘錄實現它,主要就是利用其中的類型信息,具體實現以下:
//備忘錄模式
public class BlackMemento {
public static void main(String[] args) {
BlankOriginator originator = new BlankOriginator(); //發起人
BlackCaretaker caretaker = new BlackCaretaker(); //負責人
originator.setState("stateOne"); //設置狀態
caretaker.saveMemento(originator.createMemento()); //保存信息
originator.setState("stateTwo"); //修改狀態
originator.recoverMemento(caretaker.recoverMemento());//恢復狀態
}
}
interface MementoIF {}
//發起人
class BlankOriginator {
private String state;
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
public MementoIF createMemento(){
return new Memento(state);
}
public void recoverMemento(MementoIF memento){
this.setState(((Memento)memento).getState());
}
//之內部類實現備忘錄角色
private class Memento implements MementoIF{
private String state;
private Memento(String state){
this.state = state;
}
private String getState() {
return state;
}
}
}
//負責人
class BlackCaretaker {
private MementoIF memento;
public MementoIF recoverMemento(){
return memento;
}
public void saveMemento(MementoIF memento){
this.memento = memento;
}
}
上面兩個例子,演示的都是保存一個狀態(不是指一個成員,而是隻存了最近一次狀態),即一個檢查點,可是實際應用中,狀態每每不止存儲一次,咱們將上面儲存狀態的變量改成一個棧(或隊列,主要看需求)便可。好比:BlackCaretaker中的private MementoIF memento;改成LinkedList<MementoIF> mementos 實現,保存的時候壓棧(入隊),恢復的時候出棧(出隊)。具體實現都已經描述很清楚了,代碼就不貼了(文章原本就太長了)。
針對上例,若是發起人和負責人咱們並不介意他們必須是獨立的,就能夠把他們融合到一塊兒,實現就會更佳簡單,代碼也簡潔:
上例演示僅保存一個檢查點。下面再給出一個實際的例子:
咱們有個程序,供用戶編輯文本,用戶作出修改後,能夠保存文本,保存修改後,能夠依次恢復到保存前的多個狀態中的一個,若是恢復後用戶沒有修改,還能夠取消恢復(重作),下面就演示整個程序。
這個程序爲了保證功能相對完整,寫做演示可能有點長了:
//文本編輯器
public class TextEditor {
public static void main(String[] args) {
//使用這個文本編輯器
MyTextEditor editor = new MyTextEditor("這裏是初始文本,可能爲文件中讀取的值。");
System.out.println("開始修改文本:");
editor.append("添加文字1");
editor.delWords(); //刪除最後一個
// editor.delWords(2); //刪除最後2個 這兩個方法是沒有問題的,這裏避免控制檯輸出太多,取消這兩次修改
// editor.delWords(1,5); //刪除前面5個
System.out.println("開始恢復:");
for(int i=0;i<10;i++) editor.recoverMemento();//恢復大於實際修改的次數不會出錯,只會將文本設爲o初始化狀態
System.out.println("開始重作:");
for(int i=0;i<10;i++) editor.redo(); //重作大於實際恢復的次數不會出錯,只會將文本設爲最後狀態
System.out.println("再次恢復:");
for(int i=0;i<10;i++) editor.recoverMemento();//恢復大於實際修改的次數不會出錯,只會將文本設爲o初始化狀態
System.out.println("再次重作:");
for(int i=0;i<10;i++) editor.redo(); //重作大於實際恢復的次數不會出錯,只會將文本設爲最後狀態
System.out.println("再次恢復:");
for(int i=0;i<10;i++) editor.recoverMemento();//恢復大於實際修改的次數不會出錯,只會將文本設爲o初始化狀態
editor.append("添加文字2");
System.out.println("再次重作:");
for(int i=0;i<10;i++) editor.redo(); //重作大於實際恢復的次數不會出錯,只會將文本設爲最後狀態
}
}
interface IMemento {}
//發起人兼負責人
class MyTextEditor {
public StringBuffer text;
private LinkedList<IMemento> mementos; //保存快照
private LinkedList<IMemento> undos; //保存撤銷的操做
public MyTextEditor(){
this("");
}
public MyTextEditor(String defaultStr){
text = new StringBuffer(defaultStr);
mementos = new LinkedList<IMemento>();
undos = new LinkedList<IMemento>();
print();
}
public void clearHistory(){
mementos.clear();
undos.clear();
}
public void append(String appendStr){
if(appendStr==null||appendStr.length()==0) return;
createMemento();
text.append(appendStr);
print();
undos.clear();
}
//刪除最後一個
public void delWords(){
delWords(1);
}
//刪除最後n個
public void delWords(int n){
if(n<1||n>text.length()) return;
delWords(text.length()-n+1,text.length());
}
//刪除中間start到end的字符,第一個文字爲第一個(而不是0)
public void delWords(int start,int end){
if(start<1 || end>text.length()+1) return;
createMemento();
text = text.delete(start-1, end);
print();
}
public void reset(String text){
this.text = new StringBuffer(text);
}
//新的快照
public void createMemento(){
mementos.push(new Memento(this));
}
//恢復狀態
public boolean recoverMemento(){
Memento memento = (Memento) mementos.poll();
if(memento==null) return false;
undos.push(new Memento(this));
reset(memento.state);
print();
return true;
}
//redo,redo的操做也能夠恢復!
public boolean redo(){
Memento memento = (Memento) undos.poll();
if(memento==null) return false;
createMemento();
reset(memento.state);
print();
return true;
}
//內部類實現備忘錄
private class Memento implements IMemento{
private String state;
private Memento(MyTextEditor editor){
this.state = editor.text.toString();
}
}
void print(){
System.out.println("當前文本:" + text);
}
}
控制檯輸出:
能夠看到功能都是正確的,最後的重作由於在恢復後有修改發生,因此重作是無效的(目前咱們所用的編輯器都是這種策略)。屢次的恢復和重作是沒有問題的。
該例子就是備忘錄模式典型的例子。
別名: 依賴,發佈/訂閱(Another Name: Dependents, Publish/Subscribe)
定義對象間的一種一對多的依賴關係,當一個對象狀態發生改變時,全部依賴它的對象都獲得通知並被自動更新。
什麼時候使用
優勢
...
好比咱們有個天氣服務(主題),而後有多個使用它的客戶端(觀察者),包括android和iphone端app的服務(觀察者),那麼就能夠使用這麼模式。
咱們須要一種結構存放天氣信息(注意,省略了get、set方法!):
//天氣的消息實體
public class WeatherInfo {
private long time;
private String weather;
public WeatherInfo(long time,String weather){
this.time = time;
this.weather = weather;
}
@Override
public boolean equals(Object obj) {
WeatherInfo info = (WeatherInfo) obj;
return info.time==this.time&&info.weather.equals(this.weather);
}
}
而後咱們定義天氣服務的接口(主題),以表示它應實現哪些功能:
接着就是客戶端的接口描述:
//觀察者
public interface Client {
void getWeather(WeatherInfo info);
}
而後實現具體的天氣服務,這裏一樣用到了單例模式:
最後就是具體的客戶端(觀察者,此處給出兩個):
public class ClientAndroidServer implements Client {
private static String name = "安卓服務";
private WeatherInfo info;
@Override
public void getWeather(WeatherInfo info) {
this.info = info;
dealMsg();
}
private void dealMsg(){
System.out.println(name + "收到最新天氣:time="+info.getTime()+"msg="+info.getWeather()+"。立刻開始推送消息...");
}
}
好,如今就能夠直接使用了:
public class TestUse {
public static void main(String args[]){
//建立主題
WeatherService service = WeatherService.instance;
//添加觀察者
service.addClient(new ClientAndroidServer());
service.addClient(new ClientIphoneServer());
//更新主題
service.updateWeather(new WeatherInfo(System.currentTimeMillis(), "多雲"));
service.updateWeather(new WeatherInfo(System.currentTimeMillis()+1000*60*60*24, "多雲轉晴"));
service.updateWeather(new WeatherInfo(System.currentTimeMillis()+1000*60*60*24*2, "晴"));
}
}
運行後,控制檯有以下輸出:
能夠看出,觀察者模式是一對多的。而本例是將更新的內容整個推給客戶端。
而觀察者模式中的數據有推和拉的區別,上例是推。
推的方式會將主題更改的內容所有直接推給客戶端,拉的方式就是主題的數據更新後,不直接將數據推給客戶端,而是先推送一個通知並提供對應的方法供客戶端拉取數據。
若是上例中,天氣服務每半小時更新(半點和整點推消息),還有一個客戶端,不須要特別即時的天氣消息,只取整點的消息,那麼咱們就能夠使用拉的方式,數據更新後,給客戶端推送一個標誌,客戶端本身按需取得數據(天氣服務須要提供這樣一個接口)。這就是拉。
java.util包中也提供了觀察者模式的支持,由於java程序設計中使用比較普遍。有一個Observable類(至關於這裏的具體主題)和一個Observer接口(至關於這裏的主題接口):
public interface Observer {
void update(Observable o, Object arg);
}
其實跟上面的例子大致差很少,若是有這方面的需求,也能夠直接使用Java的API。 但能夠看到裏面還在使用Vector(已過期),這實際上是不推薦的,咱們能夠本身實現觀察者模式,若是是多線程中,咱們也能夠本身實現同步。
別名:狀態對象(Another Name:Objects for States)
Allow an object to alter its behavior when its internal state changes.The object will appear to change its class.
容許一個對象在其內部狀態改變時改變它的行爲,對象看起來彷佛修改了它的類。
什麼時候使用:
優勢:
用一句話來表述,狀態模式把所研究的對象的行爲包裝在不一樣的狀態對象裏,每個狀態對象都屬於一個抽象狀態類的一個子類。狀態模式的意圖是讓一個對象在其內部狀態改變的時候,其行爲也隨之改變。
可能這段時間總是在想數據庫相關的事兒,因此一想例子就就想到這方面來了...不過,這樣你們也能更好的對比設計模式之間的差別,下本例仍是與這方面相關的。
設想咱們有一個程序,要保存數據的,按照數據(這裏以String舉例)的大小,使用不一樣的方式保存。若是數據很小,咱們將其保存到Redis(緩存數據庫)中,若是數據庫不過小也不太大,咱們將其保存到mysql中,若是數據很是大,咱們直接將其寫入到文件中。數據的大小就是一種狀態,很適合使用狀態模式:
環境:
//環境(Context)
public class SaveDataController {
private ISaveData saveData;
public void save(String data){
//爲了演示,此處的大的數據其實也是很小的
if(data.length()<1<<2)
saveData = SaveSmallData.instance;
else if(data.length()<1<<4)
saveData = SaveMiddleData.instance;
else
saveData = SaveBigData.instance;
saveData.save(data);
}
}
抽象狀態:
具體狀態(每一個具體狀態都使用到了單例模式):
//具體狀態
public enum SaveSmallData implements ISaveData{
instance;
@Override
public void save(Object data) {
System.out.println("保存到Redis:" + data);
}
}
//具體狀態
public enum SaveBigData implements ISaveData{
instance;
@Override
public void save(Object data) {
System.out.println("保存到文件:" + data);
}
}
使用:
輸出:
保存到Redis:小數據
保存到Mysql:介於小數據和大數據之間的數據
保存到文件:這裏就假定這是一個很大很大很大的數據
能夠看到,咱們對三種數據都使用了同一個對象的相同方法,可是行爲是不一樣的,由於他們的狀態不同。
上面例子的狀態更改是自動的,也能夠添加setState()方法,手動切換狀態,並在執行的方法體中不在自動判斷狀態。不過自動判斷的,更智能一些,而手動切換狀態的,可控性更好。
1四、策略模式(Strategy Pattern)
定義一系列算法,把他們一個個封裝起來,而且使他們可相互替換。本模式使得算法可獨立於其餘客戶端而變化。
什麼時候使用
優勢
策略模式是對算法的包裝,是把使用算法的責任和算法自己分割開來,委派給不一樣的對象管理。策略模式一般把一個系列的算法包裝到一系列的策略類裏面,做爲一個抽象策略類的子類。用一句話來講,就是:「準備一組算法,並將每個算法封裝起來,使得它們能夠互換」。下面就以一個示意性的實現講解策略模式實例的結構。
策略模式中包括三種角色:
策略(Strategy):一個接口,定義了若干個算法(抽象方法)。
具體策略(ConcreteStrategy):策略的實現。
上下文/環境(Context):依賴於策略接口的類。
策略模式的重心不是如何實現算法,而是如何組織、調用這些算法,從而讓程序結構更靈活,具備更好的維護性和擴展性。
策略模式一個很大的特色就是各個策略算法的平等性。對於一系列具體的策略算法,你們的地位是徹底同樣的,正由於這個平等性,才能實現算法之間能夠相互替換。全部的策略算法在實現上也是相互獨立的,相互之間是沒有依賴的。因此能夠這樣描述這一系列策略算法:策略算法是相同行爲的不一樣實現。
運行期間,策略模式在每個時刻只能使用一個具體的策略實現對象,雖然能夠動態地在不一樣的策略實現中切換,可是同時只能使用一個。
常常見到的是,全部的具體策略類都有一些公有的行爲。這時候,就應當把這些公有的行爲放到共同的抽象策略角色Strategy類裏面。固然這時候抽象策略角色必需要用Java抽象類實現,而不能使用接口。 這其實也是典型的將代碼向繼承等級結構的上方集中的標準作法。
上次咱們使用狀態模式將數據按不一樣狀態保存到不一樣地方,這裏,咱們使用策略模式來實現經過不一樣的策略來選擇數據的保存方式。
首先是抽象的數據保持類(策略):
而後是具體的數據保存類,三個(具體策略):
public class SaveToRedis implements ISaveData {
@Override
public void save(Object data) {
System.out.println("數據:" + data + " 保存到Redis");
}
}
//具體策略
public class SaveToMysql implements ISaveData {
@Override
public void save(Object data) {
System.out.println("數據:" + data + " 保存到Mysql");
}
}
最後是客戶端(環境Context):
使用:
public class TestUse {
public static void main(String args[]){
Object data = "數據";
ISaveData saveData = new SaveToRedis();
SaveClient client = new SaveClient(saveData);
client.save(data);
client.setSaveData(new SaveToFile());
client.save(data);
}
}
這裏數據的保存就是根據使用的時候設置的策略來決定。
使用策略模式能夠避免使用多重條件(if-else)語句。多重條件語句不易維護,它把採起哪種算法或採起哪種行爲的邏輯與算法或行爲的邏輯混合在一塊兒,通通列在一個多重條件語句裏面,比使用繼承的辦法還要原始和落後。
客戶端必須知道全部的策略類,並自行決定使用哪個策略類。這就意味着客戶端必須理解這些算法的區別,以便適時選擇恰當的算法類。換言之,策略模式只適用於客戶端知道算法或行爲的狀況。因爲策略模式把每一個具體的策略實現都單獨封裝成爲類,若是備選的策略不少的話,那麼對象的數目就會很可觀。
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.
定義一個操做中算法的骨架,而將一些步驟延遲到子類中。模板方法使子類能夠不改變一個算法的結構便可重定義該算法的某些特定步驟。
什麼時候使用:
設計者須要給出一個算法的固定步驟,並將某些步驟的具體實現留給子類來實現。
須要對代碼進行重構,將各個子類公共行爲提取出來集中到一個共同的父類中以免代碼重複。
優勢:
能夠經過在抽象摸吧能定義模板方法給出成熟的算法步驟,同時又不限制步驟的細節,具體模板實現算法細節不會改變整個算法的骨架。
在抽象模板模式中,能夠經過鉤子方法對某些步驟進行掛鉤,具體模板經過鉤子能夠選擇算法骨架中的某些步驟。
模板方法模式是全部模式中最爲常見的幾個模式之一,是基於繼承的代碼複用的基本技術。 模板方法模式須要開發抽象類和具體子類的設計師之間的協做。一個設計師負責給出一個算法的輪廓和骨架,另外一些設計師則負責給出這個算法的各個邏輯步驟。表明這些具體邏輯步驟的方法稱作基本方法(primitive method);而將這些基本方法彙總起來的方法叫作模板方法(template method),這個設計模式的名字就是今後而來。
例如,咱們有這樣的操做:首先獲得一些數據,而後計算這些數據,最後再輸出數據,至於這些操做如何實現(固然,一些方法也能夠提早實現),咱們沒有要求,可是這些操做的前後邏輯咱們已經肯定好了,子類不能改變:
抽象模板:
具體模板:
//具體模板
public class Template extends AbstractTemplate {
@Override
void getData() {
data = "data";
}
@Override
void calcData() {
data = (String)data+data;
}
}
使用:
模板方法也比較簡單,可是很是經常使用,如Android中Activity中生命週期的一些方法,都會被按序調用,也用到了這種設計模式。一樣的,咱們一般會使用一些封裝好的http請求庫,裏面的實現要咱們本身寫,可是邏輯都已經規定好了,不也是模板方法模式麼。總之,模板方法模式使用是很是普遍的。
Represent an opration to be performed on the elements of an object structure.Visitor lets you define a new operation without changing the classes of the elements on which it oprates.
表示一個做用於某對象結構中的各個元素的操做。它能夠在不改變各個元素的類的前提下定義做用於這些元素的新操做。
什麼時候使用:
優勢:
訪問者模式的目的是封裝一些施加於某種數據結構元素之上的操做。一旦這些操做須要修改的話,接受這個操做的數據結構則能夠保持不變。
這個模式可能稍微難理解一點,但願讀者一點一點讀下去,碰到不清楚的先跳過,看完例子再回過頭來基本就清楚是怎麼回事了。
在介紹訪問者模式前,先介紹一下分派的概念。
變量被聲明時的類型叫作變量的靜態類型(Static Type),而變量所引用的對象的真實類型又叫作變量的實際類型(Actual Type),如:
List<String> list = new ArrayList<String>();
這個list變量的靜態類型是List,而它的實際類型是ArrayList。根據對象的類型而對方法進行的選擇,就是分派(Dispatch)。分派又分爲兩種:靜態分派和動態分派。
靜態分派(Static Dispatch)發生在編譯時期,分派根據靜態類型信息發生。靜態分派對於咱們來講並不陌生,方法重載就是靜態分派。
動態分派(Dynamic Dispatch)發生在運行時期,動態分派動態地置換掉某個方法。
看一個例子:
能夠看到,重載的分派是根據靜態類型進行的。
java的方法重寫是根據實際類型來的(動態分派),編譯器編譯時並不知道其真實類型,而是運行時動態決定的。
一個對象又叫作它所包含的方法的接收者,java中的動態分派,要調用哪個方法,是由這個對象的真實類型決定的。
若是可以根據參數和接收者來動態的決定調用某個方法,這就是動態的多分派語言,若是能夠根據這兩種方式來動態的決定方法調用,就是動態雙重分派,但前面已經說了,java中重載是根據靜態類型進行的,因此java只能動態的根據接收者來進行方法調用,即java是動態單分派語言,若是要實現雙重分派,就必須經過設計模式來完成。
OK,講到重點了,訪問者模式正是實現雙重分派的模式。java中經過兩次方法調用來實現兩次分派。
既然重載不能完成動態分派,咱們就添加一個Visitor:
public class MultiDispatch {
public static void main(String args[]){
Child child = new Child();
child.print();
child.print(new Vistor());
}
}
class Father{
void print(){
System.out.println("父類");
}
}
class Child extends Father{
void print(){
System.out.print("子類");
}
void print(Vistor c){
c.print(this);
}
}
class Vistor {
public void print(Child child){
child.print();
}
}//輸出:子類子類
這樣,動態雙重分派也算是完成了(經過調用一個其它對象的方法,傳入本身,在其餘類的這個方法中再經過傳入的這個參數調用本身。若是仍是不清楚,請繼續看下面的例子)。那麼這個有什麼用呢?下面繼續解釋。
好比咱們有個app須要接收用戶的反饋,用戶由會員和普通用戶,由於反饋太多,並非全部反饋都會被記錄到有效反饋表中,是否記錄爲有效一般不是用戶說了算,而是有咱們本身定。
本例中的用戶就是抽象元素:
具體的用戶就是具體元素,本例有兩個:
//普通用戶,具體元素
public class UserOrdinary implements User{
String estimation;
public UserOrdinary(String estimation){
this.estimation = estimation;
}
@Override
public void accept(Visitor visitor) {
visitor.visit(this);//這個就是重點,第一次分派是調用accept()方法時根據接收者的實際類型來調用的,第二次分派就是經過visitor.visit(this),傳入靜態類型,而後再visit()方法中反過來調用this自己的方法。
}
String getEstimation(){
return estimation;
}
}
抽象訪問者:
//抽象訪問者
public interface Visitor {
void visit(UserVIP user);
void visit(UserOrdinary user);
}
具體訪問者,檢查反饋是否記錄到有效反饋中:
使用:
public class TestUse {
public static void main(String args[]){