前言

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

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

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

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

系列文章回顾

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

引子

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

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

“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类图,可以先粗略浏览下该图,想深入了解的话,可以继续谷歌,深入学习:

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

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

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

2. 工厂方法(Factory Method)

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

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

3. 抽象工厂(Abstract Factory)

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

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

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

时序图

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

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

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

2. 工厂方法(Factory Method)

3. 抽象工厂(Abstract Factory)

代码样例

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

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源码相关的知识,正在学习中。

参考

  • 《HEAD FIRST 设计模式》
  • https://www.jianshu.com/p/d1b6731c1c0e
  • https://www.jianshu.com/p/1cf9859e0f7c
  • https://www.jianshu.com/p/d6622f3e71ed
  • https://design-patterns.readthedocs.io/zh_CN/latest/creational_patterns/abstract_factory.html

关注我

我是一名后端开发工程师。

主要关注后端开发,数据安全,爬虫,物联网,边缘计算等方向,欢迎交流。

各大平台都可以找到我

原创博客主要内容

  • Java面试知识点复习全手册
  • 设计模式/数据结构 自习室
  • Leetcode/剑指offer 算法题解析
  • SpringBoot/SpringCloud菜鸟入门实战系列
  • 爬虫相关技术文章
  • 后端开发相关技术文章
  • 逸闻趣事/好书分享/个人兴趣

个人公众号:后端技术漫谈

如果文章对你有帮助,不妨收藏,投币,转发,在看起来~