开闭原则
861字约3分钟
设计模式
2022-07-11
定义
原始定义: Software entities (classes, modules, functions) should be open for extension but closed for modification.
- 对扩展开放, 意味着有新的需求或变化时, 可以对现有代码进行扩展, 以适应新的情况.
- 对修改封闭, 意味着类一旦设计完成, 就可以独立完成其工作, 而不要对已有代码进行任何修改.
作用
开闭原则是面向对象程序设计的终极目标, 它使软件实体拥有一定的适应性和灵活性的同时具备稳定性和延续性. 具体来说, 其作用如下.
对软件测试的影响: 软件遵守开闭原则的话, 软件测试时只需要对扩展的代码进行测试就可以了, 因为原有的测试代码仍然能够正常运行.
可以提高代码的可复用性: 粒度越小, 被复用的可能性就越大;在面向对象的程序设计中, 根据原子和抽象编程可以提高代码的可复用性.
可以提高软件的可维护性: 遵守开闭原则的软件, 其稳定性高和延续性强, 从而易于扩展和维护.
实现方法
可以通过"抽象约束、封装变化"来实现开闭原则, 即通过接口或者抽象类为软件实体定义一个相对稳定的抽象层, 而将相同的可变因素封装在相同的具体实现类中.
因为抽象灵活性好, 适应性广, 只要抽象的合理, 可以基本保持软件架构的稳定. 而软件中易变的细节可以从抽象派生来的实现类来进行扩展, 当软件需要发生变化时, 只需要根据需求重新派生一个实现类来扩展就可以了.
建议: 用抽象基类与多态的使用来遵循开闭原则以提高扩展性与可重用性 例子: 面对新需求, 我们的选择: 实现类(单独实现新需求), 修改原类(改getPrice功能函数、新增getOffPrice功能函数). 推荐的方法是: 增加一个子类OffNovelBook, 覆写getPrice方法.
示例
反例: 一个扩展性不足的电影搜索功能
class Movie:
# Setting up attributes
def __init__(self, name, genre):
self.name = name
self.genre = genre
class MovieBrowser:
# Movie filter by Name
def search_movie_by_name(self, movies, name):
return [movie for movie in movies if movie.name == name]
# Movie filter by Genre
def search_movie_by_genre(self, movies, genre):
return [movie for movie in movies if movie.genre == genre]
正例: 多态+抽象类
from abc import ABC, abstractmethod
class Movie:
# Setting up attributes
def __init__(self, name, genre):
self.name = name
self.genre = genre
# Abstract base class 定义抽象基类, 以@abc.abstractmethod装饰器将它们装饰为抽象方法
class SearchBy(ABC):
@abstractmethod
def is_matched(self, movie):
pass
# Overloaded and operator
def __and__(self, other):
return AndSearchBy(self, other)
# Inheriting SearchBy and Filter on Genre(流派)
class SearchByGenre(SearchBy):
def __init__(self, genre):
self.genre = genre
def is_matched(self, movie):
return movie.genre == self.genre
# Inheriting SearchBy and Filter on Name
class SearchByName(SearchBy):
def __init__(self, name):
self.name = name
def is_matched(self, movie):
return movie.name == self.name
# Added AndSearchBy method without altering existing classes
class AndSearchBy(SearchBy):
def __init__(self, name, release_date):
self.name = name
self.release_date = release_date
def is_matched(self, movie):
return self.name.is_matched(movie) and self.release_date.is_matched(movie)
class MovieBrowser:
def search(self, movies, searchby):
return [movie for movie in movies if searchby.is_matched(movie)]
ige项目中的例子
正面的例子: 不同数据库的适配(dbdrive)
反面的例子: 编解码(解码服务、规则解码)