当前位置: 首页 > 科技观察

如何在Python中应用设计原则

时间:2023-03-14 17:53:15 科技观察

编写有效的代码很容易,编写有效的代码却很难。什么是易用码?其实代码质量还是比较高的。如何评价代码的质量?最常用和最重要的评估标准是代码的可维护性、可读性、可扩展性和灵活性。、简单性、可重用性和可测试性。好用的代码也会遵循一个原则,就是设计原则,它们是:单一职责原则(SRP)开闭原则(OCP)里氏代换原则(LSP)接口隔离原则(ISP)依赖倒置原则(DIP)摘录这五个原则的缩写,就是SOLID原则。下面分别介绍并展示如何在Python中应用它们。一、单一职责原则SRP单一职责原则(SingleResponsibilityPrinciple)该原则的英文描述如下:一个类或模块应该具有单一职责。如果我们翻译成中文,那就是:一个类或模块只负责完成一个职责(或功能)。让我们举一个更简单的例子,我们有一个数字列表L=[n1,n2,…,nx]并且我们计算一些数学函数。例如,计算最大值、平均值等。一个不好的方法是让一个函数完成所有工作:importnumpyasnpdefmath_operations(list_):#ComputeAverageprint(f"themeanis{np.mean(list_)}")#ComputeMaxprint(f"themaxis{np.max(list_)}")math_operations(list_=[1,2,3,4,5])#themeanis3.0#themaxis5在实际开发中,可以认为math_operations很大,有各种函数代码混合。为了让这个更符合单一职责原则,我们首先要做的是将函数math_operations拆分成更细粒度的函数,一个只做一件事的函数:defget_mean(list_):'''ComputeMax'''print(f"themeanis{np.mean(list_)}")defget_max(list_):'''ComputeMax'''print(f"themaxis{np.max(list_)}")defmain(list_):#ComputeAverageget_mean(list_)#ComputeMaxget_max(list_)main([1,2,3,4,5])#themeanis3.0#themaxis5这样做的好处是:易于阅读和调试,更容易定位错误。可复用,代码的任何部分都可以在代码的其他部分复用。可测试,更容易为代码的每个功能创建测试。但是要增加新的功能,比如计算中位数,main函数还是很难维护,所以需要第二个原则:OCP。2.开闭原则OCP开闭原则(OpenClosedPrinciple)对扩展开放,对修改关闭,可以大大提高代码的可维护性,也就是说在增加新功能时,只需要修改新代码即可不加修改地添加对于原来的代码,这样做很简单,而且不会影响之前的单元测试,也不容易出错。即使有错误,也只需要检查新添加的代码即可。通过上面的代码,我们可以通过将我们编写的所有函数子类化到一个类中来解决这个问题。代码如下:importnumpyasnpfromabcimportABC,abstractmethodclassOperations(ABC):'''Operations'''@abstractmethoddefoperation():passclassMean(Operations):'''ComputeMax'''defoperation(list_):print(f"Themeanis{np.mean(list_)}")classMax(Operations):'''ComputeMax'''defoperation(list_):print(f"Themaxis{np.max(list_)}")classMain:'''Main'''defget_operations(list_):#__subclasses__willfoundallclassesinheritingfromOperationsforoperationinOperations.__subclasses__():operation.operation(list_)if__name__=="__main__":Main.get_operations([1,2,3,4,5])#Themeanis3.0#Themaxis5如果现在我们想添加一个新的Operation,例如:median,我们只需要添加一个继承自Operations类的Median类即可。新形成的子类将立即被__subclasses__()拾取,而无需修改代码的任何其他部分。3、里氏??替换原则(LSP)里氏替换原则英文为LiskovSubstitutionPrinciple,简称LSP。这个原则最早是由BarbaraLiskov于1986年提出的。他是这样描述这个原则的:如果S是T的子类型,那么T类型的对象可以被S类型的对象替换,而不会破坏程序。也就是说,子类对象可以替换程序中任何父类对象出现的地方,保证原程序的逻辑行为不变,不破坏正确性。其实李式替换原则还有一个更实用、更有指导意义的描述,就是按照约定来设计。在设计子类时,必须遵守父类的行为约定(或协议)。父类定义了函数的行为契约,子类可以改变函数的内部实现逻辑,但不能改变函数原有的行为契约。这里的行为约定包括:函数声明要实现的功能;关于输入、输出和异常的协议;甚至评论中列出的任何特殊说明。4、接口隔离原则(ISP)接口隔离原则英文翻译为InterfaceSegregationPrinciple,简称ISP。RobertMartin在SOLID原则中将其定义如下:不应强迫客户端依赖于它们不使用的接口。直译成中文就是:客户端不应该被迫依赖它不需要的接口。客户端可以理解为接口的调用者或使用者。例如:fromabcimportABC,abstractmethodclassMammals(ABC):@abstractmethoddefswim(self)->bool:pass@abstractmethoddefwalk(self)->bool:passclassHuman(Mammals):defswim(self)->bool:print("Humanscanswim")returnTruedefwalk(self)->bool:print("Humanscanwalk")returnTrueclassWhale(哺乳动物):defwalk(self)->bool:print("Whalescan'twalk")returnFalsedefswim(self):print("Whalescanswim")returnTruehuman=Human()人类.swim()human.walk()whale=Whale()whale.swim()whale.walk()执行结果:HumanscanswimHumanscanwalkWhalescanswimWhalescan'twalk实际上,子类Whale不应该依赖它不需要的接口walk,因为这种情况下,需要拆分接口,代码如下:fromabcimportABC,abstractmethodclassSwimer(ABC):@abstractmethoddefswim(self)->bool:passclassWalker(ABC):@abstractmethoddefwalk(self)->bool:passclassHuman(游泳者,步行者):defswim(self)->bool:print(“Humanscanswim”)returnTruedefwalk(self)->bool:print(“Humanscanwalk”)returnTrueclassWhale(游泳者):defswim(自我):print("Whalescanswim")returnTruehuman=Human()human.swim()human.walk()whale=Whale()whale.swim()5.依赖倒置原则(DIP)DependencyInversionPrinciple的英文翻译是DependencyInversionPrinciple,缩写为DIP英文说明:High-levelmodulesshouldn'tdependonlow-levelmodules.Bothmodulesshoulddependonabstractions.另外,abstractionsshouldn'tdependeddetails.Detailsdependonabstractions.我们将其翻译成中文,大致意思是:高层模块不依赖低层模块,高层模块和低层模块应该通过抽象相互依赖,另外,抽象不依赖具体的实现细节,具体的实现细节依赖关于抽象。在调用链中,调用者属于高层,被调用者属于低层。我们写的代码属于底层,被框架调用。在正常的业务代码开发中,那里e高层模块依赖低层模块是没有问题的,但是在框架层设计的时候一定要考虑通用性,高层要依赖抽象接口,底层要实现相应的接口。如下图所示:也就是说,ObjectA本来是依赖于ObjectB的,但是为了扩展,可能还有ObjectC和ObjectD,经常变化,所以为了频繁变化,让高层模块依赖抽象接口接口,然后让ObjectB依次依赖接口。这就是依赖倒置的原则。例如,wsgi协议是一个抽象接口。高层模块有uWSGI、gunicorn等,底层模块有Django、Flask等,uWSGI和gunicorn并不直接依赖Django和Flask,而是通过wsgi协议相互依赖。依赖倒置原则的概念是高层模块不依赖于低层模块。看似需要高层模块,其实上面是为了规范低层模块的设计。低层模块提供的接口必须足够抽象和通用,设计时需要考虑高层模块的使用类型和场景。显然,高层模块必须使用低层模块。分层模块具有依赖性。而现在,低层模块需要按照高层模块来设计,这就显得“倒置”了。这样设计有两个好处:底层模块更通用,更适用高层模块不依赖于底层模块的具体实现,方便底层模块的更换最后,我买了去年(2020)2月3号《设计模式之美》专栏,花了将近一年半的时间才学完,回头看之前的代码,真是一堆垃圾。重构前一天写的代码,花了将近一周的时间。重构之后,感觉自己可以重构的更好。似乎无穷无尽。正如肖正所说,无论项目大小,都可以有技术含量,所谓代码细节就是魔鬼,细节处处处取舍。什么该做,什么不该做,很有学问。本文转载自微信公众号“Python7号”,可通过以下二维码关注。转载本文请联系Python7号公众号。