设计模式-对象创建
对象创建
- 前言
- 1. Factory Method
- 1.1 模式介绍
- 1.2 模式代码
- 1.2.1 问题代码
- 1.2.2 重构代码
- 1.3 模式类图
- 1.4 要点总结
- 2. Abstract Factory
- 2.1 模式介绍
- 2.2 模式代码
- 2.2.1 问题代码
- 2.2.2 重构代码
- 2.3 模式类图
- 2.4 要点总结
- 3. Prototype
- 3.1 模式介绍
- 3.2 模式代码
- 3.3 模式类图
- 3.4 要点总结
- 4. Builder
- 4.1 模式介绍
- 4.2 模式代码
- 4.3 模式类图
- 4.4 要点总结
前言
“对象创建”模式:
通过“对象创建” 模式绕开new,来避免对象创建(new)过程中所导致的紧耦合(依赖具体类),从而支持对象创建的稳定。它是接口抽象之后的第一步工作。
典型模式:
- Factory Method 工厂方法
- Abstract Factory 抽象工厂
- Prototype
- Builder
1. Factory Method
1.1 模式介绍
动机:在软件系统中,经常面临着创建对象的工作;由于需求的变化,需要创建的对象的具体类型经常变化。
如何应对这种变化?如何绕过常规的对象创建方法(new),提供一种“封装机制”来避免客户程序和这种“具体对象创建工作”的紧耦合?
定义一个用于创建对象的接口,让子类决定实例化哪一个类。Factory Method使得一个类的实例化延迟(目的:解耦,手段:虚函数)到子类。
——《设计模式》GoF
1.2 模式代码
1.2.1 问题代码
class ISplitter{public:virtual void split()=0;virtual ~ISplitter(){}};class BinarySplitter : public ISplitter{};class TxtSplitter: public ISplitter{};class PictureSplitter: public ISplitter{};class VideoSplitter: public ISplitter{};class MainForm : public Form
{TextBox* txtFilePath;TextBox* txtFileNumber;ProgressBar* progressBar;public:void Button1_Click(){ISplitter * splitter=new BinarySplitter();//依赖具体类splitter->split();}
};
你有一个按钮,点击按钮以后可以对二进制文件、文本文件、图片文件、视频文件进行分割,如果按照上述代码进行编写,其依赖具体类,如果你想增加其他文件格式方法时,得修改button里的方法,这会使得封装性被破坏
1.2.2 重构代码
//抽象类
class ISplitter{public:virtual void split()=0;virtual ~ISplitter(){}};//工厂基类
class SplitterFactory{
public:virtual ISplitter* CreateSplitter()=0;virtual ~SplitterFactory(){}
};//具体类
class BinarySplitter : public ISplitter{virtual void split(){}};class TxtSplitter: public ISplitter{virtual void split(){}
};class PictureSplitter: public ISplitter{virtual void split(){}
};class VideoSplitter: public ISplitter{virtual void split(){}
};//具体工厂
class BinarySplitterFactory: public SplitterFactory{
public:virtual ISplitter* CreateSplitter(){return new BinarySplitter();}
};class TxtSplitterFactory: public SplitterFactory{
public:virtual ISplitter* CreateSplitter(){return new TxtSplitter();}
};class PictureSplitterFactory: public SplitterFactory{
public:virtual ISplitter* CreateSplitter(){return new PictureSplitter();}
};class VideoSplitterFactory: public SplitterFactory{
public:virtual ISplitter* CreateSplitter(){return new VideoSplitter();}
};class MainForm : public Form
{SplitterFactory* factory;//工厂public:MainForm(SplitterFactory* factory){this->factory=factory;}void Button1_Click(){ISplitter * splitter=factory->CreateSplitter(); //多态newsplitter->split();}
};
解决方法:设计一个工厂基类,声明一个创建对象的接口,对象基础工厂基类,实现创建对象的方法,即可实现运行时绑定
1.3 模式类图

1.4 要点总结
- Factory Method模式用于隔离类对象的使用者和具体类型之间的耦合关系。面对一个经常变化的具体类型,紧耦合关系(new)会导致软件的脆弱。
- Factory Method模式通过面向对象的手法,将所要创建的具体对象工作延迟到子类,从而实现一种扩展(而非更改)的策略,较好地解决了这种紧耦合关系。
- Factory Method模式解决“单个对象”的需求变化。缺点在于要求创建方法/参数相同。
2. Abstract Factory
2.1 模式介绍
动机:在软件系统中,经常面临着“一系列相互依赖的对象”的创建工作;同时,由于需求的变化,往往存在更多系列对象的创建工作。
如何应对这种变化?如何绕过常规的对象创建方法(new),提供一种“封装机制”来避免客户程序和这种“多系列具体对象创建工作”的紧耦合?
提供一个接口,让该接口负责创建一系列“相关或者相互依赖的对象”,无需指定它们具体的类。
——《设计模式》GoF
2.2 模式代码
抽象工厂是工厂方法模式的子集,当要创建的对象之间有关联时才使用抽象工厂
2.2.1 问题代码
假设现在要实现SQL处理的功能
问题代码1:直接将类型硬编码到功能里,如果有多种数据库,比如MySQL、Oracle等等,就会不利于扩展
class EmployeeDAO{public:vector<EmployeeDO> GetEmployees(){SqlConnection* connection =new SqlConnection();connection->ConnectionString = "...";SqlCommand* command =new SqlCommand();command->CommandText="...";command->SetConnection(connection);SqlDataReader* reader = command->ExecuteReader();while (reader->Read()){}}
};
问题代码2:在问题代码1的基础上,使用工厂方法解决依赖具体类问题
//数据库访问有关的基类
class IDBConnection{};
class IDBConnectionFactory{
public:virtual IDBConnection* CreateDBConnection()=0;
};class IDBCommand{};
class IDBCommandFactory{
public:virtual IDBCommand* CreateDBCommand()=0;
};class IDataReader{};
class IDataReaderFactory{
public:virtual IDataReader* CreateDataReader()=0;
};//支持SQL Server
class SqlConnection: public IDBConnection{};
class SqlConnectionFactory:public IDBConnectionFactory{};class SqlCommand: public IDBCommand{};
class SqlCommandFactory:public IDBCommandFactory{};class SqlDataReader: public IDataReader{};
class SqlDataReaderFactory:public IDataReaderFactory{};//支持Oracle
class OracleConnection: public IDBConnection{};class OracleCommand: public IDBCommand{};class OracleDataReader: public IDataReader{};class EmployeeDAO{IDBConnectionFactory* dbConnectionFactory;IDBCommandFactory* dbCommandFactory;IDataReaderFactory* dataReaderFactory;public:vector<EmployeeDO> GetEmployees(){IDBConnection* connection =dbConnectionFactory->CreateDBConnection();connection->ConnectionString("...");IDBCommand* command =dbCommandFactory->CreateDBCommand();command->CommandText("...");command->SetConnection(connection); //关联性IDBDataReader* reader = command->ExecuteReader(); //关联性while (reader->Read()){}}
};
如果传递的dbConnectionFactory、dbCommandFactory、dataReaderFactory不是同一系列的就会出问题,例如一部分是MySQL对象,一部分是Oracle对象
2.2.2 重构代码
//数据库访问有关的基类
class IDBConnection{};class IDBCommand{};class IDataReader{};class IDBFactory{
public:virtual IDBConnection* CreateDBConnection()=0;virtual IDBCommand* CreateDBCommand()=0;virtual IDataReader* CreateDataReader()=0;};//支持SQL Server
class SqlConnection: public IDBConnection{};
class SqlCommand: public IDBCommand{};
class SqlDataReader: public IDataReader{};class SqlDBFactory:public IDBFactory{
public:virtual IDBConnection* CreateDBConnection()=0;virtual IDBCommand* CreateDBCommand()=0;virtual IDataReader* CreateDataReader()=0;};//支持Oracle
class OracleConnection: public IDBConnection{};class OracleCommand: public IDBCommand{};class OracleDataReader: public IDataReader{};class EmployeeDAO{IDBFactory* dbFactory;public:vector<EmployeeDO> GetEmployees(){IDBConnection* connection =dbFactory->CreateDBConnection();connection->ConnectionString("...");IDBCommand* command =dbFactory->CreateDBCommand();command->CommandText("...");command->SetConnection(connection); //关联性IDBDataReader* reader = command->ExecuteReader(); //关联性while (reader->Read()){}}
};
将一系列方法封装在一起,这便是抽象工厂模式
2.3 模式类图

2.4 要点总结
- 如果没有应对“多系列对象构建”的需求变化,则没有必要使用Abstract Factory模式,这时候使用简单的工厂完全可以。
- “系列对象”指的是在某一特定系列下的对象之间有相互依赖、或作用的关系。不同系列的对象之间不能相互依赖。
- Abstract Factory模式主要在于应对“新系列”的需求变动。其缺点在于难以应对“新对象”的需求变动。
3. Prototype
3.1 模式介绍
原型是一种创建型设计模式,它允许您复制现有对象,而不使您的代码依赖于它们的类。
问题:
假设你有一个对象,你想创建它的一个精确副本。你会怎么做?首先,你必须创建一个相同类的新对象。然后你必须遍历原始对象的所有字段并将其值复制到新对象。
很好!但是有一个问题。并非所有对象都可以通过这种方式复制,因为某些对象的字段可能是私有的,从对象本身外部不可见。

直接方法还有一个问题。由于您必须知道对象的类才能创建副本,因此您的代码将依赖于该类。如果额外的依赖关系不吓到您,那么还有另一个问题。有时您只知道对象遵循的接口,但不知道其具体类,例如,当方法中的参数接受遵循某个接口的任何对象时。
3.2 模式代码
//抽象类
class ISplitter{
public:virtual void split()=0;virtual ISplitter* clone()=0; //通过克隆自己来创建对象virtual ~ISplitter(){}};//具体类
class BinarySplitter : public ISplitter{
public:virtual ISplitter* clone(){return new BinarySplitter(*this);}virtual void split(){}
};class TxtSplitter: public ISplitter{
public:virtual ISplitter* clone(){return new TxtSplitter(*this);}virtual void split(){}
};class PictureSplitter: public ISplitter{
public:virtual ISplitter* clone(){return new PictureSplitter(*this);}virtual void split(){}
};class VideoSplitter: public ISplitter{
public:virtual ISplitter* clone(){return new VideoSplitter(*this);}virtual void split(){}
};class MainForm : public Form
{ISplitter* prototype;//原型对象public:MainForm(ISplitter* prototype){this->prototype=prototype;}void Button1_Click(){ISplitter * splitter=prototype->clone(); //克隆原型splitter->split();}
};
3.3 模式类图

3.4 要点总结
- 您可以克隆对象而不与其具体类耦合。
- 您可以摆脱重复的初始化代码,转而克隆预先构建的原型。
- 您可以更加方便地制作复杂的物体。
- 处理复杂对象的配置预设时,您可以获得继承的替代方法。
4. Builder
4.1 模式介绍
动机:在软件系统中,有时候面临着“一个复杂对象”的创建工作,其通常由各个部分的子对象用一定的算法构成;由于需求的变化,这个复杂对象的各个部分经常面临着剧烈的变化,但是将它们组合在一起的算法却相对稳定。
如何应对这种变化?如何提供一种“封装机制”来隔离出“复杂对象的各个部分”的变化,从而保持系统中的“稳定构建算法”不随着需求改变而改变?
将一个复杂对象的构建与其表示相分离,使得同样的构建过程(稳定)可以创建不同的表示(变化)。
——《设计模式》GoF
4.2 模式代码
class House{//....
};class HouseBuilder {
public:House* GetResult(){return pHouse;}virtual ~HouseBuilder(){}
protected:House* pHouse;virtual void BuildPart1()=0;virtual void BuildPart2()=0;virtual void BuildPart3()=0;virtual void BuildPart4()=0;virtual void BuildPart5()=0;};class StoneHouse: public House{};class StoneHouseBuilder: public HouseBuilder{
protected:virtual void BuildPart1(){//pHouse->Part1 = ...;}virtual void BuildPart2(){}virtual void BuildPart3(){}virtual void BuildPart4(){}virtual void BuildPart5(){}};class HouseDirector{public:HouseBuilder* pHouseBuilder;HouseDirector(HouseBuilder* pHouseBuilder){this->pHouseBuilder=pHouseBuilder;}House* Construct(){pHouseBuilder->BuildPart1();for (int i = 0; i < 4; i++){pHouseBuilder->BuildPart2();}bool flag=pHouseBuilder->BuildPart3();if(flag){pHouseBuilder->BuildPart4();}pHouseBuilder->BuildPart5();return pHouseBuilder->GetResult();}
};
构建器HouseBuilder负责定义构建House时每个步骤的具体接口(变化)和管理正在构建的对象,由子类继承并实现接口;
HouseDirector负责实现House在构建时的整体流程(不变)
4.3 模式类图

4.4 要点总结
- Builder 模式主要用于“分步骤构建一个复杂的对象”。在这其中“分步骤”是一个稳定的算法,而复杂对象的各个部分则经常变化。
- 变化点在哪里,封装哪里—— Builder模式主要在于应对“复杂对象各个部分”的频繁需求变动。其缺点在于难以应对“分步骤构建算法”的需求变动。
- 在Builder模式中,要注意不同语言中构造器内调用虚函数的差别(C++ vs. C#) 。
相关文章:
设计模式-对象创建
对象创建 前言1. Factory Method1.1 模式介绍1.2 模式代码1.2.1 问题代码1.2.2 重构代码 1.3 模式类图1.4 要点总结 2. Abstract Factory2.1 模式介绍2.2 模式代码2.2.1 问题代码2.2.2 重构代码 2.3 模式类图2.4 要点总结 3. Prototype3.1 模式介绍3.2 模式代码3.3 模式类图3.4…...
谈谈你对前端工程化的理解,它包含哪些方面
大白话谈谈你对前端工程化的理解,它包含哪些方面 前端工程化其实就是把前端开发变得更规范、更高效、更易于维护的一套方法和流程。就好比你盖房子,不能随便瞎盖,得有设计图纸、施工标准、分工合作,前端工程化也是类似的道理。 项…...
JSON数据格式介绍
2.5 JSON 2.5.1.JSON格式的用途 在开发中凡是涉及到『跨平台数据传输』,JSON格式一定是首选 2.5.2.JSON格式的说明 1.JSON数据两端要么是{},要么是[] {}定义JSON对象[]定义JSON数组 2.JSON对象的格式是:json {key:value,key:value,...,ke…...
java的WeakHashMap可以用来做缓存使用?强软弱虚四种引用对比
在 Java 中,引用(Reference)机制用于管理对象的生命周期和垃圾回收。Java 提供了四种类型的引用:强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Refer…...
【AVRCP】Notification PDUs 深入解析与应用
目录 一、Notification PDUs 概述 二、GetPlayStatus:同步查询播放状态 2.1 命令功能与应用场景 2.2 请求格式(CT → TG) 2.3 响应格式(TG → CT) 2.4 注意事项 2.5 协议实现示例(伪代码) 三、RegisterNotification:异步事件订阅 3.1 命令概述 3.2 命令格式 …...
从过拟合到强化学习:机器学习核心知识全解析
Langchain系列文章目录 01-玩转LangChain:从模型调用到Prompt模板与输出解析的完整指南 02-玩转 LangChain Memory 模块:四种记忆类型详解及应用场景全覆盖 03-全面掌握 LangChain:从核心链条构建到动态任务分配的实战指南 04-玩转 LangChai…...
【MySQL基础-9】深入理解MySQL中的聚合函数
在数据库操作中,聚合函数是一类非常重要的函数,它们用于对一组值执行计算并返回单个值。MySQL提供了多种聚合函数,如COUNT、SUM、AVG、MIN和MAX等。这些函数在数据分析和报表生成中扮演着关键角色。本文将深入探讨这些聚合函数的使用方法、注…...
Lora 中 怎么 实现 矩阵压缩
Lora 中 怎么 实现 矩阵压缩 1. 导入必要的库 import torch import re from datasets import Dataset from transformers import AutoTokenizer, AutoModelForCausalLM, TrainingArguments, Trainer, \get_cosine_schedule_with_warmup, EarlyStoppingCallback from peft...
MATLAB 控制系统设计与仿真 - 27
状态空间的标准型 传递函数和状态空间可以相互转换,接下来会举例如何有传递函数转成状态空间标准型。 对角标准型 当 G(s)可以写成: 即: 根据上图可知: 约当标准型 当 G(s)可以写成: 即: 根据上图…...
linux 命令 cp
cp 是 Linux 中用于复制文件和目录的命令,基本功能是将源文件或目录复制到目标位置 基本语法 cp [选项] 源文件 目标文件 cp [选项] 源文件1 源文件2 ... 目标目录 常用选项 选项说明-i交互模式(覆盖前询问确认)-r 或 -R递归复制目录&#…...
从FFmpeg命令行到Rust:多场景实战指南
FFmpeg作为功能强大的多媒体处理工具,被广泛应用于视频编辑、格式转换等领域。然而,直接使用FFmpeg的命令行界面(CLI)可能会遇到以下挑战: 命令复杂度高:FFmpeg的命令行参数众多且复杂,初学者可…...
蓝桥杯高频考点——进制转换
进制转换 二进制转十进制代码演示 十六进制转十进制代码演示 十进制转K进制代码演示 任意进制之间的转换代码演示 二进制转十进制 代码演示 // 定义函数 calc,用于将字符转换为对应的数值 int calc(char c) {// 若字符 c 大于等于 9(注:此处…...
【算法百题】专题七_分治快排_专题八_分治归并
文章目录 前言分治快排题:043. [颜⾊分类(medium)](https://leetcode.cn/problems/sort-colors/description/)分析 044. [快速排序(medium)](https://leetcode.cn/problems/sort-an-array/description/)分析 045. [快速…...
DOM4J解析XML, 修改xml的值
1. 引入pom依赖 <dependency><groupId>org.dom4j</groupId><artifactId>dom4j</artifactId><version>2.1.3</version> </dependency> 2. 解析xml, 修改xml节点的值 import org.apache.commons.io.IOUtils; import org.dom4…...
3.16[A]FPGA
FPGA的工作原理是通过配置存储器中的数据来控制可编程逻辑单元和互连资源,从而实现用户定义的逻辑功能。用户可以通过硬件描述语言(HDL)编写代码,然后通过综合、映射、布局布线等步骤生成配置数据,最后将这些数据加载到…...
ssh转发笔记
工作中又学到了,大脑转不过来 现有主机A,主机B,主机C A能访问B,B能访问C,A不能访问C C上80端口有个服务,现在A想访问这个服务,领导让用ssh转发,研究半天没找到理想的语句…...
使用OBS进行webRTC推流参考
参考腾讯云官方文档: 云直播 OBS WebRTC 推流_腾讯云 说明非常详细,分为通过WHIP和OBS插件的形式进行推流。 注意:通过OBS插件的形式进行推流需要使用较低的版本,文档里有说明,需要仔细阅读。...
(链表)面试题 02.07. 链表相交
给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null 。 图示两个链表在节点 c1 开始相交: 题目数据 保证 整个链式结构中不存在环。 注意,函数返回结果后ÿ…...
Python----数据可视化(Pyecharts三:绘图二:涟漪散点图,K线图,漏斗图,雷达图,词云图,地图,柱状图折线图组合,时间线轮廓图)
1、涟漪特效散点图 from pyecharts.globals import SymbolType from pyecharts.charts import EffectScatter from pyecharts.faker import Faker from pyecharts import options as opts from pyecharts.globals import ThemeType # 绘制图表 es (EffectScatter(init_optsop…...
正则表达式:贪婪匹配与非贪婪匹配
正则表达式:贪婪匹配与非贪婪匹配 非贪婪匹配 .*?这三个字符的组合就是非贪婪匹配,意思是匹配任意字符直到遇到第一个后面指定的字符,比如.*?_就表示匹配任意字符直到碰到下划线,还可以组合^来表示从头匹配,比如^.*?_就是从头…...
IP风险度自检,互联网的安全“指南针”
IP地址就像我们的网络“身份证”,而IP风险度则是衡量这个“身份证”安全性的重要指标。它关乎着我们的隐私保护、账号安全以及网络体验,今天就让我们一起深入了解一下IP风险度。 什么是IP风险度 IP风险度是指一个IP地址可能暴露用户真实身份或被网络平台…...
数据结构与算法-图论-拓扑排序
前置芝士 概念 拓扑排序(Topological Sorting)是对有向无环图(DAG,Directed Acyclic Graph)的顶点进行排序的一种算法。它将图中的所有顶点排成一个线性序列,使得对于图中的任意一条有向边 (u, v)&#x…...
Gan网络公式了解
Gan网络 生成器和判别器是亦敌亦友的关系 对于生成模型,损失函数很难定义->所以我们可以将生成模型的输出交给判别模型进行处理,来分辨好坏。 生成器的损失是通过判别器的输出来计算的,而判别器的输出是一个概率值,我们可以通过…...
解决linux mysql命令 bash: mysql: command not found 的方法
首先得知道mysql命令或mysqladmin命令的完整路径 比如mysql的路径是: /usr/local/mysql/bin/mysql,我们则可以这样执行命令: ln -s /usr/local/mysql/bin/mysql /usr/bin © 著作权归作者所有,转载或内容合作请联系作者 喜欢的朋友记得点…...
微服务存在的问题及解决方案
微服务存在的问题及解决方案 1. 存在问题 1.1 接口拖慢 因为一个接口在并发时,正好执行时长又比较长,那么当前这个接口占用过多的 Tomcat 连接,导致其他接口无法即时获取到 Tomcat 连接来完成请求,导致接口拖慢,甚至…...
【css酷炫效果】纯CSS实现立体纸张折叠动效
【css酷炫效果】纯CSS实现悬浮阴影扩散交互 缘创作背景html结构css样式完整代码基础版进阶版(3d 悬浮效果) 效果图 通过CSS box-shadow与transition属性实现悬浮阴影扩散交互,为元素添加细腻的悬浮反馈。 想直接拿走的老板,链接放在这里:htt…...
案例5_1:单位数码管显示0
文章目录 文章介绍效果图仿真图5_1放置单位数码管 代码5_1.c 文章介绍 效果图 仿真图5_1 复制案例1_2的仿真图,在此基础上修改 注意:栅格大小需要缩小 放置单位数码管 代码5_1.c #include <reg52.h>#define uchar unsigned char #define uint un…...
Linux centos7误删/boot拯救方法
1.进入救援模式 插入CentOS 7安装光盘,重启系统。在开机时按BIOS设置对应的按键(通常是F2等),将启动顺序调整为CD - ROM优先。 系统从光盘启动后,选择“Troubleshooting”,然后选择“Rescue a CentOS s…...
操作系统八股文整理(一)
操作系统八股文整理 一、进程和线程的区别二、进程与线程的切换过程一、进程切换进程切换的步骤: 二、线程切换线程切换的步骤: 三、进程切换与线程切换的对比四、上下文切换的优化 三、系统调用一、系统调用的触发二、从用户空间切换到内核空间三、执行…...
20250317笔记本电脑在ubuntu22.04下使用acpi命令查看电池电量
20250317笔记本电脑在ubuntu22.04下使用acpi命令查看电池电量 2025/3/17 18:05 百度:ubuntu查看电池电量 百度为您找到以下结果 ubuntu查看电池电量 在Ubuntu操作系统中,查看电池电量通常可以通过命令行或者图形界面来完成。下面是一些常见的方法&…...
