抽象工厂和你们口中的“类簇”

类簇可以说是Objective-C语言中比较重要的设计,Apple在官方文档中用一篇文章来介绍这个概念,尽管文章点到为止,并没有深入到内部机制,但是也用了详细的例子来说明类簇的设计是多么优秀。Apple在文档中称类簇是基于抽象工厂模式来设计的,如果你对抽象工厂的定义不清晰,可能会问,抽象工厂是什么?如果你对抽象工厂的定义清晰,有可能会问,类簇的设计真的是基于抽象工厂模式吗?本文站在设计模式的源头,对类簇进行挖掘解读。

先别急,我的文章需要延续一贯的个人风格,有足够的准备,我们先来理解设计模式中的工厂模式

工厂模式

工厂模式一共有三种,简单工厂模式,工厂方法模式,抽象工厂模式。接下来我们依次通过图文并茂的描述进行理解

简单工厂模式

先来看看简单工厂的UML图(回归经典,用的Java语言)


我们将简单工厂模式分为几个部分来解读:

  • 产品:
    • 定义了一个抽象的产品类
    • 有两个具体产品类继承了该抽象类
  • 工厂:
    • creatProduct(String):Product方法:参数为String类型,返回值为Product类型
    • 伪代码实现:
      if(type == "A") {
          return new ProductA();
      } else {
          return new ProductB();
      }
      
  • 使用:
    • 伪代码示例:
      Product product = factory.creatProduct("A");
      product.operation();
      

可以发现,主要的逻辑代码写在了工厂的方法中,那么如果产品种类增多,我们就需要去修改工厂的方法。回忆一下设计模式六大原则之一的开放-封闭原则,开放指的是面向扩展开放,封闭指的是面向修改封闭。再回到简单工厂模式来看,是不是违背了原则呢。所以在实际应用中,正如它的名字,它仅仅在一些简单场景使用。

工厂方法模式

同样地,我们先来看看工厂方法模式的UML


同样地,将其分为几个部分来解读:

  • 产品:
    • 定义了一个抽象的产品类
    • 有两个具体产品类继承了该抽象类
  • 工厂:
    • 定义了一个抽象的工厂类
    • 有两个具体工厂类继承了该抽象类
    • FactoryA类中的+ creatProduct():Product方法伪代码:
      return new ProductA();
      
    • FactoryB类中的creatProduct():Product方法伪代码:
      return new ProductB();
      
  • 使用:
    • 伪代码演示:
      // 创建产品A
      Factory factoryA = new FactoryA();
      Product productA = factoryA.creatProduct();
      // 创建产品B
      Factory factoryB = new FactoryB();
      Product productB = factoryB.creatProduct();
      

看完了简单工厂模式工厂方法模式,通过对比可以发现,工厂方法模式在简单工厂模式的基础之上,践行了开放-封闭原则,创建产品不再通过传入参数判断应该生成哪个具体产品,而是将工厂创建产品的任务放在了子类去做。当增加了新的产品时,我们只需要创建新的子类,实现方法,不需要对抽象类进行修改。

抽象工厂模式

老样子,来看看抽象工厂的UML


分成三部分来解读抽象工厂模式:

  • 产品:
    • 有两个抽象的产品类
    • 有两个具体产品类分别继承了各自的抽象类
  • 工厂:
    • 定义了一个抽象的工厂类
    • 有两个具体工厂类继承了该抽象类
    • 创建产品的方法,以Factory1类示例:
      • creatProductA():ProductA方法的伪代码:
        return new ProductA1();
        
      • creatProductB():ProductB方法的伪代码:
        return new ProductB1();
        
  • 使用:
    • 伪代码演示:
      // 型号为1的产品
      Factory factory1 = new Factory1();
      ProductA productA1 = factory1.creatProductA();
      ProductB productB1 = factory1.creatProductB();
      // 型号为2的产品
      Factory factory2 = new Factory2();
      ProductA productA2 = factory2.creatProductA();
      ProductB productB2 = factory2.creatProductB();
      

可以发现,抽象工厂模式其实就是在工厂方法模式的基础上增加了产品的种类,产品的抽象类从一个变成了多个,那么这样一来,产品就构成了一个体系。这个体系有两个关键词,产品簇产品等级结构,为了理解这两个关键字,我们先来看个实际的例子:

  • 产品等级结构:等级结构就是继承结构,如上图中手机和电脑,有一个抽象类,然后具体的产品继承这个抽象类
  • 产品簇:上图中iphoneX和MacBook Pro都是产于同一个公司,苹果公司。而小米MIX和小米笔记本Pro也是产于同一家公司。即产品簇的概念是,产于同一家工厂,位于不同产品等级结构中的一组产品。

有了以上的准备知识之后,我们来看看Objective-C中的类簇设计

类簇

官方文档中,苹果仅仅告诉我们类簇是基于抽象工厂模式来设计的

Class clusters are based on the Abstract Factory design pattern.

文档中通过对比是否使用类簇来设计接口,告诉了我们类簇的设计师优秀的。它能产生简单的概念,同时能够使接口简化。那么类簇是什么呢?

类簇的定义

Class clusters group a number of private concrete subclasses under a public abstract superclass. The grouping of classes in this way simplifies the publicly visible architecture of an object-oriented framework without reducing its functional richness.

苹果告诉我们,类簇汇集了若干私有具体子类隐藏在共有的抽象父类之下,通过这种方式,简化了面向对象框架中公共可见的架构,同时并没有减少框架功能的丰富性。

举个例子

来看下NSNumber,它是Objective-C中关于数据类型的封装类

Users of this hierarchy see only one public class, Number, so how is it possible to allocate instances of the proper subclass? The answer is in the way the abstract superclass handles instantiation.

对于这个层级的使用者,仅仅能看到一个共有类,那就是NSNumber,所以要分配内存创建合适的子类对象,又怎么可能呢?答案就是这个抽象父类对如何分配进行了处理。

看到这里,我心里油然而生一个大大的问号,这都哪跟哪,说好的抽象工厂呢,连个工厂都没有,产品自己生产了自己。如果大家也有这个问题,那么带着这个问题,继续往下看

类簇的使用

官方文档中举例:

NSNumber *aChar = [NSNumber numberWithChar:’a’];
NSNumber *anInt = [NSNumber numberWithInt:1];
NSNumber *aFloat = [NSNumber numberWithFloat:1.0];
NSNumber *aDouble = [NSNumber numberWithDouble:1.0];

The abstract superclass in a class cluster must declare methods for creating instances of its private subclasses. It’s the superclass’s responsibility to dispense an object of the proper subclass based on the creation method that you invoke—you don’t, and can’t, choose the class of the instance.

抽象父类务必需要定义创建私有子类的方法,这是抽象父类的职责。抽象父类需要基于大家调用的创建方法去分配内存到合适的子类,同时使用者不需要也不能选择对象的类型,统一使用NSNumber来管理。

到这里,官方介绍介绍,疑惑并没有解决,大家可以能有这种感觉,除了没有工厂之外,看起来就和简单工厂模式没两样。

我们应该如何理解?

首先,我们尝试着先找到工厂在哪里,官方文档中举例是通过类方法的,可以隐藏了一些细节,那么我们换做对象方法来试试

id number_alloc = [NSNumber alloc];
id number_init_int = [number_alloc initWithInt:3];
id number_init_bool = [number_alloc initWithBool:YES];
id number_init_char = [number_alloc initWithChar:'c'];
id number_init_float = [number_alloc initWithFloat:1.1];

再来看看类型

(lldb) p number_alloc
(NSPlaceholderNumber *) $0 = 0x0000600000017e30
(lldb) p number_init_int
(__NSCFNumber *) $1 = 0xb000000000000032 (int)3
(lldb) p number_init_bool
(__NSCFBoolean *) $2 = 0x00000001038815e0
(lldb) p number_init_char
(__NSCFNumber *) $3 = 0xb000000000000630 (char)99
(lldb) p number_init_float
(__NSCFNumber *) $4 = 0x00006080000323e0 (float)1.100000

看来是有些收获的,在alloc的后,得到的对象类型是NSPlaceholderNumber(回想之前解读官方文档时,苹果多次提到了allocate(分配))接着进行了init,得到的结果主要有两种类型,__NSCFNumber__NSCFBoolean。那么初步的UML架构就可以建立了:

是简单工厂吗?

从这个UML图来看,貌似和简单工厂模式差不多呢,但是细心观察,这里并没有像简单工厂模式一样违背了开放-封闭原则。在工厂类NSPlaceholderNumber中,提供了创建不种类产品的方法。这点和抽象工厂很像。那和抽象工厂不一样的地方呢?

和抽象工厂的对比

仔细地想下,NSNumberUML架构就仿佛是将抽象工厂模式中以工厂为单位分割出来的小单位。回到小米和苹果的那个例子,就好像其中的一个产品簇,例如只有苹果公司以及它的iphoneX和MacBook Pro。嘿嘿,看到这里,NSNumber的抽象工厂设计的神秘面纱悄悄地揭开了。

为何这么设计?

刚刚提到了,NSNumber目前只是产品簇中的一条产品线。那么如果以后产生了其他类型的产品线,苹果不需要修改之前的代码,只需要添加另外的工厂和另外的产品线,将开放-封闭原则发挥地玲离尽致。

参考文献

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