设计模式纵横谈学习笔记.docx
《设计模式纵横谈学习笔记.docx》由会员分享,可在线阅读,更多相关《设计模式纵横谈学习笔记.docx(39页珍藏版)》请在冰豆网上搜索。
![设计模式纵横谈学习笔记.docx](https://file1.bdocx.com/fileroot1/2023-1/23/d73f2bd6-334b-4144-9230-b126468eaadd/d73f2bd6-334b-4144-9230-b126468eaadd1.gif)
设计模式纵横谈学习笔记
设计模式纵横谈
设计模式原则
1、单一职责原则(SRP):
一个类应有仅有一个引起它变化的原因。
2、开放封闭原则(OCP):
类模块应该是可扩展的,但是不可修改(对扩展开放,对更改封闭)。
内模块不可以修改,但可以新增功能。
3、Liskov替换原则(LSP):
子类必须能够替换它们的基类。
如果调用的是父类的话,那么换成子类也完全可以运行。
4、依赖倒置原则(DIP):
高层模块(改变速度慢)不应该依赖于低层模块(改变速度快),二者都应该依赖于抽象。
抽象不应该依赖于实现细节,实现细节应该依赖于抽象。
主要是在构造对象时可以动态的创建各种具体对象,当然如果一些具体类比较稳定,就不必在弄一个抽象类做它的父类,这样有画蛇添足的感觉。
5、接口隔离原则(ISP):
不应该强迫客户程序依赖于它们不用的方法。
定制服务的例子,每一个接口应该是一种角色,不多不少,不干不该干的事,该干的事都要干。
6、合成/聚合复用原则
少用继承,多用合成关系来实现。
模式分类
1、从目的来看:
创建型(Creational)模式:
负责对象创建。
单件模式、抽象工厂模式、建造者模式、工厂模式、原型模式。
结构型(Structural)模式:
处理类与对象间的组合。
适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式。
行为型(Behavioral)模式:
类与对象交互中的职责分配。
模版方法模式、命令模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式、状态模式、策略模式、职责链模式、访问者模式。
2、从范围来看:
类模式处理类与子类的静态关系。
对象模式处理对象间的动态关系。
Singleton单件(创建型模式)
一、动机(Motivation)
特殊类,在系统中只存在一个实例。
如何绕过常规的构造器,提供一种机制来保证一个类只有一个实例?
2、意图(Intent)
保证一个类仅有一个实例,并提供一个该实例的全局访问点。
3、单线程Singleton模式实现
Singleton模式中的实例构造器可以设置为protected以允许子类派生。
Singleton模式一般不要支持ICloneable接口(实例的克隆),因为这可能会导致多个对象实例,与Singleton模式的初衷违背。
Singleton模式一般不要支持序列化,因为这也可能导致多个对象实例。
Singleton模式只考虑到了对象创建的管理,没有考虑对象销毁的管理。
就支持垃圾回收平台和对象的开销来讲,我们一般没有必要对其销毁进行特殊的管理。
不能应对多线程环境。
publicclassSingleton
{privatestaticSingletoninstance;
privateSingleton(){}
publicstaticSingletonInstance
{get
{if(instance==null)
instance=newSingleton();
returninstance;}
}}
私有的实例构造器是为了屏蔽默认产生的构造器,让类的使用者无法调用构造器。
4、多线程Singleton模式实现
publicclassSingleton
{privatestaticvolatileSingletoninstance;
privatestaticobjectlockHelper=newObject();
privateSingleton(){}
publicstaticSingletonInstance
{get
{if(instance==null)
{lock(lockHelper)
if(instance==null)
instance=newSingleton();}
returninstance;}
}}
volatile修饰:
编译器在编译代码的时候会对代码的顺序进行微调,用volatile修饰保证了严格意义的顺序。
一个定义为volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。
精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。
AbstractFactory抽象工厂(创建型模式)
1、new的问题
常规的对象创建方法:
Roadroad=newRoad();
New的问题:
实现依赖,不能应对“具体实例化类型”的变化。
解决思路:
封装变化点——哪里变化,封装哪里。
面向接口编程——依赖接口、而非依赖实现。
最简单的解决方法:
ClassRoadFactory{
PublicstaticRoadCreateRoad(){
ReturnnewRoad();}}
客户程序:
Roadroad=RoadFactory.CreateRoad();
二、如何解决
使用面向对象的技术来“封装”变化点。
3、动机
在软件系统中,经常面临着“一系列相互依赖的对象”的创建工作;同时,由于需求的变化,往往存在更多系列对象的创建工作。
四、意图
提供一个接口,让该接口负责创建一系列“相关或者相互依赖的对象”,无需指定它们具体的类。
5、要点
如果没有应多“多系列对象构建”的需求变化,则没有必要使用AbstractFactory模式,这时候使用简单的静态工厂完全可以。
“系列对象”指的是这些对象之间有相互依赖、或作用的关系,例如游戏开发场景中的“道路”与“房屋”的依赖,“道路”与“地道”的依赖。
AbstractFactory模式主要在于应对“新系列”的需求变动。
其缺点在于难以应对“新对象”的需求变动。
AbstractFactory模式经常和FactoryMethod模式共同组合来应对“对象创建”的需求变化。
FactoryMethod工厂方法模式(创建型模式)
1、从耦合关系谈起
耦合关系直接决定着软件面对变化时的行为
——模块与模块之间的紧耦合使得软件面对变化时,相关的模块都要随之更改
——模块与模块之间的松耦合使得软件面对变化时,一些模块更容易被替换或者更改,但其他模块保持不变。
2、动机
在软件系统中,经常面临着“某个对象”的创建工作:
由于需求的变化,这个对象的具体实现经常面临着剧烈的变化,但是它却拥有比较稳定的接口。
如何提供一种“封装机制”来隔离出“这个易变对象”的变化,从而保持系统中“其他依赖该对象的对象”不随着需求的改变而改变?
3、意图
定义一个用于创建对象的接口,让子类决定实例化哪一个类。
FactoryMethod使得一个类的实例化延迟到子类。
4、要点
FactoryMethod模式主要用于隔离类对象的使用者和具体类型之间的耦合关系。
面对一个经常变化的具体类型,紧耦合关系会导致软件的脆弱。
FactoryMethod模式通过面向对象的手法,将所要创建的具体对象工作延迟到子类,从而实现一种扩展(而非更改)的策略,比较好地解决了这种紧耦合关系。
FactoryMethod模式解决“单个对象”的需求变化,AbstractFactory模式解决“系列对象”的需求变化,Builder模式解决“对象部分”的需求变化。
汽车:
abstractclassAbstractCar
{
publicvoidStartup();
publicvoidRun();
publicvoidTurn(Directiondirection);
publicvoidStop();
}
汽车工厂:
abstractclassCarFactory
{
publicabstractAbstractCarCreateCar();
}
publicclassHongqiCarFactory:
CarFactory
{
publicoverrideAbstractCarCreateCar()
{
returnnewHongqiCar();
}
}
红旗汽车:
publicclassEnginee{}
publicclassHongqiCar:
AbstractCar
{
Engineeenginee1;
Engineeenginee2;
Engineeenginee3;
Engineeenginee4;
publicoverridevoidStartup(){}
publicoverridevoidRun(){}
publicoverridevoidTurn(Directiondirection){}
publicoverridevoidStop();
}
汽车测试:
classCarTestFramework
{
publicvoidBuildTestContext(CarFactorycarFactory)
{
AbstractCarc1=carFactory.CreateCar();
AbstractCarc2=carFactory.CreateCar();
}
publicvoidDoTest(AbstractCarcar)
{
car.Run();
}
publicTestDataGetTestData(AbstractCarcar)
{
TestDatat=newTestData();
returnt;
}
}
客户程序:
staticvoidMain(string[]args)
{
CarTestFrameworkcarTestFramework=newCarTestFramework();
carTestFramework.BuildTestContext(newHongqiCarFactory());
}
Builder生成器模式(创建型模式)
1、动机
在软件系统中,有时候面临着“一个复杂对象”的创建工作,期通常由各个部分的子对象用一定的算法构成:
由于需求的变化,这个复杂对象的各个部分经常面临着剧烈的变化,但是将它们组合在一起的算法却相对稳定。
如何提供一种“封装机制”来隔离出“复杂对象的各个部分”的变化,从而保持系统中的“稳定结构算法”不随着需求的改变而改变?
2、意图
将一个复杂对象的构建与其表示相分离,使得同样的构建过程可以创建不同的表示。
3、要点
Builder模式主要用于“分步骤构建一个复杂的对象”。
在这其中“分步骤”是一个比较稳定的算法,而复杂对象的各个部分则经常变化。
变化点在哪里,封装哪里——Builder模式主要在于应对“复杂对象各个部分”的频繁需求变动。
其缺点在于难以应对“分步骤构建算法”的需求变动。
AbstractFactory模式解决“系列对象”的需求变化,Builder模式解决“对象部分”的需求变化。
Builder模式通常和Composite模式组合使用。
abstractclassHouse{}
publicabstractclassBuilder
{publicabstractvoidBuildDoor();
publicabstractvoidBuildWall();
publicabstractvoidBuildWindows();
publicabstractvoidBuildFloor();
publicabstractvoidBuildHouseCeiling();
publicabstractHouseGetHouse();}
publicclassGameManager
{publicstaticHouseCreateHouse(Builderbuilder)
{builder.BuildDoor();
builder.BuildDoor();
builder.BuildWindows();
builder.BuildWindows();
builder.BuildFloor();
builder.BuildWall();
builder.BuildWall();
builder.BuildHouseCeiling();
returnbuilder.GetHouse();}}
publicclassRomainHouse:
House{}
publicclassRomainHouseBuilder:
Builder
{publicoverridevoidBuildDoor(){}
publicoverridevoidBuildWall(){}
publicoverridevoidBuildWindows(){}
publicoverridevoidBuildFloor(){}
publicoverridevoidBuildHouseCeiling(){}
publicoverrideHouseGetHouse();}
Prototype原型模式(创建型模式)
1、依赖关系的倒置
抽象不应该依赖于实现细节,实现细节应该依赖于抽象。
2、动机
在软件系统中,经常面临着“某些结构复杂的对象”的创建工作:
由于需求的变化,这些对象经常面临着剧烈的变化,但是他们却拥有着比较稳定一致的接口。
如何向“客户程序”隔离出“这些易变对象”,从而使得“依赖这些易变对象的客户程序”不随着需求的改变而改变?
3、意图
使用原型实例指定创建对象的种类,然后通过拷贝这些原型来创建新的对象。
4、要点
Prototype模式同样用于隔离类对象的使用者和具体类型(易变类)之间的耦合关系,它同样要求这些“易变类”拥有“稳定的接口”。
Prototype模式对于“如何创建易变类的实体对象”(创建型模式除了Singleton模式以外,都是用于解决创建易变类的实体对象的问题的)采用“原型克隆”的方法来做,它使得我们可以非常灵活地动态创建“拥有某些稳定接口”的新对象——所需工作仅仅是注册一个新类的对象(即原型),然后在任何需要的地方不断地Clone。
Prototype模式中的Clone方法可以利用.NET中的Object类的MemberwiseClone()方法或者序列化来实现深拷贝。
抽象工厂:
publicabstractclassNormalActor
{publicabstractNormalActorClone()
{
return(NormalActor)this.MemberwiseClone();
}
}
publicabstractclassFlyActor
{publicabstractFlyActorClone()
{
return(FlyActor)this.MemberwiseClone();
}
}
publicabstractclassWaterActor
{publicabstractWaterActorClone()
{
return(WaterActor)this.MemberwiseClone();
}
}
publicclassNormalActorB:
NormalActor{}
publicclassNormalActorB:
NormalActor{}
publicclassFlyActorA:
FlyActor{}
publicclassFlyActorB:
FlyActor{}
publicclassWaterActorA:
WaterActor{}
游戏系统:
publicclassGameSystem
{
publicvoidRun(NormalActornormalActor,FlyActorflyactor,WaterActorwateractor)
{
NormalActornormalActor1=normalActor.Clone();
NormalActornormalActor2=normalActor.Clone();
NormalActornormalActor3=normalActor.Clone();
NormalActornormalActor4=normalActor.Clone();
NormalActornormalActor5=normalActor.Clone();
FlyActorflyactor1=flyactor.Clone();
FlyActorflyactor2=flyactor.Clone();
WaterActorwateractor1=wateractor.Clone();
WaterActorwateractor2=wateractor.Clone();
}
}
将需要new的具体对象用参数传入,这样在GameSystem这个客户程序里面就只依赖于抽象而不依赖于具体了。
具体的NormalActorA、FlyActorA等都不出现在GameSystem中。
客户程序:
staticvoidMain(string[]args)
{
GameSystemgameSystem=newGameSystem();
gameSystem.Run(newNormalActorB(),newFlyActorA(),newWaterActorA());
}
有关创建型模式的讨论
Singleton模式解决的是实体对象个数的问题。
除了Singleton之外,其他创建型模式解决的都是new所带来的耦合关系。
FactoryMethod,AbstractFactory,Builder都需要一个额外的工厂类来负责实例化“易变对象”,而Prototype则是通过原型(一个特殊的工厂类)来克隆“易变对象”。
(其实原型就是一个特殊的工厂类,它只是把工厂和实体对象耦合在一起了)
如果遇到“易变类”,起初的设计通常从FactoryMethod开始,当遇到更多的复杂变化时,再考虑重构为其他三种工厂模式(AbstractFactory,Builder,Prototype)。
一般来说,如果可以使用FactoryMethod,那么一定可以使用Prototype。
但是Prototype的使用情况一般是在类比较容易克隆的条件之上,如果是每个类实现比较简单,都可以只用实现MemberwiseClone,没有引用类型的深拷贝,那么就更适合了。
Prototype如果要实现深拷贝,还需要在每个要克隆的类上加序列化标签,这点复杂度要考虑进程序中。
Adapter适配器模式(结构型模式)
1、适配:
即在不改变原有实现的基础上,将原先不兼容的接口转换为兼容的接口。
2、动机
在软件系统中,由于应用环境的变化,常常需要将“一些现存的对象”放在新的环境中应用,但是新环境要求的接口是这些现存对象所不满足的。
如何应对这种“迁移的变化”?
如何既能利用现有对象的良好实现,同时又能满足新的应用环境所要求的接口?
3、意图
将一个类的接口转换成客户希望的另一个接口。
Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
四、要点
Adapter模式主要应用于“希望复用一些现存的类,但是接口又与复用环境要求不一致的情况”,在遗留代码复用、类库迁移等方面非常有用。
GoF23定义了两种Adapter模式的实现结构:
对象适配器和类适配器。
但类适配器采用“多继承”的实现方式,带来了不良的高耦合,所以一般不推荐使用。
对象适配器采用“对象组合”的方式,更符合松耦合精神。
Adapter模式可以实现的非常灵活,不必拘泥于GoF23中定义的两种结构。
例如,完全可以将Adapter模式中的“现存对象”作为新的接口方法参数,来达到适配的目的。
Adapter模式本身要求我们尽可能地使用“面向接口的编程”风格,这样才能在后期很方便地适配。
//对象适配器(常用)
classMyStack:
IStack
{
ArrayListlist;
publicMyStack()
{
list=newArrayList();
}
publicvoidPush(objectitem)
{
list.Add(item);
}
objectPop()
{
returnlist.RemoveAt(list.Count-1);
}
}
//这种实际上是一种委派的调用,本来是发送请求给MyStack,但是MyStack实际上是委派给list去处理。
MyStack在这里其实就是Adapter(适配对象),list即是Adaptee(被适配的对象),而IStack就是客户期望的接口。
//类适配器(少用)
Bridge桥接模式(结构型模式)
1、问题的出现
假如我们需要开发一个同时支持PC和手机的坦克游戏,游戏在PC和手机上功能都一样,都有同样的类型,面临同样的功能需求变化,比如坦克可能有很多种不同的型号:
T50,T75,T90……
对于其中的坦克设计,我们可能很容易设计出来一个Tank的抽象基类,然后各种不同型号的Tank继承自该类;
另外的变化原因
但是PC和手机上的图形绘制、声效、操作等实现完全不同……因此对于各种型号的坦克,都要提供各种不同平台上的坦克实现:
二、动机
思考上述问题的症结:
事实上由于Tank类型的固有逻辑,使得Tank类型具有了两个变化的维度——一个变化的维度为“平台的变化”,一个变化的维度为“型号的变化”。
如何应对这种“多维度的变化”?
如何利用面向对象技术来使得Tank类型可以轻松地沿着“平台”和“型号”两个方向变化,而不引入额外的复杂度?
三、意图
将抽象部分与实现部分分离,使它们都可以独立地变化。
桥模式不能只是认为是抽象和实现的分离,它其实并不仅限于此。
如下面的例子,两个都是抽象的部分。
更确切的理解,应该是将一个事物中多个维度的变化分离。
4、要点
Bridge模式使用“对象间的组合关系”解耦了抽象和实现之间固有的绑定关系,使得抽象(Tank的型号)和实现(不同的平台)可以沿着格子的维度来变化。
所谓抽象和实现沿着各自维度的变化,即“子类化”它们(比如不同的Tank型号子类,和不同的平台子类),得到各个子类之后,便可以任意组合它们,从而获得不同平台上的不同型号。
Bridge模式有时候类似于多继承方案,但是多继承方案往往违背单一职责原则(即一个类只有一个变化的原因),复用性比较差。
Bridge模式是比多继承方案更好的解决方法。
publicabstractclassTank
{protectedTankPlatformImplementationtankImpl;
publicTank(