据我了解,SOLID OOP 的依赖倒置和接口隔离原则告诉我们根据接口编写程序,而不是内部细节。因此,我正在尝试用 Python 开发一个简单的股票市场数据收集器,大致如下所示的对象图,其中
main
封装了应用程序业务逻辑、用户输入处理等。以下是如何理解它的
因此主函数使用抽象接口来根据代码获取股票价格。抽象类看起来像
#!/usr/bin/env python3
# encoding: utf-8
"""
Defines the abstract stock price reader
"""
from abc import ABC, abstractmethod
class StockPriceReader(ABC):
"""Defines the general tick reader interface."""
@abstractmethod
def get_price(self, symbol:str)->float:
"""
Gets the price of a stock represented by the symbol, e.g
when symbol='AAPL', it gets the Apple Inc stock price.
"""
raise NotImplementedError
TickReaderConcrete
类实现内部细节,并通过彭博或交易交易所 API 调用等方式获取实际股票价格。进行 API 调用所需的凭据必须是内部详细信息的一部分。这里不展示代码,因为它实现起来非常简单。
现在,基于上面简单的类依赖图,同一本书(Clean Architecture)似乎暗示着(这里我强调)
主块应该甚至不知道TickReaderConcrete的存在。
至少,这是我对书上所说内容的理解,因为从
main
到TickReaderConcrete
没有箭头,如果我错了,请纠正我。
但是当我写下
main.py
时,我不能假装TickReaderConcrete
存在,换句话说,当代码看起来像时,似乎
main
忍不住知道
TickReaderConcrete
的存在。
#!/usr/bin/env python3
# encoding: utf-8
"""
The main function to invoke the stockprice reader
"""
from tickreader import TickReaderConcrete
...
if __name__ == '__main__':
# This line gives rise to the alternative class diagram below
reader=TickReaderConcrete(...)
# After initialised, we can use the interface permitted by the abstract base class
reader.get_price(symbol='IBM')
那么如何确保
main
不知道具体读者的存在呢?如果 main
根本不导入具体读取器,它甚至无法实例化具体读取器对象,并且抽象读取器无论如何也无法初始化。
那么如何基本上组织代码来正确实现上面的对象图呢?
即使抽象基类公开了必要的公共方法,至少,初始化需要了解具体子类的存在。具体子类可以隐藏在抽象基类后面吗?看一下替代对象图,它是由上面的代码片段实现的。如何消除断线?
我读《干净的建筑》已经有好几年了,但我想提出一种不同的解释。 依赖倒置原则(DIP)确实表明抽象不应该依赖于实现细节,而是相反。
定义高级接口或抽象基类并使用包含所有细节的具体类来实现它是解决此类问题的标准方法。
DIP 然后建议几乎所有代码都应该针对高级抽象编写。这意味着,如果您有需要调用
get_price
方法的领域逻辑,则应该为其提供一个 StockPriceReader
抽象对象来与之交互。
但是,该规则有一个例外,因为 您需要在某处构建对象图,而这通常是在
__main__
方法中。您可以在此处创建具体类的实例,然后将其传递给需要抽象类的所有其他代码。