您的位置:首页 > 编程语言 > Java开发

关于创建者模式

2016-04-19 00:01 323 查看
神奇的创建者模式可以把产品对象的创建过程移动产品类的外面,可以更细粒度地控制产品对象的创建过程(比如可以选择传哪些零件参数,可以控制产品组装零件的次序等。这与工厂模式不同,工厂模式关心的是拿到最终的产品即可,客户端不关心艰难的创建过程)。下面先呈上其定义和结构图。

定义:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。(这里的创建与表示分离,指的就是前面说的把产品对象的创建过程移动产品类的外面)

结构图(来自GoF):



Director的作用是根据一定的逻辑,指导产品的构建。直观上Director是一个领导,而真正干货(构建产品)的是Builder。Director还有其他作用,比如隔离Client和Builder,达到一定程度上的解耦,还有就是防止产品在其必要参数未初始化(零件没组装)之前,返回给Client使用。

下面以汽车为产品给出一般形式的代码实现:

//首先提供一个产品接口,这个是共享给Client的,不然Client怎么用你的产品
public interface ICar {
//假设只给Client提供一个run方法,不同牌子汽车run起来的方式不一样
public void run();
//这个接口对Client隐藏
protected void add();
}
//实现一辆宝马,注意权限,这个类是对Client透明的
class BMW implements ICar {
//为简化用打印字符串表示宝马run起来的过程
private List<String> sequence = new ArrayList<String>();
protected void add(String s) {
sequence.add(s);
}
public void run() {
for(String s : sequence) {
System.out.println(s);
}
}
}
//然后是Builder接口
public interface IBuilder {
//假设构建一辆可以run的车需要三步,启动引擎(必须)、闪一下灯(可选)、响一下喇叭(可选)
public void buildEngine();
public void buildLight();
public void buildHorn();
public ICar getCar();
}
//BMW的builder
public class BMWBuilder implements IBuilder {
private ICar bmw = new BMW();
public void buildEngine() {
bmw.add("宝马的引擎动起来");
}
public void buildLight() {
bmw.add("宝马的灯亮起来");
}
public void buildHorn() {
bmw.add("宝马的喇叭响起来");
}
public ICar getCar() {
return this.bmw;
}
}
//接着是导演类
public class Director {
//暂时导演只会生产宝马
IBuilder bmwBuilder = new BMWBuilder();
//创建一辆宝马的顺序
public BMW getBMW() {
//先启动引擎
bmwBuilder.buildEngine();
//灯闪一下
bmwBuilder.buildLight();
//喇叭太吵,不要了。直接返回
return (BMW) bmwBuilder.getCar();
}
}
//客户端直接拿到导演,要什么车吩咐它去生产就可以(只要它指导怎么生产)
public class Client {
public static void main(String[] args) {
Director director = new Director();
ICar bmw = director.getBMW();
bmw.run();
}
}


看到这里可能会有这样的疑问,不是说创建与表示分离,客户端可以更灵活地控制产品的创建过程吗?但这里好像都对Client隐藏了啊,这里的Client只知道Director,而且创建过程也一无所知!好像真的是这样。 其实不然,它只不过是把这个灵活的控制过程放到Director里面了,而且不同的控制过程用不同的方法给封装起来了。这种逻辑也是可以接受的,因为具体的Client只需要关系自己的业务逻辑,不应该过于关注某个对象的创建过程(想想如果Builder里面有好几十个builderPart,然后你把Director中getBMW的代码直接放到Client的情况吧)。所以这里的控制过程指的是Director里面,最多Director向Client开放,Client可以自己定制Director。

不过还是有Client直接控制创建过程的情况,它直接省略了Director。这个例子来自《Effective Java》:

//其场景说的是饮料的标签要显示食品的营养成分
public class NutritionFacts {
private final int servingSize;//饮料容量,必须显示
private final int servings;//表示每多少容量含多少下面的成分,必须显示
private final int calories;//卡路里,可选显示
private final int fat;//脂肪,可选显示
private final int sodium;//钠,可选显示
private final int carbohydrate;//碳水化合物,可选显示

public static class Builder {
// Required parameters
private final int servingSize;
private final int servings;

// Optional parameters - initialized to default values
private int calories = 0;
private int fat = 0;
private int carbohydrate = 0;
private int sodium = 0;
//用构造器很精巧的限制了必须初始化部分要先初始化
public Builder(int servingSize, int servings) {
this.servingSize = servingSize;
this.servings = servings;
}

public Builder calories(int val) {
calories = val;
return this;
}

public Builder fat(int val) {
fat = val;
return this;
}

public Builder carbohydrate(int val) {
carbohydrate = val;
return this;
}

public Builder sodium(int val) {
sodium = val;
return this;
}

public NutritionFacts build() {
return new NutritionFacts(this);
}
}

private NutritionFacts(Builder builder) {
servingSize = builder.servingSize;
servings = builder.servings;
calories = builder.calories;
fat = builder.fat;
sodium = builder.sodium;
carbohydrate = builder.carbohydrate;
}

public static void main(String[] args) {
NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8)
.calories(100).sodium(35).carbohydrate(27).build();
}
}


大师写的代码果然让人折服。首先具体的某个产品创建者以静态内部类的形式内嵌在产品里面,这样用Builder的时候前面要带上哪个产品。另外修改Builder的构造器,以限制某些必须的参数(零件)传进来了才能把Builder new出来。而后面可选的参数,客户端可以随心所欲地进行控制。这是给NutritionFacts提供不同构造器,以及用JavaBean提供各属性getter/setter所不具备的优点。(当然他这里只是想演示创建者模式的好处,没有考虑到面向接口编程的问题。而且GoF里面也说了,产品不一定需要一个统一的接口:通常情况下,有具体生成器生成的产品,它们的表示相差是如此之大以至于给不同的产品设置公共父类没有太大意义。所以这点还是要看具体场景)

仔细想想,好像StringBuilder(用于同步的StringBuffer也一样)的append方法就是这种情况(不用Director)。其中StringBuilder就是Builder(同时它也是客户端直接引用的产品),要构造的String(或者内部的char[])是真正的产品,而各重载的append方法就是各buildPart,而且每个append之后都直接返回一个产品(StringBuilder)给客户端。

以上是学习过程中对单例的理解,有问题的地方请不吝指教。

支持原创,转载请注明出处!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息