工厂模式理解了没有?

前言

只有光头才能变强

回顾前面:

昨天写了单例模式了,今天是时候写工厂模式啦~

工厂模式我个人认为其实比较难理解的,如果有接触过|听过|见过该模式的同学很可能就会想:我自己new一个对象出来就好了,简单快捷。用得着你这个工厂模式吗?搞一个工厂出来还要写一大堆的代码呢~

网上的很多资料都是在阐述着:工厂模式的好处就是解耦。相信大家对解耦这个词也不陌生,那解耦究竟有什么好处呢?

本文章试图去解释为什么要用工厂模式,用了工厂模式的好处是什么,以及工厂模式衍生出的三种形式究竟有什么区别~~

那么接下来就开始吧,如果有错的地方希望能多多包涵,并不吝在评论区指正!

一、工厂模式概述

在《设计模式之禅》这本书中分了两章节讲工厂模式:

  • 工厂方法模式
    • (ps:其中里面讲到了简单工厂模式)
  • 抽象工厂模式

网上的大部分资料都是将工厂模式分成种:

  • 简单/静态工厂模式
  • 工厂方法模式
  • 抽象工厂模式

看完上面的叙述是不是想打死我,什么鸟玩意?不急哈,下面我会一一讲到~~

1.1为什么要用工厂模式?

想想我们为什么要用工厂模式?下面我就简单举例子:

文件IO的操作我们会经常用得到吧,所以BufferedReader对象经常要创建的:


    // 创建一个BufferedReader对象
    BufferedReader bf = new BufferedReader(new FileReader(new File("aa.txt")));

你说麻烦吗?其实也不麻烦,就一行代码嘛,哪里麻烦了~如果不太熟悉IO流的同学就没有那么机灵了,创建一个BufferedReader可能就是以下的代码了:


        File file = new File("aa.txt");
        FileReader fileReader = new FileReader(file);
        BufferedReader bufferedReader = new BufferedReader(fileReader);

你说麻烦吗?其实也不麻烦,不就是三行代码嘛,哪里麻烦了~如果这个应用很多的类上都用到了BufferedReader对象的话,那每个类都写上这三行代码了。那你说麻烦吗?那肯定麻烦啊,还用想啊....

可以看出来,创建一个BufferReader对象里面需要一个FileReader对象,而FileReader对象又要File对象。那创建这个BufferReader对象还是比较麻烦的(代码上看不麻烦,从构造上看还是挺麻烦的)!

虽然比较麻烦,但我们还能用,能用就行!于是乎,我们就去写代码了,现在有三个类都要进行文件的读写操作,于是他们就有这样的代码:


public class FileOperateA {

    public static void main(String[] args) throws FileNotFoundException {
        File file = new File("aa.txt");
        FileReader fileReader = new FileReader(file);
        BufferedReader bufferedReader = new BufferedReader(fileReader);

        // 读写文件....
    }
}
image

此时:上头说,我要换成LineNumberReader来读写,有这个需求!那我们作为一个写代码的,能怎么办?很绝望也需要去完成呀。

  • 不熟悉IDE的小伙子就一个一个将BufferedReader改成LineNumberReader,现在就3个类用到了BufferedReader,也就改6次而已。(ps:那如果很多地方都用到了呢?)
  • 熟悉IDE的小伙子就全局替换重构,妥妥的!

哎,写个代码屁事真多...那有没有一种方法能够让创建对象变得简单而且修改对象时能很方便呢?

  • 哎,工厂模式就行了。

再说从面向对象的角度来看:我一个操作文件的类还要我会创建BufferReader是不是有点过分了?(职责没有分工好)

  • 交给工厂来创建对象这就很面向对象了!

1.2体验工厂模式

何为工厂?将我们的产品都交由工厂来生产!我现在用的iphone5s,从哪来?从富士康组装而来,富士康是工厂。我用得着知道iphone5s在富士康是怎么组装起来的吗?不需要。

来,我们来改造一下上面的例子。首先我们创建一个工厂类,它可以生产Reader对象


// 创建Reader对象的工厂
public class ReaderFactory {
    public static Reader getReader() throws FileNotFoundException {
        File file = new File("aa.txt");
        FileReader fileReader = new FileReader(file);
        BufferedReader reader = new BufferedReader(fileReader);
        return reader;
    }
}

那么我们要得到BufferReader对象就贼简单了:


public class FileOperateA {

    public static void main(String[] args) throws FileNotFoundException {

        //-------我有工厂了,还用自己搞吗?不用了!
        //File file = new File("aa.txt");
        //FileReader fileReader = new FileReader(file);
        //BufferedReader bufferedReader = new BufferedReader(fileReader);
        //-------我有工厂了,还用自己搞吗?不用了!

        // 用工厂来创建出对象
        Reader reader = ReaderFactory.getReader();

        // 读写文件....
    }
}

工厂将我们创建的对象过程给屏蔽了

此时我要改成LineNumberReader怎么玩?在工厂上改一下就好了:

image

我们的调用方FileOperateA|FileOperateB|FileOperateC这些类完全就不用变!

1.3使用工厂方法的好处

从上面的工厂模式体验我们就可以看到:

  • 我们修改了具体的实现类,对客户端(调用方)而言是完全不用修改的
  • 如果我们使用new的方式来创建对象的话,那么我们就说:new出来的这个对象和当前客户端(调用方)耦合了!
    • 也就是,当前客户端(调用方)依赖着这个new出来的对象!

这就是解耦的好处

我再放下我之前练习的时候写过的代码吧:

我有一个DaoFactory,逻辑很简单就是专门创建Dao对象的~

image

那么在Service层就可以使用工厂将想要的Dao对象初始化了~

image

此时我们的Service与Dao的对象低耦合的~

  • 大家可能看不出有什么好处,还弄了一大堆的字符串啥的~~

在Service与Controller层我也弄了一个ServiceFactory,根据当时业务的需要(添加权限),我创建Service时就非常灵活了:

image

二、如何使用工厂模式

在一开始我就说了,工厂模式可以分成三类:

  • 简单/静态工厂模式
  • 工厂方法模式
  • 抽象工厂模式

下面我就逐一来介绍一下每一种工厂模式有什么不一样~

三种模式都以:Java3y要买宠物的例子来讲解~

2.1工厂方法模式

很多博客都是以简单/静态工厂模式,工厂方法模式,抽象工厂模式这个顺序来讲解工厂模式的。我认为按书上的顺序比较好理解~因为简单/静态工厂模式是在工厂方法模式上缩减,抽象工厂模式是在工厂方法模式上再增强

  • 所以我就先讲工厂方法模式了。

Java3y每天写代码很无聊,想要买只宠物来陪陪自己。于是乎就去宠物店看宠物啦~~~

作为一间宠物店,号称什么宠物都有!于是乎,店主宣传的时候就说:我的宠物店什么宠物都有

于是构建宠物的工厂就诞生了~


// 号称什么宠物都有
public interface AnimalFactory {

    // 可以获取任何的宠物
    Animal createAnimal();
}

当然了,主流的宠物得进货一些先放在店里充充门面,一些特殊的宠物就告诉顾客要时间进货~

  • 所以,我们就有了构建猫和狗的工厂(继承着所有宠物的工厂)

猫工厂:


// 继承着宠物工厂
public class CatFactory implements AnimalFactory {
    @Override
    // 创建猫
    public Animal createAnimal() {
        return new Cat();
    }

}

狗工厂也是一样的:


// 继承着宠物工厂
public class DogFactory implements AnimalFactory {

    // 创建狗
    @Override
    public Animal createAnimal() {
        return new Dog();
    }

}

嗯,还有我们的实体类:猫、狗、动物(多态:猫和狗都是动物,可以直接用动物来表示了)

动物实体类:


public abstract class Animal {

    // 所有的动物都会吃东西
    public abstract void eat();
}

猫实体类:


public class Cat extends Animal {

    // 猫喜欢吃鱼
    @Override
    public void eat() {
        System.out.println("猫吃鱼");
    }

}

狗实体类:


public class Dog extends Animal {

    // 狗喜欢吃肉
    @Override
    public void eat() {
        System.out.println("狗吃肉");
    }

}

那么现在Java3y想要一只狗,跟了宠物店老板说,宠物店老板就去找狗回来了:


    // 去找狗工厂拿一只狗过来
    AnimalFactory f = new DogFactory();

    // 店主就拿到了一只狗给Java3y
    Animal a = f.createAnimal();
    a.eat();
    System.out.println("关注公众号:Java3y");

那么现在Java3y想要一只猫,跟了宠物店老板说,宠物店老板就去找猫回来了:


    // 去找猫工厂拿一只猫过来
    AnimalFactory ff = new CatFactory();

    // 店主就拿到了一只猫给Java3y
    Animal aa = ff.createAnimal();
    aa.eat();
    System.out.println("关注公众号:Java3y");

如果这个时候Java3y说想要一只蜥蜴怎么办啊?没问题啊,店主搞个蜥蜴工厂就好了~~


        // 要买蜥蜴..
        AnimalFactory fff = new LizardFactory();
        Animal aaa = ff.createAnimal();
        aaa.eat();

优点:

  • 1:客户端不需要在负责对象的创建,明确了各个类的职责
  • 2:如果有新的对象增加,只需要增加一个具体的类和具体的工厂类即可
  • 3:不会影响已有的代码,后期维护容易,增强系统的扩展性

缺点:

  • 1:需要额外的编写代码,增加了工作量

工厂方法类图:

image

2.2简单/静态工厂模式

现在宠物店生意不好做啊,号称“什么宠物都有",这吹过头了~~于是店主只卖两种常见的宠物了。

  • 既然就只有两种宠物的话,那就没必要有”猫厂“、”狗厂“了,一个猫狗厂就行了!

所以我们的工厂是这样子的:


public class AnimalFactory {
    public static Dog createDog() {
        return new Dog();
    }

    public static Cat createCat() {
        return new Cat();
    }

    // 外界想要猫要狗,这里创建就好了
    public static Animal createAnimal(String type) {
        if ("dog".equals(type)) {
            return new Dog();
        } else if ("cat".equals(type)) {
            return new Cat();
        } else {
            return null;
        }
    }
}

三个实体还是没变(动物、猫、狗)....

那么Java3y去宠物店买猫狗的时候,告诉老板我要猫、我要狗:


        // 拿到狗
        Animal A = AnimalFactory.createAnimal("dog");
        A.eat();

        // 拿到猫
        Animal C = AnimalFactory.createAnimal("cat");
        C.eat();

现在问题来了:

  • 1:我想要一个猪,可是我的工厂类没有猪
  • 2:我就去代码,写可以创建猪对象的
  • 3:接着,我又要其他的动物
  • 4:我还是得代码
  • 5...................
  • 6:这就是简单工厂类的缺点:当需求改变了,我就要改代码.

简单工厂类的优点也很明显:我就一个具体的工厂来创建对象,代码量少。

2.3抽象工厂模式

抽象工厂模式就比较复杂了,我们一般的应用都写不到。我首先来简述一下需求吧:

  • 现在非常流行在猫狗届也吹起了一股“性别风”
    • 有的喜欢公的
    • 有的喜欢母的

那我们的猫和狗都是有性别的,不是公的就是母的~~

  • 我们之前在工厂方法模式下是每个动物都开一个工厂,如果动物过多的话,那么就有很多的工厂~
  • 那现在我们可以抽取出来:每个动物不是公的就是母的~
  • 所以我们有两个工厂就足够了!

具体的代码是这样的:

我们的最大工厂还是定义了创建什么动物


public interface AnimalFactory {
    Animal createDog();
    Animal createCat();
}

创建母猫和母狗的工厂:


public class FemaleAnimalFactory implements AnimalFactory {

    // 生产母狗和母猫
    @Override
    public Animal createDog() {
        return  new FemaleDog();
    }

    @Override
    public Animal createCat() {
        return new FemaleCat();
    }

}

创建公猫和公狗的工厂:


public class MaleAnimalFactory implements AnimalFactory {

    // 生产公狗和公猫

    @Override
    public Animal createDog() {
        return new MaleDog();
    }

    @Override
    public Animal createCat() {
        return new MaleCat();
    }

}

这是所有动物都拥有的普遍行为


public abstract class Animal {

    // 所有的动物都会吃东西
    public abstract void eat();

    // 所有的动物都有性别
    public abstract void gender();
}

这是猫都拥有的普遍行为:


public abstract class Cat extends Animal {
    // 猫喜欢吃鱼
    @Override
    public void eat() {
        System.out.println("猫吃鱼");
    }
}

这是狗都拥有的普遍行为:

public abstract class Dog extends Animal {

    // 狗喜欢吃肉
    @Override
    public void eat() {
        System.out.println("狗吃肉");
    }

}

猫分为公猫、母猫。狗分为公狗和母狗:

public class FemaleCat extends Cat {

    public void gender() {
        System.out.println("I am a female Cat");
    }

}

.....

image

简单来说:工厂方法模式的工厂是创建出一种产品,而抽象工厂是创建出一类产品。

  • 一类的产品我们称之为产品族
    • 猫是一类的,狗也是一类的。所以AnimalFactory定义了两类产品--->Animal createDog();Animal createCat();
  • 产品的继承结构称之为产品等级
    • 所有的动物都是会吃东西的,它们都是有性别的,这是最普遍的。所以Animal定义了两个抽象方法:public abstract void eat();public abstract void gender();
    • 所有的狗都是会吃肉的,所以Dog实现了eat()方法
      • 狗又分成了公狗和母狗,所以定义了两个类FemaleDog和MaleDog继承了Dog,实现了gender()方法
    • 所有的猫都是会吃鱼的,所以Cat实现了eat()方法
      • 猫又分成了公猫和母猫,所以定义了两个类FemaleCat和MaleCat继承了Cat,实现了gender()方法
  • 具体的工厂是面向多个产品等级结构进行生产
    • 所以FemaleAnimalFactory定义了createDog()createCat()生产母狗和母猫
    • 所以MaleAnimalFactory定义了createDog()createCat()生产公狗和共猫
  • 找到母工厂就可以创建母猫和母狗,找到公工厂就可以创建公猫和公狗
image

    public static void main(String[] args) {

        // 需要性别为母的就去找母工厂
        AnimalFactory af = new FemaleAnimalFactory();

        // 需要一只母猫
        af.createCat().gender();

        // 需要一只母狗
        af.createDog().gender();

        System.out.println("-------------关注公众号:Java3y-------------------------");

        // 需要性别为公的就去找公工厂
        AnimalFactory aff = new MaleAnimalFactory();

        // 需要一只公狗
        aff.createDog().gender();

        // 需要一只公猫
        aff.createCat().gender();

    }

效果:

image

这是抽象工厂模式的类图:

image

抽象工厂模式说到底就是多了一层抽象,减少了工厂的数量

抽象工厂缺点也很明显:

  • 难以扩展产品族--->如果我再要宠物猪的话
    • 那我要修改AnimalFactory、FemaleAnimalFactory、MaleAnimalFactory这些类了~

三、总结

总的来说我们用简单工厂模式比较多,工厂方式模式的话代码量会比较大,抽象工厂模式的话需要业务比较大的情况下才会用到(如果有更好的理解方式不妨在评论区留言,一起交流交流涨涨见识~~)

  • 工厂模式配合反射来使用也是极好的~

参考资料:

如果文章有错的地方欢迎指正,大家互相交流。习惯在微信看技术文章,想要获取更多的Java资源的同学,可以关注微信公众号:Java3y。为了大家方便,刚新建了一下qq群:742919422,大家也可以去交流交流。谢谢支持了!希望能多介绍给其他有需要的朋友

文章的目录导航

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

推荐阅读更多精彩内容

  • 设计模式汇总 一、基础知识 1. 设计模式概述 定义:设计模式(Design Pattern)是一套被反复使用、多...
    MinoyJet阅读 3,897评论 1 15
  • 1.ios高性能编程 (1).内层 最小的内层平均值和峰值(2).耗电量 高效的算法和数据结构(3).初始化时...
    欧辰_OSR阅读 29,299评论 8 265
  • 车险处理步骤:第一步:拨打相关电话。 1、救人电话:120 2、定责电话:122 3、定损电话:400869551...
    黄祥阅读 232评论 0 0
  • 世间的感情莫过于两种: 一种是相濡以沫, 却厌倦到终老。 另一种是相忘于江湖, 却怀念到哭泣! ...
    素笺姑娘阅读 99评论 0 0
  • 见字如晤。 很高兴你还在等待我的更新,今天是整整一年的时间,是值得纪念的时间。相比于一年前,我少了那种欣喜的激动,...
    美其名曰易非凡阅读 595评论 7 1