OOAD
設計模式(Design pattern)是一套被反覆使用、多數人知曉的、經過分類編目的、代碼設計經驗的總結。
使用設計模式是爲了可重用代碼、讓代碼更容易被他人理解、保證代碼可靠性。 毫無疑問,設計模式於己於他人於系統都是多贏的,設計模式使代碼編制真正工程化,設計模式是軟件工程的基石,如同大廈的一塊塊磚石一樣。
項目中合理的運用設計模式可以完美的解決很多問題,每種模式在現在中都有相應的原理來與之對應,每一個模式描述了一個在我們周圍不斷重複發生的問題,以及該問題的核心解決方案,這也是它能被廣泛應用的原因。
一、設計模式的分類
創建型模式,共五種:工廠方法模式、抽象工廠模式、單例模式、建造者模式、原型模式。
結構型模式,共七種:適配器模式、裝飾器模式、代理模式、外觀模式、橋接模式、組合模式、享元模式。
行爲型模式,共十一種:策略模式、模板方法模式、觀察者模式、迭代子模式、責任鏈模式、命令模式、備忘錄模式、狀態模式、訪問者模式、中介者模式、解釋器模式。
二、Java的23中設計模式
1、工廠方法模式(Factory Method)
工廠方法模式分爲三種:普通工廠模式 多個工廠方法模式 靜態工廠方法模式
1.1、普通工廠模式,就是建立一個工廠類,對實現了同一接口的產品類進行實例的創建
例子:
//發送短信和郵件的接口
public interface Sender {
public void Send();
}
//發送郵件的實現類
public class MailSender implements Sender {
public void Send() {
System.out.println("發送郵件!");
}
}
//發送短信的實現類
public class SmsSender implements Sender {
public void Send() {
System.out.println("發送短信!");
}
}
//創建工廠類
public class SendFactory {
//工廠方法
public Sender produce(String type) {
if ("mail".equals(type)) {
return new MailSender();
} else if ("sms".equals(type)) {
return new SmsSender();
} else {
System.out.println("請輸入正確的類型!");
return null;
}
}
}
//測試類
public class FactoryTest {
public static void main(String[] args) {
SendFactory factory = new SendFactory();
Sender sender = factory.produce("sms");
sender.Send();
}
}
1.2、多個工廠方法模式 是對普通工廠方法模式的改進,在普通工廠方法模式中,如果傳遞的字符串出錯,則不能正確創建對象,而多個工廠方法模式是提供多個工廠方法,分別創建對象。
//將上面的代碼做下修改,改動下SendFactory類就行
//這個就不用根據用戶傳的字符串類創建對象了
public class SendFactory {
public Sender produceMail(){
return new MailSender();
}
public Sender produceSms(){
return new SmsSender();
}
}
//測試類
public class FactoryTest {
public static void main(String[] args) {
SendFactory factory = new SendFactory();
Sender sender = factory.produceMail();
sender.Send();
}
}
1.3、靜態工廠方法模式,將上面的多個工廠方法模式裏的方法置爲靜態的,不需要創建實例,直接調用即可。
public class SendFactory {
public static Sender produceMail(){
return new MailSender();
}
public static Sender produceSms(){
return new SmsSender();
}
}
//測試類
public class FactoryTest {
public static void main(String[] args) {
Sender sender = SendFactory.produceMail();
sender.Send();
}
}
2、抽象工廠模式(Abstract Factory)
工廠方法模式有一個問題就是,類的創建依賴工廠類,也就是說,如果想要拓展程序,必須對工廠類進行修改,這違背了閉包原則
,所以,從設計角度考慮,有一定的問題,如何解決?就用到抽象工廠模式,創建多個工廠類,這樣一旦需要增加新的功能,直接增加新的工廠類就可以了,
不需要修改之前的代碼。
例子:
//發送短信和郵件的接口
public interface Sender {
public void Send();
}
//發送郵件的實現類
public class MailSender implements Sender {
public void Send() {
System.out.println("發送郵件!");
}
}
//發送短信的實現類
public class SmsSender implements Sender {
public void Send() {
System.out.println("發送短信!");
}
}
//給工廠類一個接口
public interface Provider {
public Sender produce();
}
//兩個工廠的實現類
public class SendMailFactory implements Provider {
public Sender produce(){
return new MailSender();
}
}
public class SendSmsFactory implements Provider{
public Sender produce() {
return new SmsSender();
}
}
//測試類
public class Test {
public static void main(String[] args) {
Provider provider = new SendMailFactory();
Sender sender = provider.produce();
sender.Send();
}
}
注:這個模式的好處就是,如果你現在想增加一個功能:發送及時信息,則只需做一個實現類實現Sender接口,同時做一個工廠類,實現Provider接口,就OK了,無需去改動現成的代碼。這樣做,拓展性較好
3、單例模式(Singleton)
單例對象(Singleton)是一種常用的設計模式。在Java應用中,單例對象能保證在一個JVM中,該對象只有一個實例存在。這樣的模式有幾個好處:
1、某些類創建比較頻繁,對於一些大型的對象,這是一筆很大的系統開銷。
2、省去了new操作符,降低了系統內存的使用頻率,減輕GC壓力。
3、有些類如交易所的核心交易引擎,控制着交易流程,如果該類可以創建多個的話,系統完全亂了。
例子:
//簡單的單例類 餓漢模式
public class Singleton {
/* 持有私有靜態實例,防止被引用*/
private static Singleton instance = new Singleton();
/* 私有構造方法,防止被實例化 */
private Singleton() {
}
/* 靜態工程方法,返回Singleton實例 */
public static Singleton getInstance() {
return instance;
}
/* 如果該對象被用於序列化,可以保證對象在序列化前後保持一致 */
private Object readResolve() {
return instance;
}
}
這個類是可以實現單例模式的,但是存在不少問題,比如在類中不管用戶是否要使用該類的對象,就先創建好了一個實例放在內存中。
//簡單的單例類 懶漢模式
public class Singleton {
/* 持有私有靜態實例,防止被引用,此處賦值爲null,目的是實現延遲加載 */
private static Singleton instance = null;
/* 私有構造方法,防止被實例化 */
private Singleton() {
}
/* 靜態工程方法,創建實例 */
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
/* 如果該對象被用於序列化,可以保證對象在序列化前後保持一致 */
private Object readResolve() {
return instance;
}
}
這個類可以滿足基本要求,但是,像這樣毫無線程安全保護的類,如果我們把它放入多線程的環境下,肯定就會出現問題了,如何解決?我們首先會想到對getInstance方法加synchronized關鍵字,如下:
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
但是,synchronized作爲修飾符在方法上使用,在性能上會有所下降,因爲每次調用getInstance(),都要對對象上鎖,事實上,只有在第一次創建對象的時候需要加鎖,之後就不需要了,所以,這個地方需要改進。我們改成下面這個:
public static Singleton getInstance() {
if (instance == null) {
synchronized (Sigleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
似乎解決了之前提到的問題,將synchronized關鍵字加在了方法內部,也就是說當調用的時候是不需要加鎖的,只有在instance爲null,並創建對象的時候才需要加鎖,性能有一定的提升。但是,這樣的情況,還是有可能有問題的。
看下面的情況:在Java指令中創建對象和賦值操作是分開進行的,也就是說instance = new Singleton();語句是分兩步執行的。但是JVM並不保證這兩個操作的先後順序,也就是說有可能JVM會爲新的Singleton實例分配空間,然後直接賦值給instance成員,然後再去初始化這個Singleton實例。這樣就可能出錯了,我們以A、B兩個線程爲例: 是會有一定機率出現這種情況的
1)A、B線程同時進入了第一個if判斷
2)A首先進入synchronized塊,由於instance爲null,所以它執行instance = new Singleton();
3)由於JVM內部的優化機制,JVM先畫出了一些分配給Singleton實例的空白內存,並賦值給instance成員(注意此時JVM沒有開始初始化這個實例),然後A離開了synchronized塊。
4)B進入synchronized塊,由於instance此時不是null,因此它馬上離開了synchronized塊並將結果返回給調用該方法的程序。
5)此時B線程打算使用Singleton實例,卻發現它沒有被初始化,於是錯誤發生了。
所以程序還是有可能發生錯誤,其實程序在運行過程是很複雜的,從這點我們就可以看出,尤其是在寫多線程環境下的程序更有難度。
實際情況是,單例模式使用內部類來維護單例的實現,JVM內部的機制能夠保證當一個類被加載的時候,這個類的加載過程是線程互斥的(就是加載完畢後別的線程才能使用)。這樣當我們第一次調用getInstance的時候,JVM能夠幫我們保證instance只被創建一次,並且會保證把賦值給instance的內存初始化完畢,這樣我們就不用擔心上面的問題。同時該方法也只會在第一次調用的時候使用互斥機制,這樣就解決了低性能問題。例如:
public class Singleton {
/* 私有構造方法,防止被實例化 */
private Singleton() {
}
/* 此處使用一個內部類來維護單例 */
private static class SingletonFactory {
private static Singleton instance = new Singleton();
}
/* 獲取實例 */
public static Singleton getInstance() {
return SingletonFactory.instance;
}
/* 如果該對象被用於序列化,可以保證對象在序列化前後保持一致 */
private Object readResolve() {
return getInstance();
}
}
但是如果在構造函數中拋出異常,實例將永遠得不到創建,也會出錯。所以說,十分完美的東西是沒有的,我們只能根據實際情況,選擇最適合自己應用場景的實現方法。
同時,我們還可以使用反射去創建這個類的對象,即使它的構造器是私有的,我們也是可以調用到的。那麼這個時候我們就需要再次修改代碼去訪問別人反射調用構造器。
例子:
//在構造器中控制一下,構造器只允許調用一次,之後在調用就拋出異常
public class Singleton {
private static boolean flag;
/* 私有構造方法,防止被實例化 */
private Singleton() {
if(!flag){
flag = false;
}else{
throw new RuntimeException("不能多次創建單例對象");
}
}
/* 此處使用一個內部類來維護單例 */
private static class SingletonFactory {
private static Singleton instance = new Singleton();
}
/* 獲取實例 */
public static Singleton getInstance() {
return SingletonFactory.instance;
}
/* 如果該對象被用於序列化,可以保證對象在序列化前後保持一致 */
private Object readResolve() {
return getInstance();
}
}
反射的問題處理完了之後,這裏還有一個問題,就是如果把單例對象進行序列化然後再反序列化,那麼內存中就會出現倆個一樣的單例對象,只是內存地址不同。這種情況我們可以使用readResolve方法來防止。
private Object readResolve(){.....}
ObjectInputStream 會檢查對象的class是否定義了readResolve方法。如果定義了,將由readResolve方法指定返回的對象。返回對象的類型一定要是兼容的,否則會拋出ClassCastException 。
例子:
public abstract class Singleton8 implements Serializable{
private static final long serialVersionUID = 7863921642928237696L;
/* 此處使用一個內部類來維護單例 */
private static class SingletonFactory {
@SuppressWarnings("serial")
private static Singleton8 instance = new Singleton8(){};
}
//測試方式,把單例對象序列化後再反序列化從而獲得一個新的對象 就相當於複製了一個單例對象
public Singleton8 copy() throws Exception{
ByteArrayOutputStream os = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(os);
oos.writeObject(Singleton8.getInstance());
InputStream is = new ByteArrayInputStream(os.toByteArray());
ObjectInputStream ois = new ObjectInputStream(is);
Singleton8 obj = (Singleton8) ois.readObject();
return obj;
}
/* 獲取實例 */
public static Singleton8 getInstance() {
return SingletonFactory.instance;
}
/* 如果該對象被用於序列化,可以保證對象在序列化前後保持一致 */
/* 把這個方法註釋前和註釋後來運行測試代碼觀察結果 */
private Object readResolve() {
return getInstance();
}
}
最後再思考一個問題 : 是否可以使用枚舉來實現單例模式?
4、建造者模式(Builder)
工廠類模式提供的是創建單個類的模式,而建造者模式則是將各種產品集中起來進行管理,用來創建複合對象,所謂複合對象就是指某個類具有不同的屬性。
建造者模式主要用於“分步驟構建一個複雜的對象”,在這其中“分步驟”是一個穩定的算法,而複雜對象的各個部分則經常變化。
因此, 建造者模式主要用來解決“對象部分”的需求變化。 這樣可以對對象構造的過程進行更加精細的控制。
例子:
//CPU接口
public interface CPU {
}
//Inter的cup
class IntelCPU implements CPU{
}
//AMD的cpu
class AMDCPU implements CPU{
}
//內存接口
public interface Memory {
}
//金士頓內存
class KingstonMemory implements Memory{
}
//三星內存
class SamsungMemory implements Memory{
}
//主板內存
public interface Mainboard {
}
//華碩主板
class AsusMainboard implements Mainboard{
}
//技嘉主板
class GaMainboard implements Mainboard{
}
//計算機
public class Computer {
private CPU cpu;
private Memory memory;
private Mainboard mainboard;
get/set
}
//計算機的builder的接口
public interface ComputerBuilder {
public void buildCPU();
public void buildMemory();
public void buildMainboard();
public Computer getComputer();
}
//聯想電腦的builder
public class LenoveComputerBuilder implements ComputerBuilder {
private Computer lenoveComputer;
public LenoveComputerBuilder(){
lenoveComputer = new Computer();
}
public void buildCPU() {
lenoveComputer.setCpu(new IntelCPU());
}
public void buildMemory() {
lenoveComputer.setMemory(new KingstonMemory());
}
public void buildMainboard() {
lenoveComputer.setMainboard(new AsusMainboard());
}
public Computer getComputer() {
return lenoveComputer;
}
}
//惠普電腦的builder
public class HPComputerBuilder implements ComputerBuilder {
private Computer HPComputer;
public HPComputerBuilder(){
HPComputer = new Computer();
}
public void buildCPU() {
HPComputer.setCpu(new AMDCPU());
}
public void buildMemory() {
HPComputer.setMemory(new SamsungMemory());
}
public void buildMainboard() {
HPComputer.setMainboard(new GaMainboard());
}
public Computer getComputer() {
return HPComputer;
}
}
//Director類(導演)
//指導如何具體的創造電腦
public class Director {
private ComputerBuilder builder;
public Director(ComputerBuilder builder) {
this.builder = builder;
}
//用戶自定義的自造順序 具體指導各種builder如何創建電腦
public void construct() {
builder.buildCPU();
builder.buildMemory();
builder.buildMainboard();
}
}
//測試類
public class Test {
public static void main(String[] args) {
Computer lenoveComputer = null;
ComputerBuilder lenoveComputerBuilder = new LenoveComputerBuilder();
Director director = new Director(lenoveComputerBuilder);
director.construct();
lenoveComputer = lenoveComputerBuilder.getComputer();
System.out.println(lenoveComputer);
}
}
從這點看出,建造者模式將很多功能集成到一個類裏,這個類可以創造出比較複雜的東西。所以與工程模式的區別就是:工廠模式關注的是創建單個產品,而建造者模式則關注創建適合對象的多個部分。因此,是選擇工廠模式還是建造者模式,依實際情況而定。
例如一個Person類是由頭、身體、腳三個對象組成,那麼我們在建造者模式中就要先分別創造出這三個部分然後再把他們組裝成一個Person對象。
5、原型模式(Prototype)
原型模式雖然是創建型的模式,但是與工程模式沒有關係,從名字即可看出,該模式的思想就是將一個對象作爲原型,對其進行復制、克隆,產生一個和原對象類似的新對象。
在Java中,複製對象是通過clone()實現的,先創建一個原型類:
public class Prototype implements Cloneable {
public Object clone() throws CloneNotSupportedException {
Prototype proto = (Prototype) super.clone();
return proto;
}
}
很簡單,一個原型類,只需要實現Cloneable接口,覆寫clone方法,此處clone方法可以改成任意的名稱,因爲Cloneable接口是個空接口,你可以任意定義實現類的方法名,如cloneA或者cloneB,因爲此處的重點是super.clone()這句話,super.clone()調用的是Object的clone()方法,而在Object類中,clone()是native的,說明這個方法實現並不是使用java語言。
在這裏先認識倆個概念 : 淺複製 深複製
淺複製:將一個對象複製後,基本數據類型的變量都會重新創建,而引用類型,指向的還是原對象所指向的。
深複製:將一個對象複製後,不論是基本數據類型還有引用類型,都是重新創建的。簡單來說,就是深複製進行了完全徹底的複製,而淺複製不徹底。
public class Prototype implements Cloneable, Serializable {
private static final long serialVersionUID = 1L;
private String string;
//這個是在下面聲明的一個類
private SerializableObject obj;
/* 淺複製 */
public Object clone() throws CloneNotSupportedException {
Prototype proto = (Prototype) super.clone();
return proto;
}
/* 深複製 */
public Object deepClone() throws IOException, ClassNotFoundException {
/* 寫入當前對象的二進制流 */
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);
/* 讀出二進制流產生的新對象 */
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
return ois.readObject();
}
public String getString() {
return string;
}
public void setString(String string) {
this.string = string;
}
public SerializableObject getObj() {
return obj;
}
public void setObj(SerializableObject obj) {
this.obj = obj;
}
}
class SerializableObject implements Serializable {
private static final long serialVersionUID = 1L;
}
上面是五種是創建型模式,接下來看一下7種結構型模式 : 適配器模式、裝飾模式、代理模式、外觀模式、橋接模式、組合模式、享元模式
6、適配器模式(Adapter)
適配器模式將某個類的接口轉換成客戶端期望的另一個接口表示,目的是消除由於接口不匹配所造成的類的兼容性問題。主要分爲三類:類的適配器模式、對象的適配器模式、接口的適配器模式。
類的適配器模式:
核心思想就是:有一個Source類,擁有一個方法,待適配,目標接口是Targetable,通過Adapter類,將Source的功能擴展到Targetable裏
例子:
public class Source {
public void method1() {
System.out.println("this is original method!");
}
}
public interface Targetable {
/* 與原類中的方法相同 */
public void method1();
/* 新的方法 */
public void method2();
}
//類Source和接口Targetable因爲不兼容,導致不能在一起工作
//適配器Adapter則可以在不改變源代碼的基礎上解決這個問題
//這樣Targetable接口的實現類Adapter的對象即使Targetable類型,也能訪問到Source中的方法
public class Adapter extends Source implements Targetable {
public void method2() {
System.out.println("this is the targetable method!");
}
}
//測試類 這樣Targetable接口的實現類就具有了Source類的功能。
public class AdapterTest {
public static void main(String[] args) {
Targetable target = new Adapter();
target.method1();
target.method2();
}
}
對象的適配器模式
基本思路和類的適配器模式相同,只是將Adapter類作修改,這次不繼承Source類,而是持有Source類的實例,以達到解決兼容性的問題
例子:
//只需要修改Adapter類的源碼即可:
public class Wrapper implements Targetable {
private Source source;
public Wrapper(Source source){
this.source = source;
}
public void method2() {
System.out.println("this is the targetable method!");
}
public void method1() {
source.method1();
}
}
//測試類 輸出與第一種情況一樣,只是使用的適配方法不同而已。
public class AdapterTest {
public static void main(String[] args) {
Source source = new Source();
Targetable target = new Wrapper(source);
target.method1();
target.method2();
}
}
接口的適配器模式
接口的適配器是這樣的:有時我們寫的一個接口中有多個抽象方法,當我們寫該接口的實現類時,必須實現該接口的所有方法,這明顯有時比較浪費,因爲並不是所有的方法都是我們需要的,有時只需要某一些,此處爲了解決這個問題,我們引入了接口的適配器模式,藉助於一個抽象類,該抽象類實現了該接口,實現了所有的方法,而我們不和原始的接口打交道,只和該抽象類取得聯繫,所以我們寫一個類,繼承該抽象類,重寫我們需要的方法就行。
我們這GUI這個章節應該是見過不少的監聽器接口的適配器類:XxxxAdapter
例子:
public interface Sourceable {
public void method1();
public void method2();
}
//抽象類
public abstract class Wrapper implements Sourceable{
public void method1(){}
public void method2(){}
}
之後在我們寫的子類中需要什麼方法去重寫什麼方法就可以了,就不需要把接口中的所有方法都實現了
三種情況適配器模式的總結:
類的適配器模式:當希望將一個類轉換成滿足另一個新接口的類時,可以使用類的適配器模式,創建一個新類,繼承原有的類,實現新的接口即可。
對象的適配器模式:當希望將一個對象轉換成滿足另一個新接口的對象時,可以創建一個Wrapper類,持有原類的一個實例,在Wrapper類的方法中,調用實例的方法就行。
接口的適配器模式:當不希望實現一個接口中所有的方法時,可以創建一個抽象類Wrapper,實現所有方法,我們寫別的類的時候,繼承抽象類即可。
7、裝飾模式(Decorator)
顧名思義,裝飾模式就是給一個對象增加一些新的功能,而且是【動態】的,要求裝飾對象和被裝飾對象實現同一個接口,裝飾對象持有被裝飾對象的實例
這裏的動態指的是用戶可以根據自己的需求把之前定好的功能任意組合。
JDK中的IO流部分就是典型的使用了裝飾模式,回憶一下BufferedReader對象的是如何創建的
例子:
//功能接口
public interface Action {
public void go();
}
//被裝飾的類 就是需要我們裝飾的目標
public class Person implements Action{
public void go() {
System.out.println("我在走路");
}
}
//抽象的裝飾類
public abstract class Decorator implements Action{
private Action action;
public Decorator(Action action) {
this.action = action;
}
public void go() {
this.action.go();
}
}
//具體的裝飾類 可以添加一個聽音樂的功能
public class ListenDecorator extends Decorator{
public ListenDecorator(Action action) {
super(action);
}
public void go() {
listen();//可以在go方法【前】添加一個聽音樂的功能
super.go();
}
public void listen(){
System.out.println("我在聽音樂");
}
}
//具體的裝飾類 可以添加一個休息的功能
public class RelaxDecorator extends Decorator{
public RelaxDecorator(Action action) {
super(action);
}
public void go() {
super.go();
relax();//可以在go方法【後】添加一個休息的功能
}
public void relax(){
System.out.println("我在休息");
}
}
//測試類
public class Test {
/*用戶可以根據需求 任意給go方法添加聽音樂或者休息的功能*/
//Action a = new Person();
//Action a = new ListenDecorator(new Person());
//Action a = new RelaxDecorator(new Person());
//Action a = new RelaxDecorator(new ListenDecorator(new Person()));
Action a = new ListenDecorator(new RelaxDecorator(new Person()));
a.go();
}
裝飾器模式的應用場景:
1、需要擴展一個類的功能。
2、動態的爲一個對象增加功能,而且還能動態撤銷。
缺點:產生過多相似的對象,不易排錯!
8、代理模式(Proxy)
其實每個模式名稱就表明了該模式的作用,代理模式就是多一個代理類出來,替原對象進行一些操作,比如我們在租房子的時候回去找中介,爲什麼呢?
因爲你對該地區房屋的信息掌握的不夠全面,希望找一個更熟悉的人去幫你做,此處的代理就是這個意思。再如我們有的時候打官司,我們需要請律師,
因爲律師在法律方面有專長,可以替我們進行操作,表達我們的想法。
例子:
//公共接口
public interface Sourceable {
public void method();
}
//目標類/被代理類
public class Source implements Sourceable {
public void method() {
System.out.println("the original method!");
}
}
//代理類
public class Proxy implements Sourceable {
private Source source;
public Proxy(Source source){
this.source = source;
}
public void method() {
before();
source.method();
atfer();
}
private void atfer() {
System.out.println("after proxy!");
}
private void before() {
System.out.println("before proxy!");
}
}
//測試類
public class ProxyTest {
public static void main(String[] args) {
Source target = new
Source();
Sourceable proxy = new Proxy(target);
proxy.method();
}
}
代理模式的應用場景:
如果已有的方法在使用的時候需要對原有的方法進行改進,此時有兩種辦法:
1、修改原有的方法來適應。這樣違反了“對擴展開放,對修改關閉”的原則。
2、就是採用一個代理類調用原有的方法,且對產生的結果進行控制。這種方法就是代理模式。
使用代理模式,可以將功能劃分的更加清晰,有助於後期維護!
注意: 裝飾模式和代理模式在很多情況下,大部分代碼都是類似的,但是這倆種設計的意圖是不一樣的,裝飾模式是增強被包裝對象的功能,代理模式是控制被代理對象的行爲
例如一塊代碼,如果被描述爲使用了裝飾模式,那麼我們就知道設計的意圖是增加被包裝對象的功能,如果被描述爲使用了代理模式,那麼我們就知道設計的意圖是控制被代理對象的行爲,雖然這倆種情況下他們的代碼結構基本相同.
裝飾器模式:能動態的新增或組合對象的行爲。
代理模式 :爲目標對象提供一種代理以便控制對這個對象的訪問.
裝飾模式是“新增行爲”,而代理模式是“控制訪問”。
1.裝飾模式:對被裝飾的對象增加額外的行爲
如:杯子生產線,杯子必須可以裝水,在生產線上可以給杯子塗顏色,加杯蓋,但要保證杯子可以裝水。
2.代理模式:對被代理的對象提供訪問控制。
如:客戶網上商城訂購商品,網上商城是廠家的代理,網上商城可以幫客戶完成訂購商品的任務,但是商城可以對商品進行控制,不交錢不給商品,人不在不給商品,也可以贈送你額外的禮品,代金券。
對代理模式的一些重要擴展
用戶tom---買--->商品
由於各種原因導致不是很方便購買,所以就找代購
用戶tom---找--->代購者zs---買--->商品
那麼在代理模式中,用戶tom就是目標對象,代購者zs就是代理對象
創建目標對象的類叫目標類或者被代理類
創建代理對象的類加代理類
建時期,代理類可分爲兩種:
靜態代理類:
由程序員創建或由特定工具自動生成源代碼,再對其編譯。在程序運行前,代理類的.class文件就已經存在了。
動態代理類:在程序運行時,運用反射機制動態創建而成。
與靜態代理類對照的是動態代理類,動態代理類的字節碼在程序運行時由Java反射機制動態生成,無需程序員手工編寫它的源代碼。動態代理類不僅簡化了編程工作,而且提高了軟件系統的可擴展性,因爲Java 反射機制可以生成任意類型的動態代理類。java.lang.reflect 包下面的Proxy類和InvocationHandler 接口提供了生成動態代理類的能力。
CGLib代理(第三方類庫)
JDK實現動態代理需要實現類通過接口定義業務方法,對於沒有接口的類,如何實現動態代理呢,這就需要CGLib了。CGLib 採用了非常底層的字節碼技術,其原理是通過字節碼技術爲目標對象創建一個子類對象,並在子類對象中攔截所有父類方法的調用,然後在方法調用前後調用後都可以加入自己想要執行的代碼。
需要這種方法只是需要倆個第三方jar包: cglib-3.2.1.jar和asm-5.0.4.jar
同時很多框架已經把這些jar包整合到一起了,比如spring框架的spring-core-3.2.4.RELEASE.jar,這一個jar包就包括上述倆個jar包的大多數功能
靜態代理: staticProxy
例子:
//公共接口
public interface HelloService {
void sayHello();
}
//委託類
public class HelloServiceImpl implements HelloService{
public void sayHello() {
System.out.println("hello world");
}
}
//代理類
public class HelloServiceProxy implements HelloService{
private HelloService target;
public HelloServiceProxy(HelloService target) {
this.target = target;
}
public void sayHello() {
System.out.println("log:sayHello馬上要執行了...");
target.sayHello();
}
}
//測試類
public class Test {
public static void main(String[] args) {
//目標對象
HelloService target = new HelloServiceImpl();
//代理對象
HelloService proxy = new HelloServiceProxy(target);
proxy.sayHello();
}
}
JDK的動態代理: dynamicProxy
例子:
//Student類
public class Student {
private long id;
private String name;
private int age;
get/set
}
//日誌類
public class StudentLogger {
public void log(String msg){
System.out.println("log: "+msg);
}
}
//Service接口 處理學生的相關業務
public interface IStudentService {
void save(Student s);
void delete(long id);
Student find(long id);
}
//接口的一個簡單實現
public class StudentServiceImpl implements IStudentService {
public void delete(long id) {
System.out.println("student was deleted...");
}
public Student find(long id) {
System.out.println("student was found...");
return null;
}
public void save(Student s) {
System.out.println("student was saved...");
}
}
//InvocationHandler接口的實現類
//JDK動態代理中必須用到的接口實現
public class MyHandler implements InvocationHandler{
private Object target;
private StudentLogger logger = new StudentLogger();
public MyHandler(Object target, StudentLogger logger) {
this.target = target;
this.logger = logger;
}
public MyHandler(Object target) {
this.target = target;
}
//參數1 proxy 將來給目標對象所動態產生的代理對象
//參數2 method 將來你所調用的目標對象中的方法的鏡像
//參數3 args 將來你所調用方法的時候所傳的參數
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String msg = method.getName()+"方法被調用了...";
logger.log(msg);
Object o = method.invoke(target, args);
return o;
}
}
//測試類
public class DProxyTest {
public static void main(String[] args) {
IStudentService target = new StudentServiceImpl();
ClassLoader loader = target.getClass().getClassLoader();
Class<?>[] interfaces = target.getClass().getInterfaces();
InvocationHandler h = new MyHandler(target);
//參數1 loader 目標對象的類加載器
//參數2 interfaces 目標對象所實現的接口
//參數3 h InvocationHandler接口的實現類對象
IStudentService proxy = (IStudentService)Proxy.newProxyInstance(loader, interfaces, h);
proxy.delete(1);
proxy.save(null);
proxy.find(1);
System.out.println(proxy.toString());
System.out.println(proxy.getClass());
System.out.println(target.getClass());
}
}
第三方jar包提供的動態代理(cglib) CglibProxy
例子:
//目標的對象 沒有實現接口
public class BookService {
public void addBook() {
System.out.println("添加書籍成功");
}
}
//產生代理對象的工廠類
public class MyCglibProxyFactory implements MethodInterceptor {
public Object getInstance(Class<?> c) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(c);
enhancer.setCallback(this);
return enhancer.create();
}
public Object intercept(Object obj, Method method, Object[] args,
MethodProxy proxy) throws Throwable {
System.out.println("開始執行方法");
//這句代碼最終會執行到我們目標對象中的方法
proxy.invokeSuper(obj, args);
System.out.println("方法執行結束");
return null;
}
}
//測試類
public class TestCglibProxy {
public static void main(String[] args) {
MyCglibProxyFactory cglib=new MyCglibProxyFactory();
BookService bookCglib=
(BookService)cglib.getInstance(new BookService().getClass());
bookCglib.addBook();
System.out.println(bookCglib.getClass());
}
}
9、外觀模式(Facade)
外觀模式也可以叫做門面模式
爲子系統或者模塊中的一組接口提供一個一致的訪問方式,此模式定義了一個高層接口,這個接口使得各個子系統/模塊中的功能更加容易使用。
實際應用中,我們在對付一些老舊的代碼或者即便不是老舊code,但涉及多個子系統時,除了重寫全部代碼,我們還可能採用這樣一種策略:重新進行類的設計,將原來分散在源碼中的類/結構及方法重新組合,形成新的、統一的接口,供上層應用使用,同時也隱藏了子系統或者子模塊中功能實現的複雜性
例子:
//模塊A中的類
public class ServiceA {
public void start(){
System.out.println("模塊A中的start方法");
}
}
//模塊B中的類
public class ServiceB {
public void run(){
System.out.println("模塊B中的run方法");
}
}
//模塊C中的類
public class ServiceC {
public void end(){
System.out.println("模塊C中的end方法");
}
}
//外觀類/門面類
public class Facade {
private ServiceA a;
private ServiceB b;
private ServiceC c;
public Facade() {
a = new ServiceA();
b = new ServiceB();
c = new ServiceC();
}
public void start(){
a.start();
}
public void run(){
b.run();
}
public void end(){
c.end();
}
public void service(){
a.start();
b.run();
c.end();
}
}
//測試類
public class Test {
public static void main(String[] args) {
Facade f = new Facade();
f.start();
f.run();
f.end();
f.service();
}
}
Facade是我們的外觀類/門面類,用戶可以通過這個類使用到系統中不同模塊中的不同方法,同時也對用戶隱藏了系統中對這些功能的實現細節。給用戶提供了一個統一的訪問方式。
10、橋接模式(Bridge)
橋接模式(也叫橋樑模式)就是將抽象部分和實現部分分離,使它們都可以獨立的變化。橋接的用意是:將抽象化與實現化解耦,使得二者可以獨立變化,像我們常用的JDBC橋DriverManager一樣,JDBC進行連接數據庫的時候,在各個數據庫之間進行切換,基本不需要動太多的代碼,甚至絲毫不用動,原因就是JDBC提供統一接口,每個數據庫提供各自的實現,用一個叫做數據庫驅動的程序來橋接就行了。
例子:
//公共的驅動接口
public interface Driver {
public void getConnection();
}
//第一個實現類 mysql驅動類
public class MysqlDriver implements Driver{
public void getConnection() {
System.out.println("mysql 數據庫連接");
}
}
//第二個實現類 oracle驅動類
public class OracleDriver implements Driver {
public void getConnection() {
System.out.println("oracle數據庫連接");
}
}
//抽象的管理器 Bridge
public abstract class Manager {
private Driver driver;
public void getConnection(){
driver.getConnection();
}
public void setDriver(Driver driver) {
this.driver = driver;
}
}
//具體的驅動管理器 Bridge
public class DriverManager extends Manager {
public DriverManager(Driver driver){
setDriver(driver);
}
public void getConnection() {
super.getConnection();
}
}
//測試類 注意我們的抽象和具體實現是分開的,無論他們如何變化都不會影響到我們bridge中的功能執行
//JDBC中,我們使用的就是一系列javaAPI提供的接口,而且數據公司商則給我們提供接口的實現
public class Test {
public static void main(String[] args) {
DriverManager manager = new DriverManager(new MysqlDriver());
manager.getConnection();
manager = new DriverManager(new OracleDriver());
manager.getConnection();
}
}
11、組合模式(Composite)
組合模式有時又叫部分-整體模式,在處理類似樹形結構的問題時比較方便
例子:
//節點類
public class TreeNode {
private String name;
private TreeNode parent;
private Vector<TreeNode> children = new Vector<TreeNode>();
public TreeNode(String name){
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public TreeNode getParent() {
return parent;
}
public void setParent(TreeNode parent) {
this.parent = parent;
}
//添加孩子節點
public void add(TreeNode node){
children.add(node);
}
//刪除孩子節點
public void remove(TreeNode node){
children.remove(node);
}
//取得孩子節點
public Enumeration<TreeNode> getChildren(){
return children.elements();
}
}
//表示一個樹狀結構
public class Tree {
TreeNode root = null;
public Tree(String name) {
root = new TreeNode(name);
}
}
//測試類
public class Test{
public static void main(String[] args) {
Tree tree = new Tree("A");
TreeNode nodeB = new TreeNode("B");
TreeNode nodeC = new TreeNode("C");
nodeB.add(nodeC);
tree.root.add(nodeB);
System.out.println("build the tree finished!");
}
}
12、享元模式(Flyweight)
享元模式的主要目的是實現對象的共享,即共享池,當系統中對象多的時候可以減少內存的開銷,通常與工廠模式一起使用。
例子:
//數據庫連接池
public class ConnectionPool {
private Vector<Connection> pool;
/*公有屬性*/
private String url = "jdbc:mysql://localhost:3306/test";
private String username = "root";
private String password = "root";
private String driverClassName = "com.mysql.jdbc.Driver";
private int poolSize = 100;
//這裏對instance可以使用一個單例模式
private static ConnectionPool instance = null;
Connection conn = null;
/*構造方法,做一些初始化工作*/
private ConnectionPool() {
pool = new Vector<Connection>(poolSize);
for (int i = 0; i < poolSize; i++) {
try {
Class.forName(driverClassName);
conn = DriverManager.getConnection(url, username, password);
pool.add(conn);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
/* 把連接對象返回到連接池 */
public synchronized void release() {
pool.add(conn);
}
/* 返回連接池中的一個數據庫連接 */
public synchronized Connection getConnection() {
if (pool.size() > 0) {
Connection conn = pool.get(0);
pool.remove(conn);
return conn;
} else {
return null;
}
}
}
接下來我們再看剩餘的11種行爲型模式:策略模式、模板方法模式、觀察者模式、迭代子模式、責任鏈模式、命令模式、備忘錄模式、狀態模式、訪問者模式、中介者模式、解釋器模式
13、策略模式(Strategy)
策略模式定義了一系列算法,並將每個算法封裝起來,使他們可以相互替換,且算法的變化不會影響到使用算法的客戶。需要設計一個接口,爲一系列實現類提供統一的方法,多個實現類實現該接口,也可以設計一個抽象類(可有可無,屬於輔助類),提供輔助函數
例子:
//統一的接口
public interface ICalculator {
public int calculate(String exp);
}
//抽象類,作爲輔助類,可以提供一些你認爲需要的方法
public abstract class AbstractCalculator {
public int[] split(String exp,String opt){
String array[] = exp.split(opt);
int arrayInt[] = new int[2];
arrayInt[0] = Integer.parseInt(array[0]);
arrayInt[1] = Integer.parseInt(array[1]);
return arrayInt;
}
}
//接口的三個實現類:
public class Plus extends AbstractCalculator implements ICalculator {
public int calculate(String exp) {
int arrayInt[] = split(exp,"[+]");
return arrayInt[0]+arrayInt[1];
}
}
public class Minus extends AbstractCalculator implements ICalculator {
public int calculate(String exp) {
int arrayInt[] = split(exp,"-");
return arrayInt[0]-arrayInt[1];
}
}
public class Multiply extends AbstractCalculator implements ICalculator {
public int calculate(String exp) {
int arrayInt[] = split(exp,"[*]");
return arrayInt[0]*arrayInt[1];
}
}
//測試類
public class Test {
public static void main(String[] args) {
String exp = "2+8";
ICalculator cal = new Plus();
int result = cal.calculate(exp);
System.out.println(result);
}
}
策略模式的決定權在用戶,系統本身提供不同算法的實現,新增或者刪除算法,對各種算法做封裝。因此,策略模式多用在算法決策系統中,外部用戶只需要決定用哪個算法即可。
我們之前在學校TreeSet排序的時候,有一種叫做資客戶化排序的方式,就是給TreeSet傳一個比較器對象,這個其實就是使用了策略模式
14、模板方法模式(Template Method)
模板方法模式,就是指:一個抽象類中,有一個主方法,再定義1...n個方法,可以是抽象的,也可以是實際的方法,定義一個類,繼承該抽象類,重寫抽象方法,通過調用抽象類的方法,實現對子類的調用
其實就是我們之前所說的:
子類重新/實現父類中的方法,那麼調用該方法的時候則是調用到了子類中重寫之後的方法
例子:
//父類
public abstract class AbstractCalculator {
/*實現對本類其它方法的調用*/
public final int calculate(String exp,String opt){
int array[] = split(exp,opt);
return calculate(array[0],array[1]);
}
/*被子類重寫的方法*/
abstract public int calculate(int num1,int num2);
public int[] split(String exp,String opt){
String array[] = exp.split(opt);
int arrayInt[] = new int[2];
arrayInt[0] = Integer.parseInt(array[0]);
arrayInt[1] = Integer.parseInt(array[1]);
return arrayInt;
}
}
//子類
public class Plus extends AbstractCalculator {
public int calculate(int num1,int num2) {
return num1 + num2;
}
}
//測試類
public class Test {
public static void main(String[] args) {
String exp = "8+8";
AbstractCalculator cal = new Plus();
int result = cal.calculate(exp, "\\+");
System.out.println(result);
}
}
15、觀察者模式(Observer)
觀察者模式很好理解,類似於郵件訂閱和RSS訂閱,當我們瀏覽一些博客時,經常會看到RSS圖標,就這的意思是,當你訂閱了該文章,如果後續有更新,會及時通知你。其實,簡單來講就一句話:當一個對象變化時,其它依賴該對象的對象都會收到通知,並且隨着變化!對象之間是一種一對多的關係。
我們在GUI那一章學習的事件監聽機制就是可以這種設置模式來構建的代碼
例子:
//觀察者接口
public interface Observer {
public void update();
}
//觀察者1
public class Observer1 implements Observer {
public void update() {
System.out.println("observer1 has received!");
}
}
//觀察者2
public class Observer2 implements Observer {
public void update() {
System.out.println("observer2 has received!");
}
}
//被觀察者接口
public interface Subject {
/*增加觀察者*/
public void add(Observer observer);
/*刪除觀察者*/
public void del(Observer observer);
/*通知所有的觀察者*/
public void notifyObservers();
/*自身的操作*/
public void operation();
}
//被觀察者的一個抽象實現 提供基本的實現
public abstract class AbstractSubject implements Subject {
private Vector<Observer> vector = new Vector<Observer>();
public void add(Observer observer) {
vector.add(observer);
}
public void del(Observer observer) {
vector.remove(observer);
}
public void notifyObservers() {
Iterator<Observer> it = vector.iterator();
while(it.hasNext()){
Observer next = it.next();
next.update();
}
}
}
//我們自己的一個被觀察者實現 裏面可以有我們自己的各種屬性和方法
public class MySubject extends AbstractSubject {
public void operation() {
System.out.println("update self!");
notifyObservers();
}
}
//測試類
public class Test {
public static void main(String[] args) {
Subject sub = new MySubject();
sub.add(new Observer1());
sub.add(new Observer2());
sub.operation();
}
}
16、迭代子模式(Iterator)
顧名思義,迭代器模式就是順序訪問聚集中的對象,一般來說,集合中非常常見,如果對集合類比較熟悉的話,理解本模式會十分輕鬆。
例子:
public interface Collection {
public Iterator iterator();
/*取得集合元素*/
public Object get(int i);
/*取得集合大小*/
public int size();
}
public interface Iterator {
//前移 上一個元素
public Object previous();
//後移 下一個元素
public Object next();
public boolean hasNext();
//取得第一個元素
public Object first();
}
public class MyCollection implements Collection {
//假設這個集合內部是由數組實現
public String string[] = {"A","B","C","D","E"};
public Iterator iterator() {
return new MyIterator(this);
}
public Object get(int i) {
return string[i];
}
public int size() {
return string.length;
}
}
//這個地方其實一般會設計爲內部類
public class MyIterator implements Iterator {
private Collection collection;
private int pos = -1;
public MyIterator(Collection collection){
this.collection = collection;
}
public Object previous() {
if(pos > 0){
pos--;
}
return collection.get(pos);
}
public Object next() {
if(pos<collection.size()-1){
pos++;
}
return collection.get(pos);
}
public boolean hasNext() {
if(pos<collection.size()-1){
return true;
}else{
return false;
}
}
public Object first() {
pos = 0;
return collection.get(pos);
}
}
//測試類
public class Test {
public static void main(String[] args) {
Collection collection = new MyCollection();
Iterator it = collection.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
}
}
17、責任鏈模式(Chain of Responsibility)
責任鏈模式,有多個對象,每個對象持有對下一個對象的引用,這樣就會形成一條鏈,請求在這條鏈上傳遞,直到某一對象決定處理該請求。但是發出者並不清楚到底最終那個對象會處理該請求,所以,責任鏈模式可以實現,在隱瞞客戶端的情況下,對系統進行動態的調整。
(web應該中學習到的Filter其實就是一個責任鏈設計模式)
例子:
public interface Handler {
public void operator();
}
public class MyHandler implements Handler {
private String name;
private Handler handler;
public MyHandler(String name) {
this.name = name;
}
public Handler getHandler() {
return handler;
}
public void setHandler(Handler handler) {
this.handler = handler;
}
public void operator() {
System.out.println("name = "+name);
if(getHandler()!=null){
getHandler().operator();
}
}
}
//測試類
public class Test {
public static void main(String[] args) {
MyHandler h1 = new MyHandler("h1");
MyHandler h2 = new MyHandler("h2");
MyHandler h3 = new MyHandler("h3");
h1.setHandler(h2);
h2.setHandler(h3);
h1.operator();
}
}
18、命令模式(Command)
命令模式很好理解,舉個例子,司令員下令讓士兵去幹件事情,從整個事情的角度來考慮,司令員的作用是,發出口令,口令經過傳遞,傳到了士兵耳朵裏,士兵去執行。
這個過程好在,三者(司令、命令、士兵)相互解耦,任何一方都不用去依賴其他人的具體實現,只需要做好自己的事兒就行,司令員要的是結果,不會去關注到底士兵是怎麼實現的。
例子:
//命令執行的接口*/
public interface Command {
public void exe();
}
//具體實現的命令
public class MyCommand implements Command {
private Receiver receiver;
public MyCommand(Receiver receiver) {
this.receiver = receiver;
}
public void exe() {
receiver.action();
}
}
//被調用者(士兵)
public class Receiver {
public void action(){
System.out.println("command received!");
}
}
//調用者(司令員)
public class Invoker {
private Command command;
public Invoker(Command command) {
this.command = command;
}
public void action(){
command.exe();
}
}
//測試類
public class Test {
public static void main(String[] args) {
Receiver receiver = new Receiver();
Command cmd = new MyCommand(receiver);
Invoker invoker = new Invoker(cmd);
invoker.action();
}
}
這個很好理解,命令模式的目的就是達到命令的發出者和執行者之間解耦,實現請求和執行分開。
對於大多數請求-響應模式的功能,比較適合使用命令模式。
19、備忘錄模式(Memento)
也可以叫備份模式,主要目的是保存一個對象的某個狀態,以便在適當的時候恢復對象:假設有原始類A,A中有各種屬性,A可以決定需要備份的屬性,備忘錄類B是用來存儲A的一些內部狀態,類C呢,就是一個用來存儲備忘錄的,且只能存儲,不能修改等操作。
例子:
//原始類,裏面有需要保存的屬性value
public class Original {
private String value;
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
public Original(String value) {
this.value = value;
}
//創建備忘錄對象用來存儲屬性值
public Memento createMemento(){
return new Memento(value);
}
//還原屬性值
public void restoreMemento(Memento memento){
this.value = memento.getValue();
}
}
//備忘錄類,用來保存value值
public class Memento {
private String value;
public Memento(String value) {
this.value = value;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
//存儲備忘錄的類,持有Memento類的實例
public class Storage {
private Memento memento;
public Storage(Memento memento) {
this.memento = memento;
}
public Memento getMemento() {
return memento;
}
public void setMemento(Memento memento) {
this.memento = memento;
}
}
//測試類
public class Test {
public static void main(String[] args) {
// 創建原始類
Original origi = new Original("egg");
// 創建備忘錄
Storage storage = new Storage(origi.createMemento());
// 修改原始類的狀態
System.out.println("初始化狀態爲:" + origi.getValue());
origi.setValue("niu");
System.out.println("修改後的狀態爲:" + origi.getValue());
// 回覆原始類的狀態
origi.restoreMemento(storage.getMemento());
System.out.println("恢復後的狀態爲:" + origi.getValue());
}
}
20、狀態模式(State)
核心思想就是:當對象的狀態改變時,同時改變其行爲,很好理解!就拿QQ來說,有幾種狀態,在線、隱身、忙碌等,每個狀態對應不同的操作。再比如交通燈,有紅黃綠三種狀態,每種狀態下操作也是不一樣的
例子:
//狀態類
public class State {
private String value;
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
public void method1(){
System.out.println("execute the first opt!");
}
public void method2(){
System.out.println("execute the second opt!");
}
}
//Context類可以實現切換狀態
public class Context {
private State state;
public Context(State state) {
this.state = state;
}
public State getState() {
return state;
}
public void setState(State state) {
this.state = state;
}
public void method() {
if (state.getValue().equals("state1")) {
state.method1();
} else if (state.getValue().equals("state2")) {
state.method2();
}
}
}
//測試類
public class Test {
public static void main(String[] args) {
State state = new State();
Context context = new Context(state);
//設置第一種狀態
state.setValue("state1");
context.method();
//設置第二種狀態
state.setValue("state2");
context.method();
}
}
21、訪問者模式(Visitor)
訪問者模式把數據結構和作用於結構上的操作解耦合,使得對數據操作可相對自由地演化。訪問者模式適用於數據結構相對穩定,算法又易變化的系統。
因爲訪問者模式使得算法操作增加變得容易。若系統數據結構對象易於變化,經常有新的數據對象增加進來,則不適合使用訪問者模式。訪問者模式的優點是增加操作很容易,
因爲增加操作意味着增加新的訪問者。訪問者模式將有關行爲集中到一個訪問者對象中,其改變不影響系統數據結構。其缺點就是增加新的數據結構很困難。
簡單來說,訪問者模式就是一種分離對象數據結構與行爲的方法,通過這種分離,可達到爲一個被訪問者動態添加新的操作而無需做其它的修改的效果。
例子:
//訪問者接口
public interface Visitor {
public void visit(Subject sub);
}
//訪問者的一個具體實現
public class MyVisitor implements Visitor {
public void visit(Subject sub) {
System.out.println("visit the subject:"+sub.getSubject());
}
}
//被訪問者接口
public interface Subject {
public void accept(Visitor visitor);
public String getSubject();
}
//被訪問者的一個具體實現
public class MySubject implements Subject {
public void accept(Visitor visitor) {
visitor.visit(this);
}
public String getSubject() {
return "love";
}
}
//測試類
public class Test {
public static void main(String[] args) {
Visitor visitor = new MyVisitor();
Subject sub = new MySubject();
sub.accept(visitor);
}
}
該模式適用場景:如果我們想爲一個現有的類增加新功能,不得不考慮幾個事情:1、新功能會不會與現有功能出現兼容性問題?2、以後會不會再需要添加?3、如果類不允許修改代碼怎麼辦?面對這些問題,最好的解決方法就是使用訪問者模式,訪問者模式適用於數據結構相對穩定的系統,把數據結構和算法解耦
22、中介者模式(Mediator)
介者模式也是用來降低類和類之間的耦合的,因爲如果類和類之間有依賴關係的話,不利於功能的拓展和維護,因爲只要修改一個對象,其它關聯的對象都得進行修改。如果使用中介者模式,只需關心和Mediator類的關係,具體類類之間的關係及調度交給Mediator就行
例子:
//中間者接口
public interface Mediator {
public void createMediator();
public void workAll();
}
//中介者的一個具體實現
public class MyMediator implements Mediator {
private User user1;
private User user2;
public User getUser1() {
return user1;
}
public User getUser2() {
return user2;
}
public void createMediator() {
user1 = new User1(this);
user2 = new User2(this);
}
public void workAll() {
user1.work();
user2.work();
}
}
//抽象類
public abstract class User {
private Mediator mediator;
public Mediator getMediator(){
return mediator;
}
public User(Mediator mediator) {
this.mediator = mediator;
}
public abstract void work();
}
//User1
public class User1 extends User {
public User1(Mediator mediator){
super(mediator);
}
public void work() {
System.out.println("user1 exe!");
}
}
//User2
public class User2 extends User {
public User2(Mediator mediator){
super(mediator);
}
public void work() {
System.out.println("user2 exe!");
}
}
//測試類
public class Test {
public static void main(String[] args) {
Mediator mediator = new MyMediator();
mediator.createMediator();
mediator.workAll();
}
}
適用場景
在面向對象編程中,一個類必然會與其他的類發生依賴關係,完全獨立的類是沒有意義的。一個類同時依賴多個類的情況也相當普遍,既然存在這樣的情況,說明,一對多的依賴關係有它的合理性,適當的使用中介者模式可以使原本凌亂的對象關係清晰,但是如果濫用,則可能會帶來反的效果。一般來說,只有對於那種同事類之間是網狀結構的關係,纔會考慮使用中介者模式。可以將網狀結構變爲星狀結構,使同事類之間的關係變的清晰一些。
23、解釋器模式(Interpreter)
例子:解釋器接口(這裏的是專門解析數學運算表達式)
public interface Expression {
public int interpret(Context context);
}
//加法
public class Plus implements Expression {
public int interpret(Context context) {
return context.getNum1()+context.getNum2();
}
}
//減法
public class Minus implements Expression {
public int interpret(Context context) {
return context.getNum1()-context.getNum2();
}
}
//Context類是一個上下文環境類 持有運行中所需的數據
public class Context {
private int num1;
private int num2;
public Context(int num1, int num2) {
this.num1 = num1;
this.num2 = num2;
}
public int getNum1() {
return num1;
}
public void setNum1(int num1) {
this.num1 = num1;
}
public int getNum2() {
return num2;
}
public void setNum2(int num2) {
this.num2 = num2;
}
}
//測試類
public class Test {
public static void main(String[] args) {
// 計算9+2-8的值
int result = new Minus().interpret(new Context(
new Plus().interpret(new Context(9, 2)), 8));
//相當於:new Minus().interpret(new Context(11, 8));
System.out.println(result);
}
}
在以下情況下可以使用解釋器模式:
有一個簡單的語法規則,比如一個sql語句,如果我們需要根據sql語句進行其他語言的轉換,就可以使用解釋器模式來對語句進行解釋。
一些重複發生的問題,比如加減乘除四則運算,但是公式每次都不同,有時是a+b-c*d,有時是a*b+c-d,等等等等個,公式千變萬化,但是都是由加減乘除四個非終結符來連接的,這時我們就可以使用解釋器模式。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.