[设计模式]彻底搞懂建造者模式(生成器模式)——透彻理解(代码片段)

削尖的螺丝刀 削尖的螺丝刀     2023-01-12     556

关键词:

 
 

   相信很多人搜索 “建造者模式” 的时候和我一样,首先映入眼帘的就是下面这张UML图(除了属性的区别,结构完全一样):
 
[ 画外音: “不了解UML类图的话,欢迎移步 —— UML类图介绍 ——程序员(灵魂画手)必备画图技能之一”  😏​ ]

 
 
 
 


 

[大家好,我是建造者模式图]

 
 
 
 
 
 

    看完这张图后再来一个对Builder的实现类,然后再Director完成构造,再到末尾附上几句雷同的解释,让你有种似懂非懂,飘飘欲仙的感觉(此处可省略1w字)~

 
 

 
 

    好了,如果觉得没飘够的同学可以继续飘一会,反之请忘掉上面的一切吧(包括那张UML图),跟随笔者迅雷不及掩耳盗铃般的脚步,深入探索如何真正成为一名合格的 “建造者” ! 
 
 
 
 
 


 
 
 
 
 

    关于建造者模式(当然也有人叫它构建者模式创建者模式Builder模式生成器模式…Anyway)你要记住关键的也是第一的要素就是(个人提炼,如有雷同纯属巧合):
 

  • 把一个对象原本看似复杂繁琐的 [构造过程] 变得更加简单、清晰、明了
     

    当然,设计模式的好处肯定不止一两个,建造者也不会例外。但是笔者认为建造者最重要的体现就是我上面说的这句话,你也可以理解为让构造的语义更加简单明了。那么其他就都是根据不同需求场景下,体现出来边边角角的好处了。前提,首先你记住上面这句话,然后思考这个问题 —— 假如让你设计一个完美的机器人"类",包含head、body、hands、legs、size洗个属性,你会怎么设计 ?(请面壁十秒后再来看下文)

 
 
 
 
 


 
 
 
 
 

    相信你已经有了自己的答案,没错,一个可供人使用的完美的机器人"类"应该是可以配置的,是不是应该有默认值,和非默认值的必填选项? (可以类比第三方工具的配置类),具体代码如下

 

public class Robot 
    private static final int DEFAULT_HANDS = 2;
    private static final int DEFAULT_LEGS = 2;
    private static final int DEFAULT_SIZE = 10;

    // 假设大脑必须根据个人要求设定(必填)
    private String head;
    // 假设身体必须根据个人要求设定(必填)
    private String body;
    private int hands = DEFAULT_HANDS;
    private int legs = DEFAULT_LEGS;
    private int size = DEFAULT_SIZE;

    public Robot(String head, String body, Integer hands, Integer legs, Integer size) 
        if (StringUtils.isBlank(head)) 
            throw new IllegalArgumentException("WTF , don't you have head ?");
        
        this.head = head;

        if (StringUtils.isBlank(body)) 
            throw new IllegalArgumentException("WTF , don't you have body ?");
        
        this.body = body;

        if(null !=hands) 
            if (hands <= 0) 
                throw new IllegalArgumentException("Come on , you need hands to work !");
            
            this.head = head;
        


        if(null !=legs) 
            if (legs <= 0) 
                throw new IllegalArgumentException("Come on , you need legs to walk !");
            
            this.legs = legs;
        

        if(null !=size) 
            if (size <= 0) 
                throw new IllegalArgumentException("Come on , you need to be exist !");
            
            this.size = size;
        

    


    // getter方法省略...

 
 
 

好上面这个Robot让你new出来的画风是不是这样的?

Robot robot = new Robot("bigHead","bigBody",null,null,null);

 
 
 

    这还只有6个构造参数还好,那么如果有16个、60个、100个、10086个呢?代码可读性是否就降低了?而且可能一不小心就弄错了构造参数的顺序?就算你乍一眼能看出来这些参数的意思,你让熟悉代码的新员工咋办?玩锤子?作为一名负责的程序猿是否得对新猿友好?

 
 

 
 

   品了半天,你可能想到了全部改为Setter,嗯….兄嘚果然天资聪颖,是稍微好看点了(setter细节就不改了,大体构造就变会成如下)

Robot robot = new Robot("bigHead","bigBody");
robot.setHands();
robot.setLegs();
robot.setSize();
...

 
 
 
 

但是稍微加大点难度,上面的设计思路就不满足了:

  • 参数冗长: 必填属性多,任然会导致构造参数变长
  • 安全问题: 假设我们要求一个robot在构造完后就不能再对其做改动了(否则视为不安全的异常操作)
  • 依赖问题: 假设我们要求hands被改变了那legs也必须被改变,或者hands/legs必须小于size等特殊校验需求,那代码又会近一步变的凌乱

 
 
 

兄嘚我就问你怎么办???

 
 

 

 
 

这时候,建造者模式的各个闪光点就充分体现出来了:

 

  1. 所有Setter方法放入一个Builder内部类中(robot没有暴露Setter方法,外部自然无法改变
  2. 把robot的构造方法改为私有的(这样robot就 只有Bulder能够构造 了)
  3. Bulder中自然会调用robot的构造方法,把所有 特殊且必要的校验 在Robot构造方法被调用之前 统一完成

 

对应的改造代码如下:

 

public class Robot 
    // 默认值
    private static final int DEFAULT_HANDS = 2;
    private static final int DEFAULT_LEGS = 2;
    private static final int DEFAULT_SIZE = 10;

    // Robot的属性
    private String head;
    private String body;
    private int hands = DEFAULT_HANDS;
    private int legs = DEFAULT_LEGS;
    private int size = DEFAULT_SIZE;

    // 注意:Robot的构造是私有的,且是通过传入Builder来构造
    // 你无法在外面对已构造好的Robot做出任何改变
    private Robot(Builder builder) 
        this.head = builder.head;
        this.body = builder.body;
        this.hands = builder.hands;
        this.legs = builder.legs;
        this.size = builder.size;
    
    // 省略Getter方法...
    
 public static class Builder 
    private static final int DEFAULT_HANDS = 2;
    private static final int DEFAULT_LEGS = 2;
    private static final int DEFAULT_SIZE = 10;
    private String head;
    private String body;
    private int hands = DEFAULT_HANDS;
    private int legs = DEFAULT_LEGS;
    private int size = DEFAULT_SIZE;
    // Robot在此构建,在构建之前完成所有必须的 "特殊校验"
    public Robot build() 
        if (StringUtils.isBlank(head)) 
            throw new IllegalArgumentException("WTF , don't you have head ?");
        
        if (StringUtils.isBlank(body)) 
            throw new IllegalArgumentException("WTF , don't you have body ?");
        
        if (hands != DEFAULT_HANDS && legs == DEFAULT_LEGS) 
            throw new IllegalArgumentException("Bro, you should update your legs");
        
        if (hands == DEFAULT_HANDS && legs != DEFAULT_LEGS) 
            throw new IllegalArgumentException("Bro, you should update your hands");
        
        return new Robot(this);
    

    // 可以看到每个setter方法返回的都是Builder本身
    public Builder setHead(String head) 
        if (StringUtils.isBlank(head)) 
            throw new IllegalArgumentException("WTF , don't you have head ?");
        
        this.head = head;
        return this;
    
    public Builder setBody(String body) 
        if (StringUtils.isBlank(body)) 
            throw new IllegalArgumentException("WTF , don't you have body ?");
        
        this.body = body;
        return this;
    
    public Builder setHands(Integer hands) 
        if (null != hands) 
            if (hands <= 0) 
                throw new IllegalArgumentException("Come on , you need hands to work !");
            
            this.head = head;
        
        return this;
    
    public Builder setLegs(Integer legs) 
        if (null != legs) 
            if (legs <= 0) 
                throw new IllegalArgumentException("Come on , you need legs to walk !");
            
            this.legs = legs;
        
        return this;
    
    public Builder setSize(Integer size) 
        if (null != size) 
            if (size <= 0) 
                throw new IllegalArgumentException("Come on , you need to be exist !");
            
            this.size = size;
        
        return this;
    
  

 
 
 
 

   这样一来,一个构造条理清晰,且一旦构造完成,则外部无法改变的超级Robot就此诞生 !!

 
 
 

 
 
 
 

  Robot robot = new Robot.Builder()
                .setHead("superHead")
                .setBody("superBody")
                .setHands(999)
                .setLegs(999)
                .setSize(10086)
                .build();

 
 
 
 

    至此,一个《建造者模式》被完美演绎完毕!其实除了建造者除了上述的边角闪光点,还有另一个闪光点就是能避免对象的无效存在,什么是无效存在呢?比如一个“矩形”对象,你是不是必须设置完长、宽才能算有效?不然你单独设置一个长你就当矩形用?那建造者模式就完美解决了这个问题,因为被此模式建造出来的对象一定是完整可用的。

 
 
 
 

【 小结 】

想必你已经摸清建造者模式的大致轮廓了,接下来依然离不开谈到建造者模式必然和工厂模式对比,那个老生常谈的问题了!这里必须套娃引用某位网友对此的看法

—— “ 顾客走进一家餐馆点餐,我们利用工厂模式,根据用户不同的选择,来制作不同的食物,比如披萨、汉堡、沙拉。对于披萨来说,用户又有各种配料可以定制,比如奶酪、西红柿、起司,我们通过建造者模式根据用户选择的不同配料来制作披萨。 ”

 
 
 
上面的故事应该很容易理解 :

  • 其实不管是 工厂模式 还是 抽象工厂模式 ,本质上都是站在类的层面,对各种类进行创建。
  • 建造者模式, 关注的是 类本身 的创建,所以区别还是很明显的(其实和模板模式也有点像,都是设计好一个清晰的车骨架,然后让你在骨架里造轮子和其他部件。只不过模板模式是 行为型 模式,属于方法的运作,而建造者是 创建型 模式,属于对象的构建)。
     
     
     
     
     

上面大体已经应该把建造者模式说清楚了,年轻人,如果你觉得已经可以了,那就放心大胆的出去闯吧!建造你自己的世界!!

 
 
 

 
 
 

但是你如果意犹未尽,执意要做最强的建造者,那我这里还有一本《RabbitMQ的建造者模式》演绎法,你品品和我上面的例子有什么不同?请带着最开头我说的那句话 —— 让构造的语义更加简单明了 来欣赏下文:

 
 
 

 
 
 

如果你使用过或者见过RabbitMQ的配置类,那么你应该可以看到大致如下的代码:

return BindingBuilder.bind(queue).to(exchange);
 return BindingBuilder.bind(queue).to(exchange).with(routingKey);

 
 

兄弟,惊讶吗?这语义是有多明显???稍微有点小学英语文化水平的也懂这个to、with的意思吧?这还用去看底层源码?隔壁新猿小王是不是直接就可以上手?

 
 

 
 
 

如果你看过BindingBuilder内的代码逻辑,就会发现,其实这两段绑定代码也可以写成这样,依然有效。

return new Binding(queue.getName(),Binding.DestinationType.QUEUE,exchange.getName(),"",null);
return new Binding(queue.getName(),Binding.DestinationType.QUEUE,exchange.getName(),routingKey,null);

 
 
 

但是,这样又回到了最开始的问题,你让隔壁新猿小王玩锤子??玩锤子呢?!

总之,四不像和独角兽,你选一个,还是最初我说道的那句话 —— 让构造的语义更加简单明了,你品你细品。

最后,如果你点进BindingBuilder的代码看,会发现他每层的setter返回的并不是BindingBuilder本身,而是一个叫…哎光说不练假把式,talk is cheap 为了不被怼有烂尾的嫌疑,我还是 show you the code 把

首先看 BindingBuilder.bind() 方法

public static DestinationConfigurer bind(Queue queue) 
		return new DestinationConfigurer(queue.getName(), DestinationType.QUEUE);
	

你可以看到他们无一例外都返回了一个DestinationConfigurer

 
 
 

接下来看 BindingBuilder.bind().to() 方法

public static final class DestinationConfigurer 

		protected final String name; // NOSONAR

		protected final DestinationType type; // NOSONAR

		DestinationConfigurer(String name, DestinationType type) 
			this.name = name;
			this.type = type;
		

        // 看到没,这下面足足有5个to方法来应对各种交换机
		public Binding to(FanoutExchange exchange) 
			return new Binding(this.name, this.type, exchange.getName(), "", 
                               new HashMap<String, Object>());
		

		public HeadersExchangeMapConfigurer to(HeadersExchange exchange) 
			return new HeadersExchangeMapConfigurer(this, exchange);
		

    	// 这个是我下面要举例的方法哦
		public DirectExchangeRoutingKeyConfigurer to(DirectExchange exchange) 
			return new DirectExchangeRoutingKeyConfigurer(this, exchange);
		

		public TopicExchangeRoutingKeyConfigurer to(TopicExchange exchange) 
			return new TopicExchangeRoutingKeyConfigurer(this, exchange);
		

		public GenericExchangeRoutingKeyConfigurer to(Exchange exchange) 
			return new GenericExchangeRoutingKeyConfigurer(this, exchange);
		
	

 
 
 

如果你了解RabbitMQ的话,你应该知道Fanout模式是不需要指定routingKey的,所以你可以看到Fanout交换机直接执行最终的建造方法 —— 也就是 Binding() 方法

但是你也能看到在之后需要with方法的to都返回了另一个对象,其实这对象都是继承自AbstractRoutingKeyConfigurer的子类,这里就拿DirectExchangeRoutingKeyConfigurer举例把

 
 
 

  • 首先看 AbstractRoutingKeyConfigurer
	private abstract static class AbstractRoutingKeyConfigurer 

		protected final DestinationConfigurer destination; // NOSONAR

		protected final String exchange; // NOSONAR

		AbstractRoutingKeyConfigurer(DestinationConfigurer destination, String exchange) 
			this.destination = destination;
			this.exchange = exchange;
		
	

 
 
 

  • 然后看DirectExchangeRoutingKeyConfigurer
public static final class DirectExchangeRoutingKeyConfigurer extends AbstractRoutingKeyConfigurer 

		DirectExchangeRoutingKeyConfigurer(DestinationConfigurer destination, 
                                           DirectExchange exchange) 
            // 这里调用父类构造就是赋值而已
			super(destination, exchange.getName());
		

   		// 可以看到下面有三个with方法来完成对routingKey的绑定,以及最终的建造Binding()方法
		public Binding with(String routingKey) 
			return new Binding(destination.name, destination.type, exchange, routingKey,
					Collections.<String, Object>emptyMap());
		

		public Binding with(Enum<?> routingKeyEnum) 
			return new Binding(destination.name, destination.type, exchange, r
                               outingKeyEnum.toString(),
					Collections.<String, Object>emptyMap());
		

		public Binding withQueueName() 
			return new Binding(destination.name, destination.type, exchange, 
                               destination.name,
					Collections.<String, Object>emptyMap());
		
	

 
 
 

至此你已经看到了整个绑定关系的构建流程,无一例外最终都会指向那个对象的构造方法(这一点和我上面的例子一样,也是建造者模式必然的宿命),下面我们来看看这个最终的构造方法 —— Binding()

public class Binding extends AbstractDeclarable 

	/**
	 * The binding destination.
	 */
	public enum DestinationType 

		/**
		 * Queue destination.
		 */
		QUEUE,

		/**
		 * Exchange destination.
		 */
		EXCHANGE;
	

	private final String destination;

	private final String exchange;

	private final String routingKey;

	private final DestinationType destinationType;

    // 构造方法在这里
	public Binding(String destination, DestinationType destinationType, String exchange, 
                   String routingKey, @Nullable Map<String, Object> arguments) 
        
		super(arguments);
		this.destination = destination;
		this.destinationType = destinationType;
		this.exchange = exchange;

设计模式——建造者模式

分类设计模式总体划分为三大类:创建型模式:工厂模式,抽象工厂模式,单例模式,建造者模式,原型模式模式,共5种。结构型模式:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式,共7中... 查看详情

建造者模式

建造者模式的定义建造者模式(BuilderPattern)也叫做生成器模式,其定义如下:Separatetheconstructionofacomplexobjectfromitsrepresentationsothatthesameconstructionprocesscancreatedifferentrepresentations.(将一个复杂对象的构建与它的表示分离,使得同样... 查看详情

设计模式之建造者模式

1定义建造者模式(BuilderPatten):将一个复杂对象的构建与他的表示分离,使得同样的构建过程可以创建不同的表示。建造者模式用于一步一步创建一个复杂的对象,他允许用户只通过指定复杂对象的类型和内容进行构建,用户... 查看详情

设计模式之建造者模式(创建型)(代码片段)

模式定义建造者模式属于23种设计模式中的创建型模式,可以理解为创建对象的一种很好的方法。所谓建造者模式就是将组件和组件的组件过程分开,然后一步一步建造一个复杂的对象。所以建造者模式又叫生成器模式。建造者... 查看详情

设计模式——建造者模式(代码片段)

设计模式(九)——建造者模式新专题:设计模式,我会在博客(http://www.hollischuang.com)及微信公众号(hollischuang)同步更新,欢迎共同学习。本文主要介绍创建型模式的最后一种————建造者模式。概念建造者模式(英:BuilderPatt... 查看详情

大话设计模式-建造者模式(代码片段)

建造者模式建造者模式又称生成器模式,将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。建造者模式主要用于创建一些复杂的对象,这些对象内部构构建间的构造顺序通常是稳定的,但对象内... 查看详情

建造者模式的理解

...过程可以创建不同的表示的意图时,我们需要应用于一个设计模式,“建造者(Builder)模式”,又叫生成器模式。建造者模式可以将一个产品的内部表象与产品的生成过程分割开来,从而可以使一个建造过程生成具有不同的内... 查看详情

建造者模式

设计模式之建造者设计模式(也叫生成器模式)建造者模式就是将一个复杂对象的构建与他的表示分离,使得同样的构建过程可以创建不同的表示1:使用建造者模式的话,它封装了一个产品的构造过程,并且允许按照预定的步骤构... 查看详情

建造者模式(代码片段)

建造者模式标签(空格分隔):设计模式什么是建造者模式Builder模式,建造者模式也叫生成器模式,是GoF提出的23种设计模式中的一种,是一种对象的创建型模式,用来隐藏复合对象的创建过程,它把复合对象的创建过程加以抽... 查看详情

设计模式建造者模式

1、定义1.1标准定义建造者模式(BuilderPattern)也叫做生成器模式,其定义如下:Separatetheconstructionofacomplexobjectfromitsrepresentationsothatthesame constructionprocesscancreatedifferentrepresentations.(将一个复杂对象的构建与它的表示分离,使得 查看详情

设计模式-建造者模式(05)

定义  建造者模式(BuilderPattern)也叫做生成器模式。英文原话是:Separatetheconstructionofacomplexobjectfromitsrepresentationsothatthesameconstructionprocesscancreatedifferentrepresentations.意思是:将一个复杂对象的构建与它的表示分离,使得同样的... 查看详情

设计模式—建造者模式(builder)(代码片段)

title:设计模式—建造者模式建造者模式(Builder)是一步一步创建一个复杂的对象,它允许用户只通过指定复杂对象的类型和内容就可以构建它们,用户不需要知道内部的具体构建细节。建造者模式属于对象创建型模式。我们获得一... 查看详情

设计模式之建造者模式(代码片段)

设计模式之建造者模式  建造者模式(BuilderPattern)也叫生成器模式,定义:Separatetheconstructionofcomplexobjectfromitsrepresentationsothatthesameconstructionprocesscancreatedifferentrepresentations.(将一个复杂的对象的构建与它的表示 查看详情

创建模式之建造者模式

建造者模式(生成器模式)基本介绍 1) 建造者模式(Builder Pattern) 又叫生成器模式,是一种对象构建模式。它可以将复杂对象的建造过程抽象出来(抽象类别),使这个抽象过程的不同实现方法可以构造出不同表现... 查看详情

06建造者模式builder(代码片段)

...r模式也叫建造者模式或者生成器模式,是由GoF提出的23种设计模式中的一种。  Builder模式是一种对象创建型模式之一,用来隐藏复合对象的创建过程,它把复合对象的创建过程加以抽象,通过子类继承和重载的方式,动态地... 查看详情

设计模式之建造者模式(代码片段)

建造者模式1.简要概述2.模式结构3.实现代码4.优点好处5.缺点弊端6.应用场景7.应用示例1.简要概述建造者模式也可以叫做生成器模式。建造者模式就是说首先封装一个对象的构造过程,然后按照这个过程步骤去构建这个对象实... 查看详情

初识设计模式(建造者模式)(代码片段)

前言:继续学习设计模式,今天学习建造者模式。建造者模式(BuilderPattern)定义  又叫生成器模式。将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。设计原则  依赖倒转原则,抽象不应... 查看详情

建造者模式

建造者模式和工厂模式有点类似。所以建议复习一下工厂模式的文章工厂模式小结1.适用场景建造者模式,跟工厂模式一样,对客户端屏蔽了产品实现的细节,对外只呈现一个最终的产品。与工厂模式不太一样的是:1).该产品相... 查看详情