到目前为止,我们已经学习了五种创建型模式中的四种,它们分别是单例模式、工厂方法模式、抽象工厂模式和原型模式。不同的模式适用的的应用场景有所不同,但也并不是完全隔绝,需要用户根据具体的应用场景选择合适的模式。本文将介绍创建型模式中的最后一种,即建造者模式,并通过代码的方式进行阐述,同时和之前的几种模式做下对比。
建造者模式
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 建造者模式:
抽象工厂模式和建造者模式都实现了产品和生成过程的解耦,不过两者之间仍存在一定的不同之处,例如:
- 抽象工厂模式关注于产品族,用户可以使用工厂获取到同一产品族不同品类的产品,而建造者模式关注于一个完整的产品
- 用户直接调用使用抽象工厂中的方法来获取具体的产品,而建造者模式中用户只能通过指挥者来获取想要的产品