写给Python初学者的设计模式入门

 有没有想过设计模式到底是什么?通过本文可以看到设计模式为什么这么重要,通过几个Python的示例展示为什么需要设计模式,以及如何使用。

 设计模式是什么?

  设计模式是经过总结、优化的,对我们经常会碰到的一些编程问题的可重用解决方案。一个设计模式并不像一个类或一个库那样能够直接作用于我们的代码。反之,设计模式更为高级,它是一种必须在特定情形下实现的一种方法模板。设计模式不会绑定具体的编程语言。一个好的设计模式应该能够用大部分编程语言实现(如果做不到全部的话,具体取决于语言特性)。最为重要的是,设计模式也是一把双刃剑,如果设计模式被用在不恰当的情形下将会造成灾难,进而带来无穷的麻烦。然而如果设计模式在正确的时间被用在正确地地方,它将是你的救星。

  起初,你会认为“模式”就是为了解决一类特定问题而特别想出来的明智之举。说的没错,看起来的确是通过很多人一起工作,从不同的角度看待问题进而形成的一个最通用、最灵活的解决方案。也许这些问题你曾经见过或是曾经解决过,但是你的解决方案很可能没有模式这么完备。

  虽然被称为“设计模式”,但是它们同“设计“领域并非紧密联系。设计模式同传统意义上的分析、设计与实现不同,事实上设计模式将一个完整的理念根植于程序中,所以它可能出现在分析阶段或是更高层的设计阶段。很有趣的是因为设计模式的具体体现是程序代码,因此可能会让你认为它不会在具体实现阶段之前出现(事实上在进入具体实现阶段之前你都没有意识到正在使用具体的设计模式)。

  可以通过程序设计的基本概念来理解模式:增加一个抽象层。抽象一个事物就是隔离任何具体细节,这么做的目的是为了将那些不变的核心部分从其他细节中分离出来。当你发现你程序中的某些部分经常因为某些原因改动,而你不想让这些改动的部分引发其他部分的改动,这时候你就需要思考那些不会变动的设计方法了。这么做不仅会使代码可维护性更高,而且会让代码更易于理解,从而降低开发成本。

  设计出一个优雅的、易于维护的程序难点在于发现我所说的“变化的向量”(在这里,“向量”指的是最大的梯度变化方向(maximum gradient),而并非指一个容器类)。意思是找出系统中变化的最重要的部分,或者换句话说,发现影响系统最大的花销在哪里。一旦你发现了变化的向量,你就可以围绕这个重点设计你的程序。

  所以设计模式的目的就是分离代码中的可变部分。如果你这么去审视这个问题,你会立刻看到多个设计模式。举个例子,面向对象的继承(inheritance)可以看做一种设计模式(虽然是由编译器实现的)。它允许通过同样的接口(不变的部分)来表现不同的行为(变化的部分)。组合也可以被认为是一种设计模式,因为它允许通过动态或静态的方式改变实现类的对象以及他们的行为。

  另一个常见的设计模式例子是迭代器。迭代器自Python出现伊始就已经随for循环的使用而存在了,并且在Python2.2版本的时候被明确成为其一个特性。一个迭代器隐藏了容器内部的具体实现,提供一个依次访问容器对象内每个元素的方式。所以,你能够使用通用的代码对一个序列的每个元素做对应操作而不用去理会此序列是怎么建立的。所以你的代码能够对任何能够产生迭代器的对象有效。

  这里列举了三种最基本的设计模式:

  1. 结构化模式,通常用来处理实体之间的关系,使得这些实体能够更好地协同工作。
  2. 创建模式,提供实例化的方法,为适合的状况提供相应的对象创建方法。
  3. 行为模式,用于在不同的实体建进行通信,为实体之间的通信提供更容易,更灵活的通信方法。

 我们为什么要使用设计模式?

  从理论上来说,设计模式是对程序问题比较好的解决方案。无数的程序员都曾经遇到过这些问题,并且他们使用这些解决方案去处理这些问题。所以当你遇到同样的问题,为什么要去想着创建一个解决方案而不是用现成的并且被证明是有效的呢?

  例子

  假定现在有一个任务,需要你找到一个有效的方法合并两个做不同事情的类,在已有系统中这两个类在许多不同的地方被大量使用,所以移除这两个类或是改动已有的代码都是异常困难的。不仅如此,更改已有的代码会导致大量的测试工作,因为在这样一种依赖大量不同组件的系统中,这些修改总是会引入一些新的错误。为了避免这些麻烦,你可以实现一个策略模式(Strategy Pattern)和适配器模式(Adapter Pattern)的变体,这两种模式能够很好的处理这种问题。


1

2

3

4

5

6

7

8

9

10

11

12

class StrategyAndAdapterExampleClass():

    def __init__(self, context, class_one, class_two):

        self.context = context

        self.class_one = class_one

        self.class_two = class_two

    def operation1(self):

        if self.context == "Context_For_Class_One":

            self.class_one.operation1_in_class_one_context()

        else:

            self.class_two.operational_in_class_two_context()

  很简单是吧?现在让我们来仔细研究一下策略模式。

 策略模式

  策略模式是一种与行为相关的设计模式,允许你在运行时根据指定的上下文确定程序的动作。你可以在两个类中封装不同的算法,并且在程序运行时确定到底执行哪种策略。

  在上面的例子中,策略是根据实例化时context变量的值来决定的。如果给定context变量的值是“class_one”,将会执行class_one,否则就会执行class_two。

  我在那里使用它?

  假定你现在正在写一个类能够更新或创建一条新的用户记录,接收同样的输入参数(诸如姓名、地址、手机号等),但是根据不同的情况会调用对应的更新或是创建方法。当然,你可能会用一个if-else判断处理这个问题,但是如果你需要在不同的地方使用这个类呢?那么你就得不停地重写if-else判断。为什么不简单地通过指定上下文来解决这个问题。


1

2

3

4

5

6

class User():

    def create_or_update(self, name, address, mobile, userid=None):

        if userid:

            # it means the user doesn‘t exist yet, create a new record

        else:

            # it means the user already exists, just update based on the given userid

  常规的策略模式涉及到将算法封装到另一个类中,但如果这样的话,那个类就太浪费了。切记不要死记模板,把握住核心概念灵活的变通,最重要是解决问题。

 适配器模式

  适配器模式是一个结构性的设计模式,允许通过不同的接口为一个类赋予新的用途,这使得使用不同调用方式的系统都能够使用这个类。

  也可以让你改变通过客户端类接收到的输入参数以适应被适配者的相关函数。

  怎么使用?

  另一个使用适配器类的地方是包装器(wrapper),允许你讲一个动作包装成为一个类,然后可以在合适的情形下复用这个类。一个典型的例子是当你为一个table类簇创建一个domain类时,你能够将所有的对应不同表的相同动作封装成为一个适配器类,而不是一个接一个的单独调用这些不同的动作。这不仅使得你能够重用你想要的所有操作,而且当你在不同的地方使用同样的动作时不用重写代码。

  比较一下两种实现:

  不用适配器的方案


1

2

3

4

5

6

7

8

9

10

11

12

13

class User(object):

    def create_or_update(self):

        pass

class Profile(object):

    def create_or_update(self):

        pass

user = User()

user.create_or_update()

profile = Profile()

profile.create_or_update()

  如果我们需要在不同的地方做同样的事,或是在不同的项目中重用这段代码,那么我们需要重新敲一遍。

  使用包装类的解决方案

  看看我们怎么反其道而行:


1

2

account_domain = Account()

account_domain.NewAccount()

  在这种情况下,我们通过一个包装类来实现账户domain类:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

class User(object):

    def create_or_update(self):

        pass

class Profile(object):

    def create_or_update(self):

        pass

class Account():

    def new_account(self):

        user = User()

        user.create_or_update()

        profile = Profile()

        profile.create_or_update()

  这样的话,你就能够在你需要的时候使用账户domain了,你也可以将其他的类包装到domain类下。

 工厂模式

  工厂模式是一种创建型的设计模式,作用如其名称:这是一个就像工厂那样生产对象实例的类。

  这个模式的主要目的是将可能涉及到很多类的对象创建过程封装到一个单独的方法中。通过给定的上下文输出指定的对象实例。

  什么时候使用?

  使用工厂模式的最佳时机就是当你需要使用到单个实体的多个变体时。举个例子,你有一个按钮类,这个按钮类有多种变体,例如图片按钮、输入框按钮或是flash按钮等。那么在不同的场合你会需要创建不同的按钮,这时候就可以通过一个工厂来创建不同的按钮。

  让我们先来创建三个类:


1

2

3

4

5

6

7

8

9

10

11

12

13

class Button(object):

    html = ""

    def get_html(self):

        return self.html

class Image(Button):

    html = "<img alt="" />"

class Input(Button):

    html = "<input type="text" />"

class Flash(Button):

    html = ""

  然后创建我们的工厂类:


1

2

3

4

class ButtonFactory():

    def create_button(self, typ):

        targetclass = typ.capitalize()

        return globals()[targetclass]()

  译注:globals()将以字典的方式返回所有全局变量,因此targetclass = typ.capitalize()将通过传入的typ字符串得到类名(Image、Input或Flash),而globals()[targetclass]将通过类名取到类的类(见元类),而globals()[targetclass]()将创建此类的对象。

我们可以这么使用工厂类:


1

2

3

4

button_obj = ButtonFactory()

button = [‘image‘, ‘input‘, ‘flash‘]

for b in button:

    print button_obj.create_button(b).get_html()

  输出将是所有按钮类型的HTML属性。这样骂你就能够根据不同的情况指定不同类型的按钮了,并且很易于重用。

 装饰器模式

  装饰器模式是一个结构性模式,允许我们根据情况,在运行时为一个对象添加新的或附加的行为。

  目的是为给一个特定的对象实例应用扩展的函数方法,并且同时也能够产生没有新方法的原对象。它允许多装饰器结合用于一个实例,所以你就不会出现实例同单个装饰器相捆绑的情况了。这个模式是实现子类继承外的一个可选方式,子类继承是指从父类集成相应的功能。与子类继承必须在编译时添加相应的行为不同,装饰器允许你在运行时根据需要添加新的行为。

  可以根据以下步骤实现装饰器模式:

  1. 以原组件类为基类创建装饰器类。
  2. 在装饰器类中添加一个组件类的指针域
  3. 将一个组件传递给装饰器类的构造器以初始化组件类指针
  4. 在装饰器类中,将所有的组件方法指向组件类指针,并且,
  5. 在装饰器类中,重写每个需要修改功能的组件方法。

  相关维基百科(http://en.wikipedia.org/wiki/Decorator_pattern

  什么时候使用?

  使用装饰器模式的最佳时机是当你有一个根据情况需要添加新的行为的实体时。假设你有一个HTML链接元素,一个登出链接,并且你希望根据当前页面对具体的行为做微小的改动。这种情况下,我们可以使用装饰器模式。

  首先,建立我们所需要的装饰模式。

  如果我们在主页并且已经登录,那么将登出链接用h2标签标记。

  如果我们在不同的页面并且已经登录,那么用下划线标签标记链接

  如果已登录,用加粗标记链接。

  一旦建立了装饰模式,我们就可以开工了。


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

class HtmlLinks():

    def set_html(self, html):

        self.html = html

    def get_html(self):

        return self.html

    def render(self):

        print(self.html)

class LogoutLink(HtmlLinks):

    def __init__(self):

        self.html = "<a href="logout.html"> Logout </a>"

class LogoutLinkH2Decorator(HtmlLinks):

    def __init__(self, logout_link):

        self.logout_link = logout_link

        self.set_html(" {0} ".format(self.logout_link.get_html()))

    def call(self, name, args):

        self.logout_link.name(args[0])

class LogoutLinkUnderlineDecorator(HtmlLinks):

    def __init__(self, logout_link):

        self.logout_link = logout_link

        self.set_html(" {0} ".format(self.logout_link.get_html()))

    def call(self, name, args):

        self.logout_link.name(args[0])

class LogoutLinkStrongDecorator(HtmlLinks):

    def __init__(self, logout_link):

        self.logout_link = logout_link

        self.set_html("<strong> {0} </strong>".format(self.logout_link.get_html()))

    def call(self, name, args):

        self.logout_link.name(args[0])

logout_link = LogoutLink()

is_logged_in = 0

in_home_page = 0

if is_logged_in:

    logout_link = LogoutLinkStrongDecorator(logout_link)

if in_home_page:

    logout_link = LogoutLinkH2Decorator(logout_link)

else:

    logout_link = LogoutLinkUnderlineDecorator(logout_link)

logout_link.render()

 单例模式

  单例模式是一个创建型的设计模式,功能是确保运行时对某个类只存在单个实例对象,并且提供一个全局的访问点来访问这个实例对象。

  因为对于调用单例的其他对象而言这个全局唯一的访问点“协调”了对单例对象的访问请求,所以这些调用者看到的单例内变量都将是同一份。

  什么时候能够使用?

  单例模式可能是最简单的设计模式了,它提供特定类型的唯一对象。为了实现这个目标,你必须控制程序之外的对象生成。一个方便的方法是将一个私有内部类的单个对象作为单例对象。


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

class OnlyOne:

    class __OnlyOne:

        def __init__(self, arg):

            self.val = arg

        def __str__(self):

            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

    def __getattr__(self, name):

        return getattr(self.instance, name)

x = OnlyOne(‘sausage‘)

print(x)

y = OnlyOne(‘eggs‘)

print(y)

z = OnlyOne(‘spam‘)

print(z)

print(x)

print(y)

print(`x`)

print(`y`)

print(`z`)

output = ‘‘‘

<__main__.__OnlyOne instance at 0076B7AC>sausage

<__main__.__OnlyOne instance at 0076B7AC>eggs

<__main__.__OnlyOne instance at 0076B7AC>spam

<__main__.__OnlyOne instance at 0076B7AC>spam

<__main__.__OnlyOne instance at 0076B7AC>spam

<__main__.OnlyOne instance at 0076C54C>

<__main__.OnlyOne instance at 0076DAAC>

<__main__.OnlyOne instance at 0076AA3C>

‘‘‘

  因为内置类是用双下划线开始命名,所以它是私有的,用户无法直接访问。内置类包含了所有你希望放在普通类中的方法,并且通过外层包装类的构造器控制其创建。当第一次你创建OnlyOne时,初始化一个实例对象,后面则会忽略创建新实例的请求。

  通过代理的方式进行访问,使用__getattr__()方法将所有调用指向单例。你可以从输出看到虽然看起来好像创建了多个对象(OnlyOne),但 __OnlyOne对象只有一个。虽然OnlyOne实例有多个,但他们都是唯一的 __OnlyOne对象的代理。

  请注意上面的方法并没有限制你只能创建一个对象,这也是一个创建有限个对象池的技术。然而在那种情况下,你可能会遇到共享池内对象的问题。如果这真是一个问题,那你可以通过为共享对象设计签入“check-in”和迁出“check-out”机制来解决这个问题。

 总结

  在本文中,我只列举了几个我再编程中觉得十分重要的设计模式来讲,除此之外还有很多设计模式需要学习。如果你对其他的设计模式感兴趣,维基百科的设计模式部分(http://en.wikipedia.org/wiki/Design_pattern_%28computer_science%29)可以提供很多信息。如果还嫌不够,你可以看看四人帮的《设计模式:可复用面向对象软件的基础》(http://www.amazon.com/o/asin/0201633612)一书,此书是关于设计模式的经典之作。

  最后一件事:当使用设计模式时,确保你是用来解决正确地问题。正如我之前提到的,设计模式是把双刃剑:如果使用不当,它们会造成潜在的问题;如果使用得当,它们则将是不可或缺的。

  原文链接: pypix.com   翻译: 伯乐在线- 熊崽Kevin

时间: 2024-10-07 08:32:33

写给Python初学者的设计模式入门的相关文章

致Python初学者:Anaconda入门使用指南

原文出处: 鱼心fishstar 打算学习 Python 来做数据分析的你,是不是在开始时就遇到各种麻烦呢? 到底该装 Python2 呢还是 Python3 ? 为什么安装 Python 时总是出错? 怎么安装工具包呢? 为什么提示说在安装这个工具前必须先安装一堆其他不明所以的工具? 相信大多数 Python 的初学者们都曾为环境问题而头疼不已,但你并不孤独,大家都是这么折腾过来的.为了在入门时少走弯路,并且让高涨的积极性不至于太受打击,这里推荐使用 Anaconda 来管理你的安装环境和各种

Apache Solr初学者教程(入门之旅)

Apache Solr初学者教程(入门之旅) 写在前面:本文涉及solr入门的各方面,请逐行阅读,相信能帮助你对solr有个清晰全面的了解和使用. 在Apache Solr初学者教程的这个例子中,我们将讨论有关如何安装最新版本的Apache Solr,并告诉你如何配置它.此外,我们将告诉你如何进行使用solr的样本数据文件索引.Apache Solr支持不同格式,包括各种数据库,PDF文件,XML文件,CSV文件等等.在这个例子中,我们的索引将研究如何索引数据从一个CSV文件. 我们首选的这个例

Python 初学者的最佳学习资源

本文由 伯乐在线 - caimaoy 翻译,唐尤华 校稿.未经许可,禁止转载!英文出处:Matt Makai.欢迎加入翻译组. Python 社区在分享学习资源和帮助初学者掌握语言方面总是很积极的.但也就是因为资源过多,导致人们很难知道如何找到. 本文整理了最好.最通用的 Python 资源,并且简述了其内容.[伯乐在线注:译文中添加了一些中文Python学习资源] 致编程新手 如果你是第一次学习编程,本节推荐的书是比较适合你的.如果在学习Python之前你已经学习过其他的编程语言,请跳过此节直

都说python是最佳编程入门语言,为什么你学习却是如此坎坷?

为什么都说python是最佳编程入门语言? 引用Elliott Hauser 的说法,好的编程语言学生在入门时需要获得五样东西. 非常棒的首次体验,就像一本书的第一页,首先需要"入迷",学习新知识不可避免的会遇到挫折,但要有持续的热情和好奇心,这对于那些从未接触过编码的年轻人来说是至关重要的: Web编程的能力,对于职业发展和程序工艺来说,Web编程越来越重要,学生有机会就应当掌握一定的Web架构基础: 桌面编程能力,尽管将来趋势将更多的转移到Web应用上,但没什么能比开发和运行一个本

python 初学者

明确学习目标,不急于求成 当下是一个喧嚣.浮躁的时代.我们总是被生活中大量涌现的热点所吸引,几乎没有深度阅读和思考的时间和机会.我始终认为,学习是需要沉下心来慢慢钻研的,是长 期的:同时,学习不应该被赋予太多的功利色彩.一个Python 程序员的成长路线图应该是这样子的:基础语法–>语感训练–>课题练习–>分方向继续学习–>中级程序员–> 拓展深度和广度–>高级程序员. 然而,很多新手的学习路线图却是这样子的:学完基础语法之后,不了解 http 协议和 Ajax 异步请

PySide——Python图形化界面入门教程(六)

PySide——Python图形化界面入门教程(六) ——QListView和QStandardItemModel 翻译自:http://pythoncentral.io/pyside-pyqt-tutorial-qlistview-and-qstandarditemmodel/ 上一个教程中,我们讨论了Qt的QListWidget类,它用来实现简单的单列列表框(list boxes).然而,我们还需要更加灵活的widget来实现列表,Qt为此提供了QListView 来实现多种多样的项.它是一

Python的简介与入门

Python的简介与入门 ·Python是一种结合了解释.性编译性.互动性和面向对象多种特性的脚本语言.对于编程初学者而言,Python易于阅读与学习,并且支持广泛的应用程序的开发与拥有支持多种平台的广泛的基础数据库. ·安装Python在Windows环境下  1.进入Python 官方网站:https://www.python.org/                 2.点击Downloads==> Downloads for Windows==> Python 3.6.2  3.下载安

Python黑客编程2 入门demo--zip暴力破解

Python黑客编程2 入门demo--zip暴力破解 上一篇文章,我们在Kali Linux中搭建了基本的Python开发环境,本篇文章为了拉近Python和大家的距离,我们写一个暴力破解zip包密码的小程序.这个例子来自于<Voilent Python>一书,这也是一本python黑客编程的入门书,推荐大家看一看. 照顾没有接触过Python编程的同学,行文可能会有些啰嗦. 废话少说,我们进入正题. 2.1 准备基本材料 在/home/ziptest/目录下,我创建了两个文件,一个test

【摘】设计模式入门指南

设计模式入门指南 想知道设计模式是什么?在这篇文章中,我会解释为什么设计模式重要.我也会提供一些PHP的例子来解释什么时候什么情况下来使用设计模式. 什么是设计模式? 设 计模式是针对我们日常编程问题的经过优化的可重用的方法.一种设计模式不仅仅是可以简单集成到系统中的一个类或者一个库.它是一个只能在正确的情境下使用 的模板.它也不是只针对某种语言.一个好的设计模式应该可以适用于绝大多数语言中,同时也要依赖于语言的特性.最重要的是,任何设计模式如果用错地方的 话,就有可能变成一把双刃剑,它可以是灾