0

Say that there are multiple classes {C1, C2, ...} which implement an interface I, containing, among other things, methods called getColor(), getName(), and move(). Every instance of C1 returns the same value when getColor() and getName() are called, same with C2 and all of the other classes that implement I. Move is defined per class, too, but is affected by instance variables and may not behave the same way per instance. The implementations of getColor() and getName() in the C1 class might look like:

public Color getColor() {
    return Color.RED;
}

public String getName() {
    return "C1!!!";
}

All objects implementing the interface I can be added to and drawn to a screen. Suppose, however, that the way in which those objects are added, is through a different screen, with buttons on it. When one of those buttons is clicked, it signals to the application that the type of object associated with the clicked should be added to the screen.

If the goal is to label the button associated with C1 with the String returned by C1's getName() and color the button with the Color returned C1's getColor(), one could instantiate a new instance of C1, and then retrieve its name and color to customize its associated button:

...
I instance = new C1();
Button C1Button = new Button();
C1Button.setLabel(instance.getName());
C1Button.setColor(instance.getColor());
...

... and then instantiate a new instance of C2 and C3 and ... and C50, following the same process as with C1.

This seems rather dirty though, the classes are no longer being instantiated for the sake of fulfilling the other methods in I, but just to obtain the color and name properties for the buttons. Furthermore, that would be a awful lot of code. Can anyone provide suggestions as to how the color and name properties could be decoupled from the classes implementing I, so as to reduce the length of code required if the number of classes implementing I grows dramatically past two? Are there any specific design patterns that might be used to resolve this?

  • is there any business logic changes between C1 and C2? not values, but logic? – Iłya Bursov Aug 18 '17 at 02:48
  • Yes, possibly. I'll add that to the question! – paleto-fuera-de-madrid Aug 18 '17 at 02:54
  • it looks like you need static array with object instances, so instances will be reused – Iłya Bursov Aug 18 '17 at 02:55
  • doesn't the reuse of instances make the fact that move differs per instance a problem? – paleto-fuera-de-madrid Aug 18 '17 at 02:57
  • no, you could have something like `I[] array = new I[]{new C1(), new C2()...,};` – Iłya Bursov Aug 18 '17 at 02:58
  • just to confirm `move` behaves differently for each instance or each class? – Iłya Bursov Aug 18 '17 at 02:59
  • then array approach should work, so you will be able to turn your button creation code into simple loop, onclick handler will be able to use different instance of object from pool (array) – Iłya Bursov Aug 18 '17 at 03:04
  • ok. and you think that's an ok practice? – paleto-fuera-de-madrid Aug 18 '17 at 03:08
  • Its hard to say without know everything about the system you're implementing, but generally there is nothing wrong with constant objects – Iłya Bursov Aug 18 '17 at 03:11
  • @IlyaBursov array is not good since a) you have to store it somewhere b) you should update it when new class has been created c) you could do nothing but only iterate through the array. Enum, as Matt suggests, is much better since a) and c) is not relevant for enum. – ADS Aug 18 '17 at 09:57
  • @ADS a) is relevant for enum too, I don't understand c). Technically enum is array internally, so there is no conceptual difference. – Iłya Bursov Aug 18 '17 at 14:10
  • @IlyaBursov a) The difference that array stored in a field or variable inside some class. And that raises issues: how to get access from other class if needed? How to make sure nobody would change this array from another thread? c) You are [wrong](https://docs.oracle.com/javase/tutorial/java/javaOO/enum.html), in java **enum is a class** extends `java.lang.Enum` It's a crucial moment – ADS Aug 20 '17 at 16:44
  • @ADS how will you implement method values for enum? – Iłya Bursov Aug 20 '17 at 16:48
  • @IlyaBursov just see the [tutorial fom Oracle](https://docs.oracle.com/javase/tutorial/java/javaOO/enum.html)`public enum Plane { double surfaceGravity() { /*use fields*/} – ADS Aug 20 '17 at 16:53
  • @IlyaBursov I personally has created enum which contains public abstract and private classes which do some stuff. And it works on production. Such enums has been in Java for ages since Java 5 i.e. for more than decade – ADS Aug 20 '17 at 16:57
  • @ADS I mean - if you were JDK developer, how would you implement method `values` in `java.lang.Enum`? – Iłya Bursov Aug 20 '17 at 16:58
  • @IlyaBursov I don't need to dream about. I could see source for `java.lang.Enum`. But it's out of scope. I suppose any java developer should know that enum is a class and could contain any fields, constructors and methods. It's very different from array which cant have additional data than given from JDK – ADS Aug 20 '17 at 17:07
  • @ADS just to close discussion - enum **is** array, or you can say it is syntactic sugar around immutable array, with named indexes. This is why enum can return list of all declared constants (because internally it is array), every enum instance has `ordinal` (because it is index in this internal array). So there is not **conceptual** difference. – Iłya Bursov Aug 20 '17 at 17:07
  • @IlyaBursov just re-read Java basics. It's different than in another languages – ADS Aug 20 '17 at 17:09
  • @ADS how exactly enum is different in other languages? (of course languages which have such feature explicitly, implicitly - all language with arrays provide similar function) – Iłya Bursov Aug 20 '17 at 17:13
  • @IlyaBursov I agree we have to close the discussion - it's out of initial question. You could find answer at [StackOverflow](https://stackoverflow.com/questions/37345825/difference-of-enum-in-c-and-java) – ADS Aug 20 '17 at 17:21
  • @ADS you're right about different languages, I've completely forgot that some of them define enum values only as integers, I stand corrected. Anyway, in java enum is array with named indexes, or you can treat it as `Map` (which is technically also array) – Iłya Bursov Aug 20 '17 at 17:31

2 Answers2

1

I usually do that sort of thing by making an enum like this, separate from the implementation classes

public enum TypeOfC
{
    C1(Color.RED, C1::new),
    //you don't have to use different classes for every one!
    CX(Color.BLACK, () -> new C1(5)),
    C2(Color.BLUE, C2::new);

    private final Color m_color;
    private final Supplier<I> m_constructor;

    TypeOfC(Color color, Supplier<I> ctor)
    {
        m_color = color;
        m_constructor = ctor;
    }

    public Color getColor()
    {
        return m_color;
    }

    public I create()
    {
        return m_ctor.get();
    }
}

Then you can make buttons like:

for (TypeOfC t : TypeOfC.values())
{
    Button b = new Button();
    b.setColor(t.getColor());
    b.setLabel(t.name());  // make a different getter if you don't want the enum constant name
    b.setAction(()->doWatever(t.create()));
    allButtons.add(b);
}
Matt Timmermans
  • 53,709
  • 3
  • 46
  • 87
0

One possible answer that I found involves using something like the type object pattern. It seems to be conceptually related to Matt's suggestion. Instead of defining multiple different classes that inherit from I, only one class, C needs to be defined, which accepts another class, Properties, in its constructor. All properties of C can then be retrieved from the Properties class associated with it:

public class C implements I {
    private final Properties properties;

    public C(Properties p) {
        this.properties = p;
    }

    @Override
    public Properties getProperties() {
        return properties;
    }
}

The properties can all be defined in a PropertyDictionary. When constructing new C's, the appropriate properties for an object can be grabbed from the PropertyDictionary. This way, the classes inheriting from I still maintain the same functionality (in a changed method name, though) and other accesors of the data can learn what they want to about the possible values that C can take by querying the PropertyDictionary:

Properties p = new Properties("Class name", Color.YELLER);
I instance = new C(p);
// instance.getProperties().getColor() == p.getColor(); --> True
...
for(Properties prop : PropertyDictionary.getProperties()) {
    Button b = new Button();
    b.setLabel(prop.getName());
    b.setColor(prop.getColor());
}

Multiple instances of C with the same Properties all reference the same Properties instance in the PropertyDictionary, somewhat like in Flyweight:

final Properties p = new Properties("Cat", Color.BLACK);
List<I> cats = new ArrayList<>(1000);

// make 1000 black cats:
for(int i = 0; i < 1000; i++) {
    cats.add(new C(p));
}