Python编程应用设计原则详解

目录
  • 1、单一职责原则 SRP
  • 2、开闭原则 OCP
  • 3、里氏替换原则 (LSP)
  • 4、接口隔离原则 (ISP)
  • 5、依赖反转原则 (DIP)
  • 最后的话

写出能用的代码很简单,写出好用的代码很难。

好用的代码,也都会遵循一此原则,这就是设计原则,它们分别是:

  • 单一职责原则 (SRP)
  • 开闭原则 (OCP)
  • 里氏替换原则 (LSP)
  • 接口隔离原则 (ISP)
  • 依赖倒置原则 (DIP)

提取这五种原则的首字母缩写词,就是 SOLID 原则。下面分别进行介绍,并展示如何在 Python 中应用。

1、单一职责原则 SRP

单一职责原则(Single Responsibility Principle)这个原则的英文描述是这样的:A class or module should have a single responsibility。如果我们把它翻译成中文,那就是:一个类或者模块只负责完成一个职责(或者功能)。

让我们举一个更简单的例子,我们有一个数字 L = [n1, n2, …, nx] 的列表,我们计算一些数学函数。例如,计算最大值、平均值等。

一个不好的方法是让一个函数来完成所有的工作:

import numpy as np

def math_operations(list_):
    # Compute Average
    print(f"the mean is {np.mean(list_)}")
    # Compute Max
    print(f"the max is {np.max(list_)}") 

math_operations(list_ = [1,2,3,4,5])
# the mean is 3.0
# the max is 5

实际开发中,你可以认为 math_operations 很庞大,揉杂了各种功能代码。

为了使这个更符合单一职责原则,我们应该做的第一件事是将函数 math_operations 拆分为更细粒度的函数,一个函数只干一件事:

def get_mean(list_):
    '''Compute Max'''
    print(f"the mean is {np.mean(list_)}")
def get_max(list_):
    '''Compute Max'''
    print(f"the max is {np.max(list_)}")
def main(list_):
    # Compute Average
    get_mean(list_)
    # Compute Max
    get_max(list_)
main([1,2,3,4,5])
# the mean is 3.0
# the max is 5

这样做的好处就是:

  • 易读易调试,更容易定位错误。
  • 可复用,代码的任何部分都可以在代码的其他部分中重用。
  • 可测试,为代码的每个功能创建测试更容易。

但是要增加新功能,比如计算中位数,main 函数还是很难维护,因此还需要第二个原则:OCP。

2、开闭原则 OCP

开闭原则(Open Closed Principle)就是对扩展开放,对修改关闭,这可以大大提升代码的可维护性,也就是说要增加新功能时只需要添加新的代码,不修改原有的代码,这样做即简单,也不会影响之前的单元测试,不容易出错,即使出错也只需要检查新添加的代码。

上述代码,可以通过将我们编写的所有函数变成一个类的子类来解决这个问题。代码如下:

import numpy as np
from abc import ABC, abstractmethod
class Operations(ABC):
    '''Operations'''
    @abstractmethod
    def operation():
        pass
class Mean(Operations):
    '''Compute Max'''
    def operation(list_):
        print(f"The mean is {np.mean(list_)}")
class Max(Operations):
    '''Compute Max'''
    def operation(list_):
        print(f"The max is {np.max(list_)}")
class Main:
    '''Main'''
    def get_operations(list_):
        # __subclasses__ will found all classes inheriting from Operations
        for operation in Operations.__subclasses__():
            operation.operation(list_)
if __name__ == "__main__":
    Main.get_operations([1,2,3,4,5])
# The mean is 3.0
# The max is 5

如果现在我们想添加一个新的操作,例如:median,我们只需要添加一个继承自 Operations 类的 Median 类。新形成的子类将立即被 __subclasses__()接收,无需对代码的任何其他部分进行修改。

3、里氏替换原则 (LSP)

里式替换原则的英文是 Liskov Substitution Principle,缩写为 LSP。这个原则最早是在 1986 年由 Barbara Liskov 提出,他是这么描述这条原则的:

If S is a subtype of T, then objects of type T may be replaced with objects of type S, without breaking the program。

也就是说 子类对象能够替换程序中父类对象出现的任何地方,并且保证原来程序的逻辑行为不变及正确性不被破坏。

实际上,里式替换原则还有另外一个更加能落地、更有指导意义的描述,那就是按照协议来设计,子类在设计的时候,要遵守父类的行为约定(或者叫协议)。父类定义了函数的行为约定,那子类可以改变函数的内部实现逻辑,但不能改变函数原有的行为约定。这里的行为约定包括:函数声明要实现的功能;对输入、输出、异常的约定;甚至包括注释中所罗列的任何特殊说明。

4、接口隔离原则 (ISP)

接口隔离原则的英文翻译是 Interface Segregation Principle,缩写为 ISP。Robert Martin 在 SOLID 原则中是这样定义它的:Clients should not be forced to depend upon interfaces that they do not use。

直译成中文的话就是:客户端不应该被强迫依赖它不需要的接口。其中的 客户端 ,可以理解为接口的调用者或者使用者。

举个例子:

from abc import ABC, abstractmethod
class Mammals(ABC):
    @abstractmethod
    def swim(self) -> bool:
        pass
    @abstractmethod
    def walk(self) -> bool:
        pass
class Human(Mammals):
    def swim(self)-> bool:
        print("Humans can swim")
        return True
    def walk(self)-> bool:
        print("Humans can walk")
        return True
class Whale(Mammals):
     def walk(self) -> bool:
        print("Whales can't walk")
        return False
     def swim(self):
        print("Whales can swim")
        return True
human = Human()
human.swim()
human.walk()
whale = Whale()
whale.swim()
whale.walk()
 

执行结果:

Humans can swim
Humans can walk
Whales can swim
Whales can't walk

事实上,子类鲸鱼不应该依赖它不需要的接口 walk,针对这种情况,就需要对接口进行拆分,代码如下:

from abc import ABC, abstractmethod
class Swimer(ABC):
    @abstractmethod
    def swim(self) -> bool:
        pass
class Walker(ABC):
    @abstractmethod
    def walk(self) -> bool:
        pass
class Human(Swimer,Walker):
    def swim(self)-> bool:
        print("Humans can swim")
        return True
    def walk(self)-> bool:
        print("Humans can walk")
        return True
class Whale(Swimer):
    def swim(self):
        print("Whales can swim")
        return True
human = Human()
human.swim()
human.walk()
whale = Whale()
whale.swim()

5、依赖反转原则 (DIP)

依赖反转原则的英文翻译是 Dependency Inversion Principle,缩写为 DIP。英文描述:High-level modules shouldn't depend on low-level modules. Both modules should depend on abstractions. In addition, abstractions shouldn't depend on details. Details depend on abstractions。

我们将它翻译成中文,大概意思就是:高层模块不要依赖低层模块。高层模块和低层模块应该通过抽象(abstractions)来互相依赖。除此之外,抽象不要依赖具体实现细节,具体实现细节依赖抽象。

在调用链上,调用者属于高层,被调用者属于低层,我们写的代码都属于低层,由框架来调用。在平时的业务代码开发中,高层模块依赖低层模块是没有任何问题的,但是在框架层面设计的时候,就要考虑通用性,高层应该依赖抽象的接口,低层应该实现对应的接口。如下图所示:

也就是说本来 ObjectA 依赖 ObjectB,但为了扩展后面可能会有 ObjectC,ObjectD,经常变化,因此为了频繁改动,让高层模块依赖抽象的接口 interface,然后让 ObjectB 也反过来依赖 interface,这就是依赖反转原则。

举个例子,wsgi 协议就是一种抽象接口,高层模块有 uWSGI,gunicorn等,低层模块有 Django,Flask 等,uWSGI,gunicorn 并不直接依赖 Django,Flask,而是通过 wsgi 协议进行互相依赖。

依赖倒置原则概念是高层次模块不依赖于低层次模块。看似在要求高层次模块,实际上是在规范低层次模块的设计。低层次模块提供的接口要足够的抽象、通用,在设计时需要考虑高层次模块的使用种类和场景。明明是高层次模块要使用低层次模块,对低层次模块有依赖性。现在反而低层次模块需要根据高层次模块来设计,出现了「倒置」的显现。

这样设计好处有两点:

  • 低层次模块更加通用,适用性更广
  • 高层次模块没有依赖低层次模块的具体实现,方便低层次模块的替换

最后的话

我去年(2020)年 2 月 3 号购买的《设计模式之美》专栏,将近一年半才把它学习完,再回看之前的代码,真是一堆垃圾。之前一天写完的代码,重构差不多花了一个星期,重构之后,感觉还可以再重构的更好,似乎无止境,正如小争哥说的那样,项目无论大小,都可以有技术含量,所谓代码细节是魔鬼,细节到处都存在在取舍,应该怎么样,不应该怎么样,都大有学问。

以上就是Python编程应用设计原则详解的详细内容,更多关于Python编程应用设计原则的资料请关注我们其它相关文章!

时间: 2021-09-14

详解设计模式中的工厂方法模式在Python程序中的运用

工厂方法(Factory Method)模式又称为虚拟构造器(Virtual Constructor)模式或者多态工厂(Polymorphic Factory)模式,属于类的创建型模式.在工厂方法模式中,父类负责定义创建对象的公共接口,而子类则负责生成具体的对象,这样做的目的是将类的实例化操作延迟到子类中完成,即由子类来决定究竟应该实体化哪一个类. 在简单工厂模式中,一个工厂类处于对产品类进行实例化的中心位置上,它知道每一个产品类的细节,并决定何时哪一个产品类应当被实例化.简单工厂模式的优点是能

对Python函数设计规范详解

Python函数的设计规范 1.Python函数设计时具备耦合性和聚合性 1).耦合性: (1).尽可能通过参数接受输入,以及通过return产生输出以保证函数的独立性: (2).尽量减少使用全局变量进行函数间通信: (3).不要在函数中直接修改可变类型的参数: (4).避免直接改变定义在另外一个模块中的变量: 2).聚合性: (1).每个函数都应该有一个单一的.目的统一的目标: (2).每个函数的功能都应该相对简单: 2.Python函数在脚本中应用示例 例1:将/etc/passwd文件中的

Python PO设计模式的具体使用

无规矩不成方圆.编写代码也是,如果没有大概的框架,管理代码将会是一件很头疼的事. 先看看笔者以前写的python脚本: 如果只有一个用例,这样看着好像挺整洁的.但是当用例越来越多后,如果元素定位发生了改变,那你将要在多个类.多个方法中,去寻找那个元素,然后一个一个修改,这将耗费很多时间. 引入PO设计模式后,管理代码将会很轻松. 什么是PO设计模式? PO设计模式是一种业务流程与页面元素操作分离的模式:这意味着,当UI发生变化,元素定位发生变化时,只需要在一个地方修改即可. 下面是代码目录: 页

Python的设计模式编程入门指南

有没有想过设计模式到底是什么?通过本文可以看到设计模式为什么这么重要,通过几个Python的示例展示为什么需要设计模式,以及如何使用. 设计模式是什么? 设计模式是经过总结.优化的,对我们经常会碰到的一些编程问题的可重用解决方案.一个设计模式并不像一个类或一个库那样能够直接作用于我们的代码.反之,设计模式更为高级,它是一种必须在特定情形下实现的一种方法模板.设计模式不会绑定具体的编程语言.一个好的设计模式应该能够用大部分编程语言实现(如果做不到全部的话,具体取决于语言特性).最为重要的是,设计模

python的socket编程入门

Flask或者其他框架都是封装的比较完善,我们可以不去关注路由.SESSION等到底是怎么实现的,现在我们使用socket来实现一个带有注册.登录功能的To do网站,这样能对后端框架了解的稍微更深入一点(当然你也可以直接去看Flask源码). 主程序runserver.py代码如下: #!/usr/bin/env python # -*- coding:utf-8 -*- import socket from exts import Request from route import resp

Python Tkinter GUI编程入门介绍

一.Tkinter介绍 Tkinter是一个python模块,是一个调用Tcl/Tk的接口,它是一个跨平台的脚本图形界面接口.Tkinter不是唯一的python图形编程接口,但是是其中比较流行的一个.最大的特点是跨平台,缺点是性能不太好,执行速度慢. 一般使用Tkinter的方法是: From Tkinter import * 或者: import Tkinter 两者的区别我们前面讲模块的时候已经说过了.   二.Tkinter的使用 先看一下GUI程序的开发,熟悉MFC的朋友应该不会陌生.

最基础的Python的socket编程入门教程

本文介绍使用Python进行Socket网络编程,假设读者已经具备了基本的网络编程知识和Python的基本语法知识,本文中的代码如果没有说明则都是运行在Python 3.4下. Python的socket功能封装在socket库中,要使用socket,记得先import socket,socket库的详细介绍参见官方文档. 创建Socket 首先创建一个socket,使用socket库中得socket函数创建. import socket # create an INET, STREAM soc

Apache Camel的Java编程入门指南

Apache Camel是一个非常实用的规则引擎库,能够用来处理来自于不同源的事件和信息.你可以在使用不同的协议比如VM,HTTP,FTP,JMS甚至是文件系统中来传递消息,并且让你的操作逻辑和传递逻辑保持分离,这能够让你更专注于消息的内容. 在这篇文章中,我将提供一个Java语言(非Groovy)的Apache Camel入门演示. 首先创建一个Maven项目的pom.xml. <?xml version="1.0" encoding="UTF-8"?>

使用简单工厂模式来进行Python的设计模式编程

计模式的目的是让代码易维护.易扩展,不能为了模式而模式,因此一个简单的工具脚本是不需要用到任何模式的. 简单工厂模式又叫静态工厂方法模式,工厂模式家族中最简单的一种模式.这个模式的基本工作方式: 通过一个工厂来决定创建哪种具体的产品实例. 下面是一个简单的工厂实例: def create_animal(name): if name == 'dog': return Dog() elif name == 'cat': return Cat() animal = create_animal('dog

Python Socket编程入门教程

这是用来快速学习 Python Socket 套接字编程的指南和教程.Python 的 Socket 编程跟 C 语言很像. Python 官方关于 Socket 的函数请看 http://docs.python.org/library/socket.html 基本上,Socket 是任何一种计算机网络通讯中最基础的内容.例如当你在浏览器地址栏中输入 www.jb51.net 时,你会打开一个套接字,然后连接到 www.jb51.net 并读取响应的页面然后然后显示出来.而其他一些聊天客户端如

Python编程入门之Hello World的三种实现方式

本文实例讲述了Python编程入门之Hello World的三种实现方式.分享给大家供大家参考,具体如下: 第一种方式: $python >>>print('hello world') 屏幕上输出hello world print是一个常用函数 第二种方式: 复制代码 代码如下: $python hello.py 第三种方式: #!/usr/bin/env python chmod 755 hello.py ./hello.py 希望本文所述对大家Python程序设计有所帮助.

ColdFusionMX 编程指南 ColdFusionMX编程入门

第三期:ColdFusionMX编程入门 序言 上一期我们讲解了ColdFusionMX的基本管理操作,并且熟悉了ColdFusionMX的管理界面布局,而且上一期最后我们演示了两个非常短小的coldfusion程序,这一期会详细讲解coldfusion的入门编程,其中包括在asp中对于初学者而言非常令人头疼的数据库操作. 在每次开始进入正题之前,每一期的序言内容都会为大家介绍一些关于ColdFusion发展或者其他一些具有价值的小知识,第一期为大家介绍了Macromedia MX产品的策略和c

Python语法快速入门指南

Python语言与Perl,C和Java等语言有许多相似之处.但是,也存在一些差异. 在本章中我们将来学习Python的基础语法,让你快速学会Python编程. 第一个Python程序 交互式编程 交互式编程不需要创建脚本文件,是通过 Python 解释器的交互模式进来编写代码. linux上你只需要在命令行中输入 Python 命令即可启动交互式编程,提示窗口如下: $ python Python 2.7.6 (default, Sep 9 2014, 15:04:36) [GCC 4.2.1