Java Design:处理许多类使用的数据对象

时间:2012-05-20 20:53:16

标签: java data-objects

首先,简要描述了提起这个问题的图书馆:

我有一个库,它在一个提供的串口上连续监听,读取字节块并传递它们以便以一些有意义的方式处理(细节对于这个问题并不重要)。为了使库更可重用,处理这些字节被抽象出一个接口(FrameProcessor)。库本身中存在一些默认实现来处理始终会发生的处理,而不管使用它的应用程序如何。但是,支持添加自定义处理器来执行应用程序特别关注的事情。

除了传递给这些处理器的字节之外,还有一个数据对象(ReceiverData),其中包含大多数(但不保证是全部)处理器可能感兴趣的信息。它完全由库本身维护(即应用程序不负责设置/维护ReceiverData的任何实例。他们不应该关心数据是如何可用的,只是它可用)。

现在,ReceiverData作为参数传递给每个处理器:

public interface FrameProcessor {

    public boolean process(byte[] frame, ReceiverData receiverData);
}

但是,我真的不喜欢这种方法,因为它要求将数据传递给可能不一定关心它的东西。此外,对于关心ReceiverData的处理器,他们必须在它们进行的任何其他方法调用中传递对象引用(假设这些方法调用需要访问该数据)。

我考虑过将FrameProcessor更改为抽象类,然后为受保护的ReceiverData成员定义一个setter。但这似乎也很糟糕 - 必须迭代所有FrameProcessors的列表并设置ReceiverData实例。

我还考虑过某种静态线程上下文对象(必须是线程的,因为库支持一次监听多个端口)。从本质上讲,您将拥有以下内容:

public class ThreadedContext {

    private static Map<Long, ReceiverData> receiverData;

    static {
        receiverData = new HashMap<Long, ReceiverData>();
    }

    public static ReceiverData get() {
        return receiverData.get(Thread.currentThread().getId());
    }

    public static void put(ReceiverData data) {
        receiverData.put(Thread.currentThread().getId(), data);
    }
}

这样,当库中的每个线程都启动时,它可以只将其ReceiverData的引用添加到ThreadedContext,然后根据需要将其提供给处理器,而无需传递它。

这肯定是一个迂腐的问题,因为我已经有一个解决方案可以正常工作。它只是困扰我。思考?更好的方法?

3 个答案:

答案 0 :(得分:3)

我最喜欢你当前的方法。它本质上是线程安全的(因为无状态)。它允许多个线程使用相同的处理器。它易于理解和使用。它与servlet的工作方式非常相似:请求和响应对象都被传递给servlet,即使它们不关心它们。而且单元测试也很容易,因为您不必设置线程本地上下文就能测试处理器。你只需传递一个ReceiverData(真实的或假的),就是这样。

您可以,而不是传递一个字节数组和一个ReceiverData,将它们混合在一个参数中。

答案 1 :(得分:1)

byte[]ReceiverData封装到新类中,并将其传递给帧处理器。它不仅意味着它们可以将相同的单个对象传递给它们自己的方法,而且它还允许在必要时进行扩展。

public class Frame {
    private byte[] rawBytes;
    private ReceiverData receiverData;

    public ReceiverData getReceiverData() { return receiverData; }
    public byte[] getRawBytes() { return frame; }
}

public interface FrameProcessor {
    public boolean process(Frame frame);
}

虽然这看起来有些过度并且需要处理器进行不必要的方法调用,但您可能会发现您不希望提供对原始字节数组的访问。也许您想要使用ByteChannel代替并提供只读访问权限。这取决于您的库以及它的使用方式,但您可能会发现在Frame内可以提供比简单字节数组更好的API。

答案 2 :(得分:0)

正如OP所述,process(byte[] frame, ReceiverData data)的问题是实现可能会或可能不会使用ReceiverData。因此,process()依赖于ReceiverData是“错误的”。相反,FrameProcessor实现应该使用Provider,可以按需为当前帧提供ReceiverData的实例。

以下示例说明了这一点。为了清晰起见,我使用了依赖注入,但你也可以在构造函数中传递这些对象。 FrameContext将使用ThreadLocal<T>,与OP中的建议非常相似。有关实施提示,请参阅this link。 DIY Provider<T>实施可能会直接依赖FrameContext

如果您想走这条路线,请考虑使用DI框架,例如Google GuiceCDI。使用自定义范围时,Guice可能更容易。

public class MyProcessor implements FrameProcessor {

    @Inject
    private Provider<ReceiverData> dataProvider;

    public boolean process(byte[] frame) {
        ...
        ReceiverData data = dataProvider.get();
        ...
    }
}

public class Main {

    @Inject
    private FrameContext context;

    public void receiveFrame(byte[] frame, ... ) {

        context.begin();
        ...
        context.setReceiverData(...); // receiver data is thread-local
        ...

        for (FrameProcessor processor : processors)
            processor.process(frame);

        context.end();
    }
}

这种方法非常具有可扩展性;未来所需的对象可以添加到上下文/范围对象中,并将相应的提供程序注入到处理器中:

public class MyProcessor ... {

    @Inject private Provider<FrameMetaData>;
    @Inject private Provider<FrameSource>;
    ...
}

从这个例子中可以看出,这种方法还可以避免将来将“子对象”添加到ReceiverData的情况,从而导致厨房接收器对象情况(例如{ {1}},ReceiverData.metaData,...)。

注意:理想情况下,您的处理对象的生命周期等于单个帧。然后你可以声明(并注入!)依赖项来处理构造函数中的单个帧,并为每个帧创建一个新的处理器。但我认为你正在处理大量的帧,因此出于性能原因需要坚持使用当前的方法。