#Problem with enums (OOP problem)

141 messages · Page 1 of 1 (latest)

mossy hornet
#

Okay so I have a class which has an attribute which needs to be an enum state. The problem is, depending on the context, the values that this state can have can change. I have two enums class to deal with that and they both implement an Interface. The class actually has the interface as attribute as I thought it will solve the problem, but looks like this does not work...what is the cleanest way to deal with the situation ? (See the class diagram, it explains better than me)

frozen yokeBOT
#

This post has been reserved for your question.

Hey @mossy hornet! Please use /close or the Close Post button above when you're finished. Please remember to follow the help guidelines. This post will be automatically closed after 300 minutes of inactivity.

TIP: Narrow down your issue to simple and precise questions to maximize the chance that others will reply in here.

mossy hornet
#

Depending on how the program is launched, the Client can either have access to the FirstEnum states or the SecondEnum states. Note that in the future, it's possible to add a ThirdEnum or more.

rocky egret
#

What do you mean by "but looks like this does not work..."?

mossy hornet
rocky egret
#

Yeah, you're abstracting the enum behind your interface, so only the interface would be accessible.
I suppose you could just check if your Ienum is an instance of FirstEnum or SecondEnum and then cast to the appropriate one, whenever you need to perform operations specific to the enum type.

#

Although there might be an entirely more appropriate data structure for your use case. Not sure what your use case is though.

#

Is there a reason you need to know which enum was used?
If there's more behavior you want outside of execute, couldn't you add that to your interface and override it within each enum type?

mossy hornet
#

The ICWS88 standard contains 11 opcodes
The ICWS94 standard contains 18 opcodes iirc and some are executed differently because of the modifier addition (a thing that affect the operation execution)

#

The enums are containing the different opcodes

rocky egret
#

The way I've mostly commonly seen for handling behavior specific to each enum constant, would be to have your each of your execute method implementations perform a switch on (this) and specify the behavior for each case.
Although what you mentioned in #general is also interesting.

mossy hornet
#

The fact that we can add methods to enum constants can avoid really long switch case statement and make the program easier to extend later imo

#

So I use it sometimes, not that often

#

But yeah, I will try a cast, maybe this will work... 🤔
I hope...

#

And I hope I will be able to do it a clean way

rocky egret
#

The only reason I'm hesitant to recommend enum constant specific methods it is simply because I've read a lot of code and have never seen it used before.
Even when useful, syntax is problematic if it is unfamiliar to everyone who reads it. I would always prefer to be more verbose, if using an obscure syntax means the behavior is much less obvious.
Although in this case, I think the syntax is easy to understand even to those who are unfamiliar with it, so... It's probably completely fine.

mossy hornet
#

The syntax is...

public enum FooEnum {

A {
  @Override
  public void bar() {
    //code
  }
},



B {
  @Override
  public void bar() {
    //code
  }
},



C {
  @Override
  public void bar() {
    //code
  }
};


public void bar();

}
rocky egret
#

Eesh... When written out like that, it's actually even more verbose than a simple switch statement.

mossy hornet
#

But sometimes switch will be spread throughout the code

#

In that case, every method are gathered and the method are shorter

#

But the file by himself sure can be a big one

rocky egret
#

You'd do the switch in FooEnum#bar in that case.
I don't see how it's any different from the perspective of an outside class.

mossy hornet
#

I will use the switch if there's few operations and if I'm sure that I will not add more operations later

rocky egret
#
public enum FooEnum {

A,B,C;

public void bar();
  switch(this) {
    case A:
      break;
    case B:
      break;
    case C:  
  }
}

^ My class is shorter than yours, and just as obvious in terms of behavior.

mossy hornet
#

Also, is this possible for me to use the Strategy Pattern ?
If Ienum contains all the methods and then I rewrite them in FirstEnum and SecondEnum, throwing an error in case of bad use?

mossy hornet
#

And if you have multiple operations you need multiple switch

rocky egret
#

You'd also need multiple method overrides per enum constant. Pretty sure they'd scale at roughly the same rate.
But yeah, you're probably right about it being a matter of taste.

mossy hornet
#

Here is an example

rocky egret
#

If Ienum contains all the methods and then I rewrite them in FirstEnum and SecondEnum, throwing an error in case of bad use?
Sounds fine to me.

mossy hornet
#

It's big and not satisfying to look at, but easier to find what I want in the code. 🤔

mossy hornet
frozen yokeBOT
# mossy hornet I will try that then, thanks! :D

If you are finished with your post, please close it.
If you are not, please ignore this message.
Note that you will not be able to send further messages here after this post have been closed but you will be able to create new posts.

mossy hornet
#

Wow, JavaBot is efficient 👀

#

I dunno if I close the thread now or if I wait a bit

rocky egret
#

Up to you. Nobody's ever going to complain if you make more than one thread per day.

mossy hornet
#

Okay here is why I didn't close this thread, I got a problem with the Strategy Pattern.
I thought this was the best idea, but...an interface that contains enum fields does not exist ._.

frozen yokeBOT
mossy hornet
#

JavaBot isn't that efficient in the end...

#

Can an enum inherit from another enum?

#

In the end the cast was the best idea 🥹

#

I don't know how to do that properly either

mossy hornet
#

I mean, how am I supposed to say "this interface is in this enum type", this does not work like that either

#

@rocky egret It's impossible to cast in an enum type, and for the Strategy Pattern it's impossible as an interface is not an enum. :/

#

Si I'm back with the same problem...

mossy hornet
#

Okay so...

#

🥹

#

Is something like that acceptable?

rocky egret
#

Have you considered dropping the idea of enums and just making some good old fashion constants?

rocky egret
mossy hornet
mossy hornet
rocky egret
#

For your consideration:

Create an interface Opcode with a single method execute
An abstract class Standard subclassed by classes Standard88 and Standard94
Each standard implementation contains constants defining each of their Opcodes.
Because Opcode is a functional interface, you can inline the execution for each Opcode.

mossy hornet
#

Also it would be class here not enum.

#

The idea behind this model is to allow every Operation, and the only code the constant have is (if standard is FirstClass then doForA() here and if it's SecondClass then doForA() here)

mossy hornet
rocky egret
mossy hornet
rocky egret
mossy hornet
#

I didn't know about that...why is this looking like an enum? 🤔
It can be useful...I don't know I'm a bit lost...^^'>

rocky egret
#

Enums are essentially just a shortcut to making constants.
Because they're a shortcut, they've got their own limitations though, so they're not a one-size-fits-all solution for every situation that demands constants.

mossy hornet
#

And how is this working to call that ?
Like...
returnedValue = JMP(param); ?

rocky egret
#

I'm not sure what you mean.

mossy hornet
#

In my current code, every opcode has an execute() method that takes parameter and return a value. I never encountered that kind of code before so I don't really know how it works

#

The thing is, the client is an Instruction. And instruction contains an opcode and two operands.
So I can't put a standard instead of an opcode. It should be an opcode and nothing else

rocky egret
#

You can use any params/return values that you'd like. I just used strings as an example.

mossy hornet
#

What do you think about this kind of thing?

#

Basically a try to make a Strategy pattern

rocky egret
#

Hmmm....

#

I've got one more suggestion then.

mossy hornet
#

Tell me then! :D
Your suggestions are really helpful. :0

rocky egret
#

import java.util.function.BiFunction;

public enum OpCode {

    JMP((s1, s2) -> "s1 + s2 = " + s1 + s2, Standard.ICWS88, Standard.ICW94), // Both standards
    ETC((s1, s2) -> "this is the return value", Standard.ICWS88); // Just one
    
    private BiFunction<String, String, String> execution;
    private Standard[] standards;

    OpCode(BiFunction<String, String, String> execution, Standard... standards) {
        this.execution = execution;
        this.standards = standards;
        for (Standard standard : standards) {
            standard.register(this);
        }
    }

    public Standard[] getStandards() {
        return this.standards;
    }

    public String execute(String s1, String s2) {
        return this.execution.apply(s1, s2);
    }
}

import java.util.HashSet;
import java.util.Set;

public enum Standard {
    
    ICWS88, ICW94;

    private final Set<OpCode> opCodes = new HashSet<>();

    public void register(OpCode opCode) {
        this.opCodes.add(opCode);
    }

    public Set<OpCode> getOpCodes() {
        return this.opCodes;
    }
}
#

I used a bifunction because I noticed your execute takes in two params and provides a return value. You can make the types whatever you want.

mossy hornet
#

I-
Don't understand anything... 🥹

#

Probably too hard for what I am doing...
Especially considering the fact that I have only 12 hours to finish it. 🥹

rocky egret
#

You've got an enum for your two standards.
Each contains all of the valid OpCodes for that standard.

You've also got an enum containing all available OpCodes.
Each OpCode contains references to the standards it's supported by.

Each OpCode also has a function determining its execute behavior.

So you can do something like this:
OpCode.JMP.execute(s1,s2);

Or you can do something like this:

for (OpCode opCode : Standard.ICS88.getOpCodes()) {
  System.out.println("ICS88 supports OpCode: " + opCode.name());
}
#

Honestly, I can't think of a better way to represent this relationship.

#

Don't get too caught up on the bifunction. It's not as scary as it looks.

mossy hornet
#
JMP((s1, s2) -> "s1 + s2 = " + s1 + s2, Standard.ICWS88, Standard.ICW94)

I don't know how the both standards are differenciated, because sometimes in 88 and 94 opcodes can work differently

rocky egret
#

Change the BiFunction to a TriFunction and pass in your Standard as a param, then, if you need to, you can provide different behavior depending on the standard.
I can update the examples if you'd like.

mossy hornet
#

🙂🔫
Sigh

Is this simpler than using a Strategy Pattern?
Good to know that Bifunction and Trifunction exist tho but...it's frightening considering the time that left, and I will need hours after too

rocky egret
#
public enum OpCode {

    JMP((s1, s2, standard) -> {
        if (standard == Standard.ICW94) {
            return s1;
        } else {
            return s2;
        }
    }, Standard.ICWS88, Standard.ICW94), // Both standards
    ETC((s1, s2, standard) -> "this is the return value", Standard.ICWS88); // Just one
    
    private TriFunction<String, String, Standard, String> execution;
    private Standard[] standards;

    OpCode(TriFunction<String, String, Standard, String> execution, Standard... standards) {
        this.execution = execution;
        this.standards = standards;
        for (Standard standard : standards) {
            standard.register(this);
        }
    }

    public Standard[] getStandards() {
        return this.standards;
    }

    public String execute(String s1, String s2, Standard standard) {
        return this.execution.apply(s1, s2, standard);
    }
}
#

Technically a TriFunction doesn't exist, you'll have to make it yourself, but it's super easy. Just copy paste this:


@FunctionalInterface
public interface TriFunction<T, U, V, R> {
    R apply(T t, U u, V v);
}

Edit: I removed the stuff you don't need. It's less scary now.

mossy hornet
#

🥹

rocky egret
#

I can't tell if you're happy or sad.

#

😆

mossy hornet
#

I'm happy about the fact that I learnt something new and useful. 🥹
I'm sad about the fact that if I try that I will probably struggle a lot and don't finish in time so...
I will try a Strategy Pattern, and if this does not work I will try the trifunction

rocky egret
#

I have high confidence in you. thumbsupcat

mossy hornet
#

I don't know what to answer between "thanks" and "you shouldn't". 😭 😂

frozen yokeBOT
mossy hornet
#

He reacts to the word "t.hanks"...

maiden lodge
maiden lodge
mossy hornet
#

Haha, you're right!

mossy hornet
maiden lodge
#

no clue what a strategy pattern is, sorry. is it referring to the enum elements with overridden methods?

mossy hornet
#

I am talking about this kind of thing... :0

maiden lodge
#

do you have a fixed number of operations?

mossy hornet
#

For each standard yes, but it's possible to add new standards

maiden lodge
#

does each standard have the same operations, and do the operations behave identically in each standard

mossy hornet
#

No :/
I mean, mostly, but for example the 94 added modifier, which affect the execution of the operations

maiden lodge
#

no to the latter or no to both?

mossy hornet
#

No for both

maiden lodge
#

if each standard has different operations then each standard would need a seperate operation handler

mossy hornet
#

Here is my idea, I think this can work! :D

#

Depending on the standard the return value will be different.

#

There's a method 88 and 94 with different algorithm behind.

#

Non-valid opcode (for example 94 opcode in 88) will throw an error. Basic usage for strategy.

maiden lodge
#

seems like a lot of repeated code

mossy hornet
maiden lodge
#

well no, defining the methods is still repeated code

#

you could have them be lambdas, or with a different setup, even method references

mossy hornet
#

🤔

mossy hornet
#

It's 7 AM here and it's working... ;-;

#

Thanks for your help. ;-;
I still have work to do but I think the hardest part is behind now...

frozen yokeBOT
frozen yokeBOT
#

💤 Post marked as dormant

This post has been inactive for over 300 minutes, thus, it has been archived.
If your question was not answered yet, feel free to re-open this post or create a new one.

rocky egret
# maiden lodge to be pedantic, this is not future-proof to adding new elements

Kinda off subject now, but what’s not future proof about it?

If you add a new element without providing a case for it in foo() you’ll get a compiler error specifically instructing you to add a case for it.

This is the most common way I’ve seen people provide different behavior depending on the enum. Seems solid to me. Although I do prefer inlined lambdas passed into the constructor like my most recent example.

rocky egret
maiden lodge
#

ah coming back to it i had a fault in my thought process

#

wait hold on

#

well there was a fault but the point might still stand, give me a sec to test

#

i was thinking of the "guaranteed return" thing with enum switches which require a default branch if there's unconditional return later
which would catch newly-defined members without requiring a case for the member

rocky egret
#

Yeah, I intentionally didn’t provide a ‘default’ in my example in order to avoid that. Doing so would definitely be dangerous.

maiden lodge
#

it's unsafe in the same way - unimplemented new member - but for a different reason than i was thinking

#

#bot-cmds message

#

oh come on javabot

rocky egret
#

I suppose you could easily make a case that it isn’t future proof if it’s as fragile as no longer being future proof the moment you have a return outside of the switch or provide a default within the switch, because someone new to a codebase might not understand the consequences of doing so.
And that’s a good point about it not being future proof at all if there’s no return value, which is actually the case in my example. Nice catch.

mossy hornet
frozen yokeBOT
frozen yokeBOT