在圍繞設計模式的話題中,工廠這個詞頻繁出現,從 簡單工廠 模式到 工廠方法 模式,再到 抽象工廠 模式。工廠名稱含義是制造產品的工業場所,應用在面向對象中,順理成章地成為了比較典型的創建型模式。
圖源:https://media2.giphy.com/media/3ohjUKYWSqORcgIIsE/giphy.gif
“從形式上講,工廠可以是一個返回我們想要對象的一個方法 / 函數,即可以作為構造函數的一種抽象。
”
本文將會帶領大家使用 Dart 理解它們的各自的實現和它們之間的關系。
簡單工廠 & factory 關鍵字
簡單工廠模式 不在 23 種 GOF 設計模式中,卻是我們最常使用的一種編程方式。其中主要涉及到一個特殊的方法,專門用來提供我們想要的實例對象 (對象工廠),我們可以將這個方法放到一個單獨的類 SimpleFactory
中,如下:
class SimpleFactory {
/// 工廠方法
static Product createProduct(int type) {
if (type == 1) {
return ConcreteProduct1();
}
if (type == 2) {
return ConcreteProduct2();
}
return ConcreteProduct();
}
}
我們認為該方法要創建的對象同屬一個 Product 類 (抽象類),并通過參數 type 指定要創建具體的對象類型。Dart 不支持 interface
關鍵詞,但我們可以使用 abstract
以抽象類的方式定義接口, 然后各個具體的類型繼承實現它即可:
/// 抽象類
abstract class Product {
String? name;
}
/// 實現類
class ConcreteProduct implements Product {
@override
String? name = 'ConcreteProduct';
}
/// 實現類 1
class ConcreteProduct1 implements Product {
@override
String? name = 'ConcreteProduct1';
}
/// 實現類 2
class ConcreteProduct2 implements Product {
@override
String? name = 'ConcreteProduct2';
}
當我們想要在代碼中獲取對應的類型對象時,只需要通過這個方法傳入想要的類型值即可, 我們不必關心生產如何被生產以及哪個對象被選擇的具體邏輯:
void main() {
final Product product = SimpleFactory.createProduct(1);
print(product.name); // ConcreteProduct1
}
這就是 簡單工廠模式。說到這里,就不得不提到 Dart 中特有的 factory 關鍵詞了。
factory 關鍵詞 可以用來修飾 Dart 類的構造函數,意為 工廠構造函數,它能夠讓 類 的構造函數天然具有工廠的功能,使用方式如下:
class Product {
/// 工廠構造函數(修飾 create 構造函數)
factory Product.createFactory(int type) {
if (type == 1) {
return Product.product1;
} else if (type == 2) {
return Product._concrete2();
}
return Product._concrete();
}
/// 命名構造函數
Product._concrete() : name = 'concrete';
/// 命名構造函數 1
Product._concrete1() : name = 'concrete1';
/// 命名構造函數 2
Product._concrete2() : name = 'concrete2';
String name;
}
factory 修飾的構造函數需要返回一個當前類的對象實例, 我們可以根據參數調用對應的構造函數,返回對應的對象實例。
void main() {
Product product = Product.createFactory(1);
print(product.name); // concrete1
}
此外,工廠構造函數也并不要求我們每次都必須生成新的對象, 我們也可以在類中預先定義一些對象供工廠構造函數使用, 這樣每次在使用同樣的參數構建對象時,返回的會是同一個對象, 在 [單例模式] 的章節中我們已經介紹過:
class Product {
/// 工廠構造函數
factory Product.create(int type) {
if (type == 1) {
return product1;
} else if (type == 2) {
return product2();
}
return Product._concrete();
}
static final Product product1 = Product._concrete1();
static final Product product2 = Product._concrete2();
}
factory 除了可以修飾命名構造函數外,也可以修飾默認的非命名構造函數,
class Product {
factory Product(int type) {
return Product._concrete();
}
String? name;
}
到這里為止,工廠構造函數的一個缺點已經凸顯了出來,即使用者并不能直觀地感覺到自己正在使用的是工廠函數。工廠構造函數的使用方法和普通構造函數沒有區別,但這個構造函數生產的實例相當于是一種單例:
void main() {
Product product = Product(1);
print(product.name); // concrete1
}
這樣的用法很容易造成使用者的困擾,因此,我們應當盡量使用特定的命名構造函數 作為工廠構造函數(如上面示例中的 createFactory
)。
工廠方法模式
工廠方法模式同樣也是我們編程中最常用到的一種手段。
抽象工廠 UML,圖源:refactoring.guru
在簡單工廠中,它主要服務的對象是客戶,而 工廠方法 的使用者與工廠本身的類并不相干, 工廠方法模式主要服務自身的父類,如下的 ProductFactory
(類比 UML 中的 Creator):
/// 抽象工廠
abstract class ProductFactory {
/// 抽象工廠方法
Product factoryMethod();
/// 業務代碼
void dosomthing() {
Product product = factoryMethod();
print(product.name);
}
}
在 ProductFactory
類中,工廠方法 factoryMethod
是抽象方法, 每個子類都必須重寫這個方法并返回對應不同的 Product
對象, 在 dosomthing()
方法被調用時,就可以根據返回的對象做出不同的響應。具體使用方法如下:
/// 具體工廠
class ProductFactory1 extends ProductFactory {
/// 具體工廠方法1
@override
Product factoryMethod() {
return ConcreteProduct1();
}
}
class ProductFactory2 extends ProductFactory {
/// 具體工廠方法2
@override
Product factoryMethod() {
return ConcreteProduct2();
}
}
/// 使用
main() {
ProductFactory product = ProductFactory1();
product.dosomthing(); // ConcreteProduct1
}
在 Flutter 中,抽象方法有一個非常實用的應用場景。我們在使用 Flutter 開發多端應用時通常需要考慮到多平臺的適配,即在多個平臺中,同樣的操作有時會產生不同的結果/樣式,我們可以將這些不同結果/樣式生成的邏輯放在工廠方法中。
如下,我們定義一個 DialogFacory
,用作生成不同樣式 Dialog
的工廠:
abstract class DialogFacory {
Widget createDialog(BuildContext context);
Future<void> show(BuildContext context) async {
final dialog = createDialog(context);
return showDialog<void>(
context: context,
builder: (_) {
return dialog;
},
);
}
}
然后,針對 Android 和 iOS 兩個平臺,就可以創建兩個不同樣式的 Dialog
了:
/// Android 平臺
class AndroidAlertDialog extends DialogFactory {
@override
Widget createDialog(BuildContext context) {
return AlertDialog(
title: Text(getTitle()),
content: const Text('This is the material-style alert dialog!'),
actions: <Widget>[
TextButton(
onPressed: () {
Navigator.of(context).pop();
},
child: const Text('Close'),
),
],
);
}
}
/// iOS 平臺
class IOSAlertDialog extends DialogFactory {
@override
Widget createDialog(BuildContext context) {
return CupertinoAlertDialog(
title: Text(getTitle()),
content: const Text('This is the cupertino-style alert dialog!'),
actions: <Widget>[
CupertinoButton(
onPressed: () {
Navigator.of(context).pop();
},
child: const Text('Close'),
),
],
);
}
}
現在,我們就可以像這樣使用對應的 Dialog
了:
Future _showCustomDialog(BuildContext context) async {
final dialog = AndroidAlertDialog();
// final dialog = IOSAlertDialog();
await selectedDialog.show(context);
}
抽象工廠
抽象工廠模式,相較于 簡單工廠 和 工廠方法 最大的不同是:這兩種模式只生產一種對象,而抽象工廠生產的是一系列對象 (對象族),而且生成的這一系列對象一定存在某種聯系。比如 Apple 會生產 手機、平板 等多個產品,這些產品都屬于 Apple 這個品牌。
如下面這個抽象的工廠類:
abstract class ElectronicProductFactory {
Product createComputer();
Product createMobile();
Product createPad();
// ...
}
對于 Apple 來說,我就是生產這類電子產品的工廠,于是可以繼承這個類,實現其中的方法生產各類產品:
class Apple extends ElectronicProductFactory {
@override
Product createComputer() {
return Mac();
}
@override
Product createMobile() {
return IPhone();
}
@override
Product createPad() {
return IPad();
}
// ...
}
同樣地,對于華為、小米等電子產品廠商也可以使用相同的方式表示,這就是抽象工廠模式。
在開發 Flutter 應用中,我們也可以充分利用抽象工廠模式做切合應用的適配,我們可以定義如下這個抽象工廠,用于生產 widget:
abstract class IWidgetsFactory {
Widget createButton(BuildContext context);
Widget createDialog(BuildContext context);
// ...
}
我們的應用通常需要針對各個平臺展示不同風格的 widget。因此針對每一個平臺,我們都可以實現對應的實現工廠,如下:
/// Material 風格組件工廠
class MaterialWidgetsFactory extends IWidgetsFactory {
@override
Widget createButton(
BuildContext context, VoidCallback? onPressed, String text) {
return ElevatedButton(
child: Text(text),
onPressed: onPressed,
);
}
@override
Widget createDialog(BuildContext context, String title, String content) {
return AlertDialog(title: Text(title), content: Text(content));
}
/// ...
}
/// Cupertino 風格組件工廠
class CupertinoWidgetsFactory extends IWidgetsFactory {
@override
Widget createButton(
BuildContext context,
VoidCallback? onPressed,
String text,
) {
return CupertinoButton(
child: Text(text),
onPressed: onPressed,
);
}
@override
Widget createDialog(BuildContext context, String title, String content) {
return CupertinoAlertDialog(
title: Text(title),
content: Text(content),
);
}
// ...
}
這樣,在 Android 平臺上我們使用 MaterialWidgetsFactory
,在 iOS 平臺上使用 CupertinoWidgetsFactory
,就能使用對應平臺的 widget,想要適配更多平臺只需要再繼承 IWidgetsFactory
實現對應平臺的工廠類即可。
至此,我們可以發現,作為創建型模式,這三類工廠模式主要工作就是以不同的方式創建對象,但他們各有特點:簡單工廠模式抽象的是 生產對象,工廠方法模式抽象的是 類方法,工廠方法模式抽象的則是 生產對象的工廠,如何使用就見仁見智了。