【问题标题】:Enums instead of usual classes in Java枚举而不是 Java 中的常用类
【发布时间】:2013-06-06 19:06:00
【问题描述】:

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

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

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

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 关于将单例编写为枚举的建议怎么样?在这种情况下,这样的枚举必须是一个成熟的类,对吧?

【问题讨论】:

  • “利用枚举具有类主体和方法这一事实”?听起来您因使用新的(ish)语言功能而受到批评。他们是否也不喜欢使用泛型集合?
  • 好吧,他们只是说“hackish”,我使用了其余的 :) IMO 使用标准语言功能没有 hack
  • @AlexeyDanilov 我认为 Java 泛型是以一种骇人听闻的方式实现的,因为需要完全向后兼容。当您尝试使用协变量做任何重要的事情同时仍然需要静态类型安全时,类型擦除的使用会让人很烦人,例如用具有通用返回类型的方法覆盖具有原始返回类型的方法(尽管相反是有效的,看起来)。并且在泛型类/方法中对对象的方法调用也存在问题。 (并不是说我尽可能不使用泛型;它们仍然比替代方案好很多。)
  • 我同意泛型。它们不像其他语言中的类似结构那样方便处理,这些结构首先设计为类型安全。但是,是的,它们总比没有好,我们经常使用它们。
  • 我不认为在方法中使用枚举有什么“hackish”。也就是说,我认为你的枚举会更清晰,如果它只是将噪音作为基类中的一个字段,每个值作为构造函数参数传入。现在每个值都有很多代码,这使得它们看起来都在做非常不同的事情。但这都是一样的,只是将一个常数值传递给 Loudspeaker。 IMO,这种简单性正在消失在噪音中。

标签: java design-patterns enums


【解决方案1】:

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

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

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

回答您的具体问题:

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

相对于接口+具体类的方式:

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

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

我从未见过。

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

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

【讨论】:

    【解决方案2】:

    您应该只在没有(或很少)可能添加新元素的情况下使用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 {...}
    

    【讨论】:

    • "但是如果您有一些用户使用旧版本的枚举而其他用户使用更高版本怎么办?"使用旧版本的用户将获得新版本,就像他们获得任何新课程一样。而且我很确定Bestiary 已经在实现一个接口,因为他使用了@Override
    • 一个有效的观点,但正如我所提到的,在我的情况下,在需要时编辑源文件不是问题
    • @JAB 它没有实现接口;它本质上是通过在匿名类中覆盖自己的抽象方法来实现自己的。关于版本问题,我的意思是如果有一个 switch 语句不能考虑所有 Bestiarys 因为它们“尚不”存在怎么办?
    • @WChargin 哦,你是对的,我错过了enum 的最后两行。尽管枚举类型的 switch 语句问题与必须为新的 Animals 添加额外检查的问题有何不同?
    • @WChargin 这是枚举有方法的好处!您无需使用 switch 语句,而是将其重构为抽象方法并让元素提供正确的实现。
    【解决方案3】:

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

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

    【讨论】:

    • 是的,枚举值是最终的。但是,枚举字段不一定是最终的。
    • @AlexeyDanilov 那枚举类型的方法变异状态在哪里?
    • @WChargin 哎呀,这就是我的意思。
    • 在编写这个例子时,我并不真正关心封装,我的主要目标是展示设计建议......当然,这个类需要调整。但我希望看到任何指导方针,为什么这样做被认为是一种不好的做法。附言在我的示例中,您指的是哪种方法变异状态?
    • @AlexeyDanilov 没有,技术上。当我意识到这一事实时,我编辑了我的答案。诚然,我对使用枚举类型的方法引起副作用也有点怀疑(并且foods 可能会在以后修改,原因相同volatile 不适用于具有可变成员)但这更像是一个通用的设计理念,并且同样适用于常规类方法,即使人们通常不会对这些方法有太多的问题(尽管如果我没记错的话,许多环境确实不鼓励它们,这由于锅。危险是可以理解的)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-02-09
    • 2011-07-09
    • 2020-12-16
    • 2012-11-05
    • 2011-10-22
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多