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

Java设计:处理许多类使用的数据对象,java,data-objects,Java,Data Objects,首先,简要介绍提出此问题的图书馆: 我有一个库,它可以在提供的串行端口上持续侦听,读取字节块,并将它们以某种有意义的方式传递给处理者(细节对问题并不重要)。为了使库更加可重用,处理这些字节被抽象为一个接口(FrameProcessor)。库本身中存在一些默认实现,用于处理始终会发生的处理,而不管应用程序使用它。但是,支持添加自定义处理器来完成应用程序特别关心的事情 除了传递给这些处理器的字节外,还有一个数据对象(ReceiverData),其中包含大多数(但不一定是全部)处理器可能感兴趣的信息。

首先,简要介绍提出此问题的图书馆:

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

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

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

public interface FrameProcessor {

    public boolean process(byte[] frame, ReceiverData receiverData);
}
然而,我真的不喜欢这种方法,因为它需要将数据传递给可能不一定关心它的东西。另外,对于关心ReceiverData的处理器,他们必须在任何其他方法调用中传递对象引用(前提是这些方法调用需要访问该数据)

我考虑将FrameProcessor更改为抽象类,然后为受保护的ReceiverData成员定义一个setter。但这似乎也有点恶心——必须遍历所有帧处理器的列表并设置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);
    }
}
公共类ThreadedContext{
私有静态地图接收数据;
静止的{
receiverData=新HashMap();
}
公共静态接收方数据获取(){
返回receiverData.get(Thread.currentThread().getId());
}
公开静态无效认沽权(接收方数据){
receiverData.put(Thread.currentThread().getId(),data);
}
}
这样,当库中的每个线程启动时,它只需将对其ReceiverData的引用添加到ThreadedContext,然后处理器就可以根据需要使用它,而无需传递它


这当然是一个迂腐的问题,因为我已经找到了一个很好的解决方案。这让我很烦恼。思想?更好的方法?

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


您可以在单个参数中混合使用字节数组和ReceiverData,而不是传递字节数组和ReceiverData。

字节[]
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
并提供只读访问。它取决于您的库以及打算如何使用它,但您可能会发现,您可以在
帧中提供比简单字节数组更好的API。

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

下面的例子说明了这一点。为了清晰起见,我使用了依赖项注入,但您也可以在构造函数中传递这些对象。
FrameContext
将使用
ThreadLocal
s,与OP中的建议非常相似。有关实现提示,请参阅。DIY
提供者
实现可能直接依赖于
FrameContext

如果您想走这条路线,请考虑使用DI框架,如OR。使用自定义作用域时,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();
    }
}
公共类MyProcessor实现FrameProcessor{
@注入
私人提供者数据提供者;
公共布尔进程(字节[]帧){
...
ReceiverData=dataProvider.get();
...
}
}
公共班机{
@注入
私人语境;
公共无效接收帧(字节[]帧,…){
context.begin();
...
context.setReceiverData(…);//接收器数据是线程本地数据
...
用于(帧处理器:处理器)
处理器。进程(帧);
context.end();
}
}
这种方法是非常可扩展的;可以将未来需要的对象添加到上下文/范围对象,并将相应的提供程序注入处理器:

public class MyProcessor ... {

    @Inject private Provider<FrameMetaData>;
    @Inject private Provider<FrameSource>;
    ...
}
公共类MyProcessor。。。{
@注入私人供应商;
@注入私人供应商;
...
}
尽你所能