设计模式之建造者模式

lijulin 4年前
   <p>建造者模式(Builder Pattern)也叫做生成器模式,今天让我们一起学习一下建造者模式。</p>    <h3>一、基本介绍</h3>    <p>建造者模式的定义为: 将一个复杂对象的构建和它的表示分离开,使得同样的构建过程可以创建不同的表示。</p>    <p>建造者模式主要由4个角色来组成:</p>    <p>1 . 抽象建造者(Builder)角色:该角色用于规范产品的各个组成部分,并进行抽象,一般独立于应用程序的逻辑。</p>    <p>2 . 具体建造者(Concrete Builder)角色:该角色实现抽象建造者中定义的所有方法,并且返回一个组建好的产品实例。</p>    <p>3 . 产品(Product)角色:该角色是建造者中的复杂对象,一个系统中会有多于一个的产品类,这些产品类并不一定有共同的接口,完全可以是不相关联的。</p>    <p>4 . 导演者(Director)角色:该角色负责安排已有模块的顺序,然后告诉Builder开始建造。</p>    <h3>二、代码实现建造者模式</h3>    <p>上面说的东西都只是理论,多少有些空洞,现在我们就通过一个简单的例子来体验一下建造者模式。</p>    <p>1 . 创建产品类 Product.java 类</p>    <pre>  <code class="language-java">/**   * 产品类   *   */  public class Product {      //业务处理方法  }</code></pre>    <p>由于我们的目的是最终生产产品,所以产品类中的逻辑实现我们暂且不关注,具体的方法要根据业务来写。</p>    <p>2 . 创建抽象建造者类 Builder.java 类</p>    <pre>  <code class="language-java">/**   * 抽象建造者类   *   */  public abstract class Builder {      //设置产品的不同部分,以获得不同的产品      public abstract void setPart1();      public abstract void setPart2();      public abstract void setPart3();        //建造产品      public abstract Product builderProduct();  }</code></pre>    <p>该类是一个抽象类,其中我们声明了4个抽象方法,前面三个是负责给产品添加不同的部件,第四个方法是负责建造产品。但这只是一个框架,还没有具体的实现。</p>    <p>3 . 创建具体建造者类 ConcreteBuilder.java 类</p>    <pre>  <code class="language-java">/**   *具体建造者类   *   */  public class ConcreteBuilder extends Builder{      //一个产品      private Product product = new Product();        //开始安装产品的部件      @Override      public void setPart1() {          //为product安装部件1      }        @Override      public void setPart2() {          //为product安装部件2      }        @Override      public void setPart3() {          //为product安装部件3      }        //建造一个产品      @Override      public Product builderProduct() {          // TODO Auto-generated method stub          return product;      }  }</code></pre>    <p>该类会继承自抽象建造者类 Builder ,并实现其中的方法。开始先声明一个产品,然后在各个 setPart3 方法中添加具体的逻辑,然后在 builderProduct() 方法中返回生产好的产品。</p>    <p>4 . 创建导演类 Director() 类</p>    <p>上面我们已经实现了具体的建造者类,也具体写好了安装每个部件的方法,最后一步就是由导演类来知道具体构建者类如何制造产品啦。制造完的产品会交给导演类负责处理。</p>    <pre>  <code class="language-java">/**   * 导演类   *   */  public class Director1 {      private Builder builder = new ConcreteBuilder();        //构建产品,调用各个添加部件的方法      public Product build(){          builder.setPart1();          builder.setPart2();          builder.setPart3();          //调用构建产品的方法来构建产品          return builder.builderProduct();      }  }</code></pre>    <p>这个类很简单,其实就是获得一个具体的建造者对象,然后调用具体的方法给产品安装部件,安装完成后调用 builder.builderProduct() 方法获得建造好的产品,此处导演类是在 build() 方法中完成了建造过程,同时将获得的建造好的产品返回出去,以供其他模块使用该产品。</p>    <p>此处的导演类起到了封装左右,可以避免高层模块深入到建造者内部的实现类,而且导演类可以有多个,根据业务逻辑分别用来建造不同的产品并输出。</p>    <h3>三、建造者模式的优点</h3>    <p>建造者模式的有点主要有以下几点:</p>    <p>1 . 封装性 。使用建造者模式可以使客户端不必知道产品的内部实现细节</p>    <p>2 . 独立易扩展 。由于建造过程是独立的,更利于后期扩展</p>    <p>3 . 便于控制细节风险 。由于具体的产品建造是独立的,因此可以对建造过程逐步细化,而不对其他的模块产生任何影响</p>    <h3>四、建造者模式的使用场景</h3>    <p>说了这么多建造者模式的好处,那我们应该在什么场合使用它们呢,看下面:</p>    <p>1 . 相同的方法,不同的执行顺序,产生不同的结果。这种情况我们只要利用不同的导演类来控制建造过程,就可以产生不同的产品,其他部分不用修改</p>    <p>2 . 多个零件和部件,都可以装配到一个对象中,装配不同的零件,产生不同的运行结果,我们同样可以通过修改导演类来产生不同的产品</p>    <p>3 . 产品类非常复杂,此时我们也可以将产品的建造方法和具体的建造顺序分离开来处理</p>    <p>4 . 在对象创建过程中会用到系统的一些其他对象,这些对象在产品对象的创建过程中不容易得到,可以采用建造者模式封装该对象的创建过程</p>    <p>注:建造者模式关注的是零件的类型和装配工艺的顺序</p>    <h3>五、建造者模式实站</h3>    <p>说了半天建造产品,没行动有卵用,来来来,咋们就用刚学的 建造者模式生产两台不同类型的电脑 练练手,代码敲起来</p>    <p>1 . 创建产品父类 Computer 类</p>    <p>该类是我们建造的计算机的父类,其中包含了计算机的公共属性以及属性的get和set方法</p>    <pre>  <code class="language-java">package cn.codekong.start;    /**   * 计算机类   */  public class Computer {      //型号      private String type;      //CPU      private String cpu;      //内存      private String ram;      //硬盘      private String hardDisk;      //显示器      private String monitor;      //操作系统      private String os;        //对应的get和set方法      public String getType() {          return type;      }      public void setType(String type) {          this.type = type;      }      public String getCpu() {          return cpu;      }      public void setCpu(String cpu) {          this.cpu = cpu;      }      public String getRam() {          return ram;      }      public void setRam(String ram) {          this.ram = ram;      }      public String getHardDisk() {          return hardDisk;      }      public void setHardDisk(String hardDisk) {          this.hardDisk = hardDisk;      }      public String getMonitor() {          return monitor;      }      public void setMonitor(String monitor) {          this.monitor = monitor;      }      public String getOs() {          return os;      }      public void setOs(String os) {          this.os = os;      }  }</code></pre>    <p>2 . 创建具体的产品类 T410 类和 X201 类</p>    <p>这两个类均继承自上面的 Computer 类,并且我们在该类在添加了两台计算机特有的属性, T410 计算机用于独立显卡,而 X201 没有,同时重写了它们的 toString() 方法,返回它们的参数,便于最后我们建造完计算机后输出它们各自的配置参数.</p>    <pre>  <code class="language-java">T410.java</code></pre>    <pre>  <code class="language-java">package cn.codekong.start;    public class T410 extends Computer{      //显卡      private String graphicCard;      public T410() {          this.setType("Thinkpad T410");      }        public String getGraphicCard(){          return graphicCard;      }        public void setGraphicCard(String graphicCard){          this.graphicCard = graphicCard;      }        @Override      public String toString() {          return "型号:\t" + this.getType() + "\nCPU\t" + this.getCpu()                  + "\n内存\t" + this.getRam() + "\n硬盘\t" + this.getHardDisk()                  + "\n显卡\t" + this.getGraphicCard() + "\n显示器\t" + this.getMonitor()                  + "\n操作系统\t" + this.getOs();      }  }</code></pre>    <pre>  <code class="language-java">X201.java</code></pre>    <pre>  <code class="language-java">package cn.codekong.start;    public class X201 extends Computer{      public X201() {          this.setType("Thinkpad X201");      }      @Override      public String toString() {          return "型号:\t" + this.getType() + "\nCPU\t" + this.getCpu()                  + "\n内存\t" + this.getRam() + "\n硬盘\t" + this.getHardDisk()                  + "\n显示器\t" + this.getMonitor() + "\n操作系统\t" + this.getOs();      }  }</code></pre>    <p>上面的(1)(2)步只是相当于我们有了我们需要的产品类已经声明好了,下面开始写我们的抽象建造类</p>    <p>3 . 抽象计算机建造类 ComputerBuilder 类</p>    <p>我们创建了一个接口,在其中声明了我们要建造产品过程中需要用到的方法,其实就是产品的各个建造步骤</p>    <pre>  <code class="language-java">package cn.codekong.start;  /**   * 抽象的计算机建造者   * 声明建造的公共方法   */  public interface ComputerBuilder {      //建造CPU      void buildCpu();      //建造内存      void buildRam();      //建造硬盘      void buildHardDisk();      //建造显卡      void buildGraphicCard();      //建造显示器      void buildMonitor();      //建造操作系统      void buildOs();        //得到建造好的计算机      Computer getResult();  }</code></pre>    <p>4 . 创建具体的建造类 T410Builder 类和 X201Builder 类</p>    <p>这两个类要实现上一步定义的接口,然后实现里面的各个方法,其实就是实现各个具体的组装方法中的逻辑,但此时只是把每一个组装的步骤的逻辑具体化了,还没有开始正式组装。</p>    <pre>  <code class="language-java">T410Builder.java</code></pre>    <pre>  <code class="language-java">package cn.codekong.start;    /**   * T410的具体建造者实现抽象的计算机建造者   */  public class T410Builder implements ComputerBuilder{      private T410 computer = new T410();      @Override      public void buildCpu() {          computer.setCpu("i5-450");      }        @Override      public void buildRam() {          computer.setRam("4G 1333MHz");      }        @Override      public void buildHardDisk() {          computer.setHardDisk("500G 7200转");      }        @Override      public void buildGraphicCard() {          computer.setGraphicCard("Nvidia");      }        @Override      public void buildMonitor() {          computer.setMonitor("14英寸 1280*800");      }        @Override      public void buildOs() {          computer.setOs("Windows7 旗舰版");      }        @Override      public Computer getResult() {          return computer;      }  }</code></pre>    <pre>  <code class="language-java">X201Builder.java</code></pre>    <pre>  <code class="language-java">package cn.codekong.start;    /**   * X201计算机的具体建造类实现抽象的计算机建造类   */  public class X201Builder implements ComputerBuilder{      private X201 computer = new X201();      @Override      public void buildCpu() {          computer.setCpu("i3-350");      }        @Override      public void buildRam() {          computer.setRam("2G 1333M");      }        @Override      public void buildHardDisk() {          computer.setHardDisk("250G 5400 转");      }        @Override      public void buildGraphicCard() {          //无独立显卡      }        @Override      public void buildMonitor() {          computer.setMonitor("12英寸 1280*800");      }        @Override      public void buildOs() {          computer.setOs("Windows7 Home版");      }        @Override      public Computer getResult() {          return computer;      }  }</code></pre>    <p>5 . 导演类知道具体的建造者类建造产品 ComputerDirector 类</p>    <p>该类就比较简单了,在该类内部实现两个构造方法,分别对应实现两种计算机的过程,这时候才是正式的建造过程。</p>    <pre>  <code class="language-java">package cn.codekong.start;    /**   * 计算机导演类,知道具体建造者建造计算机   */  public class ComputerDirector {      ComputerBuilder builder;        //建造T410计算机      public T410 constructT410(){          builder = new T410Builder();          builder.buildCpu();          builder.buildRam();          builder.buildHardDisk();          builder.buildGraphicCard();          builder.buildMonitor();          builder.buildOs();          //建造结束将产品返回供外部使用          return (T410)builder.getResult();      }        //建造X201计算机      public X201 constructX201(){          builder = new X201Builder();          builder.buildCpu();          builder.buildRam();          builder.buildHardDisk();          //由于X201没有独立显卡,则不调用buildGraphicCard()函数          //builder.buildGraphicCard();            builder.buildMonitor();          builder.buildOs();          //建造结束将产品返回供外部使用          return (X201)builder.getResult();      }  }</code></pre>    <p>6 . 最后让我们测试一下建造的产品是否是好的,新建测试类 ComputerTest 类</p>    <pre>  <code class="language-java">package cn.codekong.start;    /**   * 计算机建造测试类   */  public class ComputerTest {      public static void main(String[] args) {          ComputerDirector computerDirector = new ComputerDirector();          //建造T410计算机          Computer t410 = computerDirector.constructT410();          //输出T410计算机的配置参数          System.out.println(t410);            System.out.println("------------我是分割线----------------");            //建造X201计算机          Computer x201 = computerDirector.constructX201();          //输出X201的计算机配置          System.out.println(x201);      }  }</code></pre>    <p>输出结果如下</p>    <pre>  <code class="language-java">型号:    Thinkpad T410  CPU    i5-450  内存    4G 1333MHz  硬盘    500G 7200转  显卡    Nvidia  显示器    14英寸 1280*800  操作系统    Windows7 旗舰版  ------------我是分割线----------------  型号:    Thinkpad X201  CPU    i3-350  内存    2G 1333M  硬盘    250G 5400 转  显示器    12英寸 1280*800  操作系统    Windows7 Home版</code></pre>    <p>好了,经过上面的步骤,我们的产品就建造好咯。</p>    <p>这时候有人会说,你这个例子还是不接地气啊,我怎么没见过什么程序这么写呢,好,那咋就来一个接地气的例子,看下面的代码:</p>    <pre>  <code class="language-java">AlertDialog alertDialog = new AlertDialog.Builder(this)                  .setTitle("我是标题")                  .setIcon(R.drawable.icon)                  .show();</code></pre>    <p>上面是一个Android里面警告框的例子,我们可以通过链式调用的方法将标题和图标传入,然后调用show()方法就构建了一个警告框。这个例子是不是很常见,那我们就用一个类使用建造者模式实现一下吧:</p>    <pre>  <code class="language-java">package com.codekong.my;    import javax.naming.Context;    public class MyDialog {      //警告框标题      private String title;      //警告框图标资源Id      private int iconId;      //上下文环境      private Context context;      public String getTitle() {          return title;      }        public int getIconId() {          return iconId;      }        public Context getContext() {          return context;      }        public static class Builder{          //设置默认值          private String title = "Title";          private int iconId = 0;          private Context context;          public Builder(Context context){              this.context = context;          }            public Builder setTitle(String title) {              this.title = title;              return this;          }            public Builder setIconId(int iconId) {              this.iconId = iconId;              return this;          }            //应用我们的设置          private void applyConfig(MyDialog myDialog){              myDialog.title = title;              myDialog.iconId = iconId;              myDialog.context = context;          }            public MyDialog show(){              MyDialog myDialog = new MyDialog();              applyConfig(myDialog);              return myDialog;          }        }  }</code></pre>    <p>上面的类主要涉及到以下几步:</p>    <p>1 . 创建一个类,先声明他的成员变量以及成员变量的get方法(其实这一步就是建造者模式里面的 产品角色 ,get方法是为了我们使用时可以随时查看我们自定义的产品属性)</p>    <p>2 . 定义一个静态内部类 Builder ,然后把我们产品定义的属性在静态内部类中复制一份,同时生成它的set方法(这一步呢其实就是我们的 抽象建造者角色 ,要注意的一点是为了实现链式调用,我们要让我们的set方法返回值为Builder, 同时在set方法中返回this,也就是返回本对象)</p>    <p>3 . 接着定义 applyConfig() 方法,把我们通过set方法设置的值全部赋值给我们的外部类对应的成员变量(这一步就是我们的 具体的建造者 角色)</p>    <p>4 . 最后对外提供一个 show() 方法,在其中先 new 出一个我们的MyDialog对象,然后把它传入调用applyConfig()方法,调用过后我们的myDialog对象就已经被设置属性了,我们此时就可以将这个设置过的对象传到外部供其他类使用(这一步就是我们的 导演角色 )</p>    <p>当我们使用的时候就可以通过下面代码使用:</p>    <pre>  <code class="language-java">MyDialog myDialog = new MyDialog.Builder(this)          .setTitle("我是标题")          .setIconId(R.drawable.icon)          .show();</code></pre>    <p> </p>    <p> </p>    <p>来自:http://www.jianshu.com/p/4d074224117d</p>    <p> </p>