1

我正在处理一个事件日志,其中有大约 60 种不同“类型”的事件。每个事件共享大约 10 个属性,然后有共享各种额外属性的事件子类别。

我如何处理这些事件确实取决于它们的类型或它们实现的分类接口。

但这似乎导致代码膨胀。我在子类方法中有很多冗余,因为它们实现了一些相同的接口。

使用具有“类型”属性的单个事件类并编写检查类型并维护类型类别的某种组织的逻辑是否更合适(例如,类别 a 的事件类型列表,类别 b 的第二个列表, ETC)?还是在这种情况下子类设计更合适?

第一种方法:

public interface Category1 {}
public interface Category2 {}

public abstract class Event {
 private base properties...;
}

public class EventType1 extends Event implements Category1, Category2 {
 private extra properties ...;
}

public class EventType2 extends Event implements Category3, Category4 {
 private extra properties ...;
}

第二种方法:

public enum EventType {TYPE1, TYPE2, TYPE3, ...}
public class Event {
 private union of all possible properties;
 private EventType type;
}

我个人的看法是,似乎单个事件对象是合适的,因为,如果我正确地考虑它,就没有必要使用继承来表示模型,因为实际上只有行为和我的条件会改变基于类型。

我需要有执行以下操作的代码:

if(event instanceof Category1) {
  ...
}

这在第一种方法中效果很好,因为我可以只调用事件上的方法并在每个适当的子类中实现“相同的代码”,而不是 instanceof。

但是第二种方法要简洁得多。然后我写了类似的东西:

if(CATEGORY1_TYPES.contains(event.getEventType()) {
 ...
}

而且我所有的“处理逻辑”都可以组织成一个类,并且没有一个是冗余地分布在子类中的。那么这种情况是不是虽然 OO 看起来更合适,但最好不要呢?

4

6 回答 6

1

我会使用每个事件类型的对象解决方案,但我会将常用的​​接口组合分组在(可能是抽象的)类下,提供它们的骨架实现。这大大减少了由于拥有许多接口而产生的代码膨胀,但另一方面,增加了类的数量。但是,如果使用得当和合理,它会导致代码更干净。

于 2008-09-28T22:54:31.047 回答
1

如果您决定扩展特定 Category 接口的抽象基类,继承可能会受到限制,因为您可能还需要实现另一个 Category。

因此,这里有一个建议的方法:假设您需要为特定的 Category 接口方法(不管 Event)提供相同的实现,您可以为每个 Category 接口编写一个实现类。

所以你会有:

public class Category1Impl implements Category1 {
    ...
}

public class Category2Impl implements Category2 {
    ...
}

然后对于您的每个 Event 类,只需指定它实现的 Category 接口,并保留 Category 实现类的私有成员实例(因此您使用组合,而不是继承)。对于每个 Category 接口方法,只需将方法调用转发到 Category 实现类即可。

于 2008-09-28T23:26:35.650 回答
1

由于我并没有真正得到我正在寻找的答案,因此我根据我不太理想的学习经验提供了我自己的最佳猜测。

事件本身实际上没有行为,只有事件的处理程序才有行为。事件仅代表数据模型。

我重写了代码,只将事件视为属性的对象数组,这样我就可以使用 Java 的新变量参数和自动装箱功能。

通过这个更改,我能够删除大约 100 个巨大的代码类,并在一个类中用大约 10 行代码完成大部分相同的逻辑。

经验教训:将 OO 范式应用于数据模型并不总是明智的。在处理大型可变域时,不要专注于通过 OO 提供完美的数据模型。OO 设计有时比模型更有利于控制器。也不要专注于前期优化,因为通常 10% 的性能损失是可以接受的,并且可以通过其他方式恢复。

基本上,我过度设计了这个问题。事实证明,在这种情况下,正确的 OO 设计过于矫枉过正,将一晚的项目变成了 3 个月的项目。当然,我必须努力学习!

于 2008-10-06T14:30:59.583 回答
0

仅仅拥有大量 .java 文件并不一定是坏事。如果您可以有意义地提取少量(2-4 个左右)表示类契约的接口,然后将所有实现打包起来,那么您提供的 API 可以非常干净,即使有 60 个实现。

我可能还建议使用一些委托或抽象类来引入通用功能。委托和/或抽象助手都应该是包私有或类私有的,在您公开的 API 之外不可用。

于 2008-09-28T23:15:56.537 回答
0

如果有相当多的行为混合和匹配,我会考虑使用其他对象的组合,然后让特定事件类型对象的构造函数创建这些对象,或者使用构建器来创建对象。

也许是这样的?

class EventType {

  protected EventPropertyHandler handler;

  public EventType(EventPropertyHandler h) {
     handler = h;
  }

  void handleEvent(map<String,String> properties) {
    handler.handle(properties);
  }
}

abstract class EventPropertyHandler {
   abstract void handle(map<String, String> properties);
}
class SomeHandler extends EventPropertyHandler {
   void handle(map<String, String> properties) {
      String value = properties.get("somekey");
      // do something with value..
   }
}

class EventBuilder {
   public static EventType buildSomeEventType() {
      // 
      EventType e = new EventType( new SomeHandler() );
   }
}

可能会有一些改进,但这可能会让你开始。

于 2008-09-28T23:19:03.097 回答
0

这取决于每种类型的事件是否固有地具有事件本身可以执行的不同行为。

您的 Event 对象是否需要每种类型行为不同的方法?如果是这样,请使用继承。

如果不是,请使用枚举对事件类型进行分类。

于 2008-09-29T19:54:12.580 回答