理解设计模式中的建造者模式

78 阅读7分钟

在这里插入图片描述

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

文章目录

建造者模式

1. 前言

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

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

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

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

public abstract class Cooking {
    public abstract void buy();
    public abstract void cut();
    public abstract void cook();
    public abstract void check();
}

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

public class Chef extends Cooking{
    @Override
    public void buy() {
        System.out.println("buying something...");
    }

    @Override
    public void cut() {
        System.out.println("cutting something....");
    }

    @Override
    public void cook() {
        System.out.println("cooking something...");
    }

    @Override
    public void check() {
        System.out.println("checking something...");
    }

    public void make(){
        buy();
        cut();
        cook();
        check();
    }
}

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

public class Consumer {
    public static void main(String[] args) {
        Chef chef = new Chef();
        chef.make();
    }
}

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

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

2. 建造者模式

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

public class Meal {
    String name;
    int price;

    public Meal(String name, int price) {
        this.name = name;
        this.price = price;
    }

    public Meal(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getPrice() {
        return price;
    }

    public void setPrice(int price) {
        this.price = price;
    }
}

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

public abstract class Chef {
    Meal meal = null;

    public abstract void buy();
    public abstract void cut();
    public abstract void slice();
    public abstract void cook();
    public abstract void check();

    public Meal make(String name){
        return new Meal(name);
    }
}

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

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

public class ChineseChef extends Chef{
    @Override
    public void buy() {
        System.out.println("buying fish...");
    }

    @Override
    public void cut() {
        System.out.println("cutting fish...");
    }

    @Override
    public void slice() {
        System.out.println("slicing fish...");
    }

    @Override
    public void cook() {
        System.out.println("cooking fish...");
    }

    @Override
    public void check() {
        System.out.println("checking fish...");
    }
}

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

public class WestChef extends Chef{

    @Override
    public void buy() {
        System.out.println("buying steak...");
    }

    @Override
    public void cut() {
    }

    @Override
    public void slice() {
    }

    @Override
    public void cook() {
        System.out.println("cooking steak...");
    }

    @Override
    public void check() {
        System.out.println("checking steak...");
    }
}

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

public class MajorChef {
    String name;
    Chef chef = null;

    public MajorChef(Chef chef, String name) {
        this.chef = chef;
        this.name = name;
    }

    public Meal direct(){
        chef.buy();
        chef.cut();
        chef.slice();
        chef.cook();
        chef.check();
        return chef.make(this.name);
    }
}

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

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

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

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

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

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

对应于上面的例子,角色对应的类图如下所示:
在这里插入图片描述

4. 总结

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

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

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

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

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