最近我一直在尝试提取直到跌落。好吧,不一定要等到我停下来,但我一直在尝试更加严格,并查看我的代码的一些指标。案件。度量标准说方法代码行是邪恶的。它是这样的:

private void startProcessing(Map<MyKeyEnum, String> map) {
    Processor myProcessor = new Processor();
    for (Entry entry : map.entrySet()) {
        switch(entry.getKey()) {
            case KEY1:
                myProcessor.processStuffAboutKey1(entry.getValue());
                break;
            case KEY2:
                myProcessor.processStuffAboutKey2(entry.getValue());
                break;
            case KEY3:
                myProcessor.processStuffAboutKey3(entry.getValue());
                break;
            case KEY4:
                myProcessor.processStuffAboutKey4(entry.getValue());
                break;
            ...
            ...
        }
    }
}


因此,基本上,您可以了解到,如果地图中的每个键都需要调用非常不同的东西,那对我来说是非常必要的。因此,我已经创建了Processor类,请注意,这已经是我能够想到的绝对最短,最紧凑的类。基本上,该方法只做一件事。

但是,以不同的方式构建较短的代码,这不是可能的吗?我目前有25个以上的案件要处理

编辑:明确了Key,Value -Pair是什么以及它们用于什么

评论

您正在传递Map 映射,但仅使用map.keySet()。您是否将Map中的值用于任何东西,或将它们传递到Processor子例程中?

哔-哔-哔!策略模式警报!

从根本上讲,看看您是否只能拥有一个processStuffAboutKey(...)并仅基于字符串/ int(例如@ dss539)对其进行参数化。 (这些动作实际上是特定于键的吗?)除非是,否则“策略模式”是过大的。

它们是非常特定于密钥的,并且会做非常不同的事情

@SimonAndréForsbergBEEP BEEP BEEP您还可以使用访问者模式以提高灵活性,但增加复杂性!

#1 楼

您可以为每个键创建一个KeyProcessor接口和实现(将当前Processor.processStuffAboutKeyX()方法的主体移到这些类上): :

public interface KeyProcessor {
    void processStuff(String data);
}


并在循环中使用该映射: switch-case方法的长度至少为75行(每种情况下25个case语句,25个break语句以及至少1-1个方法调用行)。单独的类减少了开发人员立即看到的大小和复杂性(+无需滚动),代码更易于维护和测试,因为您可以单独测试每种情况。

我认为这是具有(和处理)几个小类和相应的* Test类(每个类都有一些测试方法)要比拥有一个大类(25种以上的情况)和在一个相应的* Test中进行大量测试要容易得多类或很多* Test类,它们测试同一startProcessing方法的单独的大小写分支。输入映射很难说更多。

更多阅读内容:具有多态性的价格代码的条件逻辑

替换具有多态性的条件条件


评论


\ $ \ begingroup \ $
正是我要说的,你击败了我。 StrategyPattern FTW!
\ $ \ endgroup \ $
–西蒙·福斯伯格
14年2月19日在10:24

\ $ \ begingroup \ $
由于Keyis是枚举,所以应使用EnumMap代替HashMap。效率更高(有效Java,条款33)
\ $ \ endgroup \ $
–塞恩·帕特里克·弗洛伊德(Sean Patrick Floyd)
2014年2月19日下午14:00

\ $ \ begingroup \ $
我可能会缺少一些东西,但是这比开关解决方案好吗?您刚刚将25个“案例”从一个函数移到了另一个(初始化地图的函数),并且引入了25个新类。这肯定比开关“更面向对象”,但这并不意味着它会更好。
\ $ \ endgroup \ $
– fredoverflow
2014-02-19 17:34



\ $ \ begingroup \ $
您甚至可以继续将相应的键保存在类本身中(getProcessedKey())。因此,所有这些类的创建和地图的填充都可以“自动”进行。我曾经针对类似的问题使用过这种方法,但发现它是相当可维护的。
\ $ \ endgroup \ $
–卡斯滕·霍夫曼
2014年2月19日在20:25

\ $ \ begingroup \ $
远离开关会更好,因为它会促进开闭原理。现在生成此映射甚至可以进行配置,例如如果需要,从文件中读取密钥并使用运行时支持实例化各个对象。开关是不可能的。
\ $ \ endgroup \ $
–银河
14年2月26日在8:26

#2 楼

如何将处理方法的功能添加到枚举本身?这样,枚举本身就知道如何处理,并且如果您向枚举添加值,则不必在代码库中搜索所有switch语句来更新它们。
您可以在Joshua Bloch的著作《有效的Java》中了解有关此方法的更多信息
类似的东西:

private void startProcessing(Map<MyKeyEnum, String> map) {
    for (Entry entry : map.entrySet()) {
    entry.getKey().process(entry.getValue());
    }
}


public enum MyKeyEnum {
    VALUE1 {
        public void process(String s) {
            // do specific processing for VALUE1
        }
    },
    VALUE2 {
        public void process(String s) {
            // do specific processing for VALUE2
        }
    }
    ...
    ;

    public abstract void process(String s);
}


评论


\ $ \ begingroup \ $
这对于小枚举是个好主意,但是当枚举增长时,它将变得混乱。我更喜欢EnumMap解决方案
\ $ \ endgroup \ $
–塞恩·帕特里克·弗洛伊德(Sean Patrick Floyd)
2014年2月19日14:25

\ $ \ begingroup \ $
使用EnumMap解决方案,您仍然具有将所有处理器添加到地图中的样板代码,以及25种以上的Processor类。我认为这是一个品味问题。
\ $ \ endgroup \ $
– Paul S
2014年2月19日15:11

\ $ \ begingroup \ $
是的,我也这么说。但是,能否请您提供您所指的“有效Java”中的确切项目编号?
\ $ \ endgroup \ $
–avalancha
2014年2月19日下午16:27

\ $ \ begingroup \ $
这种解决方案是最干净的。您已经确定了核心问题是MyKeyEnum实际上应该是具有派生类型KEY1,KEY2等的基类。IMO,此枚举不应该是枚举。像OP那样的switch语句是一种代码气味,表明存在多态性的机会。
\ $ \ endgroup \ $
–dss539
2014年2月19日在16:30

\ $ \ begingroup \ $
@avalancha(有效Java第2版)中的第30项。查找带有enum Operator以及更高版本的示例。
\ $ \ endgroup \ $
– Paul S
2014年2月19日在19:46

#3 楼

您可以用一个虚拟函数调用替换switch语句中的每种情况,如palacsint的答案所示。例如,当您需要添加对新键值的支持时,在您的代码中需要添加新的case语句,而在palacsint的代码中,您需要添加新类(派生自KeyProcessor)并将新条目添加到processors map。

如果您有两个switch语句根据键值执行各种操作,则palacsint的策略更值得实施(在这种情况下,KeyProcessor具有多个抽象方法,用不同的方法替换每个方法switch语句)。拥有一个这样的switch语句不一定是个坏主意。

另请参见Large Switch语句:错误的OOP?是:也许可以构造这些类型之一,使其包含适当的抽象方法。

评论


\ $ \ begingroup \ $
关键是我建立的一个枚举,值是String。相应地编辑了问题
\ $ \ endgroup \ $
–avalancha
2014-2-19在11:48

\ $ \ begingroup \ $
您可以使用策略模式将processStuff方法放入枚举。
\ $ \ endgroup \ $
– abuzittin gillifirca
2014年2月19日在11:59

\ $ \ begingroup \ $
@avalancha如何建立枚举值?除了创建枚举值,您还可以创建抽象类的子类实例(或选择子类的轻量级单例)吗?
\ $ \ endgroup \ $
– ChristW
2014年2月19日在16:59

#4 楼

您的枚举实际上应该是带有虚方法processStuff(string)的基类。
每个键都应该是它自己的类,并且应该实现虚方法。在OO风格上更真实。

在我看来,在这里应用策略模式不是一个好主意,因为它隐藏了核心设计问题并创建了许多样板。当然,我一般都不喜欢该策略模式:


策略模式表面上很漂亮,但是策略对象通常是无状态的,这意味着它们确实是无状态的只是变相的一阶函数。 (如果它们具有状态,那么它们就是伪装的Closures。)等等。您要么得到了,因为您之前已经进行过函数式编程,或者您没有这样做,在这种情况下,我听起来像是个胡言乱语的白痴,所以我想把它总结一下。


https://sites.google.com/site/steveyegge2/singleton-considered-stupid

评论


\ $ \ begingroup \ $
+1。它没有两个相似的对象继承树(有气味);将数据和操作封装在同一类中;如果您有一个新密钥,则只需创建一个新的子类,而不必在多个地方修改代码,就不会忘记在开关中添加一个大小写,等等。(单责任原则,DRY);而且你没有一堂课,只有一堂课。 (但是我会使用接口,而不是抽象的父类。
\ $ \ endgroup \ $
–palacsint
2014年2月20日在21:59

#5 楼

@palacsint的策略和代码的一个附加功能是使用Reflection自动将实现KeyProcessor的类绑定到字典。这样,您不必手动添加每个KeyProcessor,也不必记住它。

请确保只“反射”一次,因为它相对较慢。因此,将其添加到静态块或单例的构造函数中。

评论


\ $ \ begingroup \ $
我认为使用反射不是一个好的建议
\ $ \ endgroup \ $
–冈兹
2014年2月19日14:23在

\ $ \ begingroup \ $
@GonzaloNaveira:请详细说明。海事组织没有记住,而是通过反思来实施规则。
\ $ \ endgroup \ $
– M. Mimpen
2014年2月19日14:25

\ $ \ begingroup \ $
@Mimpen在这种情况下,IMO是不值得的,我什至没有提到有关性能问题,重构和容易出错的问题。另一方面,是的,某些事物的反思是伟大的。但不总是。
\ $ \ endgroup \ $
–冈兹
2014年2月19日,14:45

#6 楼

与@M一致。 Mimpen的答案是,获取Processor对象的所有方法并将其存储在Map<MyKeyEnum, Method>中,其中的键引用参数map中的键。

另一种替代方法是,每当将一个条目添加到您的map(传递给该方法的那个)时,就将一个条目放置到Map中。 >