携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第二十九天,点击查看活动详情
重学设计模式之建造者模式
前言
建造者模式(Builder Pattern)使用多个简单的对象一步一步构建成一个复杂的对象。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
适用场景
-
隔离复杂对象的创建和使用,相同的方法,不同执行顺序,产生不同事件结果
-
多个部件都可以装配到一个对象中,但产生的运行结果不相同
-
产品类非常复杂或者产品类因为调用顺序不同而产生不同作用
-
初始化一个对象时,参数过多,或者很多参数具有默认值
-
建造者模式不适合创建差异性很大的产品类。产品内部变化复杂,会导致需要定义很多具体建造者类实现变化,增加项目中类的数量,增加系统的理解难度和运行成本
-
需要生成的产品对象有复杂的内部结构,这些产品对象具备共性
使用对多的是生成器模式可避免 “重叠构造函数 (telescoping constructor)” 的出现
类似下面这种情况:
class Pizza {
Pizza(int size) { ... }
Pizza(int size, boolean cheese) { ... }
Pizza(int size, boolean cheese, boolean pepperoni) { ... }
建造者模式角色以及职责
- 产品类(Product) :具体产品
- 抽象建造者(Builder) :为创建一个Product产品对象的各个部件指定的抽象接口
- 具体建造者(ConcreteBuilder) :具体建造者、实现Builder接口,构建和装配各个部件
- 指挥者(Director) :构建一个使用Builder接口的对象
实例
我们以创建一个电脑为实例,一个完整的电脑需要显示器、主机(主板、CPU、内存、显卡等)、外设(鼠标、键盘)
Java版本 建造者设计模式
java开发中日常代码
我们看看在日常开发中,不使用建造者模式的代码,
创建一个Computer类:
public class Computer {
//显示器
private String display;
//主板
private String motherboard;
//CUP
private String cpu;
//内存
private String ram;
//显卡
private String graphicsCard;
//键盘
private String keyboard;
//鼠标
private String mouse;
public Computer(String display, String motherboard, String cpu, String ram, String graphicsCard, String keyboard, String mouse) {
this.display = display;
this.motherboard = motherboard;
this.cpu = cpu;
this.ram = ram;
this.graphicsCard = graphicsCard;
this.keyboard = keyboard;
this.mouse = mouse;
}
public Computer(String display, String motherboard, String cpu, String ram, String keyboard, String mouse) {
this(display,motherboard,cpu,ram,"无显卡",keyboard,mouse);
}
public Computer() {
this("三星","华硕","因特尔","海盗船","七彩虹","罗技", "罗技");
}
}
我们发现会有很多构造方法,我们是用建造者模式来修改一下。
Java版本建造者修改
public class Computer {
//显示器
private String display;
//主板
private String motherboard;
//CUP
private String cpu;
//内存
private String ram;
//显卡
private String graphicsCard;
//键盘
private String keyboard;
//鼠标
private String mouse;
private Computer(Builder builder) {
this.display = builder.display;
this.motherboard = builder.motherboard;
this.cpu = builder.cpu;
this.ram = builder.ram;
this.graphicsCard = builder.graphicsCard;
this.keyboard = builder.keyboard;
this.mouse = builder.mouse;
}
@Override
public String toString() {
return "Computer{" +
"display='" + display + ''' +
", motherboard='" + motherboard + ''' +
", cpu='" + cpu + ''' +
", ram='" + ram + ''' +
", graphicsCard='" + graphicsCard + ''' +
", keyboard='" + keyboard + ''' +
", mouse='" + mouse + ''' +
'}';
}
public static class Builder {
//显示器
private String display;
//主板
private String motherboard;
//CUP
private String cpu;
//内存
private String ram;
//显卡
private String graphicsCard;
//键盘
private String keyboard;
//鼠标
private String mouse;
public Builder() {
}
public Builder setDisplay(String display) {
this.display = display;
return this;
}
public Builder setMotherboard(String motherboard) {
this.motherboard = motherboard;
return this;
}
public Builder setCpu(String cpu) {
this.cpu = cpu;
return this;
}
public Builder setRam(String ram) {
this.ram = ram;
return this;
}
public Builder setGraphicsCard(String graphicsCard) {
this.graphicsCard = graphicsCard;
return this;
}
public Builder setKeyboard(String keyboard) {
this.keyboard = keyboard;
return this;
}
public Builder setMouse(String mouse) {
this.mouse = mouse;
return this;
}
public Computer build() {
return new Computer(this);
}
}
}
调用一下:
public static void main(String[] args) {
Computer computer = new Builder()
.setDisplay("三星")
.setMotherboard("华硕")
.setCpu("因特尔")
.setRam("海盗船")
.setGraphicsCard("七彩虹")
.setKeyboard("罗技")
.setMouse("罗技")
.build();
computer.toString();
}
这里就是就是一个简单的建造者设计模式,但是你会发现我们在上面的代码中并没有使用上面所说的四个角色,那么我们还是用组装电脑来使用一下
Java中使用传统的建造者模式
- 创建产品类(Product)
Computer类:
public class Computer {
//显示器
private String display;
//主板
private String motherboard;
//CUP
private String cpu;
//内存
private String ram;
//显卡
private String graphicsCard;
//键盘
private String keyboard;
//鼠标
private String mouse;
public void setDisplay(String display) {
this.display = display;
}
public void setMotherboard(String motherboard) {
this.motherboard = motherboard;
}
public void setCpu(String cpu) {
this.cpu = cpu;
}
public void setRam(String ram) {
this.ram = ram;
}
public void setGraphicsCard(String graphicsCard) {
this.graphicsCard = graphicsCard;
}
public void setKeyboard(String keyboard) {
this.keyboard = keyboard;
}
public void setMouse(String mouse) {
this.mouse = mouse;
}
@Override
public String toString() {
return "Computer{" +
"display='" + display + ''' +
", motherboard='" + motherboard + ''' +
", cpu='" + cpu + ''' +
", ram='" + ram + ''' +
", graphicsCard='" + graphicsCard + ''' +
", keyboard='" + keyboard + ''' +
", mouse='" + mouse + ''' +
'}';
}
}
- 抽象构建者类
public abstract class ComputerBuilder {
public abstract void setDisplay();
public abstract void setMotherboard();
public abstract void setCpu();
public abstract void setRam();
public abstract void setGraphicsCard();
public abstract void setKeyboard();
public abstract void setMouse();
public abstract Computer getComputer();
}
- 具体建造者(ConcreteBuilder) 我们创建一个联想电脑:
public class LenovoComputerBuilder extends ComputerBuilder {
private Computer computer;
public LenovoComputerBuilder() {
computer=new Computer( );
}
@Override
public void setDisplay() {
computer.setDisplay("联想显示器");
}
@Override
public void setMotherboard() {
computer.setMotherboard("联想主板");
}
@Override
public void setCpu() {
computer.setCpu("因特尔");
}
@Override
public void setRam() {
computer.setRam("联想内存");
}
@Override
public void setGraphicsCard() {
computer.setGraphicsCard("联想显卡");
}
@Override
public void setKeyboard() {
computer.setKeyboard("联想键盘");
}
@Override
public void setMouse() {
computer.setMouse("联想鼠标");
}
@Override
public Computer getComputer() {
return computer;
}
}
- 指导者类(Director)
public class ComputerDirector {
public void makeComputer(ComputerBuilder builder){
builder.setDisplay();
builder.setMotherboard();
builder.setCpu();
builder.setRam();
builder.setGraphicsCard();
builder.setKeyboard();
builder.setMouse();
}
}
我们写好了这四个角色,那么我们来使用一下:
public static void main(String[] args) {
ComputerDirector director=new ComputerDirector();
ComputerBuilder lenovoBuilder=new LenovoComputerBuilder();
director.makeComputer(lenovoBuilder);
Computer lenovoComputer=lenovoBuilder.getComputer();
System.out.println("lenovo computer:"+lenovoComputer.toString());
}
输出结果:
"Computer{" +
"display='" + display + ''' +
", motherboard='" + motherboard + ''' +
", cpu='" + cpu + ''' +
", ram='" + ram + ''' +
", graphicsCard='" + graphicsCard + ''' +
", keyboard='" + keyboard + ''' +
", mouse='" + mouse + ''' +
'}';
lenovo computer:Computer{display='联想显示器', motherboard='联想主板', cpu='因特尔', ram='联想内存', graphicsCard='联想显卡'.keyboard='联想键盘',mouse='联想鼠标'}
建造者在Kotlin中的使用
在kotlin中是有默认参数和名称参数,使得建造者模式无用武之地,我们来看看是否真这样吗?我们还是以组装电脑为例
创建一个Computer类:
class Computer private constructor(
//显示器
val display: String = "三星",
//主板
val motherboard: String = "华硕",
//CUP
val cpu: String = "因特尔",
//内存
val ram: String = "海盗船",
//显卡
val graphicsCard: String = "七彩虹",
//键盘
val keyboard: String = "罗技",
//鼠标
val mouse: String = "罗技",
) {
override fun toString(): String {
return "Computer(display = '$display' motherboard= '$motherboard' cpu='$cpu', ram='$ram', graphicsCard=$graphicsCard, keyboard='$keyboard', mouse='$mouse')"
}
}
我们把Computer类修改为建造者模式:
class Computer private constructor(
//显示器
val display: String = "三星",
//主板
val motherboard: String = "华硕",
//CUP
val cpu: String = "因特尔",
//内存
val ram: String = "海盗船",
//显卡
val graphicsCard: String = "七彩虹",
//键盘
val keyboard: String = "罗技",
//鼠标
val mouse: String = "罗技",
) {
private constructor(builder: Builder) : this(
builder.display,
builder.motherboard,
builder.cpu,
builder.ram,
builder.graphicsCard,
builder.keyboard,
builder.mouse
)
class Builder {
var display: String = ""
private set
var motherboard: String = ""
private set
var cpu: String = ""
private set
var ram: String = ""
private set
var graphicsCard: String = ""
private set
var keyboard: String = ""
private set
var mouse: String = ""
private set
fun setDisplay(inputDisplay: String) = apply {
this.display = inputDisplay
}
fun setMotherboard(inputMotherboard: String) = apply {
this.motherboard = inputMotherboard
}
fun setCup(inputCup: String) = apply {
this.cpu = inputCup
}
fun setRam(inputRam: String) = apply {
this.ram = inputRam
}
fun setGraphicsCard(inputGraphicsCard: String) = apply {
this.graphicsCard = inputGraphicsCard
}
fun setKeyboard(inputKeyboard: String) = apply {
this.keyboard = inputKeyboard
}
fun setMouse(inputMouse: String) = apply {
this.mouse = inputMouse
}
fun build() = Computer(this)
override fun toString(): String {
return "Computer(display = '$display' motherboard= '$motherboard' cpu='$cpu', ram='$ram', graphicsCard=$graphicsCard, keyboard='$keyboard', mouse='$mouse')"
}
}
}
我们再来使用一下:
fun main() {
val computer = Computer.Builder()
.setDisplay("LG")
.setMotherboard("三星主板")
.setCup("英特尔")
.setRam("金士顿")
.setDisplay("三星")
.setGraphicsCard("影驰")
.setKeyboard("罗技")
.build()
}
这样在Kotlin中的建造者模式就完成,当然在Kotlin中除了传统的写法还有其他的写法,在kotlin中有一个带有接收器的函数字面量,看一下例子:
var subtract : Int.(Int) -> Int = {this - it}
println(3.subtract(2))
我们把带有接收器的函数字面量用在建造者设计模式中
我们在Computer类里面加一个companion object,在里面定义一个使用Builder构建对象的方法,其入参为 接收器为Builder的Lambda表达式
class Computer3 private constructor(...){
companion object {
inline fun build(block: Builder.() -> Unit) = Builder().apply(block).build()
}
class Builder {
...
fun build() = Computer3(this)
}
}
我们再看一下怎么使用吧:
val computer3 = Computer3.build {
setDisplay("LG")
setMotherboard("三星主板")
setCup("英特尔")
setRam("金士顿")
setDisplay("三星")
setGraphicsCard("影驰")
setKeyboard("罗技")
}
在kotlin中大部分情况下是不需要Builder模式的,但是结合Kotlin的一些语法特性也可以是的代码简洁、易读、美观,任然可以大放异彩
建造者模式优缺点
优点:
- 建造者独立,易于扩展,用户使用不同的具体构建造者即可得到不同的产品对象
- 客户端不需要知道产品内部的组成细节,便于控制细节风险,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象
缺点:
- 产品必须有共同点,范围有限制。如果内部变化复杂,会有很多的建造类
- 如果产品的内部变化复杂,该模式会增加很多的建造者类