建造者模式——结合案例,通俗易懂
一个设计模式解决一类问题,最近学习了一下建造者模式,看了很多博客,讲的模棱两可,所以决定写一下我觉得比较好理解的简介
参考自知乎 https://zhuanlan.zhihu.com/p/58093669,
一、介绍
1、啥是建造者模式
是将一个复杂的对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示
看不懂对吧!其实我也看不懂,不管它,通过案例和代码加深理解
2、使用场景
一个设计模式解决一类问题,那么建造者模式解决了什么问题呢?——对象的构建过于复杂的问题
- 当一个类的构造函数参数过多(超过四个),并且有的参数可有可无,或者很多产品有默认值。
- 产品类非常复杂或者产品类因为调用顺序不同而产生不同作用
3、优点
- 复杂产品的创建步骤分解在不同的方法中,这些方法可以调用顺序不同,结果不同,创建结果很清晰
4、缺点
- 如果产品的内部变化复杂,可能会导致需要定义很多具体建造者来实现这种变化。
二、案例
理论总是难以理解的,现在通过案例分析问题,一步步了解使用建造者模式的好处
【案例】好好看一下这个案例
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的人却不知道,造成错误的套餐出现!。
为了解决上面的两种问题:一种设计模式解决一类问题,所以建造者模式就出现了
三、建造者模式
先了解一下又哪些角色吧,看不懂没关系,看一下代码就懂了
四个角色
Product(产品角色): 一个具体的产品对象。
Builder(抽象建造者): 创建一个Product对象的各个部件指定的抽象接口。
ConcreteBuilder(具体建造者): 实现抽象接口,构建和装配各个部件。
Director(指挥者): 构建一个使用Builder接口的对象。它主要是用于创建一个复杂的对象。它主要有两个作用,一是:隔离了客户与对象的生产过程,二是:负责控制产品对象的生产过程。
拿其中两个套餐举例
套餐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);
}
@Override
public void setChicken() {
kfc.setChicken("大鸡腿");
}
@Override
public void setCola() {
kfc.setCola(null);
System.out.println("套餐A里面没有可乐");
}
@Override
public void setPizza() {
kfc.setPizza(null);
System.out.println("套餐A里面没有披萨");
}
@Override
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);
}
@Override
public void setChicken() {
kfc.setChicken("小鸡腿");
}
@Override
public void setCola() {
kfc.setCola("小可乐");
}
@Override
public void setPizza() {
kfc.setPizza("小披萨");
}
@Override
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);
}
@Override
Builder setChicken() {
kfc.setChicken("鸡腿");
return this;
}
@Override
Builder setCola() {
kfc.setCola("可乐");
return this;
}
@Override
Builder setPizza() {
kfc.setPizza("披萨");
return this;
}
@Override
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);
}
}
五、建造者模式和抽象工厂模式的区别
通过上面的代码,你发现普通的建造者模式和抽象工厂模式真的很像,在建造者模式中的builder角色很像超级工厂,然后contracterBuilder很像具体的工厂,都是规定了建造的内容
那么它们之前 有什么区别呢
- 建造者模式有指导者这个角色,直接返回一个组装好的产品,而抽象工厂模式返回一系列相关的产品,这些产品位于不同的产品等级结构,构成了一个产品族
- 建造者模式更适合复杂的产品构建
- 可以将抽象工厂模式理解成汽车零件生产工厂,而建造者模式看出组装工厂
【总结】
学习一类技巧是为了解决一类问题,学习设计模式主要是为了理解它的思想,将来遇到代码的编写,使用这种模式会更加的方式,而没有必要刻意的去使用,但是需要刻意的去练习,形成这种思想