关键时刻,第一时间送达!
作者ANDREI BOYANOV - 高级软件工程师 @TOPTAL
让我们再重申一下:Python是一种具有动态类型和动态绑定的高级编程语言。我将它描述为一个强大的高级动态语言。许多开发人员都喜欢Python,因为它具有清晰的语法、结构良好的模块和包,以及巨大的灵活性和广泛的现代功能。
在Python中,并不要求您写的类和对象进行实例化。如果您的项目中不需要复杂的结构,则可以只编写函数。更好的是,您可以编写一个平面脚本来执行一些简单而快速的任务,而不需要构建代码。
与此同时,Python是面向对象的100%语言。那是什么意思呢?简单地说,Python中的一切都是一个对象。 函数是对象,第一类对象(无论什么意思)。关于函数是对象的这个事实很重要,所以请记住它。
所以,您可以在Python中编写简单的脚本,或者只是打开Python终端,并在那里执行语句(这非常有用!)。但同时,您可以创建复杂的框架、应用程序、库等。您可以在Python中做这么多。当然还有一些限制,但这不是本文的主题。
然而,由于Python是如此强大和灵活,我们在编程时需要一些规则(或模式)。所以,让我们看看什么是模式,以及它们与Python的关系。我们还将着手实施一些基本的Python设计模式。
为什么Python对模式有好处?
任何编程语言对模式都有益。实际上,应该在任何给定的编程语言的上下文中考虑模式。模式,语言语法和类型都对我们的编程产生了限制。来自语言语法和语言本质(动态,功能,面向对象等)的限制可能与其背后存在的原因不同。模式的限制是有原因的,它们是有目的的。这是模式的基本目标; 告诉我们如何做到某事,以及怎么样会做不到。稍后我们将讨论模式,特别是Python设计模式。
Python是一种动态和灵活的语言。Python设计模式是利用其巨大潜力的好方法。
Python的理念建立在思想最好的做法之上。Python是一种动态语言(我已经说过了吗?),因此,已经实现了一些流行的设计模式,其中包含几行代码。一些设计模式是内置在Python中,所以即使不知道,我们也可以使用它们。由于语言的本质,不需要其他模式。
例如,Factory是一种结构化的Python设计模式,其旨在创建新对象,并在用户那儿隐藏实例化逻辑。但是在Python中创建对象是动态的,所以不需要像Factory这样添加。当然,如果您愿意,您可以自由地实现它。可能有些情况真的很有用,但它们是一个例外,而不是规范。
Python的哲学有什么好处?我们从这开始(在Python终端中探索它):
这些可能不是传统意义上的模式,但这些是以最优雅和最有用的方式定义“Pythonic”编程方法的规则。
我们还有PEP-8编码指南,有助于构建我们的代码。对我而言,当然有一些适当的例外。顺便说一句,PEP-8本身也鼓励这些例外:
但最重要的是:知道何时不一致 - 有时风格指南并不适用。如有疑问,请用最好的判断。看看其他的例子来决定什么看起来最好。不要犹豫,去问!
将PEP-8与Python的Zen(也是PEP - PEP-20)相结合,您将拥有创建可读和可维护代码的完美基础。添加设计模式,您已准备好创建具有一致性和可演变性的各种软件系统。
Python设计模式
什么是设计模式?
一切从四人帮(GOF)开始。如果您不熟悉GOF,请快速进行在线搜索。
设计模式是解决众所周知问题的常用方式。两个主要原则是基于GOF定义的设计模式:
面向接口编程,而不是面向实现编程
组合优于继承
从Python程序员的角度,我们来仔细看看这两个原则。
面向接口编程,而不是面向实现编程
想想Duck Typing。在Python中,我们不喜欢根据这些接口定义接口和程序类,是这样吗?但是,听我说!这并不意味着我们不会考虑接口,实际上我们一直在做这种Duck Typing。
让我们来谈谈一下臭名昭着的Duck Typing方法,看看它是如何适应这种范式的:面向接口编程。
如果它看起来像个鸭子并且像一个鸭子一样嘎嘎,那它就是一个鸭子!
我们不关心对象的本质,我们不必关心对象是什么; 我们只想知道是否能够做我们需要的(我们只对对象的接口感兴趣)。
对象可以嘎嘎叫吗?好吧,让它嘎嘎叫!
我们为鸭定义了一个接口吗?没有!我们是否面向接口编程而不是面向实现?是! 而且,我觉得这很好。
正如Alex Martelli在他关于Python中的设计模式的众所周知的演讲中指出的,“学会鸭子类型需要一段时间,但是以后可以节省您大量的工作!”
组合优于继承
现在这就是我所说的Pythonic原理!与在其他类中包装一个类(或更多的是几个类)相比,我创建了更少的类/子类。
不是这样做:
我们可以这样做:
优点很明显。我们可以限制包装类的方法是否暴露。我们可以在运行时注入持久化实例!例如,今天它是一个关系型数据库,但是明天可能是任何需要的接口(再次是那些讨厌的鸭子)。
组合对于Python来说是优雅而自然的。
行为模式
行为模式涉及对象之间的通信、对象如何交互以及如何完成给定的任务。根据GOF原则,Python中共有11种行为模式:职能链,命令,解释器,迭代器,中介者,备忘录,观察者,状态,策略,模板方法,访问者。
行为模式处理对象间通信,控制各种对象如何交互和执行不同的任务。
我发现这些模式非常有用,但这并不意味着其他模式作用不大。
Iterator(迭代器)
迭代器被构建进了Python,这是这个语言最强大的特性之一。多年前,我读到别人说是迭代器使Python变得非常优秀,到现在我仍然是这么认为的。如果您对Python迭代器和生成器有足够多的了解,那您就会知道关于这个独特Python模式需要知道的一切。
Chain of responsibility(职能链)
这个模式为我们提供了一种使用不同方式对待一个请求的方法,每种方法都针对请求的特定部分。您知道的,对于好的代码来说,最好的原则之一就是单一职责原则。
每一块代码必须做一件事,并且只能做一件事。
这一原则深深融入了这一设计模式。
例如,如果我们要过滤一些内容,我们可以实现不同的过滤器,每个过滤器都要做一件精确的事情,并且明确定义其过滤类型。这些过滤器可用于过滤令人反感的单词,广告和不适合的视频内容等。
Command(命令)
这是我作为程序员实现的Python设计模式之一。这提醒了我:模式不是被发明的,而是被发现的。他们存在,我们只需要找到并使用它们。很多年前为了实现我们的一个很大的项目我发现了这一点:一个特殊的所见即所得的XML编辑器。在代码中集中使用这个模式后,我在另外一些网站上看到了更多关于它的内容。
出于某些原因,我们需要从准备执行的内容开始,然后在需要时执行该命令模式。这样的好处是在这种方式下封装的动作可以使Python开发人员添加与执行操作相关的其他功能,例如撤消、重做或者保留操作历史等。
让我们看一下简单但经常使用的例子:
Creational Patterns(创造者模式)
首先需要指出的是创建者模式在Python中并不常用。为什么?因为Python语言本身的动态性质。
某位比我更有智慧的人曾经说过工厂方法是内置在Python中的。这意味着语言本身为我们提供了一种可以以足够优雅的方式创建对象的全部灵活性; 我们很少需要在这之上实现任何东西,如单例和工厂方法。
在一个Python设计模式教程中,我发现了有关创造者模式的描述,它是这样来声明的“模式提供了一种在隐藏创建逻辑的同时创建对象的方法,而不是使用new运算符直接实例化对象”。
它恰到好处地总结了这个问题:我们不需要在Python中使用new运算符!
不管怎样,让我们来看下如何实现其中的某一小部分,看下使用这样的模式我们能否从中获得好处。
Singleton(单例)
当我们要保证运行时只有一个给定类的实例存在时,可以使用Singleton模式。在Python中我们是否真的需要这个模式? 根据我的经验,简单创建一个实例并且随后使用它比实现单例模式更为容易。
但是如果您想实现它,这里有一个好消息:在Python中,我们可以改变实例化过程(以及其他任何东西)。记得我之前提过的__new__()方法吗?就是它了:
在这个示例中,Logger是一个单例。
在Python中使用单例模式有以下这些备选方案:
使用模块
在应用程序的顶层创建一个实例,可以是配置文件
将实例传递给需要它的每个对象。这是一个依赖注入,它是一个强大而容易掌握的机制。
Dependency Injection(依赖注入)
我不打算讨论依赖注入是否是一种设计模式,但我会说这是一个实现松耦合的非常好的机制,它有助于让我们的应用变的更加可维护和可扩展。把它和鸭子类型结合起来然后力量就总是会与您同在。
鸭子?人类?Python都不关心。它灵活得很!
我之所在这篇文章的创造者模式部分列出来是因为它处理了何时(或者更好是:何地)创建对象这个问题。它是在外部创造的。更准确的说,对象并不是在我们使用它的地方创建的,所以依赖关系也不会在消费它的地方创建。消费者代码接收这些外部的对象并使用它。更多参考,请阅读这个Stackoverflow问题最受欢迎的答案。
这是一个关于依赖注入很好的解释,而且它给了我们关于这个特殊技术一个很好的想法。基本上,答案解释了以下示例的问题:不要自己从冰箱里面拿东西来喝,而是声明一个需要。告诉您的父母您需要在午餐喝点东西。
Python为我们提供了全部需要的东西并且实现起来很简单。想一下它在Java和C#其他语言中可能的实现,您将很快意识到Python的美丽。
让我们来看一个简单的关于依赖注入的例子:
我们在Command这个类中注入了authenticator和authorizer方法。Command类所需要的是成功地执行他们,而无须担心他们实现的细节。这样的话,我们可以在Command类中使用任何我们在运行时决定使用的认证和授权机制。
我们已经展示了如何通过构造函数注入依赖关系,但是我们可以通过直接设置对象属性来轻松地注入它们,从而解锁更多的潜力:
关于依赖注入还有很多的东西要学;例如,好奇的人会去搜索IoC。
但是在您这样做之前,请阅读Stackoverflow上的另一个回答,对于这个问题投票最多的回答。
还有,我们只是演示了如何在Python中只使用内置的语言功能来实现这个美妙的设计模式。
让我们不要忘记这一切的意义:依赖注入技术允许非常灵活和容易的单元测试。想象一下,您可以随时更改数据存储的架构。模拟数据库将会变成一个微不足道的任务,不是吗?有关进一步的信息,您可以查看Toptal的Python模拟简介。
您可能也会想研究原型、生成器和工厂方法设计模式。
结构化模式
Facade(外观模式)
这可能是最为出名的Python设计模式。
假设您有一个系统有着大量的对象。每一个对象都提供了丰富的接口方法 。您可以使用这个系统做很多很多事情,但是如何简化这个接口呢?为什么不添加一个接口对象来暴露出所有API方法的精心设计的子集呢?用Facade(外观模式)!
Facade(外观模式)是一个优雅的Python设计模式。它是精简接口的一个完美方式。
Python外观模式示例:
没有什么惊喜,也没有技巧,Car类是一个外观模式,就是这样。
Adapter(适配器)
如果说外观模式用于精简接口,那么适配器就是关于修改接口。就像当系统期待鸭子时却只有牛一样。
假设您有一个将信息记录到给定目的地的工作方法。您的方法期望目标有一个write()方法(例如,每个文件对象都有)。
我会说这是一个写得很好的带依赖注入的方法,它拥有巨大的扩展能力。假设您想要纪录到某些UDP套接字中而不是某个文件里,您知道如何打开这个UDP套接字,但唯一的问题是套接字对象没有write()方法。您需要一个适配器!
但是为什么我发现适配器如此重要?好吧,当它有效地和依赖注入结合时,它就会给予我们极大的灵活性。当我们可以实现一个适配器并将新接口转换为众所周知的接口时,为什么要修改我们经过验证的代码来支持新的接口?
由于桥接和代理设计模式与适配器的相似性,您还应该了解和掌握它们。想一下在Python中实现他们是多么的简单,并且考虑一下在您的项目中可能通过哪些不同的方式来使用它们。
Decorator(装饰者模式)
哦,我们真是太幸运了!装饰者模式真的很好,而且我们也已经集成到了这门语言中。在Python中我最喜欢的是使用它来教我们使用最佳的实践。这并不是指我们不会意识到最佳实践(尤其和设计模式),但不管怎样使用Python 我感觉我正在遵循着最佳实践。就我个人而言,我发现Python的最佳实践是非常符合直觉的,这是新手和精英开发人员都赞赏的东西。
装饰者模式是关于引入额外的功能,特别的是它没有使用继承。
那么,让我们来看下如何不用内建的Python功能来装饰一个方法。以下是一个直截了当的示例。
这里不太好的是execute方法不仅仅是执行某些功能,还做了别的事情。我们没有遵循单一职责原则。
像下面这样简单编写会好点:
我们可以在另一个地方,在装饰者模式中实现任何授权和认证功能,如下所示:
现在的execute()方法是:
简单易读
只做一件事(至少在看代码时)
用身份验证装饰
装饰有授权
我们使用Python集成的装饰者语法编写类似的代码:
重要的是要注意,您没有限制像装饰者那样的活动。一个装饰者可能涵盖了整个类。唯一的要求是它们必须是可调用的。但对此我们毫无疑问;我们只需要定义__call__(self)方法。
您可能也想再进一步了解Python的函数工具(functools)模块。有待发现的东西还有很多!
结论
我已经展示了使用Python的设计模式是多么的自然和容易,而且我也展示了如何在Python中简单的编程。
“简单比复杂好,”记得吗?也许您已经注意到,没有一个设计模式是完全且正式描述的。没有展示复杂全面的实现。您需要“感觉”并以最适合您的风格和需要的方式实施它们。Python是一种伟大的语言,它给了您创作灵活、可重用代码的全部力量。
然而,它给您的远胜于这些。它给了您编写非常糟糕的代码的“自由”。不要这么做!不要重复(DRY),并且不要写超过80个字符的代码行。还有不要忘了在合适的地方使用设计模式;它是从别人那里学习并从他们丰富的免费经验中获益的最好方式之一。
来自:python部落
译者:Sarah
英文原文:https://www.toptal.com/python/python-design-patterns
Python开发整理发布,转载请联系作者获得授权