随着代码写的越来越多,关于设计模式的学习和思考也日益增多。
那么究竟什么是设计模式呢?
有一件事情让我印象非常深刻:
我和一个工作中的小伙伴‘老何’讨论设计模式的一种——命令模式,其含义是:命令模式:将“请求”封装成对象,以便使用不同的请求,队列或者日志来参数化其他对象。
我一直徘徊在代码的边缘,总感觉把握不住这个模式的奥妙。于是我就一直在疑惑:是不是我给想要的类实现统一的接口,并且把每一个要调用的类实例化对象并按顺序排列到队列中,然后通过调用统一接口的方式实现命令模式。
老何听了之后觉得非常的好笑:“你这真是把书读死了,难道我不实现接口就不能实现命令模式吗?命令模式乃至所有的设计模式其实是一种思想,而不是怎么写类,写继承,写封装。”
我突然感觉醍醐灌顶,我太在意如何通过代码实现设计模式,而忘记了设计模式本身的思想。我立马开始反思:“那这样说,我们之前写的lua代码把动作放到函数中,并做成列表去循环调用,是不是也可以算得上命令模式?”
老何嘿嘿一笑:孺子可教也。
那么,这里就来总结一下一些常用的设计模式,这里我只总结使用场景和设计图,明白了原理,代码其实很好写:
1.单例:
定义:确保一个类只有一个实例,并提供一个全局访问点。
一般实现方式:
public sealed class Singleton
{
private static Singleton instance = null;
private Singleton() { }
public static Singleton Instance
{
get
{
if (instance == null)
{
instance = new Singleton();
}
return instance;
}
}
}
更详细的实现方法可以看:C#实现单例模式的几种方法 - xiaohanxixi - 博客园
2.策略模式:
定义:定义了算法族,分别封装起来,让他们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。
UML图:
其中,Context是上下文,用一个ConcreteStrategy来配置,维护一个对Strategy对象的引用;Strategy是策略类,用于定义所有支持算法的公共接口;ConcreteStrategy是具体策略类,封装了具体的算法或行为,继承于Strategy。
使用场景:当一个系统中有很多实现的方式,并且这些方式是可以抽象成一系列算法时,如果不用策略模式来维护那么就要用大量的if else代码来处理。
举例: 比如出行方式,可以是坐公交,也可以是开车,也可以是坐地铁。游戏开发中最直接的例子就是出牌算法:对于棋牌类游戏来说,玩家的牌值都是一样的,但是斗地主和双扣的玩法是完全不一样的,那么把不同的玩法封装成独立的算法,这里用的就是策略模式。再举一个简单的例子,在所有ui界面弹出时,可以是从大到小展示,也可以是从小到大展示,还可以是从上到下展示,这些都可以使用策略模式来优化代码。
设计原则:
1.找出应用中可能需要变化之处,把他们独立出来,不要和那些不需要变化的代码混在一起出现。
2.针对接口编程,而不是针对实现编程。
3.多用组合,少用继承。
3.观察者模式:
定义:定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会受到通知,并自动更新。
UML图:
使用场景:当主题处于一对多的关系时,主题发生修改,所关注的对象根据变化都要做出动作。
举例:观察者模式是mvc,mvvm等设计架构的核心之一,得以让界面和数据代码解耦,让整个游戏代码更加清晰。cocos2dx中的lua实现方式(DataCenter)其实非常棒,用了lua的元表和元方法,代码简洁清晰,其原理是把每个数据都作为一个观察主题,观察者把自己的回调函数加到主题的观察列表中,当数据主题发生改变就依次调用观察列表中的回调函数。其实观察者还有一个特性,就是他拥有牢靠的数据,在其他界面需要数据时,可以向主题索取。后面我会再写一个设计架构的博文,详细讲观察者模式。
设计原则:
4.为了交互对象之间的松耦合而努力。
4.装饰者模式:
定义:动态的将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。
UML图:
使用场景:
- 当需要给一个现有类添加附加职责,而又不能采用生成子类的方法进行扩充时。例如,该类被隐藏或者该类是终极类或者采用继承方式会产生大量的子类。
- 当需要通过对现有的一组基本功能进行排列组合而产生非常多的功能时,采用继承关系很难实现,而采用装饰器模式却很好实现。
- 当对象的功能要求可以动态地添加,也可以再动态地撤销时。
举例:我想了很久在以前的代码中似乎没有遇到过装饰者模式的使用,但在最近接触了我们creator的框架时,发现pbfm预制体管理类在某种程度上来说就是使用的装饰者模式,为获取到的ui节点添加新的职责。
设计原则:
5.对扩展开放,对修改关闭。
5.工厂方法和抽象工厂模式:
定义:
工厂方法模式:定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法让类把实例化推迟到子类。
抽象工厂模式:提供了一个接口,用于创建相关或依赖对象的家族,而不需要明确指出具体类。
UML图:
使用场景:
- 客户只知道创建产品的工厂名,而不知道具体的产品名。如 TCL 电视工厂、海信电视工厂等。
- 创建对象的任务由多个具体子工厂中的某一个完成,而抽象工厂只提供创建产品的接口。
- 客户不关心创建产品的细节,只关心产品的品牌
举例:比如有一个需求,边茶两端生成ui弹窗的底图不一样,一个黑的一个白的,这时候就可以构建ui抽象类拥有createBg函数,边锋工厂实现createBg函数创建黑底,茶苑工厂实现createBg函数创建白底。对于界面来说,不关心如何生成底框,只关心使用的工厂是哪个。还有一个例子就是边茶的月卡,因为历史性的原因,边茶的月卡本就是完全不同的代码,然后单独一个平台的月卡里面还分为贵宾卡,连签卡,普通卡,然后这些还不是同一个人写的,web也不是一个人写的,代码耦合性非常高,接到修改月卡的需求我都头痛,其实如果有时间重构的话,使用抽象工厂去写,定义一些基本函数,比如展示页面,当天签到,一键签到,额外签到等,不同的月卡工厂去生成不同的月卡,代码就会清晰很多。
设计原则:
6.要依赖抽象,不要依赖具体类。
6.命令模式:
定义:将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。这样两者之间通过命令对象进行沟通,这样方便将命令对象进行储存、传递、调用、增加与管理。
UML图:
使用场景:
- 请求调用者需要与请求接收者解耦时,命令模式可以使调用者和接收者不直接交互。
- 系统随机请求命令或经常增加、删除命令时,命令模式可以方便地实现这些功能。
- 当系统需要执行一组操作时,命令模式可以定义宏命令来实现该功能。
- 当系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作时,可以将命令对象存储起来,采用备忘录模式来实现。
举例:比如我在最开始提到的,把动作封装成一个个函数,使用场景就在转盘类游戏,灯光从一个节点亮跳到下一个节点,最开始我使用的是一个个回调互相嵌套的方式,后来发现出bug很难查问题,而且非常不利于修改拓展,后来参考了别的代码,提前把所有跳跃动作封装到一个表中依次调用,代码清晰了很多。还有一个功能,就是进入大厅最开始的弹窗逻辑其实就是命令模式的最佳使用者,策划要求弹窗逻辑是有顺序的,有选择的,有范围的弹,使用命令模式就很好的满足这些要求,把要弹窗的请求封装成一个函数,弹窗逻辑只需要调用函数,函数内部去做ui展示,在实现弹窗逻辑的命令模式后还有一个好处,由于数据请求不是统一返回的,命令模式可以非常方便的在弹窗流程中加入新的弹窗命令。最后就是我最近考虑的一个问题,各个ui之间的弹窗调用和回调,比如说从vip跳到商城,关闭商城之后会再次弹vip,目前的解决方法是在目标ui中加入一个回调,可以由调用者赋值,那么是否也可以用命令模式来解决这个问题呢?
7.适配器模式:
定义:的定义如下:将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。
UML图:
使用场景:
- 以前开发的系统存在满足新系统功能需求的类,但其接口同新系统的接口不一致。
- 使用第三方提供的组件,但组件接口定义和自己要求的接口定义不同。
举例:最直接的例子就是边茶移动大厅的两套代码,由于历史原因很多相同功能的代码却不同,比如刷新金币这个函数,如果不使用适配器模式在后续开发中,同样的功能代码却还要特意修改一下函数名,所以我加了一个adapt类,可以让边茶使用同一套代码。
设计原则:
7.最少知识原则:只和你的密友谈话。
8.模板方法模式:
定义:在每一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
UML图:
使用场景:
- 算法的整体步骤很固定,但其中个别部分易变时,这时候可以使用模板方法模式,将容易变的部分抽象出来,供子类实现。
- 当多个子类存在公共的行为时,可以将其提取出来并集中到一个公共父类中以避免代码重复。首先,要识别现有代码中的不同之处,并且将不同之处分离为新的操作。最后,用一个调用这些新的操作的模板方法来替换这些不同的代码。
- 当需要控制子类的扩展时,模板方法只在特定点调用钩子操作,这样就只允许在这些点进行扩展。
举例:比如移动大厅的玩家中心,就是使用的模版方法模式。公共父类实现了是否展示左侧按钮,按钮点击逻辑,刷新红点,刷新不同模块,初始化展示逻辑,子类只需要实现父类show函数来实现具体功能ui逻辑。
设计原则:
8.好莱坞原则:别找我,我会找你。
9.组合模式:
定义:允许你将对象组成树结构来表现“整体/部分”的层次结构。组合能让客户以一致的方法处理个别对象和对象组合。
UML图:
使用场景:
- 在需要表示一个对象整体与部分的层次结构的场合。
- 要求对用户隐藏组合对象与单个对象的不同,用户可以用统一的接口使用组合结构中的所有对象的场合。
举例:其实cocos所使用的可视化ui界面就是用的组合模式,主场景scene,然后下面会有各类其他组件。组合模式最大的好处就是所有实例都是同源的,他们拥有一些公用的方法,也有一些特有的方法,并且可以互相组合,在处理时不进行区分。
10.状态模式:
定义:对有状态的对象,把复杂的“判断逻辑”提取到不同的状态对象中,允许状态对象在其内部状态发生改变时改变其行为。
UML图:
使用场景:
- 当一个对象的行为取决于它的状态,并且它必须在运行时根据状态改变它的行为时,就可以考虑使用状态模式。
- 一个操作中含有庞大的分支结构,并且这些分支决定于对象的状态时。
举例:老何在分享游戏逻辑代码时曾提到过状态模式的使用,当游戏逻辑处于不同的状态时,对于同一个事件会有不同的处理方式。回到上面月卡的那个例子,如果产运有一个需求,月卡可以相互切换,但有一个一键签到当前页面的按钮一直存在,点击就可以领取当前月卡的所有奖励,此时只需在本地维护一个月卡引用,当切换月卡页面时,保存到月卡引用上,调用一键签到当前页面函数,这是不是就有一点状态模式的味道了。
推荐书籍:《Head First设计模式》
推荐网站:软件设计模式概述
如果觉得讲的还不错,请为我点赞~
如果认为认知有冲突欢迎留言讨论~
非常感谢~