创建型模式 - 工厂方法模式

0x01 前言

  工厂模式(Factory Pattern)是 Java 中最常用的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

  在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。

0x02 简介

意图:定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使其创建过程延迟到子类进行。

主要解决:主要解决接口选择的问题。

何时使用:我们明确地计划不同条件下创建不同实例时。

如何解决:让其子类实现工厂接口,返回的也是一个抽象的产品。

关键代码:创建过程在其子类执行。

应用实例:1、您需要一辆汽车,可以直接从工厂里面提货,而不用去管这辆汽车是怎么做出来的,以及这个汽车里面的具体实现。2、 Hibernate 换数据库只需换方言和驱动就可以。

优点:1、一个调用者想创建一个对象,只要知道其名称就可以了。2、扩展性高,如果想增加一个产品,只要扩展一个工厂类就可以。3、屏蔽产品的具体实现,调用者只关心产品的接口。

缺点:1、每次增加一个产品时,都需要增加一个具体类和对象实现工厂,使得系统中类的个数成倍增加,在一定程度上增加了系统的复杂度,同时也增加了系统具体类的依赖。这并不是什么好事。

使用场景:1、日志记录器:记录可能记录到本地硬盘、系统事件、远程服务器等,用户可以选择记录日志到什么地方。2、数据库访问,当用户不知道最后系统采用哪一类数据库,以及数据库可能有变化时。3、设计一个连接服务器的框架,需要三个协议,"POP3"、"IMAP"、"HTTP",可以把这三个作为产品类,共同实现一个接口。

注意事项:作为一种创建类模式,在任何需要生成复杂对象的地方,都可以使用工厂方法模式。有一点需要注意的地方就是复杂对象适合使用工厂模式,而简单对象,特别是只需要通过 new 就可以完成创建的对象,无需使用工厂模式。如果使用工厂模式,就需要引入一个工厂类,会增加系统的复杂度。

0x03 设计概述

简要概述

  1. “工业革命”之前:客户需要一台联想电脑,客户直接创建一台联想电脑,直接拿去用。

  2. 简单工厂(非设计模式)时代:后来出现“工业革命”。客户不用去创建电脑,因为客户有一个工厂来帮他创建电脑。想要什么系列电脑,这个工厂就可以创建。比如想要联想系列电脑。工厂就创建这个系列的电脑。即工厂可以创建产品。

  3. 工厂方法模式时代:为了满足客户的需求,电脑的系列越来越多,如联想、华硕、宏碁等系列,一个工厂无法创建所有的电脑。于是由单独分出来多个具体的工厂,每个具体工厂创建一种系列。而这么多的具体的工厂需要一个统一的电脑工厂来规范,即创办统称,即电脑工厂。当生产某一个具体系列的电脑,由于电脑工厂是抽象的,所以你需要指定某个具体的工厂才能生产车出来。

  4. 抽象工厂模式时代:随着客户的要求越来越高,需要定制不同组合的电脑。例如联想 Y 系列,华硕 G 系列,联想 Y 配备性能级显卡,联想 G 配备发烧级显卡。那么并不需要创办一个生产联想 Y 性能级显卡电脑的工厂、华硕 G 发烧级显卡电脑的工厂。而仅仅是抽象一个工厂,用来生产联想 Y 电脑、华硕 G 电脑、性能级显卡、发烧级显卡,然后组合就形成产品。

0x04 具体实现

“工业革命”之前

  “工业革命”之前,生产电脑的方式如此简单,客户需求什么电脑就创建什么电脑。即创建对象只需要创建对应的类就可以获得对象。
这里引入一个问题,谈谈为什么要使用工厂类的设计模式。我们经常一些功能类似的类,所以我们的思路是对进行抽象,使用接口暴露公共的方法,并通过实现类来提供具体的实现。

产品类
// factory_pattern.general.product.IComputer

package factory_pattern.general.product;

public interface IComputer {

    void getComputer(String brand);

}

// factory_pattern.general.product.impl.LenovoComputer

package factory_pattern.general.product.impl;

import factory_pattern.general.product.IComputer;

public class LenovoComputer implements IComputer {

    @Override
    public void getComputer(String brand) {
        System.out.println("生产一台 " + brand + " 笔记本电脑");
    }

}
// factory_pattern.general.product.impl.AsusComputer

package factory_pattern.general.product.impl;

import factory_pattern.general.product.IComputer;

public class AsusComputer implements IComputer {

    @Override
    public void getComputer(String brand) {
        System.out.println("研发一台 " + brand + " 台式机电脑");
    }

}
用户类
// factory_pattern.general.user.ComputerTest

package factory_pattern.general.user;

import org.junit.Test;

import factory_pattern.general.product.IComputer;
import factory_pattern.general.product.impl.AsusComputer;
import factory_pattern.general.product.impl.LenovoComputer;

public class ComputerTest {

    @Test
    public void testGetComputer() {
        IComputer lenPC = new LenovoComputer();
        lenPC.getComputer("Lenovo");
        IComputer asusPC = new AsusComputer();
        asusPC.getComputer("Asus");
    }

}

简单工厂

  在“工业革命”之前(没使用简单工厂时),这些类的具体实现方法形成了一个问题,每个类的具体实现方法参数不一样,具体实现方法(这里指打印的内容)也不一样,每次调用实现方法都很麻烦。

  还有一个问题,客户类和产品类紧密耦合在一起,为了解耦,我们需要引入一个第三方,用于创建对象,即生产电脑。

  所以我们需要进行工业革命,即封装成简单工厂模式。建立一个工厂,用这个工厂来替客户创建电脑。

  产品类代码我们不需要修改,加入用于创建对象的工厂类和修改客户类代码。

工厂类
// factory_pattern.simple.factory.SimpleFactory

package factory_pattern.simple.factory;

import factory_pattern.simple.product.IComputer;
import factory_pattern.simple.product.impl.LenovoComputer;
import factory_pattern.simple.product.impl.AsusComputer;

public class SimpleFactory {

    private SimpleFactory() {
    }

    public static IComputer createComputer(String brand) {
        IComputer computer = null;
        switch (brand) {
        case "Lenovo":
            computer = new LenovoComputer();
            break;
        case "Asus":
            computer = new AsusComputer();
            break;
        default:
            throw new IllegalArgumentException();
        }
        return computer;
    }

}
用户类
// factory_pattern.simple.user.ComputerTest

package factory_pattern.simple.user;

import org.junit.Test;

import factory_pattern.simple.product.IComputer;
import factory_pattern.simple.factory.SimpleFactory;

public class ComputerTest {

    @Test
    public void testGetComputer() {
        IComputer lenPC = SimpleFactory.createComputer("Lenovo");
        lenPC.getComputer("Lenovo");
        IComputer asusPC = SimpleFactory.createComputer("Asus");
        asusPC.getComputer("Asus");
    }

}

  简单工厂又称静态工厂方法,它存在的目的很简单:定义一个用于创建对象的接口。

  静态工厂的组成:

  1. 工厂类角色:这是本模式的核心,含有一定的商业逻辑和判断逻辑,用来创建产品。

  2. 抽象产品角色:它一般是具体产品继承的父类或者实现的接口。

  3. 具体产品角色:工厂类所创建的对象就是此角色的实例,在 Java 中由一个具体类实现。

  上述的简单工厂不利于拓展,违背了“开闭原则”。每增加一个类,就要修改工厂类。什么是“开闭原则”?开闭原则(OCP)是面向对象设计中“可复用设计”的基石,是面向对象设计中最重要的原则之一,其它很多的设计原则都是实现开闭原则的一种手段。对于扩展是开放的,对于修改是关闭的,这意味着模块的行为是可以扩展的。所以将引出下面的工厂方法模式。

工厂方法模式

  工厂方法模式去掉了简单工厂模式中工厂方法的静态属性,使得它可以被子类继承。这样在简单工厂模式里集中在工厂方法上的压力可以由工厂方法模式里不同的工厂子类来分担。

  工厂方法模式组成:

  1. 抽象工厂角色:这是工厂方法模式的核心,它与应用程序无关。是具体工厂角色必须实现的接口或者必须继承的父类。在java中它由抽象类或者接口来实现。

  2. 具体工厂角色:它含有和具体业务逻辑有关的代码。由应用程序调用以创建对应的具体产品的对象。

  3. 抽象产品角色:它是具体产品继承的父类或者是实现的接口。在java中一般有抽象类或者接口来实现。

  4. 具体产品角色:具体工厂角色所创建的对象就是此角色的实例。在java中由具体的类来实现。

  工厂方法模式使得结构变得灵活起来。当有新的产品产生时,只要按照抽象产品角色、抽象工厂角色提供的合同来生成,那么就可以被客户使用,而不必去修改任何已有的代码。可以看出工厂角色的结构也是符合开闭原则的!具体实现如下:

项目结构图
项目结构图.PNG
工厂类
// factory_pattern.factory_method.factory.IFactory

package factory_pattern.factory_method.factory;

import factory_pattern.factory_method.product.IComputer;

public interface IFactory {

    IComputer createComputer();

}
// factory_pattern.factory_method.factory.impl.LenovoFactory

package factory_pattern.factory_method.factory.impl;

import factory_pattern.factory_method.factory.IFactory;
import factory_pattern.factory_method.product.IComputer;
import factory_pattern.factory_method.product.impl.LenovoComputer;

public class LenovoFactory implements IFactory {

    @Override
    public IComputer createComputer() {
        return new LenovoComputer();
    }

}
// factory_pattern.factory_method.factory.impl.AsusFactory

package factory_pattern.factory_method.factory.impl;

import factory_pattern.factory_method.factory.IFactory;
import factory_pattern.factory_method.product.impl.AsusComputer;
import factory_pattern.factory_method.product.IComputer;

public class AsusFactory implements IFactory {

    @Override
    public IComputer createComputer() {
        return new AsusComputer();
    }

}
用户类
// factory_pattern.factory_method.user.ComputerTest

package factory_pattern.factory_method.user;

import org.junit.Test;

import factory_pattern.factory_method.factory.impl.AsusFactory;
import factory_pattern.factory_method.factory.impl.LenovoFactory;
import factory_pattern.factory_method.product.IComputer;

public class ComputerTest {

    @Test
    public void testGetComputer() {
        IComputer lenPC = new LenovoFactory().createComputer();
        lenPC.getComputer("Lenovo");
        IComputer asusPC = new AsusFactory().createComputer();
        asusPC.getComputer("Asus");
    }

}

  上述就是工厂方法的具体实现,可是还有一个问题,代码变得多了,因为功能类似的产品我们进行 2 层抽象,针对每个产品我们还抽象出了 2 层的工厂类。在某个具体的业务场景中,不单单是只实例化一个类。但当我们在解决某些问题的时候,例如:举一个例子,我们生产各种品牌的笔记本,笔记本对应的各种性能的显卡。针对现在的情况我们是不是可以让一个工厂既生产对应品牌的笔记本,又生产对应性能的显卡呢?这就是抽象工厂模式。简单来说,可以把有一些有联系或者相近的产品,放到一个工厂去生产,没有必要单独再开一个工厂了。

  抽象工厂模式作为一种设计模式,将在另一篇文章中单独解释。

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

推荐阅读更多精彩内容