今天看啥  ›  专栏  ›  defaultCoder

设计模式之-构建者模式

defaultCoder  · 简书  ·  · 2019-04-28 00:08

文章预览

构建者模式又叫做建造者模式,可以将一个复杂的对象的构建和表示分离,使得对象的创建更加的灵活。


或许说得比较晦涩难懂,我还是举个例子吧。

构建者就是可以这样子创建对象。


构建者模式创建对象.png

那么,这样创建对象有什么优点呢?
就是,当一个类有很多属性的时候,比如我有个朋友开发运维自动化项目时用到多年前设计的表,表结构可以说是非常可怕,有这么一两张表的字段甚至有超过150个(虽然这种表结构反人类,大家遇不到,但是说不定还是会遇到有这么几十个字段的表),该表如果映射到类属性中,就有一百五十多个属性,这样的话如果你只使用构造方法来创建该类的对象,new一个对象,或许要好几行,这样就非常不方便。

于是我们可以使用构建者模式,如上图所示可以根据需要灵活地设置属性。

既然该模式用于简化复杂对象的构造,通过例子来理解应该不难,想象一个说明程序员简历的对象。该对象的构造可能如下所示:

// 通过构造方法创建对象
Programmer programmer = new Programmer("first name", "last name", "address", "city", birthDateObject, new String[] {"Java", "SQL", "Vue", "Angular"}, new String[] {"web shop", "cxc web shop"});
// 通过setting方法创建
Programmer programmer = new Programmer();
programmer.setName("first name");
programmer.setLastName("last name");
// ...省略address,city,... 的赋值过程
programmer.setProjects(new String[] {"web shop", "cxc web shop"});

Builder允许我们通过使用将值传递给父类的内部构建器对象来清楚地分解对象构造。所以对于我们这个程序员简历的对象的创建,构建器可以像这样做:

public class BuilderTest {
 
  @Test
  public void test() {
    Programmer programmer = new Programmer.ProgrammerBuilder()
            .setFirstName("first name").setLastName("last name")
            .setCity("city").setAddress("address")
            .setLanguages(new String[] {"Java", "SQL"})
            .setProjects(new String[] {"web shop", "cxc web shop"}).build();
  }
}
 
class Programmer {
  private String firstName;
  private String lastName;
  private String address;
  private String city;
  private String[] languages;
  private String[] projects;
   
  private Programmer(String firstName, String lastName, String addr,String city, String[] languages, String[] projects) {
    this.firstName = firstName;
    this.lastName = lastName;
    this.address = addr;
    this.city = city;
    this.languages = languages;
    this.projects = projects;
  }
   
  public static class ProgrammerBuilder {
    private String firstName;
    private String lastName;
    private String address;
    private String city;
    private String[] languages;
    private String[] projects;
     
    public ProgrammerBuilder setFirstName(String firstName) {
      this.firstName = firstName;
      return this;
    }
     
    public ProgrammerBuilder setLastName(String lastName) {
      this.lastName = lastName;
      return this;
    }
     
    public ProgrammerBuilder setAddress(String address) {
      this.address = address;
      return this;
    }
     
     
    public ProgrammerBuilder setCity(String city) {
      this.city = city;
      return this;
    }
     
    public ProgrammerBuilder setLanguages(String[] languages) {
      this.languages = languages;
      return this;
    }
    public ProgrammerBuilder setProjects(String[] projects) {
      this.projects = projects;
      return this;
    }
     
    public Programmer build() {
      return new Programmer(firstName, lastName, address, city, languages, projects);
    } 
  }
   
  @Override
  public String toString() {
    return this.firstName + " "+this.lastName;
  }
   
}

可以看出,构建器后面隐藏了对象构造的复杂性。

以上是我对构建者模式的理解,这个模式的作用远不止此。以下是我认为网上可比较好的可参考资料。

以下内容转自:菜鸟教程建造者模式

模式介绍

意图:将一个复杂的构建与其表示相分离,使得同样的构建过程可以创建不同的表示。

主要解决:主要解决在软件系统中,有时候面临着"一个复杂对象"的创建工作,其通常由各个部分的子对象用一定的算法构成;由于需求的变化,这个复杂对象的各个部分经常面临着剧烈的变化,但是将它们组合在一起的算法却相对稳定。

何时使用:一些基本部件不会变,而其组合经常变化的时候。

如何解决:将变与不变分离开。

关键代码:建造者:创建和提供实例,导演:管理建造出来的实例的依赖关系。

应用实例: 1、去肯德基,汉堡、可乐、薯条、炸鸡翅等是不变的,而其组合是经常变化的,生成出所谓的"套餐"。 2、JAVA 中的 StringBuilder。

优点: 1、建造者独立,易扩展。 2、便于控制细节风险。

缺点: 1、产品必须有共同点,范围有限制。 2、如内部变化复杂,会有很多的建造类。

使用场景: 1、需要生成的对象具有复杂的内部结构。 2、需要生成的对象内部属性本身相互依赖。

注意事项:与工厂模式的区别是:建造者模式更加关注与零件装配的顺序。

UML图
构建者模式的UML图.png
构建者模式例子

场景:去肯德基点餐,我们可以认为点餐就属于一个建造订单的过程。我们点餐的顺序是无关的,点什么东西也是没有要求的,可以单点,也可以点套餐,也可以套餐加单点,但是最后一定要点确认来完成订单。

public class OrderBuilder{
    private Burger mBurger;
    private Suit mSuit;

    //单点汉堡,num为数量
    public OrderBuilder burger(Burger burger, int num){
        mBurger = burger;
    }

    //点套餐,实际中套餐也可以点多份
    public OrderBuilder suit(Suit suit, int num){
        mSuit = suit;
    }

    //完成订单
    public Order build(){
        Order order = new Order();
        order.setBurger(mBurger);
        order.setSuit(mSuit);
        return order;
    }
}

另外适用于快速失败,在 build 时可以做校验,如果不满足必要条件,则可以直接抛出创建异常,在 OkHttp3 中的 Request.Builder 中就是这样用的。

public Request build() {  
    if (url == null) throw new IllegalStateException("url == null");  
    return new Request(this);
}

例如订单要求价格至少达到 30 块:

//完成订单
public Order build(){
    Order order = new Order();
    order.setBurger(mBurger);
    order.setSuit(mSuit);
    if(order.getPrice() < 30){
        throw new BuildException("订单金额未达到30元");
    }
    return order;
}

另外,在构建时如果有必传参数和可选参数,可以为 Builder 类添加构造函数来保证必传参数不会遗漏,例如在构建一个 http 请求时, url 是必传的:

public class RequestBuilder{
    private final String mUrl;
    private Map<String, String> mHeaders = new HashMap<String, String>();

    private RequestBuilder(String url){
        mUrl = url;
    }

    public static RequestBuilder newBuilder(String url){
        return new RequestBuilder(url);
    }

    public RequestBuilder addHeader(String key, String value){
        mHeaders.put(key, value);
    }

    public Request build(){
        Request request = new Request();
        request.setUrl(mUrl);
        request.setHeaders(mHeaders);
        return request;
    }
}
………………………………

原文地址:访问原文地址
快照地址: 访问文章快照
总结与预览地址:访问总结与预览