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

news2024/11/19 3:38:46

目录

创建型模式概述

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 {

    @Override
    public void open() {
        System.out.println("Opening Figure Document");
    }

    @Override
    public void close() {
        System.out.println("Closing Figure Document");
    }

    @Override
    public void save() {
        System.out.println("Saving Figure Document");
    }
    
}

//实现类TextDocument
public class TextDocument implements Document {

    @Override
    public void open(){
        System.out.println("Text Document Opened");
    }

    @Override
    public void close() {
        System.out.println("Text Document Closed");
    }

    @Override
    public void save() {
        System.out.println("Text Document Saved");
    }

}

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

//抽象接口
public interface Application {
    Document createDocument();
}


//实现类FigureApplication
public class FigureApplication implements Application {

    @Override
    public 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.coloradmin.cn/o/2243145.html

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

相关文章

【论文阅读】主动推理:作为感知行为的理论

文章目录 主动推理:作为感知行为的理论摘要1.引言2. 主动推理的概念和历史根源3. 主动推理的规范视角—以及它的发展历程 未完待续 主动推理:作为感知行为的理论 Active inference as a theory of sentient behavior 摘要 这篇文章综述了主动推理的历…

React--》如何高效管理前端环境变量:开发与生产环境配置详解

在前端开发中,如何让项目在不同环境下表现得更为灵活与高效,是每个开发者必须面对的挑战,从开发阶段的调试到生产环境的优化,环境变量配置无疑是其中的关键。 env配置文件:通常用于管理项目的环境变量,环境…

【工具插件类教学】在 Unity 中使用 iTextSharp 实现 PDF 文件生成与导出

目录 一、准备工作 1. 安装 iTextSharp 2. 准备资源文件 二、创建 ExportPDFTool 脚本 1、初始化 PDF 文件,设置字体 2、添加标题、内容、表格和图片 三、使用工具类生成 PDF 四、源码地址 在 Unity 项目中,我们有时会需要生成带有文本、表格和图片的 PDF 文件,以便…

【AlphaFold3】开源本地的安装及使用

文章目录 安装安装DockerInstalling Docker on Host启用Rootless Docker 安装 GPU 支持安装 NVIDIA 驱动程序安装 NVIDIA 对 Docker 的支持 获取 AlphaFold 3 源代码获取基因数据库获取模型参数构建将运行 AlphaFold 3 的 Docker 容器 参考 AlphaFold3: https://github.com/goo…

[JAVA]MyBatis框架—获取SqlSession对象

SqlSessionFactory作为MyBatis框架的核心接口有三大特性 SqlSessionFactory是MyBatis的核心对象 用于初始化MyBatis,创建SqlSession对象 保证SqlSessionFactory在应用中全局唯一 1.SqlSessionFactory是MyBatis的核心对象 假设我们要查询数据库的用户信息&#x…

ArkTS学习笔记:ArkTS起步

ArkTS是HarmonyOS的主力应用开发语言,基于TypeScript扩展,强化了静态检查和分析,旨在提升程序稳定性和性能。它采用静态类型,禁止运行时改变对象布局,并对UI开发框架能力进行扩展,支持声明式UI描述和自定义…

JAVA 之 JDBC

JDBC概述 基本介绍 1.JDBC为访问不同的数据库提供了统一的接口,为使用者屏蔽了细节问题。 2.Java程序员使用JDBC,可以连接任何提供了JDBC驱动程序的数据库系统,从而完成对数据库的各种操作。 3.JDBC的基本原理[ 重要 ] 4.模拟JDBC com.lmbc.myjdbc…

用 Python 从零开始创建神经网络(五):损失函数(Loss Functions)计算网络误差

用损失函数(Loss Functions)计算网络误差 引言1. 分类交叉熵损失(Categorical Cross-Entropy Loss)2. 分类交叉熵损失类(The Categorical Cross-Entropy Loss Class)展示到目前为止的所有代码3. 准确率计算…

Redis做分布式锁

(一)为什么要有分布式锁以及本质 在一个分布式的系统中,会涉及到多个客户端访问同一个公共资源的问题,这时候我们就需要通过锁来做互斥控制,来避免类似于线程安全的问题 因为我们学过的sychronized只能对线程加锁&…

阿里云引领智算集群网络架构的新一轮变革

阿里云引领智算集群网络架构的新一轮变革 云布道师 11 月 8 日~ 10 日在江苏张家港召开的 CCF ChinaNet(即中国网络大会)上,众多院士、教授和业界技术领袖齐聚一堂,畅谈网络未来的发展方向,聚焦智算集群网络的创新变…

预处理(1)(手绘)

大家好,今天给大家分享一下编译器预处理阶段,那么我们来看看。 上面是一些预处理阶段的知识,那么明天给大家讲讲宏吧。 今天分享就到这里,谢谢大家!!

ZYNQ程序固化——ZYNQ学习笔记7

一、ZYNQ启动过程 二、 SD卡启动实操 1、对ZYNQ进行配置添加Flash 2、添加SD卡 3、重新生成硬件信息 4、创建vitis工程文件 5、勾选板级支持包 6、对系统工程进行整体编译,生成两个Debug文件,如图所示。 7、插入SD卡,格式化为 8、考入BOOT.…

FPGA实现PCIE采集电脑端视频转SFP光口万兆UDP输出,基于XDMA+GTX架构,提供2套工程源码和技术支持

目录 1、前言工程概述免责声明 2、相关方案推荐我已有的PCIE方案10G Ethernet Subsystem实现万兆以太网物理层方案 3、PCIE基础知识扫描4、工程详细设计方案工程设计原理框图电脑端视频PCIE视频采集QT上位机XDMA配置及使用XDMA中断模块FDMA图像缓存UDP视频组包发送UDP协议栈MAC…

Mongo数据库集群搭建

目录 1、Mongo集群优势 1.1 高可用性 1.2 水平扩展性 1.3 高性能 1.4 灵活的架构设计 1.5 数据安全 1.6 管理与监控 2、下载指定操作系统版本包 3、部署和验证工作 3.1 准备配置文件及依赖 3.2 启动第一个节点 3.3 部署更多的节点 3.4 初始化副本集 3.5 设置管理…

创建vue3项目步骤

脚手架创建项目: pnpm create vue Cd 项目名称安装依赖:Pnpm iPnpm Lint:修复所有文件风格 ,不然eslint语法警告报错要双引号Pnpm dev启动项目 拦截错误代码提交到git仓库:提交前做代码检查 pnpm dlx husky-in…

C语言项⽬实践-贪吃蛇

目录 1.项目要点 2.窗口设置 2.1mode命令 2.2title命令 2.3system函数 2.Win32 API 2.1 COORD 2.2 GetStdHandle 2.3 CONSOLE_CURSOR_INFO 2.4 GetConsoleCursorInfo 2.5 SetConsoleCursorInfo 2.5 SetConsoleCursorPosition 2.7 GetAsyncKeyState 3.贪吃蛇游戏设…

nfs服务器--RHCE

一,简介 NFS(Network File System,网络文件系统)是FreeBSD支持的文件系统中的一种,它允许网络中的计 算机(不同的计算机、不同的操作系统)之间通过TCP/IP网络共享资源,主要在unix系…

自动化运维(k8s):一键获取指定命名空间镜像包脚本

前言:脚本写成并非一蹴而就,需要不断的调式和修改,这里也是改到了7版本才在 生产环境 中验证成功。 该命令 和 脚本适用于以下场景:在某些项目中,由于特定的安全或政策要求,不允许连接到你的镜像仓库。然而…

HuggingFace:基于YOLOv8的人脸检测模型

个人操作经验总结 1、YOLO的环境配置 github 不论base环境版本如何,建议在conda的虚拟环境中安装 1.1、创建虚拟环境 conda create -n yolov8-face python3.9conda create :创建conda虚拟环境, -n :给虚拟环境命名的…

Unet++改进28:添加PPA(2024最新改进方法)|多分支特征提取策略,捕获不同尺度和层次的特征信息。

本文内容:添加PPA 目录 论文简介 1.步骤一 2.步骤二 3.步骤三 4.步骤四 论文简介 红外小目标检测是一项重要的计算机视觉任务,涉及对红外图像中通常只有几个像素的微小目标进行识别和定位。然而,由于红外图像中物体的体积小,背景一般比较复杂,这给红外图像的识别带来…