1

I'm working on a library that can represent LED strips virtually. My main class, LEDStrip, is abstract and has a couple abstract methods, like getLength() and setColor(int index, Color c). This lets whoever implements the class focus on just a few simple methods and let the parent class do all of the processing (such as applying patterns or "joining" two strips virtually).

My question is how it would be best to create these "synthetic" LEDStrip objects. There are a few ways that I can think of, but I'm not sure which is considered to be best practice:

Method 1: Separate class and constructor

public class JoinedLEDStrip extends LEDStrip {

    private LEDStrip strip1;
    private LEDStrip strip2;

    public JoinedLEDStrip(LEDStrip s1, LEDStrip s2) {
        strip1 = s1;
        strip2 = s2;
    }

    @Override
    protected void setLED(int LEDNum, Color c) {
        //Select which LEDStrip to set using length of first and second strips and apply offset
    }

    @Override
    public int getNumLEDs() {
        return strip1.getNumLEDs() + strip2.getNumLEDs();
    }
}

This would simply have a separate class that implements the abstract methods of the LEDStrip class. It's pretty simple but it seems like it might be a bit wordy to use in practice.

LEDStrip joined = new JoinedLEDStrip(firstStrip, secondStrip);

Method 2: "Instance" factory

//LEDStrip.java
public final LEDStrip join(LEDStrip strip) {
    return new LEDStrip() {

        private LEDStrip strip1 = LEDStrip.this;
        private LEDStrip strip2 = strip;

        @Override
        protected void setLED(int LEDNum, Color c) {
            //Select which LEDStrip to set using length of first and second strips and apply offset
        }

        @Override
        public int getNumLEDs() {
            return strip1.getNumLEDs() + strip2.getNumLEDs();
        }


    };
}

This method seems pretty simple, though the LEDStrip class might get a bit crowded, especially if there are more than a few different synthetic versions. It also won't be especially clear to a user of the library how they can create new transformations.

LEDStrip joined = firstStrip.join(secondStrip);

Method 3: Something else?

Maybe there is another method that I haven't thought of. A static factory seems like it wouldn't be especially helpful, but I'm sure Java has some other methods that would work better.

Which way is considered to be the most accepted practice?

  • 2
    What happens when there is an odd number of LEDStrips? – radarbob Feb 9 '17 at 19:20
  • @radarbob LEDStrip final = firstStrip.join(secondStrip).join(thirdStrip). This is just an example that I made in 10 minutes, the actual version may accept an array or list of LEDStrips and decide based on that. – Daniel M. Feb 9 '17 at 19:39
  • I'm not asking about the specific operation, just the design as a whole. An alternative way would be new JoinedLEDStrip(strip1,new JoinedLEDStrip(strip2, strip3)). I'm also planning on having invert, split, etc. – Daniel M. Feb 9 '17 at 19:41
  • Which approach do you like better? – Robert Harvey Feb 9 '17 at 20:39
  • 2
    What "preferred characteristics" are you trying to achieve? There is no "best" way; there is only that way that most effectively meets your specific requirements. – Robert Harvey Feb 9 '17 at 20:54
2

Your two examples are entirely equivalent for the most part. The architecture of your system will effectively be the same with either choice. They are equally extensible.

The only difference is whether the concrete type implementing joined LED strips is part of your public API or not. In particular, exposing a LEDStrip#join(LEDStrip) method does not require you to use an anonymous type. It could just as well be implemented as LEDStrip join (LEDStrip other) { return new JoinedLEDStrip(this, other); }.

A reason to have an LEDStrip#join(LEDStrip) instance method instead of using a static method (or equivalently, a constructor) is if different kinds of LED strips would be joined differently. E.g. joining a joined LED strip might be able to use some optimizations. In that case, the join method in your base class must not be final so that it can be overridden. This allows users of your library to create new LEDStrip subclasses.

It is however not possible to add new operations this way, e.g. a new LEDStrip#blink() method. If I'd create a subclass that adds this method, existing subclasses wouldn't pick up this method. If the new behaviour would be the same for all LED strips, a static method would suffice. If each subclass would behave differently, using the visitor pattern does make this possible, but is only viable if the hierarchy of possible LEDStrip types does not change. This pattern is therefore at odds with unrestricted subclassing.

When designing your API, you have to think carefully how it will be used and how users would like to extend it, and add appropriate extension points. The choice you've presented is mostly syntactic, but there are valid extensibility considerations here.

Recommended reading: the Composite pattern.

  • Good point about having the method simply call the constructor of the public class. Since they would be effectively identical, I can expose both and make it very clear in the documentation that the method is simply a shortcut for the constructor. Also the linked article made a good point about having all composite objects inherit from the same subclass of LEDStrip. What I might do is in the base strip class, create two methods, one that accepts a Function<LedStrip, CompositeLedStrip> and one Function<List<LedStrip>, CompositeLedStrip> so users can pass in constructors in a functional style – Daniel M. Feb 10 '17 at 14:05
  • if they wish to, or simply construct their class (or use a static factory) like normal. The second method would also accept a list to pass along to the constructor. The first method would simply use this as the argument to the function, while the second method would prepend this to the list. I can also use these methods for the default functions. – Daniel M. Feb 10 '17 at 14:11

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.