close

文/ 楊加康,CFUG 社區成員,《Flutter 開發之旅從南到北》作者,小米工程師

在圍繞設計模式的話題中,工廠這個詞頻繁出現,從 簡單工廠 模式到 工廠方法 模式,再到 抽象工廠 模式。工廠名稱含義是製造產品的工業場所,應用在面向對象中,順理成章地成為了比較典型的創建型模式。

圖源:https://media2.giphy.com/media/3ohjUKYWSqORcgIIsE/giphy.gif「

從形式上講,工廠可以是一個返回我們想要對象的一個方法 / 函數,即可以作為構造函數的一種抽象。

本文將會帶領大家使用 Dart 理解它們的各自的實現和它們之間的關係。

簡單工廠 & factory 關鍵字

簡單工廠模式 不在 23 種 GOF 設計模式中,卻是我們最常使用的一種編程方式。其中主要涉及到一個特殊的方法,專門用來提供我們想要的實例對象 (對象工廠),我們可以將這個方法放到一個單獨的類 SimpleFactory 中,如下:

classSimpleFactory{///工廠方法staticProductcreateProduct(inttype){if(type==1){returnConcreteProduct1();}if(type==2){returnConcreteProduct2();}returnConcreteProduct();}}

我們認為該方法要創建的對象同屬一個 Product 類 (抽象類),並通過參數 type 指定要創建具體的對象類型。Dart 不支持 interface 關鍵詞,但我們可以使用 abstract 以抽象類的方式定義接口, 然後各個具體的類型繼承實現它即可:

///抽象類abstractclassProduct{String?name;}///實現類classConcreteProductimplementsProduct{@overrideString?name='ConcreteProduct';}///實現類1classConcreteProduct1implementsProduct{@overrideString?name='ConcreteProduct1';}///實現類2classConcreteProduct2implementsProduct{@overrideString?name='ConcreteProduct2';}

當我們想要在代碼中獲取對應的類型對象時,只需要通過這個方法傳入想要的類型值即可, 我們不必關心生產如何被生產以及哪個對象被選擇的具體邏輯:

voidmain(){finalProductproduct=SimpleFactory.createProduct(1);print(product.name);//ConcreteProduct1}

這就是 簡單工廠模式。說到這裡,就不得不提到 Dart 中特有的 factory 關鍵詞了。

factory 關鍵詞 可以用來修飾 Dart 類的構造函數,意為 工廠構造函數,它能夠讓 類 的構造函數天然具有工廠的功能,使用方式如下:

classProduct{///工廠構造函數(修飾create構造函數)factoryProduct.createFactory(inttype){if(type==1){returnProduct.product1;}elseif(type==2){returnProduct._concrete2();}returnProduct._concrete();}///命名構造函數Product._concrete():name='concrete';///命名構造函數1Product._concrete1():name='concrete1';///命名構造函數2Product._concrete2():name='concrete2';Stringname;}

factory 修飾的構造函數需要返回一個當前類的對象實例, 我們可以根據參數調用對應的構造函數,返回對應的對象實例。

voidmain(){Productproduct=Product.createFactory(1);print(product.name);//concrete1}

此外,工廠構造函數也並不要求我們每次都必須生成新的對象, 我們也可以在類中預先定義一些對象供工廠構造函數使用, 這樣每次在使用同樣的參數構建對象時,返回的會是同一個對象, 在 單例模式 的章節中我們已經介紹過:

classProduct{///工廠構造函數factoryProduct.create(inttype){if(type==1){returnproduct1;}elseif(type==2){returnproduct2();}returnProduct._concrete();}staticfinalProductproduct1=Product._concrete1();staticfinalProductproduct2=Product._concrete2();}

factory 除了可以修飾命名構造函數外,也可以修飾默認的非命名構造函數,

classProduct{factoryProduct(inttype){returnProduct._concrete();}String?name;}

到這裡為止,工廠構造函數的一個缺點已經凸顯了出來,即使用者並不能直觀地感覺到自己正在使用的是工廠函數。工廠構造函數的使用方法和普通構造函數沒有區別,但這個構造函數生產的實例相當於是一種單例:

voidmain(){Productproduct=Product(1);print(product.name);//concrete1}

這樣的用法很容易造成使用者的困擾,因此,我們應當儘量使用特定的命名構造函數 作為工廠構造函數(如上面示例中的 createFactory)。

工廠方法模式

工廠方法模式同樣也是我們編程中最常用到的一種手段。

抽象工廠 UML,圖源:refactoring.guru

在簡單工廠中,它主要服務的對象是客戶,而 工廠方法 的使用者與工廠本身的類並不相干, 工廠方法模式主要服務自身的父類,如下的 ProductFactory(類比 UML 中的 Creator):

///抽象工廠abstractclassProductFactory{///抽象工廠方法ProductfactoryMethod();///業務代碼voiddosomthing(){Productproduct=factoryMethod();print(product.name);}}

在 ProductFactory 類中,工廠方法 factoryMethod 是抽象方法, 每個子類都必須重寫這個方法並返回對應不同的 Product 對象, 在 dosomthing() 方法被調用時,就可以根據返回的對象做出不同的響應。具體使用方法如下:

///具體工廠classProductFactory1extendsProductFactory{///具體工廠方法1@overrideProductfactoryMethod(){returnConcreteProduct1();}}classProductFactory2extendsProductFactory{///具體工廠方法2@overrideProductfactoryMethod(){returnConcreteProduct2();}}///使用main(){ProductFactoryproduct=ProductFactory1();product.dosomthing();//ConcreteProduct1}

在 Flutter 中,抽象方法有一個非常實用的應用場景。我們在使用 Flutter 開發多端應用時通常需要考慮到多平台的適配,即在多個平台中,同樣的操作有時會產生不同的結果/樣式,我們可以將這些不同結果/樣式生成的邏輯放在工廠方法中。

如下,我們定義一個 DialogFacory,用作生成不同樣式 Dialog 的工廠:

abstractclassDialogFacory{WidgetcreateDialog(BuildContextcontext);Future<void>show(BuildContextcontext)async{finaldialog=createDialog(context);returnshowDialog<void>(context:context,builder:(_){returndialog;},);}}

然後,針對 Android 和 iOS 兩個平台,就可以創建兩個不同樣式的 Dialog 了:

///Android平台classAndroidAlertDialogextendsDialogFactory{@overrideWidgetcreateDialog(BuildContextcontext){returnAlertDialog(title:Text(getTitle()),content:constText('Thisisthematerial-stylealertdialog!'),actions:<Widget>[TextButton(onPressed:(){Navigator.of(context).pop();},child:constText('Close'),),],);}}///iOS平台classIOSAlertDialogextendsDialogFactory{@overrideWidgetcreateDialog(BuildContextcontext){returnCupertinoAlertDialog(title:Text(getTitle()),content:constText('Thisisthecupertino-stylealertdialog!'),actions:<Widget>[CupertinoButton(onPressed:(){Navigator.of(context).pop();},child:constText('Close'),),],);}}

現在,我們就可以像這樣使用對應的 Dialog 了:

Future_showCustomDialog(BuildContextcontext)async{finaldialog=AndroidAlertDialog();//finaldialog=IOSAlertDialog();awaitselectedDialog.show(context);}抽象工廠

抽象工廠模式,相較於 簡單工廠 和 工廠方法 最大的不同是:這兩種模式只生產一種對象,而抽象工廠生產的是一系列對象 (對象族),而且生成的這一系列對象一定存在某種聯繫。比如 Apple 會生產 手機、平板 等多個產品,這些產品都屬於 Apple 這個品牌。

如下面這個抽象的工廠類:

abstractclassElectronicProductFactory{ProductcreateComputer();ProductcreateMobile();ProductcreatePad();//...}

對於 Apple 來說,我就是生產這類電子產品的工廠,於是可以繼承這個類,實現其中的方法生產各類產品:

classAppleextendsElectronicProductFactory{@overrideProductcreateComputer(){returnMac();}@overrideProductcreateMobile(){returnIPhone();}@overrideProductcreatePad(){returnIPad();}//...}

同樣地,對於華為、小米等電子產品廠商也可以使用相同的方式表示,這就是抽象工廠模式。

在開發 Flutter 應用中,我們也可以充分利用抽象工廠模式做切合應用的適配,我們可以定義如下這個抽象工廠,用於生產 widget:

abstractclassIWidgetsFactory{WidgetcreateButton(BuildContextcontext);WidgetcreateDialog(BuildContextcontext);//...}

我們的應用通常需要針對各個平台展示不同風格的 widget。因此針對每一個平台,我們都可以實現對應的實現工廠,如下:

///Material風格組件工廠classMaterialWidgetsFactoryextendsIWidgetsFactory{@overrideWidgetcreateButton(BuildContextcontext,VoidCallback?onPressed,Stringtext){returnElevatedButton(child:Text(text),onPressed:onPressed,);}@overrideWidgetcreateDialog(BuildContextcontext,Stringtitle,Stringcontent){returnAlertDialog(title:Text(title),content:Text(content));}///...}///Cupertino風格組件工廠classCupertinoWidgetsFactoryextendsIWidgetsFactory{@overrideWidgetcreateButton(BuildContextcontext,VoidCallback?onPressed,Stringtext,){returnCupertinoButton(child:Text(text),onPressed:onPressed,);}@overrideWidgetcreateDialog(BuildContextcontext,Stringtitle,Stringcontent){returnCupertinoAlertDialog(title:Text(title),content:Text(content),);}//...}

這樣,在 Android 平台上我們使用 MaterialWidgetsFactory,在 iOS 平台上使用 CupertinoWidgetsFactory,就能使用對應平台的 widget,想要適配更多平台只需要再繼承 IWidgetsFactory 實現對應平台的工廠類即可。

至此,我們可以發現,作為創建型模式,這三類工廠模式主要工作就是以不同的方式創建對象,但他們各有特點:簡單工廠模式抽象的是 生產對象,工廠方法模式抽象的是 類方法,工廠方法模式抽象的則是 生產對象的工廠,如何使用就見仁見智了。

拓展閱讀
百度百科: 工廠方法模式http://baike.baidu.com/l/JAsmKIAk
百度百科: 抽象工廠模式http://baike.baidu.com/l/j5yzRvW
Medium 文章: Flutter 設計模式: 抽象工廠 (英文,作者 Mangirdas Kazlauskas)https://medium.com/flutter-community/flutter-design-patterns-11-abstract-factory-7098112925d8
關於本系列文章

Flutter / Dart 設計模式從南到北 (簡稱 Flutter 設計模式) 系列內容預計兩周發布一篇,着重向開發者介紹 Flutter 應用開發中常見的設計模式以及開發方式,旨在推進 Flutter / Dart 語言特性的普及,以及幫助開發者更高效地開發出高質量、可維護的 Flutter 應用。

如果您對本文還有任何疑問或者文章的建議,歡迎向中文社區官方 Github 倉庫(github.com/cfug/flutter.cn) 提交 issue 或者直接與我聯繫,我會及時回復。


arrow
arrow
    全站熱搜
    創作者介紹
    創作者 鑽石舞台 的頭像
    鑽石舞台

    鑽石舞台

    鑽石舞台 發表在 痞客邦 留言(0) 人氣()