【问题标题】:How to avoid excessive code duplication when using enums in Java在 Java 中使用枚举时如何避免过多的代码重复
【发布时间】:2011-10-06 00:20:33
【问题描述】:

我正在重构一些遗留代码并遇到了一个问题,我确信有一个优雅的解决方案 - 但我无法完全解决。

最初有很多类扩展了抽象类BaseType。这些类中的每一个都有一个枚举 - XmlElementTag - 具有特定于类的值:

enum XmlElementTag {value1, value2, value3}

他们每个人都有一个方法:

private XmlElementTag getTag(String s){
    XmlElementTag ret = null;
    try {
        ret = XmlElementTag.valueOf(s);
    } catch (Exception e) {
        Log.e(this, s+" is not supported tag");
    }
    return ret;
}

每个类都有完全相同的getTag 方法,但显然它们都指的是特定于它们所在类的XmlElementTag 枚举。所以,如果可以的话,我想摆脱这个代码重复.

我想也许我可以使用一个标记接口来解决这个问题,所以创建了一个,每个XmlElementTag枚举现在都继承并重写了getTag方法并将其放入超类中。

所以我在每个班级都有这个:

private XmlElementTag implements GenericTag {value1, value2, value3}; 

这在 BaseType 超类中:

public interface GenericTag {}

protected GenericTag getTag(String tagName){
    XmlElementTag tag = null;
    try {
        tag = XmlElementTag.valueOf(tagName);
    } catch (Exception e) {
        Log.e(this, tagName+" is not supported tag");
    }
    return tag;
}

但这同样不起作用,因为BaseType 超类不知道XmlElementTag 是什么; Java 不允许抽象类变量;在BaseType 中创建此元素将不起作用,因为getTag 代码将始终引用此枚举,而不是扩展BaseType 的类中的那个。

谁能指出我正确的方向?

【问题讨论】:

  • 可以在java中拥有抽象类型。
  • 我的意思是抽象类变量 - 将更新以反映这一点。
  • 请移除 Android 标签,因为这是一个一般的 Java 问题。

标签: java inheritance overriding superclass


【解决方案1】:

您也许可以将XmlElementTag 元素合并为一个enum,并为每个派生类型建立一个EnumSet。有一个例子here

附录:在此方案中,getTag() 将成为组合 enum 的单一方法。每个派生类都将使用它认为有效的Set 调用getTag()。该方法可能有这样的签名:

public static XmlElementTag getTag(Set valid, String s) { ... }

【讨论】:

    【解决方案2】:

    我猜你可以编写一个静态的通用辅助方法来做getTag 所做的事情。它需要在后台使用反射,并且很可能需要您将枚举的 Class 对象作为参数传递。

    但是 IMO,您不应该这样做。 getTag() 方法有点错误。它将实际上是错误的输入变成了null。从两个角度来看这是错误的:

    • 在大多数情况下,“你给了我坏东西”不应被视为“你什么也没给我”。
    • 如果你不小心谨慎,那些null 值会像NullPointerExceptions 一样回来咬你。

    实际上,您的应用程序代码应该捕获并处理在转换出错时出现的IllegalArgumentException,或者它应该允许异常冒泡到顶部,以便将其报告为(例如)解析输入流时出错。

    (我不认为enum 可以扩展或扩展,所以我认为您的enums 不能继承此类的通用版本。)

    【讨论】:

    • 我同意getTag() 是错误的说法。然而,我发现关于如何仅实现一次签名取决于具体类的方法的问题很有趣。
    • 我不会按照目前的方式编写此方法,但我不想在重构时进行任何可能引入错误的更改。如果按照我的方式,大部分代码库都会被删除和重写!
    【解决方案3】:

    我认为你想要实现这样的目标(如果我错了,请纠正我):

    interface GenericTag {
    
        public GenericTag fromString(String str) throws IllegalArgumentException;
    }
    
    
    class BaseType {
    
        protected GenericTag getTag(String tagName) {
            GenericTag tag = null;
            try {
                tag = tag.fromString(tagName); 
            } catch (Exception e) {
                Log.e(this, tagName+" tag is not supported");
            }
            return tag;
        }
    
    }
    
    class ConcreteTypeA extends BaseType {
    
        enum XmlElementTag implements GenericTag {
            TAG1, TAG2;
    
            public GenericTag fromString(String str) throws IllegalArgumentException {
                return XmlElementTag.valueOf(str);
            }
        }
    }
    

    但是,这永远不会奏效。您将需要 fromString 方法来返回实现 GenericTag 的适当类(在本例中为枚举)的实例,但 fromString 方法必须由您还没有的具体类执行,所以您会得到空指针异常。 这是一种先有鸡还是先有蛋的问题! :)

    【讨论】:

    • 是的 - 希望 Java 允许抽象类成员,这会更容易!
    【解决方案4】:

    您可以为此使用generics

    基地是

    public abstract class Base {
    protected static <T extends Enum<T>> T getTag(Class<T> enumType, String s) {
        T ret = null;
        try {
            ret = Enum.valueOf(enumType, s);
        } catch (Exception e) {
            System.err.println(s + " is not supported tag");
        }
        return ret;
    }
    
    protected abstract <T extends Enum<T>> T getTag(String s);
    }
    

    您的众多课程都有一个较短的getTag()(所有逻辑都在Base 中)

    public class ClassA extends Base {
    enum XmlElementTag {
        UL, LI
    }
    
    @Override
    protected XmlElementTag getTag(String s) {
        return Base.getTag(XmlElementTag.class, s);
    }
    }
    

    ClassB 也一样)

    【讨论】:

    • 是的 - 这样做超出了我的想法,但理想情况下我希望能够完全删除重复的代码。这种方法仍然会看到重复,这是一个问题,因为我有很多扩展 Base 的类,尽管重复的扩展显然要少得多。
    【解决方案5】:

    不幸的是,Java 枚举没有一个好的元类(Class 是邪恶的)。但是,您真正需要的只是枚举值的列表(数组)。

    既然是私有方法,不妨使用组合。

    import static java.util.Objects.requireNonNull;
    
    /* pp */ class EnumFinder<E extends Enum<E>> {
        private final E[] tags;
        protected BaseType(E[] tags) {
            this.tags = requireNonNull(tags);
        }
    
        public E getTag(String name) {
            requireNonNull(name);
            for (E tag : tags) {
                if (name.equals(tag.name())) {
                    return tag;
                }
            }
            Log.e(this, name+" is not supported tag"); // (sic)
            return null; // (sic)
        }
        ...
    }
    
    public class DerivedType {
        private static final EnumFinder<XmlElementType> finder = // note, shared
            new EnumFinder<>(XmlElementType.values());
        ...
            finder.getTag(name)
        ...
    }
    

    (如果你真的想创建一个Map&lt;String,E&gt;。对于合理大小的枚举是不必要的。)

    如果你真的想使用继承,那也差不多。 (不幸的是,我们使用的是数组,除非您在代码中添加更多样板,否则此代码将为每个实例创建一个不必要的额外数组 - 可能不是一个重要问题,但可能是。):

    /* pp */ abstract class BaseType<E extends Enum<E>> {
        private final E[] tags;
        protected BaseType(E[] tags) {
            this.tags = requireNonNull(tags);
        }
    
        public E getTag(String name) {
            requireNonNull(name);
            for (E tag : tags) {
                if (name.equals(tag.name())) {
                    return tag;
                }
            }
            Log.e(this, name+" is not supported tag"); // (sic)
            return null; // (sic)
        }
        ...
    }
    public class DerivedType extends BaseType<XmlElementType> {
        public DerivedType() {
            super(XmlElementType.values());
        }
        ...
            this.getTag(name)
        ...
    }
    

    【讨论】:

    • 这看起来不错!一个不是 - 在第一个示例中,您使用类名 EnumFinder,我认为这是一个剪切和粘贴错误,应该是 BaseType?
    • @Martyn 复制和粘贴错误是在组合示例中,DerivedType 不应扩展 BaseType。 (我先写了第二个版本。)
    • 但是您定义了类 EnumFinder,然后是 BaseType 的构造函数。我错过了什么吗?
    猜你喜欢
    • 1970-01-01
    • 2023-03-24
    • 1970-01-01
    • 2023-01-12
    • 2011-01-17
    • 1970-01-01
    • 1970-01-01
    • 2019-10-29
    • 1970-01-01
    相关资源
    最近更新 更多