design pattern to avoid exponential number of extensions

This conundrum is making me feel like a newbie. I’m trying to follow the standard advice of limiting the amount of duplicate code.

Let’s say we have a fairly involved abstract class as a starting point.
Let’s say it has methods aa(), bb(), cc(), dd(), ee().

Now, lets say that of the subclasses being made, some use identical code override aa(), others use identical code to override bb() and others use identical code to override cc().

One solution would be to make three abstract subclasses to match the three common cases. Then the overriding code is only written once, one for each case.

My conundrum, though, is that the subclasses that require overrides of aa(), bb(), and/or cc() may do so in any combination. For example one requires the new aa() and bb() but not the new cc(), or another requires the new bb() or cc() but not aa(). All in all, there are (2^3)-1 possible combinations of these method overrides. Writing each abstract subclass with duplicate code for the various method overrides is what I am hoping to avoid. (Am also worried about the growth pattern getting worse if yet more common overrides prove useful and independent.)

At that point, maybe it makes sense to just store the overriding methods as text file templates and paste them in when making the subclasses directly from the abstract class.

Seems like this would be an issue that has been solved many times in the past, but I am failing to think of keywords to search for how others have handled this or if there is a design pattern for it.

Anyone else familiar with how this might be best implemented? Or do I just live with the duplicate code.

OK, just spotted the “decorator” pattern and am reading up on it. Maybe it will help.

Extract the implementation of various subclasses as interfaces with their implementing classes. The base class now has 5 fields, containing references to the appropriate implementations. If you need data from the base class in your implementation, pass the base class as an argument to the method implementing the business logic, or pass it in the constructor.

The decorator pattern seems like a poor fit for the base class. You can use it, however, for the 5 interfaces.

Thanks Riven! I not a good enough programmer to just read and imagine the solution you proposed. I’m going to have to use my fingers and toes and Eclipse to try and program a simple case what you described. Exercises like these will eventually make it easier to visualize via reading the verbal description. (I hope!)

It does seem that Decorator (which I HAD come across before) isn’t exactly what I’m looking for, as the result of that pattern is an instance of a class, not an abstract class that itself can be subclassed.

Could you be a little less abstract in the goal you are trying to achieve?

When you get more specific, our answers will be too :wink:

Riven, I just finished a successful implementation of your suggestion, and it works quite well.

What I did:

  1. Created an interface each for methods aa(), bb(), cc(), named InterfaceAA, InterfaceBB, InterfaceCC.

  2. Created a default implementation for each, as classes CoreAA, CoreBB, CoreCC.

  3. Created alternative implementations for each, as classes AltAA, AltBB, AltCC.

  4. In my AbstractCoreClass, I created three variables: interfaceA, interfaceB, interfaceC and instantiated them with CoreAA, CoreBB, CoreCC, as the default implementations.

  5. 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.

composition > inheritance 8)

I’m skimming on cell. This sounds like a good place to use a functionalinterface

A comment on your specific use case - most polyphony code I’ve seen does both these things. They use a fixed pool of SynthNote, and search for an available note in the following way.

  • If there’s a SynthNote playing the same pitch, return it.
  • else, if there’s a SynthNote not playing, return it.
  • else, find the SynthNote that’s been playing longest (note stealing), and return it.

I was kind of thinking the same thing. Again, will have to do this slowly, as it is relatively unfamiliar territory, and unclear to me if I can do it without adding a significant cpu cost where the composed function is within the most expensive while loop.

[Example of alternate while(): I’m thinking about using a 32-frame collector size as an alternative to the current 1-frame size. All the polyphonic Note values are “collected” by the associated Synth and summed and handed as a single value to the audio mixer. The new-to-me idea is to make the 32-frame collector a circular queue, and iterate through it one frame at a time (continuing to process 1 frame at a time), but where the size of the collector allows one to put the left or right stereo track data back to where it will be read at a later iteration, based on the panning value. It seems to me, at 44100 fps, 32 frames will large enough to accommodate a reasonable approximation of the speed of sound traveling the distance of one head’s width, which theoretically is the amount of time delay that would be relevant for temporal binaural effects. To be experimented on soon! This is based upon another one of Riven’s suggestions, from another thread.]

Will have a chance to look deeper after I get some stuff about controlling timbre better organized/architected, which seems to be occupying my brain right now.

[quote=""]
Useful! Thanks. I will probably make that set of steps one of several options. One reason not to do so: am wanting to give the environmental placement and individual timbre as much weight as pitch in the first step. In that case, might as well just go for first unused. But this is getting off topic. Better to message me if there is more you want to say more about the getNote() process, unless it involves composition/functional interface aspects.

When you get a chance and can possibly post your recent coding efforts I’d be glad to do a code review and see if there are any suggestions I can offer. Are you targeting JDK 8?

Cool!
Yes, I’m using JDK 8. I guess that means I have “targeted” it. I did not think through a decision based on who might be using or running the code–just on my attempt to stay current with skills and learn new stuff.

No. Other than be wary of adding in too many method calls / overridden methods on per-sample code. Sometimes real-time audio code is ugly! :wink:

I’m not sure why you’re worrying about these calculations happening at per-sample level, or even with 32 sample period, though. Neither why you’re looking at interfaces with multiple implementations in that way.

Ideally, I’d suggest you want a series of concrete operations (potentially functional interfaces, though many will be stateful) that work on a float[] buffer and you chain those together in whatever order is required for the synth output. A chain of simple operations on a float[] will generally be faster than complicated logic per-sample.

eg. to get a synth envelope, one operation fills a float[] with the synth source (oscillator), another fills a second float[] with the envelope (0 … 1), and another operation multiplies the two together for output.

Oscillator -----
multiply -> output
Envelope -----/

This incidentally gives you sample accurate output internally.

Then you need to look at external events, in which case you either process events on a per-buffer basis (eg. every 64 samples), or alter your operations to take a size parameter too and not rely on the array length.

eg. You should know at the start of processing every soundcard buffer at what point during that buffer any event happens. You may be working with a 512 sample buffer, but know that an event happens at sample 365. In which case you pass through the 512 sample float buffer through your chain of operations, but tell them to only write up to sample 364.

Hope that makes some sense.

I dont know if somebody mentioned it already, but I’ve noticed that someone gave you advice to use interfaces.

My true advice before going to interfaces is to not use them solely in purpose to force a method in implementing classes. Probably, most of advanced programmers will give this advice. Thing is - interfaces are actually the easiest way to implement call back methods inside a class. They don’t spawn anything beside that. They do that by assigning additional type information to implementing classes, so instances of this classes can be passed to other parts of the system, which will then be able to issue commands on them - using methods of their interface. This is exactly as it sounds - calling back for something (or nothing if a method is meant to have side effects).

Even in Java 8 which allows filling interface’s method’s bodies - the concept stays the same, the only difference here is you can use the non-public interface’s methods in template method design pattern for convenience.

Be aware of this or you will end up with messy code.

[quote=""]
It does, and I appreciate the advice, and I’ve thought this through a couple of times. Sadly, the complexity of the coding involved, with my modest skills, would likely set me back several months, maybe even a year or more. I am going to stick with the adage: first get it to work, then make it more efficient. The “it” that needs to work are the higher level, procedural music/sound tools. At that level, what I’m bringing to the party are insights and ideas arising from my experience, knowledge, craft and vision as a composer.

If something can be accomplished that allows for modest arrangements involving maybe a half dozen synths or 32 note concurrency, then that will be proof of concept, and will justify devoting more time to coding to double or triple the number of notes, say. (Or lead to financing that allows hiring a pro to do it!)

[quote=""]
Interfaces are great. I have been preferring them (and composition) over extensions for a while now. Getting better with them, with understanding how to use them with functions seems to me to be a core Java programming skill, as well as contributing to functional programming chops that are potentially helpful with almost any language. Seems very much worth the investment.

I think you’re massively overestimating the complexity of this. If you’re looking for a design pattern for this particular use case, the Unit Generator one is fairly standard.