前言
自己在写一些工程的时候,发现各种模块要重写很多次,感觉自己“思路不清晰”…毕竟自己不是科班出身,实际干起活就感觉差点东西。
后来在网上学习的时候,发现了一些有关“面向对象编程的设计原则”的文章。小看一眼,感觉灵光一现,好东西!
然而网上大多数教程都是用Java来写的,这里就使用python来重写一下,顺便理理自己的思路。
定义
这里就照搬了:
1、上层模块不应该依赖底层模块,它们都应该依赖于抽象。
2、抽象不应该依赖于细节,细节应该依赖于抽象。
High level modules should not depend upon low level modules. Both should depend upon abstractions.Abstractions should not depend upon details. Details should depend upon abstractions.
什么是依赖
假设A类的变化引起了B类的变化,则说名B类依赖于A类。
浅显点的理解
按照上面的定义,抽象类应该是最大的,所有属性或者实例的变更,都不能去影响这个抽象类。写代码的时候,各种类应该由抽象类派生出来,然后依照抽象类对其进行再加工或者是继承之类的。
一个案例
先看把目光看向一个披萨店,按照我们在现实中的思维来想:先有披萨店,然后才有顾客来买披萨,然后由披萨店产出了披萨,大概如下图所示:
图中解释的会有些抽象,具体来说是这样:
因为是由披萨店产出的披萨,披萨归属于披萨店所有,也就是说披萨是披萨店的一部分。那么按照“假设A类的变化引起了B类的变化,则说名B类依赖于A类”的原则,披萨的变动就是披萨店的变动。
这种设计放在编程中就比较戏剧性了,负责设计披萨的程序员新增一种披萨,结果披萨店要因此装修改动一番?!
因此就出现了“依赖倒置原则”,在本案例中,如果按照本原则进行设计,呈现的效果会如下图所示:
按照定义里的话来解释,披萨是一种“抽象”,各种各样的披萨由该抽象类衍生。比如定义了个抽象披萨类,有两个具体属性:下层面饼,上层食料。其他的披萨,比如芝士披萨,海鲜披萨,都需要实现下层面饼,和上层食料各是什么样的。
而披萨店这个类,实现一个出“抽象”披萨的方法,这样一来由披萨设计程序员设计的任何披萨类,都能直接由披萨店产出,而无需对披萨店进行任何改动。
Python代码实现
如果按照现实思路来设计代码的话,代码如下所示
class PizzaStore:
def __init__(self):
# 按照我们的想法先设计一个披萨店
print('披萨店构建完成咯')
self.store = [] # 假设这是一个餐车吧,做完餐就加入这个列表
def _make_CheesePizza(self):
# 私有方法,制作一个CheesePizza,不让外部调用
self.store.append(CheesePizza())
def order_CheesePizza(self):
self._make_CheesePizza() # 接到点单,启动做披萨函数,餐车披萨+1
return self.store.pop() # 然后从餐车弹出披萨给顾客
def _make_SeafoodPizza(self):
# 可以看出每新增一个披萨种类,都要设计一组新函数
self.store.append(SeafoodPizza())
def order_SeafoodPizza(self):
self._make_SeafoodPizza()
return self.store.pop()
class CheesePizza:
def __init__(self):
self.name = '芝士披萨'
print('做出了个 芝士披萨')
class SeafoodPizza:
def __init__(self):
self.name = '海鲜披萨'
print('做出了个 海鲜披萨')
if __name__ == '__main__':
# 先出现个披萨店
pizza_store = PizzaStore()
pizza = pizza_store.order_CheesePizza()
print(f'拿到手的是{pizza.name}')
pizza = pizza_store.order_SeafoodPizza()
print(f'拿到手的是{pizza.name}')
上述代码运行结果如下
披萨店构建完成咯
做出了个 芝士披萨
拿到手的是芝士披萨
做出了个 海鲜披萨
拿到手的是海鲜披萨
很显然这样设计披萨店实在是太繁琐了,维护成本炸裂。所以运用依赖倒置原则:
from abc import ABC, abstractmethod #
class Pizza(ABC):
# Pizza继承了ABC(Abstract Class),成为了抽象类,任何继承该类的子类都应该实现对应的方法
def __init__(self):
pass
@abstractmethod
def get_name(self) -> str:
# 这里就简单点,任何披萨都应该有一个名字,将其返回
pass
class CheesePizza(Pizza):
def __init__(self):
super().__init__()
self.name = '芝士披萨'
print(f'做出了个 {self.name}')
def get_name(self) -> str:
return self.name
class SeafoodPizza(Pizza):
def __init__(self):
super().__init__()
self.name = '海鲜披萨'
print(f'做出了个 {self.name}')
def get_name(self) -> str:
return self.name
class PizzaStore:
def __init__(self, menu):
# 先有了披萨,那根据披萨这个抽象类设计整个PizzaStore的架构
# 由于不能依赖具体的披萨的披萨架构,因此披萨店就根据一份菜单来进行披萨的生产,menu是一个字典
print('披萨店构建完成咯')
self.menu = menu
def _make_pizza(self, pizza_name):
# 私有方法,按照菜单,制作一个Pizza
return self.menu[pizza_name]() # 这里加括号是要将其实例化
def order_pizza(self, pizza_name):
return self._make_pizza(pizza_name)
if __name__ == '__main__':
# 根据现有的披萨设计一份菜单
my_menu = {'芝士披萨': CheesePizza, '海鲜披萨': SeafoodPizza}
# 出现披萨店,披萨店的运营需要一份菜单
pizza_store = PizzaStore(my_menu)
# 开始点单
pizza = pizza_store.order_pizza('海鲜披萨')
print(f'拿到手的是{pizza.get_name()}')
上述代码运行后的结果就是
披萨店构建完成咯
做出了个 海鲜披萨
拿到手的是海鲜披萨
显然可维护性高了不少。
参考
文章评论