zoukankan      html  css  js  c++  java
  • 建造者模式

    1.模式简介

          建造者模式(Builder Pattern)属于创建型模式中的一种,在创建比较复杂的对象,或者对象包含多个组成部分时比较有用,用于将对象的创建过程与使用分离,隔离具体的创建细节,方便以后的扩展。它的使用场景包括:

    • 对象的创建比较复杂,需要进行许多处理工作
    • 对象包含多个组成部分,而这几个部分之间常常会有较为固定的顺序
    • 创建对象需要许多参数,会导致构建时的参数列表过长,难以理解和维护

          本文将以一个具体的例子作为引子,通过对比的形式讲解建造者模式的优点,最后引入定义和类图。

    2.案例分析

          这里以做菜为例子,通常做菜的步骤包括:放油,放葱姜蒜,加肉,翻炒,加盐,继续翻炒,装盘,那么做一份豆角炒肉代码可以这样写:

    public class SimpleCook {
      public static void main(String[] args) {
        SimpleCook cook = new SimpleCook();
        cook.cookmeatWithBeans();
      }
    
      public void cookmeatWithBeans(){
        System.out.println("热锅下油。。。");
        System.out.println("加肉。。。");
        System.out.println("加入葱姜蒜。。。");
        System.out.println("放入豆角。。。");
        System.out.println("翻炒均匀。。。");
        System.out.println("加盐,继续翻炒。。。");
        System.out.println("出锅。。。");
      }
    }

          OK,一盘香喷喷的豆角炒肉就做好了。不过,上面的代码还有一些问题:①代码将每个步骤一笔带过,实际上每一步都会有许多细节,比如第一步下油,什么时候倒油,倒多少合适?如果把这些详细步骤全部写在一起,各个步骤之间不仅耦合严重,而且代码还会显得杂乱,一旦某个步骤需要改善,整个方法都受影响;②扩展性问题,如果我想换个藕丁炒肉,那就需要新添加一个方法,没有扩展性。而且我们会发现豆角炒肉和藕丁炒肉所有的步骤都一样,只是把“放入豆角”改成了“放入藕丁”,随着类型菜品的增多,系统会充斥大量的冗余代码。

          因此,这里需要将每个步骤单独提出来作为一个方法,炒菜的步骤修改之后如下:

    public class SimpleCook {
      public static void main(String[] args) {
        SimpleCook cook = new SimpleCook();
        cook.cookMeatWithBeansBetterWay();
      }
    
      public void cookMeatWithBeansBetterWay(){
        addOil();
        addMeat();
        addIngredients();
        addDish();
        cookForAWhile();
        addSalt();
        finish();
      }
    
      private void finish() {
        System.out.println("出锅。。。");
      }
    
      private void addSalt() {
        System.out.println("加盐,继续翻炒。。。");
      }
    
      private void cookForAWhile() {
        System.out.println("翻炒均匀。。。");
      }
    
      private void addDish() {
        System.out.println("放入豆角。。。");
      }
    
      private void addIngredients() {
        System.out.println("加入葱姜蒜。。。");
      }
    
      private void addMeat() {
        System.out.println("加肉。。。");
      }
    
      private void addOil() {
        System.out.println("热锅下油。。。");
      }
    }

          由于很多菜品都遵循类似的步骤,只是具体实现不一样,那么就可以抽象出一个父类,然后具体的步骤交给子类去实现,对于其中共同的部分,可以在抽象父类中提供默认实现:

    public abstract class Builder {
      public abstract void addOil();
      public abstract void addMeat();
      public abstract void addIngredients();
      public abstract void addDish();
      public void cookForAWhile(){
        System.out.println("翻炒均匀。。。");
      }
      public abstract void addSalt();
      public void finish(){
        System.out.println("出锅。。。");
      }
    }

          抽象父类提供了两个方法的默认实现,其他的交给子类去处理,下面是藕丁炒肉子类的实现,豆角炒肉与此类似:

    public class MeatWithLotusRootBuilder extends Builder{
    
      @Override
      public void addOil() {
        System.out.println("热锅下油。。。");
      }
    
      @Override
      public void addMeat() {
        System.out.println("加肉。。。");
      }
    
      @Override
      public void addIngredients() {
        System.out.println("加入葱姜蒜。。。");
      }
    
      @Override
      public void addDish() {
        System.out.println("放入藕丁。。。");
      }
    
      @Override
      public void addSalt() {
        System.out.println("加盐,继续翻炒。。。");
      }
    }

          现在,就可以按照开始规定的步骤炒菜了,这里,我们将炒菜的流程放在Director中,由该类管理具体的步骤顺序:

    public class Director {
      Builder builder;
    
      public Director(Builder builder){
        this.builder = builder;
      }
    
      public void cook(){
        builder.addOil();
        builder.addIngredients();
        builder.addMeat();
        builder.addDish();
        builder.cookForAWhile();
        builder.addSalt();
        builder.finish();
      }
    }

          当我们需要点餐时,只需要告诉Director菜品的名字就可以了,菜就会按照流程做出来:

    // 客户端代码
    public class BuilderCook {
      public static void main(String[] args) {
        Builder builder = new MeatWithLotusRootBuilder();
        Director director = new Director(builder);
        director.cook();
      }
    }

          输出为:

    热锅下油。。。
    加入葱姜蒜。。。
    加肉。。。
    放入藕丁。。。
    翻炒均匀。。。
    加盐,继续翻炒。。。
    出锅。。。

          相比最开始的炒菜方法,改进之后增加了Director、Builder、Builder子类,Director类规定了创建对象的顺序,Builder抽象了炒菜的一般步骤,Builder子类则对应每种菜品的具体实现。

    3. 模式类图与定义

          根据上面的代码,总结出建造者模式的类结构如下:

          其一般定义为:

    建造者模式:将一个复杂对象的构建与它的表示相分离,使得同样的构建过程可以创建不同的表示。

     4. 常见用法

          工作中,建造者模式更常见的使用场景是对象要设置的参数过多的情况,例如,将人抽象为一个Person类,创建的时候需要传递许多信息,可以使用各种setter方法,但是Builder模式能够让这种写法更优雅,代码如下:

    public class Person {
      String name;
      int age;
      double height;
      double weight;
      String sex;
      //省略其他字段
    
      public static void main(String[] args) {
        Person person =
          new Builder().name("zhang san")
            .age(20).height(174)
            .weight(130)
            .sex("male")
            .build();
      }
    
      public Person(Builder builder){
        this.age = builder.age;
        this.height = builder.height;
        this.weight = builder.weight;
        this.name = builder.name;
        this.sex = builder.sex;
      }
    
      public static class Builder{
        String name;
        int age;
        double height;
        double weight;
        String sex;
    
        public Builder age(int age){
          this.age = age;
          return this;
        }
    
        public Builder height(double height){
          this.height = height;
          return this;
        }
    
        public Builder weight(double weight){
          this.weight = weight;
          return this;
        }
    
        public Builder name(String name){
          this.name = name;
          return this;
        }
    
        public Builder sex(String sex){
          this.sex = sex;
          return this;
        }
    
        public Person build(){
          return new Person(this);
        }
      }
    }

    5. 参考

    < <大话 设计模式>>

    一篇文章就彻底弄懂建造者模式(Builder Pattern)

  • 相关阅读:
    apt常用命令(安装,更新,删除)
    记录一次坑爹的VM连接主机的路程
    VM安装centos
    初窥DB2之insert语句
    关于虚拟机的linux不能使用shell连接时的处理方法
    linux命令之查看字符集
    趣图:学JavaScript
    PHP搭建大文件切割分块上传功能示例
    判断变量是否不为空,函数isset()、!empty()与!is_null()的比较
    Javascript 中 null、NaN和undefined的区别
  • 原文地址:https://www.cnblogs.com/qq575654643/p/11826711.html
Copyright © 2011-2022 走看看