引例
需求:建造房子,建造过程包括打地基、砌墙和封顶。房子各式各样,包括平房和别墅,编程模拟实现。
工厂模式把细节封装起来,建造者模式可根据配置创建不同的对象
一般解法
思路:定义房子类,包括建房的方法,然后定义平房和别墅继承房子类,重写相关方法。
类图:
代码:
AbstractHouse(房子)
public abstract class AbstractHouse { public abstract void buildBasic();//打地基 public abstract void buildWalls();//砌墙 public abstract void buildRoof();//封顶 public void build() { //建房 buildBasic(); //三个步骤(注意顺序) buildWalls(); buildRoof(); } }
Bungalow(平房)
public class Bungalow extends AbstractHouse{ public void buildBasic() { System.out.println("平房打地基"); } public void buildWalls() { System.out.println("平房砌墙"); } public void buildRoof() { System.out.println("平房封顶"); } }
Villa (别墅)
public class Villa extends AbstractHouse{ public void buildBasic() { System.out.println("别墅打地基"); } public void buildWalls() { System.out.println("别墅砌墙"); } public void buildRoof() { System.out.println("别墅封顶"); } }
客户端调用
public class Client { public static void main(String[] args) { Bungalow bungalow = new Bungalow(); bungalow.build(); Villa villa = new Villa(); villa.build(); } }
运行结果:
缺点:
程序不好扩展和维护,把产品和创建产品过程封装在一起,耦合性高。
解耦产品和创建产品过程=》建造者模式
建造者模式
建造者模式(Builder Pattern)又叫生成器模式,是一种对象构建模式。它可以将复杂对象的建造过程抽象出来(抽象类别),使这个抽象过程的不同实现方法可以构造出不同表现(属性)的对象。
一步一步创建一个复杂的对象,允许用户只通过指定复杂对象的类型和内容就可以构建它们,而不需要知道内部的具体构建细节
类图:
- Product(产品): 具体的产品对象
- Builder(抽象建造者): 创建一个产品对象的各个部件指定的接口/抽象类。
- ConcreteBuilder(具体建造者): 实现接口,构建和装配各个部件。
- Director(指挥者): 构建一个使用Builder接口的对象,负责控制产品对象的生产过程,隔离了客户与对象的生产过程
建造者模式解法
代码:
House类(Product产品)
package java设计模式.Builder.improve; //产品--->Produce public class House { private String baise; private String wall; private String roof; public String getBaise() { return baise; } public void setBaise(String baise) { this.baise = baise; } public String getWall() { return wall; } public void setWall(String wall) { this.wall = wall; } public String getRoof() { return roof; } public void setRoof(String roof) { this.roof = roof; } }
HouseBuilder类(Builder抽象建造者)
public abstract class HouseBuilder { protected House house = new House(); //将建造的流程写好, 抽象的方法 public abstract void buildBasic(); public abstract void buildWalls(); public abstract void buildRoof(); //建造房子好, 将产品(房子) 返回 public House buildHouse() { return house; } }
Bungalow类(ConcreteBuilder具体建造者A)
public class Bungalow extends HouseBuilder { public void buildBasic() { System.out.println("平房打地基"); } public void buildWalls() { System.out.println("平房砌墙"); } public void buildRoof() { System.out.println("平房封顶"); } }
Villa类(ConcreteBuilder具体建造者B)
public class Villa extends HouseBuilder { public void buildBasic() { System.out.println("别墅打地基"); } public void buildWalls() { System.out.println("别墅砌墙"); } public void buildRoof() { System.out.println("别墅封顶"); } }
HouseDirector类(Director指挥者)
public class HouseDirector { //聚合 housebuilder HouseBuilder houseBuilder = null; //构造器传入 houseBuilder public HouseDirector(HouseBuilder houseBuilder) { this.houseBuilder = houseBuilder; } //通过setter 传入 houseBuilder public void setHouseBuilder(HouseBuilder houseBuilder) { this.houseBuilder = houseBuilder; } //如何处理建造房子的流程,交给指挥者 public House constructHouse() { houseBuilder.buildBasic(); houseBuilder.buildWalls(); houseBuilder.buildRoof(); return houseBuilder.buildHouse(); } }
客户端调用
public class Client { public static void main(String[] args) { //盖平房 Bungalow commonHouse = new Bungalow(); //准备创建房子的指挥者 HouseDirector houseDirector = new HouseDirector(commonHouse); //完成盖房子,返回产品(普通房子) House house = houseDirector.constructHouse(); System.out.println("--------------------------"); //盖别墅 Villa highBuilding = new Villa(); //重置建造者 houseDirector.setHouseBuilder(highBuilding); //完成盖房子,返回产品(高楼) houseDirector.constructHouse(); } }
运行结果:
建造者模式在JDK的应用和源码分析
案例
理论总是难以理解的,现在通过案例分析问题,一步步了解使用建造者模式的好处
【案例】好好看一下这个案例
KFC套餐假如目前KFC里面有很多个套餐> 在套餐里面有必点,也有选点,然后每个单品又有大小之分> 必点:汉堡(hamburger),薯条(chips)> 选点:鸡腿(chicken),可乐(cola),披萨(pizza)
【用Java代码模拟场景】
我们如何构成这么多套餐实例呢?
我们不使用建造者模式也能构建代码,但是建造者模式会让代码看上去更装逼,代码到后期更结构化更容易维护和拓展
首先构建这个实体类`KFC`
public class KFC { //套餐必点 private String hamburger; private String chips; //套餐选点 private String chicken; private String cola; private String pizza; }
我们的想法是不是折叠构造函数来创建实例,下面来尝试一下
public class KFC{ //省略了上面的属性..... //必点套餐A public KFC(String hamburger,String chips){ this(hamburger,chips,null,null,null); } A //套餐B public KFC(String hamburger,String chips,String chicken){ this(hamburger,chips,chicken,null,null); } //套餐C public KFC(String hamburger,String chips,String chicken,String cola){ this(hamburger,chips,chicken,cola,null); } //......还有好多种组合方式,你会发现使用折叠构造函数的方法十分复杂 //全选 public KFC(String hamburger,String chips,String chicken,String cola,String pizza){ this.hamburger = hamburger; this.chips = chips; this.chicken = chicken; this.cola = cola; this.pizza = pizza; } }
我们会发现使用折叠构造函数的方式很复杂,很恶心,代码看都不想看
那么有人会想,我可以使用`set`方法来创建,我只要一个必点构造就好了,那继续模拟咯
解释
public class KFC{ //.....省略了属性 //必点 public KFC(String hamburger,String chips){ this.hamburger = hamburger; this.chips = chips; } //set方法 public void setChicken(String chicken) { this.chicken = chicken; } public void setCola(String cola) { this.cola = cola; } public void setPizza(String pizza) { this.pizza = pizza; } //实例化对象,你会发现这种方式就友好很多 public static void main(String[] args) { KFC kfc = new KFC("大汉堡","大薯条"); //加小份可乐 kfc.setCola("小可乐"); //加个鸡腿 kfc.setChicken("大鸡腿"); System.out.println(kfc); } }
你会发现使用`set`方式就友好了很多
这个虽然友好了很多,但是也有点小毛病,就是你**set太随意了,我可能这个套餐里面没有这个单品,而使用set的人却不知道**,造成错误的套餐出现!。
为了解决上面的两种问题:一种设计模式解决一类问题,所以**建造者模式**就出现了
> 拿其中两个套餐举例>> 套餐A:汉堡,薯条,大鸡腿> 套餐B:汉堡,薯条,小鸡腿,小可乐,小披萨>> 其中薯条和汉堡可大可小,并且必须有,> 其它的都为固定大小,但是你可以选择有或没有
- 产品(KFC)
public class KFC { //套餐必点 private String hamburger; private String chips; //套餐选点 private String chicken; private String cola; private String pizza; //必点 public KFC(String hamburger,String chips){ this.hamburger = hamburger; this.chips = chips; } //set方法 public void setChicken(String chicken) { this.chicken = chicken; } public void setCola(String cola) { this.cola = cola; } public void setPizza(String pizza) { this.pizza = pizza; } }
- Builder
定义一个接口,表明需要建造什么,得到什么
public interface Builder { void setChicken(); void setCola(); void setPizza(); KFC getKFC(); }
- ConcreteBuilder:
此时应该注意,这个时候还没有生产套餐,只是定义套餐
套餐A
public class ConcreteBuilder1 implements Builder { private KFC kfc; //这一步非常重要 public ConcreteBuilder1(String hamburger,String chips){ kfc = new KFC(hamburger,chips); } public void setChicken() { kfc.setChicken("大鸡腿"); } public void setCola() { kfc.setCola(null); System.out.println("套餐A里面没有可乐"); } public void setPizza() { kfc.setPizza(null); System.out.println("套餐A里面没有披萨"); } public KFC getKFC() { return kfc; } }
套餐B
public class ConcreteBuilder2 implements Builder { private KFC kfc; //这一步非常重要 public ConcreteBuilder2(String hamburger,String chips){ kfc = new KFC(hamburger,chips); } public void setChicken() { kfc.setChicken("小鸡腿"); } public void setCola() { kfc.setCola("小可乐"); } public void setPizza() { kfc.setPizza("小披萨"); } public KFC getKFC() { return kfc; } }
Director:
真正的执行者,这里把他当作服务员,此时你像服务员点餐
public class Director { public KFC build(Builder builder){ //套餐里面我只选了鸡腿和可乐 builder.setChicken(); builder.setCola(); return builder.getKFC(); } }
public class BuilderTest { public static void main(String[] args) { //套餐A System.out.println("======套餐A======"); Builder concreteBuilder1 = new ConcreteBuilder1("大汉堡", "小薯条"); KFC kfc1 = new Director().build(concreteBuilder1); System.out.println(kfc1); //套餐B System.out.println("======套餐B======"); Builder concreteBuilder2 = new ConcreteBuilder2("小汉堡", "小薯条"); KFC kfc2 = new Director().build(concreteBuilder2); System.out.println(kfc2); } }
输出
到了这里你还是会觉得有点麻烦,你会发现,单品可有可无的选择上面你十分的被动,代码看上去也很怪,如果你下次想全部单品先选上,再去选套餐的时候,你又要新建一个新的指导者。
```
我觉得普通的建造者模式不适合参数的可有可无的选择,**普通的建造者模式更侧重调控次序**,在有些情况下需要简化系统结构
简化版的建造者模式
这个时候简化版的建造者模式站出来了
**采用链式编程的方式**
这种模式更加灵活,更加符合定义
既然Director是变化的,并且其实在生活中我们自己本身就是Director,所以这个时候我们可以**把Director这个角色去掉**,因为我们自身就是指导者
* 产品(product)
public class KFC { //套餐必点 private String hamburger; private String chips; //套餐选点 private String chicken; private String cola; private String pizza; public KFC(String hamburger,String chips){ this.hamburger = hamburger; this.hamburger = chips; } public void setChicken(String chicken) { this.chicken = chicken; } public void setCola(String cola) { this.cola = cola; } public void setPizza(String pizza) { this.pizza = pizza; }
- 抽象建造者(builder)
public abstract class Builder { abstract Builder setChicken(); abstract Builder setCola(); abstract Builder setPizza(); abstract KFC getKFC(); }
- 具体建造者(ConcreteBuilder)
public class ConcreteBuilder extends Builder { KFC kfc; public ConcreteBuilder(String hamburger,String chips){ kfc = new KFC(hamburger,chips); } Builder setChicken() { kfc.setChicken("鸡腿"); return this; } Builder setCola() { kfc.setCola("可乐"); return this; } Builder setPizza() { kfc.setPizza("披萨"); return this; } KFC getKFC() { return kfc; } }
- 测试
public class BTest { public static void main(String[] args) { KFC kfc = new ConcreteBuilder("汉堡","薯条").setChicken().setCola().getKFC(); } }
如果不需要抽象建造者的角色来规定生产内容,那么代码到这里其实还有进一步的简化空间。
【关键代码】
**使用静态内部类的方式**
【进一步简化】
public class KFC { //套餐必点 private String hamburger; private String chips; //套餐选点 private String chicken; private String cola; private String pizza; //一定要有一个带有Builder参数的建造者 private KFC(Builder builder) { this.hamburger = builder.hamburger; this.chips = builder.chips; this.chicken = builder.chicken; this.cola = builder.cola; this.pizza = builder.pizza; } //注意必须为静态内部类 public static class Builder{ //套餐必点 private String hamburger; private String chips; //套餐选点 private String chicken; private String cola; private String pizza; public Builder(String hamburger,String chips){ this.hamburger = hamburger; this.chips = chips; } public Builder setChicken(){ this.chicken = "小鸡腿"; return this; } public Builder setCola(){ this.cola = "小可乐"; return this; } public Builder setPizza(){ this.pizza = "小披萨"; return this; } //生成一个产品 public KFC getKFC(){ return new KFC(this); } } }
测试
public class BuilderTest { public static void main(String[] args) { KFC kfc = new KFC.Builder("大汉堡", "小薯条").setChicken().setCola().getKFC(); System.out.println(kfc); } }
- 客户端不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象。
- 将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰。
- 使用不同的具体建造者即可得到不同的产品对象。
- 增加新的具体建造者无须修改原有代码。
- 若产品间的差异很大,则不适合使用建造者模式。
- 抽象工厂模式VS建造者模式抽象工厂模式是对产品家族的创建,一个产品家族是一系列产品:具有不同分类维度的产品组合,采用抽象工厂模式不需要关心构建过程,只关心什么产品由什么工厂生产即可。 而建造者模式则是要求按照指定的蓝图建造产品,它的主要目的是通过组装零配件而产生一个新产品。