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

    建造者模式

    建造者模式(Builder Pattern)使用多个简单的对象一步一步构建成一个复杂的对象。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

    一个 Builder 类会一步一步构造最终的对象。该 Builder 类是独立于其他对象的。

    介绍

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

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

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

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

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

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

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

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

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

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

    角色

    Builder(抽象建造者):它为创建一个产品Product对象的各个部件指定抽象接口,在该接口中一般声明两类方法,一类方法是buildPartX(),它们用于创建复杂对象的各个部件;另一类方法是getResult(),它们用于返回复杂对象。Builder既可以是抽象类,也可以是接口。

    ConcreteBuilder(具体建造者):它实现了Builder接口,实现各个部件的具体构造和装配方法,定义并明确它所创建的复杂对象,也可以提供一个方法返回创建好的复杂产品对象。

    Product(产品角色):它是被构建的复杂对象,包含多个组成部件,具体建造者创建该产品的内部表示并定义它的装配过程。

    Director(指挥者):指挥者又称为导演类,它负责安排复杂对象的建造次序,指挥者与抽象建造者之间存在关联关系,可以在其construct()建造方法中调用建造者对象的部件构造与装配方法,完成复杂对象的建造。客户端一般只需要与指挥者进行交互,在客户端确定具体建造者的类型,并实例化具体建造者对象(也可以通过配置文件和反射机制),然后通过指挥者类的构造函数或者Setter方法将该对象传入指挥者类中。

    在建造者模式的定义中提到了复杂对象,那么什么是复杂对象?简单来说,复杂对象是指那些包含多个成员属性的对象,这些成员属性也称为部件或零件,如汽车包括方向盘、发动机、轮胎等部件,电子邮件包括发件人、收件人、主题、内容、附件等部件

    实例:KFC套餐

    建造者模式可以用于描述KFC如何创建套餐:套餐是一个复杂对象,它一般包含主食(如汉堡、鸡肉卷等)和饮料(如果汁、可乐等)等组成部分,不同的套餐有不同的组成部分,而KFC的服务员可以根据顾客的要求,一步一步装配这些组成部分,构造一份完整的套餐,然后返回给顾客。

     

    1)Product(产品角色)

    一个具体的产品对象。

     1 public class Meal {
     2     private String food;
     3     private String drink;
     4 
     5     public String getFood() {
     6         return food;
     7     }
     8 
     9     public void setFood(String food) {
    10         this.food = food;
    11     }
    12 
    13     public String getDrink() {
    14         return drink;
    15     }
    16 
    17     public void setDrink(String drink) {
    18         this.drink = drink;
    19     }
    20 }

    2)Builder(抽象建造者)

    创建一个Product对象的各个部件指定的抽象接口。

     1 public abstract class MealBuilder {
     2     Meal meal = new Meal();
     3     
     4     public abstract void buildFood();
     5     
     6     public abstract void buildDrink();
     7     
     8     public Meal getMeal(){
     9         return meal;
    10     }
    11 }

    3) ConcreteBuilder(具体建造者)

    实现抽象接口,构建和装配各个部件。

    A套餐:

     1 public class MealA extends MealBuilder{
     2 
     3     public void buildDrink() {
     4         meal.setDrink("可乐");
     5     }
     6 
     7     public void buildFood() {
     8         meal.setFood("薯条");
     9     }
    10 
    11 }

    B套餐:

     1 public class MealB extends MealBuilder{
     2 
     3     public void buildDrink() {
     4         meal.setDrink("柠檬果汁");
     5     }
     6 
     7     public void buildFood() {
     8         meal.setFood("鸡翅");
     9     }
    10 
    11 }

    4)Director(指挥者)

    构建一个使用Builder接口的对象。它主要是用于创建一个复杂的对象,它主要有两个作用,一是:隔离了客户与对象的生产过程,二是:负责控制产品对象的生产过程。

     1 public class KFCWaiter {
     2     private MealBuilder mealBuilder;
     3     
     4     public KFCWaiter(MealBuilder mealBuilder) {
     5         this.mealBuilder = mealBuilder;
     6     }
     7     
     8 
     9     public Meal construct(){
    10         //准备食物
    11         mealBuilder.buildFood();
    12         //准备饮料
    13         mealBuilder.buildDrink();
    14         
    15         //准备完毕,返回一个完整的套餐给客户
    16         return mealBuilder.getMeal();
    17     }
    18 }

    5)测试类(客户端类)

     1 public class Test {
     2     public static void main(String[] args) {
     3 
     4         //套餐A
     5         MealA a = new MealA();
     6         //准备套餐A的服务员
     7         KFCWaiter waiter = new KFCWaiter(a);
     8         //获得套餐
     9         Meal mealA = waiter.construct();      
    10         System.out.print("套餐A的组成部分:");
    11         System.out.println("食物:"+mealA.getFood()+";   "+"饮品:"+mealA.getDrink());
    12     }
    13 }

    输出结果:

    套餐A的组成部分:食物:薯条;   饮品:可乐

    建造者模式总结

    建造者模式的主要优点如下:

    • 在建造者模式中,客户端不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象。

    • 每一个具体建造者都相对独立,而与其他的具体建造者无关,因此可以很方便地替换具体建造者或增加新的具体建造者,用户使用不同的具体建造者即可得到不同的产品对象。由于指挥者类针对抽象建造者编程,增加新的具体建造者无须修改原有类库的代码,系统扩展方便,符合 "开闭原则"。

    • 可以更加精细地控制产品的创建过程。将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰,也更方便使用程序来控制创建过程。

    建造者模式的主要缺点如下:

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

    • 如果产品的内部变化复杂,可能会导致需要定义很多具体建造者类来实现这种变化,导致系统变得很庞大,增加系统的理解难度和运行成本。

    适用场景

    • 需要生成的产品对象有复杂的内部结构,这些产品对象通常包含多个成员属性。

    • 需要生成的产品对象的属性相互依赖,需要指定其生成顺序。

    • 对象的创建过程独立于创建该对象的类。在建造者模式中通过引入了指挥者类,将创建过程封装在指挥者类中,而不在建造者类和客户类中。

    • 隔离复杂对象的创建和使用,并使得相同的创建过程可以创建不同的产品。

    建造者模式与抽象工厂模式的比较:

    • 与抽象工厂模式相比,建造者模式返回一个组装好的完整产品,而抽象工厂模式返回一系列相关的产品,这些产品位于不同的产品等级结构,构成了一个产品族 。
    • 在抽象工厂模式中,客户端实例化工厂类,然后调用工厂方法获取所需产品对象,而在建造者模式中,客户端可以不直接调用建造者的相关方法,而是通过指挥者类来指导如何生成对象,包括对象的组装过程和建造步骤,它侧重于一步步构造一个复杂对象,返回一个完整的对象 。
    • 如果将抽象工厂模式看成汽车配件生产工厂,生产一个产品族的产品,那么建造者模式就是一个汽车组装工厂,通过对部件的组装可以返回一辆完整的汽车。

    建造者模式在Java中的应用及解读

    java.lang.StringBuilder 中的建造者模式

    StringBuilder 中的 append 方法使用了建造者模式,不过装配方法只有一个,并不算复杂,append 方法返回的是 StringBuilder 自身

    1 public final class StringBuilder extends AbstractStringBuilder implements java.io.Serializable, CharSequence {
    2     @Override
    3     public StringBuilder append(String str) {
    4         super.append(str);
    5         return this;
    6     }
    7     // ...省略...
    8 }

    StringBuilder 的父类 AbstractStringBuilder 实现了 Appendable 接口

     1 abstract class AbstractStringBuilder implements Appendable, CharSequence {
     2     char[] value;
     3     int count;
     4 
     5     public AbstractStringBuilder append(String str) {
     6         if (str == null)
     7             return appendNull();
     8         int len = str.length();
     9         ensureCapacityInternal(count + len);
    10         str.getChars(0, len, value, count);
    11         count += len;
    12         return this;
    13     }
    14 
    15     private void ensureCapacityInternal(int minimumCapacity) {
    16         // overflow-conscious code
    17         if (minimumCapacity - value.length > 0) {
    18             value = Arrays.copyOf(value,
    19                     newCapacity(minimumCapacity));
    20         }
    21     }
    22     // ...省略...
    23 }

    Appendable 接口如下

    1 public interface Appendable {
    2     Appendable append(CharSequence csq) throws IOException;
    3     Appendable append(CharSequence csq, int start, int end) throws IOException;
    4     Appendable append(char c) throws IOException;
    5 }

    我们可以看出,Appendable 为抽象建造者,定义了建造方法,StringBuilder 既充当指挥者角色,又充当产品角色,又充当具体建造者,建造方法的实现由 AbstractStringBuilder 完成,而 StringBuilder 继承了 AbstractStringBuilder

    java.lang.StringBuffer 中的建造者模式

    1 public final class StringBuffer extends AbstractStringBuilder implements java.io.Serializable, CharSequence {
    2     @Override
    3     public synchronized StringBuffer append(String str) {
    4         toStringCache = null;
    5         super.append(str);
    6         return this;
    7     }
    8     //...省略...
    9 }

    看 StringBuffer 的源码如上,它们的区别就是: StringBuffer 中的 append 加了 synchronized 关键字,所以StringBuffer 是线程安全的,而 StringBuilder 是非线程安全的

    StringBuffer 中的建造者模式与 StringBuilder 是一致的

    mybatis 中的建造者模式

    org.apache.ibatis.session 包下的 SqlSessionFactoryBuilder 类里边很多重载的 build 方法,返回值都是 SqlSessionFactory,除了最后两个所有的 build最后都调用下面这个 build 方法

    public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
            SqlSessionFactory var5;
            try {
                XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
                var5 = this.build(parser.parse());
            } catch (Exception var14) {
                throw ExceptionFactory.wrapException("Error building SqlSession.", var14);
            } finally {
                ErrorContext.instance().reset();
                try {
                    reader.close();
                } catch (IOException var13) {
                    ;
                }
            }
            return var5;
        }

    其中最重要的是 XMLConfigBuilder 的 parse 方法,代码如下

     1 public class XMLConfigBuilder extends BaseBuilder {
     2     public Configuration parse() {
     3         if (this.parsed) {
     4             throw new BuilderException("Each XMLConfigBuilder can only be used once.");
     5         } else {
     6             this.parsed = true;
     7             this.parseConfiguration(this.parser.evalNode("/configuration"));
     8             return this.configuration;
     9         }
    10     }
    11 
    12     private void parseConfiguration(XNode root) {
    13         try {
    14             Properties settings = this.settingsAsPropertiess(root.evalNode("settings"));
    15             this.propertiesElement(root.evalNode("properties"));
    16             this.loadCustomVfs(settings);
    17             this.typeAliasesElement(root.evalNode("typeAliases"));
    18             this.pluginElement(root.evalNode("plugins"));
    19             this.objectFactoryElement(root.evalNode("objectFactory"));
    20             this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
    21             this.reflectorFactoryElement(root.evalNode("reflectorFactory"));
    22             this.settingsElement(settings);
    23             this.environmentsElement(root.evalNode("environments"));
    24             this.databaseIdProviderElement(root.evalNode("databaseIdProvider"));
    25             this.typeHandlerElement(root.evalNode("typeHandlers"));
    26             this.mapperElement(root.evalNode("mappers"));
    27         } catch (Exception var3) {
    28             throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + var3, var3);
    29         }
    30     }
    31     // ...省略...
    32 }

    parse 方法最终要返回一个 Configuration 对象,构建 Configuration 对象的建造过程都在 parseConfiguration 方法中,这也就是 Mybatis 解析 XML配置文件 来构建 Configuration对象的主要过程

    所以 XMLConfigBuilder 是建造者 SqlSessionFactoryBuilder 中的建造者,复杂产品对象分别是 SqlSessionFactory 和 Configuration

  • 相关阅读:
    栈和堆的区别【个人总结】
    理解堆与栈
    javacript属性
    Reapeater CommandName ,CommandArgument
    FormsAuthentication.HashPasswordForStoringInConfigFile(str1, str2);
    文件上传处理
    GetJson
    js内置对象
    Debug
    [转]关于一些SPFA的标程
  • 原文地址:https://www.cnblogs.com/xiaojiesir/p/11065977.html
Copyright © 2011-2022 走看看