2

I am way over thinking this: What I am trying to do is [hopefully not reinvent the wheel and] come up w/ a [Android] Java eventing mechanism that allows subclasses to pre-define an arbitrary set of "features" with getters and setters that fire individual callbacks.

I think I am fusioning some combination of Command, Visitor, Decorator, Facade and Observer patterns here, and confusing myself along the way. I have been programming for well over 20 years, but I feel like a n00b on this fairly simple problem! :(

I have searched SO for the compiler error and read many of the results, but I still haven't found a solution that works for me. (How to make a Java class that implements one interface with two generic types? seems to be the most relevant one that I have found, but I also want to generically get the values and fire events to callbacks when they are set).

First, let the below mostly valid code speak for itself...

interface IFeature
{
}

interface IFeatureCallbacks<T extends IFeature>
{
    boolean onChanged(Feature<T> c);
}

public static class Feature<T extends IFeature>
{
    private Set<IFeatureCallbacks<T>> listeners = new LinkedHashSet<>();

    public void addListener(IFeatureCallbacks<T> listener)
    {
        listeners.add(listener);
    }

    public void removeListener(IFeatureCallbacks<T> listener)
    {
        listeners.remove(listener);
    }

    protected void onChanged()
    {
        for (IFeatureCallbacks<T> listener : listeners)
        {
            listener.onChanged(this);
        }
    }
}

//

interface IFeatureA
        extends IFeature
{
    int getA();
}

interface IFeatureACallbacks
        extends IFeatureCallbacks<IFeatureA>
{
}

public static class FeatureA
        extends Feature<IFeatureA>
        implements IFeatureA
{
    private int a;

    public void setA(int value)
    {
        a = value;
        onChanged();
    }

    @Override
    public int getA()
    {
        return a;
    }
}

//

interface IFeatureB
        extends IFeature
{
    boolean getB();
}

interface IFeatureBCallbacks
        extends IFeatureCallbacks<IFeatureB>
{
}

public static class FeatureB
        extends Feature<IFeatureB>
        implements IFeatureB
{
    private boolean b;

    public void setB(boolean value)
    {
        b = value;
        onChanged();
    }

    @Override
    public boolean getB()
    {
        return b;
    }
}

//

interface IDeviceWithFeatureA
        extends IFeatureA
{
}

interface IDeviceWithFeatureACallbacks
        extends IFeatureACallbacks
{
}

public static class DeviceWithFeatureA
        extends Feature<IDeviceWithFeatureA>
        implements IDeviceWithFeatureA
{
    FeatureA a = new FeatureA();

    public void addListener(IDeviceWithFeatureACallbacks listener)
    {
        a.addListener(listener);
    }

    public void setA(int value)
    {
        a.setA(value);
    }

    @Override
    public int getA()
    {
        return a.getA();
    }
}

//

interface IDeviceWithFeatureB
        extends IFeatureB
{
}

interface IDeviceWithFeatureBCallbacks
        extends IFeatureBCallbacks
{
}

public static class DeviceWithFeatureAB
        extends Feature<IDeviceWithFeatureB>
        implements IDeviceWithFeatureB
{
    FeatureB b = new FeatureB();

    public void addListener(IDeviceWithFeatureBCallbacks listener)
    {
        b.addListener(listener);
    }

    public void setB(boolean value)
    {
        b.setB(value);
    }

    @Override
    public boolean getB()
    {
        return b.getB();
    }
}

The above code seems to work fine, albeit something about it smells a bit off. The problem is when I try to do this:

interface IDeviceWithFeatureAAndFeatureB
        extends IFeatureA, IFeatureB
{
}

/*
Compiler error:
'IFeatureCallbacks' cannot be inherited with different type arguments 'IFeatureA' and 'IFeatureB'
*/
interface IDeviceWithFeatureAAndFeatureBCallbacks
        extends IFeatureACallbacks, IFeatureBCallbacks
{
}

public static class DeviceWithFeatureAB
        extends Feature<IDeviceWithFeatureAAndFeatureB>
        implements IDeviceWithFeatureAAndFeatureB
{
    FeatureA a = new FeatureA();
    FeatureB b = new FeatureB();

    public void addListener(IDeviceWithFeatureAAndFeatureBCallbacks listener)
    {
        a.addListener(listener);
        b.addListener(listener);
    }

    public void setA(int value)
    {
        a.setA(value);
    }

    @Override
    public int getA()
    {
        return a.getA();
    }

    public void setB(boolean value)
    {
        b.setB(value);
    }

    @Override
    public boolean getB()
    {
        return b.getB();
    }
}

I am less interested in trying to figure out how to make what I am trying to do compilable, and I am more interested in what about my abuse of a pattern is way off base so that I can re-write it to be both simpler and compile.

Community
  • 1
  • 1
swooby
  • 3,005
  • 2
  • 36
  • 43
  • 1
    Since generics are a compile time feature, and **not** present at run-time (due to [type-erasure](http://docs.oracle.com/javase/tutorial/java/generics/erasure.html)), I think the answer is you can't do that in Java (with generics). – Elliott Frisch Sep 18 '15 at 23:49
  • @ElliottFrisch know of any non-generics way to do this? :) – swooby Sep 18 '15 at 23:57
  • Perhaps at run-time with [reflection](https://docs.oracle.com/javase/tutorial/reflect/). Also, try to break the problem down; combining patterns should make the solution cleaner (not more complex). – Elliott Frisch Sep 18 '15 at 23:59
  • Thanks. Reflection would be a last resort. I've been hashing over this for several days, and this is the most broken down solution that I have come up w/ so far. I thought I had the solution on the whiteboard this afternoon...and then coded it up and ran in to this compiler/run-time limitation. :( – swooby Sep 19 '15 at 00:03
  • Perhaps shoving a proper first class Visitor/Consumer in to the mix could provide a solution... – swooby Sep 19 '15 at 00:25

1 Answers1

0

You are abusing the basic "pattern" of OOP -- inheritance. The adage is that "favor composition over inheritance". Think in terms of "contains", instead of "is-a".

Take Zoo for example. A zoo is just a bunch of animals, right? So naturally, we may want to declare Zoo as subtype of Set<Animal>. Perhaps even have class Zoo extends HashSet<Animal>.

However, that is likely a wrong design. A zoo is actually a lot of things. It contains a set of animals, sure; but it also contains a set of people (as workers, not exhibits (although...) ). So it's better to

class Zoo

    Set<Animal> animals(){ ... }

    Set<Person> workers(){ ... }

Anywhere we need to treat a zoo as a set of animals, just use zoo.animals(); think of it as a type cast, or projection. We don't need inheritance here.

In your design, you have too many types; what's worse, too many type relationships. It seems that you simply need one generic class that reads/writes value of T, and contains listeners of T

class Feature<T>
    T value; 

    // getter 
    // setter

    Set<ChangeListener<T>> listeners;

interface ChangeListener<T>

    void onChange(T oldValue, T newValue)

A device contains a bunch of features

class SomeDevice

    Feature<Integer> featureA = new Feature<>();

    Feature<Boolean> featureB = new Feature<>();

That's it. You can operate on feature A of the device by operating on itsfeatureA.

ZhongYu
  • 19,446
  • 5
  • 33
  • 61
  • Thanks, but unfortunately for me, that simpler version is how I started out, and it did not meet my perceived needs. The "Device" is my preferred lowest level public object to subscribe for callbacks from. I will have many Devices with many assorted Features, and the external callers should not need to be concerned w/ adding callbacks for individual Features across Devices. Only the Device is concerned w/ its Features, and I was hoping to provide a single callback interface for multiple Features. I'm still thinking... – swooby Sep 21 '15 at 20:36