MacOS, Java和IDEAJava 杂谈

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

2019-08-22  本文已影响1人  SpaceCat

抽象工厂模式

用途

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模式适用于:

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


BuilderPattern

进一步说明

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

直接在类内部创建和组装一个复杂对象是非常不灵活的,这样会导致类内部有大量创建复杂对象表示的代码,不修改类无法修改对象的表示。
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模式的优势:

Builder模式的劣势:

维基百科给出了如下例子:
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工厂方法,是因为它负责“生产”对象。

说明

工厂方法模式适用于:

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


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

参考资料

上一篇下一篇

猜你喜欢

热点阅读