面向对象设计的设计模式(三):抽象工厂模式

2019-07-31 14:12:19 浏览数 (1)

定义

抽象工厂模式(Abstract Factory Pattern):提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们具体的类。

适用场景

有时候我们需要一个工厂可以提供多个产品对象,而不是单一的产品对象。比如系统中有多于一个的产品族,而每次只使用其中某一产品族,属于同一个产品族的产品将在一起使用。

在这里说一下产品族和产品等级结构的概念:

  • 产品族:同一工厂生产的不同产品
  • 产品等级结构:同一类型产品的不同实现

用一张图来帮助理解:

在上图中:

  • 纵向的,不同形状,相同色系的图形属于同一产品组的产品,而同一产品族的产品对应的是同一个工厂;
  • 横向的,同一形状,不同色系的图形属于统一产品等级结构的产品,而统一产品等级结构的产品对应的是同一个工厂方法。

下面再举一个例子帮助大家理解:

我们将小米,华为,苹果公司比作抽象工厂方法里的工厂:这三个工厂都有自己生产的手机,平板和电脑。 那么小米手机,小米平板,小米电脑就属于小米这个工厂的产品族;同样适用于华为工厂和苹果工厂。 而小米手机,华为手机,苹果手机则属于同一产品等级结构:手机的产品等级结构;平板和电脑也是如此。

结合这个例子对上面的图做一个修改可以更形象地理解抽象工厂方法的设计:

上面的关于产品族和产品等级结构的说法参考了慕课网实战课程:java设计模式精讲 Debug 方式 内存分析的6-1节。

成员与类图

成员

抽象工厂模式的成员和工厂方法模式的成员是一样的,只不过抽象工厂方法里的工厂是面向产品族的。

  1. 抽象工厂(Abstract Factory):抽象工厂负责声明具体工厂的创建产品族内的所有产品的接口。
  2. 具体工厂(Concrete Factory):具体工厂负责创建产品族内的产品。
  3. 抽象产品(Abstract Product):抽象产品是工厂所创建的所有产品对象的父类,负责声明所有产品实例所共有的公共接口。
  4. 具体产品(Concrete Product):具体产品是工厂所创建的所有产品对象类,它以自己的方式来实现其共同父类声明的接口。

下面通过类图来看一下各个成员之间的关系:

模式类图

抽象工厂模式类图

  • 抽象工厂模式与工厂方法模式最大的区别在于,工厂方法模式针对的是一个产品等级结构,而抽象工厂模式则需要面对多个产品等级结构
  • 增加新的具体工厂和产品族很方便,无须修改已有系统,符合“开闭原则”。

代码示例

场景概述

由于抽象工厂方法里的工厂是面向产品族的,所以为了贴合抽象工厂方法的特点,我们将上面的场景做一下调整:在上面两个例子中,商店只卖手机。在这个例子中我们让商店也卖电脑:分别是苹果电脑,小米电脑,华为电脑。

场景分析

如果我们还是套用上面介绍过的工厂方法模式来实现该场景的话,则需要创建三个电脑产品对应的工厂:苹果电脑工厂,小米电脑工厂,华为电脑工厂。这就导致类的个数直线上升,以后如果还增加其他的产品,还需要添加其对应的工厂类,这显然是不够优雅的。

仔细看一下这六个产品的特点,我们可以把这它们划分在三个产品族里面:

  1. 苹果产品族:苹果手机,苹果电脑
  2. 小米产品族:小米手机,小米电脑
  3. 华为产品族:华为手机,华为电脑

而抽象方法恰恰是面向产品族设计的,因此该场景适合使用的是抽象工厂方法。下面结合代码来看一下该如何设计。

代码实现

首先引入电脑的基类和各个品牌的电脑类:

电脑基类:

代码语言:javascript复制
//================== Computer.h ==================
@interface Computer : NSObject

//package to store
- (void)packaging;

@end



//================== Computer.m ==================
@implementation Computer

- (void)packaging{
    //implemented by subclass
}

@end

苹果电脑类 MacBookComputer

代码语言:javascript复制
//================== MacBookComputer.h ==================
@interface MacBookComputer : Computer

@end



//================== MacBookComputer.m ==================
@implementation MacBookComputer

- (void)packaging{
     NSLog(@"MacBookComputer has been packaged");
}

@end

小米电脑类 MIComputer

代码语言:javascript复制
//================== MIComputer.h ==================
@interface MIComputer : Computer

@end



//================== MIComputer.m ==================
@implementation MIComputer

- (void)packaging{
    NSLog(@"MIComputer has been packaged");
}

@end

华为电脑类 MateBookComputer

代码语言:javascript复制
//================== MateBookComputer.h ==================
@interface MateBookComputer : Computer

@end



//================== MateBookComputer.m ==================
@implementation MateBookComputer

- (void)packaging{
    NSLog(@"MateBookComputer has been packaged");
}

@end

引入电脑相关产品类以后,我们需要重新设计工厂类。因为抽象工厂方法模式的工厂是面向产品族的,所以抽象工厂方法模式里的工厂所创建的是同一产品族的产品。下面我们看一下抽象工厂方法模式的工厂该如何设计:

首先创建所有工厂都需要集成的抽象工厂,它声明了生产同一产品族的所有产品的接口:

代码语言:javascript复制
//================== Factory.h ==================
#import "Phone.h"
#import "Computer.h"

@interface Factory : NSObject

  (Phone *)createPhone;

  (Computer *)createComputer;

@end



//================== Factory.m ==================
@implementation Factory

  (Phone *)createPhone{

    //implemented by subclass
    return nil;
}

  (Computer *)createComputer{

    //implemented by subclass
    return nil;
}

@end

接着,根据不同的产品族,我们创建不同的具体工厂:

首先是苹果产品族工厂 AppleFactory

代码语言:javascript复制
//================== AppleFactory.h ==================
@interface AppleFactory : Factory

@end



//================== AppleFactory.m ==================
#import "IPhone.h"
#import "MacBookComputer.h"

@implementation AppleFactory

  (Phone *)createPhone{

    IPhone *iPhone = [[IPhone alloc] init];
    NSLog(@"iPhone has been created");
    return iPhone;
}

  (Computer *)createComputer{

    MacBookComputer *macbook = [[MacBookComputer alloc] init];
    NSLog(@"Macbook has been created");
    return macbook;
}

@end

接着是小米产品族工厂 MIFactory

代码语言:javascript复制
//================== MIFactory.h ==================
@interface MIFactory : Factory

@end



//================== MIFactory.m ==================
#import "MIPhone.h"
#import "MIComputer.h"

@implementation MIFactory

  (Phone *)createPhone{

    MIPhone *miPhone = [[MIPhone alloc] init];
    NSLog(@"MIPhone has been created");
    return miPhone;
}

  (Computer *)createComputer{

    MIComputer *miComputer = [[MIComputer alloc] init];
    NSLog(@"MIComputer has been created");
    return miComputer;
}

@end

最后是华为产品族工厂 HWFactory

代码语言:javascript复制
//================== HWFactory.h ==================
@interface HWFactory : Factory

@end



//================== HWFactory.m ==================
#import "HWPhone.h"
#import "MateBookComputer.h"

@implementation HWFactory

  (Phone *)createPhone{

    HWPhone *hwPhone = [[HWPhone alloc] init];
    NSLog(@"HWPhone has been created");
    return hwPhone;
}

  (Computer *)createComputer{

    MateBookComputer *hwComputer = [[MateBookComputer alloc] init];
    NSLog(@"HWComputer has been created");
    return hwComputer;
}

@end

以上就是工厂类的设计。这样设计好之后,客户端如果需要哪一产品族的某个产品的话,只需要找到对应产品族工厂后,调用生产该产品的接口即可。假如需要苹果电脑,只需要委托苹果工厂来制造苹果电脑即可;如果需要小米手机,只需要委托小米工厂制造小米手机即可。

下面用代码来模拟一下这个场景:

代码语言:javascript复制
//================== Using by client ==================


Store *store = [[Store alloc] init];

//Store wants to sell MacBook
Computer *macBook = [AppleFactory createComputer];
[macBook packaging];

[store sellComputer:macBook];


//Store wants to sell MIPhone
Phone *miPhone = [MIFactory createPhone];
[miPhone packaging];

[store sellPhone:miPhone];


//Store wants to sell MateBook
Computer *mateBook = [HWFactory createComputer];
[mateBook packaging];

[store sellComputer:mateBook];

上面的代码就是模拟了商店售卖苹果电脑,小米手机,华为电脑的场景。而今后如果该商店引入了新品牌的产品,比如联想手机,联想电脑,那么我们只需要新增联想手机类,联想电脑类,联想工厂类即可。

下面我们看一下该例子对应的 UML类图,可以更直观地看一下各个成员之间的关系:

代码对应的类图

抽象工厂模式代码示例类图

由于三个工厂的产品总数过多,因此在这里只体现了苹果工厂和小米工厂的产品。

优点

  • 具体产品在应用层代码隔离,不需要关心产品细节。只需要知道自己需要的产品是属于哪个工厂的即可 当一个产品族中的多个对象被设计成一起工作时,它能够保证客户端始终只使用同一个产品族中的对象。这对一些需要根据当前环境来决定其行为的软件系统来说,是一种非常实用的设计模式。

缺点

  • 规定了所有可能被创建的产品集合,产品族中扩展新的产品困难,需要修改抽象工厂的接口。
  • 新增产品等级比较困难
  • 产品等级固定,而产品族不固定,扩展性强的场景。

iOS SDK 和 JDK 中的应用

  • 暂未发现iOS SDK中使用抽象工厂方法的例子是NSNumber
  • JDK中有一个数据库连接的接口Connection。在这个接口里面有createStatement()prepareStatement(String sql)。这两个接口都是获取的统一产品族的对象,比如MySql和PostgreSQL产品族,具体返回的是哪个产品族对象,取决于所连接的数据库类型。

OK,到现在三个工厂模式已经讲完了。在继续讲解下面三个设计模式之前,先简单回顾一下上面讲解的三个工厂模式:

大体上看,简单工厂模式,工厂方法模式和抽象工厂模式的复杂程度是逐渐升高的。

  • 简单工厂模式使用不同的入参来让同一个工厂生产出不同的产品。
  • 工厂方法模式和抽象工厂模式都需要有特定的工厂类来生产对应的产品;而工厂方法模式里的工厂是面向同一产品等级的产品;而抽象工厂方法模式里的工厂是面向同一产品族的产品的。

在实际开发过程中,我们需要根据业务场景的复杂程度的不同来采用最适合的工厂模式。


0 人点赞