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
-
This can't be answered sensibly in a vacuum without the context of its purpose.whatsisname– whatsisname06/14/2017 04:47:46Commented Jun 14, 2017 at 4:47
-
1You are overthinking this. Like, a lot.T. Sar– T. Sar07/24/2017 04:35:33Commented Jul 24, 2017 at 4:35
3 Answers 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.
-
2I 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– Neil06/14/2017 07:40:12Commented 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– Dioxin02/13/2019 16:04:10Commented Feb 13, 2019 at 16:04
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
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;
}
}
#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;
}
-
But if I am using an ORM I will have trouble using those tricks right ? :(Walfrat– Walfrat07/24/2017 14:41:10Commented Jul 24, 2017 at 14:41
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.
Explore related questions
See similar questions with these tags.