到目前为止,我们已经学习了五种创建型模式中的四种,它们分别是单例模式、工厂方法模式、抽象工厂模式和原型模式。不同的模式适用的的应用场景有所不同,但也并不是完全隔绝,需要用户根据具体的应用场景选择合适的模式。本文将介绍创建型模式中的最后一种,即建造者模式,并通过代码的方式进行阐述,同时和之前的几种模式做下对比。

建造者模式

1. 前言

如果希望程序在整个过程中只有一个对象实例,那么单例模式中几种线程安全的形式是不错的选择;如果同一类型的产品有很多,而且希望新增该类型的产品而不需要修改之前的程序,同时希望用户不需要了解产品的细节,那么可以使用工厂方法模式;如果生产的不只是同一类型的产品,而是希望可以生产同一产品族中不同类型的产品,工厂方法模式的进一步抽象对应的抽象工厂模式是更好的选择,它不仅可以满足开闭原则,而且可以对同一产品族下的产品进行约束。

如果某类型产品本身具有一定的约束规则,但是根据不同的操作步骤会有多种多样的实现形式,那么建造者模式相较于上述的几种就是一个更好的选择。如何理解使用建造者模式的缘由呢?下面我们通过生活中的一个例子来进行理解:

由于本人很喜欢做饭,所以就拿做菜的例子来说一下~

做菜大致可以分为买菜、洗菜、切菜、做菜、摆盘和验菜等几个步骤,当然不同的菜的做法会有不同的操作步骤和其他更为细化的操作。为了表示做菜所包含的一些步骤,首先创建一个抽象类,类中的抽象方法分别表示每个步骤:

  1. public abstract class Cooking {
  2. public abstract void buy();
  3. public abstract void cut();
  4. public abstract void cook();
  5. public abstract void check();
  6. }

假设现在店里只有一个厨师,而且店里只提供一种餐品,那么表示厨师的类Chef就需要继承Cooking并重写其中的抽象方法,同时类中还应实现表示做菜流程的方法,如下所示:

  1. public class Chef extends Cooking{
  2. @Override
  3. public void buy() {
  4. System.out.println("buying something...");
  5. }
  6. @Override
  7. public void cut() {
  8. System.out.println("cutting something....");
  9. }
  10. @Override
  11. public void cook() {
  12. System.out.println("cooking something...");
  13. }
  14. @Override
  15. public void check() {
  16. System.out.println("checking something...");
  17. }
  18. public void make(){
  19. buy();
  20. cut();
  21. cook();
  22. check();
  23. }
  24. }

对于顾客来说,他的选择是唯一的。因此,当他想要点餐时,只需要告诉厨师做一份即可。

  1. public class Consumer {
  2. public static void main(String[] args) {
  3. Chef chef = new Chef();
  4. chef.make();
  5. }
  6. }

这样的方法具有以下的特点:

  • 操作简单,易于理解
  • 程序的扩展性较差,如果厨师有点上进心,他突然学会了一个新菜,而且新菜的做法和之前会的有所不同,那么此时就需要修改Chef类的实现代码,违背了开闭原则
  • 程序的耦合性较高,此时产品和创建产品的过程封装为了一个整体,所有的工作都由厨师一人完成

2. 建造者模式

如果此时餐厅做大做强了,菜品得到了极大的丰富,不仅有各种各样的中餐,同时还提供多种西餐进行选择。为了抽象表示所有的餐品,首先创建一个餐品类Meal,它包含名字和价格两个属性

  1. public class Meal {
  2. String name;
  3. int price;
  4. public Meal(String name, int price) {
  5. this.name = name;
  6. this.price = price;
  7. }
  8. public Meal(String name) {
  9. this.name = name;
  10. }
  11. public String getName() {
  12. return name;
  13. }
  14. public void setName(String name) {
  15. this.name = name;
  16. }
  17. public int getPrice() {
  18. return price;
  19. }
  20. public void setPrice(int price) {
  21. this.price = price;
  22. }
  23. }

虽然中餐和西餐品类繁多,不同的菜做法各有不同,但仍然具有很多相似的操作,假设所有的菜的操作步骤只有买、切、片、做和验这几种:

  1. public abstract class Chef {
  2. Meal meal = null;
  3. public abstract void buy();
  4. public abstract void cut();
  5. public abstract void slice();
  6. public abstract void cook();
  7. public abstract void check();
  8. public Meal make(String name){
  9. return new Meal(name);
  10. }
  11. }

同时抽象类Chef中还有方法make()表示出菜。

那么做中餐和西餐的厨师类都需要继承Chef,不过在具体的操作步骤上有所不同。假设中餐厨师擅长做烤鱼,那么做法包含买鱼、切鱼、片鱼、做鱼和验菜。

  1. public class ChineseChef extends Chef{
  2. @Override
  3. public void buy() {
  4. System.out.println("buying fish...");
  5. }
  6. @Override
  7. public void cut() {
  8. System.out.println("cutting fish...");
  9. }
  10. @Override
  11. public void slice() {
  12. System.out.println("slicing fish...");
  13. }
  14. @Override
  15. public void cook() {
  16. System.out.println("cooking fish...");
  17. }
  18. @Override
  19. public void check() {
  20. System.out.println("checking fish...");
  21. }
  22. }

而西厨更擅长做牛排,假设牛排只需要买回来进行烹饪,最后验菜即可。

  1. public class WestChef extends Chef{
  2. @Override
  3. public void buy() {
  4. System.out.println("buying steak...");
  5. }
  6. @Override
  7. public void cut() {
  8. }
  9. @Override
  10. public void slice() {
  11. }
  12. @Override
  13. public void cook() {
  14. System.out.println("cooking steak...");
  15. }
  16. @Override
  17. public void check() {
  18. System.out.println("checking steak...");
  19. }
  20. }

因为餐厅中厨师很多,为了方便后厨的管理工作,通常需要安排一个主厨来负责做菜的整体流程。主厨只需要知道要做的菜品的名字,然后安排相应的二厨去做即可,当菜做完后只需要告诉服务员走菜。

  1. public class MajorChef {
  2. String name;
  3. Chef chef = null;
  4. public MajorChef(Chef chef, String name) {
  5. this.chef = chef;
  6. this.name = name;
  7. }
  8. public Meal direct(){
  9. chef.buy();
  10. chef.cut();
  11. chef.slice();
  12. chef.cook();
  13. chef.check();
  14. return chef.make(this.name);
  15. }
  16. }

此时,顾客想要点烤鱼,那么他只需要让服务员告诉主厨菜名,主厨就会安排负责中餐的二厨来做。

  1. public class Consumer {
  2. public static void main(String[] args) {
  3. ChineseChef chineseChef = new ChineseChef();
  4. MajorChef majorChef = new MajorChef(chineseChef,"roast fish");
  5. majorChef.direct();
  6. System.out.println("--------------");
  7. }
  8. }
  1. buying fish...
  2. cutting fish...
  3. slicing fish...
  4. cooking fish...
  5. checking fish...

如果另一个顾客点了牛排,主厨就会安排负责西餐的二厨去做。

  1. public class Consumer {
  2. public static void main(String[] args) {
  3. WestChef westChef = new WestChef();
  4. MajorChef majorChef = new MajorChef(westChef, "steak")
  5. majorChef.direct();
  6. }
  7. }
  1. buying steak...
  2. cooking steak...
  3. checking steak...

这里采用的设计模式就是建造者模式,用户只需要知道自己想吃什么,而不需关心菜是怎么做的。下面我们对照例子来理解建造者模式中的角色:
建造者模式 .png

  • 产品角色(Product):具体的产品对象,如Meal
  • 抽象建造者角色(Builder):创建一个Product对象的各个部件指定的抽象类或接口,如Chef
  • 具体建造者角色(ConcreteBuilder):接口的实现类或是抽象类的子类,负责构建和装配各部件,如ChineseChef、WestChef
  • 指挥者角色(Director):使用Builder接口的对象,用于一个复杂对象的创建。它起到一个中介作用,用于隔离客户和对象的生产过程,同时负责控制产品对象的生产过程,如MajorChef

对应于上面的例子,角色对应的类图如下所示:
建造者模式案例.png

4. 总结

建造者模式具有如下的特点:

  • 用户在使用产品时并需要知道产品的生产细节,这样将产品本身和产品的创建过程进行了解耦,使用不同的创建过程可以创建不同的产品对象
  • 具体建造者之间彼此独立,用户可以使用不同的建造者得到不同的产品对象
  • 产品生产的步骤分布在每个方法中,可以更加精细的控制产品的生成过程,同时方便阅读和更新
  • 程序的扩展性好,满足开闭原则,当有新的建造者时,只需要增加新的具体建造者类即可
  • 适用于生成的产品之间具有较多的共同点的场景,以及产品的构建步骤较多的场景

抽象工厂模式 VS 建造者模式

抽象工厂模式和建造者模式都实现了产品和生成过程的解耦,不过两者之间仍存在一定的不同之处,例如:

  • 抽象工厂模式关注于产品族,用户可以使用工厂获取到同一产品族不同品类的产品,而建造者模式关注于一个完整的产品
  • 用户直接调用使用抽象工厂中的方法来获取具体的产品,而建造者模式中用户只能通过指挥者来获取想要的产品