深入源码理解【工厂模式】

点赞的靓仔,你是人群中最闪耀的光芒

开题

小学6年级的时候老师讲,好好学习、现在暂时辛苦,等上初中就好了,至少我的初中生活并不轻松。 初三, 现在辛苦,等高中就好了,高中生涯更是苦逼中的战斗机。而高三老师说,上大学就好了,上了大学发现,是真的好,没有学业、作业的压力。可以自由飞翔,却不料那是整个人生最后的狂欢,踏入社会从此就是一台不高速运转就会停摆的机器。以后会将更多的业余时间拿来学习。

2年前在做Java讲师的时候,经常会将设计模式分享给学生,学习经典的框架设计。很多框架的设计都用到了设计模式,这里将自己对设计模式的理解与各位分享。

从高内聚、低耦合说起

从入行到现在,便听众人常挂在嘴边的话语,从初级程序员到资深程序员必学的最高内功心法:高内聚、低耦合。要写出高内聚、低耦合的代码,需要一定的功力。而有这样一群人,将代码编写的经验总结为一套武功秘籍,这就是设计模式。

设计模式6大原则

设计模式也遵守一定的规范,并不是随随便便就做出一套设计模式。而这规范就是设计模式六大原则。

  1. 开闭原则(Open Close Principle)

开闭原则的意思是:对扩展开放,对修改关闭。

  1. 里氏代换原则(Liskov Substitution Principle)

任何基类可以出现的地方,子类一定可以出现。

  1. 依赖倒转原则(Dependence Inversion Principle)

这个原则是开闭原则的基础,具体内容:针对接口编程,依赖于抽象而不依赖于具体。

  1. 接口隔离原则(Interface Segregation Principle)

这个原则的意思是:使用多个隔离的接口,比使用单个接口要好。它还有另外一个意思是:降低类之间的耦合度。由此可见,其实设计模式就是从大型软件架构出发、便于升级和维护的软件设计思想,它强调降低依赖,降低耦合。

  1. 迪米特法则,又称最少知道原则(Demeter Principle)

最少知道原则是指:一个实体应当尽量少地与其他实体之间发生相互作用,使得系统功能模块相对独立

  1. 合成复用原则(Composite Reuse Principle)
    合成复用原则是指:尽量使用合成/聚合的方式,而不是使用继承。

以上内容基本是复制的理论知识,并不是太深奥的东西,理解即可。

工厂模式

在现实中,工厂是用来生产各种产品的,而在面向对象的编程世界,一切皆对象,工厂模式就是用来创建我们需要的产品,即对象。
而工厂模式又分为:简单工厂、静态方法工厂、抽象工厂三种。我们先来看一波三种工厂的UML图,比较三种模式的区别。


image
image
image

由图中可以看到,三种模式在产品类的结构不变的情况下,针对工厂类有不同的实现,具体如下。

简单工厂

简单工厂的工厂类比较简单,提供一个接收字符串并返回产品的方法,在方法内部根据传入的字符串而返回对应的对象。
工厂类代码SimpleFactory如下。

public class SimpleFactory implements Factoty{

    @Override
    public Sender getSender(String senderCode) {
        switch (senderCode){
            case "email":
                return new EmailSender();
            case "phone":
                return new PhoneSender();
        }
        return null;
    }
}

可能大家能看出区别,这里的SimpleFactory与UML图中并不一致,二十实现了一个Factory工厂。其实这里是否实现是根据自己的代码设计而来,并不是必须是普通类或者是接口。再来看下一测试代码。

public class SimpleTest {

    public static void main(String[] args) {
        SimpleFactory factory = new SimpleFactory();
        Sender email = factory.getSender("email");
        email.send("隔壁老王叫你早点回家吃饭");
    }

}
image

整个代码的实现比较简单,不知道大家看测试代码的时候有没有熟悉的感觉的?
没错,这就是Spring的Factory的实现,下面我们来感受一下Spring容器的调用。

public class SpringTest {

    public static void main(String[] args) {
        //获取Spring容器对象
        ApplicationContext app = new ClassPathXmlApplicationContext("");
        //从容器获取对象
        SpringTest bean = app.getBean("",SpringTest.class);
        //调用方法
        bean.run();
    }
    public void run(){
        System.out.println("run");
    }
}

对比发下,真的是像两个亲兄弟。其实,Spring的BeanFactory就是使用简单工厂模式来实现的。上源码。

public interface BeanFactory {
    String FACTORY_BEAN_PREFIX = "&";

    Object getBean(String var1) throws BeansException;

    <T> T getBean(String var1, Class<T> var2) throws BeansException;

    Object getBean(String var1, Object... var2) throws BeansException;

    <T> T getBean(Class<T> var1) throws BeansException;

    <T> T getBean(Class<T> var1, Object... var2) throws BeansException;

    <T> ObjectProvider<T> getBeanProvider(Class<T> var1);

    <T> ObjectProvider<T> getBeanProvider(ResolvableType var1);
    
    // more code
}

ApplicationContext的getBean方法是继承于BeanFactory顶级父类,结构如下。

image

静态方法工厂

静态方法工厂的工厂类不再提供一个创建产品的方法,而是为每个产品提供一个静态方法。代码如下。

/**
 * 静态方法工厂模式
 */
public class StaticFactory {
    //Email生产方法
    public Sender getEmailSender(){
        return new EmailSender();
    }
    //Phone生产方法
    public Sender getPhoneSender(){
        return new PhoneSender();
    }
}

对于静态方法工厂模式的应用场景,我查阅了资料并没有找到对其应用场景的具体说明。
这一点我个人理解的是:静态工厂的每一个不同的生产对象都需要提供一个静态方法,而如果生产的对象非常多,那么这个工厂内中将会有非常多的静态方法,比如1000个类,需要1000个方法来创建,而10000个呢? 所以我判断静态方法工厂模式不太适合创建多种对象,而适合用来创建少量的对象,而这个创建对象的代码可能在很多地方都会使用到,所以使用静态方法来创建,这样就不需要每次都来创建工厂对象了。
我的推断来源于JDK源码。

Calendar的静态方法工厂

Calender对外提供了4个创建对象的getInstance方法, 分别对应不同的实现,源码如下。

image

Executers的静态方法工厂

线程池是工作中比较常用的,而Executers工具类也提供了4种线程池的默认实现。也是使用了静态方法工厂模式来实现的。

public class ExecutersDemo {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        ExecutorService executorService1 = Executors.newFixedThreadPool(5);
        ExecutorService executorService2 = Executors.newCachedThreadPool();
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(5);
    }
}

其实,类似的还要很多,比如包装类的valueOf方法、NumberFormat的newInstance方法等等,通过源码研究,我们可以轻易的找到静态工厂方法的套路:

  1. 可能使用的地方较多,而通过静态方法以避免创建过多的工厂对象
  2. 不适用过多的生产对象方式,通常一类的静态工厂方法数量不会超过10个
  3. 其实就是简单的静态方法创建对象

抽象工厂模式

抽象工厂模式我认为是对静态工厂模式的进一步提升,同样,其工厂的复杂程度也相应的提升。先上测试代码。

AbstractFactory

public abstract class AbstractFactory {
    //抽象层提供创建对象的方法
    public abstract Sender getSender();
}

EmailFactory

public class EmailFactory extends AbstractFactory{
    @Override
    public Sender getSender() {
        return new EmailSender();
    }
}

PhoneFactory

public class PhoneFactory extends AbstractFactory{
    @Override
    public Sender getSender() {
        return new PhoneSender();
    }
}

抽象工厂在静态工厂的基础上做了升级,静态工厂是一个对象对应一个方法,而抽象工厂则直接使用一个工厂对应一个对象。

这样的抽象工厂模式的理解是我在最初学习工厂模式时候的理解。但当时并没有深入研究抽象工厂的应用场景,以至于对抽象工厂的理解有一些偏差,这一次一起将这个坑给补上。

首先,抽象工厂模式的应用场景并不是创建单一的产品对象。而是用来创建一个系列的产品,这个概念称之为‘产品族’,看图。

image

这是从业务场景剥离出来的结构,也不难理解。 比如一个贷款功能, 图种可以看到有两种角色,一种是产品工厂,一种是产品,这一点和我们前面讲到的工厂模式是类似的,但是区别在于,每个工厂不再是只生产一种产品,而是一系列的产品。如果觉得比较难理解我们可以再看一个例子:

苹果公司生产什么产品呢? 平板、手机、表、电脑。
华为也会生产这些产品:平板、手机、表、电脑。
这样的比较就比较清晰,苹果和华为工厂都有自己的产品族,且产品族是类似的。这种情况就可以使用抽象工厂模式。

UML类图如下。


image

网上看了很多相关的文章,总结抽象工厂模式有一下特点:
1、产品被划分为多个产品族,即上面的例子
2、系统一次仅消费使用其中一个族的产品。即每次都会使用同一个族的产品, 比如你是苹果粉,那么只能使用苹果的产品,华为粉则只能使用华为的产品。
抽象工厂的优缺点:
1、抽象工厂模式的纵向扩展(扩展工厂)非常容易
2、抽象工厂模式横向扩展(扩展产品)非常困难,因为在产品族新增一个产品会导致整个工厂及产品代码的修改。

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

推荐阅读更多精彩内容