裝飾公司 網(wǎng)站模板網(wǎng)絡(luò)推廣中心
設(shè)計模式的分類
總體來說設(shè)計模式分為三大類:
創(chuàng)建型模式,共五種:工廠方法模式、抽象工廠模式、單例模式、建造者模式、原型模式。
結(jié)構(gòu)型模式,共七種:適配器模式、裝飾器模式、代理模式、外觀模式、橋接模式、組合模式、享元模式。
行為型模式,共十一種:策略模式、模板方法模式、觀察者模式、迭代子模式、責(zé)任鏈模式、命令模式、備忘錄模式、狀態(tài)模式、訪問者模式、中介者模式、解釋器模式。
一、創(chuàng)建模式(5種)
工廠方法模式、抽象工廠模式、單例模式、建造者模式、原型模式。
1 工廠模式
1.1 簡單工廠模式
定義:定義了一個創(chuàng)建對象的類,由這個類來封裝實例化對象的行為。
舉例:(我們舉一個pizza工廠的例子)
pizza工廠一共生產(chǎn)三種類型的pizza:chesse,pepper,greak。通過工廠類(SimplePizzaFactory)實例化這三種類型的對象。類圖如下:
工廠類的代碼:
public class SimplePizzaFactory {
public Pizza CreatePizza(String ordertype) {
Pizza pizza = null;
if (ordertype.equals(“cheese”)) {
pizza = new CheesePizza();
} else if (ordertype.equals(“greek”)) {
pizza = new GreekPizza();
} else if (ordertype.equals(“pepper”)) {
pizza = new PepperPizza();
}
return pizza;
}
}
簡單工廠存在的問題與解決方法: 簡單工廠模式有一個問題就是,類的創(chuàng)建依賴工廠類,也就是說,如果想要拓展程序,必須對工廠類進行修改,這違背了開閉原則,所以,從設(shè)計角度考慮,有一定的問題,如何解決?我們可以定義一個創(chuàng)建對象的抽象方法并創(chuàng)建多個不同的工廠類實現(xiàn)該抽象方法,這樣一旦需要增加新的功能,直接增加新的工廠類就可以了,不需要修改之前的代碼。這種方法也就是我們接下來要說的工廠方法模式。
1.2 工廠方法模式
定義:定義了一個創(chuàng)建對象的抽象方法,由子類決定要實例化的類。工廠方法模式將對象的實例化推遲到子類。
舉例:(我們依然舉pizza工廠的例子,不過這個例子中,pizza產(chǎn)地有兩個:倫敦和紐約)。添加了一個新的產(chǎn)地,如果用簡單工廠模式的的話,我們要去修改工廠代碼,并且會增加一堆的if else語句。而工廠方法模式克服了簡單工廠要修改代碼的缺點,它會直接創(chuàng)建兩個工廠,紐約工廠和倫敦工廠。類圖如下:
OrderPizza中有個抽象的方法:
abstract Pizza createPizza();
兩個工廠類繼承OrderPizza并實現(xiàn)抽象方法:
public class LDOrderPizza extends OrderPizza {
Pizza createPizza(String ordertype) {
Pizza pizza = null;
if (ordertype.equals(“cheese”)) {
pizza = new LDCheesePizza();
} else if (ordertype.equals(“pepper”)) {
pizza = new LDPepperPizza();
}
return pizza;
}
}
public class NYOrderPizza extends OrderPizza {
Pizza createPizza(String ordertype) {Pizza pizza = null;if (ordertype.equals("cheese")) {pizza = new NYCheesePizza();} else if (ordertype.equals("pepper")) {pizza = new NYPepperPizza();}return pizza;}
}
通過不同的工廠會得到不同的實例化的對象,PizzaStroe的代碼如下:
public class PizzaStroe {
public static void main(String[] args) {
OrderPizza mOrderPizza;
mOrderPizza = new NYOrderPizza();
}
}
其實這個模式的好處就是,如果你現(xiàn)在想增加一個功能,只需做一個實現(xiàn)類就OK了,無需去改動現(xiàn)成的代碼。這樣做,拓展性較好!
工廠方法存在的問題與解決方法:客戶端需要創(chuàng)建類的具體的實例。簡單來說就是用戶要訂紐約工廠的披薩,他必須去紐約工廠,想訂倫敦工廠的披薩,必須去倫敦工廠。 當(dāng)倫敦工廠和紐約工廠發(fā)生變化了,用戶也要跟著變化,這無疑就增加了用戶的操作復(fù)雜性。為了解決這一問題,我們可以把工廠類抽象為接口,用戶只需要去找默認(rèn)的工廠提出自己的需求(傳入?yún)?shù)),便能得到自己想要產(chǎn)品,而不用根據(jù)產(chǎn)品去尋找不同的工廠,方便用戶操作。這也就是我們接下來要說的抽象工廠模式。
1.3 抽象工廠模式
定義:定義了一個接口用于創(chuàng)建相關(guān)或有依賴關(guān)系的對象族,而無需明確指定具體類。
舉例:(我們依然舉pizza工廠的例子,pizza工廠有兩個:紐約工廠和倫敦工廠)。類圖如下:
工廠的接口:
public interface AbsFactory {
Pizza CreatePizza(String ordertype) ;
}
工廠的實現(xiàn):
public class LDFactory implements AbsFactory {
@Override
public Pizza CreatePizza(String ordertype) {
Pizza pizza = null;
if (“cheese”.equals(ordertype)) {
pizza = new LDCheesePizza();
} else if (“pepper”.equals(ordertype)) {
pizza = new LDPepperPizza();
}
return pizza;
}
}
PizzaStroe的代碼如下:
public class PizzaStroe {
public static void main(String[] args) {
OrderPizza mOrderPizza;
mOrderPizza = new OrderPizza(“London”);
}
}
解決了工廠方法模式的問題:在抽象工廠中PizzaStroe中只需要傳入?yún)?shù)就可以實例化對象。
1.4 工廠模式適用的場合
大量的產(chǎn)品需要創(chuàng)建,并且這些產(chǎn)品具有共同的接口 。
1.5 三種工廠模式的使用選擇
簡單工廠 : 用來生產(chǎn)同一等級結(jié)構(gòu)中的任意產(chǎn)品。(不支持拓展增加產(chǎn)品)
工廠方法 :用來生產(chǎn)同一等級結(jié)構(gòu)中的固定產(chǎn)品。(支持拓展增加產(chǎn)品)
抽象工廠 :用來生產(chǎn)不同產(chǎn)品族的全部產(chǎn)品。(支持拓展增加產(chǎn)品;支持增加產(chǎn)品族)
簡單工廠的適用場合:只有倫敦工廠(只有這一個等級),并且這個工廠只生產(chǎn)三種類型的pizza:chesse,pepper,greak(固定產(chǎn)品)。
工廠方法的適用場合:現(xiàn)在不光有倫敦工廠,還增設(shè)了紐約工廠(仍然是同一等級結(jié)構(gòu),但是支持了產(chǎn)品的拓展),這兩個工廠依然只生產(chǎn)三種類型的pizza:chesse,pepper,greak(固定產(chǎn)品)。
抽象工廠的適用場合:不光增設(shè)了紐約工廠(仍然是同一等級結(jié)構(gòu),但是支持了產(chǎn)品的拓展),這兩個工廠還增加了一種新的類型的pizza:chinese pizza(增加產(chǎn)品族)。
所以說抽象工廠就像工廠,而工廠方法則像是工廠的一種產(chǎn)品生產(chǎn)線。因此,我們可以用抽象工廠模式創(chuàng)建工廠,而用工廠方法模式創(chuàng)建生產(chǎn)線。比如,我們可以使用抽象工廠模式創(chuàng)建倫敦工廠和紐約工廠,使用工廠方法實現(xiàn)cheese pizza和greak pizza的生產(chǎn)。類圖如下:
總結(jié)一下三種模式:
簡單工廠模式就是建立一個實例化對象的類,在該類中對多個對象實例化。工廠方法模式是定義了一個創(chuàng)建對象的抽象方法,由子類決定要實例化的類。這樣做的好處是再有新的類型的對象需要實例化只要增加子類即可。抽象工廠模式定義了一個接口用于創(chuàng)建對象族,而無需明確指定具體類。抽象工廠也是把對象的實例化交給了子類,即支持拓展。同時提供給客戶端接口,避免了用戶直接操作子類工廠。
2 單例模式
定義:確保一個類最多只有一個實例,并提供一個全局訪問點
單例模式可以分為兩種:預(yù)加載和懶加載
2.1 預(yù)加載
顧名思義,就是預(yù)先加載。再進一步解釋就是還沒有使用該單例對象,但是,該單例對象就已經(jīng)被加載到內(nèi)存了。
public class PreloadSingleton {
public static PreloadSingleton instance = new PreloadSingleton();//其他的類無法實例化單例類的對象private PreloadSingleton() {};public static PreloadSingleton getInstance() {return instance;}
}
很明顯,沒有使用該單例對象,該對象就被加載到了內(nèi)存,會造成內(nèi)存的浪費。
2.2 懶加載
為了避免內(nèi)存的浪費,我們可以采用懶加載,即用到該單例對象的時候再創(chuàng)建。
public class Singleton {
private static Singleton instance=null;private Singleton(){};public static Singleton getInstance(){if(instance==null){instance=new Singleton();}return instance;}
}
2.3 單例模式和線程安全
(1)預(yù)加載只有一條語句return instance,這顯然可以保證線程安全。但是,我們知道預(yù)加載會造成內(nèi)存的浪費。
(2)懶加載不浪費內(nèi)存,但是無法保證線程的安全。首先,if判斷以及其內(nèi)存執(zhí)行代碼是非原子性的。其次,new Singleton()無法保證執(zhí)行的順序性。
不滿足原子性或者順序性,線程肯定是不安全的,這是基本的常識,不再贅述。我主要講一下為什么new Singleton()無法保證順序性。我們知道創(chuàng)建一個對象分三步:
memory=allocate();//1:初始化內(nèi)存空間
ctorInstance(memory);//2:初始化對象
instance=memory();//3:設(shè)置instance指向剛分配的內(nèi)存地址
jvm為了提高程序執(zhí)行性能,會對沒有依賴關(guān)系的代碼進行重排序,上面2和3行代碼可能被重新排序。我們用兩個線程來說明線程是不安全的。線程A和線程B都創(chuàng)建對象。其中,A2和A3的重排序,將導(dǎo)致線程B在B1處判斷出instance不為空,線程B接下來將訪問instance引用的對象。此時,線程B將會訪問到一個還未初始化的對象(線程不安全)。
2.4 保證懶加載的線程安全
我們首先想到的就是使用synchronized關(guān)鍵字。synchronized加載getInstace()函數(shù)上確實保證了線程的安全。但是,如果要經(jīng)常的調(diào)用getInstance()方法,不管有沒有初始化實例,都會喚醒和阻塞線程。為了避免線程的上下文切換消耗大量時間,如果對象已經(jīng)實例化了,我們沒有必要再使用synchronized加鎖,直接返回對象。
public class Singleton {
private static Singleton instance = null;
private Singleton() {
};
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
我們把sychronized加在if(instance==null)判斷語句里面,保證instance未實例化的時候才加鎖
public class Singleton {
private static Singleton instance = null;
private Singleton() {
};
public static synchronized Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
我們經(jīng)過2.3的討論知道new一個對象的代碼是無法保證順序性的,因此,我們需要使用另一個關(guān)鍵字volatile保證對象實例化過程的順序性。
public class Singleton {
private static volatile Singleton instance = null;
private Singleton() {
};
public static synchronized Singleton getInstance() {
if (instance == null) {
synchronized (instance) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
到此,我們就保證了懶加載的線程安全。
3 生成器模式
定義:封裝一個復(fù)雜對象構(gòu)造過程,并允許按步驟構(gòu)造。
定義解釋: 我們可以將生成器模式理解為,假設(shè)我們有一個對象需要建立,這個對象是由多個組件(Component)組合而成,每個組件的建立都比較復(fù)雜,但運用組件來建立所需的對象非常簡單,所以我們就可以將構(gòu)建復(fù)雜組件的步驟與運用組件構(gòu)建對象分離,使用builder模式可以建立。
3.1 模式的結(jié)構(gòu)和代碼示例
生成器模式結(jié)構(gòu)中包括四種角色:
(1)產(chǎn)品(Product):具體生產(chǎn)器要構(gòu)造的復(fù)雜對象;
(2)抽象生成器(Bulider):抽象生成器是一個接口,該接口除了為創(chuàng)建一個Product對象的各個組件定義了若干個方法之外,還要定義返回Product對象的方法(定義構(gòu)造步驟);
(3)具體生產(chǎn)器(ConcreteBuilder):實現(xiàn)Builder接口的類,具體生成器將實現(xiàn)Builder接口所定義的方法(生產(chǎn)各個組件);
(4)指揮者(Director):指揮者是一個類,該類需要含有Builder接口聲明的變量。指揮者的職責(zé)是負(fù)責(zé)向用戶提供具體生成器,即指揮者將請求具體生成器類來構(gòu)造用戶所需要的Product對象,如果所請求的具體生成器成功地構(gòu)造出Product對象,指揮者就可以讓該具體生產(chǎn)器返回所構(gòu)造的Product對象。(按照步驟組裝部件,并返回Product)
舉例(我們?nèi)绻麡?gòu)建生成一臺電腦,那么我們可能需要這么幾個步驟(1)需要一個主機(2)需要一個顯示器(3)需要一個鍵盤(4)需要一個鼠標(biāo))
雖然我們具體在構(gòu)建一臺主機的時候,每個對象的實際步驟是不一樣的,比如,有的對象構(gòu)建了i7cpu的主機,有的對象構(gòu)建了i5cpu的主機,有的對象構(gòu)建了普通鍵盤,有的對象構(gòu)建了機械鍵盤等。但不管怎樣,你總是需要經(jīng)過一個步驟就是構(gòu)建一臺主機,一臺鍵盤。對于這個例子,我們就可以使用生成器模式來生成一臺電腦,他需要通過多個步驟來生成。類圖如下:
ComputerBuilder類定義構(gòu)造步驟:
public abstract class ComputerBuilder {
protected Computer computer;public Computer getComputer() {return computer;
}public void buildComputer() {computer = new Computer();System.out.println("生成了一臺電腦!!!");
}
public abstract void buildMaster();
public abstract void buildScreen();
public abstract void buildKeyboard();
public abstract void buildMouse();
public abstract void buildAudio();
}
HPComputerBuilder定義各個組件:
public class HPComputerBuilder extends ComputerBuilder {
@Override
public void buildMaster() {
// TODO Auto-generated method stub
computer.setMaster(“i7,16g,512SSD,1060”);
System.out.println(“(i7,16g,512SSD,1060)的惠普主機”);
}
@Override
public void buildScreen() {
// TODO Auto-generated method stub
computer.setScreen(“1080p”);
System.out.println(“(1080p)的惠普顯示屏”);
}
@Override
public void buildKeyboard() {
// TODO Auto-generated method stub
computer.setKeyboard(“cherry 青軸機械鍵盤”);
System.out.println(“(cherry 青軸機械鍵盤)的鍵盤”);
}
@Override
public void buildMouse() {
// TODO Auto-generated method stub
computer.setMouse(“MI 鼠標(biāo)”);
System.out.println(“(MI 鼠標(biāo))的鼠標(biāo)”);
}
@Override
public void buildAudio() {
// TODO Auto-generated method stub
computer.setAudio(“飛利浦 音響”);
System.out.println(“(飛利浦 音響)的音響”);
}
}
Director類對組件進行組裝并生成產(chǎn)品
public class Director {
private ComputerBuilder computerBuilder;
public void setComputerBuilder(ComputerBuilder computerBuilder) {this.computerBuilder = computerBuilder;
}public Computer getComputer() {return computerBuilder.getComputer();
}public void constructComputer() {computerBuilder.buildComputer();computerBuilder.buildMaster();computerBuilder.buildScreen();computerBuilder.buildKeyboard();computerBuilder.buildMouse();computerBuilder.buildAudio();
}
}
3.2 生成器模式的優(yōu)缺點
優(yōu)點
將一個對象分解為各個組件
將對象組件的構(gòu)造封裝起來
可以控制整個對象的生成過程
缺點
對不同類型的對象需要實現(xiàn)不同的具體構(gòu)造器的類,這可能回答大大增加類的數(shù)量
3.3 生成器模式與工廠模式的不同
生成器模式構(gòu)建對象的時候,對象通常構(gòu)建的過程中需要多個步驟,就像我們例子中的先有主機,再有顯示屏,再有鼠標(biāo)等等,生成器模式的作用就是將這些復(fù)雜的構(gòu)建過程封裝起來。工廠模式構(gòu)建對象的時候通常就只有一個步驟,調(diào)用一個工廠方法就可以生成一個對象。
4 原型模式
定義:通過復(fù)制現(xiàn)有實例來創(chuàng)建新的實例,無需知道相應(yīng)類的信息。
簡單地理解,其實就是當(dāng)需要創(chuàng)建一個指定的對象時,我們剛好有一個這樣的對象,但是又不能直接使用,我會clone一個一毛一樣的新對象來使用;基本上這就是原型模式。關(guān)鍵字:Clone。
4.1 深拷貝和淺拷貝
淺復(fù)制:將一個對象復(fù)制后,基本數(shù)據(jù)類型的變量都會重新創(chuàng)建,而引用類型,指向的還是原對象所指向的。
深復(fù)制:將一個對象復(fù)制后,不論是基本數(shù)據(jù)類型還有引用類型,都是重新創(chuàng)建的。簡單來說,就是深復(fù)制進行了完全徹底的復(fù)制,而淺復(fù)制不徹底。clone明顯是深復(fù)制,clone出來的對象是是不能去影響原型對象的
4.2 原型模式的結(jié)構(gòu)和代碼示例
Client:使用者
Prototype:接口(抽象類),聲明具備clone能力,例如java中得Cloneable接口
ConcretePrototype:具體的原型類
可以看出設(shè)計模式還是比較簡單的,重點在于Prototype接口和Prototype接口的實現(xiàn)類ConcretePrototype。原型模式的具體實現(xiàn):一個原型類,只需要實現(xiàn)Cloneable接口,覆寫clone方法,此處clone方法可以改成任意的名稱,因為Cloneable接口是個空接口,你可以任意定義實現(xiàn)類的方法名,如cloneA或者cloneB,因為此處的重點是super.clone()這句話,super.clone()調(diào)用的是Object的clone()方法。
public class Prototype implements Cloneable {
public Object clone() throws CloneNotSupportedException {
Prototype proto = (Prototype) super.clone();
return proto;
}
}
舉例(銀行發(fā)送大量郵件,使用clone和不使用clone的時間對比):我們模擬創(chuàng)建一個對象需要耗費比較長的時間,因此,在構(gòu)造函數(shù)中我們讓當(dāng)前線程sleep一會
public Mail(EventTemplate et) {
this.tail = et.geteventContent();
this.subject = et.geteventSubject();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
不使用clone,發(fā)送十個郵件
public static void main(String[] args) {
int i = 0;
int MAX_COUNT = 10;
EventTemplate et = new EventTemplate(“9月份信用卡賬單”, “國慶抽獎活動…”);
long start = System.currentTimeMillis();
while (i < MAX_COUNT) {
// 以下是每封郵件不同的地方
Mail mail = new Mail(et);
mail.setContent(getRandString(5) + “,先生(女士):你的信用卡賬單…” + mail.getTail());
mail.setReceiver(getRandString(5) + “@” + getRandString(8) + “.com”);
// 然后發(fā)送郵件
sendMail(mail);
i++;
}
long end = System.currentTimeMillis();
System.out.println(“用時:” + (end - start));
}
用時:10001
使用clone,發(fā)送十個郵件
public static void main(String[] args) {int i = 0;int MAX_COUNT = 10;EventTemplate et = new EventTemplate("9月份信用卡賬單", "國慶抽獎活動...");long start=System.currentTimeMillis();Mail mail = new Mail(et); while (i < MAX_COUNT) {Mail cloneMail = mail.clone();mail.setContent(getRandString(5) + ",先生(女士):你的信用卡賬單..."+ mail.getTail());mail.setReceiver(getRandString(5) + "@" + getRandString(8) + ".com");sendMail(cloneMail);i++;}long end=System.currentTimeMillis();System.out.println("用時:"+(end-start));}
用時:1001
4.3 總結(jié)
原型模式的本質(zhì)就是clone,可以解決構(gòu)建復(fù)雜對象的資源消耗問題,能再某些場景中提升構(gòu)建對象的效率;還有一個重要的用途就是保護性拷貝,可以通過返回一個拷貝對象的形式,實現(xiàn)只讀的限制。
二、結(jié)構(gòu)模式(7種)
適配器模式、裝飾器模式、代理模式、外觀模式、橋接模式、組合模式、享元模式。
5 適配器模式
定義: 適配器模式將某個類的接口轉(zhuǎn)換成客戶端期望的另一個接口表示,目的是消除由于接口不匹配所造成的類的兼容性問題。
主要分為三類:類的適配器模式、對象的適配器模式、接口的適配器模式。
5.1 類適配器模式
通過多重繼承目標(biāo)接口和被適配者類方式來實現(xiàn)適配
舉例(將USB接口轉(zhuǎn)為VGA接口),類圖如下:
USBImpl的代碼:
public class USBImpl implements USB{
@Override
public void showPPT() {
// TODO Auto-generated method stub
System.out.println(“PPT內(nèi)容演示”);
}
}
AdatperUSB2VGA 首先繼承USBImpl獲取USB的功能,其次,實現(xiàn)VGA接口,表示該類的類型為VGA。
public class AdapterUSB2VGA extends USBImpl implements VGA {
@Override
public void projection() {
super.showPPT();
}
}
Projector將USB映射為VGA,只有VGA接口才可以連接上投影儀進行投影
public class Projector {
public void projection(T t) {
if (t instanceof VGA) {
System.out.println(“開始投影”);
VGA v = new VGAImpl();
v = (VGA) t;
v.projection();
} else {
System.out.println(“接口不匹配,無法投影”);
}
}
}
test代碼
@Testpublic void test2(){//通過適配器創(chuàng)建一個VGA對象,這個適配器實際是使用的是USB的showPPT()方法VGA a=new AdapterUSB2VGA();//進行投影Projector p1=new Projector();p1.projection(a);}
5.2 對象適配器模式
對象適配器和類適配器使用了不同的方法實現(xiàn)適配,對象適配器使用組合,類適配器使用繼承。
舉例(將USB接口轉(zhuǎn)為VGA接口),類圖如下:
public class AdapterUSB2VGA implements VGA {
USB u = new USBImpl();
@Override
public void projection() {
u.showPPT();
}
}
實現(xiàn)VGA接口,表示適配器類是VGA類型的,適配器方法中直接使用USB對象。
5.3 接口適配器模式
當(dāng)不需要全部實現(xiàn)接口提供的方法時,可先設(shè)計一個抽象類實現(xiàn)接口,并為該接口中每個方法提供一個默認(rèn)實現(xiàn)(空方法),那么該抽象類的子類可有選擇地覆蓋父類的某些方法來實現(xiàn)需求,它適用于一個接口不想使用其所有的方法的情況。
舉例(將USB接口轉(zhuǎn)為VGA接口,VGA中的b()和c()不會被實現(xiàn)),類圖如下:
AdapterUSB2VGA抽象類
public abstract class AdapterUSB2VGA implements VGA {
USB u = new USBImpl();
@Override
public void projection() {
u.showPPT();
}
@Override
public void b() {
};
@Override
public void c() {
};
}
AdapterUSB2VGA實現(xiàn),不用去實現(xiàn)b()和c()方法。
public class AdapterUSB2VGAImpl extends AdapterUSB2VGA {
public void projection() {
super.projection();
}
}
5.4 總結(jié)
總結(jié)一下三種適配器模式的應(yīng)用場景:
類適配器模式:當(dāng)希望將一個類轉(zhuǎn)換成滿足另一個新接口的類時,可以使用類的適配器模式,創(chuàng)建一個新類,繼承原有的類,實現(xiàn)新的接口即可。
對象適配器模式:當(dāng)希望將一個對象轉(zhuǎn)換成滿足另一個新接口的對象時,可以創(chuàng)建一個Wrapper類,持有原類的一個實例,在Wrapper類的方法中,調(diào)用實例的方法就行。
接口適配器模式:當(dāng)不希望實現(xiàn)一個接口中所有的方法時,可以創(chuàng)建一個抽象類Wrapper,實現(xiàn)所有方法,我們寫別的類的時候,繼承抽象類即可。
命名規(guī)則:
我個人理解,三種命名方式,是根據(jù) src是以怎樣的形式給到Adapter(在Adapter里的形式)來命名的。
類適配器,以類給到,在Adapter里,就是將src當(dāng)做類,繼承,
對象適配器,以對象給到,在Adapter里,將src作為一個對象,持有。
接口適配器,以接口給到,在Adapter里,將src作為一個接口,實現(xiàn)。
使用選擇:
根據(jù)合成復(fù)用原則,組合大于繼承。因此,類的適配器模式應(yīng)該少用。
6 裝飾者模式
定義:動態(tài)的將新功能附加到對象上。在對象功能擴展方面,它比繼承更有彈性。
6.1 裝飾者模式結(jié)構(gòu)圖與代碼示例
1.Component(被裝飾對象的基類)
定義一個對象接口,可以給這些對象動態(tài)地添加職責(zé)。
2.ConcreteComponent(具體被裝飾對象)
定義一個對象,可以給這個對象添加一些職責(zé)。
3.Decorator(裝飾者抽象類)
維持一個指向Component實例的引用,并定義一個與Component接口一致的接口。
4.ConcreteDecorator(具體裝飾者)
具體的裝飾對象,給內(nèi)部持有的具體被裝飾對象,增加具體的職責(zé)。
被裝飾對象和修飾者繼承自同一個超類
舉例(咖啡館訂單項目:1)、咖啡種類:Espresso、ShortBlack、LongBlack、Decaf2)、調(diào)料(裝飾者):Milk、Soy、Chocolate),類圖如下:
被裝飾的對象和裝飾者都繼承自同一個超類
public abstract class Drink {
public String description=“”;
private float price=0f;;
public void setDescription(String description){this.description=description;}public String getDescription(){return description+"-"+this.getPrice();}public float getPrice(){return price;}public void setPrice(float price){this.price=price;}public abstract float cost();
}
被裝飾的對象,不用去改造。原來怎么樣寫,現(xiàn)在還是怎么寫。
public class Coffee extends Drink {
@Override
public float cost() {
// TODO Auto-generated method stub
return super.getPrice();
}
}
裝飾者
裝飾者不僅要考慮自身,還要考慮被它修飾的對象,它是在被修飾的對象上繼續(xù)添加修飾。例如,咖啡里面加牛奶,再加巧克力。加糖后價格為coffee+milk。再加牛奶價格為coffee+milk+chocolate。
public class Decorator extends Drink {
private Drink Obj;
public Decorator(Drink Obj) {
this.Obj = Obj;
};
@Override
public float cost() {
// TODO Auto-generated method stub
return super.getPrice() + Obj.cost();
}
@Override
public String getDescription() {
return super.description + “-” + super.getPrice() + “&&” + Obj.getDescription();
}
}
裝飾者實例化(加牛奶)。這里面要對被修飾的對象進行實例化。
public class Milk extends Decorator {
public Milk(Drink Obj) {
super(Obj);
// TODO Auto-generated constructor stub
super.setDescription(“Milk”);
super.setPrice(2.0f);
}
}
coffee店:初始化一個被修飾對象,修飾者實例需要對被修改者實例化,才能對具體的被修飾者進行修飾。
public class CoffeeBar {
public static void main(String[] args) {
Drink order;
order = new Decaf();
System.out.println(“order1 price:” + order.cost());
System.out.println(“order1 desc:” + order.getDescription());
System.out.println(“****************”);
order = new LongBlack();
order = new Milk(order);
order = new Chocolate(order);
order = new Chocolate(order);
System.out.println(“order2 price:” + order.cost());
System.out.println(“order2 desc:” + order.getDescription());
}
}
6.2 總結(jié)
裝飾者和被裝飾者之間必須是一樣的類型,也就是要有共同的超類。在這里應(yīng)用繼承并不是實現(xiàn)方法的復(fù)制,而是實現(xiàn)類型的匹配。因為裝飾者和被裝飾者是同一個類型,因此裝飾者可以取代被裝飾者,這樣就使被裝飾者擁有了裝飾者獨有的行為。根據(jù)裝飾者模式的理念,我們可以在任何時候,實現(xiàn)新的裝飾者增加新的行為。如果是用繼承,每當(dāng)需要增加新的行為時,就要修改原程序了。
7 代理模式
定義:代理模式給某一個對象提供一個代理對象,并由代理對象控制對原對象的引用。通俗的來講代理模式就是我們生活中常見的中介。
舉個例子來說明:假如說我現(xiàn)在想買一輛二手車,雖然我可以自己去找車源,做質(zhì)量檢測等一系列的車輛過戶流程,但是這確實太浪費我得時間和精力了。我只是想買一輛車而已為什么我還要額外做這么多事呢?于是我就通過中介公司來買車,他們來給我找車源,幫我辦理車輛過戶流程,我只是負(fù)責(zé)選擇自己喜歡的車,然后付錢就可以了。用圖表示如下:
7.1 為什么要用代理模式?
中介隔離作用:在某些情況下,一個客戶類不想或者不能直接引用一個委托對象,而代理類對象可以在客戶類和委托對象之間起到中介的作用,其特征是代理類和委托類實現(xiàn)相同的接口。
開閉原則,增加功能:代理類除了是客戶類和委托類的中介之外,我們還可以通過給代理類增加額外的功能來擴展委托類的功能,這樣做我們只需要修改代理類而不需要再修改委托類,符合代碼設(shè)計的開閉原則。代理類主要負(fù)責(zé)為委托類預(yù)處理消息、過濾消息、把消息轉(zhuǎn)發(fā)給委托類,以及事后對返回結(jié)果的處理等。代理類本身并不真正實現(xiàn)服務(wù),而是同過調(diào)用委托類的相關(guān)方法,來提供特定的服務(wù)。真正的業(yè)務(wù)功能還是由委托類來實現(xiàn),但是可以在業(yè)務(wù)功能執(zhí)行的前后加入一些公共的服務(wù)。例如我們想給項目加入緩存、日志這些功能,我們就可以使用代理類來完成,而沒必要打開已經(jīng)封裝好的委托類。
代理模式分為三類:1. 靜態(tài)代理 2. 動態(tài)代理 3. CGLIB代理
7.2 靜態(tài)代理
舉例(買房),類圖如下:
第一步:創(chuàng)建服務(wù)類接口
public interface BuyHouse {
void buyHosue();
}
第二步:實現(xiàn)服務(wù)接口
public class BuyHouseImpl implements BuyHouse {
@Override
public void buyHosue() {
System.out.println(“我要買房”);
}
}
第三步:創(chuàng)建代理類
public class BuyHouseProxy implements BuyHouse {
private BuyHouse buyHouse;
public BuyHouseProxy(final BuyHouse buyHouse) {
this.buyHouse = buyHouse;
}
@Override
public void buyHosue() {
System.out.println(“買房前準(zhǔn)備”);
buyHouse.buyHosue();
System.out.println(“買房后裝修”);
}
}
總結(jié):
優(yōu)點:可以做到在符合開閉原則的情況下對目標(biāo)對象進行功能擴展。
缺點: 代理對象與目標(biāo)對象要實現(xiàn)相同的接口,我們得為每一個服務(wù)都得創(chuàng)建代理類,工作量太大,不易管理。同時接口一旦發(fā)生改變,代理類也得相應(yīng)修改。
7.3 動態(tài)代理
動態(tài)代理有以下特點:
1.代理對象,不需要實現(xiàn)接口
2.代理對象的生成,是利用JDK的API,動態(tài)的在內(nèi)存中構(gòu)建代理對象(需要我們指定創(chuàng)建代理對象/目標(biāo)對象實現(xiàn)的接口的類型)
代理類不用再實現(xiàn)接口了。但是,要求被代理對象必須有接口。
動態(tài)代理實現(xiàn):
Java.lang.reflect.Proxy類可以直接生成一個代理對象
Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)生成一個代理對象
參數(shù)1:ClassLoader loader 代理對象的類加載器 一般使用被代理對象的類加載器
參數(shù)2:Class<?>[] interfaces 代理對象的要實現(xiàn)的接口 一般使用的被代理對象實現(xiàn)的接口
參數(shù)3:InvocationHandler h (接口)執(zhí)行處理類
InvocationHandler中的invoke(Object proxy, Method method, Object[] args)方法:調(diào)用代理類的任何方法,此方法都會執(zhí)行
參數(shù)3.1:代理對象(慎用)
參數(shù)3.2:當(dāng)前執(zhí)行的方法
參數(shù)3.3:當(dāng)前執(zhí)行的方法運行時傳遞過來的參數(shù)
第一步:編寫動態(tài)處理器
public class DynamicProxyHandler implements InvocationHandler {
private Object object;
public DynamicProxyHandler(final Object object) {
this.object = object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(“買房前準(zhǔn)備”);
Object result = method.invoke(object, args);
System.out.println(“買房后裝修”);
return result;
}
}
第二步:編寫測試類
public class DynamicProxyTest {
public static void main(String[] args) {
BuyHouse buyHouse = new BuyHouseImpl();
BuyHouse proxyBuyHouse = (BuyHouse) Proxy.newProxyInstance(BuyHouse.class.getClassLoader(), new
Class[]{BuyHouse.class}, new DynamicProxyHandler(buyHouse));
proxyBuyHouse.buyHosue();
}
}
動態(tài)代理總結(jié):雖然相對于靜態(tài)代理,動態(tài)代理大大減少了我們的開發(fā)任務(wù),同時減少了對業(yè)務(wù)接口的依賴,降低了耦合度。但是還是有一點點小小的遺憾之處,那就是它始終無法擺脫僅支持interface代理的桎梏(我們要使用被代理的對象的接口),因為它的設(shè)計注定了這個遺憾。
7.4 CGLIB代理
CGLIB 原理:動態(tài)生成一個要代理類的子類,子類重寫要代理的類的所有不是final的方法。在子類中采用方法攔截的技術(shù)攔截所有父類方法的調(diào)用,順勢織入橫切邏輯。它比使用java反射的JDK動態(tài)代理要快。
CGLIB 底層:使用字節(jié)碼處理框架ASM,來轉(zhuǎn)換字節(jié)碼并生成新的類。不鼓勵直接使用ASM,因為它要求你必須對JVM內(nèi)部結(jié)構(gòu)包括class文件的格式和指令集都很熟悉。
CGLIB缺點:對于final方法,無法進行代理。
CGLIB的實現(xiàn)步驟:
第一步:建立攔截器
public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("買房前準(zhǔn)備");Object result = methodProxy.invoke(object, args);System.out.println("買房后裝修");return result;}
參數(shù):Object為由CGLib動態(tài)生成的代理類實例,Method為上文中實體類所調(diào)用的被代理的方法引用,Object[]為參數(shù)值列表,MethodProxy為生成的代理類對方法的代理引用。
返回:從代理實例的方法調(diào)用返回的值。
其中,proxy.invokeSuper(obj,arg) 調(diào)用代理類實例上的proxy方法的父類方法(即實體類TargetObject中對應(yīng)的方法)
第二步: 生成動態(tài)代理類
public class CglibProxy implements MethodInterceptor {
private Object target;
public Object getInstance(final Object target) {
this.target = target;
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(this.target.getClass());
enhancer.setCallback(this);
return enhancer.create();
}
public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println(“買房前準(zhǔn)備”);
Object result = methodProxy.invoke(object, args);
System.out.println(“買房后裝修”);
return result;
}
}
這里Enhancer類是CGLib中的一個字節(jié)碼增強器,它可以方便的對你想要處理的類進行擴展,以后會經(jīng)??吹剿?。
首先將被代理類TargetObject設(shè)置成父類,然后設(shè)置攔截器TargetInterceptor,最后執(zhí)行enhancer.create()動態(tài)生成一個代理類,并從Object強制轉(zhuǎn)型成父類型TargetObject。
第三步:測試
public class CglibProxyTest {
public static void main(String[] args){
BuyHouse buyHouse = new BuyHouseImpl();
CglibProxy cglibProxy = new CglibProxy();
BuyHouseImpl buyHouseCglibProxy = (BuyHouseImpl) cglibProxy.getInstance(buyHouse);
buyHouseCglibProxy.buyHosue();
}
}
CGLIB代理總結(jié): CGLIB創(chuàng)建的動態(tài)代理對象比JDK創(chuàng)建的動態(tài)代理對象的性能更高,但是CGLIB創(chuàng)建代理對象時所花費的時間卻比JDK多得多。所以對于單例的對象,因為無需頻繁創(chuàng)建對象,用CGLIB合適,反之使用JDK方式要更為合適一些。同時由于CGLib由于是采用動態(tài)創(chuàng)建子類的方法,對于final修飾的方法無法進行代理。
8 外觀模式
定義: 隱藏了系統(tǒng)的復(fù)雜性,并向客戶端提供了一個可以訪問系統(tǒng)的接口。
8.1 模式結(jié)構(gòu)和代碼示例
簡單來說,該模式就是把一些復(fù)雜的流程封裝成一個接口供給外部用戶更簡單的使用。這個模式中,設(shè)計到3個角色。
1).門面角色:外觀模式的核心。它被客戶角色調(diào)用,它熟悉子系統(tǒng)的功能。內(nèi)部根據(jù)客戶角色的需求預(yù)定了幾種功能的組合。(客戶調(diào)用,同時自身調(diào)用子系統(tǒng)功能)
2).子系統(tǒng)角色:實現(xiàn)了子系統(tǒng)的功能。它對客戶角色和Facade時未知的。它內(nèi)部可以有系統(tǒng)內(nèi)的相互交互,也可以由供外界調(diào)用的接口。(實現(xiàn)具體功能)
3).客戶角色:通過調(diào)用Facede來完成要實現(xiàn)的功能(調(diào)用門面角色)。
舉例(每個Computer都有CPU、Memory、Disk。在Computer開啟和關(guān)閉的時候,相應(yīng)的部件也會開啟和關(guān)閉),類圖如下:
首先是子系統(tǒng)類:
public class CPU {
public void start() {System.out.println("cpu is start...");
}public void shutDown() {System.out.println("CPU is shutDown...");
}
}
public class Disk {
public void start() {
System.out.println(“Disk is start…”);
}
public void shutDown() {System.out.println("Disk is shutDown...");
}
}
public class Memory {
public void start() {
System.out.println(“Memory is start…”);
}
public void shutDown() {System.out.println("Memory is shutDown...");
}
}
然后是,門面類Facade
public class Computer {
private CPU cpu;
private Memory memory;
private Disk disk;public Computer() {cpu = new CPU();memory = new Memory();disk = new Disk();
}public void start() {System.out.println("Computer start begin");cpu.start();disk.start();memory.start();System.out.println("Computer start end");
}public void shutDown() {System.out.println("Computer shutDown begin");cpu.shutDown();disk.shutDown();memory.shutDown();System.out.println("Computer shutDown end...");
}
}
最后為,客戶角色
public class Client {
public static void main(String[] args) {Computer computer = new Computer();computer.start();System.out.println("=================");computer.shutDown();
}
}
8.2 優(yōu)點
- 松散耦合
使得客戶端和子系統(tǒng)之間解耦,讓子系統(tǒng)內(nèi)部的模塊功能更容易擴展和維護;
- 簡單易用
客戶端根本不需要知道子系統(tǒng)內(nèi)部的實現(xiàn),或者根本不需要知道子系統(tǒng)內(nèi)部的構(gòu)成,它只需要跟Facade類交互即可。
- 更好的劃分訪問層次
有些方法是對系統(tǒng)外的,有些方法是系統(tǒng)內(nèi)部相互交互的使用的。子系統(tǒng)把那些暴露給外部的功能集中到門面中,這樣就可以實現(xiàn)客戶端的使用,很好的隱藏了子系統(tǒng)內(nèi)部的細(xì)節(jié)。
9 橋接模式
定義: 將抽象部分與它的實現(xiàn)部分分離,使它們都可以獨立地變化。
9.1 案例
看下圖手機與手機軟件的類圖
增加一款新的手機軟件,需要在所有手機品牌類下添加對應(yīng)的手機軟件類,當(dāng)手機軟件種類較多時,將導(dǎo)致類的個數(shù)急劇膨脹,難以維護
手機和手機中的軟件是什么關(guān)系?
手機中的軟件從本質(zhì)上來說并不是一種手機,手機軟件運行在手機中,是一種包含與被包含關(guān)系,而不是一種父與子或者說一般與特殊的關(guān)系,通過繼承手機類實現(xiàn)手機軟件類的設(shè)計是違反一般規(guī)律的。
如果Oppo手機實現(xiàn)了wifi功能,繼承它的Oppo應(yīng)用商城也會繼承wifi功能,并且Oppo手機類的任何變動,都會影響其子類
換一種解決思路
從類圖上看起來更像是手機軟件類圖,涉及到手機本身相關(guān)的功能,比如說:wifi功能,放到哪個類中實現(xiàn)呢?放到OppoAppStore中實現(xiàn)顯然是不合適的
引起整個結(jié)構(gòu)變化的元素有兩個,一個是手機品牌,一個是手機軟件,所以我們將這兩個點抽出來,分別進行封裝
9.2 橋接模式結(jié)構(gòu)和代碼示例
類圖:
實現(xiàn):
public interface Software {
public void run();
}
public class AppStore implements Software {
@Override
public void run() {System.out.println("run app store");
}
}
public class Camera implements Software {
@Override
public void run() {System.out.println("run camera");
}
}
抽象:
public abstract class Phone {
protected Software software;public void setSoftware(Software software) {this.software = software;
}public abstract void run();
}
public class Oppo extends Phone {
@Override
public void run() {Coming Soon();
}
}
public class Vivo extends Phone {
@Override
public void run() {Coming Soon();
}
}
對比最初的設(shè)計,將抽象部分(手機)與它的實現(xiàn)部分(手機軟件類)分離,將實現(xiàn)部分抽象成單獨的類,使它們都可以獨立地變化。整個類圖看起來像一座橋,所以稱為橋接模式
繼承是一種強耦合關(guān)系,子類的實現(xiàn)與它的父類有非常緊密的依賴關(guān)系,父類的任何變化 都會導(dǎo)致子類發(fā)生變化,因此繼承或者說強耦合關(guān)系嚴(yán)重影響了類的靈活性,并最終限制了可復(fù)用性
從橋接模式的設(shè)計上我們可以看出聚合是一種比繼承要弱的關(guān)聯(lián)關(guān)系,手機類和軟件類都可獨立的進行變化,不會互相影響
9.3 適用場景
橋接模式通常適用于以下場景。
當(dāng)一個類存在兩個獨立變化的維度,且這兩個維度都需要進行擴展時。
當(dāng)一個系統(tǒng)不希望使用繼承或因為多層次繼承導(dǎo)致系統(tǒng)類的個數(shù)急劇增加時。
當(dāng)一個系統(tǒng)需要在構(gòu)件的抽象化角色和具體化角色之間增加更多的靈活性時。
9.4 優(yōu)缺點
優(yōu)點:
(1)在很多情況下,橋接模式可以取代多層繼承方案,多層繼承方案違背了“單一職責(zé)原則”,復(fù)用性較差,且類的個數(shù)非常多,橋接模式是比多層繼承方案更好的解決方法,它極大減少了子類的個數(shù)。
(2)橋接模式提高了系統(tǒng)的可擴展性,在兩個變化維度中任意擴展一個維度,都不需要修改原有系統(tǒng),符合“開閉原則”。
缺點:
橋接模式的使用會增加系統(tǒng)的理解與設(shè)計難度,由于關(guān)聯(lián)關(guān)系建立在抽象層,要求開發(fā)者一開始就針對抽象層進行設(shè)計與編程。
10 組合模式
定義:有時又叫作部分-整體模式,它是一種將對象組合成樹狀的層次結(jié)構(gòu)的模式,用來表示“部分-整體”的關(guān)系,使用戶對單個對象和組合對象具有一致的訪問性。
意圖:將對象組合成樹形結(jié)構(gòu)以表示"部分-整體"的層次結(jié)構(gòu)。組合模式使得用戶對單個對象和組合對象的使用具有一致性。
主要解決:它在我們樹型結(jié)構(gòu)的問題中,模糊了簡單元素和復(fù)雜元素的概念,客戶程序可以向處理簡單元素一樣來處理復(fù)雜元素,從而使得客戶程序與復(fù)雜元素的內(nèi)部結(jié)構(gòu)解耦。
何時使用: 1、您想表示對象的部分-整體層次結(jié)構(gòu)(樹形結(jié)構(gòu))。 2、您希望用戶忽略組合對象與單個對象的不同,用戶將統(tǒng)一地使用組合結(jié)構(gòu)中的所有對象。
如何解決:樹枝和葉子實現(xiàn)統(tǒng)一接口,樹枝內(nèi)部組合該接口。
關(guān)鍵代碼:樹枝內(nèi)部組合該接口,并且含有內(nèi)部屬性 List,里面放 Component。
組合模式的主要優(yōu)點有:
組合模式使得客戶端代碼可以一致地處理單個對象和組合對象,無須關(guān)心自己處理的是單個對象,還是組合對象,這簡化了客戶端代碼;
更容易在組合體內(nèi)加入新的對象,客戶端不會因為加入了新的對象而更改源代碼,滿足“開閉原則”;
其主要缺點是:
設(shè)計較復(fù)雜,客戶端需要花更多時間理清類之間的層次關(guān)系;
不容易限制容器中的構(gòu)件;
不容易用繼承的方法來增加構(gòu)件的新功能;
10.1 模式結(jié)構(gòu)和代碼示例
抽象構(gòu)件(Component)角色:它的主要作用是為樹葉構(gòu)件和樹枝構(gòu)件聲明公共接口,并實現(xiàn)它們的默認(rèn)行為。在透明式的組合模式中抽象構(gòu)件還聲明訪問和管理子類的接口;在安全式的組合模式中不聲明訪問和管理子類的接口,管理工作由樹枝構(gòu)件完成。
樹葉構(gòu)件(Leaf)角色:是組合中的葉節(jié)點對象,它沒有子節(jié)點,用于實現(xiàn)抽象構(gòu)件角色中 聲明的公共接口。
樹枝構(gòu)件(Composite)角色:是組合中的分支節(jié)點對象,它有子節(jié)點。它實現(xiàn)了抽象構(gòu)件角色中聲明的接口,它的主要作用是存儲和管理子部件,通常包含 Add()、Remove()、GetChild() 等方法
舉例(訪問一顆樹),類圖如下:
1 組件
public interface Component {
public void add(Component c);
public void remove(Component c);
public Component getChild(int i);
public void operation();
}
2 葉子
public class Leaf implements Component{
private String name;public Leaf(String name) {this.name = name;
}@Override
public void add(Component c) {}@Override
public void remove(Component c) {}@Override
public Component getChild(int i) {// TODO Auto-generated method stubreturn null;
}@Override
public void operation() {// TODO Auto-generated method stubSystem.out.println("樹葉"+name+":被訪問!");
}
}
3 樹枝
public class Composite implements Component {
private ArrayList<Component> children = new ArrayList<Component>();public void add(Component c) {children.add(c);
}public void remove(Component c) {children.remove(c);
}public Component getChild(int i) {return children.get(i);
}public void operation() {for (Object obj : children) {((Component) obj).operation();}
}
}
11 享元模式
定義:通過共享的方式高效的支持大量細(xì)粒度的對象。
主要解決:在有大量對象時,有可能會造成內(nèi)存溢出,我們把其中共同的部分抽象出來,如果有相同的業(yè)務(wù)請求,直接返回在內(nèi)存中已有的對象,避免重新創(chuàng)建。
何時使用:
1、系統(tǒng)中有大量對象。
2、這些對象消耗大量內(nèi)存。
3、這些對象的狀態(tài)大部分可以外部化。
4、這些對象可以按照內(nèi)蘊狀態(tài)分為很多組,當(dāng)把外蘊對象從對象中剔除出來時,每一組對象都可以用一個對象來代替。
5、系統(tǒng)不依賴于這些對象身份,這些對象是不可分辨的。
如何解決:用唯一標(biāo)識碼判斷,如果在內(nèi)存中有,則返回這個唯一標(biāo)識碼所標(biāo)識的對象。
關(guān)鍵代碼:用 HashMap 存儲這些對象。
應(yīng)用實例: 1、JAVA 中的 String,如果有則返回,如果沒有則創(chuàng)建一個字符串保存在字符串緩存池里面。
優(yōu)點:大大減少對象的創(chuàng)建,降低系統(tǒng)的內(nèi)存,使效率提高。
缺點:提高了系統(tǒng)的復(fù)雜度,需要分離出外部狀態(tài)和內(nèi)部狀態(tài),而且外部狀態(tài)具有固有化的性質(zhì),不應(yīng)該隨著內(nèi)部狀態(tài)的變化而變化,否則會造成系統(tǒng)的混亂。
簡單來說,我們抽取出一個對象的外部狀態(tài)(不能共享)和內(nèi)部狀態(tài)(可以共享)。然后根據(jù)外部狀態(tài)的決定是否創(chuàng)建內(nèi)部狀態(tài)對象。內(nèi)部狀態(tài)對象是通過哈希表保存的,當(dāng)外部狀態(tài)相同的時候,不再重復(fù)的創(chuàng)建內(nèi)部狀態(tài)對象,從而減少要創(chuàng)建對象的數(shù)量。
11.1 享元模式的結(jié)構(gòu)圖和代碼示例
1、Flyweight (享元抽象類):一般是接口或者抽象類,定義了享元類的公共方法。這些方法可以分享內(nèi)部狀態(tài)的數(shù)據(jù),也可以調(diào)用這些方法修改外部狀態(tài)。
2、ConcreteFlyweight(具體享元類):具體享元類實現(xiàn)了抽象享元類的方法,為享元對象開辟了內(nèi)存空間來保存享元對象的內(nèi)部數(shù)據(jù),同時可以通過和單例模式結(jié)合只創(chuàng)建一個享元對象。
3、FlyweightFactory(享元工廠類):享元工廠類創(chuàng)建并且管理享元類,享元工廠類針對享元類來進行編程,通過提供一個享元池來進行享元對象的管理。一般享元池設(shè)計成鍵值對,或者其他的存儲結(jié)構(gòu)來存儲。當(dāng)客戶端進行享元對象的請求時,如果享元池中有對應(yīng)的享元對象則直接返回對應(yīng)的對象,否則工廠類創(chuàng)建對應(yīng)的享元對象并保存到享元池。
舉例(JAVA 中的 String,如果有則返回,如果沒有則創(chuàng)建一個字符串保存在字符串緩存池里面)。類圖如下:
(1)創(chuàng)建享元對象接口
public interface IFlyweight {
void print();
}
(2)創(chuàng)建具體享元對象
public class Flyweight implements IFlyweight {
private String id;
public Flyweight(String id){
this.id = id;
}
@Override
public void print() {
System.out.println(“Flyweight.id = " + getId() + " …”);
}
public String getId() {
return id;
}
}
(3)創(chuàng)建工廠,這里要特別注意,為了避免享元對象被重復(fù)創(chuàng)建,我們使用HashMap中的key值保證其唯一。
public class FlyweightFactory {
private Map<String, IFlyweight> flyweightMap = new HashMap();
public IFlyweight getFlyweight(String str){
IFlyweight flyweight = flyweightMap.get(str);
if(flyweight == null){
flyweight = new Flyweight(str);
flyweightMap.put(str, flyweight);
}
return flyweight;
}
public int getFlyweightMapSize(){
return flyweightMap.size();
}
}
(4)測試,我們創(chuàng)建三個字符串,但是只會產(chǎn)生兩個享元對象
public class MainTest {
public static void main(String[] args) {
FlyweightFactory flyweightFactory = new FlyweightFactory();
IFlyweight flyweight1 = flyweightFactory.getFlyweight(“A”);
IFlyweight flyweight2 = flyweightFactory.getFlyweight(“B”);
IFlyweight flyweight3 = flyweightFactory.getFlyweight(“A”);
flyweight1.print();
flyweight2.print();
flyweight3.print();
System.out.println(flyweightFactory.getFlyweightMapSize());
}
}
三、關(guān)系模式(11種)
先來張圖,看看這11中模式的關(guān)系:
第一類:通過父類與子類的關(guān)系進行實現(xiàn)。
第二類:兩個類之間。
第三類:類的狀態(tài)。
第四類:通過中間類
12 策略模式
定義: 策略模式定義了一系列算法,并將每個算法封裝起來,使他們可以相互替換,且算法的變化不會影響到使用算法的客戶。
意圖:定義一系列的算法,把它們一個個封裝起來, 并且使它們可相互替換。
主要解決:在有多種算法相似的情況下,使用 if…else 所帶來的復(fù)雜和難以維護。
何時使用:一個系統(tǒng)有許多許多類,而區(qū)分它們的只是他們直接的行為。
如何解決:將這些算法封裝成一個一個的類,任意地替換。
關(guān)鍵代碼:實現(xiàn)同一個接口。
優(yōu)點: 1、算法可以自由切換。 2、避免使用多重條件判斷。 3、擴展性良好。
缺點: 1、策略類會增多。 2、所有策略類都需要對外暴露。
12.1 策略模式結(jié)構(gòu)和示例代碼
抽象策略角色: 這個是一個抽象的角色,通常情況下使用接口或者抽象類去實現(xiàn)。對比來說,就是我們的Comparator接口。
具體策略角色: 包裝了具體的算法和行為。對比來說,就是實現(xiàn)了Comparator接口的實現(xiàn)一組實現(xiàn)類。
環(huán)境角色: 內(nèi)部會持有一個抽象角色的引用,給客戶端調(diào)用。
舉例如下( 實現(xiàn)一個加減的功能),類圖如下:
1、定義抽象策略角色
public interface Strategy {
public int calc(int num1,int num2);
}
2、定義具體策略角色
public class AddStrategy implements Strategy {
@Override
public int calc(int num1, int num2) {// TODO Auto-generated method stubreturn num1 + num2;
}
}
public class SubstractStrategy implements Strategy {
@Override
public int calc(int num1, int num2) {// TODO Auto-generated method stubreturn num1 - num2;
}
}
3、環(huán)境角色
public class Environment {
private Strategy strategy;
public Environment(Strategy strategy) {this.strategy = strategy;
}public int calculate(int a, int b) {return strategy.calc(a, b);
}
}
4、測試
public class MainTest {
public static void main(String[] args) {
Environment environment=new Environment(new AddStrategy());int result=environment.calculate(20, 5);System.out.println(result);Environment environment1=new Environment(new SubstractStrategy());int result1=environment1.calculate(20, 5);System.out.println(result1);
}
}
13 模板模式
定義:定義一個操作中算法的骨架,而將一些步驟延遲到子類中,模板方法使得子類可以不改變算法的結(jié)構(gòu)即可重定義該算法的某些特定步驟。
通俗點的理解就是 :完成一件事情,有固定的數(shù)個步驟,但是每個步驟根據(jù)對象的不同,而實現(xiàn)細(xì)節(jié)不同;就可以在父類中定義一個完成該事情的總方法,按照完成事件需要的步驟去調(diào)用其每個步驟的實現(xiàn)方法。每個步驟的具體實現(xiàn),由子類完成。
13.1 模式結(jié)構(gòu)和代碼示例
抽象父類(AbstractClass):實現(xiàn)了模板方法,定義了算法的骨架。
具體類(ConcreteClass):實現(xiàn)抽象類中的抽象方法,即不同的對象的具體實現(xiàn)細(xì)節(jié)。23 種設(shè)計模式詳解(全23種)具體類(ConcreteClass):實現(xiàn)抽象類中的抽象方法,即不同的對象的具體實現(xiàn)細(xì)節(jié)。
舉例( 我們做菜可以分為三個步驟 (1)備料 (2)具體做菜 (3)盛菜端給客人享用,這三部就是算法的骨架 ;然而做不同菜需要的料,做的方法,以及如何盛裝給客人享用都是不同的這個就是不同的實現(xiàn)細(xì)節(jié)。)。類圖如下:
a. 先來寫一個抽象的做菜父類:
public abstract class Dish {
/**
* 具體的整個過程
/
protected void dodish(){
this.preparation();
this.doing();
this.carriedDishes();
}
/*
* 備料
/
public abstract void preparation();
/*
* 做菜
/
public abstract void doing();
/*
* 上菜
*/
public abstract void carriedDishes ();
}
b. 下來做兩個番茄炒蛋(EggsWithTomato)和紅燒肉(Bouilli)實現(xiàn)父類中的抽象方法
public class EggsWithTomato extends Dish {
@Override
public void preparation() {System.out.println("洗并切西紅柿,打雞蛋。");
}@Override
public void doing() {System.out.println("雞蛋倒入鍋里,然后倒入西紅柿一起炒。");
}@Override
public void carriedDishes() {System.out.println("將炒好的西紅寺雞蛋裝入碟子里,端給客人吃。");
}
}
public class Bouilli extends Dish{
@Override
public void preparation() {System.out.println("切豬肉和土豆。");
}@Override
public void doing() {System.out.println("將切好的豬肉倒入鍋中炒一會然后倒入土豆連炒帶燉。");
}@Override
public void carriedDishes() {System.out.println("將做好的紅燒肉盛進碗里端給客人吃。");
}
}
c. 在測試類中我們來做菜:
public class MainTest {
public static void main(String[] args) {
Dish eggsWithTomato = new EggsWithTomato();
eggsWithTomato.dodish();
System.out.println("-----------------------------");Dish bouilli = new Bouilli();bouilli.dodish();
}
}
13.2 模板模式的優(yōu)點和缺點
優(yōu)點:
(1)具體細(xì)節(jié)步驟實現(xiàn)定義在子類中,子類定義詳細(xì)處理算法是不會改變算法整體結(jié)構(gòu)。
(2)代碼復(fù)用的基本技術(shù),在數(shù)據(jù)庫設(shè)計中尤為重要。
(3)存在一種反向的控制結(jié)構(gòu),通過一個父類調(diào)用其子類的操作,通過子類對父類進行擴展增加新的行為,符合“開閉原則”。
缺點:
每個不同的實現(xiàn)都需要定義一個子類,會導(dǎo)致類的個數(shù)增加,系統(tǒng)更加龐大。
14 觀察者模式
定義: 定義對象間的一種一對多的依賴關(guān)系,當(dāng)一個對象的狀態(tài)發(fā)生改變時,所有依賴于它的對象都得到通知并被自動更新。
主要解決:一個對象狀態(tài)改變給其他對象通知的問題,而且要考慮到易用和低耦合,保證高度的協(xié)作。
何時使用:一個對象(目標(biāo)對象)的狀態(tài)發(fā)生改變,所有的依賴對象(觀察者對象)都將得到通知,進行廣播通知。
如何解決:使用面向?qū)ο蠹夹g(shù),可以將這種依賴關(guān)系弱化。
關(guān)鍵代碼:在抽象類里有一個 ArrayList 存放觀察者們。
優(yōu)點:
1、觀察者和被觀察者是抽象耦合的。
2、建立一套觸發(fā)機制。
缺點:
1、如果一個被觀察者對象有很多的直接和間接的觀察者的話,將所有的觀察者都通知到會花費很多時間。
2、如果在觀察者和觀察目標(biāo)之間有循環(huán)依賴的話,觀察目標(biāo)會觸發(fā)它們之間進行循環(huán)調(diào)用,可能導(dǎo)致系統(tǒng)崩潰。
3、觀察者模式?jīng)]有相應(yīng)的機制讓觀察者知道所觀察的目標(biāo)對象是怎么發(fā)生變化的,而僅僅只是知道觀察目標(biāo)發(fā)生了變化。
14.1 模式結(jié)構(gòu)圖和代碼示例
抽象被觀察者角色:也就是一個抽象主題,它把所有對觀察者對象的引用保存在一個集合中,每個主題都可以有任意數(shù)量的觀察者。抽象主題提供一個接口,可以增加和刪除觀察者角色。一般用一個抽象類和接口來實現(xiàn)。
抽象觀察者角色:為所有的具體觀察者定義一個接口,在得到主題通知時更新自己
具體被觀察者角色:也就是一個具體的主題,在集體主題的內(nèi)部狀態(tài)改變時,所有登記過的觀察者發(fā)出通知。
具體觀察者角色:實現(xiàn)抽象觀察者角色所需要的更新接口,一邊使本身的狀態(tài)與制圖的狀態(tài)相協(xié)調(diào)。
舉例(有一個微信公眾號服務(wù),不定時發(fā)布一些消息,關(guān)注公眾號就可以收到推送消息,取消關(guān)注就收不到推送消息。)類圖如下:
1、定義一個抽象被觀察者接口
public interface Subject {
public void registerObserver(Observer o);public void removeObserver(Observer o);public void notifyObserver();
}
2、定義一個抽象觀察者接口
public interface Observer {
public void update(String message);
}
3、定義被觀察者,實現(xiàn)了Observerable接口,對Observerable接口的三個方法進行了具體實現(xiàn),同時有一個List集合,用以保存注冊的觀察者,等需要通知觀察者時,遍歷該集合即可。
public class WechatServer implements Subject {
private List<Observer> list;
private String message;public WechatServer() {list = new ArrayList<Observer>();
}@Override
public void registerObserver(Observer o) {// TODO Auto-generated method stublist.add(o);
}@Override
public void removeObserver(Observer o) {// TODO Auto-generated method stubif (!list.isEmpty()) {list.remove(o);}
}@Override
public void notifyObserver() {// TODO Auto-generated method stubfor (Observer o : list) {o.update(message);}
}public void setInfomation(String s) {this.message = s;System.out.println("微信服務(wù)更新消息: " + s);// 消息更新,通知所有觀察者notifyObserver();
}
}
4、定義具體觀察者,微信公眾號的具體觀察者為用戶User
public class User implements Observer {
private String name;
private String message;public User(String name) {this.name = name;
}@Override
public void update(String message) {this.message = message;read();
}public void read() {System.out.println(name + " 收到推送消息: " + message);
}
}
5、編寫一個測試類
public class MainTest {
public static void main(String[] args) {WechatServer server = new WechatServer();Observer userZhang = new User("ZhangSan");Observer userLi = new User("LiSi");Observer userWang = new User("WangWu");server.registerObserver(userZhang);server.registerObserver(userLi);server.registerObserver(userWang);server.setInfomation("PHP是世界上最好用的語言!");System.out.println("----------------------------------------------");server.removeObserver(userZhang);server.setInfomation("JAVA是世界上最好用的語言!");}
}
15 迭代器模式
定義:提供一種方法順序訪問一個聚合對象中各個元素, 而又無須暴露該對象的內(nèi)部表示。
簡單來說,不同種類的對象可能需要不同的遍歷方式,我們對每一種類型的對象配一個迭代器,最后多個迭代器合成一個。
主要解決:不同的方式來遍歷整個整合對象。
何時使用:遍歷一個聚合對象。
如何解決:把在元素之間游走的責(zé)任交給迭代器,而不是聚合對象。
關(guān)鍵代碼:定義接口:hasNext, next。
應(yīng)用實例:JAVA 中的 iterator。
優(yōu)點: 1、它支持以不同的方式遍歷一個聚合對象。 2、迭代器簡化了聚合類。 3、在同一個聚合上可以有多個遍歷。 4、在迭代器模式中,增加新的聚合類和迭代器類都很方便,無須修改原有代碼。
缺點:由于迭代器模式將存儲數(shù)據(jù)和遍歷數(shù)據(jù)的職責(zé)分離,增加新的聚合類需要對應(yīng)增加新的迭代器類,類的個數(shù)成對增加,這在一定程度上增加了系統(tǒng)的復(fù)雜性。
15.1 模式結(jié)構(gòu)和代碼示例
(1)迭代器角色(Iterator):定義遍歷元素所需要的方法,一般來說會有這么三個方法:取得下一個元素的方法next(),判斷是否遍歷結(jié)束的方法hasNext()),移出當(dāng)前對象的方法remove(),
(2)具體迭代器角色(Concrete Iterator):實現(xiàn)迭代器接口中定義的方法,完成集合的迭代。
(3)容器角色(Aggregate): 一般是一個接口,提供一個iterator()方法,例如java中的Collection接口,List接口,Set接口等
(4)具體容器角色(ConcreteAggregate):就是抽象容器的具體實現(xiàn)類,比如List接口的有序列表實現(xiàn)ArrayList,List接口的鏈表實現(xiàn)LinkList,Set接口的哈希列表的實現(xiàn)HashSet等。
舉例(咖啡廳和中餐廳合并,他們兩個餐廳的菜單一個是數(shù)組保存的,一個是ArrayList保存的。遍歷方式不一樣,使用迭代器聚合訪問,只需要一種方式)
1 迭代器接口
public interface Iterator {
public boolean hasNext();
public Object next();
}
2 咖啡店菜單和咖啡店菜單遍歷器
public class CakeHouseMenu {
private ArrayList menuItems;
public CakeHouseMenu() {menuItems = new ArrayList<MenuItem>();addItem("KFC Cake Breakfast","boiled eggs&toast&cabbage",true,3.99f);addItem("MDL Cake Breakfast","fried eggs&toast",false,3.59f);addItem("Stawberry Cake","fresh stawberry",true,3.29f);addItem("Regular Cake Breakfast","toast&sausage",true,2.59f);
}private void addItem(String name, String description, boolean vegetable,float price) {MenuItem menuItem = new MenuItem(name, description, vegetable, price);menuItems.add(menuItem);
}public Iterator getIterator()
{return new CakeHouseIterator() ;
}class CakeHouseIterator implements Iterator{ private int position=0;public CakeHouseIterator(){position=0;}@Overridepublic boolean hasNext() {// TODO Auto-generated method stubif(position<menuItems.size()){return true;}return false;}@Overridepublic Object next() {// TODO Auto-generated method stubMenuItem menuItem =menuItems.get(position);position++;return menuItem;}};
//鍏朵粬鍔熻兘浠g爜
}
3 中餐廳菜單和中餐廳菜單遍歷器
public class DinerMenu {
private final static int Max_Items = 5;
private int numberOfItems = 0;
private MenuItem[] menuItems;
public DinerMenu() {menuItems = new MenuItem[Max_Items];addItem("vegetable Blt", "bacon&lettuce&tomato&cabbage", true, 3.58f);addItem("Blt", "bacon&lettuce&tomato", false, 3.00f);addItem("bean soup", "bean&potato salad", true, 3.28f);addItem("hotdog", "onions&cheese&bread", false, 3.05f);}private void addItem(String name, String description, boolean vegetable,float price) {MenuItem menuItem = new MenuItem(name, description, vegetable, price);if (numberOfItems >= Max_Items) {System.err.println("sorry,menu is full!can not add another item");} else {menuItems[numberOfItems] = menuItem;numberOfItems++;}}public Iterator getIterator() {return new DinerIterator();
}class DinerIterator implements Iterator {private int position;public DinerIterator() {position = 0;}@Overridepublic boolean hasNext() {// TODO Auto-generated method stubif (position < numberOfItems) {return true;}return false;}@Overridepublic Object next() {// TODO Auto-generated method stubMenuItem menuItem = menuItems[position];position++;return menuItem;}
};
}
4 女服務(wù)員
public class Waitress {
private ArrayList iterators = new ArrayList();
public Waitress() {}public void addIterator(Iterator iterator) {iterators.add(iterator);}public void printMenu() {Iterator iterator;MenuItem menuItem;for (int i = 0, len = iterators.size(); i < len; i++) {iterator = iterators.get(i);while (iterator.hasNext()) {menuItem = (MenuItem) iterator.next();System.out.println(menuItem.getName() + "***" + menuItem.getPrice() + "***" + menuItem.getDescription());}}}public void printBreakfastMenu() {}public void printLunchMenu() {}public void printVegetableMenu() {}
}
16 責(zé)任鏈模式
定義:如果有多個對象有機會處理請求,責(zé)任鏈可使請求的發(fā)送者和接受者解耦,請求沿著責(zé)任鏈傳遞,直到有一個對象處理了它為止。
主要解決:職責(zé)鏈上的處理者負(fù)責(zé)處理請求,客戶只需要將請求發(fā)送到職責(zé)鏈上即可,無須關(guān)心請求的處理細(xì)節(jié)和請求的傳遞,所以職責(zé)鏈將請求的發(fā)送者和請求的處理者解耦了。
何時使用:在處理消息的時候以過濾很多道。
如何解決:攔截的類都實現(xiàn)統(tǒng)一接口。
關(guān)鍵代碼:Handler 里面聚合它自己,在 HandlerRequest 里判斷是否合適,如果沒達到條件則向下傳遞,向誰傳遞之前 set 進去。
16.1 模式的結(jié)構(gòu)和代碼示例
抽象處理者(Handler)角色:定義一個處理請求的接口,包含抽象處理方法和一個后繼連接。
具體處理者(Concrete Handler)角色:實現(xiàn)抽象處理者的處理方法,判斷能否處理本次請求,如果可以處理請求則處理,否則將該請求轉(zhuǎn)給它的后繼者。
客戶類(Client)角色:創(chuàng)建處理鏈,并向鏈頭的具體處理者對象提交請求,它不關(guān)心處理細(xì)節(jié)和請求的傳遞過程。
舉例(購買請求決策,價格不同要由不同的級別決定:組長、部長、副部、總裁)。類圖如下:
1 決策者抽象類,包含對請求處理的函數(shù),同時還包含指定下一個決策者的函數(shù)
public abstract class Approver {
Approver successor;
String Name;
public Approver(String Name)
{
this.Name=Name;
}
public abstract void ProcessRequest( PurchaseRequest request);
public void SetSuccessor(Approver successor) {
// TODO Auto-generated method stub
this.successor=successor;
}
}
2 客戶端以及請求
public class PurchaseRequest {
private int Type = 0;
private int Number = 0;
private float Price = 0;
private int ID = 0;
public PurchaseRequest(int Type, int Number, float Price) {this.Type = Type;this.Number = Number;this.Price = Price;
}public int GetType() {return Type;
}public float GetSum() {return Number * Price;
}public int GetID() {return (int) (Math.random() * 1000);
}
}
public class Client {
public Client() {}public PurchaseRequest sendRequst(int Type, int Number, float Price) {return new PurchaseRequest(Type, Number, Price);
}
}
3 組長、部長。。。繼承決策者抽象類
public class GroupApprover extends Approver {
public GroupApprover(String Name) {super(Name + " GroupLeader");// TODO Auto-generated constructor stub}@Override
public void ProcessRequest(PurchaseRequest request) {// TODO Auto-generated method stubif (request.GetSum() < 5000) {System.out.println("**This request " + request.GetID() + " will be handled by " + this.Name + " **");} else {successor.ProcessRequest(request);}
}
}
public class DepartmentApprover extends Approver {
public DepartmentApprover(String Name) {super(Name + " DepartmentLeader");}@Override
public void ProcessRequest(PurchaseRequest request) {// TODO Auto-generated method stubif ((5000 <= request.GetSum()) && (request.GetSum() < 10000)) {System.out.println("**This request " + request.GetID()+ " will be handled by " + this.Name + " **");} else {successor.ProcessRequest(request);}}
}
4測試
public class MainTest {
public static void main(String[] args) {Client mClient = new Client();Approver GroupLeader = new GroupApprover("Tom");Approver DepartmentLeader = new DepartmentApprover("Jerry");Approver VicePresident = new VicePresidentApprover("Kate");Approver President = new PresidentApprover("Bush");GroupLeader.SetSuccessor(VicePresident);DepartmentLeader.SetSuccessor(President);VicePresident.SetSuccessor(DepartmentLeader);President.SetSuccessor(GroupLeader);GroupLeader.ProcessRequest(mClient.sendRequst(1, 10000, 40));}
}
17 命令模式
定義:將一個請求封裝為一個對象,使發(fā)出請求的責(zé)任和執(zhí)行請求的責(zé)任分割開。這樣兩者之間通過命令對象進行溝通,這樣方便將命令對象進行儲存、傳遞、調(diào)用、增加與管理。
意圖:將一個請求封裝成一個對象,從而使您可以用不同的請求對客戶進行參數(shù)化。
主要解決:在軟件系統(tǒng)中,行為請求者與行為實現(xiàn)者通常是一種緊耦合的關(guān)系,但某些場合,比如需要對行為進行記錄、撤銷或重做、事務(wù)等處理時,這種無法抵御變化的緊耦合的設(shè)計就不太合適。
何時使用:在某些場合,比如要對行為進行"記錄、撤銷/重做、事務(wù)"等處理,這種無法抵御變化的緊耦合是不合適的。在這種情況下,如何將"行為請求者"與"行為實現(xiàn)者"解耦?將一組行為抽象為對象,可以實現(xiàn)二者之間的松耦合。
如何解決:通過調(diào)用者調(diào)用接受者執(zhí)行命令,順序:調(diào)用者→接受者→命令。
17.1模式結(jié)構(gòu)和代碼示例
抽象命令類(Command)角色:聲明執(zhí)行命令的接口,擁有執(zhí)行命令的抽象方法 execute()。
具體命令角色(Concrete Command)角色:是抽象命令類的具體實現(xiàn)類,它擁有接收者對象,并通過調(diào)用接收者的功能來完成命令要執(zhí)行的操作。
實現(xiàn)者/接收者(Receiver)角色:執(zhí)行命令功能的相關(guān)操作,是具體命令對象業(yè)務(wù)的真正實現(xiàn)者。
調(diào)用者/請求者(Invoker)角色:是請求的發(fā)送者,它通常擁有很多的命令對象,并通過訪問命令對象來執(zhí)行相關(guān)請求,它不直接訪問接收者。
代碼舉例(開燈和關(guān)燈),類圖如下:
1 命令抽象類
public interface Command {
public void excute();
public void undo();
}
2 具體命令對象
public class TurnOffLight implements Command {
private Light light;public TurnOffLight(Light light) {this.light = light;
}@Override
public void excute() {// TODO Auto-generated method stublight.Off();
}@Override
public void undo() {// TODO Auto-generated method stublight.On();
}
}
3 實現(xiàn)者
public class Light {
String loc = "";public Light(String loc) {this.loc = loc;
}public void On() {System.out.println(loc + " On");
}public void Off() {System.out.println(loc + " Off");
}
}
4 請求者
public class Contral{
public void CommandExcute(Command command) {// TODO Auto-generated method stubcommand.excute();
}public void CommandUndo(Command command) {// TODO Auto-generated method stubcommand.undo();
}
}
18 狀態(tài)模式
定義: 在狀態(tài)模式中,我們創(chuàng)建表示各種狀態(tài)的對象和一個行為隨著狀態(tài)對象改變而改變的 context 對象。
簡單理解,一個擁有狀態(tài)的context對象,在不同的狀態(tài)下,其行為會發(fā)生改變。
意圖:允許對象在內(nèi)部狀態(tài)發(fā)生改變時改變它的行為,對象看起來好像修改了它的類。
主要解決:對象的行為依賴于它的狀態(tài)(屬性),并且可以根據(jù)它的狀態(tài)改變而改變它的相關(guān)行為。
何時使用:代碼中包含大量與對象狀態(tài)有關(guān)的條件語句。
如何解決:將各種具體的狀態(tài)類抽象出來。
關(guān)鍵代碼:通常命令模式的接口中只有一個方法。而狀態(tài)模式的接口中有一個或者多個方法。而且,狀態(tài)模式的實現(xiàn)類的方法,一般返回值,或者是改變實例變量的值。也就是說,狀態(tài)模式一般和對象的狀態(tài)有關(guān)。實現(xiàn)類的方法有不同的功能,覆蓋接口中的方法。狀態(tài)模式和命令模式一樣,也可以用于消除 if…else 等條件選擇語句。
優(yōu)點: 1、封裝了轉(zhuǎn)換規(guī)則。 2、枚舉可能的狀態(tài),在枚舉狀態(tài)之前需要確定狀態(tài)種類。 3、將所有與某個狀態(tài)有關(guān)的行為放到一個類中,并且可以方便地增加新的狀態(tài),只需要改變對象狀態(tài)即可改變對象的行為。 4、允許狀態(tài)轉(zhuǎn)換邏輯與狀態(tài)對象合成一體,而不是某一個巨大的條件語句塊。 5、可以讓多個環(huán)境對象共享一個狀態(tài)對象,從而減少系統(tǒng)中對象的個數(shù)。
缺點: 1、狀態(tài)模式的使用必然會增加系統(tǒng)類和對象的個數(shù)。 2、狀態(tài)模式的結(jié)構(gòu)與實現(xiàn)都較為復(fù)雜,如果使用不當(dāng)將導(dǎo)致程序結(jié)構(gòu)和代碼的混亂。 3、狀態(tài)模式對"開閉原則"的支持并不太好,對于可以切換狀態(tài)的狀態(tài)模式,增加新的狀態(tài)類需要修改那些負(fù)責(zé)狀態(tài)轉(zhuǎn)換的源代碼,否則無法切換到新增狀態(tài),而且修改某個狀態(tài)類的行為也需修改對應(yīng)類的源代碼。
18.1 模式結(jié)構(gòu)和代碼示例
State抽象狀態(tài)角色
接口或抽象類,負(fù)責(zé)對象狀態(tài)定義,并且封裝環(huán)境角色以實現(xiàn)狀態(tài)切換。
ConcreteState具體狀態(tài)角色
具體狀態(tài)主要有兩個職責(zé):一是處理本狀態(tài)下的事情,二是從本狀態(tài)如何過渡到其他狀態(tài)。
Context環(huán)境角色
定義客戶端需要的接口,并且負(fù)責(zé)具體狀態(tài)的切換。
舉例(人物在地點A向地點B移動,在地點B向地點A移動)。類圖如下:
1 state接口
public interface State {
public void stop();
public void move();
}
2 狀態(tài)實例
public class PlaceA implements State {
private Player context;public PlaceA(Player context) {this.context = context;
}@Override
public void move() {System.out.println("處于地點A,開始向B移動");System.out.println("--------");context.setDirection("AB");context.setState(context.onMove);}@Override
public void stop() {// TODO Auto-generated method stubSystem.out.println("正處在地點A,不用停止移動");System.out.println("--------");
}
}
3 context(player)擁有狀態(tài)的對象
public class Player {
State placeA;
State placeB;
State onMove;
private State state;
private String direction;public Player() {direction = "AB";placeA = new PlaceA(this);placeB = new PlaceB(this);onMove = new OnMove(this);this.state = placeA;
}public void move() {System.out.println("指令:開始移動");state.move();
}public void stop() {System.out.println("指令:停止移動");state.stop();
}public State getState() {return state;
}public void setState(State state) {this.state = state;
}public void setDirection(String direction) {this.direction = direction;
}public String getDirection() {return direction;
}
}
19 備忘錄模式
定義: 在不破壞封裝性的前提下,捕獲一個對象的內(nèi)部狀態(tài),并在該對象之外保存這個狀態(tài),以便以后當(dāng)需要時能將該對象恢復(fù)到原先保存的狀態(tài)。該模式又叫快照模式。
備忘錄模式是一種對象行為型模式,其主要優(yōu)點如下。
提供了一種可以恢復(fù)狀態(tài)的機制。當(dāng)用戶需要時能夠比較方便地將數(shù)據(jù)恢復(fù)到某個歷史的狀態(tài)。
實現(xiàn)了內(nèi)部狀態(tài)的封裝。除了創(chuàng)建它的發(fā)起人之外,其他對象都不能夠訪問這些狀態(tài)信息。
簡化了發(fā)起人類。發(fā)起人不需要管理和保存其內(nèi)部狀態(tài)的各個備份,所有狀態(tài)信息都保存在備忘錄中,并由管理者進行管理,這符合單一職責(zé)原則。
其主要缺點是:資源消耗大。如果要保存的內(nèi)部狀態(tài)信息過多或者特別頻繁,將會占用比較大的內(nèi)存資源。
19.1 模式結(jié)構(gòu)圖和代碼示例
發(fā)起人(Originator)角色:記錄當(dāng)前時刻的內(nèi)部狀態(tài)信息,提供創(chuàng)建備忘錄和恢復(fù)備忘錄數(shù)據(jù)的功能,實現(xiàn)其他業(yè)務(wù)功能,它可以訪問備忘錄里的所有信息。
備忘錄(Memento)角色:負(fù)責(zé)存儲發(fā)起人的內(nèi)部狀態(tài),在需要的時候提供這些內(nèi)部狀態(tài)給發(fā)起人。
管理者(Caretaker)角色:對備忘錄進行管理,提供保存與獲取備忘錄的功能,但其不能對備忘錄的內(nèi)容進行訪問與修改。
舉例(發(fā)起者通過備忘錄存儲信息和獲取信息),類圖如下:
1 備忘錄接口
public interface MementoIF {
}
2 備忘錄
public class Memento implements MementoIF{
private String state;public Memento(String state) {this.state = state;
}public String getState(){return state;
}
}
3 發(fā)起者
public class Originator {
private String state;public String getState() {return state;
}public void setState(String state) {this.state = state;
}public Memento saveToMemento() {return new Memento(state);
}public String getStateFromMemento(MementoIF memento) {return ((Memento) memento).getState();
}
}
4 管理者
public class CareTaker {
private List<MementoIF> mementoList = new ArrayList<MementoIF>();public void add(MementoIF memento) {mementoList.add(memento);
}public MementoIF get(int index) {return mementoList.get(index);
}
}
20 訪問者模式
定義:將作用于某種數(shù)據(jù)結(jié)構(gòu)中的各元素的操作分離出來封裝成獨立的類,使其在不改變數(shù)據(jù)結(jié)構(gòu)的前提下可以添加作用于這些元素的新的操作,為數(shù)據(jù)結(jié)構(gòu)中的每個元素提供多種訪問方式。它將對數(shù)據(jù)的操作與數(shù)據(jù)結(jié)構(gòu)進行分離。
訪問者(Visitor)模式是一種對象行為型模式,其主要優(yōu)點如下。
擴展性好。能夠在不修改對象結(jié)構(gòu)中的元素的情況下,為對象結(jié)構(gòu)中的元素添加新的功能。
復(fù)用性好。可以通過訪問者來定義整個對象結(jié)構(gòu)通用的功能,從而提高系統(tǒng)的復(fù)用程度。
靈活性好。訪問者模式將數(shù)據(jù)結(jié)構(gòu)與作用于結(jié)構(gòu)上的操作解耦,使得操作集合可相對自由地演化而不影響系統(tǒng)的數(shù)據(jù)結(jié)構(gòu)。
符合單一職責(zé)原則。訪問者模式把相關(guān)的行為封裝在一起,構(gòu)成一個訪問者,使每一個訪問者的功能都比較單一。
訪問者(Visitor)模式的主要缺點如下。
增加新的元素類很困難。在訪問者模式中,每增加一個新的元素類,都要在每一個具體訪問者類中增加相應(yīng)的具體操作,這違背了“開閉原則”。
破壞封裝。訪問者模式中具體元素對訪問者公布細(xì)節(jié),這破壞了對象的封裝性。
違反了依賴倒置原則。訪問者模式依賴了具體類,而沒有依賴抽象類。
20.1 模式結(jié)構(gòu)和代碼示例
訪問者模式包含以下主要角色。
抽象訪問者(Visitor)角色:定義一個訪問具體元素的接口,為每個具體元素類對應(yīng)一個訪問操作 visit() ,該操作中的參數(shù)類型標(biāo)識了被訪問的具體元素。
具體訪問者(ConcreteVisitor)角色:實現(xiàn)抽象訪問者角色中聲明的各個訪問操作,確定訪問者訪問一個元素時該做什么。
抽象元素(Element)角色:聲明一個包含接受操作 accept() 的接口,被接受的訪問者對象作為 accept() 方法的參數(shù)。
具體元素(ConcreteElement)角色:實現(xiàn)抽象元素角色提供的 accept() 操作,其方法體通常都是 visitor.visit(this) ,另外具體元素中可能還包含本身業(yè)務(wù)邏輯的相關(guān)操作。
對象結(jié)構(gòu)(Object Structure)角色:是一個包含元素角色的容器,提供讓訪問者對象遍歷容器中的所有元素的方法,通常由 List、Set、Map 等聚合類實現(xiàn)。
1 抽象訪問者
public interface Visitor {
abstract public void Visit(Element element);
}
2 具體訪問者
public class CompensationVisitor implements Visitor {
@Override
public void Visit(Element element) {// TODO Auto-generated method stubEmployee employee = ((Employee) element);System.out.println(employee.getName() + "'s Compensation is " + (employee.getDegree() * employee.getVacationDays() * 10));
}
}
3 抽象元素
public interface Element {
abstract public void Accept(Visitor visitor);
}
4 具體元素
public class CompensationVisitor implements Visitor {
@Override
public void Visit(Element element) {// TODO Auto-generated method stubEmployee employee = ((Employee) element);System.out.println(employee.getName() + "'s Compensation is " + (employee.getDegree() * employee.getVacationDays() * 10));
}
}
5 對象結(jié)構(gòu)
public class ObjectStructure {
private HashMap<String, Employee> employees;
public ObjectStructure() {employees = new HashMap();
}public void Attach(Employee employee) {employees.put(employee.getName(), employee);
}public void Detach(Employee employee) {employees.remove(employee);
}public Employee getEmployee(String name) {return employees.get(name);
}public void Accept(Visitor visitor) {for (Employee e : employees.values()) {e.Accept(visitor);}
}
}
21 中介者模式
定義:定義一個中介對象來封裝一系列對象之間的交互,使原有對象之間的耦合松散,且可以獨立地改變它們之間的交互。中介者模式又叫調(diào)停模式,它是迪米特法則的典型應(yīng)用。
中介者模式是一種對象行為型模式,其主要優(yōu)點如下。
降低了對象之間的耦合性,使得對象易于獨立地被復(fù)用。
將對象間的一對多關(guān)聯(lián)轉(zhuǎn)變?yōu)橐粚σ坏年P(guān)聯(lián),提高系統(tǒng)的靈活性,使得系統(tǒng)易于維護和擴展。
其主要缺點是:當(dāng)同事類太多時,中介者的職責(zé)將很大,它會變得復(fù)雜而龐大,以至于系統(tǒng)難以維護。
21.1 模式結(jié)構(gòu)和代碼示例
抽象中介者(Mediator)角色:它是中介者的接口,提供了同事對象注冊與轉(zhuǎn)發(fā)同事對象信息的抽象方法。
具體中介者(ConcreteMediator)角色:實現(xiàn)中介者接口,定義一個 List 來管理同事對象,協(xié)調(diào)各個同事角色之間的交互關(guān)系,因此它依賴于同事角色。
抽象同事類(Colleague)角色:定義同事類的接口,保存中介者對象,提供同事對象交互的抽象方法,實現(xiàn)所有相互影響的同事類的公共功能。
具體同事類(Concrete Colleague)角色:是抽象同事類的實現(xiàn)者,當(dāng)需要與其他同事對象交互時,由中介者對象負(fù)責(zé)后續(xù)的交互。
舉例(通過中介賣方),類圖如下:
1 抽象中介者
public interface Mediator {
void register(Colleague colleague); // 客戶注冊void relay(String from, String to,String ad); // 轉(zhuǎn)發(fā)
}
2 具體中介者
public class ConcreteMediator implements Mediator {
private List<Colleague> colleagues = new ArrayList<Colleague>();@Override
public void register(Colleague colleague) {// TODO Auto-generated method stubif (!colleagues.contains(colleague)) {colleagues.add(colleague);colleague.setMedium(this);}
}@Override
public void relay(String from, String to, String ad) {// TODO Auto-generated method stubfor (Colleague cl : colleagues) {String name = cl.getName();if (name.equals(to)) {cl.receive(from, ad);}}}
}
3 抽象同事類
public abstract class Colleague {
protected Mediator mediator;
protected String name;public Colleague(String name) {this.name = name;
}public void setMedium(Mediator mediator) {this.mediator = mediator;}public String getName() {return name;
}public abstract void Send(String to, String ad);public abstract void receive(String from, String ad);
}
4 具體同事類
public class Buyer extends Colleague {
public Buyer(String name) {super(name);}@Override
public void Send(String to, String ad) {// TODO Auto-generated method stubmediator.relay(name, to, ad);
}@Override
public void receive(String from, String ad) {// TODO Auto-generated method stubSystem.out.println(name + "接收到來自" + from + "的消息:" + ad);
}
}