设计模式:建造者模式

2021-08-25  本文已影响0人  闹鬼的金矿

前一篇博客主要讲了工厂方法和抽象工厂用来创建对象的模式,这一篇主要来讲一下建造者模式。再讲它的结构之前,先举一个实际的例子用来说明一种常见的问题。

比如在有些业务场景下,当你需要创建一个对象的时候,这个对象可能需要初始化的属性比较多。这个时候init方法的参数就会比较混乱,在实际进行初始化的地方比较容易出现错误,比如参数A可能传成了传成参数B。出现这种情况一是参数多,而是部分参数是可以传nil的,有时候会出现各种排列组合的情况。这种情况下,可能实际上这个对象是可以被拆分成不同的类型的对象,然后通过建造者模式来创建它,而不是同一在一个初始化方法里实现。

建造者模式的结构:


建造者模式.png

对于某个具体的Build,它包含了多个方法,每个方法实现这个产品某一部分零件或者功能的代码。Director它依赖的是Builder,它的具体作用是通过传入的Builder,按特定的顺序调用Builder内的方法。而对于需求Client方来说,它需要根据实际的需求,生成相应的Builder并把它传给对应的Director。由Director来按步骤来调用Builder的方法,最后需求方通过Builder来获取具体的产品。可以理解为,比如说Director提供了ConstructA,ConstructB, ContructC 三个方法,每个方法都会构建出不同的产品ProductA, ProdcutB, ProductC, 需求方需要给Director传BuilderA,BuilderB, BuidlerC, 所以实际上 ConstrutX ,BuilderX,ProductX是对应起来的。

看一个实际的代码例子:

protocol BuilderProtocol {
    func reset()
    func setSeats(count: Int)
    func setEngine(engine: String)
    func setColor(color: String)
}

enum Builder {
    
    class BMWCar {
        
        var seats: Int;
        var engine: String;
        var color: String;
        
        init() {
            self.seats = 0;
            self.engine = "";
            self.color = "";
        }
        
        func printSelf() {
            print("MBW一台: \(self.seats)个座位,\(self.engine)发动机,颜色\(self.color)");
        }
    }
    
    class BMWBuilder: BuilderProtocol {
        
        var car:BMWCar;
        
        init() {
            self.car = BMWCar();
        }
        
        func reset() {
            self.car = BMWCar();
        }
        
        func setSeats(count: Int) {
            self.car.seats = count;
        }
        
        func setEngine(engine: String) {
            self.car.engine = engine;
        }
        
        func setColor(color: String) {
            self.car.color = color;
        }
        
        func product() -> BMWCar {
            return self.car;
        }
    }
    
    class Audi {
        var seats: Int;
        var engine: String;
        var color: String;
        
        init() {
            self.seats = 0;
            self.engine = "";
            self.color = "";
        }
        
        func printSelf() {
            print("Audi一台: \(self.seats)个座位,\(self.engine)发动机,颜色\(self.color)");
        }
    }
    
    class AudiBuilder: BuilderProtocol {
        
        var car: Audi;
        
        init() {
            self.car = Audi();
        }
        
        func reset() {
            self.car = Audi();
        }
        
        func setSeats(count: Int) {
            self.car.seats = count;
        }
        
        func setEngine(engine: String) {
            self.car.engine = engine;
        }
        
        func setColor(color: String) {
            self.car.color = color;
        }
        
        func product() -> Audi {
            return self.car;
        }
        
    }
    
    class Director {
        
        var builder: BuilderProtocol?
        
        init() {
            
        }
        
        func makeBMW(b: BMWBuilder) {
            self.builder = b;
        }
        
        func makeAudi(b: AudiBuilder) {
            self.builder = b
        }
        
        func productBMW() {
            self.builder?.reset();
            self.builder?.setSeats(count: 2);
            self.builder?.setEngine(engine: "4缸");
            self.builder?.setColor(color: "白色");
        }
        
        func productAudi() {
            self.builder?.reset();
            self.builder?.setSeats(count: 4);
            self.builder?.setEngine(engine: "6缸");
            self.builder?.setColor(color: "黑色");
        }
    }
}

func builder() {
    let d = Builder.Director();
    let ab = Builder.AudiBuilder();
    d.makeAudi(b: ab);
    d.productAudi();
    let audi = ab.product();
    audi.printSelf();
    
    let bmwB = Builder.BMWBuilder();
    d.makeBMW(b: bmwB);
    d.productBMW();
    let bmw = bmwB.product();
    bmw.printSelf();
}

考虑一下文章最开始的问题,可能是N种排列组合,那么Director可能是N个Contrcut方法。这种情况还是会特别复杂,比如构建一个网络请求的时候,像请求参数1, 请求方法2,重试次数3,压缩编码4,超时时间5,参数格式6,这是通常会考虑的变量。并且传参数的方式get/post就不太一样,如果通过director去封装这个构建的过程,如果只有那么几种常见的情况比如get/post,其他变量都是一样,那么问题不大。但是如果在调用方需求可能差别比较大,这个时候就需要调用方自己去取代Director构建自己的网络请求了,这个时候对于Builder就只需要一个具体类型,不需要对它进行抽象了。

另外还需要考虑写出更加优雅的代码,可以考虑链式语法,最后的方式应该是:

buidler = Network.method(.post).format(.json).params({"a": 1}).retry(3).timeout(10).build();
builder.api_response(data, error){
  
};

Reference: Dive into design patterns

上一篇 下一篇

猜你喜欢

热点阅读