《设计模式》创建型模式总结

目录

创建型模式概述

Factory Method: 唯一的类创建型模式

Abstract Factory

Builder模式

Prototype模式

Singleton模式


最近在参与一个量化交易系统的项目,里面涉及到用java来重构部分vnpy的开源框架,因为是框架的搭建,所以会涉及到像事件驱动等设计模式的应用,因此不了解基础的设计模式就无法理解框架设计者对于各个模块以及类的设计,而这也正是我目前所欠缺的能力,之前看到大多数是偏编码规则。这也是我选取《设计模式-可复用面向对对象软件的基础》这本书进行学习的原因。

这本书分为三个部分,第一部分是导论,虽然说是导论但我觉得更多的是跳出设计模式的细节来看它具体在实际项目中的工作流以及一些设计模式的抽象概念,这部分我自认为对于一个对于各个设计模式没有实际实现经验的初学者来说过于抽象,所以我准备放到最后来看,可能会有更深的体会。第二部分是一个案例学习,带领你从零开始构建一个文本编辑器的设计模式并实现它,这部分我准备放在第二部分来看。第三部分就是介绍了三类23个典型的设计模式,这部分我准备首先了解,以期对于设计模式先有一个更加深刻的认识。

这篇文章先总结一下三类设计模式中的第一类-创建型模式。

创建型模式概述

首先,要明确这里所说的设计模式重点聚焦系统级的设计,系统本质上是一组类实例的组合,它区别于单一的类或者接口的设计,需要一定的抽象才能够使得后续的编码工作更有效率。创建型模式的最主要作用就是抽象了系统的实例化过程。在没有这种抽象之前,系统的实例化只是单纯的被看作是N个类的实例化,不利于后续的编码执行。

这种抽象在大体上有两个实现的原则。其一是将系统所需要用到的各种类信息给封装起来;其二是把这些类的创建和组合方式也给隐藏起来。总之,整个系统有一套抽象的接口负责统一和外部对接。至于这个系统内各个类的配置可以是静态的(在编译时固定,也就是类创建型模式),或者是动态的(在运行时指定,也就是对象创建型模式)。换句话说,类创建模式就是为每一个系统预先定义好类的框架,每一个框架通过特定的方法完成创建;而对象创建型模式就是没有预先定义系统类框架,系统是在程序执行中动态完成创建的。

Factory Method: 唯一的类创建型模式

Factory Method(工厂方法)首先针对目标系统定义一个抽象接口,然后让具体的系统实现类来决定如何实例化,它要求使用者必须先定义系统类的框架,而把类的实例化延迟到了子类。

文中作者举了一个案例,对于要设计一套面向不同文件类型的应用处理系统,比如对于图像文件要有图像文件处理系统,对于文本文件要有文本文件处理系统等等。

首先我预先定义好文件类型的接口以及具体的实现类:

//抽象接口
public interface Document {void open();void close();void save();
}//实现类FigureDocument
public class FigureDocument implements Document {@Overridepublic void open() {System.out.println("Opening Figure Document");}@Overridepublic void close() {System.out.println("Closing Figure Document");}@Overridepublic void save() {System.out.println("Saving Figure Document");}}//实现类TextDocument
public class TextDocument implements Document {@Overridepublic void open(){System.out.println("Text Document Opened");}@Overridepublic void close() {System.out.println("Text Document Closed");}@Overridepublic void save() {System.out.println("Text Document Saved");}}

很显然在Application抽象实例中,它是不知道要在什么情况下创建什么文件的,因此我们针对每一个文档都设计对应的application实现类,并且重构对应的document工厂方法:

//抽象接口
public interface Application {Document createDocument();
}//实现类FigureApplication
public class FigureApplication implements Application {@Overridepublic Document createDocument() {return new FigureDocument();}}//实现类TextApplication
public class TextApplication implements Application {public Document createDocument(){return new TextDocument();}
}

然后客户端就可以轻松针对不同的application实现来针对性的创建对应的文件:

public class Client {public static void main(String[] args) {Application figureApplication = new FigureApplication();Document figuredoc = figureApplication.createDocument();figuredoc.open();}
}

优点

首先我认为设计模式的优点最主要还是要面向系统框架的应用开发人员,他们是不是能在系统稳定的前提下简化并且高效率的使用这个框架是核心。从这个维度上说工厂模式有以下的优点:它将具体的系统类构建和它的抽象框架分离。使得应用开发人员除了在实例化的时候要关注创建的系统实例类型,其余时候都只需要对着抽象框架的接口来编程。

除此以外,工厂方法有两个注意事项:

工厂方法可以为子类提供一个扩展功能的钩子:通过在AbstractCreator的抽象工厂方法内提供一个缺省的实现,可以扩展工厂方法的功能,并且这个扩展的功能是可以根据不同的子类进行变化的(当然这个缺省的实现要通过super继承到子类的工厂方法中)。

连接系统外的类:只要是引入了对应工厂方法的类,相当于都整体纳入了你所设计系统的抽象体系当中,要注意在设计层面抽象的颗粒度。

Abstract Factory

Abstract Factory(抽象工厂设计模式)是对象创建型的,所以它并不是预先定义好系统的类框架,而是通过设计一个工厂体系来承担起系统的创建,每一个工厂里都包含了组成系统所必需组件的不同实现方式:

相比于工厂方法设计模式,抽象工厂设计模式面向需要更加灵活的系统设计,同时系统的各个组件未来预计会有大量优化迭代需求。比如文中举例的多视感用户界面应用,每一个应用实例内的组件都会有个性化的视觉需求,而且这种视觉需求需要不停的进行优化迭代,以期保持产品的竞争力。 这种场景里,再使用工厂方法在每一个子类中对每一个组件进行硬编码就显得过于耦合,也不利于后续各组件的优化迭代。

在具体实现时,首先对于系统的各个核心组件定义抽象接口(而不再是系统的抽象接口):

//核心组件Button的抽象接口
public interface Button {void paintButton();void clickButton();...
}//核心组件TextBox的抽象接口
public interface TextBox {void paintTextBox();...}

随后分别去实现不同风格的组件实例:

public class MacOSButton implements Button {private String ButtonName;public MacOSButton(String ButtonName){this.ButtonName = ButtonName;}public void paintButton(){System.out.println("MacOS Button " + ButtonName + " painted");}public void clickButton(){System.out.println("MacOS Button " + ButtonName + " clicked");}
}public class WindowsButton implements Button{private String ButtonName;public WindowsButton(String ButtonName){this.ButtonName = ButtonName;}public void paintButton(){System.out.println("Windows Button: " + ButtonName);}public void clickButton(){System.out.println("Windows Button: " + ButtonName + " is clicked");}
}public class MacOSTextBox implements TextBox{private String TextBoxName;public MacOSTextBox(String TextBoxName){this.TextBoxName = TextBoxName;}public void paintTextBox(){System.out.println("Paint MacOS TextBox: " + TextBoxName);}}public class WindowsTextBox implements TextBox {private String textBoxName;public WindowsTextBox(String textBoxName){this.textBoxName = textBoxName;}public void paintTextBox(){System.out.println("Paint Windows TextBox: " + textBoxName);}
}

随后定义相应的工厂体系,明确对于每一种风格的系统组件实现的组合:

//抽象工厂接口
public interface GUIFactory {Button createButton(String ButtonName);TextBox createTextBox(String TextBoxName);}//工厂实例1
public class MacOSFactory implements GUIFactory{public Button createButton(String ButtonName){return new MacOSButton(ButtonName);}public TextBox createTextBox(String TextBoxName){return new MacOSTextBox(TextBoxName);}}//工厂实例2
public class WindoxsFactory implements GUIFactory{public Button createButton(String ButtonName){return new WindowsButton(ButtonName);}public TextBox createTextBox(String TextBoxName){return new WindowsTextBox(TextBoxName);}
}

在客户端具体实现时可以直接通过统一的抽象方法来创建相应的组件,实现应用开发者接口调用的无感化:

public class Application {public static void main(String[] args) {GUIFactory macOSFactory = new MacOSFactory();Button buttonA = macOSFactory.createButton("ButtonA");//工厂方法无需具像化到特定组件类型TextBox textBoxA = macOSFactory.createTextBox("TextBoxA");//工厂方法无需具像化到特定组件类型buttonA.paintButton();textBoxA.paintTextBox();}}

优点

其实前文也说明了,抽象工厂设计模式抛弃了系统类的设计,将系统打散成了各个核心的组件来单独设计,最后通过工厂方法将对应的组件实例串起来。可以看出,这个设计模式更加的灵活,体现在一下几个方面:

  1. 使得产品系列的切换变得非常容易:基于上面的案例,可以更进一步在应用类中设置一个应用的创建方法,将工厂类作为输入,就可以实现只要一个工厂方法就可以完成一套系统所有组件的部署,只要改变一个工厂方法,整个应用的所有组件就会立刻完成变化。
  2. 有利于产品的一致性:同一个工厂方法定义了所有组件的一套版本,不会出现不同版本冲突的问题。

缺点:

  1. 难以支持新的组件:新的组件首先要在抽象工厂方法中定义创建接口,然后在每一个具体的工厂方法中分别实现,所需要的工作量比较大。

Builder模式

Builder模式侧重于实现系统的创建流程和具体的系统实例分离,也就是可以实现用一套创建流程来创建多种系统实例。细心的朋友们可以发现,这个功能Abstract Factory也可以实现,其实整体来看这两种方法非常的相似,都是尝试用一层抽象来统一不同系统实例的创建,但是两者存在一些差异,这个放到最后再说。

 从这张架构图就可以看出来builder模式和Abstract Factory的相似性之高(Director这个角色在Abstract Factory中也完全可以设置,就是其优点第一点中所提的应用类中一个应用的创建方法)。而且Builder的设计与Factory也非常的类似,我把文中两个关于Maze的C++抽象类贴出来,大家可以对比一下:

首先对于各个组件的构建没有太大的区别,只不过Factory提供了缺省的实现,并且提供了公有的构造器(其实我觉得用protected未尝不可,这是一个抽象类,也不应该被子类之外来调用,大家批评指正)。

唯一MazeBuilder多的是GetMaze()这个方法,他会返回一个完整的Maze对象,这是Factory所没有的,这就引出了这两者最主要的区别,Builder实例中是带着系统实例的,但是Factory实例中没有,它只是单纯的工厂方法集合。 这就决定了他们创建系统实例的逻辑是不一样的,如下对比所示

factory方法是先创建maze实例,然后通过其他的工厂方法去为这个系统实例添加各个组件,而builder模式是一步一步的构建各个组件,最终通过get方法获取系统实例(其实再往下看一层,builder也是先创建系统实例,但是在面向应用开发接口的具体实现这一层(CreateMaze的方法实现),他是最后才获得系统实例,所以如果要说本质的区别,那就是builder相对于abstract factory再抽象了一层吧,至于这一层的抽象有没有意义,我觉得有吧,至少看着更简洁了一点,就像老马的猛禽1和猛禽3)。

知道了这个区别以后就不难理解builder模式的几个优势:

  1. 面向应用开发人员将系统的创建流程和具体的系统实例分离(这一点Abstract Factory也可以做到)
  2. 可以使得应用开发人员对于系统实例的创建过程进行更加精确的控制,并且这个控制相对于其他的创建模式更加简洁

Prototype模式

Prototype(原型)模式的核心就是用原型实例指定创建对象的种类,并且通过拷贝的方式创建这些原型新的对象。

原型模式主要应用于系统的各组件需要保持一致的场景,通过克隆的方式高效的保证所有组件都是一致的。

书中举了一个乐谱编辑器的例子,这个编辑器的主要处理对象当然是乐谱,以及在乐谱上的各种音符(以全音符和二分音符为例),这三个东西都是有标准的不会因为不同的乐谱对象而改变。所以适合使用原型模式。

首先定义这三个实现类及其接口,可以看到他们都实现了clone方法(建议还是要自定义一个接口,而不要直接使用Clonable接口,应用研发人员不易看懂):

//组件接口
public interface Graphic {void draw(Position position);Graphic clone();
}//乐谱实现类
public class Staff implements Graphic {public void draw(Position position){System.out.println("Drawing staff at line "+position.getLineCount()+", column "+position.getColumnCount());}public Graphic clone(){try {return (Graphic) super.clone();} catch (Exception e) {throw new AssertionError("Clone not supported");}}
}//半分音符实现类
public class HalfNote implements Graphic{public void draw(Position position){System.out.println("Drawing HalfNote at line "+position.getLineCount()+", column "+position.getColumnCount());}public Graphic clone(){try {return (Graphic) super.clone();} catch (Exception e) {throw new AssertionError("Clone not supported");}}
}//全音符实现类
public class WholeNote implements Graphic {public void draw(Position position){System.out.println("Drawing WholeNote at line "+position.getLineCount()+", column "+position.getColumnCount());}public Graphic clone(){try {return (Graphic) super.clone();} catch (Exception e) {throw new AssertionError("Clone not supported");}}}

然后可以直接定义一个工厂方法,通过已经实现的组件实例作为输入来调用他们的clone方法,达到创建的目的:

public class GraphicCreateFactory{private Staff staff;private WholeNote wholeNote;private HalfNote halfNote;public GraphicCreateFactory(Staff staff, WholeNote wholeNote, HalfNote halfNote){this.staff = staff;this.wholeNote = wholeNote;this.halfNote = halfNote;};public Staff createStaff(){return staff.clone();}public WholeNote createWholeNote(){return wholeNote.clone();}public HalfNote createHalfNote(){return halfNote.clone();}}

所以客户端可以预先创建原型后,作为参数调用相应的创建方法。

Singleton模式

Singleton(单例)模式在之前Effective Java中也介绍过,就是保证一个类仅有一个实例,并且提供一个访问它的全局访问点。它要实现的目的其实和Prototype有些类似,就是要保证组件的唯一性。

下面的单例模式设计可以根据环境变量type的值来定向实例化适当的MazeFactory子类。

public class MazeFactory {private static MazeFactory instance;protected MazeFactory(){};public static MazeFactory getInstance(String type){if(instance == null){synchronized(MazeFactory.class){if (instance==null) {if (type.equals("Standard")) {instance = new MazeFactory();}if (type.equals("Bombed")) {instance = new BombMazeFactory();}}}}return instance;}public Maze createMaze(){return new Maze();}public Room createRoom(int roomNo){return new Room(roomNo);}public Wall createWall(){return new Wall();}public Door createDoor(Room room1, Room room2){return new Door(room1, room2);}
}

还有就是单例的实现方式要注意并发的访问设计,这部分在Effective Java学习笔记--单例(Singleton)及其属性的增强有涉及,这里就不展开了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/61197.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

c++类对象练习

#include <iostream> #include <cstring>using namespace std;class mystring {char* buf; public:mystring(); //构造函数mystring(const char* str); //构造函数void show(); //输出函数void setmystr(const mystring str); //设置函数const char* getmystr() co…

CH03_反射

第3章&#xff1a;反射 本章目标 掌握反射的原理 熟悉反射的基本运用 本章内容 反射是什么 C# 编译运行过程 首先我们在VS点击编译的时候&#xff0c;就会将C#源代码编译成程序集 程序集以可执行文件 (.exe) 或动态链接库文件 (.dll) 的形式实现 程序集中包含有Microsoft …

多品牌摄像机视频平台EasyCVR视频融合平台+应急布控球:打造城市安全监控新体系

在当今快速发展的智慧城市和数字化转型浪潮中&#xff0c;视频监控技术已成为提升公共安全、优化城市管理、增强应急响应能力的重要工具。EasyCVR视频监控平台以其强大的多协议接入能力和多样化的视频流格式分发功能&#xff0c;为用户提供了一个全面、灵活、高效的视频监控解决…

深入理解 Maven 生命周期与常用命令:从编译到安装

Maven 是 Java 项目管理中不可或缺的工具之一&#xff0c;其核心功能包括依赖管理、项目构建和发布等。本文将围绕 Maven 的生命周期及常用命令&#xff0c;解析从项目编译到安装的完整流程&#xff0c;并结合实际案例帮助读者更好地掌握 Maven 的使用。 1. Maven 生命周期概述…

数据结构 (3)线性表的概念及其抽象数据类型定义

一、线性表的概念 定义&#xff1a;线性表是指具有相同数据类型的n个数据元素的有限序列。可以表示为L(a1,a2,…,ai,…,an)&#xff0c;其中a1是第一个元素&#xff0c;称为表头&#xff1b;an是最后一个元素&#xff0c;称为表尾。 特点&#xff1a; 有序性&#xff1a;线性表…

Java基础——继承和多态

目录 一、继承 继承的定义&#xff1a; 继承的基本用法&#xff1a; 如何调用父类的方法&#xff1f; 二、多态 多态性的好处 多态中的强制类型转换&#xff1a; 包的命名规则——域名倒叙 一、继承 继承的定义&#xff1a; 继承是面向对象编程中的一种机制&#xff0c…

【Zookeeper】一、Zookeeper的使命

摩尔定律揭示了集成电路每18个月计算性能就会增加一倍。 Zookeeper以Fast Paxos算法为基础。 在一个大型应用中&#xff0c;经常会按照功能边界将应用分为多个模块&#xff0c;这些模块可以分别独立部署。而要完成某一项具体的功能&#xff0c;不能仅靠其中一个模块&#xff…

vue3中父div设置display flex,2个子div重叠

在Vue 3中&#xff0c;若要设置父div使用flex布局并且使得2个子div重叠&#xff0c;可以在父div上使用样式display: flex以及position: relative&#xff0c;然后在子div上使用position: absolute来定位。 <template><div class"parent"><div class&…

Elasticsearch面试内容整理-分析与映射

在 Elasticsearch 中,分析(Analysis)和映射(Mapping)是数据处理和存储的核心部分。它们共同决定了数据如何被解析、存储以及如何被有效地搜索和查询。以下是关于分析和映射的详细介绍。 分析(Analysis) 分析是将文本数据转换为可以被 Elasticsearch 搜索的索引格式的过程…

播放器开发之ffmpeg 硬件解码方案

硬件编解码的概念 硬件编解码是⾮CPU通过烧写运⾏视频加速功能对⾼清视频流进⾏编解码&#xff0c;其中⾮CPU可包括GPU、FPGA或者 ASIC等独⽴硬件模块&#xff0c;把CPU⾼使⽤率的视频解码⼯作从CPU⾥分离出来&#xff0c;降低CPU的使⽤负荷&#xff0c;使得平台能 ⾼效且流畅…

Go 编译代码-分平台编译

要针对 Mac, Linux, 和 Windows 编译单个 main.go 文件&#xff0c;可以使用 Go 的交叉编译功能&#xff0c;通过设置环境变量 GOOS 和 GOARCH 来指定目标操作系统和架构。 编译命令 在项目目录下执行以下命令&#xff1a; 1. MacOS 编译 GOOSdarwin GOARCHamd64 go build …

使用Python和OpenCV连接并处理IP摄像头视频流

使用Python和OpenCV连接并处理IP摄像头视频流 随着智能设备的发展&#xff0c;越来越多的家庭和企业开始使用IP摄像头进行安全监控或远程查看。这些摄像头通常可以通过网络访问&#xff0c;提供了丰富的功能&#xff0c;如实时视频流、云台控制等。本文将详细介绍如何利用Pyth…

计算机毕业设计SparkStreaming+Kafka旅游推荐系统 旅游景点客流量预测 旅游可视化 旅游大数据 Hive数据仓库 机器学习 深度学习

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 作者简介&#xff1a;Java领…

【C#】面向对象:矩形类计算周长和面积

文章目录 一、矩形类的设计与实现1.1 矩形类的属性1.2 矩形类的构造函数1.3 矩形类的方法1.4 代码实现1.4.1 运行 一、矩形类的设计与实现 题目&#xff1a;编写一个矩形类&#xff0c;私有数据成员为举行的长(Len)和宽(Wid)&#xff0c;无参构造函数将len和wid设置为0&#x…

上海市计算机学会竞赛平台2024年11月月赛丙组考勤系统

题目描述 在 Carol 的办公楼的入口处有一套刷卡系统&#xff0c;每个员工都有一张唯一的身份卡&#xff0c;他们每次进出大楼都要刷卡&#xff0c;而系统会依次记录每次刷卡的员工编号&#xff0c;员工和他的编号一一对应&#xff0c;且在一天内一共有 nn 次刷卡记录。 一个员…

【PyTorch][chapter 28] 揭秘 Transformer:缩放定律指南

概括 我们介绍了 LLM 的各种缩放定律&#xff0c;研究了模型损失如何随着训练数据和参数数量的增加而变化。讨论包括对用于解释 LLM 缩放定律的 IsoLoss 轮廓和 IsoFLOPs 切片的解释&#xff0c;从而为优化计算资源提供了见解。 最后&#xff0c;我们讨论了 FLOP 和 FLOPS 的概…

Android上运行Opencv(TODO)

在高通安卓平台上&#xff0c;确实可以通过 NDK 使用 OpenCV 并访问摄像头。NDK 提供了更高性能的计算能力&#xff0c;特别是在图像处理和计算密集型任务中&#xff0c;与 OpenCV 结合可以充分利用高通平台的硬件资源&#xff08;如 NEON SIMD 指令集和 GPU 加速&#xff09;。…

【GNU】gcc -g编译选项 -g0 -g1 -g2 -g3 -gdwarf

1、gcc -g的作用 GCC 的 -g 选项用于在编译时生成调试信息&#xff0c;这些信息会嵌入到生成的目标文件或可执行文件中&#xff0c;主要目的是为了支持调试器&#xff08;如 gdb&#xff09;对程序的调试工作。 1.1 生成调试信息 当你在编译代码时使用 -g 选项&#xff0c;GCC…

【课堂笔记】隐私计算实训营第四期:隐私求交PSI

隐私计算实训营第四期&#xff1a;隐私求交PSI 安全求交集&#xff08;PSI&#xff09;定义PSI功能和分类最基础的PSI&#xff1a;Two-Party Semi-Honest PSI如何设计Two-Party Semi-Honest PSI方法1&#xff1a;一个基于Hash的PSI方法2&#xff1a;基于Diffie-Hellman密钥交换…

当企业服务器受到网络攻击该怎样处理?

在如今的互联网社会当中&#xff0c;网络攻击无处不在&#xff0c;其中最为常见的攻击方式就是分布式拒绝服务攻击和CC大流量攻击&#xff0c;对目标服务器或者是网络进行资源占用&#xff0c;导致服务器出现拒接服务&#xff0c;下面我们则主要了解一下网络攻击的方式。 一、攻…