首先,简要描述了提起这个问题的图书馆:
我有一个库,它在一个提供的串口上连续监听,读取字节块并传递它们以便以一些有意义的方式处理(细节对于这个问题并不重要)。为了使库更可重用,处理这些字节被抽象出一个接口(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,然后根据需要将其提供给处理器,而无需传递它。
这肯定是一个迂腐的问题,因为我已经有一个解决方案可以正常工作。它只是困扰我。思考?更好的方法?
答案 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 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
的情况,从而导致厨房接收器对象情况(例如{ {1}},ReceiverData.metaData
,...)。
注意:理想情况下,您的处理对象的生命周期等于单个帧。然后你可以声明(并注入!)依赖项来处理构造函数中的单个帧,并为每个帧创建一个新的处理器。但我认为你正在处理大量的帧,因此出于性能原因需要坚持使用当前的方法。