Dependency Inversion Principle - Why Not Use A Base Class?

Viewed 85

Let's say I have an application which uses a special network socket, which transmits data over a socket in XML format called XMLSocket.

XMLSocket inherits BaseSocket, and satisfies the Liskov Substitution Principle. We can interchange them.

And I have a client application which uses XMLSocket:

class Client:
    def start(sock: BaseSocket):
        sock.connect(ip, port)
        sock.send(some_request)
        some_response = sock.recv()

Now, we can pass XMLSocket or BaseSocket into Client.start(), and this code works.

We could even add on more derivations of BaseSocket such as JSONSocket, and as long as the overall flow, function signatures, and return types are the same, it would still work with any of our socket classes in the hierarchy. Yet, this violates the dependency inversion principle.

Now let's create an interface ISocket:

class ISocket(ABC):
    def connect(ip, port):
        pass

    def send(request):
        pass

    def recv() -> Response:
        pass

And make Client.start() depend on our interface instead, therefore satisfying the dependency inversion principle:

class Client:
    def start(sock: ISocket):
        sock.connect(ip, port)
        sock.send(some_request)
        some_response = sock.recv()

Well, it seems like we've accomplished the same exact thing as the base class did. We can pass in any variation of a socket we can dream of, as long as it behaves like a socket.

So, why must it depend on an abstraction, instead of a base class?

1 Answers

It is better to use abstract class or interface as:

  • Don't derive from concrete classes. Or, as Scott Meyers puts it in Item 33 of More Effective C++,[8] "Make non-leaf classes abstract." (Admittedly, it can happen in practice - in code written by someone else, of course, not by you! - and in this one case you may have to have a public virtual destructor just to accommodate what's already a poor design. Better to refactor and fix the design, though, if you can.)

  • It is not possible to create instance from abstract class. So it makes easier to add abstract methods. And by using abstract keyword or interface you tell to other developers and compiler to inherit from it to create a concrete useful implementation.

Related