小言_互联网的博客

设计模式 ~ 工厂模式剖析与实战

355人阅读  评论(0)

设计模式系列文章目录导读:

设计模式 ~ 面向对象 6 大设计原则剖析与实战
设计模式 ~ 模板方法模式分析与实战
设计模式 ~ 观察者模式分析与实战
设计模式 ~ 单例模式分析与实战
设计模式 ~ 深入理解建造者模式与实战
设计模式 ~ 工厂模式剖析与实战
设计模式 ~ 适配器模式分析与实战
设计模式 ~ 装饰模式探究
设计模式 ~ 深入理解代理模式
设计模式 ~ 小结

工厂模式概述

工厂模式 负责将大量有共同接口的类实例化,工厂模式主要有如下几种形态:

  • 简单工厂(Simple Factory)模式

    又称之为静态工厂方法模式(Simple Factory Method Pattern)

  • 工厂方法(Factory Method)模式

    又称之为多态性工厂(Polymorphic Factory)模式或虚拟构造函数(Virtual Constructor)模式

  • 抽象工厂(Abstract Factory)模式

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

不管是哪种形态的工厂模式,它们的核心原则都是:屏蔽对象的创建细节,客户端只负责“消费”对象,不关心对象的创建。

简单工厂模式

简单工厂模式将对象的创建交给工厂类,工厂类提供一个专门创建某一类对象的静态方法,所以说简单工厂又称之为静态工厂方法模式

这样的客户端只负责消费对象,而不负责对象的创建,工厂模式屏蔽了对象的创建细节

简单工厂由三个角色组成:

  • 抽象产品(Abstract Product)
  • 具体产品(Concrete Product)
  • 工厂类(Factory)

简单工厂模式的类图:

下面通过一段代码实现一个简单的工厂模式:

// 抽象产品
public interface Animal {
    void eat();

    void run();

    void sleep();
}

// 具体产品1
public class Cat implements Animal {
    @Override
    public void sleep() {
        System.out.println("小猫正在睡觉...");
    }

    @Override
    public void eat() {
        System.out.println("小猫正在吃...");

    }

    @Override
    public void run() {
        System.out.println("小猫正在跑...");
    }
}

// 具体产品2
public class Dog implements Animal {
    @Override
    public void sleep() {
        System.out.println("小狗正在睡觉...");
    }

    @Override
    public void eat() {
        System.out.println("小狗正在吃...");

    }

    @Override
    public void run() {
        System.out.println("小狗正在跑...");
    }
}

// 工厂类,动物管理员
public class ZooKeeper {
    public static Animal create(String type) {
        switch (type) {
            case "cat":
                return new Cat();
            case "dog":
                return new Dog();
            default:
                throw new IllegalArgumentException("不支持的类型:" + type);
        }
    }
}

// 测试类
public class Client {
    public static void main(String[] args) {
        Animal dog = ZooKeeper.create("dog");
        Animal cat = ZooKeeper.create("cat");
    }
}

如果具体产品类没有共同的逻辑,那么抽象产品的角色可以是一个Java接口,如果有相同的逻辑,那么公有逻辑移到抽象角色里,此时抽象角色可以是抽象类。

省略抽象产品角色

如果只有一个具体产品角色,那么可以省略抽象产品角色,这样就只剩下两个角色:具体产品和工厂

// 具体产品
public class Dog {
    public void sleep() {
        System.out.println("小狗正在睡觉...");
    }

    public void eat() {
        System.out.println("小狗正在吃...");

    }

    public void run() {
        System.out.println("小狗正在跑...");
    }
}

// 工厂类,动物管理员
public class ZooKeeper {

    public static Dog create() {
        return new Dog();
    }
}

工厂角色和抽象产品角色合并

工厂角色和抽象产品角色合并意思就是说抽象产品具有工厂角色的功能

这样的例子在 JDK 源码中也是很常见,比如 Calendar/DateFormat,这里仅以 Calendar 为例 :

首先 Calendar 是一个抽象类,它是一个抽象产品,然后它提供了一系列的静态工厂方法来后去实例

public abstract class Calendar implements Serializable, Cloneable, Comparable<Calendar> {
    
    // 静态工厂方法
    public static Calendar getInstance() {
        //...
    }
    public static Calendar getInstance(TimeZone zone) {
        //...
    }
    public static Calendar getInstance(Locale aLocale) {
        //... 
    }
    public static Calendar getInstance(TimeZone zone, Locale aLocale) {
        //...
    }
}

Calendar 是抽象类,不能直接被实例化,所以静态工厂方法返回的 Calendar 的子类

这样就将具体子类实例化的工作隐藏起来了,使用者无需考虑子类该如何实例化

将来如果有新的子类(新的具体产品)被添加,静态工厂方法可以将新的子类实例返回给调用者,对客户端使用者完全透明,扩展性更好。

三个角色全部合并

在实际开发中,根据情况也可以将工厂类和具体产品类合并,这样就只有一个角色了:具体产品类。也就是说产品类自行创建自己的实例:

public class Cat{
    public void sleep() {
        System.out.println("小猫正在睡觉...");
    }
    public void eat() {
        System.out.println("小猫正在吃...");

    }
    public void run() {
        System.out.println("小猫正在跑...");
    }
    // 静态工厂方法
    public static Cat create(String type) {
        return new Cat();
    }
}

三个角色全部合并的简单工厂方法模式在 JDK 中也有着大量的应用,如 Java 中基本类型的装包类型中的 valueOf 方法:

public static Float valueOf(float f) {
    return new Float(f);
}

public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}

public static Boolean valueOf(boolean b) {
    return (b ? TRUE : FALSE);
}

// 还有其他的装包类型就一一列举了

除此以外,在依赖注入框架 Dagger2 中也有应用:

public final class FragmentPresenter_Factory
    implements Factory<FragmentPresenter> {
  private final Provider<FragmentContract.View> viewProvider;

  private final Provider<Repository> RepositoryProvider;

  public FragmentPresenter_Factory(
      Provider<FragmentContract.View> viewProvider,
      Provider<Repository> RepositoryProvider) {
    assert viewProvider != null;
    this.viewProvider = viewProvider;
    assert RepositoryProvider != null;
    this.RepositoryProvider = RepositoryProvider;
  }

  @Override
  public FragmentPresenter get() {
    return new FragmentPresenter(
        viewProvider.get(), RepositoryProvider.get());
  }

 
  // 简单工厂方法创建实例
  public static Factory<FragmentPresenter> create(
      Provider<FragmentContract.View> viewProvider,
      Provider<Repository> RepositoryProvider) {
    return new FragmentPresenter_Factory(
        viewProvider, RepositoryProvider);
  }
}

还有在介绍 建造者模式 的时候讲到的简单工厂方法模式的对比案例:接口请求参数的封装,比如某个接口用获取订单各个状态下总数,店铺又分为餐饮行业和零售行业,这个接口的调用需要告知后台是获取餐饮的还是零售的,这个参数当然就可以使用常量来表示,这个参数不用每次都从外面传递进来,因为创建的过程可以屏蔽,代码如下:

public class OrderStatusRequest{
    
    // 省略其他代码...

    // 餐饮行业,只需要传递用户ID
    public static OrderStatusRequest createForCatering(String userId) {
        OrderStatusRequest request = new OrderStatusRequest();
        request.setUserId(userId);
        request.setType(IndustryTypeUtils.getIndustryType(IndustryType.CATERING));
        return request;
    }
    
    // 零售行业,只需要传递用户ID
    public static OrderStatusRequest createForRetail(String userId) {
        OrderStatusRequest request = new OrderStatusRequest();
        request.setUserId(userId);
        request.setType(IndustryTypeUtils.getIndustryType(IndustryType.RETAIL));
        return request;
    }
}

简单工厂模式的优缺点

简单工厂模式优点

简单工厂模式的核心是工厂类,工厂类负责产品实例的创建,对使用这屏蔽了具体的创建对象的细节,使用者只负责对产品的消费,很好的对职责进行划分。相较于通过构造方法创建对象的方式有如下优势:

  • 简单工厂模式有自己的方法名称,可读性更好

    例如上面的例子:接口请求参数的封装,通过两个静态工厂方法,区分不同的“行业”,开发者很容易选择使用哪个方法。

  • 对返回的对象有更好的控制

    通过静态工厂方法获取的对象可以是通过缓存获取的,构造函数则实现不了这一点
    例如,IntegervalueOf 静态工厂方法,只要整型值在 [-128, 127] 区间,都会返回缓存好的对象:

    public static Integer valueOf(int i) {
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
    }
    

    需要注意的是 127 这个值不是死的,可以配置的:

    private static class IntegerCache {
            static final int low = -128;
            static final int high;
            static final Integer cache[];
    
            static {
                // high value may be configured by property
                int h = 127;
                String integerCacheHighPropValue =
                    sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
                if (integerCacheHighPropValue != null) {
                    try {
                        int i = parseInt(integerCacheHighPropValue);
                        i = Math.max(i, 127);
                        // Maximum array size is Integer.MAX_VALUE
                        h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
                    } catch( NumberFormatException nfe) {
                        // If the property cannot be parsed into an int, ignore it.
                    }
                }
                high = h;
    
                cache = new Integer[(high - low) + 1];
                int j = low;
                for(int k = 0; k < cache.length; k++)
                    cache[k] = new Integer(j++);
    
                // range [-128, 127] must be interned (JLS7 5.1.7)
                assert IntegerCache.high >= 127;
            }
    
            private IntegerCache() {}
        }
    
    
  • 简单工厂方法可以返回原类型的任何子类型对象

    这个已经在介绍 工厂角色和抽象产品角色合并 一节介绍过了。

简单工厂模式缺点

简单工厂模式的缺点主要体现在两个方面:

  • 工厂类比较臃肿

    因为工厂类包含了所有产品的创建逻辑,形成一个无所不知的类,如果产品比较多,工厂类就变得比较庞大

  • 开闭原则 支持的不够彻底

    对于 开闭原则 不是很了解的,可以看我前面的文章《设计模式 ~ 面向对象 6 大设计原则剖析与实战》
    开闭原则 简而言之就是 对修改关闭,对扩展开放。如果我们需要创建一个新的产品,对消费的地方(Client)是成立,它不需要修改,所以从这个角度看是符合开闭原则的
    但是工厂类需要修改,每新增一个新的产品,都需要修改工厂类的代码,从这个角度看是不符合开闭原则的。所以说简单工厂对开闭原则支持不彻底

工厂方法模式

上面我们提到了 简单工厂 的一些缺点,接下来我们看下 工厂方法模式是如何保持简单工厂的优点,克服了简单工厂模式的缺点的。

我们先看下工厂模式的定义: 定义一个用于创建对象的接口,让子类决定实例化哪个类,工厂方法使一个类的实例化延迟到其子类

工厂方法模式包含下面4个角色:

  • 抽象工厂

    该角色是工厂方法模式的核心,任何创建对象的工厂都需要实现该接口

  • 具体工厂

    该角色实现抽象工厂接口,用于Client调用创建具体产品

  • 抽象产品

    该角色是产品对象的共同父类,抽象具体产品的公共逻辑

  • 具体产品

    该角色实现了抽象产品接口,一个具体工厂创建对应的具体产品

工厂方法模式的类图如下所示:

下面我们还是上面 动物园 的例子,现在改成每中动物一个工厂类,而不是用一个工厂类

抽象工厂类

public interface AnimalFactory {
    Animal create();
}

Dog工厂类

public class DogFactory implements AnimalFactory {
    @Override
    public Animal create() {
        return new Dog();
    }
}

Cat工厂类

public class CatFactory implements AnimalFactory {
    @Override
    public Animal create() {
        return new Cat();
    }
}

测试类

public class Client {
    public static void main(String[] args) {
        AnimalFactory dogFactory = new DogFactory();
        Animal dog = dogFactory.create();
        dog.eat();

        AnimalFactory catFactory = new CatFactory();
        Animal cat = catFactory.create();
        cat.eat();
    }
}

// 控制台输出:

小狗正在吃...
小猫正在吃...

需要注意的是工厂方法应当返还的是产品的抽象类型,而不是具体类型,只有这样才能保证针对产品的多态性。如果工厂方法返回类型声明为具体类型,客户端从工厂方法的声明上可以得知将要得到的是什么类型的对象,这违背了工厂方法模式的用意,这也就是为什么工厂方法模式也叫做多态性工厂。另一方面,如果只有一个具体工厂,抽象工厂角色也可以省略。

抽象工厂模式

抽象工厂是所有形态的工厂模式中最为抽象和最具一般性的形态,它的类图如下所示:

抽象工厂可以向客户端提供一个接口,使得客户端再不必指定产品的具体类型的情况下,创建多个 产品族 中的产品对象。

抽象工厂模式与工厂方法模式的最大区别在于工厂方法模式针对的是一个 产品等级结构,而抽象工厂模式则需要多个 产品等级结构

上面提到了两个关键字:产品族产品等级结构

那什么是产品等级结构?什么是产品族?这是理解抽象工厂的关键

产品的等级结构 ,比如苹果这个产品的等级结构: Fruit <- Apple <- RedApple,这整个属于 1 个产品等级结构

产品族 是指位于不同产品等级结构中,功能相关联的产品组成的家族

使用一个二维坐标系来表示,如下图所示:

上面的坐标系中,横轴表示 产品等级结构,纵轴表示 产品族。一共 4 个产品族 ,3 个等级结构

上图给出的三个不同的等级结构具有平行的结构,意思就是说这三类产品都是平级的。

如果使用工厂方法模式的话,需要三个独立的工厂等级来应付这三个产品等级结构。随着产品等级结构(产品类目)的数目增加,工厂方法模式所需要的工厂等级结构数目也会随着增加。

这个时候抽象工厂模式就闪亮登场了,使用同一工厂等级结构来应对这些相同或者相似的产品等级结构,也就是说一个工厂等级可以创建分属于不同的产品等级结构的一个产品族中的所有对象,这样描述可能比较难以理解,通过下面的图帮助理解:


从上图可以看出,一个工厂等级结构可以创建出分属于不同产品等级结构的一个产品族中的所有对象,显然,抽象工厂模式比工厂方法模式更加有效率。

这个时候读者可能有疑问了,从上面的图来看, 如果使用 工厂方法模式 貌似效率更高呀,只需要 3 个工厂类,使用了抽象工厂模式反而需要 4 个工厂类,你怎么能说抽象工厂模式比工厂方法模式效率更高呢?这需要从两个方面来谈一谈:

  • 抽象工厂模式主要是解决多个产品等级结构的

    也就是说抽象工厂模式的侧重点是多个产品等级结构,看下面的图就明白了:

  • 抽象工厂模式主要是用来创建具有共性的、分布在不同产品等级结构中的所有产品

    比如下面的 抽象工厂模式的应用 中的例子,WinButtonWinText,分别属于不同的产品等级结构中,但是它们有一个共性就是都属于 Windows 控件,如果使用工厂方法模式的话,需要有 ButtonFactoryTextFactory,此时如果只需要创建 Windows 平台的 ButtonText,需要用到 ButtonFactoryTextFactory 两个类,这两个类即可创建 Windows 控件,也包含了 Unix 控件的功能,但是创建 Unix 控件的功能我们不需要。如果使用抽象工厂模式的话,只需要 WinFactory 即可,可以很好的解决这个问题, 因为它就是用来创建具有共性的、分布在不同产品等级结构中的产品族中的所有对象。

抽象工厂模式的应用

假设子系统中需要一些产品对象,而这些产品对象又属于一个以上的产品等级,那么为了将消费这些产品对象的责任和创建这些产品对象的责任分割开,可以使用抽象工厂。这句话怎么理解呢?下面我们可以通过一个代码案例来分析。

创建一个不同系统的视图控件,例如在 Windows 系统有 ButtonText 等控件,同样在 Unix 系统也有。也就是说有 2 个产品等级结构了:ButtonText

同时也有 2 个产品族,通过坐标系表示如下图所示:

下面通过代码的方式来实现下上面描述的场景

抽象产品

public interface Button {
}

public interface Text {
}

具体产品

public class WinButton implements Button {
}

public class UnixButton implements Button {
}

抽象工厂

public interface Factory {

    Button createButton();
    
    Text createText();
}

具体工厂

public class WinFactory implements Factory {
    @Override
    public Button createButton() {
        return new WinButton();
    }

    @Override
    public Text createText() {
        return new WinText();
    }
}


public class UnixFactory implements Factory {
    @Override
    public Button createButton() {
        return new UnixButton();
    }

    @Override
    public Text createText() {
        return new UnixText();
    }
}

测试

public class Client {
    public static void main(String[] args) {
        Factory winFactory = new WinFactory();
        Button winBtn = winFactory.createButton();
        Text winTxt = winFactory.createText();

        Factory unixFactory = new UnixFactory();
        Button unixBtn = unixFactory.createButton();
        Text unixTxt = unixFactory.createText();
    }
}

从上面代码可以看出, 有多少个产品等级结构,就会在工厂角色中发现有多少个工厂方法。每个产品等级结构中有多少具体产品,就会有多少个产品族,也就会在工厂等级结构总发现多少个具体工厂。

抽象工厂模式对开闭原则的支持

开闭原则就是对修改关闭对扩展开放,也就是在不修改原有代码的情况下,通过扩展的方式增强功能。

通过上面的分析,抽象工厂有两个核心概念:产品族产品等级结构

所以增强抽象工厂模式的功能,要么增加产品族,要么增加新的产品等级结构,总结一句话就是横向扩展还是纵向扩展

先来看下 增加产品族,也就是纵向扩展。这个时候只需要增加新的具体工厂即可,原来的工厂类不需要修改,从只增加产品族的角度看抽象工厂是支持开闭原则的,如下图所示:

再来看下 增加产品等级结构,也就是横向扩展。这个时候需要修改所有的工厂角色,因为需要添加新的方法。从只新增产品等级结构的角度看抽象工厂是不支持开闭原则的,如下图所示:

也就是说,抽象工厂模式和工厂方法模式一样,对 开闭原则 的支持不够彻底。

小结

本文详细介绍了工厂模式中的简单工厂模式、工厂方法模式、抽象工厂模式。虽然 简单工厂模式 相较于构造方法有很多优势,但是对 开闭原则 支持不够彻底,然后引出了 工厂方法模式 是如何克服这些不足的。最后介绍了 工厂方法模式 无法很好应对多个产品等级结构的情况,于是引出了 抽象工厂模式,同时也介绍了抽象工厂适用的应用场景。希望本文能够帮助你深入掌握工厂模式,在实际开发中能够选择对应的工厂模式来解决实际问题。

Reference

  • 《设计模式之禅》
  • 《Java设计模式及实践》
  • 《Java设计模式深入研究》
  • 《Java与模式》
  • 《Effective Java》

下面是我的公众号,干货文章不错过,有需要的可以关注下,有任何问题可以联系我:


转载:https://blog.csdn.net/johnny901114/article/details/100779270
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场