建造者模式介绍
建造者模式的核心是通过使用多个简单对象一步步构建出一个复杂对象。例如,《英雄联盟》游戏的初始化界面有道路、树木、野怪和防御塔等。换一个场景选择其他模式时,同样会建设道路、树木、野怪和守卫塔等,但是他们的摆放位置和大小各有不同。这种初始化游戏元素的场景就可以使用建造者模式。
这种根据相同的物料、不同的组装方式产生的具体内容,就是建造者模式的最终意图,即将一个复杂的构建与其表示分离,用同样的构建过程可以创建不同的表示。
建造者模式的简单使用
模拟房屋装修公司设计出的一些不同风格样式的装修套餐场景,来体现建造者模式的使用方法。
需求:很多公司都会提供一些套餐服务,一般会有:豪华欧式、轻奢田园和现代简约装修服务套餐。而这些套餐的背后是不同装修材料和设计风格的组和,例如一级顶、二级顶、多乐士涂料、立邦涂料、圣象地板、德尔地板、马可波罗地砖、东鹏地砖等。按照不同的套餐价格,选取不同的品牌进行组合,最终再结合装修面积给出整体报价。
下面模拟装修公司退出的一系列装修服务套餐,按照不同的价格组合品牌,并介绍使用建造者模式实现这一需求。
材料装修接口
1 2 3 4 5 6 7 8 9 10 11 12 13
| package com.bestrookie.interfaces; import java.math.BigDecimal;
public interface Matter { String scene(); String brand(); String model(); BigDecimal price(); String desc(); }
|
吊顶材料
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| package com.bestrookie.design.ceiling; import com.bestrookie.interfaces.Matter; import java.math.BigDecimal;
public class LevelOneCeiling implements Matter { public String scene() { return "吊顶"; } public String brand() { return "装修公司自带"; } public String model() { return "一级顶"; } public BigDecimal price() { return new BigDecimal(260); } public String desc() { return "造型只做低一级,只有一个层次的吊顶,一般离顶120~150mm"; } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| package com.bestrookie.design.ceiling; import com.bestrookie.interfaces.Matter; import java.math.BigDecimal;
public class LevelTwoCeiling implements Matter { public String scene() { return "吊顶"; }
public String brand() { return "装修公司自带"; }
public String model() { return "二级顶"; }
public BigDecimal price() { return new BigDecimal(850); }
public String desc() { return "两个层次的吊顶,二级吊顶高度一般往下吊20cm,如果层高很高,则可以增加每级的厚度"; } }
|
涂料材料
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| package com.bestrookie.design.coat; import com.bestrookie.interfaces.Matter; import java.math.BigDecimal;
public class DuluxCoat implements Matter { public String scene() { return "涂料"; } public String brand() { return "多乐土"; } public String model() { return "第二代"; } public BigDecimal price() { return new BigDecimal(719); } public String desc() { return "多乐土描述"; } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| package com.bestrookie.design.coat; import com.bestrookie.interfaces.Matter; import java.math.BigDecimal;
public class LiBangCoat implements Matter { public String scene() { return "涂料"; } public String brand() { return "立邦"; } public String model() { return "默认级别"; } public BigDecimal price() { return new BigDecimal(650); } public String desc() { return "立邦描述"; } }
|
地板材料
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| package com.bestrookie.design.floor;
import com.bestrookie.interfaces.Matter;
import java.math.BigDecimal;
public class DerFloor implements Matter {
public String scene() { return "地板"; }
public String brand() { return "德尔"; }
public String model() { return "A+"; }
public BigDecimal price() { return new BigDecimal(119); }
public String desc() { return "德尔描述"; } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| package com.bestrookie.design.floor;
import com.bestrookie.interfaces.Matter;
import java.math.BigDecimal;
public class ShengXiangFloor implements Matter { public String scene() { return "地板"; }
public String brand() { return "圣象"; }
public String model() { return "一级"; }
public BigDecimal price() { return new BigDecimal(318); }
public String desc() { return "圣象desc"; } }
|
地砖材料
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| package com.bestrookie.design.tile;
import com.bestrookie.interfaces.Matter;
import java.math.BigDecimal;
public class DongPengTile implements Matter { public String scene() { return "地砖"; }
public String brand() { return "东鹏瓷砖"; }
public String model() { return "10001"; }
public BigDecimal price() { return new BigDecimal(102); }
public String desc() { return "东鹏desc"; } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| package com.bestrookie.design.tile;
import com.bestrookie.interfaces.Matter;
import java.math.BigDecimal;
public class MarcoPoloTile implements Matter {
public String scene() { return "地砖"; }
public String brand() { return "马可波罗"; }
public String model() { return "默认"; }
public BigDecimal price() { return new BigDecimal(140); }
public String desc() { return "马可波罗desc"; } }
|
以上是本次装修公司所提供的装修配置单。接下来会通过不同的物料组合出不同的服务套餐。
违背设计模式实现
俗话说的好,没有if…else解决不了的逻辑,不行就再加一行
先看看最简单的实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
| package com.bestrookie.design; import com.bestrookie.design.ceiling.LevelOneCeiling; import com.bestrookie.design.ceiling.LevelTwoCeiling; import com.bestrookie.design.coat.DuluxCoat; import com.bestrookie.design.coat.LiBangCoat; import com.bestrookie.design.floor.ShengXiangFloor; import com.bestrookie.design.tile.DongPengTile; import com.bestrookie.design.tile.MarcoPoloTile; import com.bestrookie.interfaces.Matter; import java.math.BigDecimal; import java.util.ArrayList; import java.util.List;
public class DecorationPackgeController { public String getMatterList(BigDecimal area, Integer level){ List<Matter> list = new ArrayList<Matter>(); BigDecimal price = BigDecimal.ZERO; if (1 == level){ LevelOneCeiling levelOneCeiling = new LevelOneCeiling(); DuluxCoat duluxCoat = new DuluxCoat(); ShengXiangFloor shengXiangFloor = new ShengXiangFloor(); list.add(levelOneCeiling); list.add(duluxCoat); list.add(shengXiangFloor); price = price.add(area.multiply(new BigDecimal("0.2")).multiply(levelOneCeiling.price())); price = price.add(area.multiply(new BigDecimal("1.4")).multiply(duluxCoat.price())); price = price.add(area.multiply(shengXiangFloor.price())); } if (2 == level){ LevelTwoCeiling levelTwoCeiling = new LevelTwoCeiling(); LiBangCoat liBangCoat = new LiBangCoat(); MarcoPoloTile marcoPoloTile = new MarcoPoloTile(); list.add(levelTwoCeiling); list.add(liBangCoat); list.add(marcoPoloTile); price = price.add(area.multiply(new BigDecimal("0.2")).multiply(levelTwoCeiling.price())); price = price.add(area.multiply(new BigDecimal("1.4")).multiply(liBangCoat.price())); price = price.add(area.multiply(marcoPoloTile.price())); } if (3 == level){ LevelOneCeiling levelOneCeiling = new LevelOneCeiling(); LiBangCoat liBangCoat = new LiBangCoat(); DongPengTile dongPengTile = new DongPengTile(); list.add(levelOneCeiling); list.add(liBangCoat); list.add(dongPengTile); price = price.add(area.multiply(new BigDecimal("0.2")).multiply(levelOneCeiling.price())); price = price.add(area.multiply(new BigDecimal("1.4")).multiply(liBangCoat.price())); price = price.add(area.multiply(dongPengTile.price())); } StringBuilder detail = new StringBuilder("\r\n-------------------------------------\r\n" + "装修清单"+"\r\n" + "装修等级:" + level + "\r\n" + "装修价格:" + price.setScale(2,BigDecimal.ROUND_HALF_UP)+ "元"+"\r\n" + "房屋面积:" + area.doubleValue()+" 平方米"+ "\r\n" + "材料清单:\r\n"); for (Matter matter : list) { detail.append(matter.scene()).append(": ").append(matter.brand()).append("、") .append(matter.model()).append("、平方米价格:").append(matter.price()).append("元。 \n"); } return detail.toString(); } }
|
逻辑清晰可见,不具有复杂的代码结构,也不具有良好的扩展性,如果应对非常简单的业务,还是可以使用的。但是如果业务不断的扩展,代码持续增加,最终会难以维护。
建造者模式重构代码
建造者模式代码工程有三个核心类,这三个核心类是建造者模式的具体实现。与使用if…else判断方式实现逻辑相比,它额外新增了两个类
- Builder:建造者类具体的各种组装,都由此类实现
- DecorationPackageMenu:是IMenu接口的实现类,主要承载建造过程中的填充器,相当于一套承载物料和创建者中间衔接的内容。
定义装修包接口
1 2 3 4 5 6 7 8 9 10 11 12 13
| package com.bestrookie.builder; import com.bestrookie.interfaces.Matter;
public interface IMenu { IMenu appendCeiling(Matter matter); IMenu appendCoat(Matter matter); IMenu appendFloor(Matter matter); IMenu appendTile(Matter matter); String getDetail(); }
|
接口类定义了填充吊顶、涂料、地板、地砖各种材料的方法,以及最终提供获取全部明细的方法
实现装修包接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
| package com.bestrookie.builder;
import com.bestrookie.interfaces.Matter;
import java.math.BigDecimal; import java.util.ArrayList; import java.util.List;
public class DecorationPackageMenu implements IMenu{ private List<Matter> list = new ArrayList<Matter>(); private BigDecimal price = BigDecimal.ZERO; private BigDecimal area; private String grade; private DecorationPackageMenu(){
} public DecorationPackageMenu(Double area,String grade){ this.area = new BigDecimal(area); this.grade = grade; } public IMenu appendCeiling(Matter matter) { list.add(matter); price = price.add(area.multiply(new BigDecimal("0.2")).multiply(matter.price())); return this; }
public IMenu appendCoat(Matter matter) { list.add(matter); price = price.add(area.multiply(new BigDecimal("1.4")).multiply(matter.price())); return this; }
public IMenu appendFloor(Matter matter) { list.add(matter); price = price.add(area.multiply(matter.price())); return this; }
public IMenu appendTile(Matter matter) { list.add(matter); price = price.add(area.multiply(matter.price())); return this; }
public String getDetail() { StringBuilder detail = new StringBuilder("\r\n-------------------------------------\r\n" + "装修清单"+"\r\n" + "装修等级:" + grade + "\r\n" + "装修价格:" + price.setScale(2,BigDecimal.ROUND_HALF_UP)+ "元"+"\r\n" + "房屋面积:" + area.doubleValue()+" 平方米"+ "\r\n" + "材料清单:\r\n"); for (Matter matter : list) { detail.append(matter.scene()).append(": ").append(matter.brand()).append("、") .append(matter.model()).append("、平方米价格:").append(matter.price()).append("元。 \n"); } return detail.toString(); } }
|
在装修包的视线中,每一种方法都返回了this对象本身,可以非常方便地用于连续填充各种物料。同时,在填充时也会根据物料计算相应面积的报价,吊顶和涂料按照面积乘以单价计算。最后,同样提供了统一的获取装修清单的明细方法。
建造者类创建
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| package com.bestrookie.builder; import com.bestrookie.design.ceiling.LevelOneCeiling; import com.bestrookie.design.ceiling.LevelTwoCeiling; import com.bestrookie.design.coat.DuluxCoat; import com.bestrookie.design.coat.LiBangCoat; import com.bestrookie.design.floor.ShengXiangFloor; import com.bestrookie.design.tile.DongPengTile; import com.bestrookie.design.tile.MarcoPoloTile;
public class Builder { public IMenu levelOne(Double area){ return new DecorationPackageMenu(area,"欧式豪华").appendCeiling(new LevelOneCeiling()) .appendCoat(new DuluxCoat()) .appendFloor(new ShengXiangFloor()); } public IMenu levelTwo(Double area){ return new DecorationPackageMenu(area,"轻奢田园").appendCeiling(new LevelTwoCeiling()) .appendCoat(new LiBangCoat()) .appendCoat(new MarcoPoloTile()); } public IMenu levelThree(Double area){ return new DecorationPackageMenu(area,"现代简约").appendCeiling(new LevelOneCeiling()) .appendCoat(new LiBangCoat()) .appendCoat(new DongPengTile()); }
}
|
总结
通过上面对建造者模式的使用,可以总结出选择该设计模式的条件:当一些基本材料不变,而其组合经常变化时。此设计模式满足了单一职责原则及可复用的技术,建造者独立、易扩展、便于控制细节风险。