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

工作时间一长,需求多而杂,往往难得有时间去仔细思量代码如何写的更加优雅,习惯使然。设计模式本身不是一个神秘的东西,也许无意中也会用到,只是没有刻意去思考。正好找个时间,把设计模式用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 参考资料

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容

  • 设计模式汇总 一、基础知识 1. 设计模式概述 定义:设计模式(Design Pattern)是一套被反复使用、多...
    MinoyJet阅读 3,894评论 1 15
  • 前段时间,在自己糊里糊涂地写了一年多的代码之后,接手了一坨一个同事的代码。身边很多人包括我自己都在痛骂那些乱糟糟毫...
    丑小丫大笨蛋阅读 623评论 0 2
  • 创建型模式 抽象工厂模式(abstract facroty) 3.1模式动机 在工厂方法模式中具体工厂负责生产具体...
    僚机KK阅读 722评论 0 2
  • 设计模式基本原则 开放-封闭原则(OCP),是说软件实体(类、模块、函数等等)应该可以拓展,但是不可修改。开-闭原...
    西山薄凉阅读 3,748评论 3 13
  • 今天我爸给我发微信语音,问你们那儿冷吗,好像北方下雪了还下可大了吧,你要多穿一点啊。我说挺冷的,现在北方就是下雪下...
    冰花儿啊阅读 3,456评论 46 77