一,抽象与封装
coding 就是一个抽象的过程:在 OOP 中,我们将对象抽象为类。
- 对象的状态与行为被抽象为类中的属性与方法。
- 抽象的意义,就是为对象之的交互提供高效的接口。
封装就是在 coding 中隐藏数据与逻辑的过程:
- 对象的状态和行为如何组织到类中,模块中的类该如何组织,更高层的代码该如何组织。
- 抽象是多态和封装的基础,封装是接口的具体实现过程。
接口不一定需要由类来实现,只不过封装到类中有利于使用继承、多态、组合这些 OOP 基本特性以实现更高的代码可用性。
二,依赖倒置原则
(一)依赖正置
我们不断在强调 OOP 的软件设计应该考虑到后期可能出现的功能扩展需求,当我们的软件足够复杂之后,代码就会出现高低层次之分。
这个高低层次划分没有明确的界限,但可以肯定的是,绝对存在高层次代码对低层次代码的依赖。
低层次的代码可以是第三方 API,也可以是其他工作组定义的细节实现,高层次的代码通过继承、组合等方式依赖于它们提供的功能。
举个例子🌰:
class FXConverter:
def convert(self, from_currency, to_currency, amount):
print(f'{amount} {from_currency} = {amount * 1.2} {to_currency}')
return amount * 1.2
class App:
def start(self):
converter = FXConverter()
converter.convert('EUR', 'USD', 100)
if __name__ == '__main__':
app = App()
app.start()
>>>
100 EUR = 120.0 USD
这使得高层代码严重依赖于低层代码,虽说不至于达到硬编码那种耦合程度,但在这种依赖“正置”的情况下,面对可能不够稳定的或者不可控的低层代码时,高低层的交互耦合度还是显得有点高。
- 如果低层被破坏,则高层功能无法正确实现。
- 如果高层需要使用其他 API,则需要修改高层本身代码。
(二)依赖倒置原则
我们需要一种方法来进一步降低耦合度,比如就像依赖倒置原则( Dependency Inversion Principle,DIP)要求的这样:
- 高层不应该从低层导入任何东西,两者都应该依赖于抽象(例如,接口)。
- 抽象不应该依赖于细节。细节应该依赖于抽象。
在设计高层模块和低层模块之间的交互时,应该抽象它们之间的交互,让高层代码和底层代码都通过这层抽象来交互,避免了直接的交互动作,两者之间的耦合度自然也就降低了。
这个抽象可以通过抽象基类来实现:
from abc import ABC, abstractmethod
class CurrencyConverter(ABC):
@abstractmethod
def convert(self, from_currency, to_currency, amount) -> float:
pass
而正是这层抽象,让原本从高到低的正向依赖,转为高层依赖抽象且低层也依赖抽象:
class OldConverter(CurrencyConverter):
def convert(self, from_currency, to_currency, amount) -> float:
print('Converting currency using OldConverter API, date: 2020-12-18')
print(f'{amount} {from_currency} = {amount * 0.8156} {to_currency}')
return amount * 0.8156
class NewConverter(CurrencyConverter):
def convert(self, from_currency, to_currency, amount) -> float:
print('Converting currency using NewConverter API, date: 2022-04-06')
print(f'{amount} {from_currency} = {amount * 0.9158} {to_currency}')
return amount * 0.9158
class App:
def __init__(self, converter: CurrencyConverter):
self.converter = converter
def start(self):
self.converter.convert('USD', 'EUR', 100)
从而实现更加健壮的代码:
if __name__ == '__main__':
converter_one = OldConverter()
converter_two = NewConverter()
app = App(converter_one)
app.start()
app = App(converter_two)
app.start()
>>>
Converting currency using OldConverter API, date: 2020-12-18
100 USD = 81.56 EUR
Converting currency using NewConverter API, date: 2022-04-06
100 USD = 91.58 EUR
尽管 DIP 非常有用,但代码经过LSP、OCP、ISP 和 SRP 考虑后,已经拥有了相当的可重用性与健壮性了,这使得在实际中遵守 DIP 的代码就不太多。
Dependency Inversion in Python OOP - Best way to populate Object
|