SOLID 原则:掌握面向对象设计,编写整洁可维护代码
SOLID 是面向对象编程中类设计的助记首字母缩略词。这些原则建立实践,帮助养成良好编程习惯和可维护代码。
从长远考虑代码维护和扩展性,SOLID 原则丰富敏捷代码开发环境。核算并优化代码依赖有助于创建更直观、有序的软件开发生命周期。
什么是 SOLID 原则?
SOLID 代表一套设计类的原则。Robert C. Martin(Uncle Bob)提出了大多数设计原则并创造了该首字母缩略词。
SOLID 代表:
- 单一职责原则
- 开闭原则
- 里氏替换原则
- 接口隔离原则
- 依赖倒置原则
SOLID 原则代表软件设计最佳实践集合。每个想法代表一个设计框架,通向更好编程习惯、改进代码设计、更少错误。
SOLID:5 大原则详解
理解 SOLID 原则如何工作的最佳方式是通过示例。所有原则互补且适用于个别用例。应用原则顺序不重要,并非所有原则适用于每种情况。
以下各节用 Python 编程语言概述每条 SOLID 原则。SOLID 通用思想适用于任何面向对象语言,如 PHP、Java 或 C#。泛化规则使其适用于现代编程方法,如微服务。
单一职责原则 (SRP)
单一职责原则 (SRP) 规定:“类变更的理由永远不应超过一个。”
修改类时,应只改变单一功能,意味每个对象应只有一项工作。
看以下类:
# 多职责类 class Animal: # 属性构造函数 def __init__(self, name): self.name = name # 属性表示 def __repr__(self): return f'Animal(name="{self.name}")' # 数据库管理 def save(animal): print(f'Saved {animal} to the database') if __name__ == '__main__': # 属性实例化 a = Animal('Cat') # 保存属性到数据库 Animal.save(a)
修改 save() 方法时,变更发生在 Animal 类。修改属性时,修改也在 Animal 类。
该类有两个变更理由,违反单一职责原则。即使代码按预期工作,不遵守设计原则长远看会让代码更难管理。
实现单一职责原则,注意示例类有两个不同工作:
- 属性管理(构造函数和
get_name()) - 数据库管理
(save())
因此解决方法是将数据库管理方法分离到新类。例如:
# 负责属性管理的类 class Animal: def __init__(self, name): self.name = name def __repr__(self): return f'Animal(name="{self.name}")' # 负责数据库管理的类 class AnimalDB: def save(self, animal): print(f'Saved {animal} to the database') if __name__ == '__main__': # 属性实例化 a = Animal('Cat') # 数据库实例化 db = AnimalDB() # 保存属性到数据库 db.save(a)
应用单一职责原则后,修改 AnimalDB 类不影响 Animal 类。代码直观且易修改。
开闭原则 (OCP)
开闭原则 (OCP) 规定:“软件实体应对扩展开放,对修改关闭。”
向系统添加功能和用例不应要求修改现有实体。措辞似矛盾——添加新功能需要改现有代码。
通过以下示例易于理解:
class Animal: def __init__(self, name): self.name = name def __repr__(self): return f'Animal(name="{self.name}")' class Storage: def save_to_db(self, animal): print(f'Saved {animal} to the database')
Storage 类将 Animal 实例信息保存到数据库。添加新功能(如保存到 CSV 文件)需向 Storage 类添加代码:
class Animal: def __init__(self, name): self.name = name def __repr__(self): return f'Animal(name="{self.name}")' class Storage: def save_to_db(self, animal): print(f'Saved {animal} to the database') def save_to_csv(self,animal): printf(f'Saved {animal} to the CSV file')
save_to_csv 方法修改现有 Storage 类添加功能。此法违反开闭原则,因新功能出现时改变现有元素。
代码需移除通用 Storage 类,为特定文件格式存储创建独立类。
以下代码演示开闭原则应用:
class DB(): def save(self, animal): print(f'Saved {animal} to the database') class CSV(): def save(self, animal): print(f'Saved {animal} to a CSV file')
代码符合开闭原则。完整代码如下:
class Animal: def __init__(self, name): self.name = name def __repr__(self): return f'"{self.name}"' class DB(): def save(self, animal): print(f'Saved {animal} to the database') class CSV(): def save(self, animal): print(f'Saved {animal} to a CSV file') if __name__ == '__main__': a = Animal('Cat') db = DB() csv = CSV() db.save(a) csv.save(a)
扩展额外功能(如保存到 XML 文件)不修改现有类。
里氏替换原则 (LSP)
里氏替换原则 (LSP) 规定:“使用基类指针或引用的函数,必须能在不知情情况下使用派生类对象。”
原则规定父类可替换子类而功能无任何明显变化。
看文件写入示例:
# 父类 class FileHandling(): def write_db(self): return f'Handling DB' def write_csv(self): return f'Handling CSV' # 子类 class WriteDB(FileHandling): def write_db(self): return f'Writing to a DB' def write_csv(self): return f"Error: Can't write to CSV, wrong file type." class WriteCSV(FileHandling): def write_csv(self): return f'Writing to a CSV file' def write_db(self): return f"Error: Can't write to DB, wrong file type." if __name__ == "__main__": # 父类实例化和函数调用 db = FileHandling() csv = FileHandling() print(db.write_db()) print(db.write_csv()) # 子类实例化和函数调用 db = WriteDB() csv = WriteCSV() print(db.write_db()) print(db.write_csv()) print(csv.write_db()) print(csv.write_csv())
父类 (FileHandling) 包含写入数据库和 CSV 文件两个方法。类处理两功能并返回消息。
两个子类 (WriteDB 和 WriteCSV) 继承父类 (FileHandling) 属性。但两子类尝试使用不当写入函数时抛出错误,违反里氏替换原则,因重写函数不对应父函数。
以下代码解决问题:
# 父类 class FileHandling(): def write(self): return f'Handling file' # 子类 class WriteDB(FileHandling): def write(self): return f'Writing to a DB' class WriteCSV(FileHandling): def write(self): return f'Writing to a CSV file' if __name__ == "__main__": # 父类实例化和函数调用 db = FileHandling() csv = FileHandling() print(db.write()) print(csv.write()) # 子类实例化和函数调用 db = WriteDB() csv = WriteCSV() print(db.write()) print(csv.write())
子类正确对应父函数。
接口隔离原则 (ISP)
接口隔离原则 (ISP) 规定:“多个客户端专用接口优于单一通用接口。”
换言之,更大交互接口拆分为更小接口。原则确保类仅使用所需方法,减少整体冗余。
以下示例演示通用接口:
class Animal(): def walk(self): pass def swim(self): pass class Cat(Animal): def walk(self): print("Struts") def fly(self): raise Exception("Cats don't swim") class Duck(Animal): def walk(self): print("Waddles") def swim(self): print("Floats")
子类继承父 Animal 类,包含 walk 和 fly 方法。虽两函数对某些动物可接受,但有些动物有冗余功能。
处理方法是将接口拆分为更小部分。例如:
class Walk(): def walk(self): pass class Swim(Walk): def swim(self): pass class Cat(Walk): def walk(self): print("Struts") class Duck(Swim): def walk(self): print("Waddles") def swim(self): print("Floats")
Fly 类继承 Walk,为合适子类提供额外功能。示例满足接口隔离原则。
添加另一类动物(如鱼)需进一步原子化接口,因鱼不能走路。
依赖倒置原则 (DIP)
依赖倒置原则规定:“依赖抽象,不依赖具体。”
原则旨在通过添加抽象层减少类间耦合。将依赖移至抽象使代码健壮。
以下示例演示无抽象层的类依赖:
class LatinConverter: def latin(self, name): print(f'{name} = "Felis catus"') return "Felis catus" class Converter: def start(self): converter = LatinConverter() converter.latin('Cat') if __name__ == '__main__': converter = Converter() converter.start()
示例有两个类:
LatinConverter用假想 API 获取动物拉丁名(为简化硬编码 “Felis catus”)Converter是使用LatinConverter实例及其函数转换提供名称的高层模块。Converter严重依赖LatinConverter类,后者依赖 API。此法违反原则。
依赖倒置原则要求在两类间添加抽象接口层。
示例解决方案如下:
from abc import ABC class NameConverter(ABC): def convert(self,name): pass class LatinConverter(NameConverter): def convert(self, name): print('Converting using Latin API') print(f'{name} = "Felis catus"') return "Felis catus" class Converter: def __init__(self, converter: NameConverter): self.converter = converter def start(self): self.converter.convert('Cat') if __name__ == '__main__': latin = LatinConverter() converter = Converter(latin) converter.start()
Converter 类现在依赖 NameConverter 接口而非直接依赖 LatinConverter。未来更新允许通过 NameConverter 接口用不同语言和 API 定义名称转换。
为何需要 SOLID 原则?
SOLID 原则帮助对抗设计模式问题。SOLID 原则总体目标是减少代码依赖,添加新功能或修改代码部分不破坏整个构建。
应用 SOLID 原则于面向对象设计结果,代码变得更易理解、管理、维护和修改。因规则更适合大型项目,应用 SOLID 原则提升整体开发生命周期速度和效率。
SOLID 原则仍具相关性吗?
虽 SOLID 原则超 20 年,仍为软件架构设计提供良好基础。SOLID 提供适用于现代程序和环境的合理设计原则,不仅限于面向对象编程。
SOLID 原则适用于代码由人编写修改、组织成模块、包含内部或外部元素的场景。
结论
SOLID 原则为软件架构设计提供良好框架和指南。本指南示例显示即使像 Python 这样动态类型语言,应用原则于代码设计也获益。
接下来,阅读 9 大 DevOps 原则,帮助团队最大化 DevOps 价值。
云计算
- 摩擦学大师课:轴承设计与性能的核心原理
- 高速数据密集型应用中稳健互连的五大设计原则
- C# static 关键字:静态变量、方法与类完全掌握
- 掌握 C# 继承:概念、类型与实战代码完全指南
- 理解 C# 嵌套类:定义、访问与继承实战
- C++ 类模板实战指南:编写灵活的类型无关代码
- C# 继承完全指南:基类、派生类、初始化与多重继承替代方案
- C# 多态完全指南:静态多态与动态多态(重载、重写、虚方法)
- 精通 Java 匿名内部类:定义、语法与实战示例
- 掌握 Java ObjectOutputStream:序列化、核心方法与实战示例
- 掌握 Java 泛型:构建可复用、类型安全的代码
- 掌握 Java 文件操作:java.io 包下的创建、读取、写入与删除