設(shè)計(jì)模式系列2--三大工廠模式

image

今天學(xué)習(xí)下最常見的工廠模式,工廠模式細(xì)分下來(lái)有三大類:

 1. 簡(jiǎn)單工廠
 2. 工廠模式
 3. 抽象工廠模式

他們的目標(biāo)都是一樣的:封裝對(duì)象的創(chuàng)建。但是實(shí)現(xiàn)手段和使用場(chǎng)景卻是不相同。使用的時(shí)候三個(gè)模式也可以互相替換使用,導(dǎo)致很容易混淆三者。

下面我們來(lái)具體看看三者的使用。


簡(jiǎn)單工廠模式

準(zhǔn)確的說(shuō)簡(jiǎn)單工廠不是一個(gè)模式,而是一種編程習(xí)慣。但是平時(shí)使用的非常多,我們就把他歸到模式一類了。

1、定義

提供一個(gè)創(chuàng)建對(duì)象實(shí)例的功能,而無(wú)需關(guān)心具體實(shí)現(xiàn)。被創(chuàng)建的類型可以使接口、抽象類、具體類。

2、UML結(jié)構(gòu)圖及說(shuō)明

image

obstractClass:可以實(shí)現(xiàn)為抽象類或者具體接口,看實(shí)際需要選擇,定義具體類需要實(shí)現(xiàn)的功能
concreteClass:實(shí)現(xiàn)抽象類所定義功能的具體類,可能會(huì)有多個(gè)
simpleFactory:簡(jiǎn)單工廠,選擇合適的具體類來(lái)創(chuàng)建對(duì)象返回
client:通過simplefactory來(lái)獲取具體的對(duì)象

如果對(duì)UML圖不了解,可以先看看這篇文章:UML類圖幾種關(guān)系的總結(jié)

3、實(shí)際場(chǎng)景運(yùn)用

3.1、需求

假設(shè)我們要實(shí)現(xiàn)一個(gè)電腦組裝的功能,組裝電腦很重要的一個(gè)地方就是根據(jù)客戶指定的cpu類型來(lái)安裝。假設(shè)我們有三種類型的cpu供客戶選擇:apple,intel,AMD。

3.2、普通實(shí)現(xiàn)

在客戶端加入如下方法:

client.m文件
=====================

#import "simpleFactory.h"
#import "interCpu.h"
#import "appleCpu.h"
#import "AMDCpU.h"

@implementation client

-(Cpu *)selectCpuWithType:(NSString *)type{
    Cpu *cpu = nil;
    if ([type isEqualToString:@"intel"]) {
        cpu = [interCpu new];
        
    }else if([type isEqualToString:@"AMD"]){
        cpu = [AMDCpU new];
        
    }else{
        cpu = [appleCpu new];
        
    }
    return  cpu;
}

@end


比如像使用inter類型的cpu,只需要如下代碼:

[self selectCpuWithType@"interCpu"];

這里我只是展現(xiàn)了核心代碼,忽略了其他代碼。你需要?jiǎng)?chuàng)建一個(gè)CPU的父類,然后創(chuàng)建三個(gè)子類繼承它,分別是interCpu、AMDCpu、appleCpu。

上面的代碼可以完成功能,根據(jù)客戶傳入的type類型來(lái)創(chuàng)建相應(yīng)的cpu具體對(duì)象。

3.3、問題

雖然上述代碼可以完成功能,但是有如下問題:

1、如果要加入其他cpu類型,或者更改cpu類型,那么必須修改客戶端代碼。違反了開閉原則(不了解的童鞋可以去看設(shè)計(jì)模式開篇漫談

2、客戶端知道所有的具體cpu類,耦合度太高。客戶端必須知道所有具體的cpu類,那么任何一個(gè)類的改動(dòng)都可能會(huì)影響到客戶端。

3.4、解決問題

客戶端必須了解所有的具體cpu類才能創(chuàng)建對(duì)象,但是這會(huì)導(dǎo)致上述一系列問題。那么解決辦法就是把這些對(duì)象的創(chuàng)建封裝起來(lái),對(duì)客戶端不可見,那么之后如何改動(dòng)具體類都不會(huì)影響到客戶端。這可以通過簡(jiǎn)單工廠來(lái)實(shí)現(xiàn)。

下面我們來(lái)看看使用簡(jiǎn)單工廠重寫后的代碼

引入簡(jiǎn)單工廠類:

simpleFactory.h文件

=======================

#import <Foundation/Foundation.h>
#import "Cpu.h"

@interface simpleFactory : NSObject
-(Cpu *)selectCpuWithType:(NSString *)type;

@end


~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

simpleFactory.m文件

=======================


#import "simpleFactory.h"
#import "interCpu.h"
#import "appleCpu.h"
#import "AMDCpU.h"

@implementation simpleFactory

-(Cpu *)selectCpuWithType:(NSString *)type{
    Cpu *cpu = nil;
    if ([type isEqualToString:@"intel"]) {
        cpu = [interCpu new];
        
    }else if([type isEqualToString:@"AMD"]){
        cpu = [AMDCpU new];
        
    }else{
        cpu = [appleCpu new];
        
    }
    return  cpu;
}

@end

客戶端調(diào)用代碼:

#import <Foundation/Foundation.h>
#import "simpleFactory.h"
#import "Cpu.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        simpleFactory *factory = [simpleFactory new];
        Cpu *cpu = [factory selectCpuWithType:@"interCpu"];
        [cpu installCpu];
    }
    return 0;
}

此時(shí)不管是增加還是減少或者修改cpu類型,客戶端代碼都不用改動(dòng),降低了客戶端和具體cpu類的耦合,也遵循了開閉原則

4、反思

細(xì)心的一點(diǎn)的童鞋可能發(fā)現(xiàn),你這不是逗我嗎,僅僅是把本來(lái)客戶端的代碼移到了簡(jiǎn)單工廠類而已,有什么改變嗎?

理解這個(gè)問題的關(guān)鍵在于理解簡(jiǎn)單工廠所在的位置。

前面我們把創(chuàng)建具體cpu對(duì)象的代碼放在客戶端,導(dǎo)致一系列問題。我們的目標(biāo)就是讓客戶端從創(chuàng)建具體對(duì)象中解耦出來(lái),讓客戶端不知道對(duì)象創(chuàng)建的具體過程。而簡(jiǎn)單工廠就是和具體對(duì)象封裝在一起,算是一個(gè)封裝體內(nèi),所以簡(jiǎn)單工廠知道具體的實(shí)現(xiàn)類是沒有關(guān)系的。現(xiàn)在客戶端只要知道簡(jiǎn)單工廠和一個(gè)抽象類cpu,就可以創(chuàng)建具體對(duì)象了,實(shí)現(xiàn)了解耦。

5、改進(jìn)

雖然上面使用簡(jiǎn)單工廠后,讓客戶端實(shí)現(xiàn)了解耦,但是如果實(shí)現(xiàn)類改變了,我們還是需要需改簡(jiǎn)單工廠。有沒有什么辦法做到即使實(shí)現(xiàn)類改變也不需要改變簡(jiǎn)單工廠的代碼呢?

在java中可以使用反射或者IoC/DI來(lái)實(shí)現(xiàn),在iOS種我們有更簡(jiǎn)單的方法,一個(gè)方法足矣,具體見代碼

-(Cpu *)selectCpuWithType:(NSString *)type{
    Cpu *cpu = (Cpu *)[NSClassFromString(type)new];
    if ([cpu isKindOfClass:[Cpu class]] && cpu) {
        return  cpu;
    }else{
        return nil;
    }
}

客戶端代碼不需要改動(dòng),是不是簡(jiǎn)單了很多?

6、簡(jiǎn)單工廠優(yōu)缺點(diǎn)

  • 優(yōu)點(diǎn)
    1. 幫助封裝

      簡(jiǎn)單工廠雖然簡(jiǎn)單,但是幫我們實(shí)現(xiàn)了封裝對(duì)象創(chuàng)建的過程,讓我們可以實(shí)現(xiàn)面向接口編程。

    2. 解耦

      客戶端不需要知道具體實(shí)現(xiàn)類,也不需要知道創(chuàng)建過程。只需要知道簡(jiǎn)單工廠類就可以創(chuàng)建具體對(duì)象,實(shí)現(xiàn)了解耦

  • 缺點(diǎn)

    1.增加客戶端復(fù)雜度

    如果是通過參數(shù)來(lái)選擇創(chuàng)建具體的對(duì)象,那么客戶端就必須知道每個(gè)參數(shù)的含義,也就暴露了內(nèi)部實(shí)現(xiàn)

    2.不方便擴(kuò)展

    如果實(shí)現(xiàn)類改變,那么還是需要修改簡(jiǎn)單工廠,可以通過文中的方法來(lái)避免這個(gè)問題。或者使用下節(jié)我們講的工廠方法來(lái)解決

7、簡(jiǎn)單工廠本質(zhì)

簡(jiǎn)單工廠的本質(zhì):選擇實(shí)現(xiàn)

簡(jiǎn)單的工廠的本質(zhì)在于選擇,而不是實(shí)現(xiàn),實(shí)現(xiàn)是由具體類完成的,不要在簡(jiǎn)單工廠完成。簡(jiǎn)單工廠的目的是讓客戶端通過自己這個(gè)中介者來(lái)選擇具體的實(shí)現(xiàn),從而讓客戶端和具體實(shí)現(xiàn)解耦,任何實(shí)現(xiàn)方面的變化都被簡(jiǎn)單工廠屏蔽,客戶端不會(huì)知道。

簡(jiǎn)單工廠的實(shí)現(xiàn)難點(diǎn)在于如何“選擇實(shí)現(xiàn)”,前面講到的是靜態(tài)傳遞參數(shù)。其實(shí)還可以在運(yùn)行過程中從內(nèi)存或者數(shù)據(jù)庫(kù)動(dòng)態(tài)選擇參數(shù)來(lái)實(shí)現(xiàn),具體代碼就不演示了,只是讀取參數(shù)的方式不同,其他都一樣。

8、何時(shí)使用簡(jiǎn)單工廠

  1. 想完全封裝隔離具體實(shí)現(xiàn)

讓外部只能通過抽象類或者接口來(lái)操作,上面的例子中,就是只能操作抽象類cpu,而不能操作具體類。此時(shí)可以使用簡(jiǎn)單工廠,讓客戶端通過簡(jiǎn)單工廠來(lái)選擇創(chuàng)建具體的類,不需要?jiǎng)?chuàng)建的具體過程。

  1. 想把創(chuàng)建對(duì)象的職責(zé)集中管理起來(lái)

一個(gè)簡(jiǎn)單工廠可以創(chuàng)建許多相關(guān)或者不相關(guān)的對(duì)象,所以可以把對(duì)象的創(chuàng)建集中到簡(jiǎn)單工廠來(lái)集中管理。

完整代碼見文末。


工廠模式

1、問題

讓我們回到最原始的代碼:

client.m文件
=====================

#import "simpleFactory.h"
#import "interCpu1179.h"
#import "appleCpu1179.h"
#import "AMDCpU1179.h"

@implementation client

-(Cpu *)selectCpuWithType:(NSString *)type{
    Cpu *cpu = nil;
    if ([type isEqualToString:@"intel1179"]) {
        cpu = [interCpu1179 new];
        
    }else if([type isEqualToString:@"intel753"]){
        cpu = [interCpu753 new];
        
    }else if([type isEqualToString:@"AMD1179"]){
        cpu = [AMDCpU1179 new];
        
    }else if([type isEqualToString:@"AMD753"]){
        cpu = [AMDCpu753 new];
        
    }else if([type isEqualToString:@"apple1179"]){
        cpu = [appleCpu1179 new];
        
    }else if([type isEqualToString:@"apple753"]){
        cpu = [appleCpu753 new];
        
    }else{
        return nil;
    }return  cpu;
}

@end

仔細(xì)看這段代碼,就會(huì)發(fā)現(xiàn)一個(gè)問題:依賴于具體類。因?yàn)楸仨氃谶@里完成對(duì)象創(chuàng)建,所以不得不依賴于具體類:interCpu、appleCpu、AMDCpu。

這會(huì)導(dǎo)致什么問題呢?簡(jiǎn)單來(lái)說(shuō)就是違反了依賴倒置原則,讓高層組件client依賴于底層組件cpu。違反這個(gè)原則的后果就是一旦底層組件改動(dòng),那么高層組件也就必須改動(dòng),違反了開閉原則。聯(lián)系到上面的這個(gè)例子就是如果增加或者修改一個(gè)cpu子類,那么就必須改動(dòng)上面的代碼,即使使用了簡(jiǎn)單工廠模式,還是要修改簡(jiǎn)單工廠的代碼。

我們先來(lái)看看什么是依賴倒置原則:

定義:

要依賴抽象,不要依賴具體

展開來(lái)說(shuō)就是:不能讓高層組件依賴低層組件,而且不管高層還是低層組件,都應(yīng)該依賴于抽象。

那么如何才能避免違反這一原則呢?下面有三條建議可以參考下:

  • 變量不可以持有具體類的引用,比如new一個(gè)對(duì)象
  • 不要讓類派生自具體類,不然就會(huì)依賴于具體類,最好派生自抽象類
  • 不要覆蓋基類中已經(jīng)實(shí)現(xiàn)的方法,如果覆蓋了基類方法,就說(shuō)明該類不適合做基類,基類方法應(yīng)該是被子類共享而不是覆蓋。

但是要完全遵守上面三條,那就沒法寫代碼了。所以合適變通才是,而工廠模式就是為了遵循依賴倒置原則而生的。

下面就來(lái)看看使用工廠模式如何解決這個(gè)問題。


2、定義

定義了一個(gè)創(chuàng)建對(duì)象的接口,由子類決定實(shí)例化哪一個(gè)類,讓類的實(shí)例化延遲到子類執(zhí)行。

3、UML結(jié)構(gòu)圖及說(shuō)明

image

先記住工廠模式實(shí)現(xiàn)了依賴倒置原則,至于如何實(shí)現(xiàn)的,暫且按下不表,我們先來(lái)看代碼

4、實(shí)際場(chǎng)景運(yùn)用

還是和簡(jiǎn)單工廠的同樣的需求,但是我們根據(jù)cpu的針腳個(gè)數(shù)增加了cpu的分類,比如intelCpu1179、intelCpu753。另外兩個(gè)類型的cpu也是如此,分為1179和753兩個(gè)類型的cpu。但是這次我們用工廠模式來(lái)實(shí)現(xiàn)。

定義一個(gè)工廠基類,定義一個(gè)工廠方法

#import <Foundation/Foundation.h>
#import "Cpu.h"

@interface factory : NSObject
-(Cpu*)createCpuWithType:(NSInteger)type;

@end


=============================
#import "factory.h"

@implementation factory
-(Cpu *)createCpuWithType:(NSInteger)type{
    @throw ([NSException exceptionWithName:@"繼承錯(cuò)誤" reason:@"子類必須重寫該方法" userInfo:nil]);
    return nil;
}
@end


下面是具體工廠,繼承自工廠基類,實(shí)現(xiàn)工廠方法來(lái)創(chuàng)建具體的cpu對(duì)象

#import <Foundation/Foundation.h>
#import "factory.h"

@interface intelFactory : factory

@end

===========================

#import "intelFactory.h"
#import "interCpu753.h"
#import "interCpu1179.h"
#import "Cpu.h"

@implementation intelFactory
-(Cpu *)createCpuWithType:(NSInteger)type{
    Cpu *cpu = nil;
    if (type == 753) {
        cpu = [interCpu753 new];
    }else{
        cpu = [interCpu1179 new];
    }
    return cpu;
}
@end


上面演示的是intelCpu工廠,另外的AMD和apple的cpu具體工廠類類似,就不貼代碼了。

客戶端調(diào)用:


#import <Foundation/Foundation.h>
#import "factory.h"
#import "Cpu.h"
#import "intelFactory.h"
#import "appleFactory.h"
#import "AMDFactory.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        factory *factory = nil;
        factory = [intelFactory new];
        Cpu *cpu1 = [factory createCpuWithType:753];
        [cpu1 installCpu];
        Cpu *cpu2 = [factory createCpuWithType:1179];
        [cpu2 installCpu];
        
        factory = [AMDFactory new];
        Cpu *cpu3 = [factory createCpuWithType:753];
        [cpu3 installCpu];
        Cpu *cpu4 = [factory createCpuWithType:1179];
        [cpu4 installCpu];

        
    }
    return 0;
}

如果此時(shí)又多了一個(gè)cpu類型,比如高通的cpu,那么只需要新建一個(gè)高通cpu的工廠類,繼承自factory類,然后實(shí)現(xiàn)工廠方法,就可以了。客戶端也可以根據(jù)自己的需要選擇使用哪個(gè)工廠,不用修改原有代碼。符合開閉原則:對(duì)修改關(guān)閉,對(duì)擴(kuò)展開放。

5、如何遵循依賴倒置原則

我們先來(lái)看看沒有使用工廠方法,各個(gè)類之間的依賴關(guān)系

image

可以看到高層組件client依賴于具體的低層組件cpu類,違反了依賴倒置原則。一般我們把功能的使用者歸到高層組件,把功能的提供者歸到低層組件。

再來(lái)看看使用工廠方法后各個(gè)類之間的依賴關(guān)系

image

可以看到高層組件client依賴于抽象類cpu,低層組件也就是各種cpu具體類也依賴于抽象類factory,符合依賴倒置原則。其實(shí)說(shuō)白了,就是要針對(duì)接口編程,而不是針對(duì)實(shí)現(xiàn)編程

那么倒置在哪里呢?

對(duì)比兩個(gè)圖,就會(huì)發(fā)現(xiàn)具體cpu類的箭頭從原來(lái)向下變成了向上,也就是說(shuō)依賴關(guān)系發(fā)生了倒置。我們來(lái)看看為什么會(huì)這樣。

第一個(gè)圖里面,因?yàn)槲覀冎苯釉赾lient里面去初始化各個(gè)cpu類,倒置client就必須依賴這些具體類,依賴關(guān)系向下。

第二個(gè)圖里面,每個(gè)cpu具體類,都繼承自抽象cpu類,并且實(shí)現(xiàn)了抽象cpu的方法installCpu,此時(shí)具體cpu類就依賴于抽象cpu類,依賴關(guān)系向上。

現(xiàn)在明白為什么叫做依賴倒置了吧?這一切都是工廠方法的功勞。

有人要說(shuō),這個(gè)用簡(jiǎn)單工廠也可以實(shí)現(xiàn)的呀。是的沒錯(cuò),簡(jiǎn)單工廠也能實(shí)現(xiàn),其實(shí)如果直接在工廠方法的抽象cpu類里面實(shí)現(xiàn)對(duì)象的創(chuàng)建,那么此時(shí)工廠模式就是簡(jiǎn)單工廠。但是工廠模式有一個(gè)簡(jiǎn)單工廠模式?jīng)]有的功能:遵循開閉原則。如果此時(shí)要增加或者修改一個(gè)cpu具體類,那么簡(jiǎn)單工廠的代碼就必須修改,而工廠方法只需要擴(kuò)展就行了,不用修改原有代碼。

6、工廠模式優(yōu)缺點(diǎn)

  • 優(yōu)點(diǎn)
    1. 可以在不知道具體實(shí)現(xiàn)的情況下編程

      工廠模式可以讓你在實(shí)現(xiàn)功能時(shí)候,不需要關(guān)心具體對(duì)象,只需要使用對(duì)象的抽象接口即可,上面例子中client使用的就是cpu抽 象類,而不是具體的cpu類。

    2. 更容易擴(kuò)展新版本

      如果需要加入新的實(shí)現(xiàn),只需要擴(kuò)展一個(gè)新類,然后繼承抽象接口實(shí)現(xiàn)工廠方法即可。遵循了開閉原則。

  • 缺點(diǎn)

    具體產(chǎn)品和工廠方法耦合,因?yàn)樵诠S方法中需要?jiǎng)?chuàng)建具體實(shí)例,所以它們會(huì)耦合

7、何時(shí)使用工廠模式

通過工廠模式定義我們知道,工廠模式主要是把對(duì)象的創(chuàng)建延遲到子類執(zhí)行。如何實(shí)現(xiàn)的呢?

拿上面的例子來(lái)說(shuō),當(dāng)我們調(diào)用抽象類factory的方法createCpuWithType的時(shí)候,真正執(zhí)行的是factory的子類,比如intelFactory。做到這點(diǎn)是面向?qū)ο笳Z(yǔ)言的基本特征之一:多態(tài),它可以實(shí)現(xiàn)父類的同一個(gè)方法在不同的子類中有不同的表現(xiàn)。

了解了工廠模式的本質(zhì),我們就知道在上面情況下可以使用它了

  • 一個(gè)類不想知道它所需要?jiǎng)?chuàng)建的對(duì)象所屬的類,比如client不需要知道intelCpu1179這個(gè)具體類
  • 一個(gè)類希望由他的子類來(lái)指定它所創(chuàng)建的對(duì)象,比如factory希望IntelFactory創(chuàng)建具體cpu對(duì)象

抽象工廠

1、業(yè)務(wù)場(chǎng)景

假設(shè)我們寫了一套系統(tǒng),底層使用了兩套數(shù)據(jù)庫(kù):sqlserver和access數(shù)據(jù)庫(kù)。但是針對(duì)業(yè)務(wù)邏輯的代碼不可能寫兩套,這樣非常麻煩,也不方便擴(kuò)展新的數(shù)據(jù)庫(kù)。我們需要提供一個(gè)統(tǒng)一的接口給業(yè)務(wù)層操作,切換數(shù)據(jù)庫(kù)也不需要修改業(yè)務(wù)層邏輯。

簡(jiǎn)化下需求,假設(shè)我們每個(gè)數(shù)據(jù)庫(kù)都有user和department兩張表,業(yè)務(wù)邏輯代碼如下:


        //業(yè)務(wù)邏輯
        [user insert:@"張三"];
        [user getUser];
        [deparment insert:@"財(cái)務(wù)"];
        [deparment getDepartment];


下面我們就來(lái)看看如何使用抽象工廠來(lái)實(shí)現(xiàn)這個(gè)需求

2、需求實(shí)現(xiàn)

2.1、創(chuàng)建抽象工廠接口

我們先創(chuàng)建一個(gè)抽象接口,在iOS里面我們使用協(xié)議實(shí)現(xiàn)。

IFactory.h文件
========================
@class IUser;
@class IDepartment;

@protocol IFactory <NSObject>
@required
-(IUser*)createUser;
-(IDepartment *)createDepartment;

@end

2.2、創(chuàng)建具體工廠

下面我們來(lái)創(chuàng)建兩個(gè)具體的工廠,分別針對(duì)兩個(gè)數(shù)據(jù)庫(kù),實(shí)現(xiàn)抽象工廠的方法,來(lái)創(chuàng)建具體的表對(duì)象

#import <Foundation/Foundation.h>
#import "IFactory.h"
#import "IUser.h"

@interface SqlServerFactory : NSObject<IFactory>

@end

======================

#import "SqlServerFactory.h"
#import "SqlServerUser.h"
#import "SqlServerDepartment.h"

@implementation SqlServerFactory

-(IUser *)createUser{
    return [SqlServerUser new];
}

-(IDepartment *)createDepartment{
    return [SqlServerDepartment new];
}
@end


AccessFactory類創(chuàng)建方法類似。

2.3、創(chuàng)建產(chǎn)品

現(xiàn)在我們需要?jiǎng)?chuàng)建具體工廠需要的產(chǎn)品,這里是兩張表:user和department。但是這兩張表有分為兩個(gè)體系,sqlserver的user和department表,access的user和department表。

我們把user表抽象為基類,下面分別實(shí)現(xiàn)sqlserver和access的子類user表。department表同理,不再貼代碼了。

抽象產(chǎn)品類


#import <Foundation/Foundation.h>

@interface IUser : NSObject
-(void)insert:(NSString *)user;
-(void)getUser;
@end

=======================
#import "IUser.h"

@implementation IUser
-(void)insert:(NSString *)user{
    @throw ([NSException exceptionWithName:@"繼承錯(cuò)誤" reason:@"子類沒有實(shí)現(xiàn)父類方法" userInfo:nil]);
}

-(void)getUser{
    @throw ([NSException exceptionWithName:@"繼承錯(cuò)誤" reason:@"子類沒有實(shí)現(xiàn)父類方法" userInfo:nil]);
}
@end


具體產(chǎn)品類

#import <Foundation/Foundation.h>
#import "IUser.h"

@interface SqlServerUser : IUser

@end

==================


#import "SqlServerUser.h"

@implementation SqlServerUser
-(void)insert:(NSString *)user{
    NSLog(@"向sqlserver數(shù)據(jù)庫(kù)插入用戶:%@", user);
}

-(void)getUser{
    NSLog(@"從sqlserver數(shù)據(jù)庫(kù)獲取到一條用戶數(shù)據(jù)");
}
@end


#import <Foundation/Foundation.h>
#import "IUser.h"

@interface AccessUser : IUser

@end

=========================

#import "AccessUser.h"

@implementation AccessUser
-(void)insert:(NSString *)user{
    NSLog(@"向access數(shù)據(jù)庫(kù)插入用戶:%@", user);
}

-(void)getUser{
    NSLog(@"從access數(shù)據(jù)庫(kù)獲取到一條用戶數(shù)據(jù)");
}

@end

2.4、客戶端調(diào)用

#import <Foundation/Foundation.h>
#import "IFactory.h"
#import "IUser.h"
#import "IDepartment.h"
#import "SqlServerFactory.h"
#import "AccessFactory.h"


int main(int argc, const char * argv[]) {
    @autoreleasepool {
        id<IFactory> DBFactory = [AccessFactory new];
        IUser *user = [DBFactory createUser];
        IDepartment *deparment = [DBFactory createDepartment];
        
        //業(yè)務(wù)邏輯
        [user insert:@"張三"];
        [user getUser];
        [deparment insert:@"財(cái)務(wù)"];
        [deparment getDepartment];
        
    }
    return 0;
}

輸出:

2016-11-22 17:38:30.667 抽象工廠模式[56330:792839] 向access數(shù)據(jù)庫(kù)插入用戶:張三
2016-11-22 17:38:30.668 抽象工廠模式[56330:792839] 從access數(shù)據(jù)庫(kù)獲取到一條用戶數(shù)據(jù)
2016-11-22 17:38:30.668 抽象工廠模式[56330:792839] 向access數(shù)據(jù)庫(kù)插入部門:財(cái)務(wù)
2016-11-22 17:38:30.668 抽象工廠模式[56330:792839] 從access數(shù)據(jù)庫(kù)獲取到一條部門數(shù)據(jù)

此時(shí)如果需要切換到sqlserver數(shù)據(jù)庫(kù),只需要更改如下代碼

id<IFactory> DBFactory = [AccessFactory new];
改為:
id<IFactory> DBFactory = [SqlServerFactory new];

但是抽象工廠有個(gè)缺點(diǎn):你想下,如果此時(shí)我想增加一張工資表,那么就必須修改抽象工廠接口類IFactory和每個(gè)具體工廠類SqlServerFactory、AccessFactory,違反了開閉原則。但是總體來(lái)瑕不掩瑜。

3、 實(shí)現(xiàn)原理分析

通過上面的例子,我想大家已經(jīng)認(rèn)識(shí)到抽象工廠的優(yōu)雅之處,那么它是如何完成的呢?

我們來(lái)把上面的例子做成UML圖,這樣看的更加清晰。

image

可以看到我們創(chuàng)建了兩個(gè)具體工廠,分別是sqlserverFactory和AccessFactory。我們的產(chǎn)品有兩個(gè)user和department,每個(gè)產(chǎn)品也分為兩個(gè)體系:sqlserver的access的。

如果選擇sqlserverFactory,那么對(duì)應(yīng)的兩個(gè)工廠方法就生成sqlserver的user和department表。選擇accessFactory也是如此。

所以我們可以很方便在兩個(gè)數(shù)據(jù)庫(kù)之間切換,而不影響業(yè)務(wù)邏輯,因?yàn)闃I(yè)務(wù)邏輯都是面向抽象編程。再看下業(yè)務(wù)邏輯的代碼

        id<IFactory> DBFactory = [AccessFactory new];
        IUser *user = [DBFactory createUser];
        IDepartment *deparment = [DBFactory createDepartment];
        
        //業(yè)務(wù)邏輯
        [user insert:@"張三"];
        [user getUser];
        [deparment insert:@"財(cái)務(wù)"];
        [deparment getDepartment];

可以看到業(yè)務(wù)邏輯都是針對(duì)抽象類IUesr和IDepartment編程,所以他們的子類如何變化,不會(huì)影響到業(yè)務(wù)邏輯。

4、 抽象工廠定義

提供一個(gè)創(chuàng)建一系列相關(guān)或者相互依賴的接口,而無(wú)需依賴具體類。

好好分析這句話,關(guān)鍵的地方就是:一系列相關(guān)或者相互依賴的接口。這決定了我們使用抽象工廠的初衷,抽象工廠定義了一系列接口,這些接口必須是相互依賴或者相關(guān)的,而不是把一堆沒有什么關(guān)聯(lián)的接口放到一起。

回頭看看我們上面的抽象工廠類IFactory定義的接口,是用來(lái)創(chuàng)建兩張表,這兩張表是屬于同一個(gè)數(shù)據(jù)庫(kù)的,他們之間是相互關(guān)聯(lián)和依賴的。

后面一句“無(wú)需依賴具體類”是怎么做到的呢?

可以看到抽象工廠類只是定義了接口,而真正去實(shí)現(xiàn)這些接口產(chǎn)生具體對(duì)象的是具體工廠。客戶端面向的也是抽象工廠類編程,所以無(wú)需依賴具體類。

我們可以把抽象工廠的定義的方法看做工廠方法,然后具體工廠去實(shí)現(xiàn)這些工廠方法,這不就是工廠模式嗎?
所以說(shuō)抽象工廠包含了具體工廠。

5、思考

工廠模式和抽象工廠模式最大的區(qū)別在于,后者的一系列工廠方法是相互依賴或者相關(guān)的,而工廠模式雖然也可以定義一些列工廠方法,但是他們之間是沒有關(guān)聯(lián)的。這是區(qū)分他們的重要依據(jù)。

其實(shí)如果抽象工廠里面只定義一個(gè)工廠方法,也就是只實(shí)現(xiàn)一個(gè)產(chǎn)品,那么久退換為工廠方法了。

記住:

工廠模式創(chuàng)建一種類型的產(chǎn)品,抽象工廠創(chuàng)建一些列相關(guān)的產(chǎn)品家族。

6、何時(shí)使用抽象工廠

  • 客戶端只希望知道抽象接口,而不關(guān)心具體產(chǎn)品的實(shí)現(xiàn)的時(shí)候
  • 一個(gè)系統(tǒng)需要有多個(gè)產(chǎn)品系列中的一個(gè)來(lái)配置的時(shí)候。也就是說(shuō)可以動(dòng)態(tài)切換產(chǎn)品系列,比如上面的切換兩個(gè)數(shù)據(jù)庫(kù)
  • 需要強(qiáng)調(diào)一系列產(chǎn)品的接口有關(guān)聯(lián)的時(shí)候,以便聯(lián)合使用它們。

三個(gè)模式對(duì)比

  • 抽象工廠模式和工廠模式

    工廠模式針對(duì)單獨(dú)產(chǎn)品的創(chuàng)建,而抽象工廠注重一個(gè)產(chǎn)品系列的創(chuàng)建。如果產(chǎn)品系列只有一個(gè)產(chǎn)品的 話,那么抽象工廠就退換到工廠模式了。在抽象工廠中使用工廠方法來(lái)提供具體實(shí)現(xiàn),這個(gè)時(shí)候他們聯(lián) 合使用。

  • 工廠模式和簡(jiǎn)單工廠

    兩者非常類似,都是用來(lái)做選擇實(shí)現(xiàn)的。不同的地方在于簡(jiǎn)單工廠在自身就做了選擇實(shí)現(xiàn)。而工廠模式 則是把實(shí)現(xiàn)延遲到子類執(zhí)行。如果把工廠方法的選擇實(shí)現(xiàn)直接在父類實(shí)現(xiàn),那么此時(shí)就退化為簡(jiǎn)單工廠 模式了。

  • 簡(jiǎn)單工廠和抽象工廠
    簡(jiǎn)單工廠用于做選擇實(shí)現(xiàn),每個(gè)產(chǎn)品的實(shí)現(xiàn)之間沒有依賴關(guān)系。而抽象工廠實(shí)現(xiàn)的一個(gè)產(chǎn)品系列,相互 之間有關(guān)聯(lián)。這是他們的區(qū)別


Demo下載地址

簡(jiǎn)單工廠

工廠模式

抽象工廠模式

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • 1 場(chǎng)景問題# 1.1 選擇組裝電腦的配件## 舉個(gè)生活中常見的例子——組裝電腦,我們?cè)诮M裝電腦的時(shí)候,通常需要選...
    七寸知架構(gòu)閱讀 4,419評(píng)論 6 67
  • 設(shè)計(jì)模式匯總 一、基礎(chǔ)知識(shí) 1. 設(shè)計(jì)模式概述 定義:設(shè)計(jì)模式(Design Pattern)是一套被反復(fù)使用、多...
    MinoyJet閱讀 3,982評(píng)論 1 15
  • 一、設(shè)計(jì)模式的分類 總體來(lái)說(shuō)設(shè)計(jì)模式分為三大類: 創(chuàng)建型模式,共五種:工廠方法模式、抽象工廠模式、單例模式、建造者...
    RamboLI閱讀 773評(píng)論 0 1
  • 簡(jiǎn)單工廠模式雖然簡(jiǎn)單,但存在一個(gè)很嚴(yán)重的問題。當(dāng)系統(tǒng)中需要引入新產(chǎn)品時(shí),由于靜態(tài)工廠方法通過所傳入?yún)?shù)的不同來(lái)創(chuàng)建...
    justCode_閱讀 1,211評(píng)論 1 9
  • 一. 昨晚我沒有做推送,因?yàn)槲彝祽辛耍颓蜿?duì)的人去了逛校道,一圈一圈地走著,聊著生活的瑣事,看著校道橘黃色的燈光,...
    陸木魚啊閱讀 432評(píng)論 1 1