抽象工廠
抽象工廠設(shè)計(jì)模式是抽象方法的一種泛化。概括來(lái)說(shuō),一個(gè)抽象工廠是(邏輯上的)一組工廠方法,其中的每個(gè)工廠方法負(fù)責(zé)產(chǎn)生不同種類的對(duì)象。
真實(shí)的例子
抽象工廠用在車輛織造中。相同的機(jī)械裝置用來(lái)沖壓不同的車輛模型的部件(門,面板,車身罩體,擋泥板,以及各種鏡子)。聚合了不同機(jī)械裝置的模型是可配置的,而且在任何時(shí)候都可以輕松變更。在這個(gè)鏈接中我們可以看到一個(gè)車輛制造的抽象工廠的例子。
如下所示的示例可以作為抽象工廠實(shí)現(xiàn)的參考:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# http://ginstrom.com/scribbles/2007/10/08/design-patterns-python-style/
"""Implementation of the abstract factory pattern"""
import random
class PetShop:
"""A pet shop"""
def __init__(self, animal_factory=None):
"""pet_factory is our abstract factory. We can set it at will."""
self.pet_factory = animal_factory
def show_pet(self):
"""Creates and shows a pet using the abstract factory"""
pet = self.pet_factory.get_pet()
print("We have a lovely {}".format(pet))
print("It says {}".format(pet.speak()))
print("We also have {}".format(self.pet_factory.get_food()))
# Stuff that our factory makes
class Dog:
def speak(self):
return "woof"
def __str__(self):
return "Dog"
class Cat:
def speak(self):
return "meow"
def __str__(self):
return "Cat"
# Factory classes
class DogFactory:
def get_pet(self):
return Dog()
def get_food(self):
return "dog food"
class CatFactory:
def get_pet(self):
return Cat()
def get_food(self):
return "cat food"
# Create the proper family
def get_factory():
"""Let's be dynamic!"""
return random.choice([DogFactory, CatFactory])()
# Show pets with various factories
for i in range(3):
shop = PetShop(get_factory())
shop.show_pet()
print("=" * 20)
### OUTPUT ###
# We have a lovely Dog
# It says woof
# We also have dog food
# ====================
# We have a lovely Dog
# It says woof
# We also have dog food
# ====================
# We have a lovely Cat
# It says meow
# We also have cat food
# ====================
軟件例子
程序包django_factory是一個(gè)用于在測(cè)試中創(chuàng)建Django模型的抽象工廠實(shí)現(xiàn),可用來(lái)為支持測(cè)試專有屬性的模型創(chuàng)建實(shí)例。這能讓測(cè)試代碼的可讀性更高,且能避免共享不必要的代碼,故有其存在的價(jià)值(請(qǐng)參考網(wǎng)頁(yè)[t.cn/RqBBvcw])。
使用案例
因?yàn)槌橄蠊つJ绞枪S方法模式的歸納,它具有同樣的優(yōu)點(diǎn):使得追蹤一個(gè)對(duì)象創(chuàng)建更容易,它讓對(duì)象的使用和創(chuàng)建分離開(kāi),并且給我們內(nèi)存使用以及應(yīng)用的性能提高的可能。
這樣會(huì)產(chǎn)生一個(gè)問(wèn)題:我們?cè)趺粗篮螘r(shí)該使用工廠方法,何時(shí)又該使用抽象工廠?答案是,通常一開(kāi)始時(shí)使用工廠方法,因?yàn)樗?jiǎn)單。如果后來(lái)發(fā)現(xiàn)應(yīng)用需要許多工廠方法,那么將創(chuàng)建一系列對(duì)象的過(guò)程合并在一起更合理,從而最終引入抽象工廠。
抽象工廠有一個(gè)優(yōu)點(diǎn),在使用工廠方法時(shí)從用戶視角通常是看不到的,那就是抽象工廠能夠通過(guò)改變激活的工廠方法動(dòng)態(tài)地(運(yùn)行時(shí))改變應(yīng)用行為。一個(gè)經(jīng)典例子是能夠讓用戶在使用應(yīng)用時(shí)改變應(yīng)用的觀感(比如,Apple風(fēng)格和Windows風(fēng)格等),而不需要終止應(yīng)用然后重新啟動(dòng)。
實(shí)現(xiàn)
為演示抽象工廠模式,我將重新使用Python 3 Patterns & Idioms(Bruce Eckel著)一書(shū)中的一個(gè)例子,它是我個(gè)人最喜歡的例子之一。想象一下,我們正在創(chuàng)造一個(gè)游戲,或者想在應(yīng)用中包含一個(gè)迷你游戲讓用戶娛樂(lè)娛樂(lè)。我們希望至少包含兩個(gè)游戲, 一個(gè)面向孩子,一個(gè)面向成人。在運(yùn)行時(shí),基于用戶輸入,決定該創(chuàng)建哪個(gè)游戲并運(yùn)行。游戲的創(chuàng)建部分由一個(gè)抽象工廠維護(hù)。
從孩子的游戲說(shuō)起,我們將該游戲命名為 FrogWorld。主人公是一只青蛙,喜歡吃蟲(chóng)子。每個(gè)英雄都需要一個(gè)好名字,在我們的例子中,這個(gè)名字在運(yùn)行時(shí)由用戶給定。方法 interact_with()用于描述青蛙與障礙物(比如,蟲(chóng)子、迷宮或其他青蛙)之間的交互,如下所示:
Class Frog:
def __init__(self, name):
self.name = name
def __str__(self):
return self.name
def interact_with(self, obstacle):
print('{} the Frog encounters {} and {}!'.format(self, obstacle, obstacle.action()))
可以有很多種類的障礙,不過(guò)對(duì)于我們的這個(gè)例子來(lái)說(shuō)可以只有一個(gè)Bug。青蛙遇到一個(gè)蟲(chóng)子,只有一個(gè)行為被支持:吃掉它!
Class Bug:
def __str__(self):
return 'a bug'
def action(self):
return 'eats it'
FrogWorld類是一個(gè)抽象工廠。它的主要責(zé)任是游戲的主角和障礙物。保持創(chuàng)建方法的獨(dú)立和方法名稱的普通(例如,make_character(),make_obstacle())讓我們可以動(dòng)態(tài)的改變活動(dòng)工廠(以及活動(dòng)的游戲)而不設(shè)計(jì)任何的代碼變更。如下所示,在一個(gè)靜態(tài)類型語(yǔ)言中,抽象工廠可以是一個(gè)有著空方法的抽象類/接口,但是在Python中不要求這么做,因?yàn)轭愋驮谶\(yùn)行時(shí)才被檢查。
Class FrogWorld:
def __init__(self, name):
print(self)
self.player_name = name
def __str__(self):
return '\n\n\t------ Frog World ------'
def make_character(self):
return Forg(self.player_name)
def make_obstacle(self):
return Bug()
游戲WizarWorld也類似。唯一的不同是巫師不吃蟲(chóng)子而是與半獸人這樣的怪物戰(zhàn)斗!
Class Wizard:
def __init__(self, name):
self.name = name
def __str__(self):
return self.name
def interact_with(self, obstacle):
print('{} the Wizard batteles against {} and {}!'.format(
self, obstacle, obstacle.action()))
Class Ork:
def __str__(self):
return 'an evial ork'
def action(self):
return 'kills it'
Class WizardWorld:
def __init__(self, name):
print(self)
self.player_name = name
def __str__(self):
return '\n\n\t------ Wizard World ------'
def make_character(self):
return Wizard(self.player_name)
def make_obstacle(self):
return Ork()
GameEnviroment是我們游戲的主要入口。它接受factory作為輸入,然后用它創(chuàng)建游戲的世界。play()方法發(fā)起創(chuàng)造的hero和obstacle之間的交互如下:
Class GameEnviroment:
def __init__(self, factory):
self.hero = factory.make_character()
self.obstacle = factory.make_obstacle()
def play(self):
self.hero.interact_with(self.obstacle)
validate_age()函數(shù)提示用戶輸入一個(gè)有效的年齡。若年齡無(wú)效,則返回一個(gè)第一個(gè)元素設(shè)置為False的元組。如下所示,若輸入的年齡沒(méi)問(wèn)題,元組的第一個(gè)元素設(shè)置為True,以及我們所關(guān)心的該元組的第二個(gè)元素,即用戶給定的年齡:
def validate_age(name):
try:
age = input('Welcome {}. How old are you?'.format(name))
age = int(age)
except ValueError as err:
print("Age {} is valid, plz try again...".format(age))
return (False, age)
return (True, age)
最后,尤其是main()函數(shù)的出現(xiàn)。它詢問(wèn)了用戶的名字和年齡,然后由用戶的年齡來(lái)決定哪一個(gè)游戲應(yīng)該被運(yùn)行:
def main():
name = input("Hello. What's your name?")
valid_input = False
while not valid_input:
valid_input, age = validate_age(name)
game = FrogWorld if age < 18 else WizardWorld
enviroment = GameEnviroment(game(name))
enviroment.play()
抽象工廠實(shí)現(xiàn)(abstrac_factory.py)的完整代碼如下:
class Frog:
def __init__(self, name):
self.name = name
def __str__(self):
return self.name
def interact_with(self, obstacle):
print('{} the Frog encounters {} and {}!'.format(self,
obstacle, obstacle.action()))
class Bug:
def __str__(self):
return 'a bug'
def action(self):
return 'eats it'
class FrogWorld:
def __init__(self, name):
print(self)
self.player_name = name
def __str__(self):
return '\n\n\t------ Frog World -------'
def make_character(self):
return Frog(self.player_name)
def make_obstacle(self):
return Bug()
class Wizard:
def __init__(self, name):
self.name = name
def __str__(self):
return self.name
def interact_with(self, obstacle):
print(
'{} the Wizard battles against {} and {}!'.format(
self,
obstacle,
obstacle.action()))
class Ork:
def __str__(self):
return 'an evil ork'
def action(self):
return 'kills it'
class WizardWorld:
def __init__(self, name):
print(self)
self.player_name = name
def __str__(self):
return '\n\n\t------ Wizard World -------'
def make_character(self):
return Wizard(self.player_name)
def make_obstacle(self):
return Ork()
class GameEnvironment:
def __init__(self, factory):
self.hero = factory.make_character()
self.obstacle = factory.make_obstacle()
def play(self):
self.hero.interact_with(self.obstacle)
def validate_age(name):
try:
age = input('Welcome {}. How old are you? '.format(name))
age = int(age)
except ValueError as err:
print("Age {} is invalid, please try again...".format(age))
return (False, age)
return (True, age)
def main():
name = input("Hello. What's your name? ")
valid_input = False
while not valid_input:
valid_input, age = validate_age(name)
game = FrogWorld if age < 18 else WizardWorld
environment = GameEnvironment(game(name))
environment.play()
main()
# Hello. What's your name? Nick
# Welcome Nick. How old are you? 17
# ------ Frog World -------
# Nick the Frog encounters a bug and eats it!
試著擴(kuò)展游戲使它更加復(fù)雜。你的思想有多遠(yuǎn)就可以走多遠(yuǎn):更多的障礙物,更多的敵人,以及任何你喜歡的東西。
Abstract Factory versus Factory Method
Let's look at the instances where we need to use the Abstract Factory or Factory Method.
Use the Factory Method pattern when there is a need to decouple a client from a particular product it uses. Use the Factory Method to relieve a client of the responsibility of creating and configuring instances of a product.
Use the Abstract Factory pattern when clients must be decoupled from the product classes. The Abstract Factory pattern can also enforce constraints specifying which classes must be used with others, creating independent families of objects.”
總結(jié)
本章中,我們學(xué)習(xí)了如何使用工廠方法和抽象工廠設(shè)計(jì)模式。兩種模式都可以用于以下幾種場(chǎng)景:(a)想要追蹤對(duì)象的創(chuàng)建時(shí),(b)想要將對(duì)象的創(chuàng)建與使用解耦時(shí),(c)想要優(yōu)化應(yīng)用的性能和資源占用時(shí)。場(chǎng)景(c)在本章中并未詳細(xì)說(shuō)明,你也許可以將其作為一個(gè)練習(xí)。
工廠方法設(shè)計(jì)模式的實(shí)現(xiàn)是一個(gè)不屬于任何類的單一函數(shù),負(fù)責(zé)單一種類對(duì)象(一個(gè)形狀、一個(gè)連接點(diǎn)或者其他對(duì)象)的創(chuàng)建。我們看到工廠方法是如何與玩具制造相關(guān)聯(lián)的,提到Django是如何將其用于創(chuàng)建不同表單字段的,并討論了其他可能的應(yīng)用案例。作為示例,我們實(shí)現(xiàn)了一個(gè)工廠方法,提供了訪問(wèn)XML和JSON文件的能力。
抽象工廠設(shè)計(jì)模式的實(shí)現(xiàn)是同屬于單個(gè)類的許多個(gè)工廠方法用于創(chuàng)建一系列種類的相關(guān)對(duì)象(一輛車的部件、一個(gè)游戲的環(huán)境,或者其他對(duì)象)。我們提到抽象工廠如何與汽車制造業(yè)相關(guān)聯(lián),Django程序包django_factory是如何利用抽象工廠創(chuàng)建干凈的測(cè)試用例,并學(xué)習(xí)了抽象工廠的應(yīng)用案例。作為抽象工廠實(shí)現(xiàn)的示例,我們完成了一個(gè)迷你游戲,演示了如何在單個(gè)類中使用多個(gè)相關(guān)工廠。