前言:
每个逻辑的实现都是由原子逻辑组成的,不可分割的原子逻辑称为低层模块,在 Python 中用抽象类来表示,由原子逻辑组装而成的就是高层模块,抽象类是不能被实例化的。细节就是实现类,通过继承抽象类而产生的类就是细节,是可以被直接实例化的。一言以蔽之,采用依赖倒置原则可以减少类间的耦合性,提高系统的稳定性,提高代码的可读性和可维护性。依赖倒置原则包含如下三层含义:
1、高层模块(由原子逻辑组装而成)不应该依赖低层模块(不可分割的原子逻辑),二者都应该依赖抽象
2、抽象(抽象类)不应该依赖细节(实现类)
3、细节应该依赖抽象
依赖倒置原则究竟倒置在哪里?
“倒置”指的是和一般面向对象的代码的设计思考方式完全相反。举个例子,假设现在有建设一个汽车城,首先会想到是这里会卖很多品牌的汽车。如奔驰,宝马,奥迪,沃尔沃等。汽车城是上层模块,汽车是下层模块。如图所示:
modb_20211009_35bbc080-2858-11ec-abb4-fa163eb4f6be.jpg
利用上图模块间依赖关系设计的代码,当每新增一个品牌的汽车(低层模块),汽车城(高层模块)就多了一个新的依赖;汽车城(高层模块)依赖所有品牌的汽车(低层模块),因为它直接创建汽车;对于不同品牌的汽车(低层模块)具体实现的任何改变都会影响到汽车城(高层模块),这就违背了依赖倒置原则。根据依赖倒置原则,高层模块不应该依赖低层模块,两者都应该依赖抽象。也就是说汽车城这个高层模块是不应该依赖汽车品牌这个低层模块,所以代码设计思路应该从下层模块开始思考并对其进行抽象。如下图所示:
modb_20211009_35f5287a-2858-11ec-abb4-fa163eb4f6be.jpg
这样就很好的解释了 “上层模块不应该依赖底层模块,它们都应该依赖于抽象”这一概念,在最开始的设计中,高层模块(汽车城)直接依赖低层模块(不同品牌的车),调整设计后高层模块和低层模块都依赖于抽象(小汽车)。
依赖倒置原则应用
先不考虑依赖倒置原则,假设有如下类图所描绘的场景
modb_20211009_36267114-2858-11ec-abb4-fa163eb4f6be.jpg
由以上类图所描绘的场景可知,司机类和奔驰类都属于细节,并没有实现或继承抽象。它们是对象级别的耦合。通过类图可知司机类 Driver 有一个用来开车的 drive() 方法,Benz 类有一个表示车辆运行的 run() 方法,并且奔驰车类 Benz 依赖于司机类 Driver,用户模块 Client 是一个高层模块,负责调用司机类 Driver 和奔驰类 Benz。未采用依赖倒置原则的代码如下:
class Driver:
def __init__(self, name):
self.name = name
def drive(self, benz):
print("%s is driving a benz" % self.name)
benz.run()
class Benz:
def __init__(self, name = 'benz'):
self.name = name
def run(self):
print("%s is running ..." % self.name)
class BMW:
def __init__(self, name="bmw"):
self.name = name
def run(self):
print("%s is running..." % self.name)
if __name__ == "__main__":
d = Driver("张三")
benz = Benz()
d.drive(benz)
bmw = BMW()
d.drive(bmw)
这是最不灵活的写法,如果驾驶员的车库中新增了一辆 bmw,新车无法上路。继续修改代码如下:
class Driver:
def __init__(self, name):
self.name = name
def drive(self, car):
print("%s is driving a %s" % (self.name, car.name))
car.run()
class Benz:
def __init__(self, name="benz"):
self.name = name
def run(self):
print("%s is running ..." %self.name)
class BMW:
def __init__(self, name="bmw"):
self.name = name
def bmw_run(self):
print("%s is running ..." % self.name)
if __name__ == "__main__":
d = Driver("张三")
benz = Benz()
d.drive(benz)
print("#" * 20)
bmw = BMW()
d.drive(bmw)
以上代码中,如果 benz 或 bmw 的运行方法不是 run,那么将会报错。此时需要将 Car 类的run方法进行抽象,来对子类进行约束。上面代码的设计没有使用依赖倒置原则,我们已经郁闷的发现,模块与模块之间耦合度太高,生产力太低,只有需求一变就会面临代码的大面积重构。现在引入依赖倒置原则,重新设计的类图如下:
modb_20211009_372f38ca-2858-11ec-abb4-fa163eb4f6be.jpg
from abr import ABCMeta, abstractmethod
class Driver:
__metaclass__ = ABCMeta
def __init__(self, name):
self.name = name
@abstarctmethod
def drive(self, car):
pass
class Car:
__metaclass__ = ABCMeta
def __init__(self, name):
self.name = name
@abstractmethod
def run(self):
pass
class BenzCar(Car):
def run(self):
print("%s is running ..." % self.name)
class BmwzCar(Car):
def run(self):
print("%s is running ..." % self.name)
class CarDrive(Driver):
def drive(self, car):
print("%s is driving a car ..." % self.name)
car.run()
if __name__ == "__main__":
rs = CarDrive("张三")
bz = BenzCar("奔驰")
bm = BmwCar("宝马")
rs.drive(bz)
rs.drive(bm)
网友评论