工作時間一長,需求多而雜,往往難得有時間去仔細思量代碼如何寫的更加優(yōu)雅,習慣使然。設(shè)計模式本身不是一個神秘的東西,也許無意中也會用到,只是沒有刻意去思考。正好找個時間,把設(shè)計模式用python實現(xiàn)一遍,加深印象,為寫出優(yōu)雅的代碼努力。
1 設(shè)計模式簡介
設(shè)計模式的概念最初來自建筑學,記得以前讀過一本《java與模式》,里面用道家思想講設(shè)計模式,頗有新意。不過對于計算機領(lǐng)域的設(shè)計模式,大家一致認可的是GoF提出的設(shè)計模式,那本《設(shè)計模式》一書有點晦澀,不過好在另外有一本《深入淺出設(shè)計模式》,講設(shè)計模式十分的通俗易懂,可以參考。設(shè)計模式分為創(chuàng)建型,結(jié)構(gòu)型,行為型等幾類。這個系列準備分三篇按照設(shè)計模式類型來總結(jié),本篇文章里面提到的設(shè)計模式都屬于創(chuàng)建型模式。
2 工廠模式
工廠模式可以分為簡單工廠模式,工廠方法模式以及抽象工廠模式,這里以制作披薩作為例子來看看這幾種模式的用法。所謂工廠模式其實是體現(xiàn)了設(shè)計模式中的依賴倒置原則,即要依賴抽象,而不是具體實現(xiàn)。
2.1 簡單工廠模式
簡單工廠模式實現(xiàn)很簡單,我們單獨用一個工廠類來創(chuàng)建不同類型的披薩。我們的例子中有三種類型的披薩,分別是cheese,clam,veggie三種口味。簡單工廠的優(yōu)點是實現(xiàn)簡單,但是對變化不大友好,如果要增加口味或者新開分店,就要改動create_pizza函數(shù)代碼。
#!/usr/bin/env python
#coding:utf8
class Pizza(object):
def prepare(self):
print 'prepare pizza'
def bake(self):
print 'bake pizza'
def cut(self):
print 'cut pizza'
def box(self):
print 'box pizza'
class CheesePizza(Pizza):
def __init__(self):
self.name = "cheese pizza"
class ClamPizza(Pizza):
def __init__(self):
self.name = "clam pizza"
class VeggiePizza(Pizza):
def __init__(self):
self.name = "veggie pizza"
class SimplePizzaFactory(object):
def create_pizza(self, type):
pizza = None
if type == "cheese":
pizza = CheesePizza()
elif type == "clam":
pizza = ClamPizza()
elif type == "veggie":
pizza = VeggiePizza()
return pizza
class PizzaStore(object):
def __init__(self, factory):
self.factory = factory
def order_pizza(self, type):
pizza = self.factory.create_pizza(type)
pizza.prepare()
pizza.bake()
pizza.cut()
pizza.box()
return pizza
if __name__ == "__main__":
store = PizzaStore(SimplePizzaFactory())
pizza = store.order_pizza('cheese')
print pizza.name
2.2 工廠方法模式
簡單工廠模式在一個地方就把所有口味的披薩制作完成了,如果此時我們要在北京上海廣州開分店,如果全部代碼寫在簡單工廠里面,如果以后有代碼變動,會難以維護,缺少彈性,而且對象依賴太多,設(shè)計模式告訴我們要依賴抽象,不要依賴具體類,要針對接口編程,而不是具體實現(xiàn)編程。簡單工廠模式里面,PizzaStore是高層的組件,而Pizza是低層組件,PizzaStore過于依賴這些具體的披薩類如CheesePizza,ClamPizza等。工廠方法模式就是針對每一個分店加一個工廠,代碼如下,當然在python里面我們也可以改進下create_pizza函數(shù)代碼,通過globals()[type]()這種方式來創(chuàng)建對象,不需要寫那么多的if-else:
#!/usr/bin/env python
#coding:utf8
class Pizza(object):
def prepare(self):
print 'prepare pizza'
def bake(self):
print 'bake pizza'
def cut(self):
print 'cut pizza'
def box(self):
print 'box pizza'
class GZCheesePizza(Pizza):
def __init__(self):
self.name = "guangzhou cheese pizza"
class GZClamPizza(Pizza):
def __init__(self):
self.name = "guangzhou clam pizza"
class GZVeggiePizza(Pizza):
def __init__(self):
self.name = "guangzhou veggie pizza"
class PizzaStore(object):
def create_pizza(self, item):
raise NotImplementedError
def order_pizza(self, type):
pizza = self.create_pizza(type)
pizza.prepare()
pizza.bake()
pizza.cut()
pizza.box()
return pizza
class GZPizzaStore(PizzaStore):
def create_pizza(self, type):
pizza = None
if type == "cheese":
pizza = GZCheesePizza()
elif type == "clam":
pizza = GZClamPizza()
elif type == "veggie":
pizza = GZVeggiePizza()
return pizza
if __name__ == "__main__":
gz_store = GZPizzaStore()
pizza = gz_store.order_pizza('cheese')
print pizza.name
2.3 抽象工廠模式
抽象工廠模式則是針對同一個產(chǎn)品簇來建立抽象工廠和具體工廠角色。所謂產(chǎn)品簇就是位于不同產(chǎn)品等級結(jié)構(gòu)中,功能相關(guān)聯(lián)的產(chǎn)品組成的家族。比如在我們的例子中,北京上海廣州幾個不同地區(qū)的分店都會有cheese,clam,veggie等口味的披薩,不同地區(qū)雖然風味和口味有所不同,但使用的原料有許多是一樣的,這樣原料就可以使用抽象工廠模式,這樣不同地區(qū)不同口味的披薩可以使用相同的原料工廠。這里代碼就不加示例了。
3 單例模式
單例模式用于生成特定類型的唯一對象,即全局只能有一個實例。在許多場景中,我們只是需要一個唯一的實例,比如一些工具類對象,我們通常只要一個實例即可,這樣可以節(jié)約內(nèi)存。一個實現(xiàn)如下,用一個私有內(nèi)部類作為唯一單例對象:
class OnlyOne(object):
class __OnlyOne(object):
def __init__(self, arg):
self.val = arg
def __str__(self):
print 'call str'
return repr(self) + self.val
instance = None
def __init__(self, arg):
if not OnlyOne.instance:
OnlyOne.instance = OnlyOne.__OnlyOne(arg)
else:
OnlyOne.instance.val = arg
x = OnlyOne('sausage')
y = OnlyOne('eggs')
z = OnlyOne('spam')
assert id(x.instance) == id(y.instance) == id(z.instance)
另一種感覺更pythonic的方法是復寫類的__new__方法,如下:
#!/usr/bin/env python
#coding:utf8
class Singleton(object):
def __new__(cls, *args, **kw):
if not hasattr(cls, '_instance'):
cls._instance = super(Singleton, cls).__new__(cls, *args, **kw)
return cls._instance
class SingletonClass(Singleton):
pass
if __name__ == "__main__":
instance1 = SingletonClass()
instance2 = SingletonClass()
assert id(instance1) == id(instance2)
當然還有種稱之為borg模式,據(jù)說比單例模式實現(xiàn)單例更好。
class Borg(object):
_state = {}
def __new__(cls, *args, **kw):
obj = super(Borg, cls).__new__(cls, *args, **kw)
obj.__dict__ = cls._state
return obj
class SingleInstance(Borg):
pass
instance1 = SingleInstance()
instance2 = SingleInstance()
assert instance1.__dict__ is instance2.__dict__ #實例不同,但是字典相同
assert id(instance1) != id(instance2)
4 建造模式
建造模式用于將構(gòu)建復雜對象的過程和組成對象的組件解耦。一般有四個角色:指導者,抽象建造者,具體建造者,產(chǎn)品。首先需要創(chuàng)建一個具體建造者,然后將建造者傳入指導者對象進行配置,然后指導者對象調(diào)用建造者的方法創(chuàng)建產(chǎn)品對象,最后客戶端從指導者那里獲取產(chǎn)品。與工廠模式不同的是,建造模式創(chuàng)建產(chǎn)品是在建造者對象中完成,而不像工廠模式產(chǎn)品創(chuàng)建是在產(chǎn)品類中完成的,一個示例如下,代碼取自參考資料3:
#!/usr/bin/python
# -*- coding : utf-8 -*-
"""
@author: Diogenes Augusto Fernandes Herminio <diofeher@gmail.com>
https://gist.github.com/420905#file_builder_python.py
"""
# Director
class Director(object):
def __init__(self):
self.builder = None
def construct_building(self):
self.builder.new_building()
self.builder.build_floor()
self.builder.build_size()
def get_building(self):
return self.builder.building
# Abstract Builder
class Builder(object):
def __init__(self):
self.building = None
def new_building(self):
self.building = Building()
# Concrete Builder
class BuilderHouse(Builder):
def build_floor(self):
self.building.floor = 'One'
def build_size(self):
self.building.size = 'Big'
class BuilderFlat(Builder):
def build_floor(self):
self.building.floor = 'More than One'
def build_size(self):
self.building.size = 'Small'
# Product
class Building(object):
def __init__(self):
self.floor = None
self.size = None
def __repr__(self):
return 'Floor: {0.floor} | Size: {0.size}'.format(self)
# Client
if __name__ == "__main__":
director = Director()
director.builder = BuilderHouse()
director.construct_building()
building = director.get_building()
print(building)
director.builder = BuilderFlat()
director.construct_building()
building = director.get_building()
print(building)
### OUTPUT ###
# Floor: One | Size: Big
# Floor: More than One | Size: Small
5 原型模式
原型模式即是通過拷貝原型對象來創(chuàng)建新的對象,python中提供了copy模塊來實現(xiàn)原型模式。原型模式里面通常會有一個原型管理器,用于注冊管理原型對象。注意原型模式中的copy與創(chuàng)建一個對象是不一樣的,創(chuàng)建對象只是得到一個初始化的對象,而拷貝對象則可以保持對象的原有狀態(tài)并在原來對象基礎(chǔ)上進行操作。比如游戲里面創(chuàng)建角色,可以用到原型模式,對同樣的屬性不用改變,不同的屬性可以拷貝對象后再重新設(shè)置。示例代碼如下,取自參考資料3:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import copy
class Prototype(object):
def __init__(self):
self._objects = {}
def register_object(self, name, obj):
"""Register an object"""
self._objects[name] = obj
def unregister_object(self, name):
"""Unregister an object"""
del self._objects[name]
def clone(self, name, **attr):
"""Clone a registered object and update inner attributes dictionary"""
obj = copy.deepcopy(self._objects.get(name))
obj.__dict__.update(attr)
return obj
class A(object):
def __init__(self):
self.x = 3
self.y = 8
self.z = 15
self.garbage = [38, 11, 19]
def __str__(self):
return '{} {} {} {}'.format(self.x, self.y, self.z, self.garbage)
def main():
a = A()
prototype = Prototype()
prototype.register_object('objecta', a)
b = prototype.clone('objecta')
c = prototype.clone('objecta', x=1, y=2, garbage=[88, 1])
print([str(i) for i in (a, b, c)])
if __name__ == '__main__':
main()
### OUTPUT ###
# ['3 8 15 [38, 11, 19]', '3 8 15 [38, 11, 19]', '1 2 15 [88, 1]']
6 對象池模式
開發(fā)中總是少不了用到各種池,比如線程池,連接池等。對象池就是將對象用完后先不銷毀,而是存起來,后面繼續(xù)用,節(jié)省重新創(chuàng)建對象的開銷。示例代碼來自參考資料3:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
class QueueObject():
def __init__(self, queue, auto_get=False):
self._queue = queue
self.object = self._queue.get() if auto_get else None
def __enter__(self):
if self.object is None:
self.object = self._queue.get()
return self.object
def __exit__(self, Type, value, traceback):
if self.object is not None:
self._queue.put(self.object)
self.object = None
def __del__(self):
if self.object is not None:
self._queue.put(self.object)
self.object = None
def main():
try:
import queue
except ImportError: # python 2.x compatibility
import Queue as queue
def test_object(queue):
queue_object = QueueObject(queue, True)
print('Inside func: {}'.format(queue_object.object))
sample_queue = queue.Queue()
sample_queue.put('yam')
with QueueObject(sample_queue) as obj:
print('Inside with: {}'.format(obj))
print('Outside with: {}'.format(sample_queue.get()))
sample_queue.put('sam')
test_object(sample_queue)
print('Outside func: {}'.format(sample_queue.get()))
if not sample_queue.empty():
print(sample_queue.get())
if __name__ == '__main__':
main()
### OUTPUT ###
# Inside with: yam
# Outside with: yam
# Inside func: sam
# Outside func: sam
7 參考資料
- 《深入淺出設(shè)計模式》
- 小明明的設(shè)計模式教程
- Python patterns
- 寫給python初學者的設(shè)計模式入門