Reputation: 1632
So far as I understand, the Dependency Inversion and Interface Segregation principles of SOLID OOP tell us to write our program according to the interface, not internal details. So, I am trying to develop a simple stock-market data collector in Python, roughly with the following object diagram, where main
encapsulates the application business logic, user input handling etc. Here is how to make sense of it
So the main function uses the abstract interface which fetches a stock price against a symbol. The abstract class looks like
#!/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
The TickReaderConcrete
class implements the internal details, and gets the actual stock price by something like a Bloomberg or trading exchange API call. The credentials necessary to make the API call have to be part of the internal details. Not showing the code here, as it is pretty simple to implement.
Now, based on the above simple class dependency diagram, the same book (Clean Architecture) seems to imply that (here I emphasise)
The main block should not even be aware that the TickReaderConcrete exists.
At least, that is my understanding of what the book is saying, as there is no arrow head from main
to the TickReaderConcrete
, correct me if I am wrong.
But when I write the main.py
I cannot pretend that TickReaderConcrete
does not exist, in other words, seems main
cannot help but know about the existence of TickReaderConcrete
when the code looks like
#!/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')
So how to make sure the main
is unaware of the concrete reader's existence? If main
does not import the concrete reader at all, it cannot even instantiate the concrete reader object, and the abstract reader cannot be initialised anyway.
So how to basically organise the code to properly implement the object diagram above?
Even if the abstract base class exposes the necessary public methods, at the very least, the initialisation requires knowledge of the existence of concrete subclass. Can the concrete subclass hide behind the abstract base class? Look at the alternative object diagram, which is what is implemented by the above code snippet. How to get rid of the broken line?
Upvotes: 1
Views: 132
Reputation: 233347
It's been some years since I read Clean Architecture, but I'd like to suggest a variant interpretation. The Dependency Inversion Principle (DIP) does, indeed, suggest that that abstractions shouldn't depend on implementation details, but rather the other way around.
Defining a high-level interface or abstract base class and implementing it with a concrete class that contains all the details is a standard approach to address such concerns.
The DIP then suggests that almost all of your code should be written against the high-level abstraction. This means that if you have domain logic that needs to call the get_price
method, it should be given a StockPriceReader
abstract object to interact with.
There is, however, one exception to that rule, because you need to compose the object graphs somewhere, and that's usually in the __main__
method. This is where you create an instance of the concrete class, and then pass it to all the other code that expects the abstract class.
Upvotes: 2