抽象工厂模式

意图

抽象工厂模式是一种创建型设计模式, 它能创建一系列相关的对象, 而无需指定其具体类。

简单来说,抽象工厂模式下,我们实际的对象创建都交给了一些工厂类去做,这些工厂类根据具体需求不同,可以创建出很多种类型的工厂类。

问题场景

比如去家具城买家具,如果确定了某种风格的家居装饰(比如:中式),那后续在家具城买的桌子,椅子、床等等家具都是类似风格。

此时需要设法去单独生成每一件家具对象,保证出于一致的风格,最后才能让整个房间看着和谐统一。

但是在代码实现过程中,我们也不希望每次增加新的装修风格时,都需要对已有的代码进行调整,并且家具的产品目录更新迭代非常快,要保证每次调整只是新增,而非修改既有代码。

解决方案

那么针对上面的情况,在抽象工厂模式下,可以为每件家具产品确定一个声明接口,比如椅子,就创建一个Chair接口;然后根据风格不同,每种风格都实现这些接口,这样就组成了一个继承关系:

Xnip2022-11-05_14-40-11.jpg

然后,接下来就需要声明一些抽象工厂,用于创建这一系列的具体家具产品,比如:创建一个FurnitureFactory,它里面定义一系列创建产品的方法,createChair、createSofa等等。

注意,定义的这些方法,返回的都是抽象产品,并不是具体类型的实际产品,然后根据风格,比如ChineseFurnitureFactory、ModerFurnitureFactory。每一种具体的Factory里面才生产具体风格类型的产品。

这么一处理之后,整个模型就差不多出来了:

  • 首先是有了一个抽象工厂类FurnitureFactory,里面定义了一系列抽象方法,主要是各种create方法,创建各种家具产品(桌子、椅子、沙发这类),但是这里的create方法返回类型都是抽象类型

  • 然后定义一系列具体类型的家具工厂类,在这些具体的家具工厂类中,重写各种create方法,来生成具体风格类型的家具产品。

  • 与此同时,定义一些家具产品的接口类(抽象类型),比如Chair、Sofa等等

  • 然后就可以引入一系列各种风格的具体产品类,比如中式椅子(ChineseChair)

这样后续如果有新的风格加入了,比如现在客人需要的是维多利亚风格的家具,那么很简单,FurenitureFactory体系中,加入一种新的VictorianFurnitureFactory,继承自FurenitureFactory,然后创建一系列Victorian风格的家居产品类,继承对应产品类型的抽象类,如:VictorianChair implements Chair等。

代码示意

这里结合一个场景给出一些代码示例,假如我们在开发中,需要开发一些界面,界面上有一系列相关的元素组件,这里只举 Button和Checkbox为例。

此时界面上需要展示一些按钮和复选框,但是不能一刀切全部都是同一种风格,要根据所处平台的不同,展示不同风格的界面:Windows风格、macOS风格等。

此时可以参考上面介绍过的抽象工厂方法模式,我们最终想要的效果可能就是:通过一个GUI对象,调用它的paint方法,根据不同所处的不同平台,最终渲染出不同风格的组件效果。

那么此时可以先设计一个抽象工厂类,它的作用主要就是用来createButton、createCheckbox。此时这两个create方法返回值类型必须是抽象的类型,因此需要有一个Button接口和Checkbox接口作为抽象类型。

然后根据平台需要,这里暂定两种风格:Windows、Mac。

那么就需要有具体风格类型的工厂类,用于创建对应风格的产品:WindowsFactory、MacFactory。这两种factory内部都有对应的create方法,用于创建对应平台风格的产品组件。

然后针对产品组件,也需要进行风格分类,因此就有了WindowsButton、MacButton、WindowsCheckbox、MacCheckbox,它们各自实现Button和Checkbox接口。

WindowsFactory里面创建Windows风格的按钮和复选框对象,MacFactory则创建Mac风格的按钮和复选框对象

Xnip2022-11-05_15-05-48.jpg

源代码

/**
 * 这是一个抽象的工厂,它可以通配所有的产品类型
 */
public interface GUIFactory {
    Button createButton();
    Checkbox createCheckbox();
}

public class MacOSFactory implements GUIFactory{
    @Override
    public Button createButton() {
        return new MacOSButton();
    }

    @Override
    public Checkbox createCheckbox() {
        return new MacOSCheckbox();
    }
}

public class WindowsFactory implements GUIFactory{
    @Override
    public Button createButton() {
        return new WindowsButton();
    }

    @Override
    public Checkbox createCheckbox() {
        return new WindowsCheckbox();
    }
}

public interface Button {
    void paint();
}

public class MacOSButton implements Button {
    @Override
    public void paint() {
        System.out.println("MacOS Button created!");
    }
}

public class WindowsButton implements Button{
    @Override
    public void paint() {
        System.out.println("Windows Button created!");
    }
}

public interface Checkbox {
    void paint();
}

public class MacOSCheckbox implements Checkbox {
    @Override
    public void paint() {
        System.out.println("MacOS checkbox created!");
    }
}

public class WindowsCheckbox implements Checkbox{
    @Override
    public void paint() {
        System.out.println("Windows Checkbox created!");
    }
}

客户端代码

/**
 * 客户端代码
 * 此时Factory并不关心具体的Factory类型使用,而是通过抽象接口来进行通配
 */
public class Application {
    private Button button;
    private Checkbox checkbox;

    public Application(GUIFactory factory) {
        button = factory.createButton();
        checkbox = factory.createCheckbox();
    }

    public void paint() {
        button.paint();
        checkbox.paint();
    }
}

Demo.java

public class Demo {
    /**
     * 程序中需要关注factory的类型,并且在运行中创建对应的对象
     * 通常使用配置或者环境变量来实现具体类型的创建和初始化
     */
    private static Application configureApplication() {
        Application app;
        GUIFactory factory;
        String osName = System.getProperty("os.name").toLowerCase();
        if (osName.contains("mac")) {
            factory = new MacOSFactory();
        } else {
            factory = new WindowsFactory();
        }
        app = new Application(factory);
        return app;
    }

    public static void main(String[] args) {
        Application app = configureApplication();
        app.paint();
    }
}

实际应用案例

java.sql.Connection

学习过Java的人,学到一定程度,一定都会与数据库打交道,当初在学习Java的过程中,第一次遇到了数据库的问题后,在没有任何其他ORM框架引入的时候,首选的都是java.sql包下面的相关数据库接口来操作数据库。
和数据库打交道,就不得不面临一个类的使用:java.sql.Connection

public interface Connection extends Wrapper, AutoCloseable{
  //执行静态SQL
  Statement createStatement() throws SQLException;
  //预占位的SQL
  PreparedStatement prepareStatement(String sql) throws SQLException;
  //执行数据库存储过程
  CallableStatement prepareCall(String sql) throws SQLException;
  ......
}

而关于Statement。它实际上是一个接口:

public interface Statement extends Wrapper, AutoCloseable {
......
}

出于安全考虑,Java的课程里面都会给出建议,考虑使用PreparedStatement
这里它就涉及到了抽象工厂模式:
首先最大的抽象工厂是Connection,它里面有一个抽象产品叫Statement,同时Statement本身也是一个抽象工厂,它里面也有一些抽象产品PreparedStatementCallableStatement
那么它这么做有什么好处呢?

好处

我们知道数据库是有很多的,常见的关系型数据库:MySQL、Oracle、SQL Server等等。不同的数据库厂商就会有不同的实现,这样以来,以后不管对接多少数据库种类,各大厂商无需对JDK内部的这个Connection进行修改,只需要继承它,扩展相应的子类就可以达到数据库连接的多样化。这样JDK相当于只是提供了一个标准框架,后续所有行为都在这个框架体系内,这样可以很容易做扩展和对接。

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

推荐阅读更多精彩内容