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



首先,简要介绍一下提出这个问题的库:

我有一个库,它可以连续侦听提供的串行端口,读取字节块并将它们传递以某种有意义的方式进行处理(细节对问题并不重要(。 为了使库更具可重用性,处理这些字节被抽象到一个接口(FrameProcessor(。 库本身中存在一些默认实现,用于处理无论使用何种应用程序都会发生的处理。 但是,支持添加自定义处理器来执行应用程序特别关心的操作。

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

目前,ReceiverData 作为参数传递给每个处理器:

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

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

我考虑过将 FrameProcessor 更改为抽象类,然后为受保护的 ReceiverData 成员定义一个 setter。 但这似乎也有点恶心 - 必须遍历所有FrameProcessor的列表并设置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,然后处理器就可以根据需要使用它,而无需传递它。

这当然是一个迂腐的问题,因为我已经有一个工作正常的解决方案。 这让我很困扰。 思潮? 更好的方法?

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

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

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。

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

下面的示例对此进行了说明。为了清楚起见,我使用了依赖注入,但您也可以在构造函数中传递这些对象。FrameContext将使用ThreadLocal<T>,就像OP中建议的那样。有关实现提示,请参阅此链接。DIY Provider<T>的实施可能直接取决于FrameContext

如果你想走这条路,考虑使用DI框架,如Google Guice或CDI。使用自定义范围时,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添加"子对象"的情况,从而导致厨房水槽对象的情况(例如,ReceiverData.metaDataReceiverData.frameSource、...(。

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

相关内容

  • 没有找到相关文章

最新更新