设计模式笔记
关于设计模式
1. 如何阅读本文
略
2. 面向对象程序设计简介
2.1 面向对象程序设计基础
面向对象程序设计 (Object-Oriented Programming,缩写为 OOP)是一种范式,其基本理念是将 数据块 及 与数据相关的行为 封装成为特殊的、名为对象的 实体,同时对象实体的生成工作则是基于程序员给出的一系列 “蓝图”,这些 “蓝图”就是 类。
2.1.1 对象和类 - 对象是类的实例
- 成员变量 --> 数据 --> 状态
- 方法 --> 行为
2.1.1 层次结构
一些类可能会组织起来形成 类层次结构。
定义的父类被称为超类。继承它的类被称为子类。子类会继承其父类的状态和行为,其中只需定义不同于父类的属性或行为。
这种由各种类组成的金字塔就是 层次结构图。
如果展示类之间的关系比展示其内容更重要的话,那可对 UML 图中的类进行简化。
子类可以对从父类中继承而来的方法的行为进行重写。子类可以完全替换默认行为,也可以仅提供额外内容来对其进行加强。
2.2 面向对象程序设计
有四个要点:
2.2.1 抽象
抽象 是一种反映 真实世界对象 或 现象中特定内容 的模型,它能高精度地反映 所有与特定内容相关的 详细信息,同时忽略其他内容。
开发一款程序时,你的对象只需模拟真实对象的特定属性和行为即可,其他内容可以忽略
上图所示的 飞行模拟器 和 航班预订程序 Airplane 类所包含的信息差别很大
2.2.2 封装
封装是指一个对象对其他对象隐藏其部分状态和行为,而仅向程序其他部分暴露有限的接口的能力。
比如要启动一辆汽车,只暴露启动开关的 接口
, 而隐藏 打开引擎盖手动接线、转动曲轴和气缸并启动发动机的动力循环等细节。
封装某个内容意味着使用关键字 private 私有来对其进行修饰,这样仅有其所在类中的方法才能访问这些内容。还有一种限制程度较小的关键字 protected 保护 ,其所修饰的对象仅允许父?类访问其类中的成员。
接口机制 (通常使用 interface或 protocol关键字来声明)允许你定义对象之间的交互协议。
2.2.3 继承
继承是指在根据已有类创建新类的能力。
2.2.4 多态
多态 是指程序能够 检测 对象所属的实际类,并在当前上下文不知道其真实类型的情况下调用其实现的能力。
bag = [new Cat(), new Dog()];foreach (Animal a : bag)a.makeSound()// 喵喵!
// 汪汪!
2.3 对象之间的关系
- 继承: 类 A 继承类 B 的接口和实现,但是可以对其进行扩展。对象 A 可被视为对象 B。类 A 依赖于类 B。
- 实现: 类 A 定义的方法由接口 B 声明。对象 A 可被视为对象 B。类 A 依赖于类 B。
- 依赖: **对类 B 进行修改会影响到类 A。**修改一个类可能造成另一个类的变化就存在依赖关系
- 关联: **对象 A 知道对象 B。类 A 依赖于类 B。**一个对象使用另一对象或与另一对象进行交互的关系。
class Professor isfield Student student // 2. 学生类是教授类的依赖// 3. 教授能访问所有学生的成员,致依赖加强为关联// ……method teach(Course c) is // 1. 修改 Course(getKnowledge的函数名或添加参数) 代码会崩溃, 这就是依赖关系// ……this.student.remember(c.getKnowledge())
- 聚合:**对象 A 知道对象 B 且由 B 构成。类 A 依赖于类 B。**用于表示多个对象之间的 “一对多”、 “多对多”或 “整体对部分”的关系。通常在聚合关系中,一个对象 “拥有”一组其他对象,并扮演着容器或集合的角色。组件可以独立于容器存在,也可以同时连接多个容器。在 UML 图中,聚合关系使用一端是空心菱形,另一端指向组件的箭头来表示。
- 组合:**对象 A 知道对象 B、由 B 构成而且管理着 B 的生命周期。类 A 依赖于类 B。**组合是一种特殊类型的聚合,其中一个对象由一个或多个其他对象实例构成。组合与其他关系的区别在于组件仅能作为容器的一部分存在。在 UML 图中,组合与聚合关系的符号相同,但箭头起始处的菱形是实心的。
关系总结
3. 设计模式简介
3.1 什么是设计模式?
设计模式是软件设计中常见问题的典型解决方案。它们就像能根据需求进行调整的预制蓝图,可用于解决代码中反复出现的设计问题。
设计模式与方法或库的使用方式不同,你很难直接在自己的程序中套用某个设计模式。模式并不是一段特定的代码,而是解决特定问题的一般性概念。你可以根据模式来实现符合自己程序实际所需的解决方案。
人们常常会混淆模式和算法,因为两者在概念上都是已知特定问题的典型解决方案。但算法总是明确定义达成特定目标所需的一系列步骤,而模式则是对解决方案的更高层次描述。同一模式在两个不同程序中的实现代码可能会不一样。
3.1.1 模式包含哪些内容?
大部分模式的描述都会遵循特定的形式,以便在不同情况下使用。模式的描述通常会包括以下部分:
- 意图部分简要地描述问题和解决方案。
- 动机部分进一步解释问题并说明模式会如何提供解决方案。
- 结构部分展示模式的各个部分和它们之间的关系。
- 在不同语言中的实现提供流行编程语言的代码,让读者更好地理解模式背后的思想。
3.1.2 模式的分类
最基础的、底层的模式通常被称为惯用技巧。只能在一种编程语言中使用。
最通用的、高层的模式是架构模式。可以在任何编程语言中使用这类模式。
所有模式可以根据其意图或目的来分类。本书覆盖了三种主要的模式类别:
- 创建型模式提供创建对象的机制,增加已有代码的灵活性和可复用性。
- 结构型模式介绍如何将对象和类组装成较大的结构,并同时保持结构的灵活和高效。
- 行为模式负责对象间的高效沟通和职责委派。
3.2 为什么以及如何学习设计模式?
- 设计模式是针对软件设计中常见问题的工具箱,其中的工具就是各种经过实践验证的解决方案。即使你从未遇到过这些问题,了解模式仍然非常有用,因为它能指导你如何使用面向对象的设计原则来解决各种问题。
- 设计模式定义了一种让你和团队成员能够更高效沟通的通用语言。你只需说 “哦,这里用单例就可以了”,所有人都会理解这条建议背后的想法。只要知晓模式及其名称,你就无需解释什么是单例。
4. 软件设计原则
4.1 优秀设计的特征
在开始学习实际的模式前,让我们来看看软件架构的设计过程,了解一下需要达成目标与需要尽量避免的陷阱。
4.1.1 代码复用
代码复用是减少开发成本时最常用的方式之一。其意图非常明显:与其反复从头开发,不如在新对象中重用已有代码。
这个想法表面看起来很棒,但实际上要让已有代码在全新的上下文中工作,通常还是需要付出额外努力的。组件间紧密的耦合、对具体类而非接口的依赖和硬编码的行为都会降低代码的灵活性,使得复用这些代码变得更加困难。
使用设计模式是增加软件组件灵活性并使其易于复用的方式之一。但是有时,这也会让组件变得更加复杂。
4.1.2 扩展性
变化是程序员生命中唯一不变的事情。在设计程序架构时,所有有经验的开发者会尽量选择支持未来任何可能变更的方式。
4.2 设计原则
什么是优秀的软件设计?如何对其进行评估?你需要遵循哪些实践方式才能实现这样的方式?如何让你的架构灵活、稳定且易于理解?
有几个通用的软件设计原则可能会对解决这些问题有所帮助。本书中列出的绝大部分设计模式都是基于这些原则的。
4.2.1 封装变化的内容
找到程序中的变化内容并将其与不变的内容区分开。该原则的主要目的是将变更造成的影响最小化。
4.2.2 方法层面的封装
以计算税金为例。
修改前:
修改后:
4.2.3 面向接口进行开发,而不是面向实现
面向接口进行开发,而不是面向实现;依赖于抽象类型,而不是具体类。
如果无需修改已有代码就能轻松对类进行扩展,那就可以说这样的设计是灵活的。
可用另外一种更灵活的方式来设置对象之间的合作关系。
- 确定一个对象对另一对象的确切需求:它需执行哪些方法?
- 在一个新的接口或抽象类中描述这些方法。
- 让被依赖的类实现该接口。
- 现在让有需求的类依赖于这个接口,而不依赖于具体的类。你仍可与原始类中的对象进行互动,但现在其连接将会灵活得多。
以猫吃不同食物为例:
以公司-雇员为例:
修改前:公司Company类与具体雇员类紧密耦合。
修改后:归纳出几个与工作相关的方法,并且将其抽取为所有雇员的通用接口。此后,我们可在 公司类内应用多态机制,通过 雇员Employee接口来处理各类雇员对象。
修改后:声明一个抽象方法来获取雇员。每个具体公司都将以不同方式实现该方法,从而创建自己所需的雇员。修改后的 公司类将独立于各种雇员类。现在你可以对该类进行扩展,并在复用部分公司基类的情况下引入新的公司和雇员类型。对公司基类进行扩展时无需修改任何依赖于基类的已有代码。
4.3 组合优于继承
继承这件事通常只有在程序中已包含大量类,且修改任何东西都非常困难时才会引起关注。下面就是此类问题的清单。
- 子类不能减少超类的接口。你必须实现父类中所有的抽象方法,即使它们没什么用。
- 在重写方法时,你需要确保新行为与其基类中的版本兼容。这一点很重要,因为子类的所有对象都可能被传递给以超类对象为参数的任何代码,相信你不会希望这些代码崩溃的。
- 继承打破了超类的封装,因为子类拥有访问父类内部详细内容的权限。此外还可能会有相反的情况出现,那就是程序员为了进一步扩展的方便而让超类知晓子类的内部详细内容。
- 子类与超类紧密耦合。超类中的任何修改都可能会破坏子类的功能。
- 通过继承复用代码可能导致平行继承体系的产生。继承通常仅发生在一个维度中。只要出现了两个以上的维度,你就必须创建数量巨大的类组合,从而使类层次结构膨胀到不可思议的程度。
组合是代替继承的一种方法。继承代表类之间的 **“是”**关系 (汽车是交通工具),而组合则代表 **“有”**关系 (汽车有一个引擎)。
必须一提的是,这个原则也能应用于聚合 (一种更松弛的组合变体,一个对象可引用另一个对象,但并不管理其生命周期)。例如:一辆汽车上有司机,但是司机也可能会使用另一辆汽车,或者选择步行而不使用汽车。
示例:假如你需要为汽车制造商创建一个目录程序。该公司同时生产 汽车Car和 卡车Truck ,车辆可能是 电动车Electric或 汽油车Combustion ;所有车型都配备了 手动控制manual control或 自动驾驶Autopilot功能。
继承:
组合:
5. SOLID 原则
这五条原则是在罗伯特·马丁的著作 《敏捷软件开发:原则、模式与实践 36》中首次提出的。
SOLID 是让软件设计更易于理解、更加灵活和更易于维护的五个原则的简称。
与生活中所有事情一样,盲目遵守这些原则可能会弊大于利。在程序架构中应用这些原则可能会使其变得过于复杂。我对于是否真的有能够同时应用所有这五条原则的成功软件产品表示怀疑。有原则是件好事,但是也要时刻从实用的角度来考量,不要把这里的每句话当作放之四海皆准的教条。
5.1 S: 单一职责原则(Single Responsibility)
Single Responsibility Principle: 修改一个类的原因只能有一个。
尽量让每个类只负责软件中的一个功能,并将该功能完全封装 (你也可称之为隐藏)在该类中。
这条原则的主要目的是减少复杂度。你不需要费尽心机地去构思如何仅用 200 行代码来实现复杂设计,实际上完全可以使用十几个清晰的方法。
当程序规模不断扩大、变更不断增加后,真实问题才会逐渐显现出来。到了某个时候,类会变得过于庞大,以至于你无法记住其细节。查找代码将变得非常缓慢,你必须浏览整个类,甚至整个程序才能找到需要的东西。程序中实体的数量会让你的大脑堆栈过载,你会感觉自己对代码失去了控制。
还有一点:如果类负责的东西太多,那么当其中任何一件事发生改变时,你都必须对类进行修改。而在进行修改时,你就有可能改动类中自己并不希望改动的部分。
如果你开始感觉在同时关注程序特定方面的内容时有些困难的话,请回忆单一职责原则并考虑现在是否应将某些类分割为几个部分。
5.2 O: 开闭原则(Open/Closed)
Open/Closed Principle: 对扩展开放;对修改封闭
本原则的主要理念是在实现新功能时能保持已有代码不变。
通过关键字 final 可以限制一个类不再 “开放”(不能再被继承),如果某个类已做好了充分的准备并可供其他类使用的话(即其接口已明确定义且以后不会修改),那么该类就是封闭 (你可以称之为 完整 )的.
这条原则并不能应用于所有对类进行的修改中。如果你发现类中存在缺陷,直接对其进行修复即可,不要为它创建子类。子类不应该对其父类的问题负责。
5.3 L: 里氏替换原则(Liskov Substitution)
当你扩展一个类时,要能在不修改客户端代码的情况下将子类的对象作为父类对象进行传递(原理需要父类型参数的地方传子类参数也能用)。
这意味着子类必须保持与父类行为的兼容。在重写一个方法时,你要对基类行为进行扩展,而不是将其完全替换。
替换原则是用于预测子类是否与代码兼容,以及是否能与其超类对象协作的一组检查。这一概念在开发程序库和框架时非常重要,因为其中的类将会在他人的代码中使用——你是无法直接访问和修改这些代码的。
与有着多种解释方式的其他设计模式不同,替代原则包含一组对子类 (特别是其方法)的形式要求。让我们来仔细看看这些要求。
超类的不变量必须保留
- 子类方法的参数类型必须与其超类的参数类型相匹配或更加抽象
- 子类方法的返回值类型必须与超类方法的返回值类型或是其子类别相匹配(跟参数类型的要求相反)
- 子类中的方法不应抛出基础方法预期之外的异常类型
- 异常类型必须与基础方法能抛出的异常或是其子类别相 匹配 。这条规则源于一个事实:客户端代码的
<font size="-1" face="Menlo" color="inherit">try-catch</font>
代码块针对的是基础方法可能抛出的异常类型。因此,预期之外的异常可能会穿透客户端的防御代码,从而使整个应用崩溃。
- 异常类型必须与基础方法能抛出的异常或是其子类别相 匹配 。这条规则源于一个事实:客户端代码的
- 子类不应该加强其前置条件
- 子类不能削弱其后置条件
- 超类的不变量必须保留
- 子类不能修改超类中私有成员变量的值
5.4 I: 接口隔离原则(Interface Segregation)
客户端不应被强迫依赖于其不使用的方法。尽量缩小接口的范围,使得客户端的类不必实现其不需要的行为。
根据接口隔离原则,你必须将 “臃肿”的方法拆分为多个颗粒度更小的具体方法。**客户端必须仅实现其实际需要的方法。**否则,对于 “臃肿”接口的修改可能会导致程序出错,即使客户端根本没有使用修改后的方法。
继承只允许类拥有一个超类,但是它并不限制类可同时实现的接口的数量。因此,你不需要将大量无关的类塞进单个接口。你可将其拆分为更精细的接口,如有需要可在单个类中实现所有接口,某些类也可只实现其中的一个接口。
5.5 D: 依赖倒置原则(Dependency Inversion)
高层次的类不应该依赖于低层次的类。两者都应该依赖于抽象接口。抽象接口不应依赖于具体实现。具体实现应该依赖于抽象接口。
通常在设计软件时,你可以辨别出不同层次的类
- 低层次类:实现基础操作 (例如磁盘操作、传输网络数据和连接数据库等)
- 高层次类:包含复杂业务逻辑以指导低层次类执行特定操作
有时人们会先设计低层次的类,然后才会开发高层次的类。当你在新系统上开发原型产品时,这种情况很常见。由于低层次的东西还没有实现或不确定,你甚至无法确定高层次类能实现哪些功能。如果采用这种方式,业务逻辑类可能会更依赖于低层原语类。
依赖倒置原则建议改变这种依赖方式。
- 作为初学者,你最好使用业务术语来对高层次类依赖的低层次操作接口进行描述。例如,业务逻辑应该调用名为 openReport(file)的方法,而不是 openFile(x) 、 readBytes(n)和 closeFile(x)等一系列方法。这些接口被视为是高层次的
- 现在你可基于这些接口创建高层次类,而不是基于低层次的具体类。这要比原始的依赖关系灵活很多。
- 一旦低层次的类实现了这些接口,它们将依赖于业务逻辑层,从而倒置了原始的依赖关系
具体设计模式
1. 创建型模式
创建型模式提供了创建对象的机制,能够提升已有代码的灵活性和可复用性。
1.1 工厂方法/Factory Method
亦称:虚拟构造函数、Virtual Constructor、Factory Method
工厂方法 是一种创建型设计模式,其在父类中提供一个创建对象的方法,允许子类决定实例化对象的类型。
看一个C++的例子:
它的结构总结起来就是下面的图:
1.2 抽象工厂/Abstract Factory
抽象工厂是一种创建型设计模式, 它能创建一系列相关的对象,而无需指定其具体类
看一个C++的例子:
它的结构总结起来就是下面的图:
1.3 生成器/Builder
亦称:建造者模式、Builder
生成器是一种创建型设计模式,使你能够分步骤创建复杂对象。该模式允许你使用相同的创建 代码生成不同类型和形式的对象。
生成器模式建议将对象构造代码从产品类中抽取出来,并将其放在一个名为生成器的独立对象中。该模式会将对象构造过程划分为一组步骤,比如 buildWalls 创建墙壁和 buildDoor 创建房门创建房门等。每次创建对象时,你都需要通过生成器对象执行一系列步骤。重点在于你无需调用所有步骤,而只需调用创建特定对象配置所需的那些步骤即可。
看一个C++的例子:
它的结构总结起来就是下面的图:
1.4 原型/Prototype
亦称:克隆、Clone、Prototype
原型是一种创建型设计模式,使你能够 复制已有对象,而又无需使代码依赖它们所属的类。
看一个C++的例子:
它的结构总结起来就是下面的图:
1.5 单例/Singleton
亦称:单件模式、Singleton
单例是一种创建型设计模式,让你能够保证一个类只有一个实例,并提供一个访问该实例的全局节点
所有单例的实现都包含以下两个相同的步骤:
- 将默认构造函数设为私有,防止其他对象使用单例类的 new 运算符。
- 新建一个静态构建方法作为构造函数。该函数会“偷偷”调用私有构造函数来创建对象,并将其保存在一个静态成员变量中。此后所有对于该函数的调用都将返回这一缓存对象
它的结构总结起来就是下面的图:
2. 结构型模式
2.1 适配器/Adapter
亦称:封装器模式、Wrapper、Adapter
适配器是一种结构型设计模式,它能使接口不兼容的对象能够相互合作
适配器模式通过封装对象将复杂的转换过程隐藏于幕后。被封装的对象甚至察觉不到适配器的存在。例如,你可以使用一个将所有数据转换为英制单位 (如英尺和英里)的适配器封装运行于米和千米单位制中的对象。
适配器不仅可以转换不同格式的数据,其还有助于采用不同接口的对象之间的合作。它的运作方式如下:
- 适配器实现与其中一个现有对象兼容的接口。
- 现有对象可以使用该接口安全地调用适配器方法。
- 适配器方法被调用后将以另一个对象兼容的格式和顺序将请求传递给该对象
它的结构总结起来就是下面的图:
2.2 桥接/Bridge
亦称:Bridge
桥接是一种结构型设计模式,可将一个大类或一系列紧密相关的类拆分为抽象和实现两个独立的层次结构,从而能在开发时分别使用
它的结构总结起来就是下面的图:
2.3 组合/Composite
亦称:对象树、Object Tree、Composite
组合是一种结构型设计模式,你可以使用它将对象组合成树状结构,并且能像使用独立对象一样使用它们。
2.4 装饰/Decorator
亦称:装饰者模式、装饰器模式、Wrapper、Decorator
装饰是一种结构型设计模式,允许你通过将对象放入包含行为的特殊封装对象中来为原对象绑定新的行为。
2.5 外观/Facade
亦称:Facade
外观是一种结构型设计模式,能为程序库、框架或其他复杂类提供一个简单的接口
2.6 享元/Flyweight
亦称:缓存、Cache、Flyweight
享元是一种结构型设计模式,它摒弃了在每个对象中保存所有数据的方式,通过共享多个对象所共有的相同状态,让你能在有限的内存容量中载入更多对象。
<img src="./picture/08.Flyweights.png" width="360px" alt="xx" />
2.7 代理/Proxy
亦称:Proxy
代理是一种结构型设计模式,让你能够提供对象的替代品或其占位符。代理控制着对于原对象的访问,并允许在将请求提交给对象前后进行一些处理。
代理模式建议新建一个与原服务对象接口相同的代理类,然后更新应用以将代理对象传递给所有原始对象客户端。代理类接收到客户端请求后会创建实际的服务对象,并将所有工作委派给它.
代理与装饰的代码很像,那它们的区别在哪?
《java与模式》阎宏编著:
装饰器模式应当为所装饰的对象提供增强功能,而代理模式对所代理对象的使用施加控制,并不提供对象本身的增强功能
chatGPT:
- 目的不同 :装饰模式的主要目的是增强对象的功能,而代理模式的主要目的是控制对对象的访问。
- 结构不同 :装饰模式通常涉及多个装饰者类的组合,而代理模式通常涉及一个代理类和一个具体主题类。
- 实现方式 :装饰模式通过组合来动态添加功能,代理模式通过引用来控制访问。
- 使用场景不同 :装饰模式适用于需要动态添加功能的情况,而代理模式适用于需要控制对对象的访问的情况
3. 行为模式
3.1 责任链 / Chain of Responsibility
亦称:职责链模式、命令链、CoR、Chain of Command、Chain of Responsibility
责任链是一种行为设计模式,允许你将请求沿着处理者链进行发送。收到请求后,每个处理者均可对请求进行处理,或将其传递给链上的下个处理者
C++举例(核心代码):
3.2 命令 / Command
亦称:动作、事务、Action、Transaction、Command
命令是一种行为设计模式,它可将请求转换为一个包含与请求相关的所有信息的独立对象。该转换让你能根据不同的请求将方法参数化、延迟请求执行或将其放入队列中,且能实现可撤销操作
优秀的软件设计通常会将关注点进行分离,而这往往会导致软件的分层。最常见的例子:一层负责用户图像界面;另一层负责业务逻辑。GUI 层负责在屏幕上渲染美观的图形,捕获所有输入并显示用户和程序工作的结果。当需要完成一些重要内容时 (比如计算月球轨道或撰写年度报告),GUI 层则会将工作委派给业务逻辑底层.
3.3 迭代器 / Iterator
亦称:Iterator
迭代器是一种行为设计模式,让你能在不暴露集合底层表现形式(列表、栈和树等)的情况下遍历集合中所有的元素.
集合是编程中最常使用的数据类型之一。尽管如此,集合只是一组对象的容器而已。
3.4 中介者 / Mediator
亦称:调解人、控制器、Intermediary、Controller、Mediator
中介者是一种行为设计模式,能让你减少对象之间混乱无序的依赖关系。该模式会限制对象之间的直接交互,迫使它们通过一个中介者对象进行合作.
中介者模式建议你停止组件之间的直接交流并使其相互独立。这些组件必须调用特殊的中介者对象,通过中介者对象重定向调用行为,以间接的方式进行合作。最终,组件仅依赖于一个中介者类,无需与多个其他组件相耦合。
C++举例(核心代码):
3.5 备忘录 / Memento
亦称:快照、Snapshot、Memento
备忘录是一种行为设计模式,允许在不暴露对象实现细节的情况下保存和恢复对象之前的状态
C++举例(核心代码):
3.6 观察者 / Observer
亦称:事件订阅者、监听者、Event-Subscriber、Listener、Observer
观察者是一种行为设计模式,允许你定义一种订阅机制,可在对象事件发生时通知多个“观察”该对象的其他对象
C++举例(核心代码):
3.7 状态 / State
亦称:State
状态是一种行为设计模式,让你能在一个对象的内部状态变化时改变其行为,使其看上去就像改变了自身所属的类一样
状态模式与有限状态机的概念紧密相关. 其主要思想是程序在任意时刻仅可处于几种有限的状态中。在任何一个特定状态中,程序的行为都不相同,且可瞬间从一个状态切换到另一个状态。不过,根据当前状态,程序可能会切换到另外一种状态,也可能会保持当前状态不变。这些数量有限且预先定义的状态切换规则被称为转移。
状态机通常由众多条件运算符 ( if或 switch )实现,可根据对象的当前状态选择相应的行为。 “状态”通常只是对象中的一组成员变量值。即使你之前从未听说过有限状态机,你也很可能已经实现过状态模式。下面的代码应该能帮助你回忆起来。
C++举例(核心代码):
3.8 策略 / Strategy
亦称:Strategy
策略是一种行为设计模式,它能让你定义一系列算法,并将每种算法分别放入独立的类中,以使算法的对象能够相互替换。
C++举例(核心代码) – 与策略模式很像(UML图相同), 都将一些工作委托给其他对象:
3.9 模板方法 / Template Method
亦称:Template Method
模板方法是一种行为设计模式,它在超类中定义了一个算法的框架,允许子类在不修改结构的情况下重写算法的特定步骤。
C++举例(核心代码):
3.10 访问者 / Visitor
亦称:Visitor
访问者是一种行为设计模式,它能将算法与其所作用的对象隔离开来。
访问者模式建议将新行为放入一个名为访问者的独立类中,而不是试图将其整合到已有类中。现在,需要执行操作的原始对象将作为参数被传递给访问者中的方法,让方法能访问对象所包含的一切必要数据。
C++举例(核心代码):
结语
世界上还有许多其他的模式。希望本文提到的22中典型模式和能作为读者获取程序设计超能力的起点
相关文章:
设计模式笔记
关于设计模式 1. 如何阅读本文 略 2. 面向对象程序设计简介 2.1 面向对象程序设计基础 面向对象程序设计 (Object-Oriented Programming,缩写为 OOP)是一种范式,其基本理念是将 数据块 及 与数据相关的行为 封装成为特殊的、…...
c==ubuntu+vscode debug redis7源码
新建.vscode文件夹,创建launch.json和tasks.json {"version": "0.2.0","configurations": [{"name": "C/C Launch","type": "cppdbg","request": "launch","prog…...
java字符串储存底层原理
字符串原理:原理1: 内存原理 (1)直接赋值给字符串,会把这个字符串放到常量池里,如果之后出现重复使用这个字符串的,就会直接从这个常量池中去引用,不会再去new一个字符串 (2)new出来的字符串不会重复使用,而是开辟一个新的空间存储原理2: 字符串中的""比较的是什么?…...
c++获取当前时间的字符串
代码 void getNowTimePrefix(std::string& prefix) {std::time_t nowTime;struct tm* p new tm;std::time(&nowTime);localtime_s(p, &nowTime);int year p->tm_year 1900;int month p->tm_mon 1;int day p->tm_mday;int hour p->tm_hour;int …...
【精品】通用Mapper 批量更新bug解决方案
问题描述 环境:mysql8.xmybatis3.5.13tk.mybatis4.2.3 在使用tk.mybatis做批量更新时,程序会报错,说是执行的SQL语法错误,经研究源代码发现tk.mybatis在实现批量更新时是通过多次执行update语句实现的。这本身就不符合MySQL批量…...
腾讯mini项目-【指标监控服务重构-会议记录】2023-07-06
7/6 会议记录 Profile4个步骤 解压kafka消息初始化性能事件,分析事件将数据写入kafkaRun 开始执行各stage handler 上报耗时到otel-collector。。。 // ConsumerDispatchHandler consumer // // param msg *sarama.ConsumerMessage // param consumer *databus.K…...
【React】函数式组件和类式组件的用法和逻辑
组件的使用 当应用是以多组件的方式实现,这个应用就是一个组件化的应用 注意: 组件名必须是首字母大写虚拟DOM元素只能有一个根元素虚拟DOM元素必须有结束标签 < /> 渲染类组件标签的基本流程React 内部会创建组件实例对象调用render()得到虚拟 …...
题目 1061: 二级C语言-计负均正
从键盘输入任意20个整型数,统计其中的负数个数并求所有正数的平均值。 保留两位小数 样例输入 1 2 3 4 5 6 7 8 9 10 -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 样例输出 10 5.50 解题思路: 如题所示,输入20个正负数,---》求付数的个…...
数位和(C++)
系列文章目录 进阶的卡莎C++_睡觉觉觉得的博客-CSDN博客数1的个数_睡觉觉觉得的博客-CSDN博客双精度浮点数的输入输出_睡觉觉觉得的博客-CSDN博客足球联赛积分_睡觉觉觉得的博客-CSDN博客大减价(一级)_睡觉觉觉得的博客-CSDN博客小写字母的判断_睡觉觉觉得的博客-CSDN博客纸币(…...
[牛客复盘] 牛客周赛round13 20230924
[牛客复盘] 牛客周赛round13 20230924 总结矩阵转置置2. 思路分析3. 代码实现 小红买基金1. 题目描述2. 思路分析3. 代码实现 小红的密码修改1. 题目描述2. 思路分析3. 代码实现 小红的转账设置方式1. 题目描述2. 思路分析3. 代码实现 小红打boss1. 题目描述2. 思路分析3. 代码…...
mybatsi-MyBatis的逆向工程
mybatsi-MyBatis的逆向工程 一、前言二、创建逆向工程的步骤1.添加依赖和插件2.创建MyBatis的核心配置文件3.创建逆向工程的配置文件4.执行MBG插件的generate目标 一、前言 正向工程:先创建Java实体类,由框架负责根据实体类生成数据库表。 Hibernate是支…...
转转闲鱼交易猫链接源码 支持二维码收款
最新仿二手闲置链接源码 后台一键生成链接,后台管理教程:解压源码,修改数据库config/Congig 不会可以看源码里有教程 下载程序:https://pan.baidu.com/s/16lN3gvRIZm7pqhvVMYYecQ?pwd6zw3...
Python爬虫基础(三):使用Selenium动态加载网页
文章目录 系列文章索引一、Selenium简介1、什么是selenium?2、为什么使用selenium3、安装selenium(1)谷歌浏览器驱动下载安装(2)安装selenium 二、Selenium使用1、简单使用2、元素定位3、获取元素信息4、交互 三、Phan…...
Linux系统下安装Mysql
1、执行命令:rpm -qa | grep -i mysql,先查看系统之前是否有安装相关的rpm包,如果有,会显示类似下面的信息; 2、通过命令yum -y remove mysql-* 一次性删除系统上所有相关的rpm包,或者通过命令yum -y …...
Jenkins学习笔记1
CI 服务器: 认识Jenkins: Jenkins是一个可扩展的持续集成(CI)引擎,是一个开源项目,旨在提供一个开放易用的软件平台,使得软件持续集成变成可能。Jenkins非常易于安装和配置,简单易…...
注意力机制
概念没什么好说的,反正大家都会说,具体实战怎么写才是最为重要的 1.自注意力 假设有一组数据,都是一维的向量,这个向量可能是一个样本,可能是其他什么,都无所谓。 假设有一组一维向量x1,x2,x3,x4,x5; 第…...
JVM-Java字节码技术笔记
Java字节码技术 Java字节码是java代码编译后的中间代码格式,JVM需要读取并解析字节码才能执行相应的任务 获取字节码简介:由单字节(byte)的指令组成 操作码( 指令), 主要由类型前缀和操作名称两部分组成。根据指令的性质…...
C++ 友元、重载、继承、多态
友元 关键字:friend 友元的三种实现 全局函数做友元类做友元成员函数做友元 全局函数做友元 //建筑物类 class Building {//goodGay全局函数是Building好朋友,可以访问Building中私有成员friend void goodGay(Building& building); public:Build…...
Spring Boot 日志文件
前言 本篇博客主要介绍自定义的日志打印、日志的级别高低、如何保存日志等等..... 一、日志是什么?日志有什么用? 日志就是我们控制台上输出的内容,控制台上的输出的信息就是日志信息,如下所示: 日志有什么用&#x…...
vulhub venom
文章目录 靶场环境信息收集ftp服务二、信息利用三、任意文件上传三 sudo提权靶场环境 `vmware 靶场信息:https://www.vulnhub.com/entry/venom-1,701/ 下载地址:https://download.vulnhub.com/venom/venom.zip 新建虚拟机打开下载后的ovf文件 遇见导入失败合规性检查时,重试…...
量化交易之One Piece篇 - linux - 定时任务(重启服务器、执行程序、验证)
linux 执行命令: crontab -e 0 5 * * 1-5 sudo /sbin/shutdown -r now 0 17 * * 1-5 sudo /sbin/shutdown -r now 45 8 * * 1-5 cd /home/ubuntu/onepiece/bin/datacore && ./datacore 45 20 * * 1-5 cd /home/ubuntu/onepiece/bin/datacore && ./datacore 以…...
Qt5开发及实例V2.0-第二十三章-Qt-多功能文档查看器实例
Qt5开发及实例V2.0-第二十三章-Qt-多功能文档查看器实例 第23章 多功能文档查看器实例23.1. 简介23.2. 界面与程序框架设计23.2.1. 图片资源23.2.2. 网页资源23.2.3. 测试用文件 23.3 主程序代码框架23.4 浏览网页功能实现23.4.1 实现HtmIHandler处理器 23.5. 部分代码实现23.5…...
爬虫笔记_
爬虫简介 爬虫初始深入 爬虫在使用场景中的分类 通用爬虫: 抓取系统重要组成部分。抓取的是一整张页面数据 聚焦爬虫: 是建立在通用爬虫的基础上。抓取的是页面中特定的局部内容。 增量式爬虫 监测网站中数据更新的情况。只会抓取网站中最新更新出来的…...
Spring设计模式,事务管理和代理模式的应用
扩充:贝叶斯定理答案见底。 设计模式对关于面向对象问题的具体解决方案. 1,单例多例 在设计单例模式时,要注意两个点 1.构造方法要私有 2.成员变量要私有 3.创建对象所用的方法要被synchronized修饰.(因为方法体中会涉及到判断当…...
基于海康Ehome/ISUP接入到LiveNVR实现海康摄像头、录像机视频统一汇聚,做到物联网无插件直播回放和控制
LiveNVR支持海康NVR摄像头通EHOME接入ISUP接入LiveNVR分发视频流或是转GB28181 1、海康 ISUP 接入配置2、海康设备接入2.1、海康EHOME接入配置示例2.2、海康ISUP接入配置示例 3、通道配置3.1、直播流接入类型 海康ISUP3.2、海康 ISUP 设备ID3.3、启用保存3.4、接入成功 4、相关…...
Linux下git安装及使用
Linux下Git使用 1. git的安装 sudo apt install git安装完,使用git --version查看git版本 2. 配置git git config --global user.name "Your Name“ ##配置用户 git config --global user.email emailexample.com ##配置邮箱git config --global --list …...
python读取图片
要在Python中读取图片,你可以使用第三方库Pillow(Python Imaging Library,PIL)或OpenCV。以下是使用这两个库的示例: 使用Pillow库读取图片: 首先,确保你已经安装了Pillow库。如果还没有安装&am…...
虚幻4学习笔记(15)读档 和存档 的实现
虚幻4学习笔记 读档存档 B站UP谌嘉诚课程:https://www.bilibili.com/video/BV164411Y732 读档 添加UI蓝图 SaveGame_UMG 添加Scroll Box 修改Scrollbar Thickness滚动条厚度 15 15 勾选 is variable 添加text 读档界面 添加背景模糊 添加UI蓝图 SaveGame_Slot …...
Spring面试题22:Spring支持哪些ORM框架?优缺点分别是什么?Spring可以通过哪些方式访问Hibernate?
该文章专注于面试,面试只要回答关键点即可,不需要对框架有非常深入的回答,如果你想应付面试,是足够了,抓住关键点 面试官:Spring支持哪些ORM框架?优缺点分别是什么? Spring 支持多种 ORM(对象关系映射)框架,其中包括: Hibernate:Hibernate 是一个强大的 ORM 框架…...
流行的Python库numpy及Pandas简要介绍
numpy.ndarray 是NumPy库中的主要数据结构,它是一个多维数组,用于存储和操作数值数据。NumPy是Python中用于数值计算的强大库,numpy.ndarray 是它的核心数据类型,提供了高效的数值运算和广泛的数学函数。 以下是 numpy.ndarray 的…...
成都网站的建设/线上营销推广方案
容灾备份的等级 容灾备份是通过在异地建立和维护一个备份存储系统,利用地理上的分离来保证系统和数据对灾难性事件的抵御能力。 根据容灾系统对灾难的抵抗程度,可分为数据容灾和应用容灾。数据容灾是指建立一个异地的数据系统,该系统是对本地…...
阿旗建设局举报网站/百度站长平台提交网站
解题来源于蓝桥杯老师 题目要求如上 两种方法一种是用递归,另一种是用algorithm库函数中的全排列函数next_permutation具体代码如下: #include <iostream> #include <algorithm> using namespace std; int a[]{1,2,3,4,5,6,7,8,9}; int c…...
巩义网站建设方案表/ui设计公司
本次项目主要需要改进的方面: 1.界面 界面的优化是其中的一大项,群众和使用者第一眼看到的是软件的界面,能不能让大家感觉到眼前一亮的感觉是十分重要的,我们在界面上没有什么经验,不知道通过怎样的方式去进行修改&…...
深圳网站优化排名/怎么做电商平台
前言 迭代器貌似是 Python3 才有的(猜的),在廖雪峰大神的网站中 Python2 是没有迭代器一栏的 可 for 循环的对象 常见集合数据类型(迭代对象):list、tuple、dict、set、str生成器 generator 可迭代对象(Iterable) 可以直接用 for 循环的对象都叫可迭代对…...
wordpress炫酷背景图片/流量精灵app
Yii中loadModel的使用:$this->loadModel($modelId);一般在R/U/D操作中,会loadModel($modelId),这是因为此时,读,更新和删除时,已经通过GET方式获取到了model的id值,只需要将对应id的model加载…...
佛山哪里有做网站的/基本营销策略有哪些
当快船手握16分领先进入易地再战时,老李充满了欢欣与愉悦,甚至还想哼个小曲儿。毕竟赫拉克利特曾说过:一个人不可能两次踏进同一条河流。G5已经被掘金翻了一回,难不成还想再玩逆转?得亏老李不是五五开,否则…...