Della
Della

Reputation: 1632

How to Implement Dependency Inversion and Interface Segregation for a Concrete Class that Needs to Be Initiated?

Context

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

Object diagram of stock price reader

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.

Dilemma

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')
Question

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?

Slightly Paraphrased Question

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?

enter image description here

Upvotes: 1

Views: 132

Answers (1)

Mark Seemann
Mark Seemann

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

Related Questions