建造者模式——结合案例,通俗易懂

Posted yxm2020

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了建造者模式——结合案例,通俗易懂相关的知识,希望对你有一定的参考价值。

一个设计模式解决一类问题,最近学习了一下建造者模式,看了很多博客,讲的模棱两可,所以决定写一下我觉得比较好理解的简介
参考自知乎 https://zhuanlan.zhihu.com/p/58093669,

一、介绍

1、啥是建造者模式

是将一个复杂的对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示

看不懂对吧!其实我也看不懂,不管它,通过案例和代码加深理解

2、使用场景

一个设计模式解决一类问题,那么建造者模式解决了什么问题呢?——对象的构建过于复杂的问题

  • 当一个类的构造函数参数过多(超过四个),并且有的参数可有可无,或者很多产品有默认值。
  • 产品类非常复杂或者产品类因为调用顺序不同而产生不同作用
3、优点
  • 复杂产品的创建步骤分解在不同的方法中,这些方法可以调用顺序不同,结果不同,创建结果很清晰
4、缺点
  • 如果产品的内部变化复杂,可能会导致需要定义很多具体建造者来实现这种变化。

二、案例

理论总是难以理解的,现在通过案例分析问题,一步步了解使用建造者模式的好处

【案例】好好看一下这个案例

KFC套餐
假如目前KFC里面有很多个套餐
在套餐里面有必点,也有选点,然后每个单品又有大小之分
必点:汉堡(hamburger),薯条(chips)
选点:鸡腿(chicken),可乐(cola),披萨(pizza)

【用Java代码模拟场景】

我们如何构成这么多套餐实例呢?

我们不使用建造者模式也能构建代码,但是建造者模式会让代码看上去更装逼,代码到后期更结构化更容易维护和拓展

首先构建这个实体类KFC

public class KFC {
    //套餐必点
    private String hamburger;
    private String chips;
    
    //套餐选点
    private String chicken;
    private String cola;
    private String pizza;
}

我们的想法是不是折叠构造函数来创建实例,下面来尝试一下

public class KFC{
    //省略了上面的属性.....
    
    //必点套餐A
    public KFC(String hamburger,String chips){
        this(hamburger,chips,null,null,null);
    }
    A
	//套餐B
	public KFC(String hamburger,String chips,String chicken){
    	this(hamburger,chips,chicken,null,null);
	}
    //套餐C
	public KFC(String hamburger,String chips,String chicken,String cola){
    	this(hamburger,chips,chicken,cola,null);
	}
    
	//......还有好多种组合方式,你会发现使用折叠构造函数的方法十分复杂
    
	//全选
	public KFC(String hamburger,String chips,String chicken,String cola,String pizza){
    	this.hamburger = hamburger;
   	 	this.chips = chips;
    	this.chicken = chicken;
    	this.cola = cola;
    	this.pizza = pizza;
	}
}

我们会发现使用折叠构造函数的方式很复杂,很恶心,代码看都不想看

那么有人会想,我可以使用set方法来创建,我只要一个必点构造就好了,那继续模拟咯

public class KFC{
    //.....省略了属性
    //必点
    public KFC(String hamburger,String chips){
        this.hamburger = hamburger;
        this.chips = chips;
    }
    //set方法
    public void setChicken(String chicken) {
        this.chicken = chicken;
    }

    public void setCola(String cola) {
        this.cola = cola;
    }

    public void setPizza(String pizza) {
        this.pizza = pizza;
    }
    
    //实例化对象,你会发现这种方式就友好很多
     public static void main(String[] args) {
        KFC kfc = new KFC("大汉堡","大薯条");
        //加小份可乐
        kfc.setCola("小可乐");
        //加个鸡腿
        kfc.setChicken("大鸡腿");
        System.out.println(kfc);
    }
}

你会发现使用set方式就友好了很多

这个虽然友好了很多,但是也有点小毛病,就是你set太随意了,我可能这个套餐里面没有这个单品,而使用set的人却不知道,造成错误的套餐出现!。

为了解决上面的两种问题:一种设计模式解决一类问题,所以建造者模式就出现了


三、建造者模式

先了解一下又哪些角色吧,看不懂没关系,看一下代码就懂了

技术图片

四个角色

Product(产品角色): 一个具体的产品对象。

Builder(抽象建造者): 创建一个Product对象的各个部件指定的抽象接口。

ConcreteBuilder(具体建造者): 实现抽象接口,构建和装配各个部件。

Director(指挥者): 构建一个使用Builder接口的对象。它主要是用于创建一个复杂的对象。它主要有两个作用,一是:隔离了客户与对象的生产过程,二是:负责控制产品对象的生产过程。

拿其中两个套餐举例

套餐A:汉堡,薯条,大鸡腿
套餐B:汉堡,薯条,小鸡腿,小可乐,小披萨

其中薯条和汉堡可大可小,并且必须有,
其它的都为固定大小,但是你可以选择有或没有

  • 产品(KFC)
public class KFC {
    //套餐必点
    private String hamburger;
    private String chips;
    
    //套餐选点
    private String chicken;
    private String cola;
    private String pizza;
    
        //必点
    public KFC(String hamburger,String chips){
        this.hamburger = hamburger;
        this.chips = chips;
    }
    //set方法
    public void setChicken(String chicken) {
        this.chicken = chicken;
    }

    public void setCola(String cola) {
        this.cola = cola;
    }

    public void setPizza(String pizza) {
        this.pizza = pizza;
    }
}
  • Builder

定义一个接口,表明需要建造什么,得到什么

public interface Builder {
    void setChicken();
    void setCola();
    void setPizza();
    KFC getKFC();
}
  • ConcreteBuilder:

此时应该注意,这个时候还没有生产套餐,只是定义套餐

套餐A

public class ConcreteBuilder1 implements Builder {
    private KFC kfc;
    //这一步非常重要
    public ConcreteBuilder1(String hamburger,String chips){
        kfc = new KFC(hamburger,chips);
    }
    @Override
    public void setChicken() {
        kfc.setChicken("大鸡腿");
    }

    @Override
    public void setCola() {
        kfc.setCola(null);
        System.out.println("套餐A里面没有可乐");
    }

    @Override
    public void setPizza() {
        kfc.setPizza(null);
        System.out.println("套餐A里面没有披萨");
    }

    @Override
    public KFC getKFC() {
        return kfc;
    }
}

套餐B

public class ConcreteBuilder2 implements Builder {
    private KFC kfc;
    //这一步非常重要
    public ConcreteBuilder2(String hamburger,String chips){
        kfc = new KFC(hamburger,chips);
    }
    @Override
    public void setChicken() {
        kfc.setChicken("小鸡腿");
    }

    @Override
    public void setCola() {
        kfc.setCola("小可乐");
    }

    @Override
    public void setPizza() {
        kfc.setPizza("小披萨");
    }

    @Override
    public KFC getKFC() {
        return kfc;
    }
}

Director:

真正的执行者,这里把他当作服务员,此时你像服务员点餐

public class Director {
    public KFC build(Builder builder){
        //套餐里面我只选了鸡腿和可乐
        builder.setChicken();
        builder.setCola();
        return builder.getKFC();
    }
}

测试

public class BuilderTest {
    public static void main(String[] args) {
       //套餐A
        System.out.println("======套餐A======");
        Builder concreteBuilder1 = new ConcreteBuilder1("大汉堡", "小薯条");
        KFC kfc1 = new Director().build(concreteBuilder1);
        System.out.println(kfc1);
        //套餐B
        System.out.println("======套餐B======");
        Builder concreteBuilder2 = new ConcreteBuilder2("小汉堡", "小薯条");
        KFC kfc2 = new Director().build(concreteBuilder2);
        System.out.println(kfc2);
    }
}

输出

技术图片

到了这里你还是会觉得有点麻烦,你会发现,单品可有可无的选择上面你十分的被动,代码看上去也很怪,如果你下次想全部单品先选上,再去选套餐的时候,你又要新建一个新的指导者。

我觉得普通的建造者模式不适合参数的可有可无的选择,普通的建造者模式更侧重调控次序,在有些情况下需要简化系统结构


四、简化版的建造者模式

这个时候简化版的建造者模式站出来了

采用链式编程的方式

这种模式更加灵活,更加符合定义

既然Director是变化的,并且其实在生活中我们自己本身就是Director,所以这个时候我们可以把Director这个角色去掉,因为我们自身就是指导者

  • 产品(product)
public class KFC {
    //套餐必点
    private String hamburger;
    private String chips;

    //套餐选点
    private String chicken;
    private String cola;
    private String pizza;
    public KFC(String hamburger,String chips){
        this.hamburger = hamburger;
        this.hamburger = chips;
    }
    public void setChicken(String chicken) {
        this.chicken = chicken;
    }

    public void setCola(String cola) {
        this.cola = cola;
    }

    public void setPizza(String pizza) {
        this.pizza = pizza;
    }
}

  • 抽象建造者(builder)
public abstract class Builder {
        abstract Builder setChicken();
        abstract Builder setCola();
        abstract Builder setPizza();
        abstract KFC getKFC();
}
  • 具体建造者(ConcreteBuilder)
public class ConcreteBuilder extends Builder {
    KFC kfc;
    public ConcreteBuilder(String hamburger,String chips){
        kfc = new KFC(hamburger,chips);
    }
    @Override
    Builder setChicken() {
        kfc.setChicken("鸡腿");
        return this;
    }

    @Override
    Builder setCola() {
        kfc.setCola("可乐");
        return this;
    }

    @Override
    Builder setPizza() {
        kfc.setPizza("披萨");
        return this;
    }

    @Override
    KFC getKFC() {
        return kfc;
    }
}

  • 测试
public class BTest {
    public static void main(String[] args) {
       KFC kfc = new ConcreteBuilder("汉堡","薯条").setChicken().setCola().getKFC();
    }
}

如果不需要抽象建造者的角色来规定生产内容,那么代码到这里其实还有进一步的简化空间。

【关键代码】

使用静态内部类的方式

【进一步简化】

public class KFC {
    //套餐必点
    private String hamburger;
    private String chips;

    //套餐选点
    private String chicken;
    private String cola;
    private String pizza;
	
    //一定要有一个带有Builder参数的建造者
    private KFC(Builder builder) {
        this.hamburger = builder.hamburger;
        this.chips = builder.chips;
        this.chicken = builder.chicken;
        this.cola = builder.cola;
        this.pizza = builder.pizza;
    }

    //注意必须为静态内部类
    public static class Builder{
        //套餐必点
        private String hamburger;
        private String chips;

        //套餐选点
        private String chicken;
        private String cola;
        private String pizza;

        public Builder(String hamburger,String chips){
            this.hamburger = hamburger;
            this.chips = chips;
        }
        public Builder setChicken(){
            this.chicken = "小鸡腿";
            return this;
        }
        public Builder setCola(){
            this.cola = "小可乐";
            return this;
        }
        public Builder setPizza(){
            this.pizza = "小披萨";
            return this;
        }
        
        //生成一个产品
        public KFC getKFC(){
            return new KFC(this);
        }
    }
}

测试

public class BuilderTest {
   public static void main(String[] args) {
       KFC kfc = new KFC.Builder("大汉堡", "小薯条").setChicken().setCola().getKFC();
       System.out.println(kfc);
    }
}

五、建造者模式和抽象工厂模式的区别

通过上面的代码,你发现普通的建造者模式和抽象工厂模式真的很像,在建造者模式中的builder角色很像超级工厂,然后contracterBuilder很像具体的工厂,都是规定了建造的内容

那么它们之前 有什么区别呢

  • 建造者模式有指导者这个角色,直接返回一个组装好的产品,而抽象工厂模式返回一系列相关的产品,这些产品位于不同的产品等级结构,构成了一个产品族
  • 建造者模式更适合复杂的产品构建
  • 可以将抽象工厂模式理解成汽车零件生产工厂,而建造者模式看出组装工厂

【总结】

学习一类技巧是为了解决一类问题,学习设计模式主要是为了理解它的思想,将来遇到代码的编写,使用这种模式会更加的方式,而没有必要刻意的去使用,但是需要刻意的去练习,形成这种思想












以上是关于建造者模式——结合案例,通俗易懂的主要内容,如果未能解决你的问题,请参考以下文章

设计模式之建造者模式

建造者(Builder)模式

建造者模式案例

设计模式之建造者模式案例详解

《大话设计模式》——读后感 好菜每回味不同——建造者模式之基础案例

RabbitMq高级特性之延迟队列 通俗易懂 超详细 内含案例