亿迅智能制造网
工业4.0先进制造技术信息网站!
首页 | 制造技术 | 制造设备 | 工业物联网 | 工业材料 | 设备保养维修 | 工业编程 |
home  MfgRobots >> 亿迅智能制造网 >  >> Industrial Internet of Things >> 云计算

SOLID:面向对象的设计原则

SOLID 是面向对象编程中类设计的助记词首字母缩写词。这些原则制定了有助于培养良好编程习惯和可维护代码的实践。

从长远来看,通过考虑代码维护和可扩展性,SOLID 原则丰富了敏捷代码开发环境。考虑和优化代码依赖关系有助于创建更直接、更有条理的软件开发生命周期。

什么是 SOLID 原则?

SOLID 代表了一组设计类的原则。 Robert C. Martin(鲍勃叔叔)介绍了大多数设计原则并创造了首字母缩略词。
SOLID 代表:

SOLID 原则代表了软件设计最佳实践的集合。每个想法都代表一个设计框架,从而带来更好的编程习惯、改进的代码设计和更少的错误。

SOLID:解释 5 条原则

了解 SOLID 原则如何工作的最佳方式是通过示例。所有原则都是互补的,适用于个别用例。原则的应用顺序并不重要,并非所有原则都适用于所有情况。

下面的每个部分都概述了 Python 编程语言中的每个 SOLID 原则。 SOLID 的一般思想适用于任何面向对象的语言,例如 PHP、Java 或 C#。概括规则使其适用于现代编程方法,例如微服务。

单一职责原则 (SRP)

单一职责原则 (SRP) 规定:“改变班级的理由不应该不止一个。”

当改变一个类时,我们应该只改变一个功能,这意味着每个对象应该只有一个工作。

例如,看下面的类:

# A class with multiple responsibilities
class Animal:
    # Property constructor
    def __init__(self, name):
        self.name = name

    # Property representation
    def __repr__(self):
        return f'Animal(name="{self.name}")'

    # Database management
    def save(animal):
        print(f'Saved {animal} to the database')

if __name__ == '__main__':
    # Property instantiation
    a = Animal('Cat')
    # Saving property to a database
    Animal.save(a)

save() 进行任何更改时 方法,更改发生在 Animal 班级。进行属性更改时,修改也发生在 Animal 类。

类有两个原因改变和违反单一责任原则。即使代码按预期工作,但不遵守设计原则会使代码从长远来看更难管理。

要实现单一职责原则,请注意示例类有两个不同的工作:

因此,解决该问题的最佳方法是将数据库管理方法分离到一个新类中。例如:

# A class responsible for property management
class Animal:
    def __init__(self, name):
        self.name = name
    def __repr__(self):
        return f'Animal(name="{self.name}")'

# A class responsible for database management
class AnimalDB:
    def save(self, animal):
        print(f'Saved {animal} to the database')

if __name__ == '__main__':
    # Property instantiation
    a = Animal('Cat')
    # Database instantiation
    db = AnimalDB()
    # Saving property to a database
    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)

Liskov 替换原则 (LSP) 规定:“使用指向基类的指针或引用的函数必须能够在不知情的情况下使用派生类的对象。”

该原则指出,父类可以替换子类,而功能不会发生任何明显变化。

查看下面的文件写入示例:

# Parent class
class FileHandling():
    def write_db(self):
        return f'Handling DB'
    def write_csv(self):
        return f'Handling CSV'

# Child classes
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__":
   # Parent class instantiation and function calls
    db = FileHandling()
    csv = FileHandling()
    print(db.write_db())
    print(db.write_csv())

    # Children classes instantiations and function calls
    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 )。但是,两个孩子在尝试使用不合适的 write 函数时都会抛出错误,这违反了 Liskov Substitution 原则,因为覆盖函数与父函数不对应。

以下代码解决了问题:

# Parent class
class FileHandling():
    def write(self):
        return f'Handling file'

# Child classes
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__":
   # Parent class instantiation and function calls
    db = FileHandling()
    csv = FileHandling()
    print(db.write())
    print(csv.write())

    # Children classes instantiations and function calls
    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()

该示例有两个类:

依赖倒置原则需要在两个类之间增加一个抽象接口层。

示例解决方案如下所示:

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。


云计算

  1. C# 静态关键字
  2. C# 继承
  3. C# 嵌套类
  4. C++ 类模板
  5. Java 匿名类
  6. Java ObjectOutputStream 类
  7. Java 泛型
  8. Java 文件类
  9. 为数据密集型应用应用稳健互连的 5 条设计原则
  10. C# - 继承
  11. C# - 多态性
  12. 摩擦和轴承设计原理