重学设计模式之建造者模式(Kotlin)

494 阅读6分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第二十九天,点击查看活动详情

重学设计模式之建造者模式

前言

建造者模式(Builder Pattern)使用多个简单的对象一步一步构建成一个复杂的对象。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

适用场景

  1. 隔离复杂对象的创建和使用,相同的方法,不同执行顺序,产生不同事件结果

  2. 多个部件都可以装配到一个对象中,但产生的运行结果不相同

  3. 产品类非常复杂或者产品类因为调用顺序不同而产生不同作用

  4. 初始化一个对象时,参数过多,或者很多参数具有默认值

  5. 建造者模式不适合创建差异性很大的产品类。产品内部变化复杂,会导致需要定义很多具体建造者类实现变化,增加项目中类的数量,增加系统的理解难度和运行成本

  6. 需要生成的产品对象有复杂的内部结构,这些产品对象具备共性

使用对多的是生成器模式可避免 “重叠构造函数 (tele­scop­ing con­struc­tor)” 的出现

类似下面这种情况:

class Pizza {
    Pizza(int size) { ... }
    Pizza(int size, boolean cheese) { ... }
    Pizza(int size, boolean cheese, boolean pepperoni) { ... }

建造者模式角色以及职责

  • 产品类(Product) :具体产品
  • 抽象建造者(Builder) :为创建一个Product产品对象的各个部件指定的抽象接口
  • 具体建造者(ConcreteBuilder) :具体建造者、实现Builder接口,构建和装配各个部件
  • 指挥者(Director) :构建一个使用Builder接口的对象

20181220093218755.png

实例

我们以创建一个电脑为实例,一个完整的电脑需要显示器、主机(主板、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中使用传统的建造者模式

  1. 创建产品类(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 + ''' +
                '}';
    }
}
  1. 抽象构建者类
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();
}
  1. 具体建造者(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;
    }
}
  1. 指导者类(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的一些语法特性也可以是的代码简洁、易读、美观,任然可以大放异彩

建造者模式优缺点

优点:

  • 建造者独立,易于扩展,用户使用不同的具体构建造者即可得到不同的产品对象
  • 客户端不需要知道产品内部的组成细节,便于控制细节风险,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象

缺点:

  • 产品必须有共同点,范围有限制。如果内部变化复杂,会有很多的建造类
  • 如果产品的内部变化复杂,该模式会增加很多的建造者类