前言:记录了总6w字的面经知识点,文章中的知识点若想深入了解,可以点击链接学习。由于文本太多,按类型分开。这一篇是 设计模式 常问问题总结,有帮助的可以收藏。
1. 面向对象设计原则:
1. 单一职责原则
一个类,最好只做一件事,只有一个引起它的变化。
2. 开放封闭原则(开闭原则)
对于扩展是开放的,对于更改是封闭的。
为了满足开闭原则,需要对系统进行抽象化设计,抽象化是开闭原则的关键。
3. 里氏替换原则
子类对象能够替换程序中父类对象出现的任何地方,并且保证原来程序的逻辑行为不变及正确性不被破坏。
里氏代换原则是实现开闭原则的重要方式之一,在运用里氏代换原则时,应该将父类设计为抽象类或者接口,让子类继承父类或实现父接口,并实现在父类中声明的方法,运行时,子类实例替换父类实例,可以很方便的扩展系统的功能,无须修改原有子类的代码,增加新的功能可以通过增加一个新的子类来实现。
4. 依赖倒置原则
抽象不应该依赖于细节,细节应当依赖于抽象。换句话说,要针对接口编程,而不是针对实现编程。
5. 接口隔离原则
使用多个专门的接口,而不使用单一的总接口,即客户端不应该依赖那些他不需要的接口。
6. 合成复用原则
尽量使用对象组合,而不是继承来达到复用的目的。
7. 迪米特法则
一个软件实体应当尽可能少地与其他实体发生相互作用。
2. 工厂模式: (GameManager,UIManager)
定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使其创建过程延迟到子类进行。这满足创建型模式中所要求的“创建与使用相分离”的特点。
使用场景:比如说写技能是一系列类,那么就可以使用工厂模式创建。
游戏场景转换;角色AI状态管理。
3. 观察者模式 (服务器与客户端之间的通信)
观察者模式定义了对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知,并自动更新。
类似于邮件订阅和RSS订阅,当我们浏览一些博客或wiki时,经常会看到RSS图标,就这的意思是,当你订阅了该文章,如果后续有更新,会及时通知你。
多用于角色血条值下降被改变(防御力、攻击力等)
4. 发布-订阅者模式(服务器与客户端之间的通信)
是一种消息范式,消息的发送者(称为发布者)不会将消息直接发送给特定的接收者(称为订阅者)。而是将发布的消息分为不同的类别,无需了解哪些订阅者(如果有的话)可能存在。同样的,订阅者可以表达对一个或多个类别的兴趣,只接收感兴趣的消息,无需了解哪些发布者存在。
观察者模式 与 发布/订阅者模式 的区别区别:
在观察者模式中,观察者是知道Subject的,Subject一直保持对观察者进行记录。然而,在发布订阅模式中,发布者和订阅者不知道对方的存在。它们只有通过消息代理进行通信。
观察者和被观察者,是松耦合的关系。发布订阅模式里,发布者和订阅者,不是松耦合,而是完全解耦的。观察者模式大多数时候是同步的,比如当事件触发,Subject就会去调用观察者的方法。而发布-订阅模式大多数时候是异步的(使用消息队列)。
可以看出,发布订阅模式相比观察者模式多了个事件通道,事件通道作为调度中心,管理事件的订阅和发布工作,彻底隔绝了订阅者和发布者的依赖关系。即订阅者在订阅事件的时候,只关注事件本身,而不关心谁会发布这个事件;发布者在发布事件的时候,只关注事件本身,而不关心谁订阅了这个事件。
观察者模式有两个重要的角色,即目标和观察者。在目标和观察者之间是没有事件通道的。一方面,观察者要想订阅目标事件,由于没有事件通道,因此必须将自己添加到目标(Subject) 中进行管理;另一方面,目标在触发事件的时候,也无法将通知操作(notify) 委托给事件通道,因此只能亲自去通知所有的观察者。
5. 单例模式(UIManager)
在C#应用中,单例对象能保证在一个CLR中,该对象只有一个实例存在。而且自行实例化并向整个系统提供这个实例,避免频繁创建对象,节约内存。
特点:
1.一个类只能有一个实例;
2.自己创建这个实例;
3.整个系统都要使用这个实例。
单例模式的基本构造
- 私有的构造方法(确保全局唯一性)
- 私有的、静态的实例化变量应用
- 提供一个公有的、静态的获取类实例对象方法
单例模式的缺点:
- 不适用于变化的对象,如果同一类型的对象总是要在不同的用例场景发生变化,单例就会引起数据的错误,不能保存彼此的状态。
- 由于单例模式没有抽象层,所以扩展困难,在一定程度上违背了“单一职责”原则。
单例模式的优点:
1、某些类创建比较频繁,对于一些大型的对象,这是一笔很大的系统开销。
2、省去了new操作符,降低了系统内存的使用频率,减轻GC压力。
有些类如交易所的核心交易引擎,控制着交易流程,如果该类可以创建多个的话,系统完全乱了。(比如一个军队出现了多个司令员同时指挥,肯定会乱成一团),所以只有使用单例模式,才能保证核心交易服务器独立控制整个流程。
在游戏中多用于 控制游戏中声音播放;UI窗口显示;资源加载;计时器等。
1. 饿汉模式
饿汉模式就是在类加载的时候立刻会实例化,后续使用就只会出现一份实例。
package thread.example;
//饿汉模式
public class HungrySingle {
//在类加载的时候就实例化了,类加载只有一次,所以值实例化出了一份该实例对象
private static HungrySingle instance = new HungrySingle();
public static HungrySingle getInstance() {
return instance;
}
}
缺点:
1.执行较慢。
2.脚本执行顺序无法被保证问题,调用语句在生成(类加载)之前调用,会报空。
2. 懒汉模式
在类加载的时候没有直接实例化,而是调用指定实例方法的时候再进行实例化,这样就能保证不想使用的时候也不会实例化。一般来说比饿汉模式的效率高。
package thread.example;
//单线程的懒汉模式
public class LazySingle {
private static LazySingle instance = null;
//只有在调用该方法的时候才实例化
public static LazySingle getInstance() {
if(instance == null) {
instance = new LazySingle();
}
return instance;
}
}
问题:多线程,同步调用单例,判断不存在就会同时生成,生成多个实例脱离单例模式 ,可用双向枷锁解决。
3. 饱汉模式
直接new出来,不管需不需要都给你。
6. 代理模式
一个是真正的你要访问的对象(目标类),一个是代理对象,真正对象与代理对象实现同一个接口,先访问代理类再访问真正要访问的对象。
代理模式就是多一个代理类出来,替原对象进行一些操作。
比如我们在租房子的时候回去找中介,为什么呢?因为你对该地区房屋的信息掌握的不够全面,希望找一个更熟悉的人去帮你做,此处的代理就是这个意思。再如我们有的时候打官司,我们需要请律师,因为律师在法律方面有专长,可以替我们进行操作,表达我们的想法。
多用于代理Text, Button, Image等组件的功能;登陆设置;委托;代理下载资源等。
7. 命令模式
命令模式(Command Pattern)是一种数据驱动的设计模式,它属于行为型模式。请求以命令的形式包裹在对象中,并传给调用对象。调用对象寻找可以处理该命令的合适的对象,并把该命令传给相应的对象,该对象执行命令。
命令模式将“请求”封装成对象,以便使用不同的请求、队列或者日志来参数化其他对象,同时支持可撤消的操作。
多用于用户的客户端请求,实现请求的队列操作和日志里,并且支持对操作进行撤销回退。
8. 策略模式
定义一系列算法,并将每个算法封装起来,使他们可以相互替换,且算法的变化不会影响到使用算法的客户。
策略模式的决定权在用户,系统本身提供不同算法的实现,新增或者删除算法,对各种算法做封装。因此,策略模式多用在算法决策系统中,外部用户只需要决定用哪个算法即可。
多用于技能效果类和方法的构建。
9. 组合模式
组合模式(Composite Pattern),又叫部分整体模式,是用于把一组相似的对象当作一个单一的对象。
组合模式依据树形结构来组合对象,用来表示部分以及整体层次。这种类型的设计模式属于结构型模式,它创建了对象组的树形结构。
多用于: 1、您想表示对象的部分-整体层次结构(树形结构)。
2、您希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象。
例如:不同的界面的逻辑和UI通过一个管理器统一控制。
10. MVC
MVC全名是Model View Controller,是模型(Model)-视图(View)-控制器(Controller)的缩写,一种软件设计典范。
用一种业务逻辑、数据、界面显示分离的方法组织代码,将业务逻辑聚集到一个部件里面,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑。
同一个模块内
M只用来操作数据并发送更新消息。
V只用来接受消息并控制界面显示跳转
使用C来处理界面与数据的频繁操作。
一般流程:
- View(界面)触发事件
- Controller (业务)处理了业务触发数据更新
- 更新了Model (数据)
- Model(带着数据)回到了View(界面)
- View更新了数据
总结一下,关于MVC各层之间关系所对应的设计模式
1. View层,单独实现了组合模式。
2. Model层和View层,实现了观察者模式。
3. View层和Controller层,实现了策咯模式。
在实际的商业项目中v和c是并起来的,如果v和c分开的话,如果一个界面有十几个value,就会对应十几个value的controller,文件数量大,找起来也费劲。
MVC优点:
1.耦合性较低,重用性较高,可维护性高,部署快(前端注重显示,后端注重逻辑)。
2.生命周期成本低(MVC使开发和维护用户接口的技术含量降低)。
3.各司其职,互不干涉(编程思路更清晰)。
4.有利于开发中的分工(多人协同开发时,同步并行)
5.有利于组件重用(项目换皮时,功能变化小时,提高开发效率)
缺点:
1.没有明确定义(完全理解MVC模式不是很容易,内部原理比较复杂)。
2.不合适中小型规模程序。
3.增加系统结构和实现的复杂性(频繁操作,对象间的相互跳转,降低运行效率)。
4.视图控制器间连接过于紧密。
5.增加了程序文件的体量(脚本由一变三)。
12. 游戏设计模式——ECS架构
ECS全称Entity-Component-System(实体-组件-系统),是基于组合优于继承的原则的一种模式(将不变的部分使用继承以方便复用, 将多变的部分用组合来方便拓展)。
游戏中的每一个单元(怪物、相机等)都是一个实体,每个实体又是由一个或多个组件构成的,每个组件仅包含该组件需要关注的数据(例如技能组件保存技能伤害、范围),而系统则用来处理这些实体的集合,其只存逻辑,不存状态,类似于一个工具,例如技能系统会根据遍历到的每一个拥有技能组件的实体,根据状态执行技能。
ECS的优势
(1)容易添加新的复杂的实体类型
添加一个新的实体只需要明确实体上有哪些组件,将组件快速整合起来。
(2)容易定义新的实体数据
需要新的实体数据就可以通过定义一个新的组件来进行存储。
(3)更加的高效率
系统只关心对应的组件数据,而不用关心一个对象上的所有数据,例如技能系统只关心技能,移动系统只关心位移数据, 在遍历的时候,内存的命中率比较高,首先可以通过在创建的时 候申请一个连续的内存空间,同时相对于一整个对象,它的内存 跨度相对会小很多,打个比方,就是在一排套房里面找一间又一间的厕所和在一排厕所里面找下一件厕所。
ECS的劣势
(1)数据共享
比如渲染组件需要对象的位置信息、碰撞组件也要对象的位置信息,那么我们怎么解决这里的数据共享问题?
一种解决方法是把位置信息作为组件抽象出来,但是这样带来的问题就是效率会降低,在处理渲染和碰撞的时候都会再去存取一次位置信息。
另一种解决方法是使用引用(比如使用C++中的智能指针)在构建对象的时候分配同一位置对象,然后传递引用给其他组件。
(2)遍历次数
当游戏中数量巨大并且系统数量也增加时,很明显整个算法的复杂度将不低于O(n*m),遍历对象的次数将变得巨大。
比如碰撞检测系统若是两两对象进行逻辑处理那么速度上的损失将是十分巨大的。
这里的一种解决方法是分集合处理。
详细请看:
希望此篇文章可以帮助到更多的同学