当前位置: 首页 > 后端技术 > Java

设计模式【4】--Builder模式详解

时间:2023-04-01 20:52:02 Java

从一张图说起,剩下的靠写...Introduction设计模式合集:http://aphysia.cn/categories/...如果你用过Mybatis,相信你对下面的代码并不陌生,先创建一个builder对象,然后调用.build()函数:InputStreamis=Resources.getResourceAsStream("mybatis.xml");SqlSessionFactorysqlSessionFactory=newSqlSessionFactoryBuilder().build(is);SqlSessionsqlSession=sqlSessionFactory.openSession();上面其实就是我们本文要讲解的建造者模式,大家一起来琢磨。什么是建造者模式?建造者模式是一种将复杂对象的构造与其表示分离的设计模式,使得相同的构造过程可以创建不同的表示。(来自百度百科)建造者模式其实是创造模式的一种,也是23种设计模式之一。从上面的定义来看是模糊的,但是我们不得不承认,当我们有能力用简洁的词语来定义一个东西的时候,我们才真正理解它,因为这个时候我们已经知道它的边界在哪里。所谓将复杂对象的构造与其表示分离,就是将对象的构造函数抽象出来。构造过程是一样的,但是不同的构造器可以实现不同的表示。结构与实例建造者模式主要分为以下四个角色:产品(Product):具体生产者要构造的复杂对象抽象建造者(Bulider):抽象建造者是一个接口,为每个组件创建一个接口方法产品,以及退回产品的方法。ConcreteBuilder:根据自己产品的特点,实现abstractbuilder对应的接口。Director(导演):创建一个复杂的对象并控制具体的过程说到这里,可能有点让人费解。毕竟都是定义。让我们从一个实际的例子来谈谈。我们以程序员最喜欢的电脑为例。假设我们要生产各种计算机。电脑有屏幕、鼠标、cpu、主板、磁盘、内存等,我们可能马上就可以写成:publicclassComputer{privateStringscreen;私人字符串鼠标;私有字符串cpu;私人字符串主板;私有字符串磁盘;私有字符串内存;...publicStringgetMouse(){返回鼠标;}publicvoidsetMouse(Stringmouse){this.mouse=mouse;}publicStringgetCpu(){返回cpu;}publicvoidsetCpu(Stringcpu){this.cpu=cpu;}...}在上面的例子中,每个属性都使用了单独的set方法。如果生产不同的计算机的不同部分,具体实现是不一样的。构件的构造理论上是计算机的一部分,我们可以考虑流水线处理。当然还有另一种实现方式,就是多个构造函数,不同的构造函数有不同的参数,实现可选参数:publicclassComputer{privateStringscreen;私人字符串鼠标;私有字符串cpu;私有字符串主板;私有字符串磁盘;私有字符串内存;公共计算机(字符串屏幕){this.screen=screen;}publicComputer(Stringscreen,Stringmouse){this.screen=screen;this.mouse=鼠标;}publicComputer(Stringscreen,Stringmouse,Stringcpu){this.screen=screen;this.mouse=鼠标;这个.cpu=cpu;}...}以上各种参数的构造方法理论上可以满足按需构造的要求,但是还是有不足:如果每个组件的构造过程比较复杂,那么构造器看起来会很乱。如果按需构建有多个需求,那么构造器就会过多。构建不同的计算机类型,耦合在一块,它必须被抽象。首先,我们使用流水线的方式实现按需构造,不能重载那么多构造函数:publicclassComputer{privateStringscreen;私人字符串鼠标;私有字符串cpu;私人字符串主板;私有字符串磁盘;私有字符串内存;publicComputersetScreen(Stringscreen){this.screen=screen;归还这个;}publicComputersetMouse(Stringmouse){this.mouse=mouse;归还这个;}publicComputersetCpu(Stringcpu){this.cpu=cpu;归还这个;}publicComputersetMainBoard(StringmainBoard){这个。主板=主板;归还这个;}publicComputersetDisk(Stringdisk){this.disk=disk;归还这个;}publicComputersetMemory(Stringmemory){this.memory=memory;看起来像一条流水线,一步步搭建起来:"联想主板").setMemory("32G内存").setDisk("512G磁盘");但是上面的写法不够优雅,既然构造过程可能会很复杂,那为什么不用具体的类来构造呢?这样构建过程就和主类分离了,职责更加明确。在这里,内部类很好:packagedesignpattern.builder;importjavax.swing.*;publicclassComputer{privateStringscreen;私人字符串鼠标;私有字符串cpu;私人字符串主板;私有字符串磁盘;私有字符串内存;计算机(Builderbuilder){this.screen=builder.screen;this.cpu=builder.cpu;this.disk=builder.disk;this.mainBoard=builder.mainBoard;this.memory=builder.memory;this.mouse=builder.mouse;}publicstaticclassBuilder{privateStringscreen;私人字符串鼠标;私有字符串cpu;私人字符串主板;私有字符串磁盘;私有字符串内存;publicBuildersetScreen(Stringscreen){this.screen=screen;归还这个;}publicBuildersetMouse(Stringmouse){this.mouse=mouse;归还这个;}publicBuildersetCpu(Stringcpu){this.cpu=cpu;归还这个;}publicBuildersetMainBoard(StringmainBoard){this.mainBoard=mainBoard;归还这个;}publicBuildersetDisk(Stringdisk){this.disk=disk;归还这个;}publicBuildersetMemory(Stringmemory){this.memory=memory;归还这个;}publicComputerbuild(){returnnewComputer(this);}}}使用的时候,使用builder来构建。构建完成后,在调用构建时,将具体的值赋给我们需要的对象(这里是Computer):setScreen("高清屏").setMouse("罗技鼠标").setCpu("i7处理器").setMainBoard("联想主板").setMemory("32G内存").setDisk("512G磁盘").build();System.out.println(computer.toString());}}但是上面的写法,如果我们要构造各种各样的计算机,每台计算机的配置都不一样,构造过程也不一样,那我们就必须把构造函数抽象出来,变成一个抽象类首先我们定义产品类Computer:publicclassComputer{privateStringscreen;私人字符串鼠标;私有字符串cpu;publicvoidsetScreen(Stringscreen){this.screen=screen;}publicvoidsetMouse(Stringmouse){this.mouse=mouse;}publicvoidsetCpu(Stringcpu){this.cpu=cpu;}@OverridepublicStringtoString(){return"Computer{"+"screen='"+screen+'\''+",mouse='"+mouse+'\''+",cpu='"+cpu+'\''+'}';}}为所有计算机构造定义一个抽象构造类:publicabstractclassBuilder{abstractBuilderbuildScreen(Stringscreen);抽象生成器buildMouse(String鼠标);抽象生成器buildCpu(Stringcpu);abstractComputerbuild();}首先构建一台联想电脑,然后联想电脑必须实现自己的构造函数,每台电脑总是有自己的特殊功能:publicclassLenovoBuilderextendsBuilder{privateComputercomputer=newComputer();@OverrideBuilderbuildScreen(字符串屏幕){computer.setScreen(屏幕);归还这个;}@OverrideBuilderbuildMouse(Stringmouse){computer.setMouse(mouse);归还这个;}@OverrideBuilderbuildCpu(Stringcpu){computer.setCpu(cpu);归还这个;}@OverrideComputerbuild(){System.out.println("Building...");归还电脑;}}有了建造者,我们需要一个负责建造我们特定计算机的指挥官:publicclassDirector{Builderbuilder=null;publicDirector(Builderbuilder){this.builder=builder;}publicvoiddoProcess(Stringscreen,Stringmouse,Stringcpu){builder.buildScreen(screen).buildMouse(mouse).buildCpu(cpu);}}在使用的??时候,我们只需要先构建builder,然后将builder传递给commander,由commander负责具体的构建。构建完成后,构建器调用.build()方法创建计算机公共类测试{publicstaticvoidmain(String[]args){LenovoBuilderbuilder=newLenovoBuilder();Directordirector=newDirector(builder);director.doProcess("联想屏","游戏鼠标","高性能cpu");计算机computer=builder.build();System.out.println(计算机);}}打印结果:building...Computer{screen='联想屏',mouse='游戏鼠标',cpu='高性能cpu'}以上其实就是一个完整的builder模式,不过大部分是我们平时用的就是直接调用构建器Builder,一路set(),最后build()创建对象。使用场景构建这种模式有什么好处?首先想到的应该是对构建过程进行解耦。如果施工过程复杂,单独写出来,简洁明了。其次,每个部分的构建实际上都可以独立创建,不需要多种构建方法,构建的工作交给构建者,而不是对象本身。专业的人做专业的事。同样,建造者模式更适用于不同的构造方法或构造顺序可能会产生不同构造结果的场景。但是,仍然存在一些缺点。有必要维护额外的Builder对象。如果各种产品之间的共性不多,抽象构建器就会失去应有的作用。如果产品的种类很多,那么实现这个变化的构造类定义太多,代码会变得更复杂。最近在公司用GRPC。里面几乎所有的对象都是基于建造者模式的。链条结构确实非常舒适和优雅。代码是写给人们阅读的。我们所做的所有设计模式都是为了缩放、解耦和避免代码口耳相传。【作者简介】:公众号【秦淮杂货铺】作者秦淮,技术之路不是一时的,山高水长,纵使慢也不会停。个人写作方向:Java源码分析、JDBC、Mybatis、Spring、redis、分布式、简知Offer、LeetCode等,认真写好每篇文章,不喜欢头条党,不喜欢花里胡哨,多写系列的文章,不保证我写的是完全正确的,但我保证我写的是经过实践或搜索过的资料。如有遗漏或错误,敬请指正。剑指提供所有问答PDF2020年我写了什么?开源编程笔记