zoukankan      html  css  js  c++  java
  • 【设计模式】建造者模式

    建造者模式的理解

      定义: 官方的说法是,将一个复杂的对象的构建与它的表示分离,即隐藏了复杂对象的创建过程,把复杂对象的创建过程加以抽象,通过子类继承或者重载的方式,动态的创建具有复合属性的对象。

     

    四个角色:

       1.产品角色(Product):最终要生成的对象实例

       2.抽象建造者(Builder):构建者的抽象基类(也可用接口代替),里面定义了构建product的步骤。通常还包含一个返回复杂产品的方法 getResult()。

       3.具体建造者(Concrete Builder):实现 Builder 接口,完成复杂产品的各个部件的具体创建方法。

       4.指挥者(Director):它调用建造者对象中的部件构造与装配方法完成复杂对象的创建,在指挥者中不涉及具体产品的信息。

     

    具体使用

       1.首先生成一个director

       2.然后生成一个目标builder

       3.接着使用director组装builder

       4.组装完毕后使用builder创建产品实例

     

    大白话理解:

       比如现在有5个产品,他们的参数全部或者大部分是固定的,比如颜色、大小、重量等,那么我们就需要写死在一个地方,这就可以用到建造者模式,这样每次直接创建就可以按照如下顺序直接创建出来了。先创建director、再创建builder、接着使用director组装builder、组装完毕后使用builder创建产品实例

     

    什么时候可以使用

       1.当一个类里面的属性过多的时候,建议使用

       2.当一个类的构造函数参数个数超过4个,而且这些参数有些是可选的参数,考虑使用构造者模式

     

    建造者模式主要适用于以下应用场景:

       1.相同的方法,不同的执行顺序,产生不同的结果。

       2.多个部件或零件,都可以装配到一个对象中,但是产生的结果又不相同。

       3.产品类非常复杂,或者产品类中不同的调用顺序产生不同的作用。

       4.初始化一个对象特别复杂,参数多,而且很多参数都具有默认值。

     

    优点

       1.使用建造者模式可以使客户端不必知道产品内部组成的细节。

       2.具体的建造者类之间是相互独立的,这有利于系统的扩展。

       3.具体的建造者相互独立,因此可以对建造的过程逐步细化,而不会对其他模块产生任何影响。

     

    缺点

       1.建造者模式所创建的产品一般具有较多的共同点,其组成部分相似;如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制。

       2.如果产品的内部变化复杂,可能会导致需要定义很多具体建造者类来实现这种变化,导致系统变得很庞大。

       3.如果产品内部发生变化,则建造者也要同步修改,后期维护成本较大。

     

    建造者模式和工厂模式的区别

       1.建造者模式更加注重方法的调用顺序,工厂模式注重创建对象。

       2.创建对象的力度不同,建造者模式创建复杂的对象,由各种复杂的部件组成,工厂模式创建出来的对象都一样

       3.关注重点不一样,工厂模式只需要把对象创建出来就可以了,而建造者模式不仅要创建出对象,还要知道对象由哪些部件组成。

       4.建造者模式根据建造过程中的顺序不一样,最终对象部件组成也不一样。

     

    传统方式的建造者模式

    背景介绍

    这里我是参考了我们项目里的一个业务,简单说就是有一个任务表,里面可以存储多种类型的任务,不同类型的任务存储的字段以及值不同。里面共有20多个字段。现在暂定只有两种任务。打印任务,即打印pdf文件的任务。刻录任务,即刻录光盘的任务。

    具体实现

    1. 创建一个任务的实体类,这个就很常见了,里面定义一些属性和方法

    2. 创建一个抽象建造者类,里面定义好多个抽象的方法,将来会将这些方法组合起来组成建造的流程。

    3. 创建两个具体的建造者类,一个是打印任务建造者,一个是刻录任务建造者,分别继承抽象建造者,并重写抽象方法。

    4. 创建一个指挥者类,里面可以定义通过构造器或者setter方法传入参数,最后定义一个具体的建造任务的流程方法。

    5. 最后创建一个测试类,可以通过传入不同的具体建造者类,来创建出不同的任务类。

    import lombok.Data;
    
    //首先定义一个Product产品类
    //定义一些属性和方法
    @Data
    public class Task {
    
        //任务类别  1:打印  2:刻录
        private Integer type;
    
        //任务名称
        private String name;
    
        //使用状态 1:使用 0:禁用
        private Integer status;
    
        //省略其他字段
    
        //这里可以定义一些Task类的方法,随便举个例子,比如获取task任务的信息
        public void getTaskMsg(){
            System.out.println("当前任务的信息为:Type:"+type+" Name:"+name+" Status"+status);
        }
    
    }
    
    //定义一个抽象建造者类
    //里面可以自定义多个抽象方法,即这些抽象的方法组合起来就是建造的流程
    public abstract class TaskBuilder {
    
        protected Task task = new Task();
    
        //抽象方法-->建造的流程
        public abstract void buildType();
    
        public abstract void buildName();
    
        public abstract void buildStatus();
    
        //建造任务,然后将产品(任务)return
        public Task buildTask() {
            return task;
        }
    
    }
    
    //接下来新建两个具体的建造者对象,分别代表打印任务和刻录任务,并让他们继承抽象继承者
    public class PrintTask extends TaskBuilder {
    
        //这里可以传参,也可以不传。具体逻辑自己定义,返回值也可以自己定义。
        //可以通过返回具体建造者类来实现链式调用。
        @Override
        public void buildType() {
            task.setType(1);
        }
    
        @Override
        public void buildName() {
            task.setName("打印任务");
        }
    
        @Override
        public void buildStatus() {
            task.setStatus(1);
        }
    }
    
    //具体含义可以参考PrintTask建造者
    public class BurnTask extends TaskBuilder {
        @Override
        public void buildType() {
            task.setType(2);
        }
    
        @Override
        public void buildName() {
            task.setName("刻录任务");
        }
    
        @Override
        public void buildStatus() {
            task.setStatus(1);
        }
    }
    
    //指挥者,在这里可以动态的去指定制作流程,返回产品
    public class TaskDirector {
    
        TaskBuilder taskBuilder = null;
        //下面可以看出,可以通过构造器或者setter方式实现赋值
        //构造器传入 taskBuilder
        public TaskDirector(TaskBuilder taskBuilder) {
            this.taskBuilder = taskBuilder;
        }
        //通过setter传入taskBuilder
        public void setTaskBuilder(TaskBuilder taskBuilder) {
            this.taskBuilder = taskBuilder;
        }
        //如何处理建造任务的流程,交给指挥者。
        public Task createTask() {
            taskBuilder.buildName();
            taskBuilder.buildStatus();
            taskBuilder.buildType();
            return taskBuilder.buildTask();
        }
    
    }
    
    //测试类
    public class Test {
    
        public static void main(String[] args) {
            //创建打印任务
            TaskDirector taskDirector1 = new TaskDirector(new PrintTask());
            Task printTask = taskDirector1.createTask();
            System.out.println(printTask);
    
            //创建刻录任务
            TaskDirector taskDirector2 = new TaskDirector(new BurnTask());
            Task burnTask = taskDirector2.createTask();
            System.out.println(burnTask);
        }
    
    }
    View Code

    结果:

    //Task(type=1, name=打印任务, status=1)
    //Task(type=2, name=刻录任务, status=1)

     

    简化后的建造者模式

    步骤如下:

    1. 创建一个user类,里面定义三个属性用于演示,实际上user表可能有超过20个字段的可能。

    2. 创建一个建造者类,里面将user类改称为其的内部类,并将各个属性的构造步骤添加进去,每次完成一个步骤都返回this。

    3. 创建一个测试类,使用建造者方式的链式写法和传统方式两种方法分别创建user对象,并设置属性值,可以看到建造者方法,代码更简洁,逻辑更清楚,且user对象的属性越多,优点越明显。

    //定义一个user对象,并定义三个属性,类上加上Data注解,省略了set get方法
    @Data
    public class User {
        private String name;
    
        private Integer age;
    
        private String sex;
    }
    
    //创建建造者类
    public class UserBuilder {
        //将User类改成为UserBuilder类的内部类,并将构造步骤添加进来,每次完成一个步骤都返回this
        private User user = new User();
    
        public UserBuilder addName(String name) {
            this.user.setName(name);
            return this;
        }
    
        public UserBuilder addAge(Integer age) {
            this.user.setAge(age);
            return this;
        }
    
        public UserBuilder addSex(String sex) {
            this.user.setSex(sex);
            return this;
        }
    
        public User builder() {
            return this.user;
        }
    }
    
    //创建测试类
    public class Test {
        public static void main(String[] args) {
            //下面使用了两种方法来创建了user对象,那么可以看出方法1使用了建造者模式的链式写法,
            //代码更简洁,逻辑更清楚。且user对象的属性越多,优点越明显
    
            //方法1
            UserBuilder userBuilder = new UserBuilder();
            userBuilder.addAge(11).addName("张三").addSex("男");
            User user = userBuilder.builder();
            System.out.println("建造者方法生成用户为:"+user);
    
            //方法2
            User user2 = new User();
            user2.setSex("女");
            user2.setName("小丽");
            user2.setAge(11);
            System.out.println("普通方法生成对象:"+user2);
        }
    }

     

    应用

      我们在开发过程中常见的StringBuilder其实就用到了建造者模式,并使用了链式调用,如下:

    public static void main(String[] args) {
        StringBuilder builder = new StringBuilder();
        builder.append("1").append("2").append("3");
        System.out.println(builder);
    }

    分析

      首先看他的部分源码

      总结:

        1.Appendable接口定义了多个append方法(抽象方法),即Appendable为抽象建造者,定义了抽象方法。

        2.AbstractStringBuilder实现了Appendable接口。所以他是建造者,虽然他是一个抽象类,不能实例化。

        3.StringBuilder类继承AbstractStringBuilder,对于它来说,它既充当了指挥者角色,同时充当了具体的建造者。建造方法的具体实现是由AbstractStringBuilder完成

    //StringBuilder
    public final class StringBuilder extends AbstractStringBuilder implements java.io.Serializable, CharSequence{
    
      static final long serialVersionUID = 4383685877147921099L;
    
      @Override
      public StringBuilder append(Object obj) {
        return append(String.valueOf(obj));
      }
    
      @Override
      public StringBuilder append(String str) {
        super.append(str);
        return this;
      }
    
      public java.lang.StringBuilder append(StringBuffer sb) {
        super.append(sb);
        return this;
      }
    
      @Override
      public java.lang.StringBuilder append(CharSequence s) {
        super.append(s);
        return this;
      }
    
    }
    
    //AbstractStringBuilder
    abstract class AbstractStringBuilder implements Appendable, CharSequence {}
    
    
    //Appendable
    public interface Appendable {
      Appendable append(CharSequence csq) throws IOException;
      Appendable append(CharSequence csq, int start, int end) throws IOException;
      Appendable append(char c) throws IOException;
    }
    View Code

     

    参考:

    https://www.jianshu.com/p/3d1c9ffb0a28
    https://zhuanlan.zhihu.com/p/58093669
    http://c.biancheng.net/view/1354.html
    https://blog.csdn.net/Woo_home/article/details/104362776
    https://blog.csdn.net/qq_42339210/article/details/106742211

     

     

  • 相关阅读:
    Python异常处理
    奇异值分解(SVD)详解及其应用
    上楼梯问题
    Python面向对象(特殊成员)
    Best Time to Buy and Sell Stock II
    String to Integer (atoi)
    Gas Station
    N-Queens II
    Letter Combinations of a Phone Number
    N-Queens
  • 原文地址:https://www.cnblogs.com/flyinghome/p/15196047.html
Copyright © 2011-2022 走看看