Python设计模式1-创建型模式

767 查看

工作时间一长,需求多而杂,往往难得有时间去仔细思量代码如何写的更加优雅,习惯使然。设计模式本身不是一个神秘的东西,也许无意中也会用到,只是没有刻意去思考。正好找个时间,把设计模式用python实现一遍,加深印象,为写出优雅的代码努力。

1 设计模式简介

设计模式的概念最初来自建筑学,记得以前读过一本《java与模式》,里面用道家思想讲设计模式,颇有新意。不过对于计算机领域的设计模式,大家一致认可的是GoF提出的设计模式,那本《设计模式》一书有点晦涩,不过好在另外有一本《深入浅出设计模式》,讲设计模式十分的通俗易懂,可以参考。设计模式分为创建型,结构型,行为型等几类。这个系列准备分三篇按照设计模式类型来总结,本篇文章里面提到的设计模式都属于创建型模式。

2 工厂模式

工厂模式可以分为简单工厂模式,工厂方法模式以及抽象工厂模式,这里以制作披萨作为例子来看看这几种模式的用法。所谓工厂模式其实是体现了设计模式中的依赖倒置原则,即要依赖抽象,而不是具体实现。

2.1 简单工厂模式

简单工厂模式实现很简单,我们单独用一个工厂类来创建不同类型的披萨。我们的例子中有三种类型的披萨,分别是cheese,clam,veggie三种口味。简单工厂的优点是实现简单,但是对变化不大友好,如果要增加口味或者新开分店,就要改动create_pizza函数代码。

#!/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 工厂方法模式

简单工厂模式在一个地方就把所有口味的披萨制作完成了,如果此时我们要在北京上海广州开分店,如果全部代码写在简单工厂里面,如果以后有代码变动,会难以维护,缺少弹性,而且对象依赖太多,设计模式告诉我们要依赖抽象,不要依赖具体类,要针对接口编程,而不是具体实现编程。简单工厂模式里面,PizzaStore是高层的组件,而Pizza是低层组件,PizzaStore过于依赖这些具体的披萨类如CheesePizza,ClamPizza等。工厂方法模式就是针对每一个分店加一个工厂,代码如下,当然在python里面我们也可以改进下create_pizza函数代码,通过globals()[type]()这种方式来创建对象,不需要写那么多的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 抽象工厂模式

抽象工厂模式则是针对同一个产品簇来建立抽象工厂和具体工厂角色。所谓产品簇就是位于不同产品等级结构中,功能相关联的产品组成的家族。比如在我们的例子中,北京上海广州几个不同地区的分店都会有cheese,clam,veggie等口味的披萨,不同地区虽然风味和口味有所不同,但使用的原料有许多是一样的,这样原料就可以使用抽象工厂模式,这样不同地区不同口味的披萨可以使用相同的原料工厂。这里代码就不加示例了。

3 单例模式

单例模式用于生成特定类型的唯一对象,即全局只能有一个实例。在许多场景中,我们只是需要一个唯一的实例,比如一些工具类对象,我们通常只要一个实例即可,这样可以节约内存。一个实现如下,用一个私有内部类作为唯一单例对象:

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模式,据说比单例模式实现单例更好。

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 建造模式

建造模式用于将构建复杂对象的过程和组成对象的组件解耦。一般有四个角色:指导者,抽象建造者,具体建造者,产品。首先需要创建一个具体建造者,然后将建造者传入指导者对象进行配置,然后指导者对象调用建造者的方法创建产品对象,最后客户端从指导者那里获取产品。与工厂模式不同的是,建造模式创建产品是在建造者对象中完成,而不像工厂模式产品创建是在产品类中完成的,一个示例如下,代码取自参考资料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 原型模式

原型模式即是通过拷贝原型对象来创建新的对象,python中提供了copy模块来实现原型模式。原型模式里面通常会有一个原型管理器,用于注册管理原型对象。注意原型模式中的copy与创建一个对象是不一样的,创建对象只是得到一个初始化的对象,而拷贝对象则可以保持对象的原有状态并在原来对象基础上进行操作。比如游戏里面创建角色,可以用到原型模式,对同样的属性不用改变,不同的属性可以拷贝对象后再重新设置。示例代码如下,取自参考资料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 对象池模式

开发中总是少不了用到各种池,比如线程池,连接池等。对象池就是将对象用完后先不销毁,而是存起来,后面继续用,节省重新创建对象的开销。示例代码来自参考资料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 参考资料