如何理解這6種常見設(shè)計模式?
一? 前言
最近在改造一些歷史的代碼,發(fā)現(xiàn)一個很明顯的特點,大部分代碼是記敘文,按照事件的發(fā)展過程將故事平鋪直敘的講解出來。
這種方式的好處是比較符合人類的思維習(xí)慣,一條主線講到底,代碼閱讀起來沒有太大難度,只要順著藤就能摸到瓜,但是缺點也很明顯,一旦故事線中需要插入一些新的元素,比如:加入一個新的人物角色、新的時間線,都會需要大量更改故事線以配合這個新元素的融入,甚至對原有文章造成破壞性的影響。
為了解決這個問題,人們總結(jié)出了很多種文章結(jié)構(gòu),例如:總-分結(jié)構(gòu),并列結(jié)構(gòu),總-分-總結(jié)構(gòu)等等,有了這些結(jié)構(gòu),在加入新元素的時候,甚至不必考慮新元素與原故事情節(jié)的關(guān)聯(lián)性,直接單拉一個分支故事線獨立去講就好了,只要能夠在整體故事結(jié)束前,與匯聚到主線故事就可以了(是不是很像git?)。
在軟件開發(fā)領(lǐng)域,也有很多這樣的非常有用的實踐總結(jié),我們稱之為設(shè)計模式。對于設(shè)計模式,大家都不陌生,隨便找個人,估計都能講出N個設(shè)計模式來,但是除了這些設(shè)計模式的概念,很多人不知道如何靈活運用這些設(shè)計模式。所以借這篇文章和大家共同學(xué)習(xí)設(shè)計模式的思想。
二? 理解設(shè)計模式
我盡量用最通俗易懂的示例和語言來講述我理解的設(shè)計模式,希望能對大家有所幫助。
另外也無需精通所有的設(shè)計模式,只要能夠融匯貫通常見的設(shè)計模式,就能讓你的代碼變得優(yōu)雅。就像程咬金只會三板斧,但是熟練度無人能及,照樣能橫行天下。
1? 工廠模式(Factory)
簡單工廠(Simple Factory)
小明追妹子的時候,請她喝了不少咖啡,她愛喝卡布奇諾,每次去咖啡店,只要跟服務(wù)員說“來杯卡布奇諾”就行了,雖然各家的口味有些不同,但是不管是星爸爸還是Costa,都能夠提供卡布奇諾這種咖啡。這里的星爸爸和Costa就是生產(chǎn)咖啡的工廠。
(1)簡單工廠模式結(jié)構(gòu)
簡單工廠模式包含如下角色:
Factory:工廠角色-負(fù)責(zé)實現(xiàn)創(chuàng)建所有實例的內(nèi)部邏輯.
Product:抽象產(chǎn)品角色-是所創(chuàng)建的所有對象的父類,負(fù)責(zé)描述所有實例所共有的公共接口。
ConcreteProduct:具體產(chǎn)品角色-是創(chuàng)建目標(biāo),所有創(chuàng)建的對象都充當(dāng)這個角色的某個具體類的實例。


優(yōu)點:客戶類和工廠類分開。消費者任何時候需要某種產(chǎn)品,只需向工廠請求即可。消費者無須修改就可以接納新產(chǎn)品。
缺點:是當(dāng)產(chǎn)品修改時,工廠類也要做相應(yīng)的修改。
以前經(jīng)常帶老婆去優(yōu)衣庫(簡單工廠)買衣服,就那么多款式,逛的次數(shù)多了,她就煩了。后來我改變策略,帶老婆去逛商場(抽象工廠),商場里有各式品牌的店鋪,不用我管,她自己就能逛上一整天。 區(qū)別于簡單工廠,核心工廠類(商場)不再負(fù)責(zé)所有產(chǎn)品的創(chuàng)建,而是將具體創(chuàng)建的工作交給子類(服裝店)去做,成為一個抽象工廠角色,僅負(fù)責(zé)給出具體工廠類必須實現(xiàn)的接口(門店),而不接觸哪一個產(chǎn)品類應(yīng)當(dāng)被實例化這種細(xì)節(jié)。
Product:抽象產(chǎn)品 ConcreteProduct:具體產(chǎn)品 Factory:抽象工廠 ConcreteFactory:具體工廠


package FactoryMethod;public class FactoryPattern{public static void main(String[] args){Factory factory = new ConcreteFactoryA();Product product = factory.createProduct();product.use();}}//抽象產(chǎn)品:提供了產(chǎn)品的接口interface Product{public void use();}//具體產(chǎn)品A:實現(xiàn)抽象產(chǎn)品中的抽象方法class ConcreteProductA implements Product{public void use(){System.out.println("具體產(chǎn)品A顯示...");}}//具體產(chǎn)品B:實現(xiàn)抽象產(chǎn)品中的抽象方法class ConcreteProductB implements Product{public void use(){System.out.println("具體產(chǎn)品B顯示...");}}//抽象工廠:提供了廠品的生成方法interface Factory{public Product createProduct();}//具體工廠A:實現(xiàn)了廠品的生成方法class ConcreteFactoryA implements AbstractFactory{public Product createProduct(){System.out.println("具體工廠A生成-->具體產(chǎn)品A.");return new ConcreteProductA();}}//具體工廠B:實現(xiàn)了廠品的生成方法class ConcreteFactoryB implements AbstractFactory{public Product createProduct(){System.out.println("具體工廠B生成-->具體產(chǎn)品B.");return new ConcreteProductB();}}
韋小寶有7個老婆,但是每個都只有他這一個老公,他的所有老婆叫老公時,指的都是他,他就是一個單例。
Singleton:單例


優(yōu)點:全局只有一個實例,便于統(tǒng)一控制,同時減少了系統(tǒng)資源開銷。
缺點:沒有抽象層,擴展困難。
public class Singleton{private static volatile Singleton instance=null; //保證 instance 在所有線程中同步private Singleton(){} //private 避免類在外部被實例化public static synchronized Singleton getInstance(){//getInstance 方法前加同步if(instance == null){instance = new Singleton();}return instance;}}
大學(xué)畢業(yè),想要送給室友一個有紀(jì)念意義的禮物,就找到一張大家的合照,在上面寫上“永遠(yuǎn)的兄弟!”,然后拿去禮品店裝了個相框,再包上禮盒。這里的我和禮品店都是裝飾器,都沒有改變照片本身,卻都讓照片變得更適合作為禮物送人。
Component:抽象構(gòu)件 ConcreteComponent:具體構(gòu)件 Decorator:抽象裝飾類 ConcreteDecorator:具體裝飾類


優(yōu)點:比繼承更加靈活(繼承是耦合度很大的靜態(tài)關(guān)系),可以動態(tài)的為對象增加職責(zé),可以通過使用不同的裝飾器組合為對象擴展N個新功能,而不會影響到對象本身。
缺點:當(dāng)一個對象的裝飾器過多時,會產(chǎn)生很多的裝飾類小對象和裝飾組合策略,增加系統(tǒng)復(fù)雜度,增加代碼的閱讀理解成本。
適合需要(通過配置,如:diamond)來動態(tài)增減對象功能的場景。
適合一個對象需要N種功能排列組合的場景(如果用繼承,會使子類數(shù)量爆炸式增長)
一個裝飾類的接口必須與被裝飾類的接口保持相同,對于客戶端來說無論是裝飾之前的對象還是裝飾之后的對象都可以一致對待。
盡量保持具體構(gòu)件類Component作為一個“輕”類,也就是說不要把太多的邏輯和狀態(tài)放在具體構(gòu)件類中,可以通過裝飾類。
package decorator;public class DecoratorPattern{public static void main(String[] args){Component component = new ConcreteComponent();component.operation();System.out.println("---------------------------------");Component decorator = new ConcreteDecorator(component);decorator.operation();}}//抽象構(gòu)件角色interface Component{public void operation();}//具體構(gòu)件角色class ConcreteComponent implements Component{public ConcreteComponent(){System.out.println("創(chuàng)建具體構(gòu)件角色");}public void operation(){System.out.println("調(diào)用具體構(gòu)件角色的方法operation()");}}//抽象裝飾角色class Decorator implements Component{private Component component;public Decorator(Component component){this.component=component;}public void operation(){component.operation();}}//具體裝飾角色class ConcreteDecorator extends Decorator{public ConcreteDecorator(Component component){super(component);}public void operation(){super.operation();addBehavior();}public void addBehavior(){System.out.println("為具體構(gòu)件角色增加額外的功能addBehavior()");}}
男生追妹子時,一般都會用到這種模式,常見的策略有這些:約會吃飯;看電影;看演唱會;逛街;去旅行……,雖然做的事情不同,但可以相互替換,唯一的目標(biāo)都是捕獲妹子的芳心。
Context: 環(huán)境類 Strategy: 抽象策略類 ConcreteStrategy: 具體策略類


優(yōu)點:策略模式提供了對“開閉原則”的完美支持,用戶可以在不修改原有系統(tǒng)的基礎(chǔ)上選擇算法或行為。干掉復(fù)雜難看的if-else。
缺點:調(diào)用時,必須提前知道都有哪些策略模式類,才能自行決定當(dāng)前場景該使用何種策略。
package strategy;public class StrategyPattern{public static void main(String[] args){Context context = new Context();Strategy strategyA = new ConcreteStrategyA();context.setStrategy(strategyA);context.algorithm();System.out.println("-----------------");Strategy strategyB = new ConcreteStrategyB();context.setStrategy(strategyB);context.algorithm();}}//抽象策略類interface Strategy{public void algorithm(); //策略方法}//具體策略類Aclass ConcreteStrategyA implements Strategy{public void algorithm(){System.out.println("具體策略A的策略方法被訪問!");}}//具體策略類Bclass ConcreteStrategyB implements Strategy{public void algorithm(){System.out.println("具體策略B的策略方法被訪問!");}}//環(huán)境類class Context{private Strategy strategy;public Strategy getStrategy(){return strategy;}public void setStrategy(Strategy strategy){this.strategy=strategy;}public void algorithm(){strategy.algorithm();}}
淘寶店客服總是會收到非常多的重復(fù)問題,例如:有沒有現(xiàn)貨?什么時候發(fā)貨?發(fā)什么快遞?大量回答重復(fù)性的問題太煩了,于是就出現(xiàn)了小蜜機器人,他來幫客服回答那些已知的問題,當(dāng)碰到小蜜無法解答的問題時,才會轉(zhuǎn)到人工客服。這里的小蜜機器人就是客服的代理。
Subject: 抽象主題角色 Proxy: 代理主題角色 RealSubject: 真實主題角色


優(yōu)點:代理可以協(xié)調(diào)調(diào)用方與被調(diào)用方,降低了系統(tǒng)的耦合度。根據(jù)代理類型和場景的不同,可以起到控制安全性、減小系統(tǒng)開銷等作用。
缺點:增加了一層代理處理,增加了系統(tǒng)的復(fù)雜度,同時可能會降低系統(tǒng)的相應(yīng)速度。
遠(yuǎn)程(Remote)代理:為一個位于不同的地址空間的對象提供一個本地的代理對象,這個不同的地址空間可以是在同一臺主機中,也可是在另一臺主機中,遠(yuǎn)程代理又叫做大使(Ambassador)。
虛擬(Virtual)代理:如果需要創(chuàng)建一個資源消耗較大的對象,先創(chuàng)建一個消耗相對較小的對象來表示,真實對象只在需要時才會被真正創(chuàng)建。
Copy-on-Write代理:它是虛擬代理的一種,把復(fù)制(克?。┎僮餮舆t到只有在客戶端真正需要時才執(zhí)行。一般來說,對象的深克隆是一個開銷較大的操作,Copy-on-Write代理可以讓這個操作延遲,只有對象被用到的時候才被克隆。
保護(hù)(Protect or Access)代理:控制對一個對象的訪問,可以給不同的用戶提供不同級別的使用權(quán)限。
緩沖(Cache)代理:為某一個目標(biāo)操作的結(jié)果提供臨時的存儲空間,以便多個客戶端可以共享這些結(jié)果。
防火墻(Firewall)代理:保護(hù)目標(biāo)不讓惡意用戶接近。
同步化(Synchronization)代理:使幾個用戶能夠同時使用一個對象而沒有沖突。
智能引用(Smart Reference)代理:當(dāng)一個對象被引用時,提供一些額外的操作,如將此對象被調(diào)用的次數(shù)記錄下來等。
package proxy;public class ProxyPattern{public static void main(String[] args){Proxy proxy = new Proxy();proxy.request();}}//抽象主題interface Subject{void request();}//真實主題class RealSubject implements Subject{public void request(){System.out.println("訪問真實主題方法...");}}//代理class Proxy implements Subject{private RealSubject realSubject;public void request(){if (realSubject==null){realSubject=new RealSubject();}preRequest();realSubject.request();afterRequest();}public void preRequest(){System.out.println("訪問真實主題之前的預(yù)處理。");}public void afterRequest(){System.out.println("訪問真實主題之后的后續(xù)處理。");}}
出差在外,想了解孩子在家的情況,這時候只要加入“相親相愛一家人”群,老爸老媽會經(jīng)常把孩子的照片和視頻發(fā)到群里,你要做的就是作為一個觀察者,刷一刷群里的信息就能夠了解一切了。
Subject:目標(biāo) ConcreteSubject:具體目標(biāo) Observer:觀察者 ConcreteObserver:具體觀察者


優(yōu)點:將復(fù)雜的串行處理邏輯變?yōu)閱卧莫毩⑻幚磉壿?,被觀察者只是按照自己的邏輯發(fā)出消息,不用關(guān)心誰來消費消息,每個觀察者只處理自己關(guān)心的內(nèi)容。邏輯相互隔離帶來簡單清爽的代碼結(jié)構(gòu)。
缺點:觀察者較多時,可能會花費一定的開銷來發(fā)消息,但這個消息可能僅一個觀察者消費。
package observer;import java.util.*;public class ObserverPattern{public static void main(String[] args){Subject subject = new ConcreteSubject();Observer obsA = new ConcreteObserverA();Observer obsb = new ConcreteObserverB();subject.add(obsA);subject.add(obsB);subject.setState(0);}}//抽象目標(biāo)abstract class Subject{protected ListobserverList = new ArrayList (); //增加觀察者方法public void add(Observer observer){observers.add(observer);}//刪除觀察者方法public void remove(Observer observer){observers.remove(observer);}public abstract void notify(); //通知觀察者方法}//具體目標(biāo)class ConcreteSubject extends Subject{private Integer state;public void setState(Integer state){this.state = state;// 狀態(tài)改變通知觀察者notify();}public void notify(){System.out.println("具體目標(biāo)狀態(tài)發(fā)生改變...");System.out.println("--------------");for(Observer obs:observers){obs.process();}}}//抽象觀察者interface Observer{void process(); //具體的處理}//具體觀察者Aclass ConcreteObserverA implements Observer{public void process(){System.out.println("具體觀察者A處理!");}}//具體觀察者Bclass ConcreteObserverB implements Observer{public void process(){System.out.println("具體觀察者B處理!");}}
有道無術(shù),術(shù)可成;有術(shù)無道,止于術(shù)
歡迎大家關(guān)注Java之道公眾號
好文章,我在看??
