定义
建造者模式的核心目的是通过使用多个简单对象一步步地构建出一个复杂对象
。
定义和特点
建造者(Builder)模式的定义:指将一个复杂对象的构造与它的表示分离,使同样的构建过程可以创建不同的表示,这样的设计模式被称为建造者模式。它是将一个复杂的对象分解为多个简单的对象,然后一步一步构建而成。它将变与不变相分离,即产品的组成部分是不变的,但每一部分是可以灵活选择的。
该模式的主要优点如下:
- 封装性好,构建和表示分离。
- 扩展性好,各个具体的建造者相互独立,有利于系统的解耦。
- 客户端不必知道产品内部组成的细节,建造者可以对创建过程逐步细化,而不对其它模块产生任何影响,便于控制细节风险。
其缺点如下:
- 产品的组成部分必须相同,这限制了其使用范围。
- 如果产品的内部变化复杂,如果产品内部发生变化,则建造者也要同步修改,后期维护成本较大。
建造者(Builder)模式和工厂模式的关注点不同:建造者模式注重零部件的组装过程,而工厂方法模式更注重零部件的创建过程,但两者可以结合使用。
实践
这里模拟房屋装修公司设计出一些不同风格样式的装修套餐场景,来体现建造者模式的使用方法。
很多装修公司都会提供一些套餐服务,一般会有:豪华欧式、轻奢田园和现代简约装修服务套餐等。而这些套餐的背后是不同装修材料和设计风格的组合,例如一级顶、二级顶、多乐士涂料、立邦涂料、圣象地板、德尔地板、马可波罗地砖、东鹏地砖等。按照不同的套餐价格,选取不同的品牌进行组合,最终再结合装修面积给出整体报价。
在模拟的装修材料工程中,提供了如下类。
- ceilling(吊顶材料)包:LevelOneCeiling、LevelTwoCeiling;
- coat(涂料材料)包:DuluxCoat、LiBangCoat;
- floor(地板材料)包:DerFloor、ShengXiangFloor;
- tile(地砖材料)包:DongPengTile、MarcoPoloTile。
装修材料接口
吊顶
代码语言:javascript复制public class LevelOneCeiling implements Matter {
@Override
public String scene() {
return "吊顶";
}
@Override
public String brand() {
return "装修公司自带";
}
@Override
public String model() {
return "一级顶";
}
@Override
public BigDecimal price() {
return new BigDecimal(260);
}
@Override
public String desc() {
return "造型只做低一级,只有一个层次的吊顶,一般离顶120-150cm";
}
}
代码语言:javascript复制public class LevelTwoCeiling implements Matter {
@Override
public String scene() {
return "吊顶";
}
@Override
public String brand() {
return "装修公司自带";
}
@Override
public String model() {
return "二级顶";
}
@Override
public BigDecimal price() {
return new BigDecimal(850);
}
@Override
public String desc() {
return "两个层次的吊顶";
}
}
涂料
代码语言:javascript复制public class DuluxCoat implements Matter {
@Override
public String scene() {
return "涂料";
}
@Override
public String brand() {
return "多乐士";
}
@Override
public String model() {
return "第二代";
}
@Override
public BigDecimal price() {
return new BigDecimal(719);
}
@Override
public String desc() {
return "多乐士涂料";
}
}
代码语言:javascript复制public class LiBangCoat implements Matter {
@Override
public String scene() {
return "涂料";
}
@Override
public String brand() {
return "立邦";
}
@Override
public String model() {
return "默认级别";
}
@Override
public BigDecimal price() {
return new BigDecimal(650);
}
@Override
public String desc() {
return "立邦涂料";
}
}
地板
代码语言:javascript复制public class DerFloor implements Matter {
@Override
public String scene() {
return "地板";
}
@Override
public String brand() {
return "德尔";
}
@Override
public String model() {
return "A ";
}
@Override
public BigDecimal price() {
return new BigDecimal(119);
}
@Override
public String desc() {
return "德尔地板";
}
}
代码语言:javascript复制public class ShengXiangFloor implements Matter {
@Override
public String scene() {
return "地板";
}
@Override
public String brand() {
return "圣象";
}
@Override
public String model() {
return "一级 ";
}
@Override
public BigDecimal price() {
return new BigDecimal(318);
}
@Override
public String desc() {
return "圣象地板";
}
}
地砖
代码语言:javascript复制public class DongPengTile implements Matter {
@Override
public String scene() {
return "地砖";
}
@Override
public String brand() {
return "东鹏";
}
@Override
public String model() {
return "11110";
}
@Override
public BigDecimal price() {
return new BigDecimal(102);
}
@Override
public String desc() {
return "东鹏瓷砖";
}
}
代码语言:javascript复制public class MarcoPoloTile implements Matter {
@Override
public String scene() {
return "地砖";
}
@Override
public String brand() {
return "马可波罗";
}
@Override
public String model() {
return "默认";
}
@Override
public BigDecimal price() {
return new BigDecimal(140);
}
@Override
public String desc() {
return "马可波罗瓷砖";
}
}
以上就是材料清单,接下来会通过不同的物料组合出不同的服务套餐。
建造模式使用
这里会把构建的过程交给创建者类,而创建者通过使用构建工具包构建出不同的装修套餐。
建造者模式代码工程有三个核心类,这三个核心类是建造者模式的具体实现。
与使用if…else判断方式实现逻辑相比,它额外新增了两个类,具体功能如下:
- Builder:建造者类具体的各种组装,都由此类实现。
- DecorationPackageMenu:是IMenu接口的实现类,主要承载建造过程中的填充器,相当于一套承载物料和创建者中间衔接的内容。
定义装修包接口
代码语言:javascript复制public interface IMenu {
IMenu appendCeiling(Matter matter);
IMenu appendCoat(Matter matter);
IMenu appendFloor(Matter matter);
IMenu appendTile(Matter matter);
String getDetail();
}
实现装修包接口
代码语言:javascript复制public class DecorationPackageMenu implements IMenu {
private List<Matter> list = new ArrayList<>();
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;
}
@Override
public IMenu appendCeiling(Matter matter) {
list.add(matter);
price = price.add(area.multiply(new BigDecimal("0.2")).multiply(matter.price()));
return this;
}
@Override
public IMenu appendCoat(Matter matter) {
list.add(matter);
price = price.add(area.multiply(new BigDecimal("1.4")).multiply(matter.price()));
return this;
}
@Override
public IMenu appendFloor(Matter matter) {
list.add(matter);
price = price.add(area.multiply(matter.price()));
return this;
}
@Override
public IMenu appendTile(Matter matter) {
list.add(matter);
price = price.add(area.multiply(matter.price()));
return this;
}
@Override
public String getDetail() {
StringBuilder detail = new StringBuilder("rn----------------rn");
detail.append("装修清单: rn");
detail.append("装修等级:").append(grade).append("rn");
detail.append("装修价格:").append(price.setScale(2, BigDecimal.ROUND_HALF_UP)).append("元rn");
detail.append("房屋面积:").append(area.doubleValue()).append("平方米rn");
detail.append("材料清单:rn");
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对象本身,可以非常方便地用于连续填充各种物料。同时,在填充时也会根据物料计算相应面积的报价,吊顶和涂料按照面积乘以单价计算。最后,同样提供了统一的获取装修清单的明细方法。
建造者类创建
代码语言:javascript复制public class Builder {
public IMenu levelOne(Double area) {
return new DecorationPackageMenu(area, "豪华欧式")
.appendCeiling(new LevelTwoCeiling())
.appendCoat(new DuluxCoat())
.appendCoat(new ShengXiangFloor());
}
public IMenu levelTwo(Double area) {
return new DecorationPackageMenu(area, "轻奢田园")
.appendCeiling(new LevelTwoCeiling())
.appendCoat(new LiBangCoat())
.appendTile(new MarcoPoloTile());
}
public IMenu levelThree(Double area) {
return new DecorationPackageMenu(area, "现代简约")
.appendCeiling(new LevelOneCeiling())
.appendCoat(new LiBangCoat())
.appendTile(new DongPengTile());
}
}
测试验证
代码语言:javascript复制public class TestBuilder {
public static void main(String[] args) {
Builder builder = new Builder();
System.out.println(builder.levelOne(140.52D).getDetail());
System.out.println(builder.levelTwo(98.88D).getDetail());
System.out.println(builder.levelThree(82.55D).getDetail());
}
}
总结
通过对建造者模式的使用,可以总结出选择该设计模式的条件:当一些基本材料不变,而其组合经常变化时。
此设计模式满足了单一职责原则及可复用的技术,建造者独立、易扩展、便于控制细节风险
。
缺点如下:出现特别多的物料及组合时,类的不断扩展也会造成难以维护的问题。
但这种设计模式可以把重复的内容抽象到数据库中,按照需要配置,减少大量的重复代码。
虽然设计模式能带给我们一些设计思想,但在平时的开发中如何清晰地提炼出符合此思路的建造模块是比较困难的。需要经过一些练习,不断承接更多的项目来获得经验。有时代码写得好,往往是通过复杂的业务、频繁的变化和不断的挑战,逐步积累而来的。