如果类型很多,是否应该不按对象类型子类化?
我正在处理事件日志,其中大约有60种不同的"类型"事件。每个事件共享大约10个属性,然后有共享不同属性的事件子类别。
我如何处理这些事件确实取决于它们的类型或者实现的分类接口。
但这似乎导致代码膨胀。我在子类方法中有很多冗余,因为它们实现了某些相同的接口。
使用具有"类型"属性的单个事件类并编写检查类型并维护某种类型类别组织的逻辑(例如,类别为a的事件类型列表,类别b为第二个列表,等等)?还是在这种情况下子类设计更合适?
第一种方法:
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看起来更合适,但还是最好不要这样做?
解决方案
我将使用每个事件类型的对象的解决方案,但是我将通常使用的接口组合归类在(可能是抽象的)类下,以提供其基本实现。这极大地减少了由于具有许多接口而产生的代码膨胀,但另一方面,却增加了类的数量。但是,如果正确合理地使用它,则可以使代码更简洁。
仅拥有大量的.java文件并不一定很糟糕。如果我们可以有意义地提取少量(2-4个左右)表示类契约的接口,然后将所有实现打包在一起,那么即使使用60个实现,我们提供的API也可以非常干净。
我可能还会建议使用一些委托或者抽象类来引入通用功能。委托和/或者抽象帮助程序应全部为程序包私有或者类私有的,在我们公开的API之外不可用。
如果存在大量的行为混合和匹配,我会考虑使用其他对象的组合,然后让特定事件类型对象的构造函数创建这些对象,或者使用构建器创建该对象。
也许是这样的?
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() ); } }
可能可以进行一些改进,但这可能会使我们入门。
如果我们决定扩展一个对象的抽象基类,则继承可能会受到限制。
特定的类别接口,因为我们可能还需要实现另一个类别。
因此,这是一种建议的方法:
假设我们需要为特定的Category接口方法使用相同的实现(与Event无关),则可以为每个Category接口编写一个实现类。
因此,我们将拥有:
public class Category1Impl implements Category1 { ... } public class Category2Impl implements Category2 { ... }
然后,对于每个Event类,只需指定其实现的Category接口,并保留Category实现类的私有成员实例即可(因此,我们使用组合,而不是继承)。对于每个Category接口方法,只需将方法调用转发到Category实现类。
这取决于每种事件类型固有地具有事件本身可以执行的不同行为。
事件对象是否需要每种类型的行为不同的方法?如果是这样,请使用继承。
如果不是,请使用枚举对事件类型进行分类。
由于我并没有真正找到答案,因此我根据自己不太理想的学习经历提供了自己的最佳猜测。
事件本身实际上没有行为,只有事件的处理程序才具有行为。这些事件仅代表数据模型。
我重写了代码,只将事件视为属性的对象数组,以便可以使用Java的新变量参数和自动装箱功能。
通过此更改,我能够删除大约100个巨大的代码类,并在单个类中的大约10行代码中完成许多相同的逻辑。
获得的经验:将OO范例应用于数据模型并不总是明智的。在使用大型可变域时,不要专注于通过OO提供完美的数据模型。 OO设计有时比模型给控制器带来更多好处。也不要将注意力放在预先优化上,因为通常10%的性能损失是可以接受的,并且可以通过其他方式重新获得。
基本上,我过度设计了这个问题。事实证明,在这种情况下,正确的面向对象设计过于夸张,将一个晚上的项目变成了三个月的项目。当然,我必须努力学习!