设计模式【4】-- 建造者模式详解

开局一张图,剩下全靠写...

引言

设计模式集合:http://aphysia.cn/categories/designpattern

如果你用过 Mybatis ,相信你对以下代码的写法并不陌生,先创建一个builder对象,然后再调用.build()函数:

InputStream is = Resources.getResourceAsStream("mybatis.xml"); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is); SqlSession sqlSession = sqlSessionFactory.openSession();

上面其实就是我们这篇文章所要讲解的 建造者模式,下面让我们一起来琢磨一下它。

什么是建造者模式

建造者模式是设计模式的一种,将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。(来源于百度百科)

建造者模式,其实是创建型模式的一种,也是23种设计模式中的一种,从上面的定义来看比较模糊,但是不得不承认,当我们有能力用简洁的话去定义一个东西的时候,我们才是真的了解它了,因为这个时候我们已经知道它的界限在哪。

所谓将一个复杂对象的构建与它的表示分离,就是将对象的构建器抽象出来,构造的过程一样,但是不一样的构造器可以实现不一样的表示。

结构与例子

建造者模式主要分为以下四种角色:

  • 产品(Product):具体生产器要构造的复杂对象
  • 抽象生成器(Bulider):抽象生成器是一个接口,创建一个产品各个部件的接口方法,以及返回产品的方法
  • 具体建造者(ConcreteBuilder):按照自己的产品特性,实现抽象建造者对应的接口
  • 指挥者(Director):创建一个复杂的对象,控制具体的流程

说到这里,可能会有点懵,毕竟全都是定义,下面从实际例子来讲讲,就拿程序员最喜欢的电脑来说,假设现在要生产多种电脑,电脑有屏幕,鼠标,cpu,主板,磁盘,内存等等,我们可能立马就能写出来:

public class Computer { private String screen; private String mouse; private String cpu; private String mainBoard; private String disk; private String memory; ... public String getMouse() { return mouse; } public void setMouse(String mouse) { this.mouse = mouse; } public String getCpu() { return cpu; } public void setCpu(String cpu) { this.cpu = cpu; } ... }

上面的例子中,每一种属性都使用单独的set方法,要是生产不同的电脑的不同部件,具体的实现还不太一样,这样一个类实现起来貌似不是很优雅,比如联想电脑和华硕电脑的屏幕的构建过程不一样,而且这些部件的构建,理论上都是电脑的一部分,我们可以考虑流水线式的处理。

当然,也有另外一种实现,就是多个构造函数,不同的构造函数带有不同的参数,实现了可选的参数:

public class Computer { private String screen; private String mouse; private String cpu; private String mainBoard; private String disk; private String memory; public Computer(String screen) { this.screen = screen; } public Computer(String screen, String mouse) { this.screen = screen; this.mouse = mouse; } public Computer(String screen, String mouse, String cpu) { this.screen = screen; this.mouse = mouse; this.cpu = cpu; } ... }

上面多种参数的构造方法,理论上满足了按需构造的要求,但是还是会有不足的地方:

  • 倘若构造每一个部件的过程都比较复杂,那么构造函数看起来就比较凌乱
  • 如果有多种按需构造的要求,构造函数就太多了
  • 构造不同的电脑类型,耦合在一块,必须抽象出来

首先,我们先用流水线的方式,实现按需构造,不能重载那么多构造函数:

public class Computer { private String screen; private String mouse; private String cpu; private String mainBoard; private String disk; private String memory; public Computer setScreen(String screen) { this.screen = screen; return this; } public Computer setMouse(String mouse) { this.mouse = mouse; return this; } public Computer setCpu(String cpu) { this.cpu = cpu; return this; } public Computer setMainBoard(String mainBoard) { this.mainBoard = mainBoard; return this; } public Computer setDisk(String disk) { this.disk = disk; return this; } public Computer setMemory(String memory) { this.memory = memory; return this; } }

使用的时候,构造起来,就像是流水线一样,一步一步构造就可以:

Computer computer = new Computer() .setScreen("高清屏幕") .setMouse("罗技鼠标") .setCpu("i7处理器") .setMainBoard("联想主板") .setMemory("32G内存") .setDisk("512G磁盘");

但是以上的写法不够优雅,既然构造过程可能很复杂,为何不用一个特定的类来构造呢?这样构造的过程和主类就分离了,职责更加清晰,在这里内部类就可以了:

package designpattern.builder; import javax.swing.*; public class Computer { private String screen; private String mouse; private String cpu; private String mainBoard; private String disk; private String memory; Computer(Builder builder) { this.screen = builder.screen; this.cpu = builder.cpu; this.disk = builder.disk; this.mainBoard = builder.mainBoard; this.memory = builder.memory; this.mouse = builder.mouse; } public static class Builder { private String screen; private String mouse; private String cpu; private String mainBoard; private String disk; private String memory; public Builder setScreen(String screen) { this.screen = screen; return this; } public Builder setMouse(String mouse) { this.mouse = mouse; return this; } public Builder setCpu(String cpu) { this.cpu = cpu; return this; } public Builder setMainBoard(String mainBoard) { this.mainBoard = mainBoard; return this; } public Builder setDisk(String disk) { this.disk = disk; return this; } public Builder setMemory(String memory) { this.memory = memory; return this; } public Computer build() { return new Computer(this); } } }

使用的时候,使用builder来构建,构建完成之后,调用build的时候,再将具体的值,赋予我们需要的对象(这里是Computer):

public class Test { public static void main(String[] args) { Computer computer = new Computer.Builder() .setScreen("高清屏幕") .setMouse("罗技鼠标") .setCpu("i7处理器") .setMainBoard("联想主板") .setMemory("32G内存") .setDisk("512G磁盘") .build(); System.out.println(computer.toString()); } }

但是上面的写法,如果我们构造多种电脑,每种电脑的配置不太一样,构建的过程也不一样,那么我们就必须将构造器抽象出来,变成一个抽象类。

首先我们定义产品类Computer

public class Computer { private String screen; private String mouse; private String cpu; public void setScreen(String screen) { this.screen = screen; } public void setMouse(String mouse) { this.mouse = mouse; } public void setCpu(String cpu) { this.cpu = cpu; } @Override public String toString() { return "Computer{" + "screen='" + screen + '\'' + ", mouse='" + mouse + '\'' + ", cpu='" + cpu + '\'' + '}'; } }

定义一个抽象的构造类,用于所有的电脑类构造:

public abstract class Builder { abstract Builder buildScreen(String screen); abstract Builder buildMouse(String mouse); abstract Builder buildCpu(String cpu); abstract Computer build(); }

先构造一台联想电脑,那联想电脑必须实现自己的构造器,每一款电脑总有自己特殊的地方:

public class LenovoBuilder extends Builder { private Computer computer = new Computer(); @Override Builder buildScreen(String screen) { computer.setScreen(screen); return this; } @Override Builder buildMouse(String mouse) { computer.setMouse(mouse); return this; } @Override Builder buildCpu(String cpu) { computer.setCpu(cpu); return this; } @Override Computer build() { System.out.println("构建中..."); return computer; } }

构建器有了,还需要有个指挥者,它负责去构建我们具体的电脑:

public class Director { Builder builder = null; public Director(Builder builder){ this.builder = builder; } public void doProcess(String screen,String mouse,String cpu){ builder.buildScreen(screen) .buildMouse(mouse) .buildCpu(cpu); } }

使用的时候,我们只需要先构建builder,然后把builder传递给指挥者,他负责具体的构建,构建完之后,构建器调用一下.build()方法,就可以创建出一台电脑。

public class Test { public static void main(String[] args) { LenovoBuilder builder = new LenovoBuilder(); Director director = new Director(builder); director.doProcess("联想屏幕","游戏鼠标","高性能cpu"); Computer computer = builder.build(); System.out.println(computer); } }

打印结果:

构建中... Computer{screen='联想屏幕', mouse='游戏鼠标', cpu='高性能cpu'}

以上其实就是完整的建造者模式,但是我们平时用的,大部分都是自己直接调用构建器Builder,一路set(),最后build(),就创建出了一个对象。

使用场景

构建这模式的好处是什么?首先想到的应该是将构建的过程解耦了,构建的过程如果很复杂,单独拎出来写,清晰简洁。其次,每个部分的构建,其实都是可以独立去创建的,不需要多个构造方法,构建的工作交给了构建器,而不是对象本身。专业的人做专业的事。同样,构建者模式也比较适用于不同的构造方法或者构造顺序,可能会产生不同的构造结果的场景。

但是缺点还是有的,需要维护多出来的Builder对象,如果多种产品之间的共性不多,那么抽象的构建器将会失去它该有的作用。如果产品类型很多,那么定义太多的构建类来实现这种变化,代码也会变得比较复杂。

最近在公司用GRPC,里面的对象几乎都是基于构建者模式,链式的构建确实写着很舒服,也比较优雅,代码是写给人看的,我们所做的一切设计模式,都是为了拓展,解耦,以及避免代码只能口口相传。

【作者简介】
秦怀,公众号【秦怀杂货店】作者,技术之路不在一时,山高水长,纵使缓慢,驰而不息。个人写作方向:Java源码解析JDBCMybatisSpringredis分布式剑指OfferLeetCode等,认真写好每一篇文章,不喜欢标题党,不喜欢花里胡哨,大多写系列文章,不能保证我写的都完全正确,但是我保证所写的均经过实践或者查找资料。遗漏或者错误之处,还望指正。

剑指Offer全部题解PDF

2020年我写了什么?

开源编程笔记


__EOF__

本文作者秦怀杂货店
本文链接https://www.cnblogs.com/Damaer/p/15631702.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   第十六封  阅读(371)  评论(0编辑  收藏  举报
编辑推荐:
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· .NET Core 托管堆内存泄露/CPU异常的常见思路
· PostgreSQL 和 SQL Server 在统计信息维护中的关键差异
· C++代码改造为UTF-8编码问题的总结
· DeepSeek 解答了困扰我五年的技术问题
阅读排行:
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 清华大学推出第四讲使用 DeepSeek + DeepResearch 让科研像聊天一样简单!
· 实操Deepseek接入个人知识库
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库
点击右上角即可分享
微信分享提示