枚举而不是Java中的常用类

时间:2013-06-06 19:06:00

标签: java design-patterns enums

在处理项目时,我遇到了一项任务,即设计一组实现定义简单操作的接口的类。通常这些类会按特定顺序完成它们的工作,但同时也只需要从其中一个方法调用方法。

考虑到以上所有因素并考虑到: - 每个班级都有相当基本的逻辑 - 不需要延长另一堂课 - 将所有类放在一个文件中可能很方便 - 在需要时编辑源文件不是问题

我提出了以下解决方案(实际课程不是那么做作,但下面的例子足以给你一些基本的想法):

public enum Bestiary {
DOG(1) {
    @Override
    void makeNoise(Loudspeaker ls) {
        ls.shoutOutLoud("I am alpha dog");
    }
},

CAT(2) {
    @Override
    void makeNoise(Loudspeaker ls) {
        ls.shoutOutLoud("I am beta cat");
    }
},

RAT(3) {
    List<String> foods = new ArrayList<>();
    {
        foods.add("gods");
        foods.add("dogs");
        foods.add("cats");
        foods.add("other rats");
    }

    @Override
    void makeNoise(Loudspeaker ls) {
        StringBuilder cry = new StringBuilder("I am THE rat; usually I eat ");
        for (int i = 0; i < foods.size(); i++) {
            cry.append(foods.get(i));
            if (i != (foods.size() - 1)) {
                cry.append(", ");
            }
        }
        ls.shoutOutLoud(cry.toString());
    }
},

THE_THING(4) {
    String name = "r2d2";

    @Override
    void makeNoise(Loudspeaker ls) {
        ls.shoutOutLoud(calculateHash(name));

    }

    private String calculateHash(String smth) {
        return String.valueOf(smth.hashCode());
    }
};

private int id;

public int getId() {
    return id;
}

Bestiary(int id) {
    this.id = id;
}

abstract void makeNoise(Loudspeaker ls); // all enum elements will need to implement this - kind of like implementing an interface (which was also an option); note that we pass some arbitrary object and call methods on it
}

调用此类的代码可能如下所示:

public final class Loudspeaker {
private static Loudspeaker loudspeaker = new Loudspeaker();

public static void shoutOutLoud(String cry) {
    System.out.println(cry);
}

static class Noizemakers {
    public static void makeSomeNoise() {
        for (Bestiary creature: Bestiary.values()) {
            System.out.println(creature + " with id " + creature.getId() +  " says: ");
            creature.makeNoise(loudspeaker);
        }
    }
}

public static void main(String[] args) {
    Noizemakers.makeSomeNoise();
    Bestiary.CAT.makeNoise(loudspeaker);
}
}

在代码审查期间,我的建议被嘲笑为“过于hacky,利用枚举有类体和方法的事实,并且整体上有一个糟糕的代码味道”。在将它转换为单独的接口时,一堆常见的Java类等只需几分钟,我对这个解释并不十分满意。是否有任何指导方针说您应该以其基本形式使用枚举,与其他语言类似?这种方法有什么真正的缺点?约书亚布洛赫建议把单身人士写成名词 - 在这种情况下,这样的词汇必须是一个完整的阶级,对吗?

3 个答案:

答案 0 :(得分:3)

您应该只使用enum,其中没有(或很少)添加新元素的可能性。这并不是说你不应该给类枚举类函数。 For example

public enum Planet {
    MERCURY (3.303e+23, 2.4397e6),
    VENUS   (4.869e+24, 6.0518e6),
    EARTH   (5.976e+24, 6.37814e6),
    MARS    (6.421e+23, 3.3972e6),
    JUPITER (1.9e+27,   7.1492e7),
    SATURN  (5.688e+26, 6.0268e7),
    URANUS  (8.686e+25, 2.5559e7),
    NEPTUNE (1.024e+26, 2.4746e7);

    private final double mass;   // in kilograms
    private final double radius; // in meters
    Planet(double mass, double radius) {
        this.mass = mass;
        this.radius = radius;
    }
}

这有多种原因:

  • 语义/预期目的。根据单词的定义,没有意义包含非枚举的枚举。
  • 兼容性。如果我想在您的动物群中添加一只鸟怎么办?你必须修改enum。很容易,但如果你有一些用户使用旧版本的枚举和其他使用更高版本的用户怎么办?这会带来很多兼容性问题。

如果必须使用枚举,则一个(次优)解决方案将是:

interface Animal {
    void makeNoise();
}

enum Bestiary implements Animal {
    // the rest of the stuff here
}

然后,当前接受Bestiary的任何方法都可以轻松切换为接受Animal。但是,如果你这样做,那么最好只有:

interface Animal {
    void makeNoise();
}
public class Dog implements Animal {...}
public class Cat implements Animal {...}
public class Rat implements Animal {...}
public class Thing implements Animal {...}

答案 1 :(得分:3)

一个可以在任何你有浅层次结构的地方使用enum,在可扩展性(类有更多,枚举更少)和简洁(如果功能很简单,枚举可能更清晰)。这不是一直都做的事情,但在某些时候做某些事情肯定是可以的,只要注意一下这些差异,其中一些我在下面列出。

在我看来,你所处理的情况在我看来恰好是语言设计者通过允许枚举有方法来支持的事情。在我看来,你至少没有颠覆这种语言特征的意图。

作为我工作的一个例子,我经常使用枚举方法来实现各种无状态策略,但也将它们用于其他方面,包括作为Class的一种可扩展形式。

回答您的具体问题:

  

这种方法有哪些真正的缺点?

与界面+具体类方法相比:

  • 无法从该值外部调用特定枚举值中定义的方法。例如,如果您为RAT定义了一个名为squeak()的方法,则无人可以调用它。
  • 没有可变状态,因为每个枚举值实际上都是单例。
  • 如果类型数量急剧增加,或者每种类型的代码增加,您的枚举类文件可能会过长。
  • 无法对枚举值进行子类化,它们实际上是final
  • 毫无疑问其他人......
  

是否有任何指导方针说您应该以基本形式使用枚举,与其他语言类似?

我从未见过的。

  

Joshua Bloch关于将单身人士作为词汇写的建议怎么样 - 在这种情况下,这样的enum必须是一个完整的课程,对吗?

遵循提问者的逻辑,是的。所以这就成了一个问题,你是不是应该听他们,还是听Josh Bloch。

答案 2 :(得分:2)

我个人认为enum s不应包含任何变异方法,因为它违反了大多数具有常量状态的枚举值的假设。 ......但是再次查看你的工作,实际上并非如此。以这种方式做这当然看起来很奇怪,但它更像是一种“意外使用”的东西,而不是特别是“做错的方式”的事情。

确保枚举类型中的任何可能可修改的值无法在外部访问,例如foods。 (String可以final,这不是问题,但是foods最终版本不会阻止人们操纵列表本身,只需指定一个新列表。“ / p>