Riven, I just finished a successful implementation of your suggestion, and it works quite well.
What I did:
-
Created an interface each for methods aa(), bb(), cc(), named InterfaceAA, InterfaceBB, InterfaceCC.
-
Created a default implementation for each, as classes CoreAA, CoreBB, CoreCC.
-
Created alternative implementations for each, as classes AltAA, AltBB, AltCC.
-
In my AbstractCoreClass, I created three variables: interfaceA, interfaceB, interfaceC and instantiated them with CoreAA, CoreBB, CoreCC, as the default implementations.
-
In my AbstractCoreClass, the method aa() was rewritten to execute the following: “interfaceA.aa();” Parallel writing for bb() & cc().
Now, when I create a new class that extends AbstractCoreClass, I have the option of instantiating the alternative implementation classes and loading them into the corresponding variables. I also still have the ability to override classes in the AbstractCoreClass and to write additional methods.
Is there an existing name for this pattern?
Now to figure out if I can make this work with my specific case! I didn’t get into describing the specifics because of not wanting to complicate things.
stop here for tldr version <<
This is for use with the synthesizers I have been writing. I had been cutting and pasting lots of code, much of it duplicate, over the course of the last year and a half or more, making around 20 individual synths. I came up with some changes that I wanted to apply to all of them, but resistant to making the change in each and every synth. So, I created an abstract CoreSynth with the desired new capabilities, and have been rewriting my synths as extensions of this CoreSynth (am about 2/3rds through, first pass).
During the course of the rewrite, I started seeing where it would be beneficial to have something like another layer of abstract synths with certain “extras” or “special features” so that the extra or special feature would NOT have to be rewritten for each concrete synth that uses it. That is what motivated this thread. With the pattern you describe, the functionality in the synth will be rewritten to have a default implementation and an alternative, optional implementation. I won’t have to actually create a proliferation of intermediate abstract synths, but instead can optionally put in the alternate methods as needed.
Here is an example. The synth has a premade pool of SynthNotes, which match the polyphonic capability of the synth. The default implementation searches through this collection of notes for one that is flagged 'isAvailable".
An alternative implementation defines the pool of SynthNotes by creating one per each permitted pitch. When a new SynthNote is needed, the search pulls the SynthNote for that specific pitch, and restrikes it (whether the note is playing or not).
The first case is good for music where few notes play at the same time, but the choice of pitch is wide or undetermined. The second case is good where many notes are heard at once, but from a known limited set of pitches. The need for many notes at once is a common result where there are long decay times. The restriking of a note that is in the process of decaying can sound perfectly clean and requires less cpu than having multiple instances of the same note, where the loudest effectively masks (aurally) those that are more decayed.
Other examples are things like real-time volume or real-time timbre response, which require slightly more steps in relatively costly while loops (where target levels are reconciled with actual levels). So, instead of making those capabilities “default” and present on every synth, I wanted to make them optional.