接口倒置原则的最简化python实践
在编程的SOLID 5大设计原则中,有人说最后一条接口倒置原则(Dependency Inverse Principle)是最重要的一条(也叫依赖倒置原则),因为他描述了代码如何通过抽象接口层实现松解耦和模块化,今天就来用python尝试实现以下最简化的接口倒置原则,体验一下接口倒置原则的道理。
简单来说,面向接口编程,实现依赖于接口,接口不要依赖于实现。(或者说细节依赖于抽象,抽象不要依赖与细节)。个人理解:尽可能减少每个模块对其它模块的依赖,尽可能只依赖其它模块的接口,而不是依赖于细节,这样我们修改细节后者增加细节的时候就不会破坏原来的大框架。有利于模块化和可扩展性。
以吃货喜欢的蛋糕作为例子,我们来写一个最简单的蛋糕类,这个蛋糕有两个属性,分别是:
- 形状属性:表示蛋糕的形状,比如三角形或者四边形
- 食材属性:表示蛋糕的材料组分,这里简化为某种脂状物 (cream) ,比如黄油cream或者芝士cream
蛋糕类的总体架构设计
这里我们把蛋糕的两个属性抽象为shape属性和cream属性,对应两个抽象接口类Shape类和Cream类,这两个接口类放在最底层(图中越往上依赖度越低对应的层级越低),这两个接口类不依赖于任何其它模块。对于形状Shape接口类,可以实现为具体的类比如三角形Triangle或者长方形Rectangle, 而Cream类则可以实现为具体的黄油Butter类或者芝士Cheese类。上面的类图可以分为两层:
- 第0层:抽象接口类Shape和Cream, 这两个接口类放在最底层(上图中最上面的一层),无其它依赖,同时Shape和Cream之间互不相干
- 第1层:同层的5个类(上图中同高度的5块)Cake, Triangle, Rectangle, Butter 和 Cheese之间完全独立,互不相干。
- 其中蛋糕类依赖与前面的两个接口类Shape和Cream, 分别描述了蛋糕的形状属性和食材属性
- 三角形类Triangle和长方形类Rectangle实现(继承)了作为抽象接口的形状类Shape, 使形状得到具象化
- 黄油类Butter和芝士类Cheese实现(继承)了作为抽象接口的Cream类,使得脂状物Cream具象化为具体的黄油cream或者芝士cream.
这种设计方式实现了解耦合,提高了扩展性,如果我们想对Shape类进行扩展,提供更多形状,比如增加六边形形状,那么只需增加一个六边形类继承形状类,而形状类对应的代码无需修改,Cake类的代码也无需修改,因此扩展起来非常方便。
通常还有一个主模块需要依赖上述所有模块,比如会有一个主程序main, 这个main函数会依赖于上述的所有模块。这个main函数相当于给蛋糕注入灵魂,把芝士和三角形状赋给蛋糕,实际构造出一个三角形的芝士蛋糕对象,如下图所示:
代码的文件结构
这里用Python来实现上述的类图结构,对应的文件结构如下图所示:
- 其中cream/文件夹中的cream_base.py 文件写了CreamBase抽象接口类,cream文件夹中的butter.py和cheese.py则分别继承实现了基类CreamBase;
- shape/文件夹中的shape_base.py文件写了ShapeBase抽象接口类,文件夹中的另外两个文件triangle.py和rectangle.py则依赖了shape_base.py, 分别实现了Triangle类和Rectangle类来继承和实现ShapeBase类。
- cake.py文件通过依赖 cream/cream_base.py 和 shape/shape_base.py 这两个文件来依赖CreamBase 和 ShapeBase 这两个接口类;但是对具体的实现类没有依赖,即没有依赖于具体的Butter, Cheese, Triangle, Rectangle等类。
- main.py 对其它所有文件都有依赖。
组成类:形状类设计
形状类放在 shape/ 文件夹中,基类 shape_base.py文件内容为:
import abc
class ShapeBase(abc.ABC):
@abc.abstractmethod
def __init__(self):
pass
@abc.abstractmethod
def cornersNumber(self):
pass
其中通过 python 的abc模块定义了一个抽象接口类ShapeBase, 继承这个抽象类的其它类都需要实现里面的方法(其中conersNumber方法表示形状中有多少个角,比如三角形有3个角,四边形有4个角)。
继承ShapeBase的Trangle类写在 shape/triangle.py文件中,文件内容为:
from shape.shape_base import ShapeBase
class Triangle(ShapeBase):
def __init__(self):
pass
def cornersNumber(self):
return 3
继承ShapeBase的 Rectangle 类写在 shape/rectangle.py 文件中,内容为:
from shape.shape_base import ShapeBase
class Rectangle(ShapeBase):
def __init__(self):
pass
def cornersNumber(self):
return 4
_init_.py 文件为:
import sys
sys.path.append("./shape")
from triangle import Triangle
from rectangle import Rectangle
组成类:cream类设计
cream的基类放在文件 cream/cream_base.py 文件中,内容为:
import abc
class CreamBase(abc.ABC):
@abc.abstractmethod
def __init__(self):
pass
@abc.abstractmethod
def color(self):
pass
继承CreamBase 的 Butter 类放在 cream/butter.py 文件中,内容为:
from cream_base import CreamBase
class Butter(CreamBase):
def __init__(self):
pass
def color(self):
return "yellow"
继承CreamBase 的 Cheese 类放在 cream/cheese.py 文件中,内容为:
from cream_base import CreamBase
class Cheese(CreamBase):
def __init__(self):
pass
def color(self):
return "white"
_init_.py 文件为:
import sys
sys.path.append("./cream")
from cheese import Cheese
from butter import Butter
依赖接口类的蛋糕类
依赖接口类的蛋糕类放在 cake.py 中,内容为:
from cream.cream_base import CreamBase
from shape.shape_base import ShapeBase
class Cake(object):
def __init__(self, shape: ShapeBase, cream: CreamBase):
self._shape = shape
self._cream = cream
def cornersNumber(self):
return self._shape.cornersNumber()
def color(self):
return self._cream.color()
运行的主程序 main.py 为:
from cake import Cake
from cream import *
from shape import *
if __name__ == "__main__":
cake1 = Cake(Rectangle(), Butter())
cake2 = Cake(Triangle(), Cheese())
print(
f"cake1.color() = {cake1.color()}, cake1.cornersNumber() = {cake1.cornersNumber()}"
)
print(
f"cake2.color() = {cake2.color()}, cake2.cornersNumber() = {cake2.cornersNumber()}"
)
主要思路和好处
时间所限,后面再来补充