【问题标题】:Java refactoring code duplicationJava重构代码重复
【发布时间】:2017-10-26 01:31:04
【问题描述】:

我有很多重复的代码,但我不知道如何正确重构它。

我有一个类Foo,它解析从套接字接收的网络消息/数据并调用相应的onEvent() 方法。 Foo 纯粹是一个网络消息的解析器,它没有关于它接收到的事件采取什么行动的逻辑。谁想要添加这样的逻辑必须继承Foo 并覆盖onEvent() 方法。

abstract class Foo {
    void processNetwotkMessage(String message) {
        ...
        onEvent1(arg1, arg2, arg3)
        reutn;
        ...
        onEvent2(arg4);
        return;
        ...
        onEvent3()
        return;
        ...
        onEvent999(arg1337);
    }

    abstract protected void onEvent1(Arg1 arg1, Arg2 arg2, Arg3 arg3);
    abstract protected void onEvent2(Arg4 arg4);
    abstract protected void onEvent3();
    ...
    abstract protected void onEvent999(Arg1337 arg1337);
}

现在,我的程序应该是模块化的,我有许多单独的模块类想要接收这些事件并处理它们。模块实现Plugin 接口。该接口匹配来自FooonEvent() 方法,但添加了PluginContext ctx 作为第一个参数。

interface Plugin {
    void onEvent1(PluginContext ctx, Arg1 arg1, Arg2 arg2, Arg3 arg3);
    void onEvent2(PluginContext ctx, Arg4 arg4);
    void onEvent3(PluginContext ctx);
    ...
    void onEvent999(PluginContext ctx, Arg1337 arg1337);
}

现在,为了将事件分派给模块,我创建了 Foo 的模块感知子类,名为 PluginSupporingFoo

class PluginSupporingFoo extends Foo implements PluginContext {
    List<Plugin> plugins;

    @Override
    protected void onEvent1(Arg1 arg1, Arg2 arg2, Arg3 arg3) {
        synchronized (plugins) {
            for (Plugin p : plugins) {
                p.onEvent1(this, arg1, arg2, arg3);
            }
        }
    }

    @Override
    protected void onEvent2(Arg4 arg4) {
        synchronized (plugins) {
            for (Plugin p : plugins) {
                if (PluginIsAllowedToBeAwareOfThisEvent(p, arg4)) {
                    p.onEvent2(this, arg4);
                }
            }
        }
    }

    @Override
    protected void onEvent3() {
        synchronized (plugins) {
            for (Plugin p : plugins) {
                p.onEvent3(this);
            }
        }
    }

    ...

    @Override
    protected void onEvent999(Arg1337 arg1337) {
        synchronized (plugins) {
            for (Plugin p : plugins) {
                p.onEvent999(this, arg1337);
            }
        }
    }
}

如您所见,每当Foo 调用onEvent() 方法之一时,来自PluginSupporingFoo 的被覆盖方法被调用,然后它通过调用@987654339 的相应onEvent() 方法将此事件分派给所有模块@interface,增加了一个额外的参数——PluginContext ctx。有时还有一个条件是否告诉模块一个事件,就像你在PluginSupporingFoo.onEvent2()中看到的那样。

现在,我想删除很多重复的代码。

  1. 首先,Plugin 接口和Foo 类具有几乎相同的方法。实际上,Plugin 接口需要具有 Foo 具有的所有 onEvent 方法,但 PluginContext ctx 作为额外的第一个参数。

  2. 另一个代码重复在PluginSupporingFoo。所有onEvent() 方法几乎都是彼此的副本:

.

protected void on${EventName} ( ${ArgList} ) {
    synchronized (plugins) {
        for (Plugin p : plugins) {
            ${ OPTIONAL: if (filter(p, ${ArgList}.arg1)) { }
                p.on{EventName}(this, ${ArgList}.allArgs);
            ${ OPTIONAL: } }
        }
    }
}

鉴于onEvent 方法有很多,拥有如此多的复制粘贴代码令人沮丧,而且如果需要,很难全部修改它们。

【问题讨论】:

  • 从 Java 8 开始我们有默认方法,这意味着你不需要接口和抽象类。您可以使用该接口并为其某些方法提供默认实现。这将消除大部分代码重复。当你实现接口时,你只需要实现你没有提供实现的方法 - 除非有一个你想要重写的实现。
  • 请注意接口和抽象类中的方法有不同数量的参数,因此您的建议将不起作用。
  • 在那种情况下,拥有接口有什么意义呢?实现类是否覆盖了这两种方法(具有不同的签名)?
  • 请阅读我的问题,它解释了什么覆盖了什么。
  • 那么,抽象类Foo有什么用呢?两者都保持是没有意义的。其次,你有点实现观察者模式,所以看看它应该如何完成:如果某些插件应该只“观察”某些事件 - 你应该基于此将你的观察者和可观察对象拆分为不同的类型。这样做不仅会重构您的代码,还会通过删除以下行来减少代码:if (PluginIsAllowedToBeAwareOfThisEvent(p, arg4))

标签: java refactoring code-duplication


【解决方案1】:

哇...这是一个糟糕的设计,有 eventXX( foo, bar, baz) 等,因为每次添加新事件时,都必须添加相应的侦听器方法。

也许更好的设计是重构它,以便您的 Foo 类只有少数或理想情况下有一个采用新 Event 接口的 onEvent() 方法

public class Foo{

  void onEvent(Event e){ ... }

} 

public interface Event{
     Object[] getArgs();

     //other Event specific methods
     ...
}

那么每个eventXX 方法将是Event 接口的新实现。

public class Event2 implements Event{

   public Object[] getArgs(){
       //Arg4 like in your code
       return new Object[]{ new Arg4() };
   }
}

插件也可以同样只有一种方法

interface Plugin{

   onEvent(PluginContext ctx, Event e);

}

现在,当您需要添加一个新事件时,它只是一个新的事件实现,这些接口不需要任何额外的方法。

处理程序可以检查事件的类型,或者您可以根据需要制作EventType 或其他类型的鉴别器。

 class MyPlugin implements Plugin{
     public void onEvent(PluginContext ctx, Event e){
         //this is only useful if we only care about a few types
         if( e instanceOf Event2){
            //we know this is Arg4
            Arg4 arg4 = (Arg4) e.getArgs()[0];
            ...
         }

     }
 }

现在有了 Java Lambda,我们甚至可以拥有一个处理程序 Map&lt;Class&lt;? extends Event&gt;, Function&gt;,如果你想花哨的话。

【讨论】:

  • "因为每次添加新事件都必须添加相应的监听方法。"好吧,实际上有一个类,我们称之为BasePlugin,它实现了Plugin 接口,它的所有方法都有{}。这样,模块可以改为扩展该类并覆盖它们想要监听的任何事件的方法。这意味着如果我将一个新的事件方法添加到插件接口,模块不必实现一个新的事件方法,因为我将在每个模块扩展的BasePlugin 中添加一个{} 实现。
  • 您的解决方案在一方面很好,但另一方面很糟糕。这很好,因为它允许删除我抱怨的所有重复代码,本质上这回答了我原来的问题,但是你这样做的代价是每个模块现在都必须执行这个事件调度 instanceOf 事情并转换事件的参数进入正确的类型,这会将重复的代码推送到模块上,因为 module1 和 module2 需要为此仪式包含相同的代码,而我不必强制转换参数或弄清楚我原来的解决方案是什么事件。
  • 其实你不需要强制转换参数,只要让Event2public Arg4 getArg4(),所以模块之间没有代码重复,除了开关,完全没问题。我喜欢你的解决方案。
猜你喜欢
  • 2011-02-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-10-11
  • 2015-07-31
相关资源
最近更新 更多