简体   繁体   中英

Avoiding code duplication when overriding abstract methods in enum

I am implementing a state machine in enum in Java. I have a toy example below, where I transition between X , Y and Z states based on group membership.

The issue is, the transition rule for Y and Z are identical (ie, the Overriden methods are identical).

Is there any way to avoid code duplication here? In my real life example, it is a bit more severe so the code duplication potential is worse.

enum Group {
    A,B,C
}

enum Element {
    X(Group.A) {
        @Override
        public Element getNextElement(Element nextElement) {
            if(nextElement.getGroup() == Group.B) {
                return nextElement;
            } else {
                return this;
            }
        }
    },
    Y(Group.B) {
        @Override
        public Element getNextElement(Element nextElement) {
            if(nextElement.getGroup() == Group.A) {
                return nextElement;
            } else {
                return this;
            }
        }
    },
    Z(Group.C) {
        @Override
        public Element getNextElement(Element nextElement) {
            if(nextElement.getGroup() == Group.A) {
                return nextElement;
            } else {
                return this;
            }
        }
    };

    Group group;

    Element(Group group) {
        this.group=group; 
    };

    public Group getGroup() {
        return this.group;
    }

    public abstract Element getNextElement(Element nextElement);

}

Given that your logic is identical except for the value in the transition rule, you can just parameterize by that:

enum Element {
    X(Group.A, Group.B),
    Y(Group.B, Group.A),
    Z(Group.C, Group.A);

    private final Group group;
    private final Group nextGroup

    private Element(Group group, Group nextGroup) {
        this.group = group; 
        this.nextGroup = nextGroup;
    }

    public Group getGroup() {
        return this.group;
    }

    public Element getNextElement(Element nextElement) {
        return nextElement.getGroup() == nextGroup ? nextElement : this;
    }
}

You can still override getNextElement in some values. For example:

enum Element {
    X(Group.A, Group.B) {
        @Override public Element getNextElement(Element nextElement) {
            return someRandomCondition ? nextElement : this;
        }
    }
    Y(Group.B, Group.A),
    Z(Group.C, Group.A);

    // Other code as above
}

You could use the strategy pattern , roughly like in the following example. The example could be improved by thinking about how the different strategies relate to each other and pulling similarities into one or several strategy base classes:

enum Element {

    interface TransitionStrategy {
       Element getNextElement (Element myself, Element nextElement);
    }

    static class NextOnBStrategy implements TransitionStrategy {
       Element getNextElement (Element myself, Element nextElement) {
            if(nextElement.getGroup() == Group.B) {
                return nextElement;
            } else {
                return myself;
            }
        }                
    }

    // other strategies

    X(Group.A, new NextOnBStrategy ()),
    Y(Group.B, new NextOnAStrategy ()),
    Z(Group.C, new NextOnAStrategy ());

    Group group;
    TransitionStrategy strategy;

    Element(Group group, TransitionStrategy strategy) {
        this.group=group; 
        this.strategy=strategy;
    };

    // ...


    public Element getNextElement(Element nextElement) {
       return this.strategy.getNextElement (this, nextElement);
    }

}

It depends on the kind of complexity you need to manage the transitions conditions, a good objective oriented approach would be to encapsulate rules, eg:

class RuleSet {
  Map<Element, List<Rule>> rules = new HashMap<Element, List<Rule>>();

  void addRule(Element element, Rule rule) {
    List<Rule> rulesOfElem = rules.get(element);
    if (rulesOfElem == null) {
      rulesOfElem = new ArrayList<Rule>();
      rules.put(element, rulesOfElem);
    }

    rulesOfElem.add(rule);
  }

  Element evaluate(Element element, Environment env) {
    List<Rule> rulesOfElem = rules.get(element);
    if (rulesOfElem != null) {
      for (Rule rule : rulesOfElem) {
        Element next = rule.evaluate(element, env);
        if (next != null) return next;
      }
    }

    return element;
  }
}

abstract class Rule {
  Element evaluate(Element current, Environment env);
}

class GroupRule extends Rule {
  private final Group from, to

  GroupRule(Group from, Group to) {
    this.from = from;
    this.to = to;

    for (each Element in Group)
      ruleSet.add(element, this);
  }

  Element evaluate(Element element, Environment env) {
    ...
  }
}

But without knowing the required complexity it's hard to decide if it's better to keep things simple or keep them maintainable.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM