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

目录

创建型模式概述

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)及其属性的增强有涉及,这里就不展开了。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Bug合集】——Java大小写引起传参失败,获取值为null的解决方案

阿华代码,不是逆风,就是我疯 你们的点赞收藏是我前进最大的动力!! 希望本文内容能够帮助到你!! 目录 一:本文面向的人群 二:错误场景引入 三:正确场景引入 四&#xf…

论文阅读--supervised learning with quantum enhanced feature spaces

简略摘要 量子算法实现计算加速的核心要素是通过可控纠缠和干涉利用指数级大的量子态空间。本文在超导处理器上提出并实验实现了两种量子算法。这两种方法的一个关键组成部分是使用量子态空间作为特征空间。只有在量子计算机上才能有效访问的量子增强特征空间的使用为量子优势提…

网络安全之信息收集-实战-1

请注意,本文仅供合法和授权的渗透测试使用,任何未经授权的活动都是违法的。 实战:补天公益src“吉林通用航空职业技术学院” 奇安信|用户登录https://www.butian.net/Loo/submit?cid64918 域名或ip:https://www.jlth…

jenkins离线安装插件

Jenkins 在线安装插件失败 报错: Caused: java.io.IOException: Failed to load https://updates.jenkins.io/download/plugins/login-theme/244.vd67c77f0c4c8/login-theme.hpi to /var/jenkins_home/plugins/login-theme.jpi.tmpat hudson.model.UpdateCenter$Up…

MATLAB的语音信号采集与处理分析

1、基本描述 本文描述的系统是一个全面而精细的语音信号处理平台,核心组件由MATLAB的高级功能模块构建而成。系统的核心交互界面,借助于MATLAB的uifigure函数搭建,为用户提供了一个直观且响应迅速的操作环境。通过设计的GUI按钮,如…

【赵渝强老师】MySQL的慢查询日志

MySQL的慢查询日志可以把超过参数long_query_time时间的所有SQL语句记录进来,帮助DBA人员优化所有有问题的SQL语句。通过mysqldumpslow工具可以查看慢查询日志。 视频讲解如下: MySQL的慢查询日志 【赵渝强老师】MySQL的慢查询日志 下面通过具体的演示…

IDEA指定Maven的settings不生效问题处理

文章目录 一、问题描述二、问题分析三、问题解决 一、问题描述 在Idea中手动指定了maven的settings配置文件,但是一直没生效。 如下图:设置加载settings-aliyun.xml文件,但是最后发现还是在加载settings.xml文件 二、问题分析 ‌在Intel…

论文阅读:Uni-ISP Unifying the Learning of ISPs from Multiple Cameras

这是 ECCV 2024 的一篇文章,文章作者想建立一个统一的 ISP 模型,以实现在不同手机之间的自由切换。文章作者是香港中文大学的 xue tianfan 和 Gu jinwei 老师。 Abstract 现代端到端图像信号处理器(ISPs)能够学习从 RAW/XYZ 数据…

[免费]SpringBoot+Vue毕业设计论文管理系统【论文+源码+SQL脚本】

大家好,我是java1234_小锋老师,看到一个不错的SpringBootVue毕业设计论文管理系统,分享下哈。 项目视频演示 【免费】SpringBootVue毕业设计论文管理系统 Java毕业设计_哔哩哔哩_bilibili 项目介绍 现代经济快节奏发展以及不断完善升级的信…

项目技术栈-解决方案-web3去中心化

web3去中心化 Web3 DApp区块链:钱包:智能合约:UI:ETH系开发技能树DeFi应用 去中心化金融P2P 去中心化网络参考Web3 DApp 区块链: 以以太坊(Ethereum)为主流,也包括Solana、Aptos等其他非EVM链。 区块链本身是软件,需要运行在一系列节点上,这些节点组成P2P网络或者半…

使用 Axios 拦截器优化 HTTP 请求与响应的实践

目录 前言1. Axios 简介与拦截器概念1.1 Axios 的特点1.2 什么是拦截器 2. 请求拦截器的应用与实践2.1 请求拦截器的作用2.2 请求拦截器实现 3. 响应拦截器的应用与实践3.1 响应拦截器的作用3.2 响应拦截器实现 4. 综合实例:一个完整的 Axios 配置5. 使用拦截器的好…

Photino:通过.NET Core构建跨平台桌面应用程序,.net国产系统

一、Photino.NET简介: 最近发现了一个不错的框架 Photino.Net 一份代码运行,三个平台 windows max linux ,其中windows10,windows11,ubuntu 18.04,ubuntu 20.04 已测试均可以。mac 因为没有相关电脑没有测试。 github:https://github.com/t…

Python爬虫:如何从1688阿里巴巴获取公司信息

在当今的数字化时代,数据已成为企业决策和市场分析的重要资产。对于市场研究人员和企业分析师来说,能够快速获取和分析大量数据至关重要。阿里巴巴的1688.com作为中国最大的B2B电子商务平台之一,拥有海量的企业档案和产品信息。本文将介绍如何…

如何构建高效的接口自动化测试框架?

🍅 点击文末小卡片 ,免费获取软件测试全套资料,资料在手,涨薪更快 在选择接口测试自动化框架时,需要根据团队的技术栈和项目需求来综合考虑。对于测试团队来说,使用Python相关的测试框架更为便捷。无论选…

创建型设计模式(模版方法、观察者模式、策略模式)

继承过程中,虚函数表的变化。动态多态,继承中虚函数的重写

数据结构(顺序队列——c语言实现)

队列的概念: 队列是限制在两端进行插入和删除操作的线性表,允许进行存入的一端称为“队尾”,允许进行删除操作的一端称为“队头”。当线性表中没有元素时,称为“空队”。特点:先进先出(FIFO)。 …

3C产品说明书电子化转变:用户体验、环保与商业机遇的共赢

在科技日新月异的当代社会,3C产品(涵盖计算机类、通信类和消费类电子产品)已成为我们日常生活中不可或缺的重要元素。与此同时,这些产品的配套说明书也经历了一场从纸质到电子化的深刻变革。这一转变不仅体现了技术的飞速进步&…

web服务nginx实验6:nginx发布动态页面的方法

安装软件: 启动服务: 创建文件: 再vim打开,写东西: 重启服务: Windows客户端测试:(服务端要关防火墙) 删除默认访问发.php文件: 创建一个新的配置文件&#x…

Three.js 相机控制器Controls

在 3D 场景中,摄像机的控制尤为重要,因为它决定了用户如何观察和与场景互动。Three.js 提供了多种相机控制器,最常用的有 OrbitControls、TrackballControls、FlyControls 和 FirstPersonControls。OrbitControls 适合用于查看和检查 3D 模型…

应急响应:玄机_Linux后门应急

https://xj.edisec.net/challenges/95 11关做出拿到万能密码,ATMB6666,后面都在root权限下操作 1、主机后门用户名称:提交格式如:flag{backdoor} cat /etc/passwd,发现后门用户 flag{backdoor} 2、主机排查项中可以…