设计模式总结

什么是设计模式,为什么要用设计模式?

简单来说,设计模式就是为了规范,为了复用以及出问题修改方便。
举个例子,你去到了一家新公司,软件分为画面和音乐等几个部分。新版本发布,用户说没有声音,此时你不可能把所有的代码全部看完,然后找出问题在哪,这样效率太低了,你只需要查看音乐部分设置的代码,就可以解决问题。那么如何知道音乐部分代码在哪?这就需要用到设计模式,假设我们规定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;
    }
}

新增一个装饰类,继承咖啡类,自身含有一个对咖啡类的引用,可以调用父类的价格方法,然后加上自身额外调料的方法,就是装饰模式了。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值