建造者模式
定义
将一个复杂对象的构建与其表示相分离,使得同样的构建过程(稳定)可以创建不同的表示(变化)
目的
将复杂对象的构造与其表示分开,以便同一构造过程可以创建不同的表示。
动机
在软件系统中,有时候面临一个"复杂对象"的创建工作,其通常由各个部分的子对象用一定算法构成;
由于需求的变化,这个复杂对象的各个部分经常面临着剧烈的变化,但是将它们组合到一起的算法却相对稳定。
如何应对种变化呢?如何提供一种"封装机制"来隔离出"复杂对象的各个部分"的变化,
从而保持系统中的"稳定构建算法"不随需求的改变而改变?
通俗的说
允许你创建不同口味的对象同时避免构造器污染。当一个对象可能有几种口味,或者一个对象的创建涉及到很多步骤时会很有用。
维基百科
建造者模式是一种对象创建的软件设计模式,旨在为伸缩构造器反模式寻找一个解决方案。
伸缩构造器反模式
我们肯定都见过像下面这样的构造器:
public Hero(Profession profession, String name, HairType hairType, HairColor hairColor, Armor armor, Weapon weapon) {
}
就像你看到的构造器参数的数量很快就会失控同时参数的排列方式可能变得难以理解。
另外,如果您将来希望添加更多选项,则此参数列表可能会继续增长。这就被称伸缩构造器反模式。
编程示例
角色扮演游戏的角色生成器。
最简单的选择是让计算机为你创建角色。但是如果你想选择一些像专业,性别,发色等角色细节时,
这个角色生成就变成了一个渐进的过程。当所有选择完成时,该过程也将完成。
步骤1.创建武器类
public enum Weapon {
DAGGER, SWORD, AXE, WARHAMMER, BOW;
@Override
public String toString() {
return name().toLowerCase();
}
}
步骤2.创建头发相关类
public enum HairColor {
WHITE,
BLOND,
RED,
BROWN,
BLACK;
@Override
public String toString() {
return name().toLowerCase();
}
}
public enum HairType {
BALD("bald"),
SHORT("short"),
CURLY("curly"),
LONG_STRAIGHT("long straight"),
LONG_CURLY("long curly");
private final String title;
HairType(String title) {
this.title = title;
}
@Override
public String toString() {
return title;
}
}
步骤3.创建职业类
public enum Profession {
WARRIOR, THIEF, MAGE, PRIEST;
@Override
public String toString() {
return name().toLowerCase();
}
}
步骤4.创建服装类
public enum Armor {
CLOTHES("clothes"),
LEATHER("leather"),
CHAIN_MAIL("chain mail"),
PLATE_MAIL("plate mail");
private final String title;
Armor(String title) {
this.title = title;
}
@Override
public String toString() {
return title;
}
}
步骤5.使用建造模式创建英雄类
public class Hero {
private final Profession profession;
private final String name;
private final HairType hairType;
private final HairColor hairColor;
private final Armor armor;
private final Weapon weapon;
private Hero(Builder builder) {
this.profession = builder.profession;
this.name = builder.name;
this.hairColor = builder.hairColor;
this.hairType = builder.hairType;
this.weapon = builder.weapon;
this.armor = builder.armor;
}
public Profession getProfession() {
return profession;
}
public String getName() {
return name;
}
public HairType getHairType() {
return hairType;
}
public HairColor getHairColor() {
return hairColor;
}
public Armor getArmor() {
return armor;
}
public Weapon getWeapon() {
return weapon;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("This is a ")
.append(profession)
.append(" named ")
.append(name);
if (hairColor != null || hairType != null) {
sb.append(" with ");
if (hairColor != null) {
sb.append(hairColor).append(' ');
}
if (hairType != null) {
sb.append(hairType).append(' ');
}
sb.append(hairType != HairType.BALD ? "hair" : "head");
}
if (armor != null) {
sb.append(" wearing ").append(armor);
}
if (weapon != null) {
sb.append(" and wielding a ").append(weapon);
}
sb.append('.');
return sb.toString();
}
/**
* The builder class.
*/
public static class Builder {
private final Profession profession;
private final String name;
private HairType hairType;
private HairColor hairColor;
private Armor armor;
private Weapon weapon;
/**
* Constructor.
*/
public Builder(Profession profession, String name) {
if (profession == null || name == null) {
throw new IllegalArgumentException("profession and name can not be null");
}
this.profession = profession;
this.name = name;
}
public Builder withHairType(HairType hairType) {
this.hairType = hairType;
return this;
}
public Builder withHairColor(HairColor hairColor) {
this.hairColor = hairColor;
return this;
}
public Builder withArmor(Armor armor) {
this.armor = armor;
return this;
}
public Builder withWeapon(Weapon weapon) {
this.weapon = weapon;
return this;
}
public Hero build() {
return new Hero(this);
}
}
}
步骤6.创建测试类BuilderTest
public class BuilderTest {
public static void main(String[] args) {
Hero mage = new Hero.Builder(Profession.MAGE, "Riobard")
.withHairColor(HairColor.BLACK)
.withWeapon(Weapon.DAGGER)
.build();
System.out.println(mage.toString());
Hero warrior = new Hero.Builder(Profession.WARRIOR, "Amberjill")
.withHairColor(HairColor.BLOND)
.withHairType(HairType.LONG_CURLY).withArmor(Armor.CHAIN_MAIL).withWeapon(Weapon.SWORD)
.build();
System.out.println(warrior.toString());
Hero thief = new Hero.Builder(Profession.THIEF, "Desmond")
.withHairType(HairType.BALD)
.withWeapon(Weapon.BOW)
.build();
System.out.println(thief.toString());
}
}
运行日志:
This is a mage named Riobard with black hair and wielding a dagger.
This is a warrior named Amberjill with blond long curly hair wearing chain mail and wielding a sword.
This is a thief named Desmond with bald head and wielding a bow.
真谛(找到变化,封装变化)
在上述代码示例中我们可以将代码分为稳定部分和变化部分。
稳定部分是指那些不太可能发生变化的代码,而变化部分则是指那些可能会根据具体需求而变化的代码。
稳定部分
- 稳定的接口和抽象
Hero类中的getProfession(), getName(), getHairType(), getHairColor(), getArmor(), getWeapon()等访问器方法是稳定的,因为它们提供了对英雄属性的标准访问方式,这些方法不太可能发生变化。
Builder类的build()方法是稳定的,因为它提供了构建Hero对象的标准方式。尽管内部实现可能会有所不同,但build()方法本身通常保持不变。 - 稳定的结构
Hero类是一个最终类(final),其构造函数是私有的,这确保了其不可变性,即类的结构是稳定的,不能被继承或修改。
Builder类通常包含与Hero类相同的属性,但它们的属性是可变的,允许在构建过程中设置不同的值。这种结构是稳定的,因为它允许灵活的构建过程。 - 稳定的逻辑
toString()方法在Hero类中是一个稳定的实现,它根据英雄的属性生成描述字符串。这个逻辑不太可能改变,因为它反映了Hero对象的固有特性。
变化部分
- 可变的属性
Builder类中的属性(如hairType, hairColor, armor, weapon等)是变化的部分,因为这些属性可以在构建Hero对象时通过不同的值来设置。 - 可扩展性
建造者模式允许在不修改现有代码的情况下添加新的属性或方法。例如,如果将来需要为Hero类添加新的装备或技能,可以通过在Builder类中添加新的方法和属性来实现,而不需要修改Hero类本身。 - 客户端代码
使用Builder类来构建Hero对象的客户端代码是变化的部分。根据应用的不同需求,客户端代码可能会创建具有不同属性和配置的Hero对象。
总体来说,稳定部分主要包括接口、抽象、结构和逻辑,这些都是不易发生变化的部分。
而变化部分主要涉及到具体实现和客户端代码,这些部分可能会根据需求的不同而有所变化。
建造者模式的优点在于它允许在不破坏稳定性的情况下进行灵活的变化和扩展。
本质
生成器模式的本质:分离整体构建算法和部件构造
适用场景
如遇以下情况,可用建造者模式
- 创建复杂对象的算法应独立于组成对象的零件及其组装方式
- 构造过程必须允许所构造的对象具有不同的表示形式
案例
java.lang.StringBuilder
java.lang.StringBuffer
java.lang.Appendable
要点总结
- Builder模式主要用于"分布构建一个复杂的对象",在这其中"分步骤"是一个稳定的算法,而复杂对象的各个部分则经常变化。
- 变化点在哪里,封装哪里;Builder模式主要在于应对"复杂对象各个部分"的频繁需求变动。其缺点在于难以应对"分步骤构建算法"
的需求变动。