总结¶
基本上就 - 工厂模式的运用?统一管理对象创建和销毁,明确所有权。 - 单例,双检查锁,饿汉懒汉,callonce保证。优缺点? - 观察者--事件系统 -
创建型模式¶
人话:创建新的类对象时用的,强调创建、构造。
(☆☆☆☆☆)工厂模式(简单工厂模式) 1 To 1¶
为啥打个五星,因为他很简单。。。虽然也没啥优越性就对了
典型例子:创建各种生物
人话就是:switch case创建类的模式,一个工厂一个产品。这其实并不算设计模式 提供一个Factory工厂类,用于决策实例化的对象。核心是单一职责
Factory()
{
CreateFatory(string)
{
switch
case: new Class
}
}
Factory f = new Factory();
Product = f.CreateFactory();
应用场景: 如果无法预知对象确切类别及其依赖关系时, 可使用工厂方法。能在不影响其他代码的情况下扩展产品创建部分代码。
问题:每次添加新的类需要对Factory工厂类进行改动,不符合开闭原则。
1.(☆☆☆)工厂模式(工厂方法模式) N To 1¶
典型例子:创建动物、植物,都提供一个Update函数允许更新。
可以建立多个工厂,每个工厂生产一类产品 工厂方法模式定义了一个创建对象的接口,但由子类决定实例化哪个类。工厂方法将对象的创建延迟到子类。
Factory{ CreateFactory(); }
Factory1{CreateFactory(){switch,case;}}
Factory2{CreateFactory(){switch,case;}}
Factory f = new Factory1();
Product = f.CreateFactory();
应用场景: 如果无法预知对象确切类别及其依赖关系时, 可使用工厂方法。能在不影响其他代码的情况下扩展产品创建部分代码。
问题: 个人评价是,依旧没有完全解决switch case导致的开闭原则问题,只是延迟到子类工厂创建了。只不过相对来说减少了一些耦合的发生,为产品提供了一种分类方式。
2.(☆☆)抽象工厂模式 N To M¶
为什么我给它两星?因为这玩意儿需要在工厂提前声明好统一的方法!!! 典型例子:动物、植物,有呼吸,运动等功能。但是我想给某种动物如鸟加一个飞翔,你就需要给所有动物工厂的所有类提供一个飞行函数!!!这是多余且不必要的,非常不利于拓展逻辑,对于游戏开发来说简直就是大忌...
在设计良好的程序中, 每个类仅负责一件事。 如果一个类与多种类型产品交互, 就可以考虑将工厂方法抽取到独立的工厂类或具备完整功能的抽象工厂类中。
可以建立多个工厂,每个工厂生产多类产品。 个人感觉就是二维复合的工厂方法模式(工厂种类+产品种类),工厂方法模式算是一维复合(工厂种类)。 网上说:单一(确定?)职责,开闭(个锤子)闭原则
Factory{ CreateFactory1(); CreateFactory2();}
Factory1{CreateFactory1(){switch,case;};CreateFactory2(){switch,case;};}
Factory2{CreateFactory1(){switch,case;};CreateFactory2(){switch,case;};}
Factory f = new Factory1();
Product = f.CreateFactory();
3.(☆☆☆☆)生成器模式(建造者模式)【重量级对象构造】¶
生成器模式其实是面向客户的一种创建模式,是对大量参数对象构造的一种优化。
面对生产一个有大量参数需要的对象,正常情况下我们一般有两种方法: - 创建多种构造函数,通过不同的参数来构建参数。但问题就是,参数越多,需要写的构造函数也会爆炸性增长。(虽然现在可以设置默认值来缓解这种情况) - 设置各种类,但是参数类型不同,也会造成大量子类的冗余创建。
因此,生成器模式就提供了一个典型的思路,通过Builder提供的方法设置参数,构造函数也可以通过接受Builder参数便能生成实例。
链式创建¶
为了方便使用,我们可以通过设置返回值为Builder类型,进行链式的创建。
代码¶
其实生成器模式的写法已经相当固定了,像是Dotween之类的代码我们肯定也都使用过。
class House{
private Window window;
private Door door;
private Wall wall;
public House(Builder builder){
this.window = builder.window;
...
}
static class Builder{
private Window window;
private Door door;
private Wall wall;
public Builder SetWindow(Window window)
{
this.window = new Window(window);
return this;
}
....
public House build(){
return new House(this);
}
}
}
House house = new House.Builder().SetDoor(new Door()).setWall.setWindow.build();
应用场景¶
- 使用生成器模式可避免 “重叠构造函数 (telescoping constructor)” 的出现。
- 希望使用代码创建不同形式的产品 (例如石头或木头房屋) 时
- 使用生成器构造组合树或其他复杂对象。
缺点¶
- 不太能对外发布未完成的作品,避免构造出错误对象。
- 只能在创建时使用。
4.(☆☆☆☆)原型模式(拷贝构造)¶
典型应用:Prefab。Prefab很容易理解吧,我们可以拿到一个原型物体,并在这个基础上创建他的副本。
这玩意儿在就很像深拷贝的拷贝构造。
public class Prototype implements Cloneable {
public Object clone() {
Prototype proto = (Prototype) super.clone();
return proto;
}
Prototype newp = (Prototype)p.Clone();
AnyElse other = (AnyElse)p.Clone();
应用场景¶
- 如果你需要复制一些对象, 同时又希望代码独立于这些对象所属的具体类, 可以使用原型模式。
- 如果子类的区别仅在于其对象的初始化方式, 那么你可以使用该模式来减少子类的数量。 别人创建这些子类的目的可能是为了创建特定类型的对象。
- 性能优化:发送大量相似对象时,我们可以先提前创建个原型再去修改。比每次都单独创建对象快很多。
缺点¶
总的来说就是写的不多,用的多。 - 但其实我们程序上很少会写这玩意儿,而是直接选择拷贝构造。毕竟中间的转换过程不安全 - 此外,深拷贝的复杂结构遍历也需要注意
5.(☆☆☆☆/☆☆☆☆☆)单例模式¶
评价为超大杯
这玩意儿是个人都知道吧。但是这玩意儿很容易滥用。单例依赖、内存占用、线程安全等一大堆问题很容易找上你,开发大型项目就容易爆炸。
这个我就不想讲了,网上一大堆,面试啥的都会考。
结构性模式¶
人话:方便组装方法、类用的模式。强调组装、整体
1.(☆☆)适配器模式¶
可以理解为创建一个包装器(Wrapper),去包裹。
适配器模式一般分为: - 类模式:通过继承实现 - 对象模式:通过组合实现。
应用场景¶
这个设计模式的本质就是为了适配原本不适配的接口,特别是在你接入别人的代码接口(第三方代码库)的时候。 所以,应用基本上是在接入第三方库中
客户只能调用的Target的request方法,所以我们需要用我们自己的Target的Request方法,就能实现调用到Adptee的SpecificRequest方法的效果。
组合实现代码:
class Target{
Request()
{
std::cout<<"Target::Request"<<std::endl;
}
}
class Adaptee {
void SpecificRequest()
{
std::cout<<"Adaptee::SpecificRequest"<<std::endl;
}
};
class Adapter:public Target {
Adapter(Adaptee* ade)
{
this->_ade = ade;
};
void Request()
{
_ade->SpecificRequest();
}
Adaptee* _ade;
};
Adaptee* ade = new Adaptee;
Target* adt = new Adapter(ade);
adt->Request();
缺点¶
问题是一个优秀的第三方库接口一般也不会那么难用。或者你非要把他调整成你习惯的模式才会用到这个,总之一般没必要搞这个。
评价为小杯
2.(☆☆☆☆☆)Bridge桥接模式¶
一句话:将m* n 个实现类转换为m+n个实现类。
像之前抽象工厂模式这种,我们写程序时可能因为不同的组合产生多个类,因此用桥接模式就可以减少这种现象。
举例:实现一个武器-人物类¶
public abstract class IWeapon
{
public abstract void Fire(Vector3 targetPosition);
}
//基类实现
public class WeaponGun : IWeapon
{
public override void Fire(Vector3 targetPosition)
{
}
}
public class WeaponRocketLuncher : IWeapon
{
public override void Fire(Vector3 targetPosition)
{
}
}
//--------------人物-------------
public abstract class ICharacter{
protected IWeapon weapon;
public void Attack(Vector3 targetPosition)
{
weapon.Fire();
}
}
public class Player1: ICharacter
{
weapon = new WeaponGun();
}
public class Player2: ICharacter
{
weapon = new WeaponRocketLuncher();
}
//执行与处理
ICharacter player = new Player1();
player.Fire(targetPosition);
3.(☆☆☆☆)Composite组合模式 递归-树状结构¶
一句话:用树状结构的方式来组合对象。
典型例子¶
unity的所有子对象都继承自Monobehaviour,可以执行Monobehaviour的一些统一的方法如Update(),Start()等。
代码¶
public class Employee{
List<Employee> childs;
void add(Employee e) {childs.add(e);}
void remove(Employee e) {childs.remove(e);}
virtual void excute(){childs.excute();}
}
public class GoodEmplyee : Employee
override void excute()
{
print("Good Job!");
}
}
应用场景¶
- 需要树状结构,或者层次结构管理对象,可以用递归的方式去遍历整个结构。
- 希望客户端代码能够以相同的方式去处理简单和复杂的元素。ECS也是基于组合模式进行的演化。
问题¶
对于功能差异比较大的类型,使用公共接口肯定不太妥当。
4.(☆)Decorator装饰器模式¶
动态地给一个对象添加一些额外的功能。
一般需要给一个对象增加新功能: - 继承生成子类 - 装饰器模式
interface IEnemy{
virtual void doSomething();
}
class FirstEnemy implements IEnemy{
virtual void doSomething(){...}
}
class EnemyDecorator implements IEnemy{
private IEnemy enemy;
public EnemyDecorator(IEnemy enemy){
this.enemy = enemy;
}
override void doSomething(){enemy.doSomething()};
//扩展新的逻辑
public void doMoreThing();
}
应用场景¶
- 无需修改代码时使用对象,并增添额外的行为
- 还有继承扩展对象的方式行不通的时候,可以考虑使用装饰器模式。
缺点¶
- ”层层包裹“: 游戏客户端开发我怎么也想不通需要这个模式的时候。。。继承很好用,一般需要扩展功能直接在原有逻辑代码上改就行了。
而且装饰器会让程序员很难定位你的逻辑代码到底变成了啥。这种层层套“洋葱”的方式比继承更难理清逻辑。
只有像那种固定的STL或者引擎源码,你又不能去动他,这种时候再用装饰器模式去扩展。如果是屎山代码,之前的一改就塌倒是可以考虑一下,但是这种时候leader多半都会让你重构这一大坨了。
- “即用即付”:
你看这个装饰器,是不是还得靠继承实现?比如你想给敌人加一个buff,就要一个装饰器。你想给敌人上把武器,你又需要个装饰器。这么多子类相当麻烦,而且调用后他的职责也结束了,堆栈到这里结束了,那么你要取消这些东西怎么办?
你还得再加个装饰器去取消之前的加成,但问题是,如何在混乱的堆栈知道之前调用了什么样的装饰器进行回退?
在大型游戏开发这种一次性行为是非常不看好的,应该尽量避免。
所以游戏开发中我不推荐使用,评价为杯盖。
5.(☆☆☆☆☆)外观模式¶
一句话概括就是:用一个接口,封装其他多个系统的接口。
典型应用: 你可能没注意,其实你写游戏时肯定一直在用外观模式。。。比如在Update()里调用各种类的函数。
//-------------子系统-----------
// 子系统1:角色加载
public class CharacterLoader {
public void LoadPlayer() {
Debug.Log("加载玩家角色");
}
}
// 子系统2:音效播放
public class SoundManager {
public void PlayButtonSound() {
Debug.Log("播放按钮点击音效");
}
}
// 子系统3:UI控制
public class UIController {
public void ShowWelcomeText() {
Debug.Log("显示欢迎语:欢迎来到游戏!");
}
}
// 外观类:游戏启动器
public class GameStarter {
private CharacterLoader _characterLoader = new CharacterLoader();
private SoundManager _soundManager = new SoundManager();
private UIController _uiController = new UIController();
// 核心方法:一键启动游戏
public void StartGame() {
_soundManager.PlayButtonSound();
_characterLoader.LoadPlayer();
_uiController.ShowWelcomeText();
}
}
应用场景¶
外观模式强调的是统一接口 - 如果你需要一个指向复杂子系统的直接接口, 则可以使用外观模式。 - 如果需要将子系统组织为多层结构(比如多个个系统都有添加和删除功能,就可以指定两个添加和删除的外观函数), 可以使用外观。
问题¶
- 不要过度依赖外观,过多的子对象依赖,会使外观可能成为与程序中所有类都耦合的上帝对象。
- 添加新的子系统功能可能不太符合开闭原则,需要在外观类新增代码。
- 不是把方法一股脑塞到一个函数里就叫外观,子系统也请尽量设计单一职责的接口,再通过外观进行封装。
但总之,外观模式超级好用且应用广泛,评测为超大杯。
6.(☆☆☆)享元模式¶
一句话:共享重复使用的细粒度对象,分离内部状态(不变部分)和外部状态(可变部分),减少内存占用。
典型应用:批渲染,大量相同类型的处理,不同怪物的属性和其控制器的分离。
代码实现¶
比如渲染 1000 个相同树模型,每棵树的位置和颜色不同。
// 享元对象:共享的树模型数据(内部状态)
public class TreeModel {
public Mesh Mesh; // 模型网格
public Material Material; // 基础材质
}
// 外部状态:每棵树独立的数据
public class TreeInstance {
public Vector3 Position;
public Color Color;
}
public class Forest : MonoBehaviour {
TreeModel model = new TreeModel();
void Start() {
// 创建 1000 棵树,共享同一个模型数据
for (int i = 0; i < 1000; i++) {
// 创建独立实例(外部状态)
TreeInstance tree = new TreeInstance {
Position = Random.insideUnitSphere * 100f,
Color = Random.ColorHSV()
};
// 渲染(实际项目用GPU Instancing优化)
DrawTree(model, tree);
}
}
}
应用场景:¶
- 仅在程序必须支持大量对象且没有足够的内存容量时使用享元模式。
缺点¶
其实享元模式的思路相当的清晰且简洁,主要就是应用范围太窄了。但是游戏中,对大量物体的内存优化刚好是一个比较能运用该模式的典型案例,因此在游戏开发中缺点没那么明显。
所以一般程序开发中都是小杯,但这是游戏开发,综合评价中杯下。
7.代理模式¶
主要是控制访问,不影响原功能的使用,但是提供一个对原功能改装了的新接口。
典型应用¶
资源加载代理(比如改成延迟加载),网络请求代理,权限验证(增加安全性)。
代码示例¶
//为资源加载增添缓存功能。
public class ResourceProxy {
private GameObject _cachedPrefab;
public GameObject Load(string path) {
if (_cachedPrefab == null) {
Debug.Log("首次加载资源: " + path);
_cachedPrefab = Resources.Load<GameObject>(path);
}
return _cachedPrefab;
}
}
// 使用示例
ResourceProxy proxy = new ResourceProxy();
GameObject enemyPrefab = proxy.Load("Prefabs/Boss"); // 第一次实际加载
GameObject enemyPrefab2 = proxy.Load("Prefabs/Boss"); // 返回缓存
缺点¶
区分装饰器模式¶
- 代理模式:主要为了控制,限制某个功能。
- 装饰器模式:为了添加某个新功能,或者修改原来的功能。
行为模式¶
人话:负责对象之间的行为、职责控制。强调沟通、关系
1.(☆)责任链模式¶
![[Pasted image 20250307011418.png]]
缺点¶
2.(☆☆☆)命令模式¶
应用场景¶
- 封装操作,方便定点,回退,撤销
- 工具类、连招系统
缺陷¶
- 不是所有游戏都需要这个,
因此,鉴定中杯上
3.(☆)迭代器模式¶
缺点¶
迭代器模式应用广泛,但是广泛应用在底层数据结构中。正常的开发过程中很少会需要你去重新设计迭代器,不会要求你设计数据结构的同时还要去写个迭代器。 因此综合评定为:杯盖上。
4.(☆☆☆☆)中介者模式¶
学计算机的应该都学过计网吧,MAC地址就可以直接定位了,为什么需要IP进行路由呢?其中IP就负责了路由器的地位。
应用场景¶
实际上,我们在开发中写的Controller完全可以理解为一个去管理其他类(Component,Object)的关系中介者。
缺点:¶
- 中介者管理方式写不好的话,很容易爆炸
区分¶
-
外观模式:执行其他类的方法,不注重其他类之间的关系(执行多次变为执行一次,n to 1)
-
桥接模式:桥接模式通过抽象类(接口)连接,让一个抽象类的子类和另一个子类进行结合,通过抽象进行连接,不注重子类间的关系。(mxn变为m + n)
- 中介者模式:帮助理清类与类的关系,让子类不再关注和其他类的联系(让网状结构变为星状机构)。
5.(☆☆☆)备忘录模式¶
6.(☆☆☆☆☆)观察者模式¶
7.(☆☆☆☆)状态模式¶
8.(☆☆☆☆☆)策略模式¶
区分¶
策略模式和状态模式极其相似,类图基本上也一样,不过我们需要对其概念有所区分: - 我之前有写过一篇Untiy构建有限状态机模式的文章,我们可以看到针对每种状态我们都有其枚举:Idle、Attack、Move······但是程序关注的只是那个枚举(状态),并不关注枚举对应的类到底实现了什么。并且状态间存在感知,根据条件不同,状态之间还可能会(自动的)的进行转换。
- 而策略模式更像是关注同一个目标,不同的策略都是为了解决同一个问题:比如技能策略,策略只需要知道我要发射什么技能就行了。派生的技能策略类之间并不需要考虑如何相互转换。并且策略一般也是在外部进行显示的选择
状态模式 | 策略模式 | |
---|---|---|
核心目标 | 管理对象内部状态的迁移 | 封装可替换的算法或策略 |
状态/策略关系 | 状态间相互感知并触发转换 | 策略彼此独立无关联 |
生命周期 | 状态可能自动切换(如超时) | 策略由外部显式选择 |
综合评定为:超大杯
9.(☆☆)模版方法模式¶
规范做事顺序。
典型应用¶
Monobehaviour中,对象的生命周期、渲染管线:
应用场景¶
10.( ☆☆)访问者模式¶
11.(☆☆)解释器模式¶
问题¶
跟迭代器一样的问题,都是底层做好了的,不需要我们怎么动。