zoukankan      html  css  js  c++  java
  • 建造者模式——结合案例,通俗易懂

    一个设计模式解决一类问题,最近学习了一下建造者模式,看了很多博客,讲的模棱两可,所以决定写一下我觉得比较好理解的简介
    参考自知乎 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很像具体的工厂,都是规定了建造的内容

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

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

    【总结】

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

  • 相关阅读:
    Android Studio 2.2以上支持了Cmake的配置JNI的相关参数
    Unable to instantiate receiver xxx.receiver.NetworkReceiver异常
    关于imageview matrix
    Android NDK开发 JNI操作java构造方法,普通方法,静态方法(七)
    COOKIE和SESSION的区别
    Android NetworkInterface 的 name
    Android ROM资源文件存放位置
    selinux
    当WebView运行在特权进程时抛出安全异常,Hook方式解决方案(包含对Android 8.0的处理)
    Android判断当前是否在主线程
  • 原文地址:https://www.cnblogs.com/yxm2020/p/12821797.html
Copyright © 2011-2022 走看看