知識(shí)需要不斷積累、總結(jié)和沉淀,思考和寫作是成長的催化劑
老生常談
GOF23種設(shè)計(jì)模式,想必都有聽過或?qū)W習(xí)過,就是有四個(gè)人搞了一本書總結(jié)了在面向?qū)ο箝_發(fā)過程中常見問題的解決方案。
啥是模式?就是發(fā)現(xiàn)一種可識(shí)別的規(guī)律,比如色彩模式(簡歷模版也算)。模式也往往和抽象思維有關(guān),分析事物共性,抽取事物共同的部分來幫助人們認(rèn)識(shí)規(guī)律。
啥又是設(shè)計(jì)模式?就是對(duì)事物的重新定位整合來解決問題的一種模版一種套路。它是成熟的解決方案,解決類似的問題,這樣我們可以站在巨人的肩膀上,更加優(yōu)雅的開發(fā)設(shè)計(jì)。
(本手冊(cè)盡可能采用口語化表達(dá),便于代入,很基礎(chǔ)的細(xì)節(jié)會(huì)刪減,了解過設(shè)計(jì)模式者服用更佳,查漏溫故,瀏覽起來會(huì)比較輕松)
進(jìn)入正題,設(shè)計(jì)模式按功能分為三大類
創(chuàng)建型
創(chuàng)建型設(shè)計(jì)模式,顧名思義用于對(duì)象的創(chuàng)建。提供創(chuàng)建對(duì)象就是如何New一個(gè)對(duì)象
的通用方法。
1、Singleton單例
單例模式使得一個(gè)類只有一個(gè)實(shí)例。通常像一些工具類,只需要初始化一個(gè)實(shí)例即可,不需要每次使用都去再實(shí)例化一個(gè),這樣可以解決些資源浪費(fèi)。和靜態(tài)類功能上類似,只不過單例是個(gè)對(duì)象實(shí)例。
套路1
:通常三步走,私有構(gòu)造函數(shù)禁止外部構(gòu)造,公開靜態(tài)方法提供給外部使用,私有靜態(tài)變量保證唯一(這樣只是單線程模式下適用)。外部通過Singleton.GetInstance()獲取唯一對(duì)象實(shí)例。
class Singleton
{
private static Singleton instance;
private Singleton()
{
}
public static Singleton GetSingleton()
{
if (instance == null)
{
instance = new Singleton();
}
return instance;
}
}
套路2
:多線程下簡單粗暴的方式就是lock加鎖,讓代碼塊只能有一個(gè)線程進(jìn)入。
private static readonly object syncRoot = new object();
public static Singleton GetSingleton()
{
lock (syncRoot)
{
if (instance == null)
{
instance = new Singleton();
}
}
return instance;
}
再優(yōu)化一下,上面這種方法會(huì)導(dǎo)致多個(gè)線程都需要等待,無論實(shí)例是否已經(jīng)創(chuàng)建。我們想在實(shí)例已經(jīng)創(chuàng)建的情況下多線程就不需要等待,直接返回就行。在lock外面加個(gè)判斷null可以保證以后的多線程訪問不用排隊(duì)等待。這就是雙重鎖定。
public static Singleton GetSingleton()
{
if (instance == null)
{
lock (syncRoot)
{
if (instance == null)
{
instance = new Singleton();
}
}
}
return instance;
}
套路3
:上面的已經(jīng)完全實(shí)現(xiàn)了單例模式,但還有一個(gè)更簡單的方式-靜態(tài)初始化。CLR會(huì)在類加載時(shí)初始化靜態(tài)字段,且是線程安全的,所以可以把類實(shí)例化放在靜態(tài)字段上。
public sealed class Singleton
{
private static readonly Singleton instance = new Singleton();
private Singleton()
{
}
public static Singleton GetSingleton()
{
return instance;
}
}
這種稱之為餓漢式單例,類一加載就實(shí)例化對(duì)象。前面的是懶漢式,在第一次引用時(shí)實(shí)例化。
2、Factory Method工廠方法
簡單工廠模式:什么是工廠?現(xiàn)實(shí)中就是生產(chǎn)東西的地方,程序里也是,就是有一個(gè)單獨(dú)的地方(類)來負(fù)責(zé)創(chuàng)建各種實(shí)例
。
以經(jīng)典的加減乘除計(jì)算程序?yàn)槔?,如果按照面向過程流程開發(fā),大概步驟就是用戶輸入數(shù)字1,然后輸入運(yùn)算符,輸入數(shù)字2,然后根據(jù)運(yùn)算符if或switch判斷調(diào)用哪個(gè)數(shù)學(xué)方法,這種沒有面向?qū)ο?,都在一起,耦合太緊(代碼就不貼了,都是過來人)
先看一下使用簡單工廠模式之后的類圖
把加減乘除各個(gè)運(yùn)算操作封裝成單獨(dú)類,都繼承自運(yùn)算類,共用基類的成員變量AB,重寫GetResult獲取運(yùn)算結(jié)果方法。封裝之后,提供一個(gè)簡單工廠類,通過傳入不同的操作符,實(shí)例化不同的操作運(yùn)算類。這樣增加新的操作運(yùn)算符時(shí)只需要修改工廠就行。(增加一個(gè)流水線)
簡單工廠類核心:
public class OperationFactory
{
public static Operation CreateOperate(string operate)
{
Operation oper = null;
switch (operate)
{
case "+":
oper = new OperationAdd();
break;
case "-":
oper = new OperationSub();
break;
case "*":
oper = new OperationMul();
break;
case "/":
oper = new OperationDiv();
break;
}
return oper;
}
}
工廠方法模式:對(duì)簡單工廠模式的進(jìn)一步抽象。簡單工廠是要求把邏輯放在在工廠類中,新增需要修改case分支,違背了開放封閉原則。工廠方法模式進(jìn)一步在工廠上做文章,定義一個(gè)創(chuàng)建對(duì)象的接口,讓子類決定實(shí)例化哪一個(gè)類。
工廠部分像下面這樣
interface IFactory
{
Operation CreateOperation() ;
}
class AddFactory : IFactory
{
public Operation CreateOperation()
{
return new OperationAdd();
}
}
class SubFactory : IFactory
{
public Operation CreateOperation()
{
return new OperationSub();
}
}
客戶端調(diào)用像下面這樣。如果增加操作運(yùn)算符,增加相應(yīng)的運(yùn)算類和工廠,不需要像簡單工廠那樣修改工廠類內(nèi)的邏輯。
IFactory operFactory = new AddFactory();
Operation oper = operFactory.CreateOperation();
3、Abstract Factory抽象工廠
抽象工廠模式和工廠方法類似。它是提供一個(gè)創(chuàng)建一系列對(duì)象的工廠接口,無需
指定它們具體的類。我們看下結(jié)構(gòu)圖
如果說工廠方式模式只是提供單一產(chǎn)品創(chuàng)建接口,那抽象工廠就是讓工廠抽象類擁有創(chuàng)建更多產(chǎn)品的能力,一個(gè)汽車生產(chǎn)線包括車架,底盤,輪轂等。抽象工廠的好處便于交換產(chǎn)品系列。如常見的數(shù)據(jù)庫訪問類。
interface IFactory
{
IUser CreateUser();
IDepartment CreateDepartment();
}
class SqlServerFactory : IFactory
{
public IUser CreateUser()
{
return new SqlserverUser();
}
public IDepartment CreateDepartment()
{
return new SqlserverDepartment();
}
}
class AccessFactory : IFactory
{
public IUser CreateUser()
{
return new AccessUser();
}
public IDepartment CreateDepartment()
{
return new AccessDepartment();
}
}
如果只是替換產(chǎn)品線比較容易,要是新增一個(gè)數(shù)據(jù)庫訪問表就要修改IFactory,SqlServerFactory ,AccessFactory。 這里可以用簡單工廠改進(jìn)。雖然簡單工廠會(huì)多一些switch或if判斷,但可以通過反射
配置去掉。
4、builder建造者
又叫生成器模式,將一個(gè)復(fù)雜產(chǎn)品的生成過程和它的表示分離
,這樣給不同的表示就可以創(chuàng)建出不同的產(chǎn)品,就像去買咖啡,加不加糖,加幾塊,加不加奶,做出來就是不同的咖啡,用戶只需要指定我要冰美式就行。
有多個(gè)產(chǎn)品Builder構(gòu)建類生成不同的產(chǎn)品,用戶Director指揮者指定一種產(chǎn)品就可以通過GetResult獲取這款產(chǎn)品。這個(gè)比較好理解,產(chǎn)品創(chuàng)建過程內(nèi)部完整高內(nèi)聚,只對(duì)外暴露產(chǎn)品需求,需要什么產(chǎn)品,內(nèi)部創(chuàng)建后給客戶。
5、Prototype原型
原型模式用于創(chuàng)建重復(fù)的對(duì)象而不需要知道創(chuàng)建的細(xì)節(jié)。一般在初始化的信息不發(fā)生變化時(shí),克隆Copy可以動(dòng)態(tài)的獲取一個(gè)運(yùn)行時(shí)的對(duì)象,而且效率相比構(gòu)造函數(shù)會(huì)提高。原型模式克隆對(duì)象應(yīng)該是由于類型自己完成的
。
在dotNET中提供了一個(gè)ICloneable接口(代替原型抽象類Prototype的功能)。只需要實(shí)現(xiàn)這個(gè)接口就可以完成原型模式。
class MyClass : ICloneable
{
public object Clone()
{
return this.MemberwiseClone();
}
}
注意:MemberwiseClone是淺拷貝,就是對(duì)于引用類型只復(fù)制了引用,而沒有真的把引用類型堆地址復(fù)制一份,值類型倒沒問題是真的內(nèi)存上復(fù)制一份。所以這樣如果生成一個(gè)拷貝類,則修改拷貝類中的引用類型,原類也會(huì)跟著變動(dòng)。因此使用深拷貝老老實(shí)實(shí)在Clone方法里調(diào)用重載構(gòu)造函數(shù)(直到?jīng)]有引用類型成員)初始化拷貝類,然后將值類型變量賦值。
結(jié)構(gòu)型
結(jié)構(gòu)型設(shè)計(jì)模式關(guān)注的是對(duì)象與對(duì)象之間的關(guān)系,像拼積木,組合合并給程序提供更好的靈活和擴(kuò)展
1、Adapter 適配器
聯(lián)想到電源的適配器,各個(gè)國家的電壓不一樣,為了滿足電器使用電壓就需要適配器轉(zhuǎn)換成額定電壓,那么適配器模式的定義就是將一個(gè)類的接口轉(zhuǎn)換成客戶希望的另外一個(gè)接口,使得原本由于接口不兼容而不能一起工作的那些類可以一起工作。那么如何轉(zhuǎn)換呢,核心就是適配器繼承或依賴已有的對(duì)象,實(shí)現(xiàn)想要的目標(biāo)接口
。
核心通過繼承或依賴:
class Adapter : Target
{
private Adaptee adaptee = new Adaptee();
public override void Request()
{
adaptee.SpecificRequest();
}
}
主要解決現(xiàn)有類不能滿足系統(tǒng)接口要求下,將現(xiàn)有類(可以是多個(gè))包裝到接口繼承類下。這樣接口繼承類就能調(diào)用接口來實(shí)際調(diào)用現(xiàn)有類功能。雖然提高類的復(fù)用,增加靈活,但過多使用導(dǎo)致內(nèi)部太透明,明明調(diào)用的是A接口,后臺(tái)又實(shí)際調(diào)用的B接口。所以可以看出適配器是在項(xiàng)目服役時(shí)期做的靈活調(diào)整,屬于臨陣磨槍,設(shè)計(jì)期能重構(gòu)不用最好。不便重構(gòu)的情況下,適配器可以快速統(tǒng)一接口。
2、Decorator 裝飾
人靠衣服馬靠鞍,裝飾模式就像給人搭配服飾,可以根據(jù)不同場(chǎng)合搭配不同的風(fēng)格,裝飾模式可以動(dòng)態(tài)的給一個(gè)對(duì)象添加額外的功能職責(zé)
(比直接用子類靈活一些),在不增加子類的情況下擴(kuò)展類。
核心套路
:Component 類充當(dāng)抽象角色,不具體實(shí)現(xiàn),ConcreteComponent子類代表實(shí)際的對(duì)象,現(xiàn)在要給這個(gè)對(duì)象添加一些新的功能。裝飾類繼承并且引用Component類,通過設(shè)置裝飾類中的Component屬性調(diào)用具體被裝飾對(duì)象ConcreateComponent方法。可能有些繞,關(guān)鍵在于裝飾類和具體被裝飾對(duì)象都要繼承相同的抽象Component 類。
看下代碼也許會(huì)更容易理解
abstract class Component
{
public abstract void Operation();
}
class ConcreteComponent : Component
{
public override void Operation()
{
Console.WriteLine("具體被裝飾對(duì)象的操作 ")
}
}
abstract class Decorator : Component
{
protected Component component;
public void SetComponent(Component component)
{
this.component = component;
}
public override void Operation()
{
if (component != null)
{
component.Operation();
}
}
}
class ConcreateDecoratorA : Decorator
{
private string addedState;
public override void Operation()
{
base.Operation();//執(zhí)行原Component的功能
//可以添加需要裝飾的東西,增加什么功能
addedState = "New State";
Console.WriteLine("具體裝飾對(duì)象A的操作 ");
}
}
class ConcreateDecoratorB : Decorator
{
public override void Operation()
{
base.Operation();//執(zhí)行原Component的功能
//可以添加需要裝飾的東西,增加什么功能
AddedBehavior();
Console.WriteLine("具體裝飾對(duì)象B的操作 ");
}
private void AddedBehavior()
{
}
}
客戶端調(diào)用時(shí)像下面這樣。
ConcreteComponent c = new ConcreteComponent();
ConcreateDecoratorA d1 = new ConcreateDecoratorA();
ConcreateDecoratorB d2 = new ConcreateDecoratorB();
d1.SetComponent(c);
d2.SetComponent(d1);
d2.Operation();
利用SetComponent將待裝飾對(duì)象包裝到裝飾器中,然后調(diào)用裝飾器的同名方法(因?yàn)槎祭^承相同的抽象類)。像上面這樣可以定義不同的裝飾器,會(huì)按順序逐個(gè)調(diào)用裝飾的部分。
總之,裝飾模式可以動(dòng)態(tài)擴(kuò)展
一個(gè)實(shí)現(xiàn)類的功能,當(dāng)有實(shí)現(xiàn)類需要增加特殊行為時(shí),可以用一個(gè)單獨(dú)的裝飾類去實(shí)現(xiàn),(把被裝飾類也就是實(shí)現(xiàn)類包裝進(jìn)去即可),像前面的也可以不用一個(gè)Component抽象類,直接用裝飾類繼承被裝飾類就不需要修改原類。有一個(gè)不好的地方就是如果裝飾行為多了,請(qǐng)注意裝飾順序。
3、Bridge 橋接
像路由器橋接一樣,將兩者解耦,通過一個(gè)橋接接口連通。橋接模式用于把抽象部分和實(shí)現(xiàn)部分分離解耦
,使得兩者可以獨(dú)立變化。
橋接模式中一個(gè)重要的概念就是用關(guān)聯(lián)組合代替繼承,從而降低類與類之間的耦合和臃腫。比如一個(gè)繪制圖形的功能,有圓形、正方形等不同的圖形,它們還有不同的顏色。用繼承關(guān)系設(shè)計(jì)可能像下面這樣??梢钥匆婎惐容^多
還有一種方案就是對(duì)圖形和顏色進(jìn)行組合得到想要的顏色圖形。
所以對(duì)于有多個(gè)維度變化的系統(tǒng),采用第二種方案系統(tǒng)中的類個(gè)數(shù)會(huì)更小,擴(kuò)展方便。
abstract class Implementor
{
public abstract void Operation();
}
class ConcreteImplementorA : Implementor
{
public override void Operation()
{
Console.WriteLine("具體實(shí)現(xiàn)A的方法執(zhí)行");
}
}
class ConcreteImplementorB : Implementor
{
public override void Operation()
{
Console.WriteLine("具體實(shí)現(xiàn)B的方法執(zhí)行");
}
}
class Abstraction
{
protected Implementor implementor;
public void SetImplementor(Implementor implementor)
this.implementor = implementor;
}
public virtual void Operation()
{
implementor.Operation();
}
}
class RefinedAbstraction : Abstraction
{
public override void Operation()
{
implementor.Operation();
}
}
客戶端調(diào)用
Abstraction ab = new RefinedAbstraction();
ab.SetImplementor(new ConcreteImplementorA());
ab.Operation();
ab.SetImplementor(new ConcreteImplementorB());
ab.Operation();
如果系統(tǒng)可能有多個(gè)角度的分類,每一種角度都可能變化,就可以不用靜態(tài)的繼承連接,通過橋接模式可以使它們?cè)诔橄髮咏㈥P(guān)聯(lián)關(guān)系。
4、Composite 組合
組合模式也就是部分整理關(guān)系
,將對(duì)象組合成樹形結(jié)構(gòu)以表示“部分整體”的層次結(jié)構(gòu),組合模式使得用戶對(duì)單個(gè)對(duì)象和組合對(duì)象的使用具有一致性,用于把一組相似的對(duì)象當(dāng)做一個(gè)單一的對(duì)象。
核心
就是讓樹枝和葉子實(shí)現(xiàn)統(tǒng)一的接口,樹枝內(nèi)部組合該接口,并且含有內(nèi)部屬性List放Component。
abstract class Component
{
protected string name;
public Component(string name)
{
this.name = name;
}
public abstract void Add(Component c);
public abstract void Remove(Component c);
public abstract void Display(int depth);
}
class Leaf : Component
{
public Leaf(string name) : base(name)
{ }
public override void Add(Component c)
{
Console.WriteLine("Cannot add to a leaf");
}
public override void Remove(Component c)
{
Console.WriteLine("Cannot remove to a leaf");
}
public override void Display(int depth)
{
Console.WriteLine(new string('-', depth) + name);
}
}
class Composite : Component
{
private List<Component> children = new List<Component> { };
public Composite(string name) : base(name)
{ }
public override void Add(Component c)
{
children.Add(c);
}
public override void Remove(Component c)
{
children.Remove(c);
}
public override void Display(int depth)
{
Console.WriteLine(new string('-', depth) + name);
foreach (Component component in children)
{
component.Display(depth + 2);
}
}
}
客戶端使用像下面這樣
Composite root = new Composite("root");
root.Add(new Leaf("Leaf A"));
root.Add(new Leaf("Leaf B"));
Composite comp = new Composite("Composite X");
comp.Add(new Leaf("Leaf XA"));
comp.Add(new Leaf("Leaf XB"));
root.Add(comp);
Composite comp2 = new Composite("Composite XY");
comp2.Add(new Leaf("Leaf XYA"));
comp2.Add(new Leaf("Leaf XYB"));
comp.Add(comp2);
root.Add(new Leaf("Leaf C"));
root.Display(1);
或者像公司體系結(jié)構(gòu)這種當(dāng)需求中體現(xiàn)部分整體層次結(jié)構(gòu),以及你希望用戶可以忽略組合對(duì)象與單個(gè)對(duì)象的不同,統(tǒng)一地使用組合結(jié)構(gòu)中的所有對(duì)象時(shí),考慮使用組合模式。上面例子中客戶端無需關(guān)心處理一個(gè)葉節(jié)點(diǎn)還是枝節(jié)點(diǎn)組合組件,都用統(tǒng)一方式訪問。
5、Flyweight 享元
享元模式主要用于減少對(duì)象的數(shù)量,以減少內(nèi)存占用和提供性能。享元模式嘗試重用現(xiàn)有的同類對(duì)象
,如果未找到匹配的對(duì)象,則創(chuàng)建新對(duì)象。像棋盤中的棋子,不可能創(chuàng)建一整個(gè)所有的棋子對(duì)象。所以它主要解決當(dāng)需要大量的對(duì)象,有可能對(duì)內(nèi)存造成溢出,我們可以把共同的部分抽象出來,這樣如果有相同的業(yè)務(wù)請(qǐng)求,直接返回內(nèi)存中已有的對(duì)象,不去重復(fù)創(chuàng)建了。
核心
就是用一個(gè)字典存儲(chǔ)這些需要重復(fù)使用的對(duì)象。
通過上面結(jié)構(gòu)圖可以看見就是把Flyweight享元類聚合到FlyweightFactory享元工廠中,享元工廠中用一個(gè)Hashtable存儲(chǔ)這些具體的享元類。
class FlyweightFactory
{
private Hashtable flyweights = new Hashtable();
public FlyweightFactory()
{
flyweights.Add("X", new ConcreateFlyweight);
flyweights.Add("Y", new ConcreateFlyweight);
}
public Flyweight GetFlyweight(string key)
{
return (Flyweight)flyweights[key];
}
}
看上面代碼,估計(jì)大都用過,設(shè)計(jì)模式好多都是這種普普通通,數(shù)據(jù)庫連接池就是使用享元模式。總結(jié)下就是在系統(tǒng)中有大量相似對(duì)象或者需要緩沖池的場(chǎng)景,相似對(duì)象可以分離出內(nèi)部和外部狀態(tài),內(nèi)部狀態(tài)就是固有的,不變的。外部狀態(tài)可以通過外部調(diào)用傳遞進(jìn)去。
6、Proxy 代理
像代購一樣,我們拜托他人幫我們買某個(gè)東西。代理模式就是用一個(gè)類代表另一個(gè)類的功能
。通常在不方便直接訪問原始對(duì)象功能,或者需要對(duì)原始對(duì)象功能增加一些權(quán)限或其他控制時(shí)使用代理模式。
核心
就是增加代理層,讓代理類和真實(shí)類都實(shí)現(xiàn)相同的接口(或抽象類),然后把真實(shí)類關(guān)聯(lián)到代理類中。
上述結(jié)構(gòu)圖中的核心代碼如下
class Proxy : Subject
{
RealSubject realSubject;
public override void Request()
{
if (realSubject == null)
{
realSubject = new RealSubject();
}
realSubject.Request();
}
}
代理模式其實(shí)就是在訪問對(duì)象時(shí)引入一定程度的間接性,因此可以附加多種用途。
7、Facade外觀
外觀模式隱藏系統(tǒng)的復(fù)雜性,向客戶端提供一個(gè)高層訪問系統(tǒng)接口
。這樣降低訪問復(fù)雜系統(tǒng)的內(nèi)部子系統(tǒng)時(shí)的復(fù)雜度,簡化客戶端交互,就像公司前臺(tái)。
核心
就是將復(fù)雜系統(tǒng)里的類關(guān)聯(lián)到外觀類上。上面結(jié)構(gòu)圖就很清晰,通過外觀類方法調(diào)用各個(gè)復(fù)雜系統(tǒng)類。
這種方式對(duì)老系統(tǒng)尤其有用,新增功能需要用到舊類,如果怕改壞了,就可以簡單實(shí)用外觀封裝。還有就是設(shè)計(jì)時(shí)經(jīng)典的三層架構(gòu)也是外觀的體現(xiàn)。
行為型
行為型設(shè)計(jì)模式關(guān)注對(duì)象和行為的分離。行為型比較多,因?yàn)槌绦蜻壿嫸夹枰袨閬碛|發(fā)。
1、Interpreter 解釋器
解釋器模式,給定一個(gè)語言,定義它的文法的一種表示,并定義一個(gè)解釋器,這個(gè)解釋器使用該表示來解釋語言中的句子。有點(diǎn)拗口,簡單理解就是對(duì)于一些固定文法構(gòu)建一個(gè)解釋句子的解釋器
。像正則表達(dá)式就是這樣,查詢匹配字符問題發(fā)生的頻率很高,就把該問題各個(gè)實(shí)例表述為一個(gè)簡單語言中的句子,解釋句子來解決問題。比例諜戰(zhàn)片中的加密電報(bào),構(gòu)建一個(gè)解釋器對(duì)每個(gè)文法解析。
核心
就是構(gòu)建語法數(shù),定義終結(jié)符與非終結(jié)符。實(shí)際利用的場(chǎng)景還是比較少的,而且文法太復(fù)雜也很難維護(hù)。
實(shí)際客戶端調(diào)用時(shí)像下面這樣遍歷解釋器文法
Context context = new Context();
List<AbstractExpression> list = new ArrayList<>();
list.Add(new TerminalExpression());
list.Add(new NonterminalExpression());
list.Add(new TerminalExpression());
list.Add(new TerminalExpression());
foreach (AbstractExpression abstractExpression in list)
{
abstractExpression.Interpret(context);
}
2、Template Method 模板方法
在模板方法中,一個(gè)抽象類公開定義一個(gè)算法的執(zhí)行方式模板,而將一些步驟延遲到子類中
,子類可以按需要重寫方法實(shí)現(xiàn),但調(diào)用統(tǒng)一使用抽象類中定義的方式調(diào)用。
上面結(jié)構(gòu)圖中AbstractClass就是一個(gè)抽象類(抽象模板),定義并實(shí)現(xiàn)了一個(gè)模板方法。像下面這樣
看見核心
就是將通用方法封裝在超類中,所以在當(dāng)子類方法中有一些不變的和可變的行為,可以將不變的行為通過模板方法搬到父類,這樣子類就不需要重復(fù)這些不變的部分。是不是很簡單,設(shè)計(jì)模式就是對(duì)面向?qū)ο缶幊?,封裝繼承多態(tài)的靈活使用。
3、Chain of Responsibility 責(zé)任鏈
像公司內(nèi)的請(qǐng)假流程,如果請(qǐng)很長時(shí)間,可能先有部門經(jīng)理審批,部門經(jīng)理說時(shí)間太長了,需要問下總經(jīng)理。為請(qǐng)求創(chuàng)建了一個(gè)接受者對(duì)象的鏈,讓請(qǐng)求者和接收者解耦。這種模式中,通常每個(gè)接收者都包含對(duì)另一個(gè)接收者的引用
,這樣如果這個(gè)接收者對(duì)象不能處理該請(qǐng)求就傳遞給下一個(gè)接收者。
上述結(jié)構(gòu)圖中首先定義一個(gè)Handler處理請(qǐng)求抽象類,里面設(shè)置了下一個(gè)接收者和處理請(qǐng)求的抽象方法。然后再ConcreateHandler子類中實(shí)現(xiàn)具體的請(qǐng)求處理,如果處理不了,就轉(zhuǎn)發(fā)給下一個(gè)接收者。
abstract class Handler
{
protected Handler successor;
public void SetSuccessor(Handler successor)
{
this.successor = successor;
}
public abstract void HandleRequest(int request);
}
class ConcreateHandler1 : Handler
{
public override void HandleRequest(int request)
{
if (request >= 0 && request < 10)
{
Console.WriteLine($"{this.GetType().Name}處理請(qǐng)求 {request}");
}
else if (successor != null)
{
successor.HandleRequest(request);
}
}
}
核心就是攔截類都實(shí)現(xiàn)統(tǒng)一接口
。Handler里聚合它自己,在HandlerRequest里判斷是否能處理,如果不能就向下傳遞,要向誰傳遞就Set進(jìn)去。所以這種方式是不確定哪個(gè)接收者會(huì)處理請(qǐng)求,通常在攔截器中使用,需要對(duì)消息的處理過濾很多道時(shí)。像擊鼓傳花。
4、Command 命令
服務(wù)員,再炒個(gè)方便面,10個(gè)腰子,服務(wù)員就記下來交給炒菜師傅和烤串師傅,這就是命令模式。請(qǐng)求以命名的形式包裹在對(duì)象中
,并傳給調(diào)用對(duì)象。調(diào)用對(duì)象尋找可以處理該命名的合適的對(duì)象,并把該命名傳給相應(yīng)的對(duì)象執(zhí)行命名。并且支持可撤銷操作,如果腰子很久沒上來,可以通知服務(wù)員不要了。
核心
就是定義三個(gè)角色:1、received真正的命令執(zhí)行對(duì)象2、Command3、invoker使用命令對(duì)象的入口。最最終實(shí)現(xiàn)Invoker對(duì)象通過調(diào)用ExecuteCommand方法來調(diào)用具體命令Command的Excute方法,Excute方法里調(diào)用實(shí)際Received接收者動(dòng)作。
abstract class Command
{
protected Receiver receiver;
public Command(Receiver receiver)
{
this.receiver = receiver;
}
abstract public void Execute();
}
class ConcreteCommand : Command
{
public ConcreteCommand(Receiver receiver) : base(receiver)
{ }
public override void Execute()
{
receiver.Action();
}
}
class Invoker
{
private Command command;
public void SetCommand(Command command)
{
this.command = command;
}
public void ExecuteCommand()
{
command.Execute();
}
}
class Receiver
{
public void Action()
{
Console.WriteLine("執(zhí)行請(qǐng)求!");
}
}
調(diào)用時(shí)像下面這樣
Receiver r = new Receiver();
Command c = new ConcreteCommand(r);
Invoker i = new Invoker();
i.SetCommand(c);
i.ExecuteCommand();
命令模式可以比較容易設(shè)計(jì)成命令隊(duì)列,方便記錄日志,支持撤銷重做
5、Iterator 迭代器
迭代器提供一種方法順序訪問一個(gè)聚合對(duì)象中各個(gè)元素,而又不暴露該對(duì)象的內(nèi)部表示。就是把元素之間的遍歷游走的責(zé)任
交給迭代器,而不是集合對(duì)象本身,分離了集合對(duì)象的遍歷行為,這樣不暴露集合內(nèi)部結(jié)構(gòu),又可讓外部訪問集合內(nèi)部數(shù)據(jù)。
核心
就是定義一個(gè)有Next,CurrentItem等方法的Iterator迭代接口,然后在子類具體迭代器ConcreateIterator類(可以實(shí)現(xiàn)多個(gè)迭代方式)中定義一個(gè)具體的聚集對(duì)象,然后遍歷迭代器的Next方法遍歷聚集對(duì)象內(nèi)部數(shù)據(jù)。
在dotNET框架中已經(jīng)準(zhǔn)備好了相關(guān)接口,只需要去實(shí)現(xiàn)去就好。
IEumerator支持對(duì)非泛型集合的簡單迭代接口,就和上面Iterator一樣
public interface IEnumerator
{
object? Current { get; }
bool MoveNext();
void Reset();
}
常用的foeach遍歷,編譯器也是轉(zhuǎn)化為了IEnumerator遍歷。因?yàn)樘S?,高?jí)語言都進(jìn)行了封裝,自己也就不常用了。
6、Mediator 中介者
中介者模式用一個(gè)中介對(duì)象來封裝一系列的對(duì)象交互
。中介者使各對(duì)象不需要顯示地相互引用,從而使其耦合松散,而且可以獨(dú)立地改變它們之間的交互。就是提供一個(gè)中介類,處理不同類之間的通信。
面向抽象編程要求我們類應(yīng)該依賴于抽象
核心
就是定義中介者關(guān)聯(lián)不同的需要通信的類,通信類內(nèi)部也需要關(guān)聯(lián)具體中介者。通信類發(fā)送信息時(shí)實(shí)際已經(jīng)通過中介者來轉(zhuǎn)發(fā)。
我們看下核心的具體中介者和通信者以及客戶端調(diào)用實(shí)例
中介者模式將原來網(wǎng)狀的結(jié)構(gòu)變成星狀結(jié)構(gòu),所以中介者可能會(huì)很龐大。一般應(yīng)用在一組對(duì)象已定義良好但是復(fù)雜的方式進(jìn)行通信的場(chǎng)合。
7、Memento備忘錄
備忘錄模式保存一個(gè)對(duì)象的某個(gè)狀態(tài),以便在適當(dāng)?shù)臅r(shí)候恢復(fù)對(duì)象
。很多時(shí)候我們需要記錄一個(gè)對(duì)象的內(nèi)部狀態(tài),這樣可以讓用戶去恢復(fù)。像玩魔塔游戲存進(jìn)度一樣。
結(jié)構(gòu)圖中Originator發(fā)起人負(fù)責(zé)創(chuàng)建一個(gè)備忘錄,保存?zhèn)渫浀膬?nèi)部數(shù)據(jù)。Memento備忘錄包含要備份的數(shù)據(jù),Caretaker管理者得到或設(shè)置備忘錄。
如果保存全部信息,我們可以使用Clone實(shí)現(xiàn),但如果只保存部分信息,就應(yīng)該有一個(gè)獨(dú)立的Memento備忘錄類。
8、Observer 觀察者
觀察者模式也叫發(fā)布訂閱模式,定義了一種一對(duì)多的依賴關(guān)系,讓多個(gè)觀察者對(duì)象同時(shí)監(jiān)聽某一個(gè)主題對(duì)象。這個(gè)主題對(duì)象在狀態(tài)發(fā)生改變時(shí),會(huì)通知所有觀察者對(duì)象,是它們能夠主動(dòng)更新自己
。這也是很常用的,像委托事件就是這種模式
核心
是將所有通知者抽象類中用一個(gè)集合放所有觀察者們。當(dāng)通知者ConcreateSubject發(fā)起Notify通知時(shí),逐個(gè)調(diào)用集合中觀察者進(jìn)行Update更新。
abstract class Subject
{
private IList<Observer> observers = new List<Observer>();
// 增加觀察者
public void Attach(Observer observer)
{
observers.Add(observer);
}
// 移除觀察者
public void Detach(Observer observer)
{
observers.Remove(observer);
}
// 通知
public void Notify()
{
foreach (Observer o in observers)
{
o.Update();
}
}
}
當(dāng)一個(gè)對(duì)象的改變需要通知其他對(duì)象時(shí)使用,一般這種通知可能通過異步來處理。
9、State 狀態(tài)
在狀態(tài)模式中,類的行為時(shí)基于它的狀態(tài)改變的。就是當(dāng)一個(gè)對(duì)象的內(nèi)部狀態(tài)發(fā)生改變時(shí)改變它的行為
。主要解決當(dāng)控制一個(gè)對(duì)象狀態(tài)轉(zhuǎn)換的條件表達(dá)式過于復(fù)雜時(shí),把判斷的邏輯遷移到表示不同狀態(tài)的一系列類中,達(dá)到給功能類瘦身的目的。
上面結(jié)構(gòu)圖中將狀態(tài)單獨(dú)從Context功能類中抽出來,先有一個(gè)抽象的狀態(tài)類,然后具體不同的狀態(tài)可以繼承實(shí)現(xiàn)不同狀態(tài)的Handle處理行為,最后把抽象狀態(tài)聚合放到Context中去,最終調(diào)用Context的Request會(huì)根據(jù)不同的狀態(tài)觸發(fā)不同的Handle行為。
看下核心的代碼。也可以在每一個(gè)狀態(tài)子類Handle行為中設(shè)置Context的下一個(gè)狀態(tài),下次調(diào)用Request就觸發(fā)相應(yīng)的狀態(tài)行為。
核心
就是將狀態(tài)和行為放入一個(gè)對(duì)象中。這么多種設(shè)計(jì)模式都有很多相像的地方,反正就是面向?qū)ο?,分分合合,像前后端一樣,各有?yōu)劣。這里就和命令模式處理的問題很像,都可以用作if分支語句的代替。通過狀態(tài)模式可以很容易的增加新的狀態(tài),把狀態(tài)行為封裝起來,減輕了功能類。
10、Strategy 策略
策略模式定義一個(gè)類的行為算法可以在運(yùn)行時(shí)更改, 把這些算法一個(gè)個(gè)封裝起來
,并使它們可以相互替換。
和前面狀態(tài)模式結(jié)構(gòu)圖無差別,就是用策略代替了狀態(tài),描述不同的問題,解決方法一樣。硬要找個(gè)不同,大概就是在Context中,狀態(tài)模式調(diào)用時(shí)會(huì)傳遞本身引用到各個(gè)子狀態(tài)的以實(shí)現(xiàn)狀態(tài)的改變,策略模式中不需要傳遞,只在初始化時(shí)指定策略。
核心
和前面一樣,不同策略實(shí)現(xiàn)同一個(gè)接口,聚合到Context類中。也是為了避免多重判斷,擴(kuò)展性好,可以隨意切換新增算法。像商場(chǎng)里的打折促銷滿減不同活動(dòng),可能會(huì)有人想到,不同策略的選擇還是需要判斷語句,可以結(jié)合簡單工廠進(jìn)一步處理。追求極致那就反射嘍。反射反射,程序員的快樂。
11、Visitor 訪問者
訪問者模式應(yīng)該是這里面最復(fù)雜的,大多數(shù)時(shí)候你并不需要使用它。因?yàn)樵L問者模式表示作用于某對(duì)象結(jié)構(gòu)中各元素的操作,它使你在不改變?cè)氐念惖那疤嵯露x新操作,而對(duì)象數(shù)據(jù)結(jié)構(gòu)是在不變
的情況下。
不要怕這個(gè)結(jié)構(gòu)圖,一共就兩個(gè)部分,首先提供訪問者Visitor類,它的子類就是具體的對(duì)元素的操作算法,然后ObjectStructure就是元素集合類,里面遍歷每個(gè)元素執(zhí)行元素相對(duì)應(yīng)的算法。所以關(guān)鍵就在下面部分Element類中將Visitor作為輸入?yún)?shù)。
核心
就是在被訪問者的類中加一個(gè)對(duì)外提供接待訪問者的接口,也就是在數(shù)據(jù)元素類中有一個(gè)方法接收訪問者,將自身引用傳入訪問者,像下面示例這樣
class ConcreateElementA : Element
{
public override void Accept(Visitor visitor)
{
visitor.VisitorConcreateElementA(this);
}
}
class ObjectStructure
{
private List<Element> elements = new List<Element>();
public void Attach(Element element)
{
elements.Add(element);
}
public void Detach(Element element)
{
elements.Remove(element);
}
public void Accept(Visitor visitor)
{
foreach (Element e in elements)
{
e.Accept(visitor);
}
}
}
在對(duì)象結(jié)構(gòu)很少變動(dòng),需要在此對(duì)象結(jié)構(gòu)上定義新的操作或者本身它就有很多不相關(guān)的操作時(shí)可以考慮使用此模式。
謝謝
設(shè)計(jì)模式可能對(duì)于小白來說高大上,其實(shí)你實(shí)際也經(jīng)常會(huì)使用到,只是不知道那就是設(shè)計(jì)模式,“優(yōu)秀”總是那么相似
。
不用刻意去追求設(shè)計(jì)模式,一個(gè)問題也可能有很多解決方案,往良好的設(shè)計(jì)去優(yōu)化。自己用的多了,就知道什么場(chǎng)景使用什么設(shè)計(jì),最終會(huì)與大神不謀而合的。
主要參考程杰的《大話設(shè)計(jì)模式》
拜了拜