设计模式学习笔记及总结
什么是设计模式,为什么要用设计模式?
简单来说,设计模式就是为了规范,为了复用以及出问题修改方便。
举个例子,你去到了一家新公司,软件分为画面和音乐等几个部分。新版本发布,用户说没有声音,此时你不可能把所有的代码全部看完,然后找出问题在哪,这样效率太低了,你只需要查看音乐部分设置的代码,就可以解决问题。那么如何知道音乐部分代码在哪?这就需要用到设计模式,假设我们规定Music文件夹下的所有文件都是音乐文件,我们只需要去Music文件下找代码的问题即可。
七大设计原则
什么是设计原则, 为什么要使用设计原则?
设计原则就是为了满足符合设计模式的标准。举个简单暴力的例子,我们规定音乐的脚本都要写在Music文件夹下,这就是一个设计原则,方便程序音乐出问题,程序员可以快速定位音乐的问题,而不需要把所有的代码看一遍。
1、开闭原则
开放扩展,修改关闭。
简单来说,禁止修改源代码,如果需要改动,请使用拓展。
举个例子,现在有一个画图工具,如下图所示。
我们简单实现一下上述的UML图,有四个类,Paint类拥有三个成员,调用成员自身的方法可以实现功能。
那么问题来了,此时如果要新增一个图形,那么我们就需要改动Paint,这不符合开闭原则。
优化:
我们提取一个公共的Shape类,三个子类分别继承它,实现draw方法,Paint有一个shape类的对象,需要画什么,只需要调用draw方法就行。
此时新增一个图形,我们只需要一个新类继承Shape,实现Draw方法即可。
2、依赖倒置原则(也可称作依赖翻转)
简单理解:如下图所示
Paint依赖于Cycle,Rectangle,Trangle自身不同的draw方法,每当想新增一个图形的时候,都依赖新增的类,这样,当我们每次改动都会很烦,都需要改动源代码,不符合开闭原则。
优化:
还是提取出一个共同的基类,子类继承基类,实现Draw方法,Paint拥有基类的对象,调用Draw方法就行。具体原理需要理解继承、虚方法的使用语法,此处不过多赘述。
此时,依赖倒置,Paint不在依赖于子类。所以称之为依赖倒置或者依赖翻转原则。
3、里式替换原则
1、里氏替换原则通俗的来讲就是:子类可以扩展父类的功能,但不能改变父类原有的功能。
2、里氏代换原则告诉我们,在软件中将一个基类对象替换成它的子类对象,程序将不会产生任何错误和异常,反过来则不成立,如果一个软件实体使用的是一个子类对象的话,那么它不一定能够使用基类对象。
3、里氏代换原则是实现开闭原则的重要方式之一,由于使用基类对象的地方都可以使用子类对象,因此在程序中尽量使用基类类型来对对象进行定义,而在运行时再确定其子类类型,用子类对象来替换父类对象。
总结:声明父类对象,调用任何一个子类程序都可以正常跑通,不会出问题,即子类不能重写父类的方法,不然调用会出错。
1、子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。
2、子类中可以增加自己特有的方法。
3、当子类的方法重载父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松。
4、当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格。
4、单一职责原则
顾名思义,一个类制作相关方面的事情,降低耦合,例如,音乐管理器类就只管音乐,不管画面。这个比较容易理解,不再过多赘述。
5、接口隔离原则
类似单一职责原则。一个接口不能太臃肿,因为继承的类必须全部实现。
举个例子,现在有两个动物,老虎和鸟,他们都会吃,老虎会跑,鸟会飞。
抽取一个基类动物,有吃的方法,再整一个Run和Fly的接口。
Run和Fly方法不能写在同一个类,不然就会显得臃肿,老虎不会fly,鸟也不会run,不需要实现。
6、合成复用原则(少用继承多用复合组合)
参考
参考上面,基类动物不能有Run和Fly,不然老虎或者鸟会有冗余的方法。
另外情况就是C继承于B,B继承于A,假设C出问题了,你不仅要看C类自身,如果问题不在C,就需要看B,不在B还需要看A,一层一层看会影响效率,所以尽量不要多重继承,建议搭配接口使用。
我在工作中遇到过一个问题,绘图,基类是圆,椭圆继承圆,下面还有一个圆继承椭圆,当时我排查问题的时候就是一层一层向上看,效率低还烦心。
7、迪米特法则(直接朋友法则/最少知道原则)
直接朋友:B是A的成员变量,B是A中方法的入参,B是A中方法的返回值类型
以上三种情况称为B是A的直接朋友,其它的为陌生类
迪米特法则:陌生类最好不要以局部变量的形式出现在类的内部;以降低类与类之间的耦合
对于被依赖的类不管有多复杂,都封装在类的内部,对外只提供public方法,不要泄露任何信息;
如果所示:
Form数据从Dao中拿,但是耦合度比较高,提取出来一个Controller。
状态模式
如上图所示,我们要实现一组逻辑,即开始页面,游戏主界面,战斗场景的交互。
这三个页面,都有进入界面,退出界面要做的事情,例如向服务器请求数据,加载UI,但是我们总不能三个页面都分别写上同样的方法,这样过于分散,因此我们选择使用状态模式,即将这三个界面看成三个状态,提取一个他们的父类,父类声明方法,子类实现,Context拿到一个他们父类的对象,由执行端将子类交给Context,Context只需调用对象的方法即可,而不需要关心这个对象是哪一个子类。
简易实现:
public class DM01Sate : MonoBehaviour
{
void Start()
{
Context context = new Context();
context.SetState(new ConcreteStateA(context));
context.Handle(5);
context.Handle(20);
context.Handle(30);
context.Handle(4);
context.Handle(6);
}
}
public class Context
{
private IState mState;
public void SetState(IState state)
{
mState = state;
}
public void Handle(int arg)
{
mState.Handle(arg);
}
}
public interface IState
{
void Handle(int arg);
}
public class ConcreteStateA : IState
{
private Context mContext;
public ConcreteStateA(Context context)
{
mContext = context;
}
public void Handle(int arg)
{
Debug.Log("ConcreateStateA.Handle " + arg);
if (arg > 10)
{
mContext.SetState(new ConcreteStateB(mContext));
}
}
}
public class ConcreteStateB : IState
{
private Context mContext;
public ConcreteStateB(Context context)
{
mContext = context;
}
public void Handle(int arg)
{
Debug.Log("ConcreateStateB.Handle " + arg);
if (arg <= 10)
{
mContext.SetState(new ConcreteStateA(mContext));
}
}
}
ps:我曾在某血汗工厂做相关视频软件,不同页面的切换就是使用如上所示的状态模式,子类重写父类中声明的OnEnterView,OnExitView等方法。
外观模式
外观模式是为了实现单一指责原则与迪米特法则。
如果所示,这里三位大侠不仅要会打架,还要会泡茶,精力不够用。
在软件中也是一样,比如当前的界面需要加载音乐,或者加载一张图片,最简单的方法是分别写上一个方法,但问题是,其他界面可能也要加载音乐,也要写方法,这样不能复用,维护起来每个都要改很麻烦,这时候我们就像大侠一样需要一个店小二——音乐管理系统与图片加载系统。
单例模式
一个非常常用的类单独提取出来,不作为任何类的成员,而是作为静态类单独提取出来使用。例如XX系统,提供一个静态类成员,调用该成员然后调用方法。很常用的模式,此处不再过多赘述。
中介者模式
如图所示,上图同学之间要告知一件事情,需要每个人都说一遍,非常的麻烦,在程序中就是一个类需要其他类的应用,错综复杂。因此,我们可以使用中介者模式,让一个中介同时拥有他们,然后其他类调用这个中介,从而使用其他类的功能。
类似QQ群的功能,所有人都在这个群里,发的消息由群统一告知各位。
桥接模式
实现与抽象分离。
有点类似于外观模式,外观模式是一对多,多的那个提取出来一个管理类。
桥接模式负责的是两边的对接,即多对多,举个例子,我们要画一个圆。
分为圆的数据(半径等数据)、渲染引擎(DX,OpenGl等)俩部分。
圆类有数据,引擎里有渲染的方法,将数据传入给引擎,便可实现画图。
因此,我们可以让圆拥有一个引擎类的成员,调用引擎的绘制方法,传入数据。
现在,特殊情况1,我可能一开始用的是DX引擎,后续我改用了OpenGL,那么,圆类里就应该新增一个OpenGl类的成员,然后调用OpenGl的绘制方法。这样,换引擎,我们就需要多写一个方法,属于改动了底层,不符合代码规范。因此,我们可以将引擎提取出来一个基类。基类拥有一个绘制方法,子类继承,然后实现,交给圆调用。
特殊情况2,我现在想绘制一个方形,那么我需要一个方形类,然后这个类有数据,和上面的圆类一样,他也有引擎类的成员,但是这样写就重复了,因此,我们将需要绘制的图形也提取出来一个基类。图形基类拥有引擎基类的成员,至于实现,就全部交给子类来实现、调用。
public class IShape
{
public string name;
public IRenderEngine renderEngine;
public IShape(IRenderEngine renderEngine)
{
this.renderEngine = renderEngine;
}
public void Draw()
{
renderEngine.Render(name);
}
}
public abstract class IRenderEngine
{
public abstract void Render(string name);
}
public class Sphere:IShape
{
public Sphere(IRenderEngine re):base(re)
{
name = "Sphere";
}
}
public class Cube:IShape
{
public Cube(IRenderEngine re):base(re)
{
name = "Cube";
}
}
public class Capsule:IShape
{
public Capsule(IRenderEngine re):base(re)
{
name = "Capsule";
}
}
public class OpenGL:IRenderEngine
{
public override void Render(string name)
{
Debug.Log("OpenGL绘制出来了:" + name);
}
}
public class DirectX:IRenderEngine
{
public override void Render(string name)
{
Debug.Log("DrectX绘制出来了:" + name);
}
}
public class SuperRender : IRenderEngine
{
public override void Render(string name)
{
Debug.Log("SuperRender绘制出来了:" + name);
}
}
策略模式
参考上述依赖翻转原则。调用类拥有一个接口,有接口的继承类实现接口的方法,然后调用。
举个例子,现在有一个老色批卖水果,看到美女打1折,其他人原价。
那么我们就要写俩种方法去判断,现在,如果这个色批脑子不正常了,好看一点的9折,更好看一点的8折,以此类推,那么我们是不是要写很多方法,很麻烦,不复用,此时,我们就可以使用策略模式。将美女提取出一个基类,有一个 买水果的方法,不同的美女继承她。
public class DM03Strategy:MonoBehaviour
{
void Start()
{
StrategyContext context = new StrategyContext();
context.stragegy = new ConcreteStrategyB();
context.Cal();
}
}
public class StrategyContext
{
public IStrategy stragegy;
public void Cal()
{
stragegy.Cal();
}
}
public interface IStrategy
{
void Cal();
}
public class ConcreteStrategyA : IStrategy
{
public void Cal()
{
Debug.Log("使用A策略计算");
}
}
public class ConcreteStrategyB : IStrategy
{
public void Cal()
{
Debug.Log("使用B策略计算");
}
}
模板方法模式
一系列同样要做的事情,但是部分细节会有差异。
举个例子,张三是运动狂魔,每天做的事情就是吃饭,运动,睡觉。
李四是个宅男,每天做的事情就是吃饭,打游戏,睡觉。
我们发现,他俩除了一个运动,一个打游戏,其他行为一模一样。此时我们该如何提取他们的一个基类?
此时我们可以用模板方法模式,父类中的方法定义为虚方法,子类重写,说白了这个模式就是编程语法中虚方法的使用。
public class DM04TempleMethod:MonoBehaviour
{
void Start()
{
IPeople people = new NorthPeople();
people.Eat();
}
}
public abstract class IPeople
{
public void Eat()
{
OrderFoods();
EatSomething();
PayBill();
}
private void OrderFoods()
{
Debug.Log("点单");
}
protected virtual void EatSomething()
{
}
private void PayBill()
{
Debug.Log("买单");
}
}
public class NorthPeople : IPeople
{
protected override void EatSomething()
{
Debug.Log("我在吃面条");
}
}
public class SouthPeople : IPeople
{
protected override void EatSomething()
{
Debug.Log("我在吃米饭");
}
}
工厂模式
工厂模式分为简单工厂,工厂方法,抽象工厂
简单工厂
场景模拟:
我们去买车,身为客户,不需要管车是怎么做的,付钱就可以。但是工厂那边需要知道我们需要什么车,根据我们的选择生产对应的型号。假设现在有两种车型,A和B,我买A,工厂那边收到消息,生产了一辆A车给我们送过来。我买B,工厂那边生产一辆B车送过来。简而言之就是一个类中有两个方法,返回值是A和B,方法内部负责安装车轮。。。等等,最后给我们一辆车。这就是简单工厂。
工厂方法
现在有一种新车型出现了,C车,那么工厂里就要新增一个方法制造C车,还需要判断用户是否要C车,else if(…),这不符合我们之前提到的开闭原则,改动了源码。而且每新增一种车型,就要改动一次,非常麻烦。此时,我们可以将车提取出一个基类,基类包含一个制造的方法,子类继承实现,由子类工厂去给客户生产车,每个子类对应一种车型。此处可以参考开闭原则的例子。
抽象工厂
抽象工厂是在工厂方法上进一步衍生,生产一系列的产品。例如资源加载,unity 有AB加载的方法,Resource加载,可能要加载gameobject,Audio,或者Sprite,他们分别有不同的实现方法。
建造者模式
建造者模式:是将一个复杂的对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
通常,我们会在构造函数中对类成员的一些数据进行初始化,但是如果这个类比较复杂,我们总不能将所有初始化都写在构造方法里,这样太臃肿了。
举个例子,我们现在要制造一个机器人,机器人的手,腿,脚都需要拼接,可以看成是一些单独的类,然后作为机器人的成员变量。
机器人内部可以有一些拼接的方法,分别拼接手,腿,脚。不同的机器人有不同的实现方法。
即,不同的机器人中提取一个基类,基类中包含拼接的方法。然后一个机器人对应一个建造工厂,工厂也提取出一个基类,基类包含一个机器人成员。最后是一个研究员,研究员里有调用建造工厂的方法,代码如下:
public class DM05Builder:MonoBehaviour
{
void Start()
{
IBuilder fatBuilder = new FatPersonBuilder();
IBuilder thinBuilder = new ThinPersonBuilder();
Person fatPerson = Director.Construct(fatBuilder);
fatPerson.Show();
}
}
class Person
{
List<string> parts = new List<string>();
public void AddPart(string part)
{
parts.Add(part);
}
public void Show()
{
foreach (string part in parts)
{
Debug.Log(part);
}
}
}
class FatPerson : Person { }
class ThinPerson : Person { }
interface IBuilder
{
void AddHead();
void AddBody();
void AddHand();
void AddFeet();
Person GetResult();
}
class FatPersonBuilder : IBuilder
{
private Person person;
public FatPersonBuilder()
{
person = new FatPerson();
}
public void AddHead()
{
person.AddPart("胖人头");
}
public void AddBody()
{
person.AddPart("胖人身体");
}
public void AddHand()
{
person.AddPart("胖人手");
}
public void AddFeet()
{
person.AddPart("胖人脚");
}
public Person GetResult()
{
return person;
}
}
class ThinPersonBuilder : IBuilder
{
private Person person;
public ThinPersonBuilder()
{
person = new ThinPerson();
}
public void AddHead()
{
person.AddPart("瘦人头");
}
public void AddBody()
{
person.AddPart("瘦人身体");
}
public void AddHand()
{
person.AddPart("瘦人手");
}
public void AddFeet()
{
person.AddPart("瘦人脚");
}
public Person GetResult()
{
return person;
}
}
class Director
{
public static Person Construct(IBuilder builder)
{
builder.AddBody();
builder.AddFeet();
builder.AddHand();
builder.AddHead();
return builder.GetResult();
}
}
UML图:
享元模式
将多个类都有,并且重复的、不会改变的基础信息单独提取出来,交给一个单独的类(工厂)处理,需要用的时候,从工厂里拿,节省内存。
例如,生产机器人的工厂,假设机器人的高都是一样的,那么就没必要每个机器人都有这个高数据,将这个高数据保存在工厂即可。
参考,常量,即经常使用并且不变的数据单独提取出来使用。
组合模式
顾名思义,就是将树在代码中模拟出来。树有树根(根节点),树枝(子节点,树枝上可以有树枝可以叶子),叶子(不能在分化的属性)。
类似于电脑的目录空间,文件夹与文件搭配使用。
public abstract class DMComponent
{
protected string mName;
public string name { get { return mName; } }
public DMComponent(string name)
{
mName = name;
mChildren = new List<DMComponent>();
}
protected List<DMComponent> mChildren;
public List<DMComponent> children { get { return mChildren; } }
public abstract void AddChild(DMComponent c);
public abstract void RemoveChild(DMComponent c);
public abstract DMComponent GetChild(int index);
}
public class DMLeaf:DMComponent
{
public DMLeaf(string name) : base(name) { }
public override void AddChild(DMComponent c)
{
return;
}
public override void RemoveChild(DMComponent c)
{
return;
}
public override DMComponent GetChild(int index)
{
return null;
}
}
public class DMComposite:DMComponent
{
public DMComposite(string name) : base(name) { }
public override void AddChild(DMComponent c)
{
mChildren.Add(c);
}
public override void RemoveChild(DMComponent c)
{
mChildren.Remove(c);
}
public override DMComponent GetChild(int index)
{
return mChildren[index];
}
}
命令模式
将一个请求封装为一个对象(即我们创建的Command对象),从而使你可用不同的请求对客户进行参数化; 对请求排队或记录请求日志,以及支持可撤销的操作。
有点像外观模式,但是层级要比外观模式多一点。
举个例子,你是公司老总(Invoker),你的公司现在缺了一个开发人员,那肯定不是你来直接招人,而是告诉HR部门(Command),HR部门再告诉手底下的小喽啰(receiver)招人。
Command有一个基类的接口,Invoker有该接口类型的List(老总手下有一群各部门的首领),首领(ICommand的子类)手下有相对应的员工(具体的对象,执行者)
public class DM07Command:MonoBehaviour
{
void Start()
{
DMInvoker invoker = new DMInvoker();
ConcreteCommand1 cmd1 = new ConcreteCommand1(new DMReceiver1());
invoker.AddCommand(cmd1);
invoker.AddCommand(cmd1);
invoker.ExecuteCommand();
}
}
public class DMInvoker
{
private List<DMICommand> commands = new List<DMICommand>();
public void AddCommand(DMICommand cmd)
{
commands.Add(cmd);
}
public void ExecuteCommand()
{
foreach (DMICommand cmd in commands)
{
cmd.Execute();
}
commands.Clear();
}
}
public abstract class DMICommand{
public abstract void Execute();
}
public class ConcreteCommand1 : DMICommand
{
DMReceiver1 receiver1;
public ConcreteCommand1(DMReceiver1 receiver1)
{
this.receiver1 = receiver1;
}
public override void Execute()
{
receiver1.Action("Command1");
}
}
public class DMReceiver1
{
public void Action(string cmd)
{
Debug.Log("Receiver1执行了命令" + cmd);
}
}
责任链模式
责任链模式是对象的行为模式。使多个对象都有机会处理请求,从而避免请求的发送者和接受者直接的耦合关系。将这些对象连成一条链,沿着这条链传递该请求,直到有一个对象处理它为止。责任链模式强调的是每一个对象及其对下家的引用来组成一条链,利用这种方式将发送者和接收者解耦。
有点像工厂模式,但是这个模式用户本身不持有工厂的对象。
场景模拟,我们打电话询问客服A,问题不在客服A的工作范围内,此时将我们转接给客服B,客服B也不会,再转借给客服C。日常情况下我们只需要将问题描述一边,客服之间的转接会把我们的描述一同交接,责任链模式要实现的就是这个效果。
如图所示,吐下的情况我们要交接三遍。因此做些更改。
而是应该改为如下
public class DM08ChainOfResponsibility:MonoBehaviour
{
void Start()
{
char problem = 'c';
DMHandlerA handlerA = new DMHandlerA();
DMHandlerB handlerB = new DMHandlerB();
DMHandlerC handlerC = new DMHandlerC();
handlerA.SetNextHandler(handlerB)
.SetNextHandler(handlerC);
handlerA.Handle(problem);
}
}
public abstract class IDMHandler
{
protected IDMHandler mNextHandler = null;
public IDMHandler nextHandler
{
set { mNextHandler = value; }
}
public IDMHandler SetNextHandler( IDMHandler handler )
{
mNextHandler = handler;
return mNextHandler;
}
public virtual void Handle(char problem) { }
}
class DMHandlerA:IDMHandler
{
public override void Handle(char problem)
{
if(problem=='a')
Debug.Log("处理完了A问题");
else
{
if (mNextHandler != null)
{
mNextHandler.Handle(problem);
}
}
}
}
class DMHandlerB : IDMHandler
{
public override void Handle(char problem)
{
if (problem == 'b')
Debug.Log("处理完了B问题");
else
{
if (mNextHandler != null)
{
mNextHandler.Handle(problem);
}
}
}
}
class DMHandlerC : IDMHandler
{
public override void Handle(char problem)
{
if (problem == 'c')
Debug.Log("处理完了C问题");
else
{
if (mNextHandler != null)
{
mNextHandler.Handle(problem);
}
}
}
}
相比工厂模式,客户不持有对象,而是员工与员工之间持有对象。
提取出一个基类,基类包含自身的一个对象(下一个责任链处理人),以及处理的方法,此处还可以使用链式编程进行优化。
适用场景:游戏中的关卡系统。
观察者模式
即一个人观察另一个人,例如警察观察小偷,小偷如果逃跑,警察应该追上去,家长观察孩子,孩子放学家长应该去接孩子放学。
代码实现,即被观察者有观察者的成员对象,当被观察者某些参数改变的时候,调用观察者的某些方法。
public class DM09Observer:MonoBehaviour
{
void Start()
{
ConcreteSubject1 sub1 = new ConcreteSubject1();
ConcreteObserver1 ob1 = new ConcreteObserver1(sub1);
sub1.RegisterObserver(ob1);
ConcreteObserver2 ob2 = new ConcreteObserver2(sub1);
sub1.RegisterObserver(ob2);
sub1.subjectState = "温度 90";
}
}
public abstract class Subject
{
List<Observer> mObservers = new List<Observer>();
public void RegisterObserver( Observer ob )
{
mObservers.Add(ob);
}
public void RemoveObserver(Observer ob)
{
mObservers.Remove(ob);
}
public void NotifyObserver()
{
foreach (Observer ob in mObservers)
{
ob.Update();
}
}
}
public class ConcreteSubject1 : Subject
{
private string mSubjectState;
public string subjectState
{
set {
mSubjectState = value;
NotifyObserver();
}
get { return mSubjectState; }
}
}
public abstract class Observer
{
public abstract void Update();
}
public class ConcreteObserver1 : Observer
{
public ConcreteSubject1 mSub;
public ConcreteObserver1(ConcreteSubject1 sub)
{
mSub = sub;
}
public override void Update()
{
Debug.Log("Oberser1更新显示" + mSub.subjectState);
}
}
public class ConcreteObserver2 : Observer
{
public ConcreteSubject1 mSub;
public ConcreteObserver2(ConcreteSubject1 sub)
{
mSub = sub;
}
public override void Update()
{
Debug.Log("Oberser2更新显示" + mSub.subjectState);
}
}
此处代码实现用的是方法,实际上使用事件系统更简单。被观察者有一个事件,观察者可以将自身对应的事件注册进去。
备忘录模式
简单理解,将数据类单独拷贝一份,用于初始化或者恢复设置。
public class DM10Memento:MonoBehaviour
{
void Start()
{
CareTaker careTaker = new CareTaker();
Originator originator = new Originator();
originator.SetState("state1");
originator.ShowState();
careTaker.AddMemento("v1.0", originator.CreateMemento());
originator.SetState("state2");
originator.ShowState();
careTaker.AddMemento("v2.0", originator.CreateMemento());
originator.SetState("state3");
originator.ShowState();
careTaker.AddMemento("v3.0", originator.CreateMemento());
originator.SetMemento(careTaker.GetMemento("v2.0"));
originator.ShowState();
originator.SetMemento(careTaker.GetMemento("v1.0"));
originator.ShowState();
}
}
class Originator
{
private string mState;
public void SetState(string state)
{
mState = state;
}
public void ShowState()
{
Debug.Log("Originator state :" + mState);
}
public Memento CreateMemento()
{
Memento memento = new Memento();
memento.SetState(mState);
return memento;
}
public void SetMemento(Memento memento)
{
SetState(memento.GetState());
}
}
class Memento
{
private string mState;
public void SetState(string state)
{
mState = state;
}
public string GetState()
{
return mState;
}
}
class CareTaker
{
Dictionary<string, Memento> mMementoDict = new Dictionary<string, Memento>();
public void AddMemento(string version, Memento memento)
{
mMementoDict.Add(version, memento);
}
public Memento GetMemento(string version)
{
if (mMementoDict.ContainsKey(version) == false)
{
Debug.LogError("快照字典中不包含key:" + version); return null;
}
return mMementoDict[version];
}
}
个人工作中没有用过这个模式,恢复初始化是单独的写一个方法,将默认值传过去,这个案例用用的是string类型,实战中要小心引用类型,可能存过去的是内存地址,起不到备忘录的效果。(ps,曾经用过深拷贝和浅拷贝,踩过一些坑)
访问者模式
通常一个类写好之后是对修改关闭,拓展开放的。但是不可能考虑到所有的情况。
举个例子,图形类A,有宽和高,此时我们有求周长的方法,但是因为考虑不周,没有考虑到求面积的方法。此时需要加一个求面积的方法,那该怎么办呢?一种是直接获取宽高然后相乘。另一种就是使用访问者模式。
即新增一个类B,B中有一个求积方法,方法有一个参数为A。A中有个方法,是调用B的求积方法,然后将自身传给B。此时我们调用B的方法就可以拿到A的对象。实现了在外部拓展。
public class DM11Visitor:MonoBehaviour
{
void Start()
{
A a = new A();
B b = new B();
Debug.Log(b.GetArea(a));
}
}
class A
{
public int width = 1;
public int height = 2;
public int GetToatl()
{
return width + height;
}
public int RunVistor(B b)
{
return b.GetArea(this);
}
}
/// <summary>
/// 访问者类
/// </summary>
class B
{
public int GetArea(A a)
{
return a.width * a.height;
}
}
复杂一点的UML图
适配器模式
想象一下日常中使用的充电器转接头或者耳机线的转接头(通常是苹果),即在不同的标准之间搭接一个桥梁。类似于变压器,增大或者减小电压,适配电器。
public class DM12Adapter:MonoBehaviour
{
void Start()
{
Adapter adapter = new Adapter(new NewPlugin());
StandardInterface si = adapter;
si.Request();
}
}
interface StandardInterface
{
void Request();
}
class StandardImplementA : StandardInterface
{
public void Request()
{
Debug.Log("使用标准方法实现请求");
}
}
class Adapter:StandardInterface
{
private NewPlugin plugin;
public Adapter(NewPlugin p)
{
plugin = p;
}
public void Request()
{
plugin.SpecificRequest();
}
}
class NewPlugin
{
public void SpecificRequest()
{
Debug.Log("使用特殊的插件方法实现请求");
}
}
上述例子中,新增了一个适配器类继承原来的接口,适配器类中包含对插件的引用,继承的方法调用这歌插件的方法。
上述情况在游戏中可以使用在宠物系统或者坐骑系统等,即将怪物抓住,作为自己的坐骑或者宠物。新建一个类拥有它们的引用,然后调用它们的方法。
代理模式
为其他对象提供一种代理以控制对这个对象的访问。
有点像上述的适配器模式。
例如资源加载,有的时候,我们需要将已经加载过的资源缓存起来,提高游戏的运行效率,但是实际运行的时候,我们需要看资源消耗,可能会发现没有影响太多的效率,反而增大了内存的消耗,这个时候我们又需要将代码改回去,很麻烦,因此,我们新建一个类,拥有对原来资源加载类的引用,然后再写上一个缓存的字典,对比消耗,就不需要改动原来的资源加载类。测试可以通过这个代理类来测试。
装饰模式
日常生活中,我们买奶茶,买咖啡,是可以加额外的料的,比如珍珠,椰果,那么我们如何确定价格呢?是在一开始设计的时候就写上一些bool值,判断用户是否加料,但是万一有一些没考虑到,或者后出的新品种,此时我们就要改动源码,这显然是不行的,因此,我们要使用装饰模式,代码如下。
public class DM13Decorator:MonoBehaviour
{
void Start()
{
Coffee coffee = new Espresso();
coffee = coffee.AddDecorator(new Mocha());
coffee = coffee.AddDecorator(new Mocha());
coffee = coffee.AddDecorator(new Whip());
Debug.Log(coffee.Cost());
Debug.Log(coffee.Capacity());
}
}
public abstract class Coffee
{
public abstract double Cost();
public abstract double Capacity();
public Coffee AddDecorator(Decorator decorator)
{
decorator.coffee = this;
return decorator;
}
}
public class Espresso : Coffee
{
public override double Cost()
{
return 2.5;
}
public override double Capacity()
{
return 10;
}
}
public class Decaf : Coffee
{
public override double Cost()
{
return 2;
}
public override double Capacity()
{
return 10;
}
}
public class Decorator:Coffee
{
protected Coffee mCoffee;
public Coffee coffee
{
set { mCoffee = value; }
}
public override double Cost()
{
return mCoffee.Cost();
}
public override double Capacity()
{
return 10;
}
}
public class Mocha : Decorator
{
public override double Cost()
{
return mCoffee.Cost() + 0.1;
}
}
public class Whip : Decorator
{
public override double Cost()
{
return mCoffee.Cost() + 0.5;
}
}
新增一个装饰类,继承咖啡类,自身含有一个对咖啡类的引用,可以调用父类的价格方法,然后加上自身额外调料的方法,就是装饰模式了。