设计模式之创建型模式---建造者模式

news2024/11/28 10:55:52

文章目录

  • 建造者模式概述
  • 经典的建造者模式
  • 建造者模式的变种
  • 总结

在这里插入图片描述

建造者模式概述

建造者模式是一种广泛使用的设计模式,在三方开源库和各种SDK中经常见到。建造者设计模式在四人帮的经典著作《设计模式:可复用面向对象软件基础》中被提及,它的定义为,将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。建造者模式目前主要有两种,一种是经典的建造者模式,另外一种是变种的建造者模式。本文就是介绍建造者模式的两种形态的Java实现

经典的建造者模式

我们学过Java都知道,当我们需要一个对象的时候,直接new一个就行了,我们new对象的时候需要给其传递一些属性,最后就能得到一个可以使用的对象啦,比如构建一个简单的车轮子对象,我们只需要new 车轮子(属性)就行,但是当我们要构造一个车的对象时就没那么简单了,因为一辆车的这个大对象又包含了车轮子,引擎,车身等小对象,这些对象需要我们单独new出了,然后再组合到一起,最终形成一辆车。这个过程如果还是使用我们程序员手动new就会很麻烦了,所以出现了经典的建造者模式。

经典的建造者模式主要有四个参与者:

  1. 复杂对象:复杂对象就是被构造的的对象,例如一辆车:Car,实现抽象建造者接口的时候会引用这个对象,然后定义其构造的过程
  2. 抽象建造者接口: 定义创建复杂对象的各个组成部件的操作,例如CarBuilder,定义构建车的各个组成部件的操作
  3. 抽象建造者接口的具体实现 :可以定义多个,这个实现是实际构建复杂对象的地方,在这个类中会提供一个方法返回构建的复杂对象
  4. 抽象建造者接口的构造者和使用者 : 在这里面调用对应的建造者接口方法构造组装得到复杂对象。

接下来,我们使用一个造车的简单例子介绍经典的建造者模式
首先我们需要确定要构造的复杂对象,这里的复杂对象就是我们要造的车,我们定义一个类表示要造的车。

public class Car {
    private String wheel;
    private String body;

    private String engine;

    private String color;

    private String name;

    public Car() {

    }

    public String getWheel() {
        return wheel;
    }

    public void setWheel(String wheel) {
        this.wheel = wheel;
    }

    public String getBody() {
        return body;
    }

    public void setBody(String body) {
        this.body = body;
    }

    public String getEngine() {
        return engine;
    }

    public void setEngine(String engine) {
        this.engine = engine;
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Car{" +
                "wheel='" + wheel + '\'' +
                ", body='" + body + '\'' +
                ", engine='" + engine + '\'' +
                ", color='" + color + '\'' +
                ", name='" + name + '\'' +
                '}';
    }
}

接下来我们需要定义一个建造者接口,定义造车的各个操作,即定义抽象建造者接口。代码如下所示:

public interface CarBuilder {
     // 构建轮子
     void buildWheel();
     // 构建车身
     void buildBody();
     // 构建引擎
     void buildEngine();
     // 构建车身颜色
     void buildColor();
     // 构建名字
     void buildName();
     // 返回造好的车
     Car getCar();
}

定义好造车的操作后,我们就可以准备开始造车了,我们接下来分别定义造一辆奔驰和一辆宝马,这两种车的大致部件其实都差不多,无非都是轮子,车身,引擎,颜色,名字等,但是不同的就是各个部件中包含的技术,所以我们需要定义不同的类表示当前造的车,代码如下所示:
首先是宝马类:

public class BMWBuilder implements CarBuilder{
    private final Car mCar = new Car();
    @Override
    public void buildWheel() {
        System.out.println("构建宝马的轮子");
        mCar.setWheel("宝马轮子");
    }

    @Override
    public void buildBody() {
        System.out.println("构建宝马的车身");
        mCar.setBody("宝马车身");
    }

    @Override
    public void buildEngine() {
        System.out.println("构建宝马的引擎");
        mCar.setEngine("宝马引擎");
    }

    @Override
    public void buildColor() {
        System.out.println("构建宝马的颜色");
        mCar.setColor("黑色");

    }

    @Override
    public void buildName() {
        System.out.println("构建宝马的名字");
        mCar.setName("宝马X5");
    }

    @Override
    public Car getCar() {
        return mCar;
    }
}

奔驰车类:

public class BenzBuilder implements CarBuilder{
    private final Car mCar = new Car();
    @Override
    public void buildWheel() {
        System.out.println("构建奔驰的轮子");
        mCar.setWheel("奔驰轮子");
    }

    @Override
    public void buildBody() {
        System.out.println("构建奔驰的车身");
        mCar.setBody("奔驰车身");
    }

    @Override
    public void buildEngine() {
        System.out.println("构建奔驰的引擎");
        mCar.setEngine("奔驰引擎");
    }

    @Override
    public void buildColor() {
        System.out.println("构建奔驰的颜色");
        mCar.setColor("粉色");

    }

    @Override
    public void buildName() {
        System.out.println("构建奔驰的名字");
        mCar.setName("奔驰");
    }

    @Override
    public Car getCar() {
        return mCar;
    }
}

构造完上面的对象后,我们需要提供一个类来将这些构造的操作步骤组合到一起最终形成我们要构建的复杂对象,代码如下所示:

public class Director {
    private CarBuilder carBuilder;
    public Director(CarBuilder carBuilder){
        this.carBuilder = carBuilder;
    }

    public Car buildCar(){
        carBuilder.buildName();
        carBuilder.buildColor();
        carBuilder.buildWheel();
        carBuilder.buildBody();
        carBuilder.buildEngine();
        return carBuilder == null ? null : carBuilder.getCar();
    }
}

有了Director类,使用方只需要new一个Director对象,传入想要构建的车的Builder,调用buildCar方法就可以得到一个车的对象了。使用方法如下所示:

public class Client {
    public static void main(String[] args) {
        BMWBuilder bmwBuilder = new BMWBuilder();
        Director bmwDirector = new Director(bmwBuilder);
        Car bmwCar = bmwDirector.buildCar();
        System.out.println("构建了一辆宝马: " + bmwCar.toString());

        BenzBuilder benzBuilder = new BenzBuilder();
        Director benzDirector = new Director(benzBuilder);
        Car benzCar = benzDirector.buildCar();
        System.out.println("构建了一辆奔驰: " + benzCar.toString());
        }
}

运行结果:
在这里插入图片描述

建造者模式的变种

建造者模式的变种是目前用得比较多的,各大SDK初始化的时候基本都会使用建造者模式。经典的建造者模式重点在于抽象出对象创建的步骤,并通过调用不同的具体实现从而得到不同的结果。而变种的建造者模式目的在于减少对象在创建过程中引入的多个重载构造函数,可选参数以及Set方法的过度使用导致的不必要复杂性,变种的建造者模式更加适用于参数多的对象,并且这些参数中有部分参数是可选的,有部分参数是必传的。下面我们就以构建一个人(Person)的例子介绍变种的建造者模式。

public class Person {
    private final String mName; // 姓名,必填
    private final String mGender; // 性别,必填

    private final int mAge; // 年龄,可选

    private final String mPhoneNo; // 电话,可选

    private final String mWeight;// 体重,,可选


    public Person(String mName,
                  String mGender,
                  int mAge,
                  String mPhoneNo,
                  String mWeight) {
        this.mName = mName;
        this.mGender = mGender;
        this.mAge = mAge;
        this.mPhoneNo = mPhoneNo;
        this.mWeight = mWeight;
    }

    public Person(String mName,
                  String mGender,
                  int mAge,
                  String mPhoneNo) {
        this(mName, mGender, mAge, mPhoneNo, "");
    }

    public Person(String mName,
                  String mGender,
                  int mAge) {
        this(mName, mGender, mAge, "", "");
    }
}

在上面的类中,我们的姓名和性别是必选项,其他是可选项,为了能够实现必选和可选的功能,我们需要在类中定义多个重载构造函数,来满足我们的需求,或者是给提供set和get方法,让必选项的参数通过构造函数传递,但是参数很多的时候就会很复杂了,传递参数的时候特别头痛。所以出现了变种的建造者模式。代码如下:

public class PersonB {
    private final String mName; // 姓名
    private final String mGender; // 性别

    private final int mAge; // 年龄

    private final String mPhoneNo; // 电话

    private final String mWeight;// 体重

    private PersonB(PersonBuilder builder){
        mName = builder.name;
        mGender = builder.gender;
        mAge = builder.age;
        mPhoneNo = builder.phoneNo;
        mWeight = builder.weight;
    }

    public String getName() {
        return mName;
    }

    public String getGender() {
        return mGender;
    }

    public int getAge() {
        return mAge;
    }

    public String getPhoneNo() {
        return mPhoneNo;
    }

    public String getWeight() {
        return mWeight;
    }

    @Override
    public String toString() {
        return "PersonB{" +
                "mName='" + mName + '\'' +
                ", mGender='" + mGender + '\'' +
                ", mAge=" + mAge +
                ", mPhoneNo='" + mPhoneNo + '\'' +
                ", mWeight='" + mWeight + '\'' +
                '}';
    }

    public static class PersonBuilder {
        private final String name; // 姓名
        private final String gender; // 性别

        private int age; // 年龄

        private  String phoneNo; // 电话

        private String weight;// 体重

        public PersonBuilder(String name,String gender){
            this.name = name;
            this.gender = gender;
        }

        public PersonBuilder age(int age){
            this.age = age;
            return this;
        }

        public PersonBuilder phone(String phoneNo){
            this.phoneNo = phoneNo;
            return this;
        }

        public PersonBuilder weight(String weight){
            this.weight = weight;
            return this;
        }

        public PersonB build(){
            return new PersonB(this);
        }
    }
}

观察上面的类我们可以发现,首先我们需要构造的对象PersonB的构造函数是私有的,意味着调用者无法直接通过实例化获取PersonB 的对象,其次PersonB类里面的属性是final的并且在构造函数中设置,对外只提供get方法,即调用者只能用,不能改。最后通过PersonBuilder的构造函数接收必选参数,其他的属性可以通过PersonBuilder提供的方法设置,其中每个方法都会返回当前的PersonBuilder对象,这样可以让我们设置参数的时候使用链式调用,如下所示:

  public PersonBuilder age(int age){
            this.age = age;
            return this;
        }

最后,我们可以看下如何使用变种的建造者模式:

public class Client {
    public static void main(String[] args) {
        // 构建一个Person
       PersonB personB =  new PersonB.PersonBuilder("职场007","male")
                .age(28)
                .phone("1234567890")
                .weight("83kg")
                .build();

        System.out.println("构建者模式构建出的对象: " + personB);
    }
}

运行结果:
在这里插入图片描述

总结

以上就是建造者模式的内容,本文分别介绍了两种形式的建造者模式以及Java的实现,其中用得最多的就是变种的建造者模式,建造者模式其实有个缺点就是需要编写很多的样板代码,但是我认为尽管这样还是不影响建造者模式的优雅,并且在Android Studio也有自动化生成变种建造者模式的插件,但是我不建议使用,除非读者已经对建造者模式很了解了。不然还是得仔细的看下建造者模式的设计思想,这样在看优秀库的源码时才会感觉到事半功倍。

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

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

相关文章

赛氪网|2024中国翻译协会年会“AI科技时代竞赛与就业”分论坛

在2024年中国翻译协会年会期间,赛氪网与中西部翻译协会共同体多边合作平台共同承办,于3月30日下午在长沙成功举办了“AI科技时代竞赛与就业分论坛”。该论坛汇聚了众多翻译界、科技界和教育界的专家学者,共同探讨科技、实践、就业与竞赛人才培…

五、Redis 集群搭建

目录 一、redis集群搭建(3台机器、6个节点) 1、在安装目录下创建7001、7002文件夹,把之前的redis.conf配置文件复制到7001文件夹中,进行编辑 2、传到其他服务器的文件要记得修改端口和集群节点信息和pidfile,不然redis 起不来 …

【MySQL】C# 连接MySQL

C# 连接MySQL 1. 添加MySQL引用 安装完MySQL之后,在安装的默认目录 C:\Program Files (x86)\MySQL\Connector NET 8.0 中查找MySQLData.dll文件。 在Visual Studio 中为项目中添加引用。 2. 引入命名空间 using MySql.Data.MySqlClient;3. 构建连接 private …

【项目】棋海争锋

🎥 个人主页:Dikz12📕格言:吾愚多不敏,而愿加学欢迎大家👍点赞✍评论⭐收藏 目录 项目介绍 WebSocket介绍 使用 项目创建 数据库设计 用户模块 登录接口 注册接口 获取用户信息接口 匹配模块 …

Java每日一题(三道同一类型的题)

前言 本文一共有三道题:1.两数之和 2.三数之和 3. 四数之和 为什么把这三道题放一起呢,因为三数之和是可以根据两数之和进行推导,四数之和可以根据三数之和进行推导。 两数之和 思路分析: 我的思路: 1.排序 2.使用左右指针 3.处理细节问题 先让数组…

【C++成长记】C++入门 |函数重载、引用、内联函数

🐌博主主页:🐌​倔强的大蜗牛🐌​ 📚专栏分类:C❤️感谢大家点赞👍收藏⭐评论✍️ 目录 一、函数重载 1、函数重载概念 二、引用 1、引用概念 2、引用特性 3、常引用 4、使用场景 5、…

【C++杂货铺】详解 stack 和 queue

🌈前言🌈 欢迎收看本期【C杂货铺】,本期内容将讲解CSTL中stack和queue的内容,其中包含了stack , queue,priority_queue是什么,怎么使用以及模拟实现这些容器。 此外,还将将讲解设计模…

秋叶Stable diffusion的创世工具安装-带安装包链接

来自B站up秋葉aaaki,近期发布了Stable Diffusion整合包v4.7版本,一键在本地部署Stable Diffusion!! 适用于零基础想要使用AI绘画的小伙伴~本整合包支持SDXL,预装多种必须模型。无需安装git、python、cuda等任何内容&am…

wordpress全站开发指南-面向开发者及深度用户(全中文实操)--wordpress中的著名循环

wordpress中的著名循环 首先,在深入研究任何代码之前,我们首先要确保我们有不止一篇博客文章可以工作。因此,我们要去自己的wordpress站点,从侧边栏单机Posts(文章),进行创建 在执行代码的时候会优先执行single.php如…

【算法刷题】八大排序算法总结(冒泡、选择、插入、二分插入、归并、快速、希尔、堆排序)

文章目录 八大排序算法总结1.冒泡排序2.选择排序3.插入排序4.二分插入排序5.归并排序6.快速排序7.希尔排序8.堆排序 八大排序算法总结 排序排序方法平均情况最好情况最坏情况空间稳定性1冒泡排序O(n2)O(n)O(n2)O(1)稳定2选择排序O(n2)O(n2)O(n2)O(1)不稳定3插入排序O(n2)O(n)O…

生活中的数学 --- 等额本息贷款和等额本金贷款的月供应该怎么算?

等额本息贷款和等额本金贷款的月供应该怎么算? 从一个例子开始,假设我要从银行贷款36万(即,本金),银行给出的贷款年利率是12%(月利率为年利率除以12),贷款半年(6个月),按月还款,分6期还完。 问分…

go websocket

WebSocket 是一种网络协议,建立在 HTTP 协议之上,允许双向通信。WebSocket 协议允许服务器发送数据到客户端,同时也可以让客户端向服务器发送数据。WebSocket 使用 HTTP 协议的升级请求和响应来建立连接。WebSocket 的主要优点在于它可以通过…

【Node.js】短链接

原文链接:Nodejs 第六十二章(短链接) - 掘金 (juejin.cn) 短链接是一种缩短长网址的方法,将原始的长网址转换为更短的形式。短链接的主要用途之一是在社交媒体平台进行链接分享。由于这些平台对字符数量有限制,长网址可…

Lua热更新(AssetBundle)

AssetBundle 新版本导入ab包报错,则删除其中的Tests文件夹。 给资源分组 打包设置:平台、路径、重复打包清空文件夹、复制到streaming文件夹 建议勾选 建议使用LZ4压缩方式 用来观察文件中的包大小,不常用 参数总结: 这六个只做了解,重要的是上面的

kubesphere部署(apple m1 m2 m3)

背景:使用一个命令kk(KubeKey)同时快速安装 Kubernetes 和 KubeSphere的集成环境,提高效率,减少部署时所花费的精力。这里环境为apple m2 一、KubeSphere简介 KubeSphere 是在 Kubernetes 之上构建的面向云原生应用的分布式操作系统&#x…

YOLOv7全网独家改进: 卷积魔改 | 变形条状卷积,魔改DCNv3二次创新

💡💡💡本文独家改进: 变形条状卷积,DCNv3改进版本,不降低精度的前提下相比较DCNv3大幅度运算速度 💡💡💡强烈推荐:先到先得,paper级创新,直接使用; 💡💡💡创新点:1)去掉DCNv3中的Mask;2)空间域上的双线性插值转改为轴上的线性插值; 💡💡💡…

人工智能——深度学习

4. 深度学习 4.1. 概念 深度学习是一种机器学习的分支,旨在通过构建和训练多层神经网络模型来实现数据的高级特征表达和复杂模式识别。与传统机器学习算法相比,深度学习具有以下特点: 多层表示学习:深度学习使用深层神经网络&a…

智过网:报考中级注册安全工程师需要什么条件?

随着社会的快速发展和科技的日新月异,安全生产问题越来越受到人们的关注。中级注册安全工程师作为专业安全管理人才,其职责与角色日益凸显。那么,想要报考中级注册安全工程师,需要满足哪些条件呢? 首先,报考…

lanqiao.125卡片换位(2016年蓝桥杯C/C++省赛C组)

题目&#xff1a; 语法点&#xff1a; 1. unordered_map<string,int> dist; //存储图的不同状态及不同状态对应的步数 2. unordered_map的相关操作&#xff0c;详细见C中的unordered_map用法详解-CSDN博客 dist.count(x) //来寻找x出现的次数 dist.find(x) //来…

STM32学习和实践笔记(6):自己进行时钟配置的思路

在《STM32学习和实践笔记&#xff08;4&#xff09;: 分析和理解GPIO_InitTypeDef GPIO_InitStructure (d)-CSDN博客》 中&#xff0c;我了解到&#xff0c;在程序执行我们写的main函数之前&#xff0c;实际上先执行了一个汇编语言所写的启动文件&#xff0c;以完成相应的初始…