5

在做一个项目时,我被要求设计一组类来实现定义一个简单动作的接口。通常这些类会以特定的顺序一次性完成它们的工作,但仅从其中一个调用方法的可能性也是一个要求。

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

我想出了以下解决方案(实际课程并没有那么做作,但是下面的示例足以给您一些基本的想法):

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);
}
}

在代码审查期间,我的建议被嘲笑为“太老套,利用枚举具有类主体和方法的事实,并且总体上具有不好的代码气味”。虽然将它转换成一个单独的接口,一堆普通的 Java 类等只是几分钟的事情,但我对这个解释不太满意。是否有任何指导方针说您应该像其他语言一样仅以其基本形式使用枚举?这种方法有什么真正的缺点?Joshua Bloch 关于将单例编写为枚举的建议怎么样?在这种情况下,这样的枚举必须是一个成熟的类,对吧?

4

3 回答 3

3

可以在具有浅类层次结构的任何地方使用enum,在可扩展性(类有更多,枚举有更少)和简洁性(如果功能简单,枚举可能更清晰)之间进行权衡。这不是一直做正确的事情,但有时做一些肯定没问题,只是要注意差异,我在下面列出了其中的一些。

在我看来,您正在处理的情况在我看来正是语言设计者通过允许枚举具有方法来支持的那种事情。在我看来,您至少没有颠覆该语言功能的意图。

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

回答您的具体问题:

这种方法有什么真正的缺点?

与接口+具体类方法相比:

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

是否有任何指导方针说您应该像其他语言一样仅以其基本形式使用枚举?

没有我见过的。

Joshua Bloch 关于将单例编写为枚举的建议怎么样?在这种情况下,这样的枚举必须是一个成熟的类,对吧?

按照提问者的逻辑,是的。所以这就变成了一个问题,你是更愿意听他们,还是听乔什·布洛赫。

于 2013-06-06T21:11:09.490 回答
3

您应该只enum在没有(或很少)可能添加新元素的情况下使用。这并不是说您不应该提供类似枚举类的函数。例如

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
}

然后,当前接受 a 的任何方法Bestiary都可以轻松切换为接受 a 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 {...}
于 2013-06-06T19:17:06.820 回答
2

我个人的观点是enums 不应该包含任何变异方法,因为它违反了大多数枚举值具有恒定状态的假设。...但是再次查看您的工作,实际上似乎并非如此。这样做当然看起来很奇怪,但这更像是一种“意外使用”,而不是一种“错误的做法”。

只需确保枚举类型中的任何可能修改的值都不能从外部访问,例如foods. (String可以制作 s final,所以这不是问题,但制作foodsfinal 不会阻止人们操纵列表本身,只是分配一个新列表。)

于 2013-06-06T19:11:42.550 回答