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

目录

创建型模式概述

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.rhkb.cn/news/474703.html

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

相关文章

【支持向量机(SVM)】:相关概念及API使用

文章目录 1 SVM相关概念1.1 SVM引入1.1.1 SVM思想1.1.2 SVM分类1.1.3 线性可分、线性和非线性的区分 1.2 SVM概念1.3 支持向量概念1.4 软间隔和硬间隔1.5 惩罚系数C1.6 核函数 2 SVM API使用2.1 LinearSVC API 说明2.2 鸢尾花数据集案例2.3 惩罚参数C的影响 1 SVM相关概念 1.1…

某校园网登录界面前端加密绕过

前言 尝试对学校校园网登录框进行爆破,发现密码在前端被加密了 Burp抓包 抓包信息 DDDDD2022***&upass3d5c84b6fb1dc75987884f39c05b0e6a123456782&R10&R21&para00&0MKKey123456&v6ip From表单提交上来的文本这些参数,DDDD是…

网络基础(3)https和加密

http其它的报头 直接看图片: 上图中的第一个和第二个类型之前已经使用过了也就不多做说明了,第三个报头类型使用的很少了。第四个报头类型主要就使用在一些灰度更新的应用上,确定用户使用的软件的版本不让其访问该版本不能访问的功能。下一个…

macOS 的目录结构

文章目录 根目录 (/)常见目录及其用途示例目录结构注意事项根目录 (/)主要目录及其含义其他目录总结 macOS 的目录结构无论是在 Intel 架构还是 ARM 架构的 Mac 电脑上都是相同的。macOS 的目录结构遵循 Unix 和 BSD 的传统,具有许多标准目录。以下是一些主要目录及…

【WPF】Prism学习(八)

Prism Dependency Injection 1.处理解析错误 1.1. 处理解析错误: 这个特性是在Prism 8中引入的,如果你的应用目标是早期版本,则不适用。 1.2. 异常发生的原因: 开发者可能会遇到多种原因导致的异常,常见的错误包括…

集群聊天服务器(11)客户端开发

目录 首页面功能开发添加好友和聊天帮助和添加好友聊天功能创建群组添加群组群组聊天退出 测试问题一对一聊天第一次发送两个离线消息只收到一个创建和加入群组 首页面功能开发 #include "json.hpp" #include <iostream> #include <thread> #include &l…

Pytest-Bdd-Playwright 系列教程(10):配置功能文件路径 优化场景定义

Pytest-Bdd-Playwright 系列教程&#xff08;10&#xff09;&#xff1a;配置功能文件路径 & 优化场景定义 前言一、功能文件路径的配置1.1 全局设置功能文件路径1.2. 在场景中覆盖路径 二、避免重复输入功能文件名2.1 使用方法2.2 functools.partial 的背景 三、应用场景总…

Cyberchef使用功能之-多种压缩/解压缩操作对比

cyberchef的compression操作大类中有大量的压缩和解压缩操作&#xff0c;每种操作的功能和区别是什么&#xff0c;本章将进行讲解&#xff0c;作为我的专栏《Cyberchef 从入门到精通教程》中的一篇&#xff0c;详见这里。 关于文件格式和压缩算法的理论部分在之前的文章《压缩…

Leetcode 回文数

下面是解决这个回文数问题的一个Java解法&#xff1a; 代码解释 特殊情况处理&#xff1a; 如果数字是负数&#xff0c;直接返回false&#xff0c;因为负数不可能是回文数。如果数字以0结尾&#xff0c;但不是0本身&#xff0c;也不可能是回文数&#xff08;例如10不是回文数…

笔记02----重新思考轻量化视觉Transformer中的局部感知CloFormer(即插即用)

1. 基本信息 论文标题: 《Rethinking Local Perception in Lightweight Vision Transformer》中文标题: 《重新思考轻量化视觉Transformer中的局部感知》作者单位: 清华大学发表时间: 2023论文地址: https://arxiv.org/abs/2303.17803代码地址: https://github.com/qhfan/CloF…

JVM垃圾回收详解(重点)

堆空间的基本结构 Java 的自动内存管理主要是针对对象内存的回收和对象内存的分配。同时&#xff0c;Java 自动内存管理最核心的功能是 堆 内存中对象的分配与回收 Java 堆是垃圾收集器管理的主要区域&#xff0c;因此也被称作 GC 堆&#xff08;Garbage Collected Heap&…

深入探索Python集合(Set)的高效应用:数据处理、性能优化与实际案例分析

文章目录 前言&#x1fa81;一、 定义集合1.1 使用大括号 {} 定义集合1.2 使用 set() 函数定义集合 &#x1fa81;二、添加元素2.1 使用 add() 方法2.2 使用 update() 方法 &#x1fa81;三、移除元素3.1 使用 remove() 方法3.2 使用 discard() 方法3.3 使用 pop() 方法3.4 使用…

STM32单片机CAN总线汽车线路通断检测-分享

目录 目录 前言 一、本设计主要实现哪些很“开门”功能&#xff1f; 二、电路设计原理图 1.电路图采用Altium Designer进行设计&#xff1a; 2.实物展示图片 三、程序源代码设计 四、获取资料内容 前言 随着汽车电子技术的不断发展&#xff0c;车辆通信接口在汽车电子控…

NIST 发布后量子密码学转型战略草案

美国国家标准与技术研究所 (NIST) 发布了其初步战略草案&#xff0c;即内部报告 (IR) 8547&#xff0c;标题为“向后量子密码标准过渡”。 该草案概述了 NIST 从当前易受量子计算攻击的加密算法迁移到抗量子替代算法的战略。该草案于 2024 年 11 月 12 日发布&#xff0c;开放…

Javaweb梳理17——HTMLCSS简介

Javaweb梳理17——HTML&CSS简介 17 HTML&CSS简介17.1 HTML介绍17.2 快速入门17.3 基础标签17.3 .1 标题标签17.3.2 hr标签17.3.3 字体标签17.3.4 换行17.3.8 案例17.3.9 图片、音频、视频标签17.3.10 超链接标签17.3.11 列表标签17.3.12 表格标签17.3.11 布局标签17.3.…

【支持向量机(SVM)】:算法原理及核函数

文章目录 1 SVM算法原理1.1 目标函数确定1.2 约束条件优化问题转换1.3 对偶问题转换1.4 确定超平面1.5 计算举例1.6 SVM原理小节 2 SVM核函数2.1 核函数的作用2.2 核函数分类2.3 高斯核函数2.3 高斯核函数API2.4 超参数 γ \gamma γ 1 SVM算法原理 1.1 目标函数确定 SVM思想…

mysql bin log分析

centos7 部署collabora office (yum版 与 docker)_collabora office部署-CSDN博客 1.下载polardb的bin log文件 show binary logs; mysqlbinlog -u 用户名 -p -h 地址 --read-from-remote-server --raw mysql-bin.001768 mysqlbinlog --no-defaults --databasexxx --base64-…

初识进程——Linux

目录 概述 进程控制块 指令知识补充 标识符函数 /proc 目录介绍 /proc/&#xff08;pid&#xff09; cwd exe&#xff1a; fork 结束语 概述 进程是程序执行的实体&#xff0c;两者之间有着密切联系。程序是静态的代码与指令集合&#xff0c;每次运行程序都会创建新的进程…

湘潭大学软件工程算法设计与分析考试复习笔记(三)

回顾 湘潭大学软件工程算法设计与分析考试复习笔记&#xff08;一&#xff09;湘潭大学软件工程算法设计与分析考试复习笔记&#xff08;二&#xff09; 前言 现在继续开始复习。每天复习一点点&#xff0c;嘿嘿。今天本来准备写一个动态规划的题的&#xff0c;感觉半懂不懂…

109. UE5 GAS RPG 实现检查点的存档功能

在这一篇文章里&#xff0c;我们接着实现存档的功能&#xff0c;保存当前玩家的生成位置&#xff0c;游戏里有很多中方式去实现玩家的位置存储&#xff0c;这里我们采用检查点的方式&#xff0c;当玩家接触到当前检查点后&#xff0c;我们可以通过检查点进行保存玩家的状态&…