一、介绍
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{//省略了上面的属性.....//必点套餐Apublic KFC(String hamburger,String chips){this(hamburger,chips,null,null,null);}A//套餐Bpublic KFC(String hamburger,String chips,String chicken){this(hamburger,chips,chicken,null,null);}//套餐Cpublic 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的人却不知道,造成错误的套餐出现。
解决上面的两种问题:一种设计模式解决一类问题,所以建造者模式就出现了。
三、建造者模式
先了解一下又哪些角色吧,看不懂没关系,看一下代码就懂了
四个角色
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);}@Overridepublic void setChicken() {kfc.setChicken("大鸡腿");}@Overridepublic void setCola() {kfc.setCola(null);System.out.println("套餐A里面没有可乐");}@Overridepublic void setPizza() {kfc.setPizza(null);System.out.println("套餐A里面没有披萨");}@Overridepublic KFC getKFC() {return kfc;}}
套餐B
public class ConcreteBuilder2 implements Builder {private KFC kfc;//这一步非常重要public ConcreteBuilder2(String hamburger,String chips){kfc = new KFC(hamburger,chips);}@Overridepublic void setChicken() {kfc.setChicken("小鸡腿");}@Overridepublic void setCola() {kfc.setCola("小可乐");}@Overridepublic void setPizza() {kfc.setPizza("小披萨");}@Overridepublic 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) {//套餐ASystem.out.println("======套餐A======");Builder concreteBuilder1 = new ConcreteBuilder1("大汉堡", "小薯条");KFC kfc1 = new Director().build(concreteBuilder1);System.out.println(kfc1);//套餐BSystem.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);}@OverrideBuilder setChicken() {kfc.setChicken("鸡腿");return this;}@OverrideBuilder setCola() {kfc.setCola("可乐");return this;}@OverrideBuilder setPizza() {kfc.setPizza("披萨");return this;}@OverrideKFC 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很像具体的工厂,都是规定了建造的内容。
区别:
- 建造者模式有指导者这个角色,直接返回一个组装好的产品,而抽象工厂模式返回一系列相关的产品,这些产品位于不同的产品等级结构,构成了一个产品族
- 建造者模式更适合复杂的产品构建
- 可以将抽象工厂模式理解成汽车零件生产工厂,而建造者模式看出组装工厂
