全篇干货,值得你用几分钟认真读完。
先看定义:将一个复杂的构建与其表示想分离,使得同样的构建过程可以创建不同的标示。
使用场景:当一个类的构造函数参数个数超过4个,而且这些参数有些是可选的参数,考虑使用构造者模式。
例子
在App中用户会分成vip和普通用户,vip用户会有app的图标和用户专属图标。这里假设用户有id和name(昵称)是必选的,vipAppLogo(vip的app图标)和vipUserLogo(vip用户图标)是可选的,vipAppLogo和vipUserLogo也可能有其中一个(vip可能只开启了一个),最原始的方案大概是这样的:
class User {
var id: Int;
var name: String;
var vipAppLogo: Image; // 图标存在本地的资源文件中,通过Resource.getVipAppLogo()获取
var vipUserLogo: Image; // 图标存在本地资源文件中,通过Resource.getVipUserLogo()获取
func init(id: Int, name: String) {
self.id = id;
self.name = name;
}
func init(id: Int, name: String, vipAppLogo: Image) {
self.id = id;
self.name = name;
self.vipAppLogo = vipAppLogo;
}
func init(id: Int, name: String, vipUserLogo: Image) {
self.id = id;
self.name = name;
self.vipUserLogo = vipUserLogo;
}
func init(id: Int, name: String, vipAppLogo: Image, vipUserLogo: Image) {
self.id = id;
self.name = name;
self.vipAppLogo = vipAppLogo;
self.vipUserLogo = vipUserLogo;
}
}
使用这种方法在创建User的时候需要决定到底该使用哪个初始化方法,而且阅读起来及其不便,也非常不利于扩展,可以设想一下某次需求变化给User增加一个age的必传参数。。。相信这种情况都遇到过。
读到这里,聪明的你可能会想到,既然有的参数是可选的,那么我是否可以只使用一个初始化函数,而把其他参数都使用get、set方法传递呢,就像下面这样。
class User {
var id: Int;
var name: Int;
var vipAppLogo: Image;
var vipUserLogo: Image;
func init(id: Int, name: String) {
self.id = id;
self.name = name;
}
func setVipAppLogo(vipAppLogo: Image) {
self.avatarImage = avatarImage;
}
func setVipUserLogo(vipUserLogo: Image) {
self.vipUserLogo = vipUserLogo;
}
}
不得不说这种方法确实解决了上面提高的问题,但是如果在调用setNo之前就使用了no的话,是不是就取不到值了呢?而且,对外直接暴露set方法的话,调用者随时都可以修改属性值,对内部来说是很不安全的。
现在使用构建者模式可以这样做:
class User {
var id: int;
var name: string;
var vipAppLogo: Image;
var vipUserLogo: Image;
func init(id: int, name: string) {
self.id = id;
self.name = name;
}
func setVipAppLogo(vipAppLogo: Image) {
self.vipAppLogo = vipAppLogo;
}
func setVipUserLogo(vipUserLogo: Image) {
self.vipUserLogo = vipUserLogo;
}
}
interface UserBuilder {
func setVipAppLogo(vipAppLogo: Image);
func setVipUserLogo(vipUserLogo: Image);
func init(id: int, name: string);
func getUser() -> User;
}
class ConcreteUserBuilder: UserBuilder {
private var someUser: User;
func init(id: int, name: string) {
someUser = User(id, name);
}
func setVipAppLogo(vipAppLogo: Image) {
someUser.no = no;
}
func setVipUserLogo(vipUserLogo: Image) {
someUser.vipUserLogo = vipUserLogo;
}
func getUser() -> User {
return self.someUser;
}
}
class UserDirector {
func createUser(build: UserBuilder, vipLevel: int) -> User {
if (vipLevel > 10) {
build.setVipAppLogo(Resource.getVipAppLogo(vipLevel);
build.setVipUserLogo(Resource.getUserAppLogo(vipLevel);
}
return build.getUser();
}
}
使用的时候:
let xiaomingBuilder: ConcreteUserBuilder = new ConcreteUserBuilder(id: 1, name: "xiaoming");
let director: UserDirector = new UserDirector();
let xiaoming = director.createUser(build: xiaomingBuilder, vipLevel: 20);
经过建造者模式的封装,User的创建过程被掩盖起来。尤其是在需求变动的时候优势尤为显著。比如在vipLevel大于15时才有vipAppLogo,而在vipLevel大于20时才有vipUserLogo,这时仅改动Builder里面的代码就可以完成这个需求。
下面提供一种简化的使用方式,可能在某些语言里不适用。
public class Computer {
private final String cpu;//必须
private final String ram;//必须
private final int usbCount;//可选
private final String keyboard;//可选
private final String display;//可选
private Computer(Builder builder){
this.cpu=builder.cpu;
this.ram=builder.ram;
this.usbCount=builder.usbCount;
this.keyboard=builder.keyboard;
this.display=builder.display;
}
public static class Builder{
private String cpu;//必须
private String ram;//必须
private int usbCount;//可选
private String keyboard;//可选
private String display;//可选
public Builder(String cup,String ram){
this.cpu=cup;
this.ram=ram;
}
public Builder setUsbCount(int usbCount) {
this.usbCount = usbCount;
return this;
}
public Builder setKeyboard(String keyboard) {
this.keyboard = keyboard;
return this;
}
public Builder setDisplay(String display) {
this.display = display;
return this;
}
public Computer build(){
return new Computer(this);
}
}
}
当一个类的构造函数参数个数超过4个,而且这些参数有些是可选的参数,考虑使用构造者模式。
本文到这里就结束了,感谢阅读。有不对之处请指正。