4

首先,简要介绍一下引发此问题的库:

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

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

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

public interface FrameProcessor {

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

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

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

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

4

3 回答 3

3

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

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

于 2012-05-20T21:08:22.440 回答
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);
}

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

于 2012-05-20T22:17:23.957 回答
0

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

下面的例子说明了这一点。为了清楚起见,我使用了依赖注入,但您也可以在构造函数中传递这些对象。将FrameContext使用ThreadLocal<T>s,就像 OP 中建议的那样。有关实施提示,请参阅此链接。DIYProvider<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的情况,从而导致厨房水槽对象情况(例如,、、ReceiverData.metaData... ReceiverData.frameSource)。

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

于 2012-05-20T22:47:16.780 回答