前一篇博客主要讲了工厂方法和抽象工厂用来创建对象的模式,这一篇主要来讲一下建造者模式。再讲它的结构之前,先举一个实际的例子用来说明一种常见的问题。
比如在有些业务场景下,当你需要创建一个对象的时候,这个对象可能需要初始化的属性比较多。这个时候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
网友评论