Skip to content

Design Issue: Inheritance

Juergen Donnerstag edited this page Sep 27, 2021 · 3 revisions

Problem Statement

The question: What is the V-approach for a design pattern where

  • you have a default implementation and several specialisation
  • the default implementation consists of multiple functions, often calling each other
  • in the specialisation, you often want to replace only 1 or few aspects (functions) of the default implementation
  • it would be nice if the default implementation could be extended, without the need to make modifications to all the specialisations. Such as add an additional method in between, because a new specialisation would benefit from it.

In Java (or other object oriented languages with some sort of inheritance), I would do the following (pseudo code):

public class MyDefault {
    public void   fn_1(String name) { fn_2(name + "a") }
    public void   fn_2(String text) { fn_3("Hello " + text) }
    public void   fn_3(String text) { fn_4("*bold*" + text + "*bold*") }
    public String fn_4(String text) { return text.to_lower_case() }
}

public class Spec1 extends MyDefault {
    public void   fn_3(String text) { fn_4("*green*" + text + "*green*") }
}

public class Spec2 extends MyDefault {
    public void   fn_1(String text) { fn_2(text) }
}

public class Spec3 extends MyDefault {
    public void   fn_1(String text) { fn_2(text) }
    public void   fn_3(String text) { return text.to_upper_case() }
}

In my real current V-code I copy & paste the default implementation, and then make the modifications needed. Because it is copy & paste, I must be careful to update all specialisations, if some core logic is being changed. And of course, it happened already, that I missed something. Ofcourse we all know, copy & paste is bad, and the implementation is fragile and error prone.

I understand V is not object oriented and probably never will be. I'm fine with that. Instead V has Sum-types and interfaces. But I yet have to find a design pattern for this problem in V.

Answers

Q: "Such a design is an OOP antipattern, and the problem with it, is that it leads to a fragile base class, that with time becomes more and more complex, and difficult to change/(and where you have to read to understand, what your inherited behaviour is, you have to look and jump around at both your specialisations, and your base class source), since the calls are intertwined - both your specialisation calls the base behaviour, and your specialisations are called by the base in ways that you do not control. Instead of it (inheritance), try to use composition. If you have a common interface, that can be implemented by multiple types, use an interface that declares the common stuff explicitly, and split the common functionality into functions that accept this interface, or add it as interface methods"

May be my example source code is too simple. I can see composition being used to modify text as in the example above. But I still struggle to apply composition to my real code.

Implementation

I followed the "componentize" approach and created 4 x (standalone) "components". Each component has an interface which defines 1 or 2 functions. Every interface has an associated default implementation (struct and functions). One of the "components" is a bit like a wrapper, stitching the other components together. This way I can have a specialised implementation for it as well.

Every specialisation consist of a struct, a function complying to the (wrapper) component interface (entry point), and functions implementing the specialisations needed (complying to the respective component interface). The entry point implementation instantiates a default wrapper component and initializes it with the (specialisation) components needed. Finally the wrapper components (interface) function gets invoked.

The whole approach works and it meets the requirements I outlined above. But it is definitely more lines of source code, interfaces and structs, and because of that, not especially easy readable.

Clone this wiki locally