设计模式之建造者模式(二)

目录

  • 概述
    • 概念
    • 角色
    • 类图
    • 适用场景
  • 详述
    • 画小人业务
      • 类的介绍
      • 代码
      • 解析
    • 建造者基本代码
      • 类介绍
      • 代码
      • 解析
  • 总结
    • 设计原则
    • 其他

概述

概念

    建造者模式是一种创建型设计模式,它可以将复杂对象的构建过程与其表示分离,使得同样的构建过程可以创建不同的表示。

角色

在建造者模式中,有以下几个重要角色:

  • 产品(Product):表示正在构建的复杂对象。它由一系列部件组成,这些部件可能是具体类或接口的实现。

  • 抽象建造者(Abstract Builder):定义了构建产品的接口,声明了创建各个部件的抽象方法。

  • 具体建造者(ConcreteBuilder):实现了抽象建造者接口,负责具体的产品构建,并实现各个部件的具体创建方法。

  • 指挥者(Director):负责安排已有的部件的建造过程,和具体建造者进行交互,以便构建最终的产品。

  • 客户端(Client):通过指挥者创建产品对象的客户端代码。

    建造者模式的核心思想是将一个复杂对象的构建过程与其表示分离,使得同样的构建过程可以创建不同的表示。通过定义不同的具体建造者,可以构建出不同的产品对象,而客户端只需关心所需的产品类型和指挥者的调用即可。这样可以增加灵活性,降低了客户端与产品对象的耦合度。

类图

在这里插入图片描述

适用场景

建造者模式适用于以下情况:

  • 当要创建的对象具有复杂的内部结构时。
  • 需要通过多个步骤来构建对象。
  • 构建过程需要根据不同的配置选择不同的表示。

详述

    理解一个设计模式,可以先从概念入手,在建造者模式概念当中,有几个说的较为模糊的词语(其实是专业词汇,只是我们不太懂罢了),再来回顾下概念“将复杂对象的构建过程与其表示分离,使得同样的构建过程可以创建不同的表示”,这里的复杂对象在前面的角色当中有说过,就是product,那什么叫构建和表示分离呢,构建是什么,表示是什么。接下来结合代码和业务说下我的理解。

画小人业务

类的介绍

  • PersonBuilder 抽象建造者类:定义了构建产品各个部件的抽象方法,以及保存产品对象的引用。在例子中,它定义了画人物各个部分的抽象方法,并保存了 Graphics 类型的引用。

  • PersonThinBuilder 具体建造者类:实现了抽象建造者类中定义的抽象方法,构建小瘦人的各个部分。在例子中,它实现了画小瘦人各个部分的方法。

  • PersonFatBuilder 具体建造者类:实现了抽象建造者类中定义的抽象方法,构建小胖人的各个部分。在例子中,它实现了画小胖人各个部分的方法。

  • PersonDirector 指挥者类:负责组织建造过程,控制具体建造者的调用顺序,最终构建出完整的产品。在例子中,它将小瘦人和小胖人的构建过程组织起来,并调用具体建造者的方法构建出完整的人物。

  • Client 客户端类:通过实例化具体建造者和指挥者对象,构建出不同类型的产品。在这里,创建了一个窗口,并在窗口中画出小瘦人和小胖人。

代码

//抽象建造者
abstract class PersonBuilder {
    protected Graphics g;
    public PersonBuilder(Graphics g){
        this.g=g;
    }
    public abstract void buildHead();
    public abstract void buildBody();
    public abstract void buildArmLeft();
    public abstract void buildArmRight();
    public abstract void buildLegLeft();
    public abstract void buildLegRight();

}
//具体建造者--画小瘦人
public class PersonThinBuilder extends PersonBuilder{
    public PersonThinBuilder(Graphics g){
        super(g);
    }
    @Override
    public void buildHead() {
        g.drawOval(150, 120, 30, 30);
    }

    @Override
    public void buildBody() {
        g.drawRect(160, 150, 10, 50);
    }

    @Override
    public void buildArmLeft() {
        g.drawLine(160, 150, 140, 200);
    }

    @Override
    public void buildArmRight() {
        g.drawLine(170, 150, 190, 200);
    }

    @Override
    public void buildLegLeft() {
        g.drawLine(160, 200, 145, 250);
    }

    @Override
    public void buildLegRight() {
        g.drawLine(170, 200, 185, 250);
    }
}
//具体建造者2--画小胖人
public class PersonFatBuilder extends PersonBuilder{
    public PersonFatBuilder(Graphics g) {
        super(g);
    }

    @Override
    public void buildHead() {
        g.drawOval(250, 120, 30, 30);

    }

    @Override
    public void buildBody() {
        g.drawOval(245, 150, 40, 50);

    }

    @Override
    public void buildArmLeft() {
        g.drawLine(250, 150, 230, 200);

    }

    @Override
    public void buildArmRight() {
        g.drawLine(280, 150, 300, 200);
    }

    @Override
    public void buildLegLeft() {
        g.drawLine(260, 200, 245, 250);

    }

    @Override
    public void buildLegRight() {
        g.drawLine(280, 200, 185, 250);

    }
}
//指挥者
public class PersonDirector {
    private PersonBuilder pb;

    public PersonDirector(PersonBuilder pb) {
        this.pb = pb;

    }
public void createPerson(){
        pb.buildHead();
        pb.buildBody();
        pb.buildArmLeft();
        pb.buildArmRight();
        pb.buildLegLeft();
        pb.buildLegRight();
}
}
//客户端
public class Client extends JFrame {
    public Client() {
        setSize(400, 400);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setLocationRelativeTo(null);
    }

    public void paint(Graphics g) {
        PersonThinBuilder gThin = new PersonThinBuilder(g);
        PersonDirector pdThin = new PersonDirector(gThin);
        pdThin.createPerson();

        PersonFatBuilder gFat = new PersonFatBuilder(g);
        PersonDirector pdFat = new PersonDirector(gFat);
        pdFat.createPerson();
    }

    public static void main(String[] args) {
        new Client().setVisible(true);//显示窗口
    }
}


    在这个例子中,可以清晰地看到建造者模式的结构和流程:抽象建造者定义产品的各个部分,具体建造者实现这些部分;指挥者组织建造过程,调用具体建造者构建出完整的产品。这种方式可以使得系统更加灵活、可扩展,并且降低了组件之间的耦合度。

解析

    本例中通过定义不同的具体建造者,可以创建出具有不同特征的产品。在示例中,通过 PersonThinBuilder 和 PersonFatBuilder 可以构建出小瘦人和小胖人两种不同类型的产品。
添加新的具体建造者并不影响已有的建造者类和指挥者类。如果需要增加一种新类型的产品,只需要创建一个新的具体建造者来实现该产品的构建过程,并在指挥者类中调用新的具体建造者即可。这样,系统的扩展性得到了保证,而无需修改已有的代码。
    指挥者类负责组织建造过程,但它不知道具体建造者的细节。具体建造者类负责构建产品的各个部分,但它不知道最终产品的组装方式。这样,指挥者和具体建造者之间的关系是松耦合的,它们之间仅通过抽象建造者进行交互。这种解耦使得指挥者类和具体建造者类可以独立变化,互不影响。
    总之,这里通过将复杂对象的构建过程分解为多个简单的部分,使得系统更加灵活、可扩展,并且减少了组件之间的依赖关系,提高了系统的可维护性和可测试性。

建造者基本代码

    根据业务了解完建造者模式之后,来看下建造者的基本代码,同时也来对比一下基本代码和上面的画小人的业务有什么不一样的地方。

类介绍

    建造者模式的基本代码包含了四个角色:产品类、抽象建造者、具体建造者和指挥者。Product 类表示产品,Builder 类是抽象建造者,ConcreteBuilder1 和 ConcreteBuilder2 是具体建造者,而 Director 是指挥者。这些角色各自承担不同的责任,协作完成产品的构建过程。而在上面的画小人的代码中,并没有product,那这两段代码有什么本质上的不同吗,先看完下面的代码。

代码

//产品
public class Product {
    ArrayList<String> parts = new ArrayList<String>();
    //添加新的产品部件
    public void add(String part){
        parts.add(part);
    }
    //列举所有的产品
    public void show(){
        for(String part:parts){
            System.out.println(part);
        }
    }
}
//抽象建造者
abstract class Builder {
    public abstract void buildPartA();
    public abstract void buildPartB();
    public abstract Product getResult();

}

//具体建造者1
public class ConcreteBuilder1 extends Builder {
    private Product product = new Product();

    @Override
    public void buildPartA() {
        product.add("部件A");
    }

    @Override
    public void buildPartB() {
        product.add("部件B");
    }

    @Override
    public Product getResult() {
       return product;
    }
}
//具体建造者2
public class ConcreteBuilder2 extends Builder {
    private Product product = new Product();

    @Override
    public void buildPartA() {
        product.add("部件X" );
    }

    @Override
    public void buildPartB() {
        product.add("部件Y");
    }

    @Override
    public Product getResult() {
       return product;
    }
}
//指挥者
public class Director {
    public void construct(Builder builder){
        builder.buildPartA();
        builder.buildPartB();
    }
}
//客户端
public class Client {
    public static void main(String[] args) {
        Director director = new Director();
        Builder b1 = new ConcreteBuilder1();
        Builder b2 = new ConcreteBuilder2();

        //指挥者用concreteBuilder1的方法来建造产品
        director.construct(b1);
        Product p1=b1.getResult();
        p1.show();

        //指挥者用concreteBuilder2的方法来建造产品
        director.construct(b2);
        Product p2=b2.getResult();
        p2.show();

    }
}

解析

两段代码的不同:

    1、在上面画小人的例子中, PersonFatBuilder和PersonThintBuilder使用了具体的数值来定义绘图的位置和大小,这在一些情况下可能不够灵活和可维护。通常,建议使用变量或常量来代替这些硬编码的数值,以增加代码的可读性和可扩展性。

    2、画小人的例子中没有明确定义一个产品类(Product)。建造者模式通常包含一个产品类,建造者负责创建并组装产品,而指挥者负责调用具体的建造者来创建产品。但是在画小人的例子并没有一个显式的产品类。在这里可以认为PersonBuilder和其子类(如PersonFatBuilder)兼具了建造者和产品的角色。也就是说,建造者本身也承担了产品的属性和方法,用于表示要构建的对象,(但是要注意这里的产品有具体的属性值了,这与product就有了很大的区别,后面细说),但是即便没有显式的产品类,但仍然可以理解为遵循了建造者模式的核心思想:将构建逻辑与表现逻辑分离,通过不同的建造者来创建不同的对象。建造者模式不一定非要有一个显式的产品类,这取决于具体的设计需求。在一些简单的场景中,可以将产品的属性和方法直接定义在建造者中,以简化代码结构。然而,在更复杂的场景中,使用一个独立的产品类可以更好地组织和管理产品的属性和行为。

    3、在看两段代码中建造者里的接口(看指挥者里调用的builder接口也一样,这样可能更直观),看代码:

//画小人业务中builder里的接口
public void createPerson(){
        pb.buildHead();
        pb.buildBody();
        pb.buildArmLeft();
        pb.buildArmRight();
        pb.buildLegLeft();
        pb.buildLegRight();
}
//建造者基本代码中builder里的接口
public class Director {
    public void construct(Builder builder){
        builder.buildPartA();
        builder.buildPartB();
    }
}

    这两个看起来好像只是名字不同,其实完全不一样,画小人的业务画出来的只能是小人,因为有头有胳膊有腿,但是建造者基本代码中起名是buildPartA(),再去看两个具体的builder里添加的都是什么内容(一个是“部件A/B”,另一个是“部件X/Y”), 这里buildpartA和buildpartB并不是具体的内容,只是两个规范或者说两个标准,这里不光是标准,也定义了执行的顺序,具体创建什么东西是由product来决定的, Product当中添加的参数类型,也是可以更换的,也就是用户指定的需要创建的类型。 也就是符合Builder当中定义的规范数量,并且符合product当中 add方法接收的参数的类型的产品就可以,无论是部件a,b还是部件xy ,再举一个例子,可以创建有头有身子的小人也可以创建放盐又放醋的菜。在逻辑上就好像改变父类 Builder的类型的效果。

    4、现在就来说说要引入一个独立的产品类product有什么好处:
    前面说过概念中的复杂对象指的是product。这里product也是概念中提到的“表示”,在这里是个虚的概念,所谓虚的就是谁都可以,只要符合规范,规范指的的只要数据类型是String就可以,当然这个类型也是你自己定义的。例子中指定的是String。

    ConcreteBuilder做“构建”的,构建的是product的组件(builderPartA/B),这部分的组件是由部件(部件X/Y等)构成的,规定的是每个组件里部件(最小的颗粒)的数量和顺序。在这里也可以把builderPartA/B看成是部件的“表示”。而director相对于builder来说是构建的过程,构建的是各个组件(builderPartA/B)的顺序,以及有哪些组件(不是ConcreteBuilder定义的组件都必须调用,需要谁就调用谁)。

总结

设计原则

在建造者模式中,涉及到以下几个设计原则:

  • 单一职责原则(Single Responsibility Principle):每个类应该只有一个引起它变化的原因。在建造者模式中,具体建造者负责构建产品的各个部件,指挥者负责组织建造过程,产品负责表示正在构建的复杂对象。通过将不同的责任分配给不同的类,保持了类的单一职责。

    开闭原则(Open-Closed Principle):软件实体应对扩展开放,对修改关闭。在建造者模式中,可以通过新增具体建造者来扩展不同类型的产品,而无需修改已有的代码。这样在增加新的产品类型时,不会对现有的客户端代码产生影响。

  • 依赖倒置原则(Dependency Inversion Principle):高层模块不应依赖于低层模块,二者都应依赖于抽象。在建造者模式中,指挥者与具体建造者之间的交互是通过抽象建造者接口进行的,而不是直接依赖于具体建造者。这样可以降低耦合性,并且使得具体建造者可以灵活替换。

  • 迪米特法则(Law of Demeter):一个对象应该对其他对象保持最小的了解。在建造者模式中,客户端只需要与指挥者进行交互,而不直接与具体建造者交互。这样客户端只需关注所需产品和指挥者的调用,不需要了解具体建造者的细节。

    通过遵循以上设计原则,建造者模式可以使系统更加灵活、可扩展,并且降低了组件之间的耦合度。

其他

    建造者模式适用于创建复杂对象,但如果对象结构相对简单,可以考虑使用其他创建型模式,如工厂方法模式。
    当构建过程固定且简单时,可以考虑省略抽象建造者和指挥者的角色,直接在具体建造者中进行构建。

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

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

相关文章

MySQL——表的约束

目录 一.表的约束 二.空属性 ​编辑三.默认值 四.列描述 五.主键 1.主键 2.符合主键 六.自增长 七.唯一键 八.外键 一.表的约束 真正约束字段的是数据类型&#xff0c;但是数据类型约束很单一&#xff0c;需要有一些额外的约束&#xff0c;更好的保证数据的合法性&…

异地现场工控设备,如何实现远程配置、调试?

南京某企业专注于工业物联领域&#xff0c;在相关项目中往往会在各个点位部署基于Linux系统的中控主机&#xff0c;实现各类物联设备信息的采集、汇总。但是&#xff0c;由于各点位分散多地&#xff0c;且数量达到了上百个&#xff0c;虽然中控主机具备4G物联网接入能力&#x…

.NET微信网页开发之通过UnionID机制解决多应用用户帐号统一问题

背景 随着公司微信相关业务场景的不断拓展&#xff0c;从最初的一个微信移动应用、然后发展成微信公众号应用、然后又有了微信小程序应用。但是随着应用的拓展&#xff0c;如何保证相同用户的微信用户在不同应用中登录的同一个账号呢&#xff1f;今天的主题就来了.NET微信网页…

系列五、DQL

一、DQL 1.1、概述 DQL的英文全称为&#xff1a;Data Query Language&#xff0c;中文意思为&#xff1a;数据查询语言&#xff0c;用大白话讲就是查询数据。对于大多数系统来说&#xff0c;查询操作的频次是要远高于增删改的&#xff0c;当我们去访问企业官网、电商网站&…

持续集成交付CICD:GitLabCI操作Harbor仓库

目录 一、实验 1.GitLabCI操作Harbor仓库 二、问题 1.gitlab-runner连接docker daemon报错 一、实验 1.GitLabCI操作Harbor仓库 &#xff08;1&#xff09;修改GitLabCI共享库代码并提交到mater CI.yaml .pipelineInit:tags:- buildstage: .prevariables:GIT_CHECKOUT: …

设计原则 | 接口隔离原则

一、接口隔离原则 1、原理 客户端不应该依赖它不需要的接口&#xff0c;即一个类对另一个类的依赖应该建立在最小的接口上。如果强迫客户端依赖于那些它们不使用的接口&#xff0c;那么客户端就面临着这个未使用的接口的改变所带来的变更&#xff0c;这无意间导致了客户程序之…

【AI】如何准备mac开发vue项目的环境

为了在Mac上开发Vue项目&#xff0c;你需要准备一些工具和环境。以下是主要的步骤&#xff1a; 安装Node.js和npm&#xff1a; Vue.js是一个基于JavaScript的框架&#xff0c;因此你需要Node.js环境。访问Node.js官网下载并安装Node.js&#xff0c;这也会自动安装npm&#xff0…

深圳锐科达IP网络广播系统

深圳锐科达IP网络广播系统 网络音频广播系统是一种基于TCP/IP网络的纯数字音频广播系统。该网络音频广播系统在物理结构上与标准IP网络完全集成。它不仅真正实现了基于TCP/IP网络的数字音频的广播、直播和点播&#xff0c;而且利用TCP/IP网络的优势&#xff0c;突破了传统模拟广…

Vue 组件传参 emit

emit 属性&#xff1a;用于创建自定义事件&#xff0c;接收子组件传递过来的数据。 注意&#xff1a;如果自定义事件的名称&#xff0c;和原生事件的名称一样&#xff0c;那么只会触发自定义事件。 setup 语法糖写法请见&#xff1a;《Vue3 子传父 组件传参 defineEmits》 语…

俄罗斯军方计划用 Astra Linux 取代 Windows!

网络安全正在改变全球化的面貌&#xff0c;各国政府为了防范外国的间谍和破坏活动&#xff0c;正积极发展自己的技术。在这一趋势下&#xff0c;俄罗斯军方已经开始用 Linux 发行版 Astra Linux 替换 Windows 系统。 如何提高Linux系统安全性&#xff1f;提升Linux安全的关键策…

保障线程安全性:构建可靠的多线程应用

目录 引言 为什么线程安全性如此重要&#xff1f; 1. 竞态条件&#xff08;Race Conditions&#xff09; 2. 死锁&#xff08;Deadlocks&#xff09; 3. 数据竞争&#xff08;Data Races&#xff09; 4. 内存可见性&#xff08;Memory Visibility&#xff09; 面临的挑战…

【玩转TableAgent数据智能分析】利用TableAgent进行教育数据分析

文章目录 前言九章云极&#xff08;DataCanvas&#xff09;介绍前期准备样例数据集体验1. 样例数据集-Airbnb民宿价格&评价 体验1.1 体验一1.2 体验二 教育数据的分析&#xff08;TableAgent&ChatGLM对比&#xff09;1. 上传文件2. 数据分析与对比2.1 分析一2.1.1 Tabl…

Spring上下文之注解模块ConfigurationMethod

博主介绍:✌全网粉丝5W+,全栈开发工程师,从事多年软件开发,在大厂呆过。持有软件中级、六级等证书。可提供微服务项目搭建与毕业项目实战,博主也曾写过优秀论文,查重率极低,在这方面有丰富的经验✌ 博主作品:《Java项目案例》主要基于SpringBoot+MyBatis/MyBatis-plus+…

转载: iOS 优雅的处理网络数据

转载&#xff1a; iOS 优雅的处理网络数据 原文链接&#xff1a;https://juejin.cn/post/6952682593372340237 相信大家平时在用 App 的时候, 往往有过这样的体验&#xff0c;那就是加载网络数据等待的时间过于漫长&#xff0c;滚动浏览时伴随着卡顿&#xff0c;甚至在没有网…

十五 动手学深度学习v2计算机视觉 ——全连接神经网络FCN

文章目录 FCN FCN 全卷积网络先使用卷积神经网络抽取图像特征&#xff0c;然后通过卷积层将通道数变换为类别个数&#xff0c;最后通过转置卷积层将特征图的高和宽变换为输入图像的尺寸。 因此&#xff0c;模型输出与输入图像的高和宽相同&#xff0c;且最终输出通道包含了该空…

【MySQL学习之基础篇】函数

文章目录 前言1. 字符串函数2. 数值函数3. 日期函数4. 流程函数 前言 函数 是指一段可以直接被另一段程序调用的程序或代码。 也就意味着&#xff0c;这一段程序或代码在MySQL中已经给我们提供了&#xff0c;我们要做的就是在合适的业务场景调用对应的函数完成对应的业务需求即…

当 Sealos 遇上区块链

当 Sealos 遇上区块链 拿着区块链技术不一定是去发币&#xff0c;很多业务系统也适合用这些技术&#xff0c;比如做个统一支付系统&#xff0c;积分系统等&#xff0c;可以做为一家公司的金融基础设施&#xff0c;或支付中台。拿链的技术去做有很多好处&#xff1a; 高可用&a…

最新盲盒交友脱单系统源码

盲盒交友脱单系统源码&#xff0c;学校 爱好 城市 地区 星座等等&#xff0c;首页轮转广告&#xff0c;页面美化&#xff0c;首页两款连抽高质量底部连抽&#xff0c;后台选择开关&#xff0c;邀请奖励爱心或者&#xff0c;提现达到金额有提成奖励&#xff0c;二级分销&#xf…

【Linux】dump命令使用

dump命令 dump命令用于备份文件系统。使用dump命令可以检查ext2/3/4文件系统上的文件&#xff0c;并确定哪些文件需要备份。这些文件复制到指定的磁盘、磁带或其他存储介质保管。 语法 dump [选项] [目录|文件系统] bash: dump: 未找到命令... 安装dump yum -y install …

无参数RCE知识点

什么是无参数RCE&#xff1f; 无参rce&#xff0c;就是说在无法传入参数的情况下&#xff0c;仅仅依靠传入没有参数的函数套娃就可以达到命令执行的效果 核心代码 if(; preg_replace(/[^\W]\((?R)?\)/, , $_GET[code])) { eval($_GET[code]); } 这段代码的核心就是只…