设计模式一の设计模式详解
一、設計模式定義
設計模式(Design Pattern)是一套被反復使用、多數人知曉的、經過分類的、代碼設計經驗的總結。 使用設計模式的目的:為了代碼可重用性、讓代碼更容易被他人理解、保證代碼可靠性。 設計模式使代碼編寫真正工程化;設計模式是軟件工程的基石脈絡,如同大廈的結構一樣。 二、設計模式的四個基本要素 模式名稱、針對的問題,解決方案、效果(評價) 三、面向對象設計的原則 針對接口編程,而不是針對實現編程。 優先使用對象組合,而不是類繼承。(對象組合如:委托,參數化類型不同于對象組合和類繼承如:c++的模板Template) 設計應支持變化。 面向對象設計有五大原則:1、單一職責原則(SRP)
闡述:簡述:一個類應該只有一個發生變化的原因。(如有多個職責,應該分離)
如果沒有變化的征兆,那么應用SRP原則或者其它原則,都是不明智的。
2、開放-封閉原則(OCP)
闡述:一個軟件實體如類、模塊和函數應該對擴展開放,對修改關閉。
開閉原則的重要性:
- 開閉原則對測試的影響
開閉原則可是保持原有的測試代碼仍然能夠正常運行,我們只需要對擴展的代碼進行測試就可以了。
- 開閉原則可以提高復用性
在面向對象的設計中,所有的邏輯都是從原子邏輯組合而來的,而不是在一個類中獨立實現一個業務邏輯。只有這樣代碼才可以復用,粒度越小,被復用的可能性就越大。
- 開閉原則可以提高可維護性
- 面向對象開發的要求
如何使用開閉原則:
- 抽象約束
第一,通過接口或者抽象類約束擴展,對擴展進行邊界限定,不允許出現在接口或抽象類中不存在的public方法;
第二,參數類型、引用對象盡量使用接口或者抽象類,而不是實現類;
第三,抽象層盡量保持穩定,一旦確定即不允許修改。
- 元數據(metadata)控制模塊行為
元數據就是用來描述環境和數據的數據,通俗地說就是配置參數,參數可以從文件中獲得,也可以從數據庫中獲得。
Spring容器就是一個典型的元數據控制模塊行為的例子,其中達到極致的就是控制反轉(Inversion of Control)
- 制定項目章程
在一個團隊中,建立項目章程是非常重要的,因為章程中指定了所有人員都必須遵守的約定,對項目來說,約定優于配置。
- 封裝變化
對變化的封裝包含兩層含義:
第一,將相同的變化封裝到一個接口或者抽象類中;
第二,將不同的變化封裝到不同的接口或抽象類中,不應該有兩個不同的變化出現在同一個接口或抽象類中。
3、里氏替換原則(LSP)
闡述:子類型(subtype)必須能夠替換掉它們的基類型(basetype)
? ? ? ?如要既要遵守契約,又要遵守LSP?
? ? ? ?從CRectangle類和CSquare類,提取出公共部分,做為一個基類。比如CShape類。
??? ?CRectangle和CSquare都繼承自CShape類。
4、依賴倒置原則(DIP)
闡述:面向接口編程
表現:
- 高層模塊不應該依賴底層模塊,兩者都應該依賴其抽象;
- 抽象不應該依賴細節;
- 細節應該依賴抽象。
如何做到抽象依賴:
- 構造函數傳遞依賴對象——構造函數注入
- Setter方法傳遞依賴對象——Setter依賴注入
- 接口聲明依賴對象——接口注入
5、接口隔離原則(ISP)
闡述:不同應用場景的接口要分開定義
表現:
- 客戶端不應該依賴它不需要的接口
- 類間的依賴關系應該建立在最小的接口上
6、迪米特原則(LKP)
闡述:一個對象應該對其他對象有最少的了解。迪米特法則(Law of Demeter,LoD)也稱為最少知識原則(Least Knowledge Principle,LKP)。
通俗地講,一個類應該對自己需要耦合或調用的類知道得最少,你(被耦合或調用的類)的內部是如何復雜都和我沒關系,那是你的事情,我就知道你提供的public方法,我就調用這么多,其他的一概不關心。
含義:只和朋友交流
四、模式和框架 設計模式比框架更抽象、設計模式是比框架更小的體系結構元素、框架比設計模式更特例化、 設計模式可以分為創建模式、結構模式、行為模式三個大類。根據范圍可以分為類的模式、對象模式。 創建模式是關注對象的創建。 結構模式是關注類與類之間的關系。 行為模式是關注對象與行為之間的關系。 五、創建模式 1、abstract factory(抽象工廠) 意圖:提供一個創建一系列相關或相互依賴的對象的接口,而無需指定他們具體的類。 別名:kit 適用:一個系統要獨立于它的產品的創建、組合、表示; 一個系統要由多個產品系列中的一個來配置時。 利弊:分離具體的類,易于交換產品系列,有利于產品一致,但難以支持新產品。 例子: int main() { //創建工廠 IFactory * pFactory = NULL; IUser * pUser = NULL; IDepartment * pDepartment = NULL; int choise; cout<<"選擇數據庫: "; cin>>choise; switch(choise) { case 1: pFactory= new SqlServerFactory(); //創建SqlServer訪問的工廠 break; case 2: pFactory = new AccessFactory(); //創建Access訪問的工廠 break; } //一致的操作 pUser = pFactory->CreateUser(); pDepartment= pFactory->CreateDepartment(); pUser->Insert(); pUser->GetUser(); pDepartment->Insert(); pDepartment->GetDepartment(); return 0; }2、bulider(生成器) 意圖:將一個復雜對象的構建和它的標識分離,使同樣的創建過程創建不同的表示。
適用性:
在以下情況使用Build模式:
1 當創建復雜對象的算法應該獨立于該對象的組成部分以及它們的裝配方式時。
2 當構造過程必須允許被構造的對象有不同的表示時。
3 Builder模式要解決的也正是這樣的問題:
當我們要創建的對象很復雜的時候(通常是由很多其他的對象組合而成),
我們要復雜對象的創建過程和這個對象的表示(展示)分離開來,
這樣做的好處就是通過一步步的進行復雜對象的構建,
由于在每一步的構造過程中可以引入參數,使得經過相同的步驟創建最后得到的對象的展示不一樣。
在書中第一個例子RTF文檔閱讀器的實現中,可以看到文檔RTFReader支持。
將一個“復雜對象的構建算法”與它的“部件及組裝方式”分離,使得構件算法和組裝方式可以獨立應對變化;復用同樣的構建算法可以創建不同的表示,不同的構建過程可以復用相同的部件組裝方式。
例子:C#中 StringBuild就是一個很好的簡單的例子
1: //Client同時充當了Director()的角色2: StringBuilder builder = new StringBuilder();3: builder.Append("happyhippy");4: builder.Append(".cnblogs");5: builder.Append(".com");6: //返回string對象:happyhippy.cnblogs.com7: builder.ToString();3、factory method(工廠方法)
意圖:定義一個用于創建對象的接口,讓子類決定實例化哪一個類。即使一個類的實例化延遲到子類中
別名:虛擬構造器(Virtual Constructor)
適用:
在以下情況下可以使用工廠方法模式:
一個類不知道它所需要的對象的類:在工廠方法模式中,客戶端不需要知道具體產品類的類名,只需要知道所對應的工廠即可,具體的產品對象由具體工廠類創建;客戶端需要知道創建具體產品的工廠類。
一個類通過其子類來指定創建哪個對象:在工廠方法模式中,對于抽象工廠類只需要提供一個創建產品的接口,而由其子類來確定具體要創建的對象,利用面向對象的多態性和里氏代換原則,在程序運行時,子類對象將覆蓋父類對象,從而使得系統更容易 ? ? ? ? ? ? ? ? 擴展。
將創建對象的任務委托給多個工廠子類中的某一個,客戶端在使用時可以無須關心是哪一個工廠子類創建產品子類,需要時再動態指定,可將具體工廠類的類名存儲在配置文件或數據庫中。
例子:
namespace FactoryMethod {/// <summary>/// 抽象產品類/// </summary>public interface ICoat{void ShowCoat();} }IFactory.csnamespace FactoryMethod {/// <summary>/// 抽象工廠類,定義產品的接口/// </summary>public interface IFactory{ICoat CreateCoat();} }FashionCoat.csusing System; namespace FactoryMethod {/// <summary>/// 具體產品類,時尚上衣類/// </summary>public class FashionCoat :ICoat{public void ShowCoat(){Console.WriteLine("這件是時尚上衣");}} }BusinessCoat.csusing System; namespace FactoryMethod {/// <summary>/// 具體產品類,商務上衣類/// </summary>public class BusinessCoat :ICoat{public void ShowCoat(){Console.WriteLine("這件是商務上衣");}} }FashionFactory.csnamespace FactoryMethod {/// <summary>/// 具體工廠類,用于創建時尚上衣/// </summary>public class FashionFactory :IFactory{public ICoat CreateCoat(){return new FashionCoat();}} }BusinessFactory.csnamespace FactoryMethod {/// <summary>/// 具體工廠類:用于創建商務上衣類/// </summary>public class BusinessFactory : IFactory{public ICoat CreateCoat(){return new BusinessCoat();}} } App.config<?xml version="1.0" encoding="utf-8" ?> <configuration><appSettings><add key="FactoryName" value="FashionFactory"/></appSettings> </configuration> Program.csusing System; using System.Configuration; using System.Reflection;namespace FactoryMethod {class Client{static void Main(string[] args){//BusinessFactory factory = new BusinessFactory();//為了方便以后修改,將工廠類的類名寫在應用程序配置文件中string factoryName = ConfigurationManager.AppSettings["FactoryName"];IFactory factory = (IFactory)Assembly.Load("FactoryMethod").CreateInstance("FactoryMethod." + factoryName);ICoat coat = factory.CreateCoat();//顯示你要的上衣coat.ShowCoat();Console.ReadLine();}} }4、prototype(原型)
意圖:用原型實例指定創建對象的種類,并且通過拷貝這些原型創建新的對象
結構:
適用:當一個系統應該獨立于他的產品的創建、構成和表示 例子: 我定義了一個場景,一個人開這一輛車在一條公路上。現在這件事是確定的,但不確定的有幾點:1、人:姓名,性別,年齡;2車:什么牌子的;3公路:公路名字,長度,類型(柏油還是土路)。現在我們一個個實現。先來實現人,定義一個抽象類,AbstractDriver,具體實現男性(Man)和女性(Women) public abstract class AbstractDriver{public AbstractDriver(){//// TODO: 在此處添加構造函數邏輯//}public string name;public string sex;public int age;public abstract string drive();public abstract AbstractDriver Clone();}public class Man:AbstractDriver{public Man(string strName,int intAge){sex = "Male";name = strName;age = intAge;}public override string drive(){return name + " is drive";}public override AbstractDriver Clone(){return (AbstractDriver)this.MemberwiseClone();}}public class Women:AbstractDriver{public Women(string strName,int intAge){sex = "Female";name = strName;age = intAge;}public override string drive(){return name + " is drive";}public override AbstractDriver Clone(){return (AbstractDriver)this.MemberwiseClone();}}注意:抽象代碼中有一個Clone的方法,個人認為這個方法是原型模式的一個基礎,因為前面講了原型模式是通過拷貝自身來創建新的對象。下面我們再來實現公路和汽車公路: public abstract class AbstractRoad{public AbstractRoad(){//// TODO: 在此處添加構造函數邏輯//}public string Type;public string RoadName;public int RoadLong;public abstract AbstractRoad Clone();}public class Bituminous:AbstractRoad //柏油路{public Bituminous(string strName,int intLong){RoadName = strName;RoadLong = intLong;Type = "Bituminous";}public override AbstractRoad Clone(){return (AbstractRoad)this.MemberwiseClone();}}public class Cement:AbstractRoad //水泥路{public Cement(string strName,int intLong){RoadName = strName;RoadLong = intLong;Type = "Cement";}public override AbstractRoad Clone(){return (AbstractRoad)this.MemberwiseClone();}}汽車:public abstract class AbstractCar{public AbstractCar(){//// TODO: 在此處添加構造函數邏輯//}public string OilBox;public string Wheel;public string Body;public abstract string Run();public abstract string Stop();public abstract AbstractCar Clone();}public class BMWCar:AbstractCar{public BMWCar(){OilBox = "BMW's OilBox";Wheel = "BMW's Wheel";Body = "BMW's body";}public override string Run(){return "BMW is running";}public override string Stop(){return "BMW is stoped";}public override AbstractCar Clone(){return (AbstractCar)this.MemberwiseClone();}}public class BORACar:AbstractCar{public BORACar(){OilBox = "BORA's OilBox";Wheel = "BORA's Wheel";Body = "BORA's Body";}public override string Run(){return "BORA is running";}public override string Stop(){return "BORA is stoped";}public override AbstractCar Clone(){return (AbstractCar)this.MemberwiseClone();}}public class VolvoCar:AbstractCar{public VolvoCar(){OilBox = "Volvo's OilBox";Wheel = "Volvo's Wheel";Body = "Volvo's Body";}public override string Run(){return "Volvo is running";}public override string Stop(){return "Volvo is stoped";}public override AbstractCar Clone(){return (AbstractCar)this.MemberwiseClone();}}然后我們再來看看場景,我們定義一個Manage類,在這個場景中有一個人,一輛車和一條公路,代碼實現如下: class Manage{public AbstractCar Car;public AbstractDriver Driver;public AbstractRoad Road;public void Run(AbstractCar car,AbstractDriver driver,AbstractRoad road){Car = car.Clone();Driver = driver.Clone();Road = road.Clone();}}可以看到,在這個代碼中,場景只是依賴于那幾個抽象的類來實現的。最后我們再來實現一下客戶代碼,比如我現在要一輛Volvo車,一個叫“Anli”的女司機,在一條叫“Road1”、長1000的柏油路上。static void Main(string[] args){Manage game = new Manage();game.Run(new VolvoCar(),new Women("Anli",18),new Bituminous("Road1",1000));Console.Write("CarRun:" + game.Car.Run() + "\n");Console.Write("DriverName:" + game.Driver.name + "\n");Console.Write("DriverSex:" + game.Driver.sex + "\n");Console.Write("RoadName:" + game.Road.RoadName + "\n");Console.Write("RoadType:" + game.Road.Type + "\n");Console.Write("CarStop:" + game.Car.Stop() + "\n");Console.Read();}運行的結果是:CarRun:Volvo is runningDriverName:AnliDriverSex:FemaleRoadName:Road1RoadType:BituminousCarStop:Volvo is stoped
現在我們再來看看原型模式的幾個要點:
1、Prototype模式同樣用于隔離類對象的使用者和具體類型(易變類)之間的耦合關系,它同樣要求這些“易變類”擁有“穩定的接口”。
2、Prototype模式對于“如何創建易變類的實體對象”采用“原型克隆”的方法來實現,它使得我們可以非常靈活地動態創建“擁有某些穩定接口”的新對象——所需工作僅僅是注冊一個新類的對象(即原型),然后在任何需要的地方不斷地Clone。
3、Prototype模式中的Clone方法可以利用Object類的MemberwiseClone()或者序列化來實現深拷貝。
5、singleton(單件)
意圖:保證類有且僅有一個實例,并提供一個訪問它的全局訪問點
此處沒有過多的解釋,程序只運行一個實例的例子很多,如工具管理的類,主程序等
單例模式的實現:
#的獨特語言特性決定了C#擁有實現Singleton模式的獨特方法。這里不再贅述原因,給出幾個結果:方法一:下面是利用.NET Framework平臺優勢實現Singleton模式的代碼:sealed class Singleton {private Singleton();public static readonly Singleton Instance=new Singleton(); } 這使得代碼減少了許多,同時也解決了線程問題帶來的性能上損失。那么它又是怎樣工作的呢?注意到,Singleton類被聲明為sealed,以此保證它自己不會被繼承,其次沒有了Instance的方法,將原來_instance成員變量變成public readonly,并在聲明時被初始化。通過這些改變,我們確實得到了Singleton的模式,原因是在JIT的處理過程中,如果類中的static屬性被任何方法使用時,.NET Framework將對這個屬性進行初始化,于是在初始化Instance屬性的同時Singleton類實例得以創建和裝載。而私有的構造函數和readonly(只讀)保證了Singleton不會被再次實例化,這正是Singleton設計模式的意圖。 (摘自:http://www.cnblogs.com/huqingyu/archive/2004/07/09/22721.aspx )不過這也帶來了一些問題,比如無法繼承,實例在程序一運行就被初始化,無法實現延遲初始化等。方法二:既然方法一存在問題,我們還有其它辦法。public sealed class Singleton {Singleton(){}public static Singleton GetInstance(){return Nested.instance;}class Nested{// Explicit static constructor to tell C# compiler// not to mark type as beforefieldinitstatic Nested(){}internal static readonly Singleton instance = new Singleton();} }六、結構型模式
結構型模式涉及到如何組合類和對象以或者更大的結構
1、adapter模式
意圖:把一個類的接口變換成客戶端所期待的另一種接口,?Adapter模式使原本因接口不匹配(或者不兼容)而無法在一起工作的兩個類能夠在一起工作
比如:多種手機,每一種機型都自帶有從電器,有一天自帶充電器壞了,而且市場沒有這類型充電器可買了。怎么辦?萬能充電器就可以解決。這個萬能充電器就是適配器。
分類:
共有兩類適配器模式:1.類的適配器模式(采用繼承實現)2.對象適配器(采用對象組合方式實現)
1)類適配器模式? ? ——適配器繼承自已實現的類(一般多重繼承)。
?
Adapter與Adaptee是繼承關系
1、用一個具體的Adapter類和Target進行匹配。結果是當我們想要一個匹配一個類以及所有它的子類時,類Adapter將不能勝任工作
2、使得Adapter可以重定義Adaptee的部分行為,因為Adapter是Adaptee的一個子集
3、僅僅引入一個對象,并不需要額外的指針以間接取得adaptee
2)對象適配器模式—— 適配器容納一個它包裹的類的實例。在這種情況下,適配器調用被包裹對象的物理實體。
?
Adapter與Adaptee是委托關系
1、允許一個Adapter與多個Adaptee同時工作。Adapter也可以一次給所有的Adaptee添加功能
2、使用重定義Adaptee的行為比較困難
無論哪種適配器,它的宗旨都是:保留現有類所提供的服務,向客戶提供接口,以滿足客戶的期望。
即在不改變原有系統的基礎上,提供新的接口服務。
適用:
1 ? 你想使用一個已經存在的類,而它的接口不符合你的需求。
2 ? 你想創建一個可以復用的類,該類可以與其他不相關的類或不可預見的類(即那些接口可能不一定兼容的類)協同工作。
3 ?(僅適用于對象Adapter)你想使用一些已經存在的子類,但是不可能對每一個都進行子類化以匹配它們的接口。對象適配器可以適配它的父類接口。即僅僅引入一個對象,并不需要額外的指針以間接取得adaptee。
舉例:
/// <summary>/// 定義客戶端期待的接口/// </summary>public class Target{/// <summary>/// 使用virtual修飾以便子類可以重寫/// </summary>public virtual void Request(){Console.WriteLine("This is a common request");}}/// <summary>/// 定義需要適配的類/// </summary>public class Adaptee{public void SpecificRequest(){Console.WriteLine("This is a special request.");}}/// <summary>/// 定義適配器/// </summary>public class Adapter:Target{// 建立一個私有的Adeptee對象private Adaptee adaptee = new Adaptee();/// <summary>/// 通過重寫,表面上調用Request()方法,變成了實際調用SpecificRequest()/// </summary>public override void Request(){adaptee.SpecificRequest();}} 客戶端: class Program{static void Main(string[] args){// 對客戶端來說,調用的就是Target的Request()Target target = new Adapter();target.Request();Console.Read();}}舉例:
類適配器
using System;/// 這里以插座和插頭的例子來詮釋適配器模式 /// 現在我們買的電器插頭是2個孔,但是我們買的插座只有3個孔的 /// 這是我們想把電器插在插座上的話就需要一個電適配器 namespace 設計模式之適配器模式 {/// <summary>/// 客戶端,客戶想要把2個孔的插頭 轉變成三個孔的插頭,這個轉變交給適配器就好/// 既然適配器需要完成這個功能,所以它必須同時具體2個孔插頭和三個孔插頭的特征/// </summary>class Client{static void Main(string[] args){// 現在客戶端可以通過電適配要使用2個孔的插頭了IThreeHole threehole = new PowerAdapter();threehole.Request();Console.ReadLine();}}/// <summary>/// 三個孔的插頭,也就是適配器模式中的目標角色/// </summary>public interface IThreeHole{void Request();}/// <summary>/// 兩個孔的插頭,源角色——需要適配的類/// </summary>public abstract class TwoHole{public void SpecificRequest(){Console.WriteLine("我是兩個孔的插頭");}}/// <summary>/// 適配器類,接口要放在類的后面/// 適配器類提供了三個孔插頭的行為,但其本質是調用兩個孔插頭的方法/// </summary>public class PowerAdapter:TwoHole,IThreeHole{/// <summary>/// 實現三個孔插頭接口方法/// </summary>public void Request(){// 調用兩個孔插頭方法this.SpecificRequest();}} }對象適配器
namespace 對象的適配器模式 {class Client{static void Main(string[] args){// 現在客戶端可以通過電適配要使用2個孔的插頭了ThreeHole threehole = new PowerAdapter();threehole.Request();Console.ReadLine();}}/// <summary>/// 三個孔的插頭,也就是適配器模式中的目標(Target)角色/// </summary>public class ThreeHole{// 客戶端需要的方法public virtual void Request(){// 可以把一般實現放在這里}}/// <summary>/// 兩個孔的插頭,源角色——需要適配的類/// </summary>public class TwoHole{public void SpecificRequest(){Console.WriteLine("我是兩個孔的插頭");}}/// <summary>/// 適配器類,這里適配器類沒有TwoHole類,/// 而是引用了TwoHole對象,所以是對象的適配器模式的實現/// </summary>public class PowerAdapter : ThreeHole{// 引用兩個孔插頭的實例,從而將客戶端與TwoHole聯系起來public TwoHole twoholeAdaptee = new TwoHole();/// <summary>/// 實現三個孔插頭接口方法/// </summary>public override void Request(){twoholeAdaptee.SpecificRequest();}} }
橋梁模式(bridge模式):橋梁模式與對象適配器類似,但是橋梁模式的出發點不同:橋梁模式目的是將接口部分和實現部分分離,從而對它們可以較為容易也相對獨立的加以改變。而對象適配器模式則意味著改變一個已有對象的接口
裝飾器模式(decorator模式):裝飾模式增強了其他對象的功能而同時又不改變它的接口。因此裝飾模式對應用的透明性比適配器更好。結果是decorator模式支持遞歸組合,而純粹使用適配器是不可能實現這一點的。
Facade(外觀模式):適配器模式的重點是改變一個單獨類的API。Facade的目的是給由許多對象構成的整個子系統,提供更為簡潔的接口。而適配器模式就是封裝一個單獨類,適配器模式經常用在需要第三方API協同工作的場合,設法把你的代碼與第三方庫隔離開來。
適配器模式與外觀模式都是對現相存系統的封裝。但這兩種模式的意圖完全不同,前者使現存系統與正在設計的系統協同工作而后者則為現存系統提供一個更為方便的訪問接口。簡單地說,適配器模式為事后設計,而外觀模式則必須事前設計,因為系統依靠于外觀。總之,適配器模式沒有引入新的接口,而外觀模式則定義了一個全新的接口。
?
代理模式(Proxy )在不改變它的接口的條件下,為另一個對象定義了一個代理。
?
裝飾者模式,適配器模式,外觀模式三者之間的區別:
裝飾者模式的話,它并不會改變接口,而是將一個一個的接口進行裝飾,也就是添加新的功能。
適配器模式是將一個接口通過適配來間接轉換為另一個接口。
外觀模式的話,其主要是提供一個整潔的一致的接口給客戶端。
?
轉載于:https://www.cnblogs.com/xietianjiao/p/7285011.html
總結
以上是生活随笔為你收集整理的设计模式一の设计模式详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 如何查看Laravel版本号的三种方法
- 下一篇: JAVA补充-抽象类