2

For example, if I use an enum, which all values are manually defined individually, for example:

public enum MyNum{
    Zero(0),
    One(1),
    Two(2);

    private final int value;

    MyNum(int value){
        this.value=value;
    }

    public int getValue(){
        return value;
    }
}

public class Test{
    public static MyNum getUnknownMyNum(){
        return MyNum.Two;
    }

    public static void main(String[] args){
        MyNum n1=MyNum.One;
        MyNum n2=MyNum.Two;
        System.out.println(n1==getUnknownMyNum());
        System.out.println(n2==getUnknownMyNum());
    }
}

and don't use the feature of bitwise operation, eg:

MyNum n=MyNum.Zero | MyNum.One;

,is it violating open-closed principle? Because I think at this case, adding a new enum (eg:Three(3)) needs to edit MyNum.java.

If all enum value are assigned manually, and I don't need bitwise operation when using that enum, it means each value is independent to each other, which is different from

public enum MyNum{
    Zero,
    One,
    Two
}

that calculates each value from zero, and hence we can replace the following enum :

public enum MyNum{
    Zero(0),
    One(1),
    Two(2);

    private final int value;

    MyNum(int value){
        this.value=value;
    }

    public int getValue(){
        return value;
    }
}

into individual classes:

public interface MyNum{
}

public class Zero implements MyNum{
    public static final Zero MyNum=new Zero();
    public static final int value=0;
}

public class One implements MyNum{
    public static final One MyNum=new One();
    public static final int value=1;
}

public class Two implements MyNum{
    public static final Two MyNum=new Two();
    public static final int value=2;
}

public class Test{
    public static MyNum getUnknownMyNum(){
        return Two.MyNum;
    }

    public static void main(String[] args){
        MyNum n1=One.MyNum;
        MyNum n2=Two.MyNum;
        System.out.println(n1==getUnknownMyNum());
        System.out.println(n2==getUnknownMyNum());
    }
}

which replaces the one using enums into the version that use an interface with classes. And this time we don't need to edit the MyNum.java in order to add a new enum "Three", but creating a new class Three.java:

public class Three implements MyNum{
    public static final Three MyNum=new Three();
    public static final int value=3;
}

which fits open closed principle, is it true?

Note: for c++ enums for example,

enum class MyNum{
    Zero,
    One,
    Two
}

enum class MyNum{
    Zero,
    One=2,
    Two
}

they doesn't consider as "all values are manually defined individually" because some values needs to calculate from previous value

2
  • This can't be answered sensibly in a vacuum without the context of its purpose. Jun 14, 2017 at 4:47
  • 1
    You are overthinking this. Like, a lot.
    – T. Sar
    Jul 24, 2017 at 4:35

3 Answers 3

3

The answer is yes and no. The open-close principle states that:

Software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification.

But this principle is meant for types and behaviors, not for values (refer to R.C.Martin's seminal paper - link in the wikipedia article).

No: The Open/Close principle is constrained when applied to values

Let's take it to the extreme with types of our daily life: integers. According to the language specifications, a Java integer is between -2147483648 to 2147483647. Take any of your O/C-compliant classes with an private int member having a public setter. Obviously, you couldn't use it with the value 2147483649. If that int is deeply embedded in the class' behavior, you'll have to rewrite big parts of it to extend the range of acceptable values. Would this extreme case lead you to consider that this class is not Open/Close compliant after all ?

So:

  • The open/close principle is to be understood within the valid values for which the type was designed.
  • Extension are a specialization, so you could only restrict further the acceptable values, and not enlarge them (enlarged set would be a superclass).
  • Not being able to add a new value to an enum is therefore not an infringement of that principle !

Yes: The Open/Close principle is applicable to behaviors

Unfortunately putting to much intelligence in an enum is a problem. By definition, a java enum is implicitly final. So you can't add new behaviors (methods) or override existing ones.

This design of using an enum with methods doesn't allow to extend the type for adding a set of operators (e.g. add(), increment()), or restricting the set of values in the setter (e.g. an hypothetical MyBinary type that would only use MyNum.Zero and MyNum.One subrange from MyNum).

In my view, this contradicts the Open/Close principle. An "open" (extensible) approach would use the enum only for defining the basic acceptable values, and then use this in an ordinary (extensible) class according to the principle of composition over inheritance.

2
  • 2
    I agree. If the behavior of an enum needs to be extended, then you're not using enum properly. Having specific enums which perform radically different operations is not an enum at all but classes which extend a common interface. If said class should also happen to return a identifying number value, then so be it.
    – Neil
    Jun 14, 2017 at 7:40
  • Lovely answer. But a few concerns: "So you can't add new behaviors (methods) or override existing ones." An enum could define new base methods, and an enum value could override it's base behavior. We can't extend the type to add behavior, but new behavior can be added without affecting existing code. How is O/C contradicted?
    – Dioxin
    Feb 13, 2019 at 16:04
1

In languages where enum's can't inherit from other enums there is no good way to extend them directly. Therefore using them for things that might later need new values is indeed a violation of the open closed principle.

Your workaround of using objects with static members that implement an interface works. However, there is a sneaky trick where your enums implement an interface.

The extensible enum trick

By @Joachim Sauer

interface SomeType {
  String name();
}

public enum CoreEnum implements SomeType {
  VALUE1,
  VALUE2;
}

public enum AdditionalEnum implements SomeType {
  VALUE3,
  VALUE4;
}

By @cletus

interface Digit {
  int getValue();
}

enum Decimal implements Digit {
  ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE;

  private final int value;

  Decimal() {
    value = ordinal();
  }

  @Override
  public int getValue() {
    return value;
  }
}

enum Hex implements Digit {
  A, B, C, D, E, F;

  private final int value;

  Hex() {
    value = 10 + ordinal();
  }

  @Override
  public int getValue() {
    return value;
  }
}

By @Mykola Golubyev

#include <iostream>
#include <ostream>

class Enum
{
public:
    enum
    {
        One = 1,
        Two,
        Last
    };
};

class EnumDeriv : public Enum
{
public:
    enum
    {
        Three = Enum::Last,
        Four,
        Five
    };
};

int main()
{
    std::cout << EnumDeriv::One << std::endl;
    std::cout << EnumDeriv::Four << std::endl;
    return 0;
}
1
  • But if I am using an ORM I will have trouble using those tricks right ? :(
    – Walfrat
    Jul 24, 2017 at 14:41
0

Even when not doing bitwise operations on such an enum, defining the numerical values for its members can be useful. We use that when communicating with other systems, e.g. there is an enum for message identifiers, and an enum for response codes.

It is "closed for modification": changing a number cannot be done without changing the external system. But in the same sense, it is not "open for extension", as also here the external system requires an equivalent extension.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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