架构 设计模式 设计模式 ZEROKO14 2024-07-16 2025-05-23 详解23种设计模式
在了解设计模式之前首先应该先立靶 ,即何为优秀的设计
面向对象 对象 : 属性(数据) + 方法(操作) + 对象ID
封装 : 隐藏对象的属性和实现细节,仅对外公开接口(信息隐藏技术)
类(实体类/控制类/边界类)
实体类 的对象表示现实世界中真实的实体 ,如人、物等。
接口类(边界类) 的对象为用户提供一种与系统合作交互的方式 ,分为人和系统两大类,其 中人的接口可以是显示屏、窗口、Web窗体、对话框、菜单、列表框、其他显示控制、条 形码、二维码或者用户与系统交互的其他方法。系統接口涉及到把数据发送到其他系统,或 者从其他系统接收数据。
控制类 的对象用来控制活动流,充当协调者 。
接口 : 一种特殊的类,他只有方法定义而没有实现
在面向对象的系统中,对象是运行时实体,其组成部分不包括(A);一个类定义了一组大体相似的对象,这些对象共享(D) A. 消息 B. 行为(操作) C. 对象名 D. 状态 A. 属性和状态 B. 对象名和状态 C. 行为和多重度 D. 属性和行为 ✅:最合适
在面向对象方法中,将逻辑上相关的数据以及行为绑定在一起,使信息对使用者隐蔽称为(C)。当类中的属性或方法被设计为private时,(B)可以对其进行访问。 A. 抽象 B. 继承 C. 封装 D. 多态 A. 应用程序中所有方法 B. 只有此类中定义的方法 C. 只有此类中定义的public方法 D. 同一个包中的类中定义的方法
在某销售系统中,客户采用扫描二维码进行支付。若采用面向对象方法开 发该销售系统,则客户类属于(B)类,二维码类属于(A)类。 A. 接口 B. 实体 C. 控制 D. 状态 A. 接口 B. 实体 C. 控制 D. 状态
继承与泛化 本质是一种复用机制
泛化: 找出共通点,抽象出父类
继承 是子类 =继承(特殊化)=> 父类
泛化 是子类 =泛化出=> 父类
在面向对象方法中,(B)是父类和子类之间共享数据和方法的机制。子类在原有父类接口的基础上,用适合于自己要求的实现去置换父类中的相应实现称为(C)。 A. 封装 B. 继承 C. 覆盖 D. 多态 A. 封裝 B. 继承 C. 覆盖 D. 多态
在面向对象方法中,继承用于(A)。 A. 在已存在的类的基础上创建新类 B. 在已存在的类中添加新的方法 C. 在已存在的类中添加新的属性 D. 在已存在的状态中添加新的状态
多态与动态绑定 多态 : 不同对象收到同样的消息(过程调用)产生不同的结果
过载多态 : 同一个名字在不同的上下文中所代表的含义不同 (只考到这种多态)
其他多态可以参考一下:
参数多态:应用广泛、最纯的多态。
包含多态:同样的操作可用于一个类型及其子类型。包含多态一般需要进行运行时的类型检查
强制多态:编译程序通过语义操作,把操作对象的类型强行加以变换,以符合函数或操作符的要求
过载多态 :同一个名(操作符、函数名)在不同的上下文中有所代表的含义不同
动态绑定 : 根据接收对象的具体情况将请求的 操作与实现的方法进行连接(运行时绑定)
消息和消息通信 : 对象之间进行通信的一种构造叫做消息,消息是异步通信的(消息传递: 接收到消息的对象经过解释,然后予以响应)
考察较多
在面向对象方法中,不同对象收到同一消息可以产生完全不同的结果,这一现象称为(D)。在使用时,用户可以发送一个通用的消息,而实现的细节则由接收对象自行决定。 A. 接口 B. 继承 C. 覆盖 D. 多态
在下列机制中,(D)是指过程调用和响应调用所需执行的代码在运行时加 以结合;而(C)是过程调用和响应调用所需执行的代码在编译时加以结合。 A. 消息传递 B. 类型检查 C. 静态绑定 D. 动态绑定 A. 消息传递 B. 类型检查 C. 静态绑定 D. 动态绑定
(C)多态是指操作(方法)具有相同的名称、且在不同的上下文中所代表的含义不同。 A参数 B包含 C过载 D 强制
面向对象流程 只需了解
面向对象分析
面向对象设计
面向对象程序设计
面向对象测试
面向对象分析
认定对象(名词)
组织对象(抽象成类)
对象间的相互作用
基于对象的操作
面向对象分析的目的是为了获得对应用问题的理解,其主要活动不包括(C)。 A. 认定并组织对象 B. 描述对象间的相互作用 C. 面向对象程序设计 D. 确定基于对象的操作
面向对象设计
识别类及对象
定义属性
定义服务
识别关系
识别包
面向对象设计原则
面向对象程序设计
面向对象测试
面向对象设计原则
[[开发项目管理#模块设计原则|模块设计原则]]: 高内聚低耦合
下面是面向对象设计中的重要原则,通常统称为“SOLID”原则。它们用于指导软件设计,以提高代码的可维护性、可扩展性和灵活性
面向对象七大原则
单一职责原则(Single Responsibility Principle) :设计目的单一的类
开放-封闭原则(Open-Closed Principle) :软件实体(类、模块、函数等)应该对扩展开放对修改关闭 ,可以通过扩展已有的代码来实现新功能,而不是修改已有的代码。
里氏替换原则(Liskov Substitution Principle) :子类必须能够替换其父类而不影响程序的正确性,即子类应该能够完全替代父类 。
依赖倒置原则(Dependency Inversion Principle) :高层模块不应该依赖于低层模块,二者都应该依赖于抽象.
模块间的依赖关系应该通过接口或抽象类来建立,而不是直接依赖于具体的实现类 。这样可以降低模块间的耦合度,提高代码的灵活性和可维护性
接口隔离原则(Interface Segregation Principle) :客户端不应该强制依赖它不需要的接口,应该将大接口拆分成多个小接口。 使用多个专门的接口比使用单一的总接口要好
组合重用原则(Composition Reuse Principle) : 要尽量使用组合,而不是继承关系达到重用目的
迪米特法则(Law of Demeter) :一个对象应该对其他对象有最少的了解,即一个对象应该尽可能少地与其他对象发生相互作用。 这些原则可以帮助设计出符合面向对象设计原则的软件系统,提高系统的灵活性、可维护性和可扩展性。
[[架构相关#软件开发设计哲学|完整可以参考软件开发设计哲学]]
进行面向对象设计时,就一个类而言,应该仅有一个引起它变化的原因,这属于(A) 设计原则。 A. 单一责任 B. 开放-封闭 C. 接口分离 D. 里氏替换
面向对象其他原则
重用发布等价原则(Reuse-Release Equivalence Principle) : 重用的粒度就是发布的粒度
重用粒度 :指的是在软件开发中,开发者将代码或功能模块进行重用的程度。粒度可以是细粒度(如单个函数、类)或粗粒度(如整个模块、库)。
发布粒度 :指的是将软件组件或模块发布到生产环境的单位。发布粒度同样可以是细粒度或粗粒度。
共同封闭原则(Common Closure Principle,简称 CCP) : 包中的所有类对于同一性质的变化应该是共同封闭的.一个变化若对一个包产生影响,则将对该包里的所有类产生影响,而对于其他的包不造成任何影响
共同重用原则(Common Reuse Principle,简称 CRP) : 一个包里的所有类应该是共同重用的,知道重用了包里的一个类,那么就要重用包中的所有类
如果一个模块或类被设计为可以被多个其他模块或类重用,那么它应该包含那些被共同使用的功能和数据。换句话说,相关的功能应该被组织在一起,以便于被多个模块共享和重用
无环依赖原则(No Circular Dependency Principle) : 在包的依赖关系图中不允许存在环 ,即包之间的结构必须是一个直接的无环图形
稳定抽象原则(Stable Abstractions Principle) : 在设计软件系统时,抽象应该尽量保持稳定,而具体实现则可以变化
进行面向对象系统设计时,针对包中的所有类对于同-类性质的变化;一个变化若对一个包产生影响,则将对该包中的所有类产生影响,而对于其他的包不造成任何影响。这属于(D)设计原则。 A. 共同重用 B. 开放-封闭 C. 接口分离 D. 共同封闭
组合优于继承原则 组合和继承都在尝试解决同样的问题:假如你有一段代码想复用
广为流传的“组合优于继承” 的说法是一种不严谨的翻译,其来源如下:(众多设计模式强调的两个个最核心原则《Design Patterns: Elements of Reusable Object-Oriented Software》)
Program to an interface, not an implementation. (面向接口编程,而不是具体的实现)
Favor object composition over class inheritance.(若某个场景的代码复用既可以通过类继承实现,也可以通过对象组合实现,尽量选择对象组合的方式)
对象组合与类继承的对比 面向对象设计的过程中,两个最常用的技巧就是类继承 和对象组合 ,同一个场景下的代码复用,这两个技巧基本上都可以完成。 但是他们有如下的区别:
通过继承实现的代码复用常常是一种“白盒复用” , 这里的白盒指的是可见性: 对于继承来说,父类的内部实现对于子类来说是不透明的(实现一个子类时, 你需要了解父类的实现细节, 以此决定是否需要重写某个方法)
对象组合实现的代码复用则是一种“黑盒复用” : 对象的内部细节不可见,对象仅仅是以“黑盒” 的方式出现(可以通过改变对象引用来改变其行为 方式)
继承式 这里通过汽车的刹车逻辑进行说明, 对于汽车来说,存在多种不同的型号,我们会很自然的希望定义一个类Car来描述所有汽车通用的刹车行为brake(),然后通过某种方式(继承/组合)来为不同的型号的汽车提供不同的刹车行为。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 public abstract class Car { public virtual void Brake () { } } public class CarModelA : Car { public override void Brake () { AStyleBrake(); } private void AStyleBrake () { Console.WriteLine("A" ); } } public class CarModelB : Car { public override void Brake () { BStyleBrake(); } private void BStyleBrake () { Console.WriteLine("b" ); } }
值得注意的是: 每一个型号的车的刹车行为是在编译时就确定好的,没有办法在运行时刻将CarModelB的刹车行为赋予CarModelA
组合式 通过引用组合各个组件
如果通过对象组合的实现方式,则需要为Car定义一个引用,该引用的类型是一个为刹车行为定义的抽象(可以是抽象类或接口类 )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 public interface IBrakeBehavior { void Brake () ; } public class AStyleBrake : IBrakeBehavior { public void Brake () { AStyleBrakeMethod(); } private void AStyleBrakeMethod () { } } public class BStyleBrake : IBrakeBehavior { public void Brake () { BStyleBrakeMethod(); } private void BStyleBrakeMethod () { } } public class Car { protected IBrakeBehavior brakeBehavior; public void Brake () { brakeBehavior.Brake(); } public void SetBrakeBehavior (IBrakeBehavior brakeType ) { this .brakeBehavior = brakeType; } }
注意 :上面的刹车行为不一定需要通过接口来实现,定义一个BrakeBehaviour的父类,然后再定义AStyleBrake,BStyleBrake来继承该类,实现不同的行为, 同样是组合方式的应用。所以不难发现, 当我们拿类继承 和组合 在一起进行对比时, 并不是以实现方式中是否有用到类继承 来区分的。
我们真正关注的是行为的继承 与行为的组合 :需要变化的行为是通过继承后重写的方式 实现,还是通过赋予不同的行为实例对象 实现。
继承式优缺点 类继承优点 :
类之间的继承关系时在编译时刻静态地定义好的,因此使用起来也非常直观 ,毕竟继承是被编程语言本身所支持的功能。
类继承也使得修改要复用的代码变得相对容易,因为可以仅仅重写要更改的父类方法。(如在Java集合标准库中,大量方法就是通过继承复用的,针对这些方法的维护、升级都相对简单)
类继承缺点 :
第一个缺点是伴随第一个优点而生的:无法在运行时改变继承了父类的子类行为(这一点在之前汽车的例子中已经进行了说明)。
第二个缺点更严重:与父类耦合 ,通过继承实现的代码复用,本质上把父类的内部实现细节暴露给了子类,子类的实现会和父类的实现紧密的绑定在一起,结果是父类实现的改动,会导致子类也必须得改变 。
以之前的例子进行说明,如果是通过继承的方式来实现不同型号汽车的刹车行为变化,假设现在我们基于Car这个父类实现了10种不同型号的汽车CarModel( A、B、C、D、E、F、G、H、I 、J),其中前5个型号( A、B、C、D、E) 都没有重写父类的刹车方法,直接使用了父类Car提供的默认方法,后5个型号均提供了自己独特的brake实现 。
现假设,我们希望对Car中的brake方法进行升级改造,然而升级改造后的brake行为只适用于C,D,最早的两种型号A,B并不兼容升级后的刹车行为 。这样,我们为了保证A,B依旧能正常工作,就不得不把旧的brake实现挪到A、B中,或者分别去升级C、D、E中的brake方法 。(比如Java集合标准库中,后期对Stream的支持,直接改变了interface类最早的定义,在interface中加入了默认实现机制)
组合式优缺点 对象组合优点 :
对象的组合是在运行时,通过对象之间所获取的引用关系决定的,所以对象组合要求不同的对象遵从对方所实现的接口 来做引用传递,这样反过来会要求开发者更用心地设计接口 ,以此保证客户端在使用一个对象时,可以把它和很多其他的对象组合在一起使用而不会出现问题。
对象的组合由于是通过接口实现的 ,这样在复用的过程中不会打破其封装性 。任意一个对象都可以在运行时 被替换成另外一个实现了相同接口且类型相同的对象,更重要的是,由于一个对象的实现是针对接口而编写的,具体实现之间的依赖会更少。
对象组合的方式可以帮助你保持每个类的内聚性 ,让每个类专注实现一个任务。类的层次会保持的很小,不会增长到一种无法管理的恐怖数量。(这也是Java只支持单继承的原因 )
总结而言:现代开发,组合才具备更强的灵活性而当继承面领改变的时候,因为子类与父类的耦合会带来更大的重构复杂度
根本原因也是因为,继承自然地要求捆绑所有公共元素到父类上,但一旦发现共性中的例外,这就需要很大的修改
继承遵守的契约是子类都有父类的结构,即父类共享了所有,而在组合式中,接口就是就是契约,他是最轻量最小化级的契约,更容易被附加到已经存在的类中.
说白了,组合是一种更加好用的抽象方式
现代项目开发中完全可以不使用继承,只使用组合会更好
组合所使用的引入接口的这种做法又叫依赖注入
组合缺点以及解决方案
因为需要初始化所有的内部类型,很多的实现会含有相同的重复代码
当需要从重用的代码中暴露信息时,需要创建许多包装方法(简单返回一个内部类型的调用)
1 2 3 4 void getName (){ return user.getName(); }
总之,优势远大于劣势,减少未来修改付出的代价
依赖注入
Dependency Injection
本质就是有两段代码,其中一段代码中想要使用另一段代码,但是它不直接用,而是把它当成参数传进来(使用接口的方式传入,就好比依赖了这个接口),把要用的东西传进来就是”注入 “
把被依赖的代码注入到要用它的代码 称之为 依赖注入
下面视频有利于直观感受依赖注入的好处
优势:修改简单,易于测试
一个思想:如果发现自己在想怎么测试private方法,或者需要设置一些内部变量才能进行测试,这个信号在提醒你需要把一些东西提出来,把他们分离出来并通过注入实现隔离
依赖倒置原则 依赖倒置原则(Dependency Inversion Principle, DIP)是 SOLID 设计原则之一,它的核心思想是:
高层模块不应该依赖低层模块,两者都应该依赖抽象。
抽象不应该依赖具体实现,具体实现应该依赖抽象。
通过依赖倒置原则,可以有效解决循环依赖问题,因为它将直接的依赖关系解耦为通过抽象(接口或抽象类)进行间接依赖
循环依赖是指两个或多个模块(类)之间直接或间接地相互依赖。例如:
这种情况下,代码会变得紧耦合,难以维护和测试,甚至可能导致编译或运行时错误。
一般都是通过引入抽象层和依赖注入,将循环依赖转换为对抽象的依赖 ,从而打破紧耦合关系,步骤如下:
定义接口。
让类实现接口并依赖接口。
在运行时动态注入具体实现。
概述 设计模式的重要性 马丁: 设计模式给了我们瞄准的目标。我还会做一些预先设计。模式就是用来干这个的。模式还给了我们以重构的目标。我们得以知道我们改进的方向。了解模式还有助于我们找到设计美感,因为模式至少都是一些好的设计。你可以从它们身上学到很多。因此,我仍然认为模式起着重要的作用。
必考4分左右
23种设计模式
创建型模式(Creational Patterns) 它关注类产生对象的创建过程中的各种模式
工厂模式(Factory Pattern)
抽象工厂模式(Abstract Factory Pattern)
单例模式(Singleton Pattern)
建造者模式(Builder Pattern)
原型模式(Prototype Pattern)
结构型模式(Structural Patterns) 它关注对象与对象之间的各种组合关系的各种模式
适配器模式(Adapter Pattern)
桥接模式(Bridge Pattern)
组合模式(Composite Pattern)
装饰器模式(Decorator Pattern)
外观模式(Facade Pattern)
享元模式(Flyweight Pattern)
代理模式(Proxy Pattern)
行为型模式(Behavioral Patterns) 它关注对象各种不同的用处和场景的各种模式
策略模式(Strategy Pattern)
观察者模式(Observer Pattern)
模板方法模式(Template Method Pattern)
迭代器模式(Iterator Pattern)
命令模式(Command Pattern)
备忘录模式(Memento Pattern)
状态模式(State Pattern)
访问者模式(Visitor Pattern)
中介者模式(Mediator Pattern)
解释器模式(Interpreter Pattern)
责任链模式(Chain of Responsibility Pattern)
设计模式之间的关系
上图中红色表示创建型的,绿色表示结构型的,蓝色表示行为型的,相连表示有相似性(容易混淆,连接线上的文字表示主要不同点)
图例讲解 为什么不使用UML图 讲解?因为UML图带成员,不够简洁
下面所有设计模式结构图均参考这些图例来讲解(直观)
创建型模式
设计模式名称
简要说明
速记关键字
Abstract Factory 抽象工厂模式
提供一个接口,可以创建一系列相关或相互依赖的对象,而无需指定它们具体的类
生产成系列对象
Builder 构建器模式
将一个复杂类的表示与其构造相分离,使得相同的构建过程能够得出不同的表示
复杂对象构造
Factory Method 工厂方法模式
定义一个创建对象的接口,但由子类决定需要实例化哪个类。工厂方法使得子类实例化的过程推迟
动态生产对象
Prototype 原型模式
用原型实例指定创建对象的类型,并且通过拷贝这个原型来创建新的对象
克隆对象
Singleton 单例模式
保证一个类只有一个实例,并提供一个访问它的全局访问点
单实例
工厂模式 将对象的实例化交给工厂去做,你只需要告诉工厂你需要什么对象,我给你造出来就行了
目的:让使用者只需要关注自己需要的,而不需要关注这个东西是怎么创建的,能用就行。让实例创建和实例使用解耦
意图: 定义一个创建对象的接口,但由子类决定需要实例化哪一个类,使得延迟到子类来实例化
简单工厂模式 两种实现方式
在类里写静态函数用于创建实例
解决构造函数参数太多的问题,通过定义一个静态函数用于创建实例,来简化代码
使用一个单独的类来创建实例
简单工厂:把对象的创建放到一个工厂类中,通过参数来创建不同的对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 class Factory { public : Factory (); ~Factory (); Codec *createFactory (int flag) { switch (flag) { case 0 : return new xxx; case 1 : break ; default : break ; } } }
缺点 :每添一个对象,就需要对简单工厂进行修改(尽管不是删代码,仅仅是添一个switch case,但仍然违背了“不改代码”的原则, 尽量做到添加代码而不是修改原有代码)
优点 :去除了与具体产品的依赖。
使用:
1 2 3 4 5 6 Factory* factory = new Factory (); Codec*codec = factory->createFactory (0 ); codec->msgEncode ();
抽象工厂模式 抽象工厂模式 (Abstract Factory Pattern)是围绕一个超级工厂创建其他工厂。该超级工厂又称为其他工厂的工厂 。这种类型的设计模式属于创建型模式 ,它提供了一种创建对象的最佳方式。
意图是: 提供一个接口,可以创建一系列相关或相互依赖的对象 ,而无需指定他们具体的类
好处:易于维护,易于扩充,且不用对原有的代码进行修改
工厂方法:每种产品由一种工厂来创建, 不同工厂创建不同的对象
创建一个工厂类 - 基类
在基类的工厂类中添加工厂函数, 这是一个虚函数
根据要创建的子对象添加子工厂类, 每个子对象的创建都对应一个子工厂类
在子工厂类中实现父类的工厂函数, 完成创建对象的操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 class RequestCodec { } class RespondCodec { } class Factory { public : Factory (); ~Factory (); virtual Codec *createFactory () {}; } class RequestFactory : public Factory{ public : RequestFactory () { flag = 0 ; } RequestFactory (RequestMsg *msg) { m_request = msg; flag = 1 ; } ~RequestFactory (); Codec *createFactory () { if (flag) return new RequestCodec (&msg); else return new RequestCodec (); } private : RequestMsg *m_request; bool flag; } class RespondFactory : public Factory{ public : RequestFactory (); ~RequestFactory (); Codec *createFactory () { return new RespondCodec (); } }
其实就是把switch的分支拆到了多态中
抽象工厂类的使用:
1 2 3 4 5 6 7 Factory* f = new RequestFactory (); Codec* codec = f->createFactory (); codec->msgDecode ();
实例
BaseASN1 实现最基本的[[网络编程#ASN.1|ASN1编码]]逻辑
SequenceASN1 ASN1编码的进一步封装
Codec 对为了实现对不同结构体的ASN1编码而对SequenceASN1做的进一步封装的一个用于实现多态的基类
RequestCodec和RespondCodec 用于具体实现Codec多态的[对请求数据进行ASN1编码的类型]以及[对回应数据进行ASN1编码的类型]
FactoryCodec 生产各种工厂的抽象类,其生产工厂的函数返回的类型为Codec(多态对多态)
RequestFactory和RespondFectory 具体的[生产请求数据的工厂的类]和[生产回应数据的工厂的类]
其使用方式为:
1 2 3 4 5 6 7 FactoryCodec* f = new RequestFactory (); Codec* codec = f->createCodec (); codec->msgDecode ();
单例模式 通过持有自身实例实现
意图: 保证一个类只有一个实例 ,并提供一个访问它的全局访问点
类产生的实例是全局唯一的
饿汉式-单例模式
在类创建的时候,就创建实例,并直接让这个产生出来的实例绑定到自己的某一个静态成员上,提供一个静态方法返回这个静态成员(实际上就是实例),通过这个getxxx静态方法就能拿到自身唯一的实例.这个过程中构造函数是私有的,防止了通过构造函数在外界创造实例
缺点在于,程序刚开始运行的时候会比较卡,大量new被执行(懒汉式正是解决此问题)
懒汉式-单例模式
解决的正是饿汉式的问题,让他在真正需要执行的时候再去运行这段代码
在getxxx静态方法被调用的时候去检查静态属性,如果是null才创建实例
当创建实例的代价很大的时候,并且常用该实例用不到的情况可以使用这种懒汉式单例模式
原型模式
类中实现Clone方法,通过在实例中调用克隆函数用于克隆自身
适合于克隆自己比通过构造函数创建自身效率高的场景(实际使用场景较小)
意图: 用原型实例指定创建对象的类型,并且通过拷贝这个原型来创建新的对象
构造器模式 也叫Builder模式(建造者模式),也叫生成器模式
在软件系统中,有时候面临着“一个复杂对象”的创建工作,其通常由各个部分的子对象用一定的算法构成;由于需求的变化,这个复杂对象的各个部分经常面临着剧烈的变化,但是将它们组合在一起的算法却相对稳定。如何应对这种变化?如何提供一种“封装机制”来隔离出“复杂对象的各个部分”的变化,从而保持系统中的“稳定构建算法”不随着需求改变而改变?
意图: 将一个复杂类的表示与其构造相分离,使得相同的构建过程(不同的组装)能够得出不同的表示
将一个复杂对象的构建与其表示相分离,使得同样的构建过程可以创建不同的表示。 ——《设计模式》GoF
大部分编程语言都支持可选参数,完全能更方便实现这样的功能,该模式在构造过程极其复杂的情况下有存在的意义
结构型模式 适配器模式 常用于解决旧系统或新旧接口之间的兼容问题
主要有两种
类-适配器 通过继承 实现
创建一个新类(适配器)继承新接口,同时继承老接口的实现类,在新类中新接口中的方法中调用老接口的方法同时做其他逻辑
实例 以java演示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 interface OldPrinter { void printString () ; } class SpecificPrinter implements OldPrinter { @Override public void printString () { System.out.println("Print from SpecificPrinter" ); } } interface NewPrinter { void print () ; } class PrinterAdapter extends SpecificPrinter implements NewPrinter { @Override public void print () { printString(); } } public class ClassAdapterDemo { public static void main (String[] args) { NewPrinter newPrinter = new PrinterAdapter (); newPrinter.print(); } }
UML类图
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 classDiagram class OldPrinter { <<interface>> +printString() } class SpecificPrinter { +printString() } class NewPrinter { <<interface>> +print() } class PrinterAdapter { +print() } OldPrinter <|.. SpecificPrinter SpecificPrinter <|-- PrinterAdapter NewPrinter <|.. PrinterAdapter
对象-适配器 通过组合 实现
实例 以java演示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 interface OldPrinter { void printString () ; } class SpecificPrinter implements OldPrinter { @Override public void printString () { System.out.println("Print from SpecificPrinter" ); } } interface NewPrinter { void print () ; } class PrinterAdapter implements NewPrinter { private final OldPrinter oldPrinter; public PrinterAdapter (OldPrinter oldPrinter) { this .oldPrinter = oldPrinter; } @Override public void print () { oldPrinter.printString(); } } public class ObjectAdapterDemo { public static void main (String[] args) { OldPrinter oldPrinter = new SpecificPrinter (); NewPrinter newPrinter = new PrinterAdapter (oldPrinter); newPrinter.print(); } }
UML类图
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 classDiagram class OldPrinter { <<interface>> +printString() } class SpecificPrinter { +printString() } class NewPrinter { <<interface>> +print() } class PrinterAdapter { -oldPrinter : OldPrinter +PrinterAdapter(OldPrinter oldPrinter) +print() } OldPrinter <|.. SpecificPrinter NewPrinter <|.. PrinterAdapter PrinterAdapter --> OldPrinter
桥接模式
一言以蔽之:就是将多个接口及实现的体系可以通过一个类桥接到一起
桥接模式 是一种结构型设计模式,用于将抽象部分与实现部分分离,使它们可以独立变化,互不影响。桥接模式的核心思想是通过将抽象部分和实现部分分离,使它们可以独立变化,从而提高系统的灵活性和可扩展性。
简单来说,桥接模式就像是一座桥,连接了抽象部分和实现部分,使它们可以分别变化而不相互影响。通过桥接模式,我们可以轻松地添加新的抽象部分或实现部分,而不需要修改原有的代码结构。
举个例子,假设我们要设计一个绘图软件,其中有不同的形状(圆形、矩形)和不同的颜色(红色、蓝色)。使用桥接模式,我们可以将形状和颜色分别作为抽象部分和实现部分,通过桥接将它们连接在一起。这样,我们可以轻松地组合不同的形状和颜色,而不需要修改原有的代码结构。
看似很复杂,实际上就是图中右上方的抽象类通过依赖注入 的方式,传入武器,如果还有底座啥的,也同样利用依赖注入传进来,相当于桥接了武器和底座,可以随意桥接两两
实际上就是依赖注入多个组件的思想
组合模式 组合模式 是一种结构型设计模式,它允许你将对象组合成树形结构以表示“部分-整体”的层次结构 。这种模式使得客户端可以统一处理单个对象和对象组合,而不必区分它们之间的差异。在组合模式中,组合对象和叶子对象都实现相同的接口,这样客户端可以像处理单个对象一样处理对象组合 。这种模式可以使得系统更加灵活和可扩展,同时简化了客户端的代码。
适用场景 : 当希望以一致的方式处理单个对象和对象组合时使用
实例 以C#代码为例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 using System;using System.Collections.Generic;interface IWeapon { void Attack () ; } class Sword : IWeapon { public void Attack () { Console.WriteLine("使用剑攻击!" ); } } class Gun : IWeapon { public void Attack () { Console.WriteLine("使用枪射击!" ); } } abstract class WeaponComponent : IWeapon { public abstract void Add (WeaponComponent component ) ; public abstract void Remove (WeaponComponent component ) ; } class CompositeWeapon : WeaponComponent { private List<WeaponComponent> components = new List<WeaponComponent>(); public override void Add (WeaponComponent component ) { components.Add(component); } public override void Remove (WeaponComponent component ) { components.Remove(component); } public void Attack () { foreach (WeaponComponent component in components) { component.Attack(); } } } class Program { static void Main () { Sword sword1 = new Sword(); Sword sword2 = new Sword(); Gun gun1 = new Gun(); CompositeWeapon compositeWeapon = new CompositeWeapon(); compositeWeapon.Add(sword1); compositeWeapon.Add(sword2); compositeWeapon.Add(gun1); compositeWeapon.Attack(); } }
UML类图
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 classDiagram IWeapon <|.. Sword IWeapon <|.. Gun IWeapon <|.. WeaponComponent WeaponComponent <|-- CompositeWeapon class IWeapon { +Attack() } class Sword { +Attack() } class Gun { +Attack() } class WeaponComponent { +Add(WeaponComponent component) +Remove(WeaponComponent component) } class CompositeWeapon { -List~WeaponComponent~ components +Add(WeaponComponent component) +Remove(WeaponComponent component) +Attack() } class Program { +Main() } Program --> CompositeWeapon CompositeWeapon --> WeaponComponent CompositeWeapon --> IWeapon Sword --> IWeapon Gun --> IWeapon
装饰模式 装饰模式/装饰器/装饰者模式 (Decorator Mode)
动态地给一个对象增加新的功能 ,而不改变对象原本的结构 (非常有用!)
装饰器模式允许在不影响同一类其他对象行为的情况下,静态或动态地向单个对象添加行为。装饰器模式对于遵循单一职责原则特别有用,因为它允许将功能分离到不同的类中,每个类都有自己独特的关注点。
适用场景: 当希望在不修改原有代码情况下,动态地给对象添加新功能时,可以使用装饰器模式,装饰器模式可以帮助你以灵活和可拓展的方式组合对象的各种功能
给一个类或对象增加新的功能,有两种方式:(参考上图)
上方的机器人,更新功能左下角机器人,称之为继承机制 :新类继承一个现有的类,在子类中进行拓展功能
上方的机器人,更新功能为右下角机器人,称之为关联机制 :将一个类的对象套入到另一个类的对象中,相当于把机器人嵌入到箱子里来,套一个壳子来拓展功能,这个壳子就是装饰器
对于第一种方式来说:最大的问题在于继承是静态的,没办法在运行的过程中动态的改变一个类的行为 .
第二种方法是动态的: $$ 动态地给一个对象添加一些额外的功能.就增加功能来说,装饰模式比生成子类更为灵活 $$ 可以这么理解,经过处理就可以给类添加新功能
这也是和代理模式 的区别:代理类里面是写死的,在编译的时候就确定了关系。而装饰器是在运行时来确定的
代理模式是直接编写代理对象
装饰模式是编写函数动态生成代理对象
装饰器模式关注于在一个对象上动态添加方法,而代理模式关注于控制对象的访问
大致的操作图如下:
紫色的圆是左边类的对象,通过构造传入右边类成员中,在新类的同名run中调用成员对象的原来的run,这里就可以在前后添加代码逻辑
使用场景
实现监听器功能 ,提供一个接口,让使用者自定义监听某个事件发生时的行为,在事件发生前后触发相应的操作:通过装饰器模式,可以动态地附加到想要监听的对象上,在执行目标函数的前后可以执行相应的函数(装饰器用来做监听器的场景非常多)
肉鸽游戏场景:
案例 根据这个图,给出c#的代码,以演示装饰者模式
然后就可以以如图的形式动态地(套壳)修改类的表现形式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 using System;using System.Collections.Generic;abstract class Beverage { public abstract string Description { get ; } public abstract double Cost () ; } class MilkTea : Beverage { public override string Description => "Milk Tea" ; public override double Cost () { return 2.00 ; } } class FruitTea : Beverage { public override string Description => "Fruit Tea" ; public override double Cost () { return 2.50 ; } } class Yogurt : Beverage { public override string Description => "Yogurt" ; public override double Cost () { return 3.00 ; } } abstract class ToppingDecorator : Beverage { protected Beverage beverage; public ToppingDecorator (Beverage beverage ) { this .beverage = beverage; } public override string Description => beverage.Description; public abstract override double Cost () ; } class Boba : ToppingDecorator { public Boba (Beverage beverage ) : base (beverage ) { } public override string Description => beverage.Description + ", Boba" ; public override double Cost () { return beverage.Cost() + 0.50 ; } } class Pudding : ToppingDecorator { public Pudding (Beverage beverage ) : base (beverage ) { } public override string Description => beverage.Description + ", Pudding" ; public override double Cost () { return beverage.Cost() + 0.60 ; } } class HerbalJelly : ToppingDecorator { public HerbalJelly (Beverage beverage ) : base (beverage ) { } public override string Description => beverage.Description + ", Herbal Jelly" ; public override double Cost () { return beverage.Cost() + 0.70 ; } } class Program { static void Main () { Beverage beverage = new MilkTea(); Console.WriteLine($"{beverage.Description} ${beverage.Cost()} " ); beverage = new Boba(beverage); Console.WriteLine($"{beverage.Description} ${beverage.Cost()} " ); beverage = new Pudding(beverage); Console.WriteLine($"{beverage.Description} ${beverage.Cost()} " ); beverage = new HerbalJelly(beverage); Console.WriteLine($"{beverage.Description} ${beverage.Cost()} " ); } }
当需要添加新的配料,只需要添加新的具体的配料装饰类;同样当需要添加新的饮料也只需要添加新的具体的饮料类
UML类图:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 classDiagram class Beverage { <<abstract>> +Description : string +Cost() : double } class MilkTea { +Description : string +Cost() : double } class FruitTea { +Description : string +Cost() : double } class Yogurt { +Description : string +Cost() : double } class ToppingDecorator { <<abstract>> -beverage : Beverage +ToppingDecorator(Beverage beverage) +Description : string +Cost() : double } class Boba { +Description : string +Cost() : double } class Pudding { +Description : string +Cost() : double } class HerbalJelly { +Description : string +Cost() : double } Beverage <|-- MilkTea Beverage <|-- FruitTea Beverage <|-- Yogurt Beverage <|-- ToppingDecorator ToppingDecorator <|-- Boba ToppingDecorator <|-- Pudding ToppingDecorator <|-- HerbalJelly
外观模式 外观模式/门面模式(Facade Pattern)
外观模式为子系统中的一组接口提供一个统一的接口,即定义了一个高层接口,使得这一子系统更加容易使用
通过提供一个统一的接口,来隐藏系统中多个子系统的复杂性
将多个高度耦合的多类结构,封装到一个类中,只提供统一接口,是一种封装的思想
适用场景 : 当需要简化复杂系统的使用接口时,可使用外观模式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 class ResourceManagement : def manage_resources (self ): print ("管理资源.") class TowerConstruction : def build_tower (self , location ): print (f "{location}位置建造塔.") class EnemySpawning: def spawn_enemies(self): print(" 生成敌人.") class GameLoopControl: def start_game_loop(self): print(" 开始游戏循环.") class GameFacade: def __init__(self): self.resource_management = ResourceManagement() self.tower_construction = TowerConstruction() self.enemy_spawning = EnemySpawning() self.game_loop_control = GameLoopControl() def start_game(self): self.resource_management.manage_resources() self.tower_construction.build_tower(" 中央位置") self.enemy_spawning.spawn_enemies() self.game_loop_control.start_game_loop() print(" 游戏开始!") def main(): game_facade = GameFacade() game_facade.start_game() if __name__ == " __main__": main()
享元模式 享元模式(Flyweight Pattern)
享元的本质就是公用数据的抽象,全局变量就可以算是一种享元
通过共享技术来有效地支持大量细粒度对象的重用,节省不必要的属性空间,节省内存
将共同类型的属性放到享元类中
简单来说,享元模式通过共享对象来减少内存消耗,使用一个工厂来管理这些共享对象,并确保每个对象只被创建一次
适用场景 : 当需要创建大量相似对象,并希望通过共享对象来减少内存消耗时,可以使用享元模式
享元模式通过共享相同的状态来减少内存占用和提高性能,同时通过动态传递非共享状态来实现个性化定制。享元工厂负责管理共享池,确保享元对象的唯一性和共享性
具体享元类包含了所有共享的状态 :
具体享元类是享元模式中的一种类别,它包含了所有可共享的状态或属性。这些状态是可以被多个对象共享的,从而节省内存和提高性能。具体享元类的实例通常是不可变的,它们包含了固定不变的共享状态。
非共享的状态则会在使用时动态传递 :
非共享的状态是指每个具体享元对象特有的状态或属性,这些状态不可共享。在使用具体享元对象时,这些非共享的状态会动态传递给享元对象,以便个性化地定制享元对象的行为。
享元工厂负责维护享元对象的共享池 :
享元工厂是负责创建和管理享元对象的工厂类。享元工厂维护一个共享池(Flyweight Pool),用于存储和管理已创建的享元对象。享元工厂确保相同的享元对象只被创建一次,并在需要时从共享池中获取已有的享元对象。
提供一个方法来获取享元对象 :
享元工厂提供一个方法(通常是工厂方法),用于获取享元对象。当客户端需要使用享元对象时,可以通过调用享元工厂的方法来获取相应的享元对象,并在其中传递非共享的状态参数。
案例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 using System;using System.Collections.Generic;interface ITextStyle { void ApplyStyle (string text ) ; } class ConcreteTextStyle : ITextStyle { private readonly string _font; private readonly int _size; private readonly string _color; public ConcreteTextStyle (string font, int size, string color ) { _font = font; _size = size; _color = color; } public void ApplyStyle (string text ) { Console.WriteLine($"Applying [{_font} , {_size} , {_color} ] to \"{text} \"" ); } } class TextStyleFactory { private static readonly Dictionary<string , ITextStyle> Styles = new Dictionary<string , ITextStyle>(); public static ITextStyle GetStyle (string font, int size, string color ) { string key = font + size + color; if (!Styles.TryGetValue(key, out ITextStyle style)) { style = new ConcreteTextStyle(font, size, color); Styles[key] = style; } return style; } } class FlyweightPatternDemo { static void Main () { ITextStyle style1 = TextStyleFactory.GetStyle("Arial" , 12 , "Red" ); style1.ApplyStyle("Hello, World!" ); ITextStyle style2 = TextStyleFactory.GetStyle("Arial" , 12 , "Red" ); style2.ApplyStyle("Flyweight Pattern Demo" ); Console.ReadKey(); } }
抽象享元类 ITextStyle
定义了一个方法 ApplyStyle(string text),用于应用样式。
具体享元类 ConcreteTextStyle
实现了 ITextStyle 接口。
包含三个只读属性:_font, _size, _color,分别表示字体、大小和颜色。
构造函数用于初始化这些属性。
ApplyStyle 方法打印应用的样式和文本。
享元工厂类 TextStyleFactory
使用 Dictionary<string, ITextStyle> 存储已经创建的享元对象。
GetStyle 方法根据 font, size, color 生成一个键,并检查是否已有对应的享元对象。
如果没有,则创建一个新的 ConcreteTextStyle 对象并存储到 Styles 中。
最后返回找到的或新创建的享元对象。
演示类 FlyweightPatternDemo
Main 方法演示了享元模式的使用。
调用 TextStyleFactory.GetStyle 获取享元对象并应用样式。
style1 和 style2 应用了相同的样式,因此 style2 会复用 style1 的实例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 classDiagram class ITextStyle { <<interface>> +ApplyStyle(string text) } class ConcreteTextStyle { -_font : string -_size : int -_color : string +ConcreteTextStyle(string font, int size, string color) +ApplyStyle(string text) } class TextStyleFactory { -Styles : Dictionary~string, ITextStyle~ +GetStyle(string font, int size, string color) : ITextStyle } ITextStyle <|.. ConcreteTextStyle ConcreteTextStyle --> TextStyleFactory : uses
代理模式 代理模式通过代理类控制对真实对象的访问,并在需要时创建和使用真实对象
核心思想:代理模式为其他对象提供了一种代理以控制对这个对象的访问
代理,在其最广泛的形式中,是一个用作其他事物接口的类。代理是一个包装或代理对象,客户端通过它来访问后台的实际服务对象。使用代理可以简单地将请求转发到实际对象,或者提供额外的逻辑。在代理中可以提供额外的功能,例如,当对实际对象的操作资源消耗较大时进行缓存,或在调用实际对象的操作之前进行前置条件检查。
适用场景 : 当需要在访问对象时增加额外的操作时,可以使用代理模式.例如,延迟加载,权限控制,日志记录等.代理模式可以帮助你在不修改现有代码的情况下,实现对对象访问的控制
代理类中持有[真正的类]对象,同样的函数名在其中调用[真正的类]的方法的时候还能执行一些新的动作,代理类的名字往往是真正的类+proxy
应用场景:劫持,过滤,安全检查
远程代理(Remote Proxy) :用于在不同地址空间中代表对象。远程代理可以隐藏对象存在于不同地址空间的实现复杂性,使得客户端可以像调用本地对象一样调用远程对象。
虚拟代理(Virtual Proxy) :用于延迟加载对象的开销昂贵的资源。虚拟代理会在需要时创建真实对象,而在不需要时充当占位符。
保护代理(Protection Proxy) :用于控制对对象的访问权限。保护代理可以限制对对象的访问,确保只有特定权限的用户可以访问对象。
缓存代理(Cache Proxy) :用于缓存对象,以提高系统性能。缓存代理会在访问对象前检查缓存,如果对象已经存在于缓存中,则直接返回缓存的对象,避免重复创建对象。
日志记录代理(Logging Proxy) :用于记录对象的操作日志。日志记录代理可以在调用对象的方法前后记录日志信息,用于调试和跟踪操作。
智能引用代理(Smart Reference Proxy) :用于在对象被引用时执行额外的操作。智能引用代理可以在对象被引用时执行计数、内存管理等操作,以确保对象的正确使用。
延迟初始化(Lazy Initialization) :用于延迟对象的初始化。代理模式可以通过延迟初始化来避免在对象创建时的高开销,延迟到真正需要时再进行初始化。
案例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 using System;public interface IImage { void Display () ; } public class Image : IImage { private string _fileName; public Image (string fileName ) { _fileName = fileName; LoadImageFromDisk(); } private void LoadImageFromDisk () { Console.WriteLine("Loading image from disk: " + _fileName); } public void Display () { Console.WriteLine("Displaying image: " + _fileName); } } public class ImageProxy : IImage { private readonly string _fileName; private Image _image; public ImageProxy (string fileName ) { _fileName = fileName; } public void Display () { if (_image == null ) { _image = new Image(_fileName); } _image.Display(); } } public class ImageLoader { private IImage _image; public ImageLoader (IImage image ) { _image = image; } public void DisplayImage () { _image.Display(); } } class Program { static void Main (string [] args ) { IImage imageProxy = new ImageProxy("image.jpg" ); ImageLoader imageLoader = new ImageLoader(imageProxy); Console.WriteLine("..." ); imageLoader.DisplayImage(); } }
类似单例懒汉模式,实现了使用代理模式延迟加载和显示图片
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 classDiagram direction LR class IImage { <<interface>> +Display() } class Image { -string _fileName +Image(string fileName) -LoadImageFromDisk() +Display() } class ImageProxy { -string _fileName -Image _image +ImageProxy(string fileName) +Display() } class ImageLoader { -IImage _image +ImageLoader(IImage image) +DisplayImage() } IImage <|.. Image IImage <|.. ImageProxy ImageLoader --> IImage ImageProxy --> Image
行为型模式 职责链模式 也叫责任链模式
让多个处理都有机会处理该请求,直到其中某个处理成功为止.责任链模式可以把多个处理串成链,然后让请求在链上传递,直到某一个处理处理成功为止
优点
缺点:
职责链设计模式通过将请求的处理责任分摊到多个对象中,避免了请求发送者和接收者之间的耦合。在实际开发中,职责链模式可以帮助我们创建更灵活的系统结构,提高代码的可维护性和扩展性。
抽象出一个公共的父类,然后去定义不同的处理类,他们之间通过nextHandler去连接起来
案例代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 #include <iostream> #include <string> class Handler {public : virtual ~Handler () = default ; void setNext (Handler* nextHandler) { next = nextHandler; } virtual void handleRequest (const std::string& request) { if (next) { next->handleRequest (request); } } private : Handler* next = nullptr ; }; class LowLevelHandler : public Handler {public : void handleRequest (const std::string& request) override { if (request == "low" ) { std::cout << "LowLevelHandler 处理请求: " << request << std::endl; } else { std::cout << "LowLevelHandler 无法处理请求,传递给下一个处理者" << std::endl; Handler::handleRequest (request); } } }; class MediumLevelHandler : public Handler {public : void handleRequest (const std::string& request) override { if (request == "medium" ) { std::cout << "MediumLevelHandler 处理请求: " << request << std::endl; } else { std::cout << "MediumLevelHandler 无法处理请求,传递给下一个处理者" << std::endl; Handler::handleRequest (request); } } }; class HighLevelHandler : public Handler {public : void handleRequest (const std::string& request) override { if (request == "high" ) { std::cout << "HighLevelHandler 处理请求: " << request << std::endl; } else { std::cout << "HighLevelHandler 无法处理请求,传递给下一个处理者" << std::endl; Handler::handleRequest (request); } } }; int main () { LowLevelHandler lowHandler; MediumLevelHandler mediumHandler; HighLevelHandler highHandler; lowHandler.setNext (&mediumHandler); mediumHandler.setNext (&highHandler); std::string request1 = "low" ; std::string request2 = "medium" ; std::string request3 = "high" ; std::string request4 = "unknown" ; std::cout << "处理请求: " << request1 << std::endl; lowHandler.handleRequest (request1); std::cout << "处理请求: " << request2 << std::endl; lowHandler.handleRequest (request2); std::cout << "处理请求: " << request3 << std::endl; lowHandler.handleRequest (request3); std::cout << "处理请求: " << request4 << std::endl; lowHandler.handleRequest (request4); return 0 ; }
UML类图
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 classDiagram class Handler { +setNext(Handler* nextHandler) +handleRequest(string request) -Handler* next } class LowLevelHandler { +handleRequest(string request) } class MediumLevelHandler { +handleRequest(string request) } class HighLevelHandler { +handleRequest(string request) } class Main { +main() } Handler <|-- LowLevelHandler Handler <|-- MediumLevelHandler Handler <|-- HighLevelHandler Main --> LowLevelHandler Main --> MediumLevelHandler Main --> HighLevelHandler LowLevelHandler --> Handler : next MediumLevelHandler --> Handler : next HighLevelHandler --> Handler : next
命令模式 命令模式它可将请求转换为一个包含与请求相关的所有信息的独立对象 。该从而可以进行参数化、日志记录、队列化 、撤销 等操作。命令模式中,发起者(Invoker)不直接与接收者(Receiver)进行通信,而是通过命令对象来执行操作。
命令模式**通过将请求封装成对象,可以让系统支持复杂的命令以及请求和操作的管理.**他提供了一种拓展系统功能的灵活方式,同时保持调用者和接收者之间的解耦
适用场景 : 命令模式非常适合用于实现命令的调度,记录,撤销和重做等功能,当你需要将操作封装为对象,通过操作的排列组合来调控具体行为时,命令模式就显得非常有用
命令模式典型的代表就是事件驱动
很大的优势在于可以轻松实现可撤销功能
优点:
动作封装
解耦发送者和接收者
可拓展性
简化和集中错误处理
支持撤销和重做功能
易于实现宏命令和组合命令
缺点: 复杂度和性能开销
常见应用案例
架构:
问号是表示懒得画了,太小了
实例架构:
开关灯案例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 #include <iostream> #include <memory> #include <vector> #include <stack> class Light {public : void turnOn () { std::cout << "灯已打开" << std::endl; } void turnOff () { std::cout << "灯已关闭" << std::endl; } }; class ICommand {public : virtual ~ICommand () = default ; virtual void execute () = 0 ; virtual void undo () = 0 ; }; class LightOnCommand : public ICommand {public : LightOnCommand (Light* light) : light_ (light) {} void execute () override { light_->turnOn (); } void undo () override { light_->turnOff (); } private : Light* light_; }; class LightOffCommand : public ICommand {public : LightOffCommand (Light* light) : light_ (light) {} void execute () override { light_->turnOff (); } void undo () override { light_->turnOn (); } private : Light* light_; }; class RemoteControl {public : void setCommand (std::shared_ptr<ICommand> command) { command_ = command; while (!redoHistory.empty ()) { redoHistory.pop (); } } void pressButton () { if (command_) { command_->execute (); undoHistory.push (command_); } } void pressUndo () { if (!undoHistory.empty ()) { undoHistory.top ()->undo (); redoHistory.push (undoHistory.top ()); undoHistory.pop (); } else { std::cout << "没有命令可以撤销!" << std::endl; } } void pressRedo () { if (!redoHistory.empty ()) { redoHistory.top ()->execute (); undoHistory.push (redoHistory.top ()); redoHistory.pop (); } else { std::cout << "没有命令可以重做!" << std::endl; } } private : std::shared_ptr<ICommand> command_; std::stack<std::shared_ptr<ICommand>> undoHistory; std::stack<std::shared_ptr<ICommand>> redoHistory; }; int main () { Light livingRoomLight; std::shared_ptr<ICommand> lightOnCommand = std::make_shared <LightOnCommand>(&livingRoomLight); std::shared_ptr<ICommand> lightOffCommand = std::make_shared <LightOffCommand>(&livingRoomLight); RemoteControl remoteControl; remoteControl.setCommand (lightOnCommand); remoteControl.pressButton (); remoteControl.setCommand (lightOffCommand); remoteControl.pressButton (); remoteControl.pressUndo (); remoteControl.pressUndo (); remoteControl.pressRedo (); remoteControl.pressRedo (); return 0 ; } 灯已打开 灯已关闭 灯已打开 灯已关闭 灯已打开 灯已关闭
UML类图
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 classDiagram class Light { +turnOn() +turnOff() } class ICommand { <<interface>> +execute() +undo() } class LightOnCommand { +execute() +undo() } class LightOffCommand { +execute() +undo() } class RemoteControl { +setCommand(ICommand* command) +pressButton() +pressUndo() +pressRedo() -undoHistory : stack<ICommand> -redoHistory : stack<ICommand> } LightOnCommand --> Light LightOffCommand --> Light LightOnCommand ..|> ICommand LightOffCommand ..|> ICommand RemoteControl --> ICommand
1. Light 类表示接收者,包含 turnOn() 和 turnOff() 方法。
2. ICommand 是命令接口,定义了 execute() 和 undo() 方法。
3. LightOnCommand 和 LightOffCommand 实现了 ICommand 接口,并依赖于 Light 类,用来执行开灯和关灯的命令。
4. RemoteControl 是调用者,负责将命令对象关联并执行,还管理了撤销和重做功能的栈:undoHistory 和 redoHistory。
文本编辑器案例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 #include <iostream> #include <string> #include <stack> class Command { public : virtual ~Command () = default ; virtual void execute () = 0 ; virtual void undo () = 0 ; }; class TextEditor { public : void insert (const std::string &text) { content += text; std::cout << "插入文本: " << text << std::endl; std::cout << "当前文本内容: " << content << std::endl; } void remove (size_t length) { if (length <= content.length ()) { std::string removedText = content.substr (content.length () - length, length); content.erase (content.length () - length); std::cout << "删除文本: " << removedText << std::endl; std::cout << "当前文本内容: " << content << std::endl; } } const std::string &getContent () const { return content; } private : std::string content; }; class InsertCommand : public Command{ public : InsertCommand (TextEditor *editor, const std::string &text) : editor (editor), text (text) {} void execute () override { editor->insert (text); lastInsertLength = text.length (); } void undo () override { editor->remove (lastInsertLength); } private : TextEditor *editor; std::string text; size_t lastInsertLength = 0 ; }; class CommandInvoker { public : void executeCommand (Command *command) { command->execute (); history.push (command); } void undoCommand () { if (!history.empty ()) { Command *lastCommand = history.top (); lastCommand->undo (); history.pop (); } else { std::cout << "没有命令可以撤销" << std::endl; } } private : std::stack<Command *> history; }; int main () { TextEditor editor; CommandInvoker invoker; Command *insertCommand1 = new InsertCommand (&editor, "Hello" ); Command *insertCommand2 = new InsertCommand (&editor, " World" ); invoker.executeCommand (insertCommand1); invoker.executeCommand (insertCommand2); std::cout << "\n撤销操作:\n" ; invoker.undoCommand (); std::cout << "\n再次撤销操作:\n" ; invoker.undoCommand (); delete insertCommand1; delete insertCommand2; return 0 ; }
代码结构如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 classDiagram class Command { <<interface>> +execute() void +undo() void } class TextEditor { +insert(text: string) void +remove(length: size_t) void +getContent() string } class InsertCommand { -editor: TextEditor -text: string -lastInsertLength: size_t +execute() void +undo() void } class CommandInvoker { -history: stack~Command*~ +executeCommand(command: Command*) void +undoCommand() void } InsertCommand --|> Command : implements InsertCommand o-- TextEditor : "1" CommandInvoker ..> Command : uses
解释器模式 解释器模式用于定义一个语言的语法表示,并通过解释器来解释语言中的句子。通常,解释器模式用于解析和执行简单的语法规则,如数学表达式、脚本语言或命令语言。
本质是指导我们去开发一个解释器,可以理解成一种语法解释器的开发框架
应用场景:编译器和解释器、配置文件解析器、查询语言解析器
当需要解释一种特定的语言时(例如计算器语法、SQL解析)
当想要将复杂的问题分解为更简单的子问题,并对这些子问题进行解释和执行
抽象表达式(AbstractExpression) : 声明一个interpret()方法,这个方法是所有具体表达式的接口。
终结符表达式(TerminalExpression) : 实现与文法中的终结符相关的解释操作。
非终结符表达式(NonterminalExpression) : 为文法中的非终结符实现解释操作。通常它会包含多个表达式。
上下文(Context) : 包含解释器之外的一些全局信息。
客户端(Client) : 构建或定义语法树,并将其传递给解释器进行解释。
解释器模式虽然强大,但在处理复杂语法时容易导致类爆炸,因此仅适用于相对简单的文法解析问题。
案例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 #include <iostream> #include <string> #include <map> class Expression {public : virtual int interpret (const std::map<std::string, int >& context) = 0 ; virtual ~Expression () = default ; }; class VariableExpression : public Expression { std::string name; public : VariableExpression (const std::string& name) : name (name) {} int interpret (const std::map<std::string, int >& context) override { if (context.find (name) != context.end ()) { return context.at (name); } return 0 ; } }; class AddExpression : public Expression { Expression* left; Expression* right; public : AddExpression (Expression* left, Expression* right) : left (left), right (right) {} int interpret (const std::map<std::string, int >& context) override { return left->interpret (context) + right->interpret (context); } ~AddExpression () { delete left; delete right; } }; class SubtractExpression : public Expression { Expression* left; Expression* right; public : SubtractExpression (Expression* left, Expression* right) : left (left), right (right) {} int interpret (const std::map<std::string, int >& context) override { return left->interpret (context) - right->interpret (context); } ~SubtractExpression () { delete left; delete right; } }; int main () { std::map<std::string, int > context; context["x" ] = 5 ; context["y" ] = 10 ; Expression* expression = new SubtractExpression ( new AddExpression ( new VariableExpression ("x" ), new VariableExpression ("y" ) ), new SubtractExpression ( new VariableExpression ("x" ), new VariableExpression ("y" ) ) ); int result = expression->interpret (context); std::cout << "Expression result: " << result << std::endl; delete expression; return 0 ; }
总结:
解释器模式将复杂的表达式解析和解释分解为简单的子问题,每个子问题由终结符或非终结符来处理。
在需要解释某种特定语言的场景下(如数学表达式、脚本语言),解释器模式是一个非常合适的解决方案。
对于复杂的文法解析,解释器模式可能并不是最合适的选择。更常见的替代方案是编译器技术 中的工具和技术,如解析器生成器 (parser generators)或者采用更灵活的设计模式。
在复杂的语法树遍历场景下,可以考虑使用访问者模式(Visitor Pattern) ,它能让你在不修改对象结构的情况下,增加新的操作。访问者模式通过将操作从语法树的节点中分离出来,使得对语法树的扩展更加灵活。
组合模式 允许你将对象组合成树状结构,以表示“部分-整体”的层次结构。通过这种方式,可以将复杂表达式的处理通过组合实现,而不是每个表达式都定义一个新类。
迭代器模式 迭代器模式 用于提供一种方法,顺序访问一个聚合对象中的各个元素,而又不暴露该对象的内部表示。迭代器模式将迭代的逻辑与容器解耦,使用迭代器对象来负责遍历操作。
迭代器模式的主要组成部分:
迭代器接口(Iterator Interface) : 定义访问和遍历元素的接口,通常包含first()、next()、isDone()、currentItem()等方法
具体迭代器(Concrete Iterator) : 实现迭代器接口,负责遍历聚合对象的具体元素。
聚合接口(Aggregate Interface) : 定义创建迭代器的接口,一般包含一个createIterator()方法。
具体聚合(Concrete Aggregate) : 实现聚合接口,存储元素并定义如何创建该聚合对象的迭代器。
客户端(Client) : 使用迭代器来遍历聚合对象中的元素,客户端只需要与迭代器交互,不需要知道聚合对象的内部结构。
迭代器模式的好处:
解耦 :聚合对象和迭代逻辑分离,使得聚合对象专注于存储数据,迭代器则专注于遍历操作。
统一接口 :无论聚合对象的内部结构如何复杂,客户端都可以通过统一的迭代器接口访问元素。
扩展性强 :可以很方便地增加新的遍历方式,不必修改聚合对象的代码。
适用场景 : 当你需要以一种一致的方法遍历集合中的元素,而不想暴露其内部表示时,可以使用迭代器模式
案例 假设有一个简单的Book类和BookCollection类,BookCollection是一个集合,里面存储了多个Book对象。我们使用迭代器模式来遍历BookCollection中的书。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 #include <iostream> #include <string> class Book {private : std::string name; public : Book (const std::string& name) : name (name) {} std::string getName () const { return name; } }; class Iterator {public : virtual bool hasNext () = 0 ; virtual Book* next () = 0 ; virtual ~Iterator () = default ; }; class Aggregate {public : virtual Iterator* createIterator () = 0 ; virtual ~Aggregate () = default ; }; class BookCollection : public Aggregate {private : std::vector<Book*> books; public : void addBook (Book* book) { books.push_back (book); } Book* getBookAt (int index) const { return books[index]; } int getSize () const { return books.size (); } Iterator* createIterator () override { return new BookIterator (this ); } }; class BookIterator : public Iterator {private : const BookCollection* bookCollection; int index; public : BookIterator (const BookCollection* collection) : bookCollection (collection), index (0 ) {} bool hasNext () override { return index < bookCollection->getSize (); } Book* next () override { return bookCollection->getBookAt (index++); } }; int main () { BookCollection* collection = new BookCollection (); collection->addBook (new Book ("C++ Programming" )); collection->addBook (new Book ("Design Patterns" )); collection->addBook (new Book ("Data Structures" )); Iterator* iterator = collection->createIterator (); while (iterator->hasNext ()) { Book* book = iterator->next (); std::cout << "Book: " << book->getName () << std::endl; } delete iterator; delete collection; return 0 ; }
输出:
1 2 3 Book: C++ Programming Book: Design Patterns Book: Data Structures
UML类图
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 classDiagram class Book { -name : string +Book(string name) +getName() : string } class Iterator { <<interface>> +hasNext() : bool +next() : Book* } class Aggregate { <<interface>> +createIterator() : Iterator* } class BookCollection { -books : vector<Book*> +addBook(Book* book) +getBookAt(int index) : Book* +getSize() : int +createIterator() : Iterator* } class BookIterator { -bookCollection : BookCollection* -index : int +BookIterator(BookCollection* collection) +hasNext() : bool +next() : Book* } Iterator <|.. BookIterator Aggregate <|.. BookCollection BookCollection --> Book BookIterator --> BookCollection
1. Book 类是书的简单模型,包含 name 成员和 getName() 方法。
1. Iterator 是迭代器接口,定义了 hasNext() 和 next() 方法。
1. Aggregate 是聚合接口,定义了 createIterator() 方法。
1. BookCollection 类实现了 Aggregate 接口,包含一个 vector<Book*> 成员,用于存储书本,并提供方法来添加书本和获取迭代器。
1. BookIterator 类实现了 Iterator 接口,用于遍历 BookCollection,并跟踪当前的索引。
迭代器模式通过抽象化遍历逻辑,提供了一种简洁的方式来访问聚合对象中的元素。它在C++标准库中被广泛应用,例如STL容器(vector, list, map等)都有迭代器接口,用户可以轻松地遍历容器中的元素。
观察者模式 视频教程,充分诠释了观察者模式的意义
主要作用:定义一对多的依赖关系 ,让多个观察者同时监听一个主题对象。
观察者模式其中一个对象称为主题(subject),它维护一组依赖它的对象 ,称为观察者(observers).当主题的状态发生变化时,他会主动通知所有观察者
适用场景 : 当希望一个对象状态改变时通知其他多个对象,而不希望这些对象之间存在紧耦合关系时,可使用观察者模式,例如,当需要实现事件处理系统,消息广播系统,或任何需要动态通知多个对象的情况
应用场景:社交媒体平台、股票市场、GUI工具、实时消息系统
观察者模式定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
在观察者模式中,主体是通知的发布者,它发出通知时并不需要知道谁是它的观察者, 可以有任意数目的观察者订阅并接收通知。观察者模式不仅被广泛应用于软件界面元素之间的交互,在业务对象之间的交互、权限管理等方面也有广泛的应用。
观察者模式(Observer)完美的将观察者和被观察的对象分离开。观察者模式在模块之间划定了清晰的界限,提高了应用程序的可维护性和重用性。
两个类: 观察者类 被观察者类
观察者类中:定义一个对某个事件感兴趣的处理函数,一般也叫槽函数
被观察者类中:定义一个数据结构,用来保存观察者对哪一个事件id感兴趣,使用map建立对应关系
被观察者类中,定义两个函数:1. 添加观察者与其感兴趣的事件id加入到容器之中 2. 通知事件函数执行逻辑:首先遍历容器,找是否有感兴趣的事件id,若有则代表一系列的观察者对该事件感兴趣,那么遍历观察者列表,让每一个观察执行相应的槽函数
上图中,最下面的黄色是被通知者的结构,上图的展示效果还是非常模糊
案例 下面展示的结构为Billboard和Monitor为观察者,观察Stock的变化
将Stock再抽象一层,提供独立的抽象接口Subject,将标准接口抽离出来,于是有了下面的图:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 #include <list> using namespace std;struct Observer ; struct Subject { std::list<Observer *> observers; virtual void attach (Observer *observer) = 0 ; virtual void detach (Observer *observer) = 0 ; virtual void notify () = 0 ; }; struct Observer { Subject *sub; Observer (Subject *sub) : sub (sub) { sub->attach (this ); } virtual ~Observer () { sub->detach (this ); } virtual void update () = 0 ; }; class Stock : public Subject{ int price = 0 ; public : int getPrice () { return price; } void setPrice (int v) { price = v; notify (); } void attach (Observer *observer) override { observers.push_back (observer); } void detach (Observer *observer) override { observers.remove (observer); } void notify () override { for (auto observer : observers) { observer->update (); } } }; struct Monitor : Observer{ Monitor (Subject *sub) : Observer (sub) {} void print (int v) const { std::cout << "Monitor:" << v << std::endl; } void update () override { print (static_cast <Stock *>(sub)->getPrice ()); } }; struct Billboard : Observer{ Billboard (Subject *sub) : Observer (sub) {} void display (int v) const { std::cout << "Billboard:" << v << std::endl; } void update () override { display (static_cast <Stock *>(sub)->getPrice ()); } }; Stock stock; Monitor Monitor (&stock) ;Billboard Billboard (&stock) ;stock.setPrice (10 );
观察者模式的核心是对观察者的抽象,以及所观察对象如何通过attach和detach以及notify方法来维护观察者集合
与发布-订阅模式的区别 Pub/Sub Pattern
发布订阅模式是一种常用的架构模式, 用来规定一系统中的不同部分之间应该如何进行消息传递
观察者模式
一种软件设计模式 ,主要针对代码层次,提供类与类之间的组织关系
发布/订阅模式
一种系统架构模式 ,主要针对组件/服务层次,指定他们之间的消息传递模式
中介者模式 中介者模式用于降低对象之间的耦合性。通过引入一个中介者对象,多个对象之间的交互不再直接相互通信,而是通过中介者来协调它们的行为。中介者模式的核心思想是通过集中化处理对象之间的交互,避免对象之间形成紧耦合关系 ,从而增强系统的可维护性和扩展性。
如果不使用中介模式,将会如上图左边一样调用关系非常混乱,但是使用了中介者模式后,关系将会特别清晰,如上右图
中介者模式引入了一个第三方对象(称为中介者)来控制两个或多个对象(称为同事)之间的交互,从而减少类之间的耦合,因为他们不再需要了解彼此的实现细节
中介者模式和代理模式 的区别
中介者模式侧重于简化对象之间的通信,通过一个中介者对象来管理和协调多个对象之间的交互
用于对象之间的通信协调
代理模式通过一个代理对象来控制对另一个对象的访问,通常用于添加额外的功能,如权限控制,延迟加载等
用于对象的访问控制
适用场景:当希望减少对象之间的复杂依赖关系,促进对象解耦时,可以使用中介者模式
中介者模式的主要角色有:
中介者(Mediator) :定义与各个同事对象之间的通信接口。
具体中介者(Concrete Mediator) :实现中介者接口,负责协调各同事对象之间的通信和行为。
同事类(Colleague) :每个同事对象都通过中介者与其他同事对象通信,而不会直接与其他同事对象进行交互。
优点
减少类之间的依赖 :通过引入中介者,同事类不再直接依赖其他同事类,从而使代码更加松散耦合。
增强可维护性 :集中化管理对象的交互,可以更容易地修改或扩展某些行为,而不需要在各个同事类中进行变更。
更好的扩展性 :通过添加新的中介者或同事类,可以很容易地扩展系统。
缺点
中介者复杂性 :中介者模式虽然降低了同事类的复杂性,但中介者自身可能会变得复杂,尤其是当系统规模变大时,中介者需要处理的逻辑会相应变多。
适用场景
系统中对象之间有复杂的交互关系,导致类与类之间的依赖关系复杂且难以维护。
希望通过一个中间类来封装多个类之间的交互,使得这些类之间不再直接耦合。
上图展开:
观察者模式在功能上相当于中介者模式的广播模式
案例 以下是一个简单的中介者模式的C++实现示例,描述了多个同事对象(User 类)通过一个中介者(ChatMediator)来进行通信的情况。
用户可以关注其他用户,广播消息会发送给所有关注自己的用户,点对点消息发送则无视关注列表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 #include <iostream> #include <string> #include <unordered_map> #include <vector> class ChatMediator { public : virtual void sendMessage (const std::string &message, class User *user, const std::string &receiverName = "" ) = 0 ; virtual void addUser (User *user) = 0 ; virtual void followUser (const std::string &followerName, const std::string &followeeName) = 0 ; virtual ~ChatMediator () {} }; class User { protected : ChatMediator *mediator; std::string name; std::vector<std::string> followers; public : User (ChatMediator *mediator, const std::string &name) : mediator (mediator), name (name) {} virtual void send (const std::string &message, const std::string &receiverName = "" ) = 0 ; virtual void receive (const std::string &message) = 0 ; std::string getName () { return name; } std::vector<std::string> getFollowers () { return followers; } virtual void follow (const std::string &followeeName) = 0 ; virtual ~User () {} }; class ConcreteUser : public User{ public : ConcreteUser (ChatMediator *mediator, const std::string &name) : User (mediator, name) {} void send (const std::string &message, const std::string &receiverName = "" ) override { if (receiverName.empty ()) { std::cout << name << " 发送广播消息: " << message << std::endl; } else { std::cout << name << " 发送消息给 " << receiverName << ": " << message << std::endl; } mediator->sendMessage (message, this , receiverName); } void receive (const std::string &message) override { std::cout << name << " 收到消息: " << message << std::endl; } void follow (const std::string &followeeName) override { followers.push_back (followeeName); mediator->followUser (name, followeeName); } }; class ConcreteChatMediator : public ChatMediator{ private : std::unordered_map<std::string, User *> users; std::unordered_map<std::string, std::vector<std::string>> followMap; public : void addUser (User *user) override { users[user->getName ()] = user; followMap[user->getName ()] = {}; } void followUser (const std::string &followerName, const std::string &followeeName) override { followMap[followeeName].push_back (followerName); } void sendMessage (const std::string &message, User *user, const std::string &receiverName = "" ) override { if (receiverName.empty ()) { for (auto &follower : followMap[user->getName ()]) { users[follower]->receive (message); } } else { if (users.find (receiverName) != users.end ()) { users[receiverName]->receive (message); } else { std::cout << "用户 " << receiverName << " 不存在,消息发送失败。" << std::endl; } } } }; ChatMediator *chatMediator = new ConcreteChatMediator (); User *user1 = new ConcreteUser (chatMediator, "Alice" ); User *user2 = new ConcreteUser (chatMediator, "Bob" ); User *user3 = new ConcreteUser (chatMediator, "Charlie" ); chatMediator->addUser (user1); chatMediator->addUser (user2); chatMediator->addUser (user3); user1->follow ("Bob" ); user2->follow ("Alice" ); user2->follow ("Charlie" ); user1->send ("Hello, everyone!" ); user2->send ("Hey, Alice!" , "Alice" ); user3->send ("Hi, Bob!" , "Bob" ); user1->send ("Hello, David!" , "David" ); delete chatMediator; delete user1; delete user2; delete user3; Alice 发送广播消息: Hello, everyone! Bob 收到消息: Hello, everyone! Bob 发送消息给 Alice: Hey, Alice! Alice 收到消息: Hey, Alice! Charlie 发送消息给 Bob: Hi, Bob! Bob 收到消息: Hi, Bob! Alice 发送消息给 David: Hello, David! 用户 David 不存在,消息发送失败。 Charlie 发送消息给 Alice: Hello,Alice Alice 收到消息: Hello,Alice
下面以图示的方式展示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 classDiagram class ChatMediator { <<interface>> +sendMessage(message: string, user: User, receiverName: string) +addUser(user: User) +followUser(followerName: string, followeeName: string) } class User { <<abstract>> #mediator: ChatMediator #name: string #followers: vector<string> +User(mediator: ChatMediator, name: string) +getName() string +getFollowers() vector<string> +send(message: string, receiverName: string) +receive(message: string) +follow(followeeName: string) } class ConcreteUser { +ConcreteUser(mediator: ChatMediator, name: string) +send(message: string, receiverName: string) +receive(message: string) +follow(followeeName: string) } class ConcreteChatMediator { -users: unordered_map<string, User*> -followMap: unordered_map<string, vector<string>> +addUser(user: User) +followUser(followerName: string, followeeName: string) +sendMessage(message: string, user: User, receiverName: string) } ChatMediator <|.. ConcreteChatMediator User <|-- ConcreteUser ChatMediator --o User ConcreteChatMediator --* User
ChatMediator 是一个接口,被 ConcreteChatMediator 实现。
User 是一个抽象类,被 ConcreteUser 继承。
User 和 ChatMediator 之间有一个组合关系,表示 User 包含一个 ChatMediator 的引用。
ConcreteChatMediator 和 User 之间有一个聚合关系,表示 ConcreteChatMediator 管理多个 User 对象。
本质上是在各个实例中,以组合的方式添加了一个公共使用的中介者实例,统一通过这个中介者来调用或注册自身
策略模式 策略模式(Strategy pattern)
定义一组算法,将每个算法都封装在独立的类中,使得它们之间可以互换使用,使我们能够在运行时选择或更改算法,策略模式让算法独立于使用它的客户而变化 (客户不关心策略的实现逻辑),也称为政策模式(Policy)
思想主要是:将每个策略包装为一个单独的类,拥有相同的接口
根据不同的情况来选择不同的策略,进而达到使用不同算法的目的
这种模式特别适用于处理类似支付,排序或数据处理等场景,算法的变化不会影响系统的其他部分
策略模式与状态模式的主要差异
状态模式 是同样的方法不一样的结果(E.g.没钱状态下吃霸王餐,有钱状态下付钱)
策略模式是同样的方法,不同的策略,相同的结果(E.g.用支付宝和微信都是付钱了)
即:
状态模式 关注的是对象的状态变化以及状态变化对行为的影响,强调的是“同样的方法,不同的结果”。
策略模式 关注的是算法的选择和替换,强调的是“同样的方法,不同的策略,最终的结果可能相同”。
可以这么理解,飞有很多策略,普通飞是个策略,不飞也是个策略,通过成员,将飞的各种策略兼容到一个统一的飞行行为(使用组合方式连接策略)
案例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 class FlyBehavior { public : virtual ~FlyBehavior () = default ; virtual void fly () const = 0 ; }; class FlyWithWing : public FlyBehavior{ public : void fly () const override { std::cout << "I'm flying with wings!" << std::endl; } }; class FlyNoWay : public FlyBehavior{ public : void fly () const override { std::cout << "I can't fly!" << std::endl; } }; class Duck { protected : std::unique_ptr<FlyBehavior> flyBehavior; public : Duck (std::unique_ptr<FlyBehavior> fb) : flyBehavior (std::move (fb)) {} virtual ~Duck () = default ; void performFly () const { flyBehavior->fly (); } virtual void display () const = 0 ; void setFlyBehavior (std::unique_ptr<FlyBehavior> fb) { flyBehavior = std::move (fb); } void quack () const { std::cout << "Quack!" << std::endl; } void swim () const { std::cout << "Swimming!" << std::endl; } }; class GreenDuck : public Duck{ public : GreenDuck () : Duck (std::make_unique <FlyWithWing>()) {} void display () const override { std::cout << "I'm a green duck!" << std::endl; } }; class RedDuck : public Duck{ public : RedDuck () : Duck (std::make_unique <FlyNoWay>()) {} void display () const override { std::cout << "I'm a red duck!" << std::endl; } }; class YellowDuck : public Duck{ public : YellowDuck () : Duck (std::make_unique <FlyWithWing>()) {} void display () const override { std::cout << "I'm a yellow duck!" << std::endl; } }; GreenDuck greenDuck; RedDuck redDuck; YellowDuck yellowDuck; greenDuck.display (); greenDuck.performFly (); redDuck.display (); redDuck.performFly (); yellowDuck.display (); yellowDuck.performFly (); std::cout << "\nChanging RedDuck's flying behavior..." << std::endl; redDuck.setFlyBehavior (std::make_unique <FlyWithWing>()); redDuck.display (); redDuck.performFly (); I'm a green duck! I' m flying with wings!I'm a red duck! I can' t fly!I'm a yellow duck! I' m flying with wings!Changing RedDuck's flying behavior... I' m a red duck!I'm flying with wings!
UML类图
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 classDiagram class FlyBehavior { <<interface>> +fly() const } class FlyWithWing { +fly() const } class FlyNoWay { +fly() const } class Duck { +performFly() const +display() const +setFlyBehavior(FlyBehavior*) void +quack() const +swim() const } class GreenDuck { +display() const } class RedDuck { +display() const } class YellowDuck { +display() const } FlyBehavior <|-- FlyWithWing FlyBehavior <|-- FlyNoWay Duck o-- FlyBehavior Duck <|-- GreenDuck Duck <|-- RedDuck Duck <|-- YellowDuck
FlyBehavior 是一个接口(抽象类),并且有两个具体的实现类:FlyWithWing 和 FlyNoWay,表示不同的飞行行为。
Duck 是一个基类,其中包含 FlyBehavior 的成员,鸭子可以调用 performFly() 执行飞行行为。Duck 类有具体的子类 GreenDuck、RedDuck 和 YellowDuck,它们分别代表不同种类的鸭子。
状态模式 状态模式是一种行为型设计模式,用于解决对象在不同状态下的行为变化和转换的问题。在状态模式中,对象可以在内部状态发生改变时改变其行为,而不需要改变对象本身的类。这样可以使对象的行为更加灵活,易于扩展和维护。
以面向对象的方式实现状态机,每个状态都会被实现为状态接口的一个派生类,状态的转换通过调用接口中的定义的方法来处理 .状态模式可以看作是策略模式的一种变体,其中当前的策略会根据状态转换而改变
在状态模式中,通常会定义一个状态接口或者抽象类,表示不同的状态,然后具体的状态类实现这个接口或者继承这个抽象类,每个具体的状态类实现自己的行为逻辑。另外,还会定义一个上下文类,用于维护当前的状态对象,并根据不同的状态对象调用对应的行为。
状态模式可以有效地将复杂的状态逻辑进行解耦和封装,使代码更加清晰和易于理解。它常用于处理对象在不同状态下的行为变化,例如状态机、工作流等场景。
当一个对象的行为依赖于他的状态,并且需要在运行时根据状态切换行为时,可以考虑使用状态模式
这种模式特别适用于具有复杂状态转换逻辑的系统 状态模式通过将状态与行为分离,使对象可以根据状态的变化动态调整其行为 ,这种模式提高了系统的灵活性和可维护性,使我们能轻松管理和拓展对象的状态逻辑
以一个极光炮塔的状态拆解为例:
状态模式实例
上图中的微波炉有三种状态
加热 Heating
开门 DoorOpen
关门 DoorClosed
四种操作
开门 (只能在关门状态下进行)
关门 (只能在开门状态下进行)
启动 (只能在关门状态下进行)
停止 (只能在加热状态下进行)
如果在操作中判断状态,代码中会充斥着各种条件语句
这时可以使用状态模式
每个状态对应的操作封装在一起
状态图(State Diagram)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 stateDiagram-v2 [*] --> DoorClosed DoorClosed --> DoorOpen : OpenDoor() DoorOpen --> DoorClosed : CloseDoor() DoorClosed --> Heating : Start() Heating --> DoorClosed : Stop() Heating --> DoorOpen : OpenDoor() state DoorClosed { [*] --> Idle Idle --> Ready : DoorClosed() } state DoorOpen { [*] --> Open Open --> Waiting : WaitForClose() } state Heating { [*] --> HeatingProcess HeatingProcess --> Complete : Finish() }
C++完整代码案例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 #include <iostream> #include <string> class MicrowaveOven ; class MicrowaveState { protected : MicrowaveOven *microwave; public : virtual ~MicrowaveState () = default ; void setMicrowave (MicrowaveOven *oven) { this ->microwave = oven; } virtual void OpenDoor () = 0 ; virtual void CloseDoor () = 0 ; virtual void Start () = 0 ; virtual void Stop () = 0 ; }; class HeatingState : public MicrowaveState{ public : void OpenDoor () override { std::cout << "Can't open the door while heating." << std::endl; } void CloseDoor () override { std::cout << "Door is already closed." << std::endl; } void Start () override { std::cout << "Already heating." << std::endl; } void Stop () override { std::cout << "Stopping heating..." << std::endl; } }; class DoorOpenState : public MicrowaveState{ public : void OpenDoor () override { std::cout << "Door is already open." << std::endl; } void CloseDoor () override { std::cout << "Closing the door..." << std::endl; } void Start () override { std::cout << "Can't start heating with the door open." << std::endl; } void Stop () override { std::cout << "Already stopped." << std::endl; } }; class DoorClosedState : public MicrowaveState{ public : void OpenDoor () override { std::cout << "Opening the door..." << std::endl; } void CloseDoor () override { std::cout << "Door is already closed." << std::endl; } void Start () override { std::cout << "Starting heating..." << std::endl; } void Stop () override { std::cout << "Already stopped." << std::endl; } }; class MicrowaveOven { private : MicrowaveState *state; public : MicrowaveOven (MicrowaveState *initState) : state (initState) { state->setMicrowave (this ); } void setState (MicrowaveState *newState) { this ->state = newState; state->setMicrowave (this ); } void OpenDoor () { state->OpenDoor (); } void CloseDoor () { state->CloseDoor (); } void Start () { state->Start (); } void Stop () { state->Stop (); } }; MicrowaveOven microwave (new DoorClosedState()) ;microwave.Start (); microwave.OpenDoor (); microwave.Stop (); microwave.OpenDoor ();
UML类图:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 classDiagram class MicrowaveState { +MicrowaveOven* microwave +setMicrowave(MicrowaveOven* oven) +OpenDoor() : void +CloseDoor() : void +Start() : void +Stop() : void } class HeatingState { +OpenDoor() : void +CloseDoor() : void +Start() : void +Stop() : void } class DoorOpenState { +OpenDoor() : void +CloseDoor() : void +Start() : void +Stop() : void } class DoorClosedState { +OpenDoor() : void +CloseDoor() : void +Start() : void +Stop() : void } class MicrowaveOven { -MicrowaveState* state +MicrowaveOven(MicrowaveState* initState) +setState(MicrowaveState* newState) : void +OpenDoor() : void +CloseDoor() : void +Start() : void +Stop() : void } MicrowaveState <|-- HeatingState MicrowaveState <|-- DoorOpenState MicrowaveState <|-- DoorClosedState MicrowaveOven o-- MicrowaveState
MicrowaveState 是抽象类,它有多个子类(HeatingState、DoorOpenState、DoorClosedState),分别代表微波炉的不同状态。
MicrowaveOven 类与 MicrowaveState 有一个聚合关系,表示微波炉包含一个当前状态对象。
与策略模式的意图差异
策略模式侧重: 在外部代码灵活的改变内部代码的策略的切换
状态模式侧重: 行为根据状态的自动切换
状态机 状态机可根据状态数量分为两类:
有限状态机重要性 在工业控制领域,状态机是一个极其重要的概念,它决定了设备能否按照计划流程加工,以及及时的停止加工,防止物料受损,一个好的状态机在异常处理上有着良好的 Handle 能力,需要通过一个状态模型来尽可能涵盖可能发生的情况。
一个抽象的状态机模型可以拆分为若干个状态节点,而每完成一个节点,下一个节点的动作才会被触发,我们称每一个这样的节点,为一步(Step),因此状态机可以用有向图(Directed graph) 来表示,在实际实现状态机的数据结构中,也常用作图来编写整个流程。
通常状态机可以分为两种不同的类型,
由于工艺制程的复杂性,半导体领域通常使用的是 Mealy 状态机。需要注意的是,几乎所有的状态机有自己完整的生命周期(Life Cycle)钩子,通常在被初始化(Initialization)、状态转移(State Transition)、事件触发(Event Trigger)、动作(Action)、销毁的时候,会触发相应的钩子函数,以实现更加强壮的功能,我相信很多学习前端 Vue 的朋友应该都会比较熟悉,可以把它简单的理解为一个 DOM 从创建、渲染到销毁的过程。
绘制状态机 不仅可以拆解状态模式的实现思路
还可以帮助我们梳理和表达产品需求
注意设计时需要全面充分 ,画完图要检查是否有无法进入或无法离开的状态,排除会导致状态机可以同时处在不同状态下模棱两可的切换箭头
优秀的状态模式设计 参考此项目
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 classDiagram class StateMachine~TState, TTrigger~ { +StateMachine() +Configure(state: TState) +Fire(trigger: TTrigger) } class ActivateActionBehaviour { +Execute() +ExecuteAsync() Task } StateMachine <|-- ActivateActionBehaviour class DeactivateActionBehaviour { +Execute() +ExecuteAsync() Task } StateMachine <|-- DeactivateActionBehaviour class EntryActionBehavior { +Execute(transition: Transition, args: object[]) +ExecuteAsync(transition: Transition, args: object[]) Task } StateMachine <|-- EntryActionBehavior class TriggerBehaviour { +GuardConditionsMet(args: object[]) bool +UnmetGuardConditions(args: object[]) ICollection~string~ } StateMachine <|-- TriggerBehaviour class TransitionGuard { +GuardConditionsMet(args: object[]) bool +UnmetGuardConditions(args: object[]) ICollection~string~ } StateMachine <|-- TransitionGuard class GuardCondition { +Guard: Func~object[], bool~ +Description: string } TransitionGuard o-- GuardCondition class Transition { +Source: TState +Destination: TState +Trigger: TTrigger } StateMachine <.. Transition class DynamicTriggerBehaviour { +GetDestinationState(source: TState, args: object[], out destination: TState) } StateMachine <|-- DynamicTriggerBehaviour DynamicTriggerBehaviour --|> TriggerBehaviour
备忘录模式 备忘录模式(Memento Pattern)又称之为快照模式(Snapshop Pattern)或者令牌模式(Token Pattern)
备忘录模式: 用于保存和恢复对象的状态,而不会破坏对象的封装性
适用场景 : 当需要在不破坏封装性的前提下捕获和恢复对象状态时,可以使用备忘录模式.如:在实现撤销/重做功能时,备忘录模式非常有用,可以帮你将对象的状态存储起来,并在需要时恢复到之前的状态
核心思想: 序列化,以及栈
以游戏保存为例:
上图中的堆栈(回退栈),用于存储序列化后的状态信息
案例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 #include <iostream> #include <string> #include <memory> #include <stack> #include <ctime> class ArticleMemento {private : std::string title; std::string content; std::time_t createTime; public : ArticleMemento (const std::string& title, const std::string& content, std::time_t createTime) : title (title), content (content), createTime (createTime) {} std::string getTitle () const { return title; } std::string getContent () const { return content; } std::time_t getCreateTime () const { return createTime; } }; class ArticleText {private : std::string title; std::string content; std::time_t createTime; public : ArticleText (const std::string& title, const std::string& content, std::time_t createTime) : title (title), content (content), createTime (createTime) {} std::string getTitle () const { return title; } std::string getContent () const { return content; } std::time_t getCreateTime () const { return createTime; } void setTitle (const std::string& newTitle) { title = newTitle; } void setContent (const std::string& newContent) { content = newContent; } void setCreateTime (std::time_t newTime) { createTime = newTime; } std::shared_ptr<ArticleMemento> saveToMemento () const { return std::make_shared <ArticleMemento>(title, content, createTime); } void getArticleFromMemento (const std::shared_ptr<ArticleMemento>& memento) { if (memento) { title = memento->getTitle (); content = memento->getContent (); createTime = memento->getCreateTime (); } } }; class ArticleCaretaker {private : std::stack<std::shared_ptr<ArticleMemento>> mementoStack; public : void saveState (const std::shared_ptr<ArticleMemento>& memento) { mementoStack.push (memento); } std::shared_ptr<ArticleMemento> undo () { if (mementoStack.empty ()) { std::cout << "No states to undo!" << std::endl; return nullptr ; } auto memento = mementoStack.top (); mementoStack.pop (); return memento; } bool hasStates () const { return !mementoStack.empty (); } }; int main () { std::time_t now = std::time (nullptr ); ArticleText article ("Design Patterns" , "Learning Memento Pattern" , now) ; ArticleCaretaker caretaker; std::cout << "Initial State: " << article.getTitle () << ", " << article.getContent () << ", Created at: " << std::ctime (&article.getCreateTime ()) << std::endl; caretaker.saveState (article.saveToMemento ()); article.setTitle ("Updated Design Patterns" ); article.setContent ("Updated content for Memento pattern." ); std::time_t later = std::time (nullptr ); article.setCreateTime (later); std::cout << "Modified State: " << article.getTitle () << ", " << article.getContent () << ", Created at: " << std::ctime (&article.getCreateTime ()) << std::endl; caretaker.saveState (article.saveToMemento ()); article.setTitle ("Final Design Patterns" ); article.setContent ("Final content." ); article.setCreateTime (std::time (nullptr )); std::cout << "Final State: " << article.getTitle () << ", " << article.getContent () << ", Created at: " << std::ctime (&article.getCreateTime ()) << std::endl; std::cout << "\nUndoing to previous state..." << std::endl; if (caretaker.hasStates ()) { article.getArticleFromMemento (caretaker.undo ()); std::cout << "Restored State: " << article.getTitle () << ", " << article.getContent () << ", Created at: " << std::ctime (&article.getCreateTime ()) << std::endl; } std::cout << "\nUndoing to initial state..." << std::endl; if (caretaker.hasStates ()) { article.getArticleFromMemento (caretaker.undo ()); std::cout << "Restored Initial State: " << article.getTitle () << ", " << article.getContent () << ", Created at: " << std::ctime (&article.getCreateTime ()) << std::endl; } return 0 ; } Initial State: Design Patterns, Learning Memento Pattern, Created at: Thu Oct 3 22 :43 :18 2024 Modified State: Updated Design Patterns, Updated content for Memento pattern., Created at: Thu Oct 3 22 :43 :20 2024 Final State: Final Design Patterns, Final content., Created at: Thu Oct 3 22 :43 :22 2024 Undoing to previous state... Restored State: Updated Design Patterns, Updated content for Memento pattern., Created at: Thu Oct 3 22 :43 :20 2024 Undoing to initial state... Restored Initial State: Design Patterns, Learning Memento Pattern, Created at: Thu Oct 3 22 :43 :18 2024
UML类图
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 classDiagram class ArticleMemento { -string title -string content -time_t createTime +getTitle() : string +getContent() : string +getCreateTime() : time_t } class ArticleText { -string title -string content -time_t createTime +saveToMemento() : ArticleMemento +getArticleFromMemento(memento : ArticleMemento) +getTitle() : string +getContent() : string +getCreateTime() : time_t +setTitle(newTitle : string) +setContent(newContent : string) +setCreateTime(newTime : time_t) } class ArticleCaretaker { -stack<ArticleMemento> mementoStack +saveState(memento : ArticleMemento) +undo() : ArticleMemento +hasStates() : bool } ArticleText --> ArticleMemento : save / restore ArticleCaretaker --> ArticleMemento : manage ArticleMemento : -string title ArticleMemento : -string content ArticleMemento : -time_t createTime ArticleCaretaker : -stack mementoStack
模板方法模式 Template Method
模板方法设计模式让你能够定义一个算法的通用结构,并将具体的实现延迟到子类中
模板方法在基类中定义了一个算法的骨架,并允许子类在不改变算法结构的前提下重新定义算法的某些步骤。
适用场景 : 当你有一个通用的流程需要多个类共享,但某些步骤需要根据具体情况进行自定义时,可以考虑使用模板方法设计模式,例如数据处理流程,文档生成流程等
模板方法模式与策略模式 的区别
模版方法模式:强调的是在一个通用框架内定义算法的结构,然后让子类根据需要重写部分步骤.它更关注的是一个固定的流程,而不是选择不同的算法
模版方法是通过继承 来实现算法结构的部分定制
策略模式:则更强调算法的可替换性,允许你在运行时选择不同的算法实现,但不会影响算法的结构
策略模式是将算法的每个实现封装在独立的类中,并通过接口 使它们可以互换使用
案例就省略了,因为无非也就是继承而已
访问者模式 Visitor
核心思想:不修改现有代码结构的情况下,为对象添加新的功能
访问者设计模式通过将算法与它操作的对象结构分离 ,允许在不修改这些对象结构的情况下为其添加新操作.符合开放封闭原则(Open/Closed Principle)
适用场景 : 当需要在不修改对象结构的情况下,为对象添加新功能时,可以考虑使用访问者模式.这种模式特别适用于那些对象结构稳定但需要经常添加新操作的场景.
访问者设计模式通过引入访问者对象,允许我们在不修改对象结构的前提下,为对象添加新功能。在实际开发中,访问者模式可以帮助我们保持代码的开闭原则,提高系统的可扩展性和维护性。
访问者模式通常用于处理复杂的对象结构,比如复合对象(Composite)或对象集合(如树结构),使得对这些对象的操作更加灵活 。
访问者模式可以说是GOF23中设计模式中最复杂的一个,但日常开发中使用频率却不高
最关键的两点:
要点总结:
准确识别出Visitor实用的场景,如果一个对象结构不稳定决不可使用 ,不然在增删元素时改动将非常巨大。
对象结构中的元素要可以迭代访问
Visitor里一般存在与元素个数相同的visit方法。
元素通过accept方法通过this将自己传递给了Visitor。
优点
使得给结构稳定的对象增加新算法变得容易,提搞了代码的可维护性,可扩展性。
缺点
太复杂,特别是伪动态双分派,不仔细理解很难想清楚。
访问者模式使用频率较低,而且复杂,可酌情掌握
当我们需要统一访问接口的时候,有如下办法:
方案(C#中对应关键词)
适用场景
优点
缺点
显式接口
需要类型安全且子类有部分共同行为
编译时检查,代码清晰
需预先定义接口
动态类型 (dynamic)
快速原型或不确定成员结构
灵活
失去编译时检查
通用容器 (object)
完全动态的松散结构
高度灵活
完全无类型安全
访问者模式
复杂对象结构且处理逻辑多变
解耦数据与操作
代码复杂度高
案例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 #include <iostream> #include <vector> #include <memory> class ElementA ;class ElementB ;class Visitor {public : virtual ~Visitor () = default ; virtual void visit (ElementA* elementA) = 0 ; virtual void visit (ElementB* elementB) = 0 ; }; class Element {public : virtual ~Element () = default ; virtual void accept (Visitor* visitor) = 0 ; }; class ElementA : public Element {public : void accept (Visitor* visitor) override { visitor->visit (this ); } void operationA () const { std::cout << "ElementA operation\n" ; } }; class ElementB : public Element {public : void accept (Visitor* visitor) override { visitor->visit (this ); } void operationB () const { std::cout << "ElementB operation\n" ; } }; class ConcreteVisitor1 : public Visitor {public : void visit (ElementA* elementA) override { std::cout << "ConcreteVisitor1 visiting ElementA\n" ; elementA->operationA (); } void visit (ElementB* elementB) override { std::cout << "ConcreteVisitor1 visiting ElementB\n" ; elementB->operationB (); } }; class ConcreteVisitor2 : public Visitor {public : void visit (ElementA* elementA) override { std::cout << "ConcreteVisitor2 visiting ElementA, performing a different operation\n" ; elementA->operationA (); } void visit (ElementB* elementB) override { std::cout << "ConcreteVisitor2 visiting ElementB, performing a different operation\n" ; elementB->operationB (); } }; int main () { std::vector<std::unique_ptr<Element>> elements; elements.push_back (std::make_unique <ElementA>()); elements.push_back (std::make_unique <ElementB>()); ConcreteVisitor1 visitor1; std::cout << "Using ConcreteVisitor1:\n" ; for (const auto & element : elements) { element->accept (&visitor1); } ConcreteVisitor2 visitor2; std::cout << "\nUsing ConcreteVisitor2:\n" ; for (const auto & element : elements) { element->accept (&visitor2); } return 0 ; } Using ConcreteVisitor1: ConcreteVisitor1 visiting ElementA ElementA operation ConcreteVisitor1 visiting ElementB ElementB operation Using ConcreteVisitor2: ConcreteVisitor2 visiting ElementA, performing a different operation ElementA operation ConcreteVisitor2 visiting ElementB, performing a different operation ElementB operation
核心: 每个元素(ElementA, ElementB)通过 accept 方法将访问者(ConcreteVisitor)引入元素内部,使访问者调用特定的操作
uml类图
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 classDiagram direction TB class Visitor { +visit(ElementA* elementA) +visit(ElementB* elementB) } class ConcreteVisitor1 { +visit(ElementA* elementA) +visit(ElementB* elementB) } class ConcreteVisitor2 { +visit(ElementA* elementA) +visit(ElementB* elementB) } class Element { +accept(Visitor* visitor) } class ElementA { +accept(Visitor* visitor) +operationA() } class ElementB { +accept(Visitor* visitor) +operationB() } Visitor <|-- ConcreteVisitor1 Visitor <|-- ConcreteVisitor2 Element <|-- ElementA Element <|-- ElementB ElementA --> Visitor : accept() ElementB --> Visitor : accept()
Visitor 是访问者的基类,定义了 visit(ElementA*) 和 visit(ElementB*) 两个方法。
ConcreteVisitor1 和 ConcreteVisitor2 是两个具体的访问者,实现了访问元素的方法。
Element 是元素的基类,包含 accept(Visitor*) 方法。
ElementA 和 ElementB 是具体的元素,它们实现了 accept 方法,并有各自的操作方法 operationA 和 operationB。
UML UML: 统一建模语言的简称
UML是面向对象分析与设计的工具
分值: 18分 = 3分概念 + 15分大题
上半的考点主要是识图
图类型
描述
类图
一组对象、接口、协作和它们之间的关系
对象图
一组对象以及它们之间的关系,他可以体现某一个时刻的对象的静态快照
用例图
用例、参与者以及它们之间的关系
序列图
场景的图像化表示,以时间顺序组织 的对象间的交互活动
通信图
强调收发消息的对象之间的组织结构
状态图
展现了一个状态机,由状态、转换、事件和活动组成
活动图
专注于系统的动态视图,一个活动到另一个活动的流程
组合结构图
分解类、组建或用例的内部结构
交互图
组合了序列图和活动图的特征,显示用例活动中对象如何交互
定时图
关注对象在改变状态时的时间约束条件
组件图/构件图
一组构件之间的组织和依赖,专注于系统的静态实现视图
部署图
运行处理结点以及构件的配置,给出体系结构的静态实现视图,涉及到软硬件的部署关系
包图
描述类或其他UML如何组织成包,以及包之间的依赖关系
对象图和类图的结构是一致的
在UML图中,(D)图用于展示所交付系统中软件组件和硬件之间的物理关系。 A. 类 B. 组件 C. 通信 D. 部署
各种图 类图和对象图
填类名,方法名,属性名
填多重度
填关系
用例图
参与者: 人,组织结构,系统,硬件…
用例名: 名词+动词 动词+名词
关系: 拓展,包含,泛化
细化用例描述
1 2 3 4 5 6 7 8 9 10 11 用例规约描述如下。 参与者:顾客。 主要事件流: 1. 顾客选择需要购买的饮料和数量,投入硬币; 2. 自动售货机检查顾客是否投入足够的硬币; 3. 自动售货机检查饮料储存仓中所选购的饮料是否足够; 4. 自动售货机推出饮料; 5. 自动售货机返回找要。 备选事件流: 2a. 若投入的硬币不足,则给出提示并退回到1; 3a. 若所选购的饮料数量不足,则给出提示并退回到1。
用例图中的关系参考此处
用例建模的流程:
识别参与者(必须)
合并需求获得用例(必须)
细化用例描述(必须)
调整用例模型(可选)
在UML用例图中,参与者表示(A)。 A. 人、硬件或其他系统可以扮演的角色 ✅:只有这个是对的,其他选项都有一定的局限性 B. 可以完成多种动作的相同用户 C. 不管角色的实际物理用户 D. 带接口的物理系统或者硬件设计
用例图的题干分析过程如下:
顺序图 顺序图 (sequence diagram,序列图) 。顺序图是一种交互图 (interaction diagram),交互图展现了一种交互,它由一组对象或参与者以及它们之间可能发送的消息构成。交互图专注于系统的动态视图。顺序图是强调消息的时间次序的交互图。
通信图 通信图 (communication diagram) 。通信图也是一种交互图,它强调收发消息的对象或参与者的结构组织。顺序图和通信图表达了类似的基本概念,但它们所强调的概念不同,顺序图强调的是时序,通信图强调的是对象之间的组织结构(关系) 。
注意的是
消息机制: 这个意思是在Session类对象调用了CardReader类对象中的readCard函数
活动图 活动图 (activity diagram) 。活动图将进程或其他计算结构展示为计算内部一步步的控制流和数据流 。活动图专注于系统的动态视图。它对系统的功能建模和业务流程建模特别重要,并强调对象间的控制流程。
是唯一可以表示并发 的UML图
活动名
开始,结束
并发分支,并发汇合
活动发起者
活动图还可以根据发起活动的对象来划分,形成一个类似泳道的过程,被称为泳道活动图,如下图
状态图 状态图 (state diagram) 。状态图描述一个状态机,它由状态、转移、事件和活动组成。状态图给出了对象的动态视图。它对于接口、类或协作的行为建模尤为重要,而且它强调事件导致的对象行为,这非常有助于对反应式系统建模。
和活动图很类似,但是二者最大的区别在于他的节点是状态,而不是活动
例子:
以下关于UML状态图的叙述中,不正确的是(B)。 A. 活动可以在状态内执行,也可以在迁移时执行 B. 若事件触发一个没有特定监护条件的迁移,则对象离开当前状态 ❌ C. 迁移可以包含事件触发器、监护条件和状态 D. 事件触发迁移
状态图的题干分析过程:
构件图 构件图 (component diagram) 。构件图描述一个封装的类和它的接口、端口,以及由内嵌的构件和连接件构成的内部结构。构件图用于表示系统的静态设计实现视图。对于由小的部件构建大的系统来说,构件图是很重要的。构件图是类图的变体。
也可称为组件图
大图标画法
小图标画法
锁需要钥匙,那么锁是需接口,画为半圆;钥匙是供接口,画为圆形
部署图 部署图 (deployment diagram)。部署图描述对运行时的处理节点及在其中生存的构件的配置。部署图给出了架构的静态部署视图,通常一个节点包含一个或多个部署图。
是唯一描述了物理分布关系 的UML图
立方体代表一个节点
UML关系 两个类之间的关系可以有多个,可以标识角色,可以标识多重度
UML中的多重度表示为
1:表示一个集合中的一个对象对应另一个集合中1个对象。
0..*/*表示一个巢合中的一个对象对应另一个藥合中的0个或多个对象。 (可以不对应)
1..*:表示一个集合中的一个对象对应另一个第合中的一个或多个对象。 (至少对应一个)
0..1:表示一个集合宋的一个对象对应另一个集合中的0个或1个对象。 (可以不对应)
作为对比,在ER图中多就只有*或n的表示
类图中的关系
举例组合与聚合的差异
组合关系 : 比如说翅膀和大雁,财务部和公司,如果大雁死去了,翅膀也就没有关系了;如果公司倒闭了,财务部也就没有意义了
聚合关系 : 比如说班级和学生,毕业后班级解散了,但是学生还是存在的,即生命周期不同,这种情况属于聚合关系
UML中关联是一个结构关系,描述了一组链。两个类之间(C)关联。 A. 不能有多个 B. 可以有多个由不同角色标识的 C. 可以有任意多个 ❌: 应该是由角色标识的,而不是任意多个 D. 的多个关联必须聚合成一个
用例图中的关系
包含关系 <<include>>:其中这个提取出来的公共用例 称为抽象用例,而把原始用例称为基本用例或基础用例系:当可以从两个或两个以上的用例中提取公共行为时,应该使用包含关系来表示它们。 [必选关系]
扩展关系 <<extend>>:如果一个用例明显地混合了两种或两种以上的不同场景,即根据情况可能发生多种分支,则可以将这个用例分为一个基本用例和一个或多个扩展用例,这样使描述可能更加清晰。 [可选关系]
泛化关系 :当多个用例共同拥有一种类似的结构和行为的时候,可以将它们的共性抽象成为父用例,其他的用例作为泛化关系中的子用例。在用例的泛化关系中,子用例是父用例的一种特殊形式,子用例继承了父用例所有的结构、行为和关系。 [必选关系]
UML类图
面向对象设计主要就是使用UML的类图,类图用于描述系统中所包含的类以及它们之间的相互关系,帮助人们简化对系统的理解,它是系统分析和设计阶段的重要产物,也是系统编码和测试的重要模型依据。
类的UML画法 类(Class)封装了数据和行为,是面向对象的重要组成部分,它是具有相同属性、操作、关系的对象集合的总称。在系统中,每个类都具有一定的职责,职责指的是类要完成什么样子的功能,要承担什么样子的义务。一个类可以有多种职责,但是设计得好的类一般只有一种职责。
看到该图分为三层:最顶层的为类名,中间层的为属性,最底层的为方法。
属性的表示方式为:【可见性】【属性名称】:【类型】={缺省值,可选}
方法的表示方式为:【可见性】【方法名称】(【参数列表】):【类型】
可见性都是一样的,-表示private、+表示public、#表示protected。
关系画法总结
所有关系详解如下
继承关系(Inheritance):表示一个类(子类)继承另一个类(父类)的属性和方法。
关联关系(Association):表示一个类中包含另一个类的对象作为成员变量。
聚合关系(Aggregation):表示一个类将另一个类的对象作为引用持有,但两者之间并不是强耦合关系。
组合关系(Composition):表示一个类在构造方法中实例化另一个类的对象,两者之间是强耦合关系。
组合优于继承原则 : 虽然组合关系在某种程度上是强耦合的(因为一个对象的存在依赖于另一个对象),但它通常被视为比继承低耦合的设计选择。因为组合允许在运行时动态地组合不同的对象,而继承关系在编译时是固定的。
依赖关系(Dependency):表示一个类的方法使用另一个类的对象作为参数,但两者之间不是拥有关系。
继承关系
继承也叫作泛化(Generalization),用于描述父子类之间的关系,父类又称为基类或者超类,子类又称作派生类。在UML中,泛化关系用带空心三角形的实线 来表示。
普通继承关系
抽象继承关系 比方说我想实现一个链表(Link),插入(insert)与删除(remove)动作我想让子类去实现,链表本身只实现统计链表中元素个数的动作(count),然后有一个子类单向链表(OneWayLink)去实现父类没有实现的动作,UML类图为:
在UML中,抽象类无论类名还是抽象方法名,都以斜体 的方式表示,因为这也是一种继承关系,所以子类与父类通过带空心三角形的实线来联系。
关联关系
关联(Assocition)关系是类与类之间最常见的一种关系,它是一种结构化的关系,表示一类对象与另一类 对象之间有联系,如汽车和轮胎、师傅和徒弟、班级和学生等。在UML类图中,用箭头线 连接有关联关系的对 象所对应的类,在C++中通常将一个类的对象作为另一个类的成员变量。关联关系分单向关联、双向关联、 自关联
单向关联关系
双向关联关系 默认情况下的关联都是双向的,比如顾客(Customer)购买商品(Product),反之,卖出去的商品总是与某个顾客与之相关联,这就是双向关联。类图如下:
自关联关系 自关联,指的就是对象中的属性为对象本身,这在链表中非常常见,单向链表Node中会维护一个它的前驱Node,双向链表Node中会维护一个它的前驱Node和一个它的后继Node。就以单向链表为例,UML类图为:
聚合关系
聚合(Aggregation)关系表示整体与部分的关系。在聚合关系中,整体对象持有成员对象的引用 ,成员对象是整体的一部分,但是成员对象可以脱离整体对象独立存在。在UML中,聚合关系用带空心菱形的箭头线 表示
如汽车(Car)与引擎(Engine)、轮胎(Wheel)、车灯(Light),类图 表示为:
代码实现聚合关系,成员对象通常以构造方法、Setter方法的方式注入到整体对象之中。
依赖注入:
通过构造方法注入,整体对象在实例化时会接收成员对象作为参数,并将其保存在内部。
通过Setter方法注入,整体对象可以在运行时动态设置或更改成员对象。这种注入方式可以实现对象之间的松耦合,提高代码的灵活性和可维护性。
组合关系
组合(Composition)关系也表示的是一种整体和部分的关系,但是在组合关系中整体对象可以控制成员对象的生命周期,一旦整体对象不存在,成员对象也不存在,整体对象和成员对象之间具有同生共死的关系。在UML中组合关系用带实心菱形的 箭头线表示。
比如人的头(Head)和嘴巴(Mouth)、鼻子(Nose),嘴巴和鼻子是头的组成部分之一,一旦头没了,嘴巴也没了,因此头和嘴巴、鼻子是组合关系,类图表示为:
代码实现组合关系,通常在整体类的构造方法中直接实例化成员类 ,因为组合关系的整体和部分是共生关系 ,如果通过外部注入,那么即使整体不存在,那么部分还是存在的,这就相当于变成了一种聚合关系了
依赖关系
依赖(Dependency)关系是一种使用关系,特定事物的改变有可能会影响到使用该事物的其他事物,在需要表示一个事物使用另一个事物时使用依赖关系,大多数情况下依赖关系体现在某个类的方法使用另一个类的对象作为参数。在UML中,依赖关系用带箭头的虚线 表示,由依赖的一方指向被依赖的一方。
比如,驾驶员(Driver)开车,Driver类的drive()方法将车(Car)的对象作为一个参数传递,以便在drive()方法中能够调用car的move()方法,且驾驶员的drive()方法依赖车的move()方法,因此也可以说Driver依赖Car,类图为:
依赖关系通常通过三种方式 来实现:
将一个类的对象作为另一个类中方法的参数
在一个类的方法中将另一个类的对象作为其对象的局部变量
在一个类的方法中调用另一个类的静态方法
关联、聚合、组合之间的区别 关联和 聚合的区别主要在于语义上: 关联的两个对象之间一般是平等的,聚合则一般是不平等的 。
聚合和 组合的区别则在语义和实现上都有差别: 组合的两个对象之间生命周期有很大的关联,被组合的对象在组合对象创建的同时或者创建之后创建,在组合对象销毁之前销毁,一般来说被组合对象不能脱离组合对象独立存在,而且也只能属于一个组合对象(可以理解成使用栈空间);聚合则不一样,被聚合的对象可以属于多个聚合对象
实际应用中,三种关系其实没有区分得这么清楚,不需要把细节扣得这么细,合理利用对象之间的关系给出设计方案即可。
关系画法总结跳转
绘制uml类图的网址跳转