Inheritance
class ParentClass:
def secondary(self, data):
# Do stuff.
class MainClass(ParentClass):
def main():
...
self.secondary(data)
...
def orchestrator():
...
main_processor = MainClass()
main_processor.main()
Simplicity:
Relatively complex. There are three modules, and the overhead of instantiating the container class.
The coupling consists of two elements: The direct connection of Method A calling Method B, and the potentially heavyweight connection of MainClass inheriting from ParentClass.
On the one hand, the connection via inheritance between the two modules is simpler than, say, 'Method Call On Injected Object', because the connection is explicitly hard-wired, rather than being dynamic.
On the other hand, the inheritance connection between MainClass and ParentClass couples everything in ParentClass to MainClass. If the class hierarchy has not been extremely well-designed, then MainClass will have access to lots of methods which have nothing to do with the concern of MainClass. The problem is that any coupling (even indirect) could be construed as indicating a dependency: the reader may reason that a module has access to methods because it needs them. The reader may waste time sorting out which base class methods are actually relevant to MainClass, and which simply get dragged along as a consequence of inheritance.
Replaceability:
Best case: the new functionality properly belongs on the parent class, just like the old functionality.
class ParentClass:
# def secondary(self, data):
# # Do stuff.
def alternative(self, data):
# Do different stuff.
class MainClass(ParentClass):
def main():
...
# self.secondary(data)
self.alternative(data)
...
def orchestrator():
...
main_processor = MainClass()
main_processor.main()
- Write new replacement method on parent class (base class).
- One-line change in child class, to call new method.
- No change in surrounding orchestrating code.
Worst case: The new functionality is realised to be not part of the parent class. The whole abstraction is discovered to be wrong. The inheritance hierarchy must be dismantled and rebuilt.
# class ParentClass:
#
# def secondary(self, data):
# # Do stuff.
class BetterAbstractedParentClass:
def secondary(self, data):
# Do stuff.
# class MainClass(ParentClass):
class MainClass(BetterAbstractedParentClass):
def main():
...
self.secondary(data)
...
def orchestrator():
...
main_processor = MainClass()
main_processor.main()
- Whole new parent class required.
- Parent class of main class must be changed (one line change).
- Surrounding orchestrating code should not need to be changed.
However, in practice, this change will often be very difficult. The original parent class may have many child classes. So if the main class changes to a different parent, then the main class is no longer considered to be the same type of class as the other child classes. This may have widespread ramifications for the design.
It seems that inheritance is a very brittle form of coupling. The abstraction of the inheritance relationship needs to be exactly right, because it can be very difficult to change. This implies that inheritance is not suitable for code that will developed in an agile, iterative fashion.
🙠