【深入设计模式】工厂模式—抽象工厂及工厂模式在源码中的应用

前面介绍了简单工厂模式和工厂方法模式,相信你对工厂模式有了一定理解。接下来将继续深入工厂模式,主要介绍工厂方法模式的改进——抽象工厂模式,以及工厂模式在框架源码中的应用,希望在看完这两篇介绍之后能够对彻底搞懂工厂模式,并理解其在实际开发中的意义。

1. 抽象工厂模式

1.1 简介

抽象工厂模式是对抽象方法模式的增强,解决了一个抽象工厂只能生产一个产品的问题。抽象工厂模式在工厂方法模式中的抽象工厂和具体工厂中新增生产产品的方法,从而在进行调用的时候能够拿到多个产品。注意一个工厂生产的多个产品应当是同一类的产品,即产品间有关联(至少在业务逻辑中是有关系的),这样才能够符合设计初衷。

1.2 抽象工厂模式结构

抽象工厂模式结构
/**
 * 抽象工厂类
 */
public class AbstractFactory {
    abstract Product create1();
    abstract Product create2();
    abstract Product create3();
}

/**
 * 具体工厂类 1
 */
public class SpecificFactory1 {
    public Product create1() {
        return new SpecificProduct1();
    }
    public Product create2() {
        return new SpecificProduct2();
    }
    public Product create3() {
        return new SpecificProduct3();
    }
}
/**
 * 具体工厂类 2
 */
public class SpecificFactory2 {
    public Product create1() {
        return new SpecificProduct1();
    }
    public Product create2() {
        return new SpecificProduct2();
    }
    public Product create3() {
        return new SpecificProduct3();
    }
}


/**
 * 抽象产品接口
 */
public interface Product {
    void operation();
}
/**
 * 具体产品 1
 */
public class SpecificProduct1 implements Product {
    public void operation() {
        
    }
}
/**
 * 具体产品 2
 */
public class SpecificProduct2 implements Product {
    public void operation() {
        
    }
}
/**
 * 具体产品 3
 */
public class SpecificProduct3 implements Product {
    public void operation() {
        
    }
}

1.3 抽象工厂模式示例

场景模拟:之前去的超市发现餐馆休息之后,大家没地方吃午餐,来超市买零食充饥,超市老板决定在超市里新增一个快餐口,主要卖套餐盒饭,这样周围的上班族能够选择自己喜欢的菜并很快的吃上午餐。这样在超市里,既能够购买零食,还能够买到便当。


// 商铺(抽象工厂类)及对应具体工厂
public abstract class AbstractShop {
    abstract Food getFood();
    abstract Food getPackedLunch();
}
// 超市(工厂具体实现)
public class Supermarket extends AbstractShop {
    Food getFood() {
        return new Snacks();
    }

    Food getPackedLunch() {
        return new PackedLunch();
    }
}

// 食物(抽象产品)及对应的具体食物
public interface Food {
    void eat();
}
// 零食(具体产品)
public class Snacks implements Food {
    public void eat() {
        System.out.println("eat snacks.");
    }
}
// 盒饭便当(具体产品)
public class PackedLunch implements Food {
    public void eat() {
        System.out.println("eat packed lunch.");
    }
}

// 客户调用部分(Client)
public static void main(String[] args) {
    AbstractShop shop = new Supermarket(); // 去超市
    Food food = shop.getFood(); // 选零食
    food.eat(); // 吃零食
    Food packedLunch = shop.getPackedLunch(); // 选便当
    packedLunch.eat(); // 吃便当
}

控制台打印如下:

eat snacks.
eat packed lunch.

可以看到在同一个超市里面(具体工厂),既能够吃到零食,也能够吃到便当(具体产品),相较于工厂方法模式又有了进一步的增强。

1.4 抽象工厂模式优缺点

优点:

  1. 弥补了工厂方法模式一个工厂只能生产一个产品的缺点,一个工厂可以管理多个产品的生产
  2. 在需要多个相关产品的时候,客户端任然只需要一个工厂对象

缺点:

  1. 同一类产品增加新产品时,需要修改其工厂类

2. 三种工厂模式的比较

  1. 简单工厂模式是最简单的构建对象的模式,但简单工厂模式并不属于 GoF 23 种设计模式。工厂方法模式和抽象工厂模式则是在简单工厂模式的基础上逐步进行改进增强而得到的
  2. 简单工厂模式通过静态方法的方式获取产品,工厂方法模式和抽象工厂模式通过创建工厂子类对象的方式获取产品
  3. 工厂方法模式和抽象工厂模式相比于简单工厂模式在产品生产的选择上进行了改进,将选择权从工厂转移到了客户端,从而达到工厂与产品的解耦,让工厂只专注于生产产品
  4. 工厂方法模式从抽象工厂中衍生出多个具体的工厂,每个工厂只用于生产一个产品;抽象工厂模式同样是从抽象工厂中衍生出多个具体工厂,但是每个工厂可以生产同一类的多个产品,由抽象工厂决定

3. 工厂模式的实际运用

工厂模式的实际应用非常的广泛,这里就只举几个常见的例子来进行说明

3.1 JDK 中的工厂模式

JDK 中的工厂模式运用比较常见的就是当我们在构建线程池的时候会使用到 Executors 类进行构建,而阿里巴巴的 Java 开发手册中强制不使用 Executors,而是使用 ThreadPoolExecutor 进行手动指定参数进行创建。ThreadPoolExecutor 的构造方法由 7 个参数,这个面试经常会问到,下面列举出来顺便复习一下:

  • 核心线程数(corePoolSize):线程池中执行的线程数,即线程池中的线程数,即使没有线程在运行,线程池也会持有这些线程不会销毁
  • 最大线程数(maximumPoolSize):最大执行线程数,线程池中可以执行线程数的最大值
  • 空闲线程存活时间(keepAliveTime):空闲线程在线程池中的存活时间,当空闲线程的存活时间超过该值时线程池会对其进行销毁,知道只剩 corePoolSize 个线程
  • 时间单位(unit):keepAliveTime 空闲线程存活时间的单位
  • 阻塞队列(workQueue):当请求线程数大于 maximumPoolSize 时,将多于线程存入该阻塞队列中,BlockingQueue 也有多种实现
  • 线程工厂(threadFactory):用来生产线程的线程工厂
  • 拒绝策略(handler):当请求线程数超过 maximumPoolSize 并且达到阻塞队列的上限时,对多于线程的处理方式,如直接丢弃、丢弃线程池存在时间最长的线程等

注意到参数中有一个线程工厂,通过 ThreadFactory 这个接口传入,在线程池构建 Worker 的时候会通过调用传入线程工厂对象的 newThread() 方法创建Worker 持有的线程。

// ThreadFactory 接口
public interface ThreadFactory {
    /**
     * Constructs a new {@code Thread}.  Implementations may also initialize
     * priority, name, daemon status, {@code ThreadGroup}, etc.
     *
     * @param r a runnable to be executed by new thread instance
     * @return constructed thread, or {@code null} if the request to
     *         create a thread is rejected
     */
    Thread newThread(Runnable r);
}

// Worker 构造方法
Worker(Runnable firstTask) {
    setState(-1); // inhibit interrupts until runWorker
    this.firstTask = firstTask;
    this.thread = getThreadFactory().newThread(this);
}

这里便是工厂模式的典型应用,在构建 Worker 的时候我们并不关心他是怎么船舰线程的,直接调用对应工厂的 newThread() 方法创建线程即可。在这里的应用中,ThreadFactory 就是抽象工厂,而下面的各种实现类如DefaultThreadFactory、DaemonThreadFactory 等就是对应的具体工厂,而最终构建的线程 Thread 就是工厂所生产的产品。


ThreadFactory 接口及实现类

3.2 Spring 中的工厂模式

Spring 中的工厂模式最有名的就是 BeanFactory 这个接口了,因为我们在获取 bean 时基本上都是从这个接口的实现类中获取的,具体获取过程可以参考:Spring源码分析——获取Bean

BeanFactory 接口及实现

从上面的简化图中可以看到在 BeanFactory 这个 bean 工厂体系中,BeanFactory 接口便是抽象工厂,而在实际调用中用的时下面的具体实现子类(具体工厂),如 AbstractBeanFactory 、AbstractApplicationContext、 DefaultListableBeanFactory 等等,而在调用 getBean() 等方法之后获取到的具体实例便是生产出的产品。

3.3 MyBatis 中的工厂模式

在 MyBatis 中有一个接口叫做 SqlSessionFactory ,有 DefaultSqlSessionFactory、SqlSessionManager 这两个。该类的作用在于获取到 SqlSession 对象,并利用该对象对数据库进行增删改查操作,具体源码分析可以参考:MyBatis源码分析——使用SqlSession操作数据库

SqlSession 和 SqlSessionFactory 接口及实现

在 SqlSessionFactory 这个体系中,各个类在抽象工厂模式中的应用:

  • 抽象工厂:接口 SqlSessionFactory
  • 具体工厂:DefaultSqlSessionFactory、SqlSessionManager 便是对应的具体工厂,这个两个工厂用于通过调用 openSession() 方法用于获取 SqlSession 对象
  • 抽象产品:SqlSession 对象便是具体的抽象产品
  • 具体产品:DefaultSqlSession、SqlSessionManager、SqlSessionTemplate 这三个类实现接口 SqlSession ,便是具体的产品

从接口 SqlSessionFactory 中可以看到该工厂不仅能够生产 SqlSession 对象,还能生产 Configuration ,因此整个 SqlSessionFactory 和 SqlSession 体系是个非常标准的抽象工厂模式。


4. 相关参考

【深入设计模式】工厂模式—简单工厂和工厂方法

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

推荐阅读更多精彩内容