抽象工厂、Builder模式和工厂方法

抽象工厂模式

用途

Provide an interface for creating families of related or dependent objects without specifying their concrete classes.
From: Design Patterns: Elements of Reusable Object-Oriented Software

提供一个接口,使得当创建一系列的相关或者相互依赖的对象时,不需要一一指定它们具体的类。

例子

下面是一个图形界面工具包的例子。像窗口、滚动条等组件,支持不同的样式。样式可以由界面的不同主题决定。这样,如果在整个应用中写死不同主题的不同按钮样式,非常不好维护。
为了解决这个问题,使应用便于在各个主题之间切换移植。我们可以定义一个抽象的WidgetFactory类,在其中定义创建每一种组件的接口。同样地,对于每一种组件,我们也有一个抽象类,每一种具体主题下的组件都是这个类的子类。WidgetFactory类中创建组件的接口就是创建一个新的组件实例。
这样,客户端调用WidgetFactory类的接口来得到各种组件的实例,但是,客户端并不知道获得的具体组件类是哪个。因此,就实现了调用方和具体主题的解耦。如下图。


WidgetFactory

从这里可以看出,抽象工厂类主要用于产生一系列相关的类或者一类对象。使用了抽象工厂之后,如果想要切换获得对象的类别或者系列,只需要改动抽象工厂的实例即可。通过这样,就改变了所有获得对象的主题,从而实现了调用方和具体对象的解耦。

说明

AbstractFactory defers creation of product objects to its ConcreteFactory subclass.
From: Design Patterns: Elements of Reusable Object-Oriented Software
抽象工厂将产品对象的创建下放到了具体的工厂子类中。

脱离了具体的场景之后,抽象工厂类的示意图如下:


AbstractFactory

使用抽象工厂,可以用来控制产品的一致性。当多个产品被设置为一个系列之后,应用使用同一个系列内的产品是很重要的。抽象工厂模式使这种控制变得容易。
使用抽象工厂的坏处是,支持新的产品比较麻烦。因为在抽象工厂的接口中已经固定了支持哪些产品,新增支持新的产品就需要修改这个接口。

例子

AbstractFactory.Cpu:

package AbstractFactory;

/**
 * Created by chengxia on 2019/8/16.
 */
public abstract class Cpu {
    private String name;

    public Cpu(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

AbstractFactory.PhoneCpu:

package AbstractFactory;

/**
 * Created by chengxia on 2019/8/16.
 */
public class PhoneCpu extends Cpu {
    public PhoneCpu(String name) {
        super(name);
    }
}

AbstractFactory.ComputerCpu:

package AbstractFactory;

/**
 * Created by chengxia on 2019/8/16.
 */
public class ComputerCpu extends Cpu {
    public ComputerCpu(String name) {
        super(name);
    }
}

AbstractFactory.Screen:

package AbstractFactory;

/**
 * Created by chengxia on 2019/8/16.
 */
public abstract class Screen {
    private String name;

    public Screen(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

AbstractFactory.PhoneScreen:

package AbstractFactory;

/**
 * Created by chengxia on 2019/8/16.
 */
public class PhoneScreen extends Screen {
    public PhoneScreen(String name) {
        super(name);
    }
}

AbstractFactory.ComputerScreen:

package AbstractFactory;

/**
 * Created by chengxia on 2019/8/16.
 */
public class ComputerScreen extends Screen {
    public ComputerScreen(String name) {
        super(name);
    }
}

AbstractFactory.AbstractComponentFactory:

package AbstractFactory;

/**
 * Created by chengxia on 2019/8/16.
 */
public class AbstractComponentFactory {
    public String getCPU(){
        return "Abstract CPU.";
    }
    public String getScreen(){
        return "Abstract Screen.";
    }
}

AbstractFactory.PhoneComponentFactory:

package AbstractFactory;

/**
 * Created by chengxia on 2019/8/16.
 */
public class PhoneComponentFactory extends AbstractComponentFactory{
    @Override
    public String getCPU(){
        return "Qualcomm Snapdragon CPU: 820, 821, 845, 855 ...";
    }

    @Override
    public String getScreen(){
        return "Computer Screen: JDI LED, BOE OLED, Samsung AMOLED ...";
    }
}

AbstractFactory.ComputerComponentFactory:

package AbstractFactory;

/**
 * Created by chengxia on 2019/8/16.
 */
public class ComputerComponentFactory extends AbstractComponentFactory{
    @Override
    public String getCPU(){
        return "Computer CPU: i3, i5, i7 ...";
    }
    @Override
    public String getScreen(){
        return "Computer Screen: Samsung LCD ...";
    }
}

类图如下:


AbstactFactoryExample

下面如果有手机或者电脑厂商调用上面的类的时候,可以如下:
AManufacturer.Xiaomi:

package AManufacturer;

import AbstractFactory.AbstractComponentFactory;
import AbstractFactory.PhoneComponentFactory;

/**
 * Created by chengxia on 2019/8/19.
 */
public class Xiaomi {
    public static void main(String []args){
        AbstractComponentFactory ab = new PhoneComponentFactory();
        System.out.println(ab.getCPU());
        System.out.println(ab.getScreen());
    }
}

AManufacturer.Lenovo:

package AManufacturer;

import AbstractFactory.AbstractComponentFactory;
import AbstractFactory.ComputerComponentFactory;

/**
 * Created by chengxia on 2019/8/19.
 */
public class Lenovo {
    public static void main(String []args){
        AbstractComponentFactory ab = new ComputerComponentFactory();
        System.out.println(ab.getCPU());
        System.out.println(ab.getScreen());
    }
}

Builder模式

用途

Separate the construction of a complex object from its representation so that the same construction process can create different representations.
From: Design Patterns: Elements of Reusable Object-Oriented Software

将复杂对象的创建,从其表示中剥离。同样的创建过程,会创建出不同的对象。

例子

一个RTF富文本阅读器,应该能够将富文本格式转化为各种文本格式,如ASCII、纯文本等。所以,目标格式多种多样,而且不确定。所以,设计上,应该让添加一种格式转化非常容易,最好不用修改reader本身的代码。
一种解决办法是,让RTFReader类调用TextConverter对象(该对象负责将RTF转化为另一种文本表示)。每当RTFReader认出一个RTF标记符时,它向TextConverter发起调用来转化这个标记。TextConverter对象既负责格式转化,也负责将标记表示成特定的格式。各种具体格式的转化和表示,通过TextConverter的子类来实现。如下图:


RTFReader

从上图可以看出,每一个Converter子类,实现了具体的复杂对象的创建和组装,将这些复杂的逻辑隐藏在了抽象接口之后,和调用方reader隔离开来。这样,reader只负责解析RTF文档就可以了。
这里每一个converter类称为builder,而reader称为director。这个例子中,Builder模式实现了RTF解析算法和RTF表示格式创建的分离。这让我们只需要配置不同的TextConverter子类就能够复用RTFReader的解析逻辑。

说明

Builder模式适用于:

  • the algorithm for creating a complex object should be independent of the parts that make up the object and how they're assembled.(创建复杂对象的算法应该和对象组装表示的过程分离。)
  • the construction process must allow different representations for the object that's constructed.(创建过程必须兼容对象的不同的表示形式,也就是不同的子类。)

抛除具体场景之后,示意图如下:


BuilderPattern

进一步说明

上面关于Builder模式的说明是来自《Design Patterns: Elements of Reusable Object-Oriented Software》。里面提到的例子不是特别好理解。可以参考维基百科的说明。
Builder模式主要解决如下问题:

  • How can a class (the same construction process) create different representations of a complex object?(一个类如何创建不同表示的复杂对象。)
  • How can a class that includes creating a complex object be simplified?(如何让一个创建复杂对象的类变得简单?)

直接在类内部创建和组装一个复杂对象是非常不灵活的,这样会导致类内部有大量创建复杂对象表示的代码,不修改类无法修改对象的表示。
Builder模式如下解决这个问题:

  • Encapsulate creating and assembling the parts of a complex object in a separate object.(将复杂对象的创建和组装封装在一个单独的对象中)
  • A class delegates object creation to a Builder object instead of creating the objects directly.(一个类将对象创建的过程委托给一个单独的Builder对象,而不是自己直接创建。)

Builder模式的初衷就是将复杂对象的创建和表示分离开来,通过这样,可以用同一个创建过程创建成不同表示的对象。(The intent of the Builder design pattern is to separate the construction of a complex object from its representation. By doing so the same construction process can create different representations.)
Builder模式的优势:

  • Allows you to vary a product’s internal representation.(允许你创建一个产品的不同内部表示)
  • Encapsulates code for construction and representation.(实现将创建对象和表示对象的代码封装)
  • Provides control over steps of construction process.(实现对象构件过程的逐步控制)

Builder模式的劣势:

  • Requires creating a separate ConcreteBuilder for each different type of product.(对于每一种不同的对象,都需要一个单独的Builder。)
  • Requires the builder classes to be mutable.(需要Builder类是可变的)。
  • Data members of class aren't guaranteed to be initialized.(类的数据成员不保证都被初始化)
  • Dependency injection may be less supported.(对于依赖注入的支持不好。)

维基百科给出了如下例子:
WikiBuilder.Car:

package WikiBuilder;

/**
 * Created by chengxia on 2019/8/21.
 */
public class Car {
    int wheel;
    String color;

    public Car(int wheel, String color) {
        this.wheel=wheel;
        this.color=color;
    }

    @Override
    public String toString() {
        return "Car [wheels = " + wheel+ ", color = " + color + "]";
    }
}

WikiBuilder.CarBuilder:

package WikiBuilder;

/**
 * The builder abstraction.
 * */
public class CarBuilder {
    int wheel;
    String color;

    public CarBuilder setWheel(int wheel){
        this.wheel=wheel;
        return this;
    }
    public CarBuilder setColor(String color) {
        this.color=color;
        return this;
    }
    public Car getCar() {
        return new Car(wheel,color);
    }
}

WikiBuilder.Client:

package WikiBuilder;

/**
 * Created by chengxia on 2019/8/21.
 */
public class Client {
    public static void main(String[] arg) {
        Car c= new CarBuilder().setWheel(4).setColor("Black").getCar();
        System.out.println(c);
    }
}

运行上面的测试类,输出如下:

Car [wheels = 4, color = Black]

Process finished with exit code 0

Builder模式和静态内部类

builder模式的作用将一个复杂对象的构建与他的表示分离,使用者可以一步一步的构建一个比较复杂的对象。因此,在好多时候,我们可以通过创建一个静态内部类(区别于一般的内部类,静态内部类属于所嵌入的外部类,而不是所嵌入外部类的对象,可以直接通过外部类访问实例化,不需要依赖于外部对象),来实现Builder模式。如下:
StaticInnerBuilder.Product:

package StaticInnerBuilder;

/**
 * Created by chengxia on 2019/8/22.
 */
public class Product {
    private final int id;
    private final String name;
    private final int type;
    private final float price;

    private Product(Builder builder) {
        this.id = builder.id;
        this.name = builder.name;
        this.type = builder.type;
        this.price = builder.price;
    }

    @Override
    public String toString(){
        return "Builder{id:"+id+", name:"+name+", type:"+type+", price:"+price+"}";
    }

    public static class Builder {
        private int id;
        private String name;
        private int type;
        private float price;

        public Builder id(int id) {
            this.id = id;
            return this;
        }
        public Builder name(String name) {
            this.name = name;
            return this;
        }
        public Builder type(int type) {
            this.type = type;
            return this;
        }
        public Builder price(float price) {
            this.price = price;
            return this;
        }

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

}

StaticInnerBuilder.Test:

package StaticInnerBuilder;

/**
 * Created by chengxia on 2019/8/22.
 */
public class Test {
    public static void main(String []args){
        Product p = new Product.Builder().id(1).name("Test").type(3).price(3.14f).build();
        System.out.println(p);
    }
}

从这个例子中,可以看出,这样写的时候,可以在构造对象的时候采用链式写法,比较有意思。运行前面的测试类输出如下:

Builder{id:1, name:Test, type:3, price:3.14}

Process finished with exit code 0

工厂方法模式

用途

Define an interface for creating an object, but let subclasses decide which class to instantiate. Factory Method lets a class defer instantiation to subclasses.
From: Design Patterns: Elements of Reusable Object-Oriented Software

定义一个创建对象的接口,但是让子类去决定实例化哪个具体类。工厂方法让一个类将实例化的过程下放到子类中。

例子

以一个能够开发给用户展示多种Document的应用框架为例。这个框架的两个重要的类是Application和Document,这两个类都是抽象的,使用时必须要根据具体的应用场景做具体的实现。当我们开发一个绘图应用时,我们要自己实现DrawingApplication和DrawingDocument。这个Application类负责管理Document,比如当用户点击菜单新建时,需要创建一个新的Document类。
由于具体的要实例化哪个Application子类,是由具体要实现的应用确定的。Application类并不知道要实例化哪个Document子类,他只知道什么时候创建,仅此而已。这就进退两难了:框架需要实例化类,但是它只知道抽象类,但是这个类不能被实例化。
这时候,工厂方法提供了一种解决方案:它对创建哪个Document类的子类做了封装,然后将这一过程移到框架外。(It encapsulates the knowledge of which Document subclass to create and moves this knowledge out of the framework.)如下图。


Document

Application subclasses redefine an abstract CreateDocument operation on Application to return the appropriate Document subclass. Once an Application subclass is instantiated, it can then instantiate application-specific Documents
without knowing their class. We call CreateDocument a factory method because it's
responsible for "manufacturing" an object.
Application类的子类重新写了一个Application的CreateDocument方法,用来返回一个合适的Document子类。一旦这个Application类的子类实例化完成,它就能够实例化合乎这个Application子类的Document类,而不用知道具体的它们(Document类)具体的类。
我们之所以叫CreateDocument工厂方法,是因为它负责“生产”对象。

说明

工厂方法模式适用于:

  • 一个类不能预测它将要创建的类对象时。
  • 一个类想让它的子类确定具体实例化哪个类。
  • classes delegate responsibility to one of several helper subclasses, and you want to localize the knowledge of which helper subclass is the delegate.(这句话翻不了,直接拿过来吧)

抛除具体场景后,类图如下:


FactoryMethod

工厂方法使得无需在代码中绑定具体应用的类,在代码中,只需和Product接口打交道。因此,能够适用于任何用户自定义的具体ConcreteProduct类。
工厂方法一个不好的地方在于,用户必须要创建一个Creator类的子类,来创建一个具体的ConcreteProduct对象。如果本来就有这样的继承层次还好,否则有些麻烦。
前面的例子中,工厂方法只通过Creator调用。但是,有时候,会出现类的平行继承关系。如下:


FigureManipulator

在这个例子中,通过工厂方法,实际上连接了两个平行的继承层次:Figure和Manipulator。工厂方法定义了两个类层次之间的联系,通过它,实际上实现了类之间联系的内聚。

例子

代码如下:
FactoryMethod.Shape:

package FactoryMethod;

/**
 * Created by chengxia on 2019/8/19.
 */
public abstract class Shape {

    public String name;
    public Shape(String aName){
        name = aName;
    }
    //画shape
    public abstract void draw();
    //擦去shape
    public abstract void erase();
}

FactoryMethod.Square:

package FactoryMethod;

/**
 * Created by chengxia on 2019/8/19.
 */
//方形子类
public class Square extends Shape {

    //构造函数
    public Square(String aName){
        super(aName);
    }

    public void draw() {
        System.out.println("It will draw a Square.");
    }
    public void erase() {
        System.out.println("It will erase a Square.");
    }
}

FactoryMethod.Circle:

package FactoryMethod;

/**
 * Created by chengxia on 2019/8/19.
 */
// 圆形子类
public class Circle extends Shape {

    // 构造函数
    public Circle(String aName){
        super(aName);
    }

    public void draw() {
        System.out.println("It will draw a Circle.");
    }
    public void erase() {
        System.out.println("It will erase a Circle.");
    }
}

FactoryMethod.ShapeFactory:

package FactoryMethod;

/**
 * Created by chengxia on 2019/8/19.
 */
public abstract class ShapeFactory {
    protected abstract Shape factoryMethod(String aName);
    //在operOnShape中定义对Shape的一系列使用操作
    public void operOnShape(String aName){
        Shape s = factoryMethod(aName);
        System.out.println("The current using shape is: " + s.name);
        s.draw();
        s.erase();
    }
}

FactoryMethod.SquareFactory:

package FactoryMethod;

/**
 * Created by chengxia on 2019/8/19.
 */
//定义返回Square实例的SquareFactory
public class SquareFactory extends ShapeFactory {
    //重载factoryMethod方法,返回Circle对象
    protected Shape factoryMethod(String aName) {
        return new Square(aName + " (created by SquareFactory)");
    }
}

FactoryMethod.CircleFactory:

package FactoryMethod;

/**
 * Created by chengxia on 2019/8/19.
 */
//定义返回Circle 实例的CircleFactory
public class CircleFactory extends ShapeFactory {
    //重载factoryMethod方法,返回Circle对象
    protected Shape factoryMethod(String aName) {
        return new Circle(aName + " (created by CircleFactory)");
    }
}

类图如下:
[图片上传失败...(image-7f4400-1566435759539)]
接下来写一个测试类,来调动上面的程序结构,如下。
FactoryMethod.TestFactoryMethod:

package FactoryMethod;

/**
 * Created by chengxia on 2019/8/19.
 */
public class TestFactoryMethod {
    public static void main(String[] args){
        ShapeFactory sf1 = new SquareFactory();
        ShapeFactory sf2 = new CircleFactory();
        sf1.operOnShape("Shape one");
        sf2.operOnShape("Shape two");
    }
}

从这个例子可以看出,ShapeFactory实际上将实例化Shape的职能下放到了子类SquareFactory和CircleFactory。这样,它就只需要关注具体Shape实例的行为,关注对于Shape实例的操作,而无需关注该实例是如何实例化的。

运行上面的测试例子,结果如下:

The current using shape is: Shape one (created by SquareFactory)
It will draw a Square.
It will erase a Square.
The current using shape is: Shape two (created by CircleFactory)
It will draw a Circle.
It will erase a Circle.

Process finished with exit code 0

参考资料

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,524评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,869评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,813评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,210评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,085评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,117评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,533评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,219评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,487评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,582评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,362评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,218评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,589评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,899评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,176评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,503评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,707评论 2 335

推荐阅读更多精彩内容