zoukankan      html  css  js  c++  java
  • 《重学Java设计模式》笔记——建造者模式

    1. 建造者模式可以解决什么问题

    我家里有各种形状的瓷器,盘子或者碗。虽然形状不同,但是所用的材料基本上是一样的,比如土、水、釉、彩这些基本的东西。

    但是做不同款式的瓷器,方法是不同的。假如说我现在已经写好了一段代码来生成白瓷碗和白瓷盘,正常运行起来了,这倒也没什么,可是,如果我现在要增加一个工作,制作彩绘马克杯,这样我就得修改已有的代码了。

    最简单的修改方式就是加if-else。就像是这样:

    if (type = "白瓷碗") {
       makeWhiteBowl(...);
    
    } else if (type = "白瓷盘") {
       makeWhitePlate(...);
    
    } else if (type = "彩绘马克杯") {
       makeColorCup(...);
    }
    ...
    

    Well,看起来还不很严重,那么如果你的业务越来越复杂呢?每次新添加一种type,首先就要去改代码,也许还不止这一处,这是不可忍受的,是很烂的设计。

    建造者模式,就是为了解决这种问题出现的一种设计模式。

    在GoF的《设计模式》一书中,明确的提及了建造者模式的意图:

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

    这一论断说明了这么几点:

    1. 构建过程和对象相分离,也就是说不要直接去new这个对象;
    2. 构建过程抽象出来到一个高层次上。

    回到我上面的例子,就是制造瓷器的人和瓷器相分离。客户交给匠人材料和订单,匠人自行制造交付就可以了。

    2. 建造者模式的理论

    建造者模式中会存在下面几种角色:

    • 建造者(Builder):接口,用于创建产品的各个部分的抽象;
    • ConcreteBuilder:实现Builder接口,是具体的建造者;
    • Director:这个名字看着像“导演”,其实是用于构造Builder对象的;
    • Product:各类产品,最终被Builder生产出来的东西。

    下图是GoF给出的类图:

    知道了建造者模式的基本类图结构以后,还需要知道这个模式是如何使用的,如下时序图:

    3. 具体编码实现

    在《重学Java设计模式》中的建造者模式章节提到了一个装修的例子,但是从这个例子上来看,和GoF的描述还是有一些区别的。

    还是以装修为例,书中提到了集中装修风格,其实就对应了几种ConcreteBuilder,例如田园奢华欧式、轻奢田园和现代简约。而这些建造者们都有相同的几种方法:

    • 吊顶
    • 涂墙
    • 铺地板或者地砖

    另外还有物料,比如各种品牌的装修材料等等。下面是我根据GoF的理论,对《重学Java设计模式》的建造者代码的改造。

    首先是物料,可以抽象出一个Matter接口,所有的地板,涂料都是Matter。

    /**
     * 物料的五种属性
     */
    public interface Matter {
    
        //类型,比如地板
        String scene();
    
        //品牌
        String brand();
    
        //型号
        String model();
    
        //价格
        BigDecimal price();
    
        String desc();
    }
    

    物料并非建造者模式中的核心元素,接下来就可以抽象建造者了。

    package com.example.pattern.builder.iface;
    
    public interface IBuilder {
        //装吊顶
        IBuilder appendCeiling(Matter ceiling);
        //涂墙
        IBuilder appendCoat(Matter coat);
        //铺地板
        IBuilder appendFloor(Matter floor);
        //铺地砖
        IBuilder appendTitle(Matter title);
    
        //得到装修报价单
        String getResult();
    }
    
    

    建造者要做的工作就是来料加工,也就是一个装修工人的手艺了。

    下面是对Director的定义,可以认为Director是监工吧,业主只和监工或者工头直接联系,不会给工人直接下达任务:

    package com.example.pattern.builder.builder;
    
    import com.example.pattern.builder.iface.IBuilder;
    import com.example.pattern.builder.iface.Matter;
    import lombok.Data;
    
    @Data
    public class Director {
        private IBuilder builder;
    
        private Matter ceiling;
        private Matter coat;
        private Matter floor;
        private Matter title;
    
        public Director(IBuilder builder) {
            this.builder = builder;
        }
    
        public String construct() {
            return builder.appendCeiling(ceiling).appendCoat(coat)
                    .appendFloor(floor).appendTitle(title).getResult();
        }
    
    }
    
    

    其实Director也不知道自己的物料是什么。从时序图上来看,不管是具体的建造者还是Director都是使用者(就像业主)提供的,因此使用者的代码如下:

    public class BuilderApp {
        public static void main(String[] args) {
            IBuilder europeBuilder = new EuropeBuilder(new BigDecimal(160));
            Director director = new Director(europeBuilder);
            director.setCeiling(new LevelTwoCeiling());
            director.setFloor(new MarcoPoloTitle());
            director.setCoat(new NipponCoat());
            String result = director.construct();
            System.out.println(result);
        }
    }
    

    这个结果就是计算一下装修的账单,很简单的一个示例,最终的打印是总报价。

    4. 进一步的思考

    在结城浩的《图解设计模式》一书中提到了“谁知道什么”的概念。在上面的代码中,这个概念体现了两次:

    • BuilderApp类并没有直接调用Builder,BuilderApp并不知道Builder能做什么;
    • Director也不知道自己到底调用的是哪个Builder,它使用的是一个接口。

    OOP一定是面向接口最好的,因为可以随时替换掉具体的实现,不同的类之间耦合度不高。

    在新增一个产品的时候,只需要添加一个具体的Builder就可以了,不需要修改现有代码。

    学习一种设计模式总是写这种简单的例子也感觉没什么意思。我在之前的工作中因为一些原因不能用现成的第三方包,就只能写了一个Map的生成器来做事情,我觉得这个倒是挺有建造者模式的味道的。

    首先我不需要定义Map了,因为Java.util已经提供了足够多的,而且是接口和实现分离的。

    下面需要定义一个Builder:

    package com.example.pattern.builder.maphelper;
    
    import java.util.Map;
    
    public class MapBuilder<K, V> {
        private Map<K, V> map;
    
        public MapBuilder(Map<K, V> map) {
            this.map = map;
        }
    
        public MapBuilder<K, V> put(K k, V v) {
            map.put(k, v);
            return this;
        }
    
        public Map<K, V> build() {
            return map;
        }
    }
    
    

    这个Builder更好的地方就是自己都不知道自己制造什么产品。接下来时Director的角色:

    package com.example.pattern.builder.maphelper;
    
    
    import java.util.Map;
    
    public class MapHelper {
    
        public static <K, V> MapBuilder<K, V> builder(Map<K, V> map) {
            return new MapBuilder<>(map);
        }
    
    }
    

    这简直太让人舒适了,因为调用者的代码是链式的:

    Map<String, String> map
                    = MapHelper.builder(new HashMap<String, String>()).put("foo", "bar")
                    .build();
    

    这个建造者和GoF的理论有一点差异,就是MapHelper并没有持有任何对象,没有构造方法。其实如果一定要改造也是可以的。

    Map本身是一个比较复杂的构建,因为要不停的put,代码也显得不够漂亮,链式put则显得优雅一些。而且这个Builder本身也实现了构建和表示分离的原则。

  • 相关阅读:
    windows常用命令行总结
    express安装
    MySQL去除外键关联关系
    c#实体转化
    C#之Clone
    mysql 将null转代为0(转)
    Mysql显示行号
    mysql存储过程游标加计划任务事件调度器
    mysql临时表
    Git学习笔记
  • 原文地址:https://www.cnblogs.com/wingsless/p/15726943.html
Copyright © 2011-2022 走看看