【设计模式自习室】理解工厂模式的三种形式

前言

《设计模式自习室》系列,顾名思义,本系列文章带你温习常见的设计模式。主要内容有:

  • 该模式的介绍,包括:
    • 引子、意图(大白话解释)
    • 类图、时序图(理论规范)
  • 该模式的代码示例:熟悉该模式的代码长什么样子
  • 该模式的优缺点:模式不是万金油,不可以滥用模式
  • 该模式的实际使用案例:了解它在哪些重要的源码中被使用

该系列会逐步更新于我的博客和公众号(博客见文章底部)

也希望各位观众老爷能够关注我的个人公众号:后端技术漫谈,不会错过精彩好看的文章。

系列文章回顾

创建型——简单工厂/工厂模式/抽象工厂

引子

工厂模式是一个非常重要的创建型模式,但是工厂模式又分为好多种,并且网上文章很多,很多对工厂模式的定义都不是很明确,甚至还互相冲突,本文希望通过放在一起串讲的形式,力求能够用最简洁的语言理清工厂模式。

先看一个工厂模式的定义:

“Define an interface for creating an object, but let subclasses decide which class to instantiate. Factory Method lets a class defer instantiation to subclasses.”(在基类中定义创建对象的一个接口,让子类决定实例化哪个类。工厂方法让一个类的实例化延迟到子类中进行。)

使用了工厂模式,我们可以将对象的创建和使用分离。用来防止用来实例化一个类的数据和代码在多个类中到处都是。

工厂模式最主要的形式是以下三种:

  • 简单/静态工厂(Simple Factory)
  • 工厂方法(Factory Method)
  • 抽象工厂(Abstract Factory)

意图

1. 简单/静态工厂(Simple Factory)

先来看简单工厂模式,它指的是,在创建一个对象时不向客户暴露内部细节,并提供一个创建对象的通用接口。

在简单工厂模式中,可以根据参数的不同返回不同类的实例。

2. 工厂方法(Factory Method)

工厂方法又可以称为:

  • 工厂模式
  • 虚拟构造器(Virtual Constructor)模式
  • 多态工厂(Polymorphic Factory)模式

工厂模式通过工厂子类来确定究竟应该实例化哪一个具体产品类。不再设计一个工厂类来统一负责所有产品的创建,而是将具体产品的创建过程交给专门的工厂子类去完成。

这一特点无疑使得工厂方法模式具有超越简单工厂模式的优越性,更加符合“开闭原则”。

3. 抽象工厂(Abstract Factory)

在了解抽象工厂之前,我们先要了解下什么是产品等级结构和产品族

产品族 :在抽象工厂模式中,产品族是指由同一个工厂生产的,位于不同产品等级结构中的一组产品,如海尔电器工厂生产的海尔电视机、海尔电冰箱,海尔电视机位于电视机产品等级结构中,海尔电冰箱位于电冰箱产品等级结构中。

产品等级结构 :产品等级结构即产品的继承结构,如一个抽象类是电视机,其子类有海尔电视机、海信电视机、TCL电视机,则抽象电视机与具体品牌的电视机之间构成了一个产品等级结构,抽象电视机是父类,而具体品牌的电视机是其子类。

工厂方法模式针对的是一个产品等级结构,而抽象工厂模式则需要面对多个产品等级结构,一个工厂等级结构可以负责多个不同产品等级结构中的产品对象的创建 。

抽象工厂模式是所有形式的工厂模式中最为抽象和最具一般性的一种形态。

如果看到这里还是对抽象工厂理解不够,不要着急,下方的代码示例会给你加深理解。

类图

如果看不懂UML类图,可以先粗略浏览下该图,想深入了解的话,可以继续谷歌,深入学习:

image

1. 简单/静态工厂(Simple Factory)

image

简单工厂模式包含如下角色:

  • Factory:工厂角色 负责实现创建所有实例的内部逻辑
  • Product:抽象产品角色 是所创建的所有对象的父类,负责描述所有实例所共有的公共接口
  • ConcreteProduct:具体产品角色 是创建目标,所有创建的对象都充当这个角色的某个具体类的实例。

2. 工厂方法(Factory Method)

image

(相比简单工厂,将工厂变为了抽象工厂和具体工厂)

  • Factory:抽象工厂,担任这个角色的是工厂方法模式的核心,任何在模式中创建对象的工厂类必须实现这个接口。在实际的系统中,这个角色也常常使用抽象类实现。
  • ConcreteFactory:具体工厂,担任这个角色的是实现了抽象工厂接口的具体Java类。具体工厂角色含有与业务密切相关的逻辑,并且受到使用者的调用以创建具体产品对象。
  • Product:抽象产品,工厂方法模式所创建的对象的超类,也就是所有产品类的共同父类或共同拥有的接口。在实际的系统中,这个角色也常常使用抽象类实现。
  • ConcreteProduct:具体产品,这个角色实现了抽象产品(Product)所声明的接口,工厂方法模式所创建的每一个对象都是某个具体产品的实例。

3. 抽象工厂(Abstract Factory)

image

抽象工厂模式包含如下角色:

  • AbstractFactory:抽象工厂
  • ConcreteFactory:具体工厂
  • AbstractProduct:抽象产品
  • ConcreteProduct:具体产品

你会发现工厂模式和抽象工厂的角色是相同的。

时序图

时序图(Sequence Diagram)是显示对象之间交互的图,这些对象是按时间顺序排列的。时序图中显示的是参与交互的对象及其对象之间消息交互的顺序。

我们可以大致浏览下时序图,如果感兴趣的小伙伴可以去深究一下:

1. 简单/静态工厂(Simple Factory)

image

2. 工厂方法(Factory Method)

image

3. 抽象工厂(Abstract Factory)

image

代码样例

给出的代码中,每个类都以角色来区分

1. 简单/静态工厂(Simple Factory)

代码来自:

https://www.jianshu.com/p/d1b6731c1c0e

工厂——LoginManager
public class LoginManager {
    public static Login factory(String type){
        if(type.equals("password")){
            
            return new PasswordLogin();
            
        }else if(type.equals("passcode")){
            
            return new DomainLogin();
            
        }else{
            /**
             * 这里抛出一个自定义异常会更恰当
             */
            throw new RuntimeException("没有找到登录类型");
        }
    }
}
抽象产品——Login接口
public interface Login {
    //登录验证
    public boolean verify(String name , String password);
}
具体产品——PasswordLogin
public class PasswordLogin implements Login {

    @Override
    public boolean verify(String name, String password) {
        // TODO Auto-generated method stub
        /**
         * 业务逻辑
         */
        return true;
    }
}

客户端调用

public class Test {
    public static void main(String[] args) {
        String loginType = "password";
        String name = "name";
        String password = "password";
        Login login = LoginManager.factory(loginType);
        boolean bool = login.verify(name, password);
        if (bool) {
            /**
             * 业务逻辑
             */
        } else {
            /**
             * 业务逻辑
             */
        }
    }
}

假如不使用上面的简单工厂模式则验证登录Servlet代码如下,可以看到代码耦合度很高:

public class Test {
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        
        String loginType = "password";
        String name = "name";
        String password = "password";
        //处理口令认证
        if(loginType.equals("password")){
            PasswordLogin passwordLogin = new PasswordLogin();
            boolean bool = passwordLogin.verify(name, password);
            if (bool) {
                /**
                 * 业务逻辑
                 */
            } else {
                /**
                 * 业务逻辑
                 */
            }
        }
        //处理域认证
        else if(loginType.equals("passcode")){
            DomainLogin domainLogin = new DomainLogin();
            boolean bool = domainLogin.verify(name, password);
            if (bool) {
                /**
                 * 业务逻辑
                 */
            } else {
                /**
                 * 业务逻辑
                 */
            }    
        }else{
            /**
             * 业务逻辑
             */
        }
    }
}

2. 工厂方法(Factory Method)

代码来自:https://www.jianshu.com/p/1cf9859e0f7c

(相比简单工厂,将工厂变为了抽象工厂和具体工厂)

抽象的产品接口——ILight:具备开关两种功能的产品

public interface ILight
    {
        void TurnOn();
        void TurnOff();
    }

具体的产品类——BulbLight

 public class TubeLight implements ILight
    {
        public void TurnOn()
        {
            Console.WriteLine("TubeLight turns on.");
        }

        public void TurnOff()
        {
            Console.WriteLine("TubeLight turns off.");
        }

    }

抽象的工厂类——ICreator

public interface ICreator
    {
        ILight CreateLight();
    }

具体的工厂类——BulbCreator

 public class BulbCreator implements ICreator
    {
        public ILight CreateLight()
        {
            return new BulbLight();
        }

    }

客户端调用

static void Main(string[] args)
        {
            //先给我来个灯泡
            ICreator creator = new BulbCreator();
            ILight light = creator.CreateLight();
            light.TurnOn();
            light.TurnOff();

            //再来个灯管看看
            creator = new TubeCreator();
            light = creator.CreateLight();
            light.TurnOn();
            light.TurnOff();
        }

本例中每个具体工厂类只负责生产一种类型的产品,当然每个具体工厂类也内部可以维护少数几种产品实例对象,类似于简单工厂模式。

3. 抽象工厂(Abstract Factory)

代码来自:https://www.jianshu.com/p/d6622f3e71ed

抽象产品: 苹果系列

public interface Apple
     {
        void AppleStyle();
    }

抽象产品: 三星系列

public interface Sumsung
     {
        void BangziStyle();
   }

具体产品:iphone

public class iphone implements Apple
     {
         public void AppleStyle()
         {
            Console.WriteLine("Apple's style: iPhone!");
        }
     }

具体产品:ipad

 public class ipad implements Apple
    {
 
         public void AppleStyle()
        {
             Console.WriteLine("Apple's style: iPad!");
        }
 
     }

具体产品:note2

public class note2 implements Sumsung
     {
         public void BangziStyle()
         {
            Console.WriteLine("Bangzi's style : Note2!");
         }
 
     }

抽象工厂

public interface Factory
     {
         Apple createAppleProduct();
         Sumsung createSumsungProduct();
     }

手机工厂

public class Factory_Phone implements Factory
     {
         public Apple createAppleProduct()
         {
             return new iphone();
         }
 
         public Sumsung createSumsungProduct()
         {
             return new note2();
         }
     }

pad工厂

public class Factory_Pad implements  Factory
     {
         public Apple createAppleProduct()
         {
             return new ipad();
         }
 
         public Sumsung createSumsungProduct()
         {
             return new Tabs();
         }
     }

客户端调用

public static void Main(string[] args)
         {
              //采购商要一台iPad和一台Tab
              Factory factory = new Factory_Pad();
              Apple apple = factory.createAppleProduct();
              apple.AppleStyle();
              Sumsung sumsung = factory.createSumsungProduct();
              sumsung.BangziStyle();
  
             //采购商又要一台iPhone和一台Note2
             factory = new Factory_Phone();
             apple = factory.createAppleProduct();
             apple.AppleStyle();
             sumsung = factory.createSumsungProduct();
             sumsung.BangziStyle();
         }

下面的代码是刚才已经看过的工厂模式的调用代码,对比下,发现区别了吗?工厂方法只需要管是哪个产品族,而抽象工厂还要考虑产品的等级结构,也就是他是苹果造的,还是三星造的。尽管他们都是手机!

static void Main(string[] args)
        {
            //先给我来个灯泡
            ICreator creator = new BulbCreator();
            ILight light = creator.CreateLight();
            light.TurnOn();
            light.TurnOff();

            //再来个灯管看看
            creator = new TubeCreator();
            light = creator.CreateLight();
            light.TurnOn();
            light.TurnOff();
        }

用意图里面的话再次总结一下:

工厂方法模式针对的是一个产品等级结构,而抽象工厂模式则需要面对多个产品等级结构,一个工厂等级结构可以负责多个不同产品等级结构中的产品对象的创建 。

优缺点

1. 简单/静态工厂(Simple Factory)

优点
  • 构造容易,逻辑简单。
缺点
  • 简单工厂模式中的if else判断非常多,完全是Hard Code,如果有一个新产品要加进来,就要同时添加一个新产品类,并且必须修改工厂类,再加入一个 else if 分支才可以, 这样就违背了 “开放-关闭原则“中的对修改关闭的准则了。
  • 一个工厂类中集合了所有的类的实例创建逻辑,违反了高内聚的责任分配原则,将全部的创建逻辑都集中到了一个工厂类当中,所有的业务逻辑都在这个工厂类中实现。什么时候它不能工作了,整个系统都会受到影响。因此一般只在很简单的情况下应用,比如当工厂类负责创建的对象比较少时。
  • 简单工厂模式由于使用了静态工厂方法,造成工厂角色无法形成基于继承的等级结构。

2. 工厂方法(Factory Method)

优点
  • 在工厂方法模式中,工厂方法用来创建客户所需要的产品,同时还向客户隐藏了哪种具体产品类将被实例化这一细节,用户只需要关心所需产品对应的工厂,无须关心创建细节,甚至无须知道具体产品类的类名
  • 工厂方法模式之所以又被称为多态工厂模式,是因为所有的具体工厂类都具有同一抽象父类
  • 使用工厂方法模式的另一个优点是在系统中加入新产品时,无须修改抽象工厂和抽象产品提供的接口,无须修改客户端,也无须修改其他的具体工厂和具体产品,而只要添加一个具体工厂和具体产品就可以了。这样,系统的可扩展性也就变得非常好,完全符合“开闭原则”,这点比简单工厂模式更优秀。
缺点
  • 在添加新产品时,需要编写新的具体产品类,而且还要提供与之对应的具体工厂类,系统中类的个数将成对增加,在一定程度上增加了系统的复杂度,有更多的类需要编译和运行,会给系统带来一些额外的开销。
  • 由于考虑到系统的可扩展性,需要引入抽象层,在客户端代码中均使用抽象层进行定义,增加了系统的抽象性和理解难度,且在实现时可能需要用到DOM、反射等技术,增加了系统的实现难度。

3. 抽象工厂(Abstract Factory)

优点
  • 应用抽象工厂模式可以实现高内聚低耦合的设计目的,因此抽象工厂模式得到了广泛的应用。
  • 增加新的具体工厂和产品族很方便,因为一个具体的工厂实现代表的是一个产品族,无须修改已有系统,符合“开闭原则”。
缺点

开闭原则的倾斜性(增加新的工厂和产品族容易,增加新的产品等级结构麻烦

使用场景举例

1. 简单/静态工厂(Simple Factory)

工厂类负责创建的对象比较少:由于创建的对象较少,不会造成工厂方法中的业务逻辑太过复杂。

Java JDK中使用
  1. JDK类库中广泛使用了简单工厂模式,如工具类java.text.DateFormat,它用于格式化一个本地日期或者时间。
public final static DateFormat getDateInstance();
public final static DateFormat getDateInstance(int style);
public final static DateFormat getDateInstance(int style,Locale
locale);
  1. Java加密技术
    获取不同加密算法的密钥生成器:
KeyGenerator keyGen=KeyGenerator.getInstance("DESede");
  1. 创建密码器:
Cipher cp = Cipher.getInstance("DESede");

2. 工厂方法(Factory Method)

Java JDK中使用
  • JDBC中的工厂方法:
Connection conn=DriverManager.getConnection("jdbc:microsoft:sqlserver://localhost:1433; DatabaseName=DB;user=sa;password=");
Statement statement=conn.createStatement();
ResultSet rs=statement.executeQuery("select * from UserInfo");

3. 抽象工厂(Abstract Factory)

在以下情况下可以使用抽象工厂模式:

  • 一个系统不应当依赖于产品类实例如何被创建、组合和表达的细节,这对于所有类型的工厂模式都是重要的。
  • 系统中有多于一个的产品族,而每次只使用其中某一产品族。(与工厂方法模式的区别)
  • 属于同一个产品族的产品将在一起使用,这一约束必须在系统的设计中体现出来。
  • 系统提供一个产品类的库,所有的产品以同样的接口出现,从而使客户端不依赖于具体实现。
Java JDK中使用

总结

抽象工厂模式中我们可以定义实现不止一个接口,一个工厂也可以生成不止一个产品类,抽象工厂模式较好的实现了“开放-封闭”原则,是三个模式中较为抽象,并具一般性的模式。

但是归根结底,工厂模式还是一定程度上增加了代码复杂度,有没有一种办法,不需要创建工厂,也能解决代码以后的扩展性问题呢?

答案是有的,通过控制反转(ioc),对象在被创建的时候,由一个调控系统内所有对象的外界实体,将其所依赖的对象的引用传递给它。也可以说,依赖被注入到对象中。(DI),也就是Spring最核心的思想了。大家可以自行查阅Spring思想的文章。

话说,最近也真的想总结一下Spring源码相关的知识,正在学习中。

参考

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念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

推荐阅读更多精彩内容

  • 工厂模式是我们最常用的实例化对象模式了,是用工厂方法代替new操作的一种模式。通常我们所说的工厂模式是指工厂方法模...
    zfylin阅读 1,308评论 0 7
  • 工厂方法模式通过引入工厂等级结构,解决了简单工厂模式中工厂类职责太重的问题,但由于工厂方法模式中的每个工厂只生产一...
    justCode_阅读 1,184评论 1 6
  • 简单工厂模式虽然简单,但存在一个很严重的问题。当系统中需要引入新产品时,由于静态工厂方法通过所传入参数的不同来创建...
    justCode_阅读 1,176评论 1 9
  • 设计原则: 要依赖抽象,不要依赖具体类 目录 本文的结构如下: 什么是抽象工厂模式 为什么要用该模式 模式的结构 ...
    w1992wishes阅读 1,102评论 0 6
  • 我没有记错 两个月前的现在 我正送你回家 也记得 送你到家后我并没有下车 径自乘车而去 摇开车窗 只剩下你留给我的...
    洪少的洪阅读 217评论 1 0