在阅读 clean architecture的过程中,会发现作者经常提到recompile redeploy,这些术语看起来都跟静态类型语言有关,比如Java、C++、C#。而在我经常使用的python语言中,是不存在这些概念的。于是,在阅读的时候就会有一个疑惑,《clean architecture》中提到的各种原则,比如SOLID,是否对动态类型语言 -- 如python -- 同样适用?

SOLID是面向对象设计的指导原则,更具适用性的应该是各种设计模式,GOF经典的Design Patterns: Elements of Reusable Object-Oriented Software 也是用C++来举例的,那么这些经典设计模式有多少是适用于动态语言如python的呢?本文记录对这些问题浅薄的思考,如果有认知错误的地方,还请大家不吝指教。

本文地址:https://www.cnblogs.com/xybaby/p/11782293.html

SOLID

SOLID是模块(module)设计的指导原则,有以下五个原则组成

  • SRP(Single responsibility principle):单一职责原则,一个module只有一个原因修改
  • OCP(Open/closed principle):开放-关闭原则,开放扩展,关闭修改
  • LSP(Liskov substitution principle):里氏替换原则,子类型必须能够替换它们的基类型
  • ISP(Interface segregation principle):接口隔离原则,你所依赖的必须是真正使用到的
  • DIP(Dependency inversion principle):依赖倒置原则,依赖接口而不是实现(高层不需要知道底层的实现)

ISP

首先来看ISP,接口隔离原则,《clean architecture》的作者承认这是一个语言相关的原则

This fact could lead you to conclude that the ISP is a language issue, rather than an architecture issue.

为什么呢, ISP主要是为了解决“胖接口”导致的不必要的 recompilation and redeployment, 如下所示:

Use1对op1的使用导致OPS的修改,导致User2 User3也要重新编译。而在动态语言中是不存在重新编译这样的问题的:

In dynamically typed languages like Ruby and Python, such declarations don’t exist in source code. Instead, they are inferred at runtime. Thus there are no source code dependencies to force recompilation and redeployment

DIP

DIP(依赖倒置原则)是SOLID的核心,OCP其实就依赖于DIP。也可以说,DIP是“clean architecture”的核心。

“clean architecture”由两部分组成:

  • well-isolated components
  • dependency rule

什么是”Dependency rule"呢?让低层的detail去依赖高层的policy。比如,业务逻辑(business rule)就相比数据存储(database)出于更高层,虽然逻辑上是业务逻辑要使用数据库,但为了可维护性、可扩展性,架构设计上得让database去依赖business rule,如下所示

从上图可以看出,为了达到这个目的,在静态语言中,会声明一个接口,调用的双方都依赖这个接口。如上图中的database interface,让business rule和database都去依赖这个接口,而这个database interface和business rule在一个component,这就实现了让低层的database去依赖高层的business rule。

在静态类型语言(如Java、C++)中,其实就是利用运行时多态这个特性,使得可以在运行时 -- 而不是编译时 -- 改变软件的行为,当然为了达到这个目的,需要预先声明一个虚基类 或者接口(Java Interface)。

而在python中,本来就是运行的时候去求值,而且因为ducking type,所以无需事先声明接口或者强迫继承接口

Dependency structures in these languages(dynamic typed languages) are much simpler because dependency inversion does not require either the declaration or the inheritance of interfaces.

从静态类型语言到动态类型语言,其实是省略了很多东西

  • 省略了虚函数,如template method模式
  • 省略了虚基类、接口,如DIP、strategy模式

python中的依赖与依赖倒置

在python中,怎么算依赖,怎么算依赖倒置?

'''my.py'''
import other
class My(object):
def f(self):
other.act()

这一段代码中通过import让module my依赖于module other,

'''my.py'''
class My(object):
def __init__(self, actor):
self._actor = actor def f(self):
self._actor.act()

那么在这里,my和other有依赖关系吗?没有的,这里压根就没有出现过other。由于动态类型加上ducking type,根本无需显式的接口定义,只要遵循相关的协议(契约)即可。而这个契约,没办法通过代码强行约束,调用者需要什么样的接口,被调用者应该具备什么样的行为,都只能通过文档(或者单元测试)来描述。

为了表达契约,上述代码应该加上docstring

'''my.py'''
class My(object):
def __init__(self, actor):
'''Param: actor,该对象需要具备接收0个参数的act方法
'''
self._actor = actor def f(self):
self._actor.act()

python中大量使用类似的协议,如context management, iterator protocol。虽然很方便,同时也对程序员有更高要求,因为至少得有靠谱的docstring。如果需要强加约束,那是是可以考虑使用abc的。

设计模式

首先声明的是,在本文中提到的设计模式,一般指Design Patterns: Elements of Reusable Object-Oriented Software 这本书中所描述的经典设计模式。

很早之前看过一种说法,“++设计模式是对静态语言缺陷的弥补”++,当时没经思考就全盘接受了,窃认为这就是真理。最近才真正思考这个问题,发现这种说法存在偏见与不全面。

首先抛出一个问题:设计模式是语言相关吗(language-specific)?是某种类型的编程语言需要设计模式,而另外一些编程语言就不需要?或者说,不同的编程语言需要的设计模式是不一样的?

什么是设计模式呢,《Design Patterns》中描述为,针对软件设计中某一类特定问题的简单且优美的解决方案。

Describes simple and elegant solutions to specific problems in object-oriented software design

也就是说,设计模式是解决某类特定问题的套路,或者说方法论。套路是针对某个问题,经过理论或实践验证的、行之有效的方法与步骤。没有方法论也能解决问题,可能就需要去大量的尝试、试错,得到一种解决办法(大概率也不是最优解),这个求解的过程耗时且低效。因此可以说,方法论(模式)加速了问题求解的过程。

比如,程序员每天都很大量的事情要做:要开会、要写代码、要处理bug、要自己充电。如何安排呢?可能自己思考这个问题就得焦头烂额,但是已经有成熟的方法论 --艾森豪威尔矩阵-- 可供使用了啊。

我们常说,站在巨人的肩膀上,套路、方法论就是巨人的肩膀。

设计模式同样如此。

设计模式与动态语言

《Design Patterns》这本书,写于1994年,作者提到写这本数的目标,就是将这些行之有效的经验记录下来。前面提到,设计模式是针对一类问题的解决方案,那么在介绍一种设计模式的时候,就一定会涉及到以下内容(包括但不限于):

  • 要解决的问题是什么
  • 解决方案是什么样子的
  • 解决方案的缺陷与适用场景
  • 解决方案的详细步骤
  • 针对同一个问题,有没有其他解决方案,各自的优劣

当然,首先得给这个模式取一个恰如其分的名字,命名的重要性不容质疑。至少保证程序员之间在沟通的时候所表达的是同一个问题,不管这个沟通是peer to peer,还是通过代码。名字(术语、定义)也就减轻了沟通的成本。

在《Design Patterns》写成的两年后,即1996年,Peter Norvig就做了一个分享 “Design Patterns in Dynamic Programming”, 指出由于动态语言存在更少的语言层面的限制,GOF中的大多数设计模式在Lisp或者Dylan有更简单的实现,有的甚至简单到根本无需注意

16 of 23 patterns have qualitatively simpler implementation in Lisp or Dylan than in C++ for at least some uses of each pattern

16 of 23 patterns are either invisible or simpler

那么哪些模式变得“invisible”,哪些是“simpler”了呢?

《Design Patterns》中讲设计模式大致分为三类

  • Creational: ways and means of object instantiation
  • Structural: mutual composition of classes or objects (the Facade DP is Structural)
  • Behavioral: how classes or objects interactand distribute responsibilities among them

由于在动态类型语言中,类(class, type)和方法(function)都是一等公民,因此Creational patterns在动态类型语言,如Python中就变得“invisible”。

由于动态类型、ducking type,一些Creational patterns如“Observer”,“Visitor”就变得“simpler”。这里要强调的是,变得更简单,并不意味这个这个模式就没有存在的意义了,比如观察者模式,或者订阅-发布,代表了松耦合的设计原则,在各个层级的设计中都是需要的。

对于这种体现更高原则、思想的设计模式,我们应该用模式去帮助思考和沟通,而不要拘泥于样板代码、特定语言实现。StackExchange上的这个排比句很恰当:

- I might say that I have a visitor pattern, but in any language with first class functions it will be just a function taking a function. Instead of factory class I usually have just a factory function.
- I might say I have an interface, but then it's just a couple of methods marked with comments, because there wouldn't be any other implementation (of course in python an interface is always just comments, because it's duck-typed).
- I still speak of the code as using the pattern, because it's a useful way to think about it, but don't actually type in all the stuff until I really need it.

那么回到问题,设计模式是语言相关吗(language-specific)?

我的回答是,部分设计模式是语言相关的,部分设计模式不是语言相关的,具体到某一个特定的模式还可能是变化的。

为什么呢,严谨一点,我们只能说设计模式是问题相关的 -- 是关乎某个问题的。核心在于,这个问题在什么情况下确实是一个问题。而且,随着发展,一个老问题会消亡,新问题会出现。

具体到编程语言,则应该关心的是一个问题是不是语言相关的。在静态类型语言,如C++中,对象都有类型,类型决定了其行为,那么为了运行时多态,就得有一个虚基类,同时还要做到OCP,这就需要各式各样的Creational Patterns。但到了动态类型语言,这个就不再是一个问题,因此就不再有与之对应的模式。

references

SOLID原则、设计模式适用于Python语言吗的更多相关文章

  1. 设计模式之SOLID原则

    介绍 设计模式中的SOLID原则,分别是单一原则.开闭原则.里氏替换原则.接口隔离原则.依赖倒置原则.前辈们总结出来的,遵循五大原则可以使程序解决紧耦合,更加健壮. SRP 单一责任原则 OCP 开放 ...

  2. 实践GoF的23种设计模式:SOLID原则(上)

    摘要:本文以我们日常开发中经常碰到的一些技术/问题/场景作为切入点,示范如何运用设计模式来完成相关的实现. 本文分享自华为云社区<实践GoF的23种设计模式:SOLID原则(上)>,作者: ...

  3. 【学习笔记】PYTHON语言程序设计(北理工 嵩天)

    1 Python基本语法元素 1.1 程序设计基本方法 计算机发展历史上最重要的预测法则     摩尔定律:单位面积集成电路上可容纳晶体管数量约2年翻倍 cpu/gpu.内存.硬盘.电子产品价格等都遵 ...

  4. 面向对象的SOLID原则白话篇

    面向对象的SOLID原则 简介 缩写 全称 中文 S The Single Responsibility Principle 单一责任原则 O The Open Closed Principle 开放 ...

  5. google的python语言规范

    Python语言规范   Lint Tip 对你的代码运行pylint 定义: pylint是一个在Python源代码中查找bug的工具. 对于C和C++这样的不那么动态的(译者注: 原文是less ...

  6. 设计模式及Python实现

    设计模式是什么? Christopher Alexander:“每一个模式描述了一个在我们周围不断重复发生的问题,以及该问题的解决方案的核心.这样你就能一次又一次地使用该方案而不必做重复劳动.” 设计 ...

  7. Python 语言规范

    Python 语言规范 pychecker  对你的代码运行pychecker 定义: pychecker 是一个在Python 源代码中查找bug 的工具. 对于C 和C++这样的不那 么动态的( ...

  8. 浅谈设计模式及python实现

    设计模式及Python实现   设计模式是什么? Christopher Alexander:“每一个模式描述了一个在我们周围不断重复发生的问题,以及该问题的解决方案的核心.这样你就能一次又一次地使用 ...

  9. Python 语言规范(Google)

    Python语言规范 Lint tip 对你的代码运行pylint 定义: pylint是一个在Python源代码中查找bug的工具. 对于C和C++这样的不那么动态的(译者注: 原文是less dy ...

随机推荐

  1. 004-python面向对象,错误,调试和测试

    ---恢复内容开始--- 1.面向对象 面向对象编程——Object Oriented Programming,简称OOP,是一种程序设计思想.OOP把对象作为程序的基本单元,一个对象包含了数据和操作 ...

  2. java架构之路-(SpringMVC篇)SpringMVC主要流程源码解析(上)源码执行流程

    做过web项目的小伙伴,对于SpringMVC,Struts2都是在熟悉不过了,再就是我们比较古老的servlet,我们先来复习一下我们的servlet生命周期. servlet生命周期 1)初始化阶 ...

  3. vue2.0项目记住密码和用户名实例

    的今天突来兴致,试了一下将用户名和密码存在cookie和localStorage里如何实现:从代码难易程度来讲,果断选择了将用户名和密码存在localStorage里面.当然菜鸟上这么说的,楼下. 也 ...

  4. 详解http报文(2)-web容器是如何解析http报文的

    摘要 在详解http报文一文中,详细介绍了http报文的文本结构.那么作为服务端,web容器是如何解析http报文的呢?本文以jetty和undertow容器为例,来解析web容器是如何处理http报 ...

  5. 快学Scala 第十课 (包和包对象)

    Scala包定义: 嵌套式: package a1 { class a1Class{ val age = 10 } package a2 { class PackageTest { def main( ...

  6. 【SQL】 收入支出求盈亏

    求项目ID为1000的盈亏 表名为:T 字段:ID    P_ID   AMOUNT   TYPE(1:收入 2:支出) '

  7. 手把手带你体验Stream流

    前言 只有光头才能变强. 文本已收录至我的GitHub仓库,欢迎Star:https://github.com/ZhongFuCheng3y/3y 上一篇讲解到了Lambda表达式的使用<最近学 ...

  8. js构造函数的浅薄理解

    任何函数,只要通过 new 操作符来调用,那它就可以作为构造函数 如:任何函数,只要通过 new 操作符来调用,那它就可以作为构造函数 : fuction Preson(){...} var pres ...

  9. STM32之串口DMA接收不定长数据

    STM32之串口DMA接收不定长数据 引言 在使用stm32或者其他单片机的时候,会经常使用到串口通讯,那么如何有效地接收数据呢?假如这段数据是不定长的有如何高效接收呢? 同学A:数据来了就会进入串口 ...

  10. 数据存储检索之B+树和LSM-Tree

    作为一名应用系统开发人员,为什么要关注数据内部的存储和检索呢?首先,你不太可能从头开始实现一套自己的存储引擎,往往需要从众多现有的存储引擎中选择一个适合自己应用的存储引擎.因此,为了针对你特定的工作负 ...