简体   繁体   English

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

[英]Java Design: dealing with data objects used by many classes

First, a brief description of the library that brought this up: 首先,简要介绍了提出这个问题的图书馆:

I have a library that listens continuously on a provided serial port, reading in blocks of bytes and passing them along to be processed in some meaningful way (details aren't important to the question). 我有一个库,它在一个提供的串口上连续监听,读取字节块并传递它们以一些有意义的方式处理(细节对问题并不重要)。 To make the library a bit more reusable, processing these bytes was abstracted out to an interface (FrameProcessor). 为了使库更可重用,处理这些字节被抽象出一个接口(FrameProcessor)。 A few default implementations exist in the library itself to handle processing that's always going to occur regardless of the application using it. 库本身中存在一些默认实现来处理始终会发生的处理,而不管使用它的应用程序如何。 However, there's support for adding in custom processors to do things the application specifically cares about. 但是,支持添加自定义处理器来执行应用程序特别关注的操作。

Aside from the bytes being passed to these processors, there's a data object (ReceiverData) that contains information that most (but not guaranteed to be all) of the processors might find interesting. 除了传递给这些处理器的字节之外,还有一个数据对象(ReceiverData),其中包含大多数(但不保证是全部)处理器可能感兴趣的信息。 It's maintained entirely by the library itself (ie it isn't the responsibility of the application to setup/maintain any instances of ReceiverData. They shouldn't have to care how the data is made available, just that it is available). 它完全由库本身维护(即应用程序不负责设置/维护ReceiverData的任何实例。他们不应该关心数据是如何可用的,只是它可用)。

Right now, ReceiverData is being passed as a parameter to each processor: 现在,ReceiverData作为参数传递给每个处理器:

public interface FrameProcessor {

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

However, I really don't like this approach, as it's requiring data to be passed to something that might not necessarily care about it. 但是,我真的不喜欢这种方法,因为它需要将数据传递给可能不一定关心它的东西。 Also, for the processors that do care about ReceiverData, they've got to pass the object reference around in whatever other method calls they make (provided those method calls need to access that data). 此外,对于关心ReceiverData的处理器,他们必须在它们所做的任何其他方法调用中传递对象引用(假设这些方法调用需要访问该数据)。

I've considered changing the FrameProcessor to an abstract class and then defining a setter for a protected ReceiverData member. 我已经考虑将FrameProcessor更改为抽象类,然后为受保护的ReceiverData成员定义一个setter。 But that seems kind of gross as well - having to iterate over the list of all FrameProcessors and setting the ReceiverData instance. 但这看起来有点粗糙 - 必须迭代所有FrameProcessors的列表并设置ReceiverData实例。

I've also thought about some kind of static threaded context object(necessarily threaded since the library supports listening on multiple ports at once). 我也考虑过某种静态线程上下文对象(必须是线程的,因为库支持一次监听多个端口)。 Essentially, you'd have something like the following: 基本上,你有类似以下的东西:

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);
    }
}

That way, when each thread in the library started, it could just add a reference to its ReceiverData to ThreadedContext, which would then be available as needed to the processors without needing to pass it around. 这样,当库中的每个线程启动时,它可以只将其ReceiverData的引用添加到ThreadedContext,然后根据需要将其提供给处理器而无需传递它。

This is certainly a pedantic question, as I've already got a solution that works fine. 这肯定是一个迂腐的问题,因为我已经有一个解决方案可以正常工作。 It just bothered me. 它只是困扰我。 Thoughts? 思考? Better approaches? 更好的方法?

I like your current approach the best. 我最喜欢你当前的方法。 It's inherently thread-safe (because stateless). 它本质上是线程安全的(因为无状态)。 It allows for the same processor to be used by multiple threads. 它允许多个线程使用相同的处理器。 It's easy to understand and use. 它易于理解和使用。 It's very similar to, for example, the way a servlet works: the request and the response objects are passed to the servlet, even if they don't care about them. 它与servlet的工作方式非常相似:请求和响应对象都被传递给servlet,即使它们不关心它们。 And it's also very easy to unit test, because you don't have to setup a thread-local context to be able to test a processor. 而且单元测试也很容易,因为您不必设置线程本地上下文就能测试处理器。 You just pass a ReceiverData (real or fake), and that's it. 你只需传递一个ReceiverData(真实的或假的),就是这样。

You could, instead of passing a byte array and a ReceiverData, mix both in a single argument. 你可以在一个参数中混合使用,而不是传递一个字节数组和一个ReceiverData。

Encapsulate the byte[] and ReceiverData into a new class and pass that to the frame processors. byte[]ReceiverData封装到一个新类中,并将其传递给帧处理器。 Not only does it mean they can pass the same, single object around to their own methods, but it allows for future expansion if necessary. 它不仅意味着它们可以将相同的单个对象传递给它们自己的方法,而且它还允许在必要时进行扩展。

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);
}

While this may seem like overkill and require the processors to make unnecessary method calls, you may find that you don't want to provide access to the raw byte array. 虽然这可能看起来有点过分并且需要处理器进行不必要的方法调用,但您可能会发现您不希望提供对原始字节数组的访问。 Perhaps you want to use a ByteChannel instead and provide read-only access. 也许您想要使用ByteChannel而是提供只读访问权限。 It depends on your library and how it's intended to be used, but you may find you can provide a nicer API inside Frame than a simple byte array. 这取决于您的库以及它的使用方式,但您可能会发现您可以在Frame内部提供比简单字节数组更好的API。

As the OP states, the problem with process(byte[] frame, ReceiverData data) is that ReceiverData may or may not be used by the implementation. 正如OP所述, process(byte[] frame, ReceiverData data)是实现可能使用或不使用ReceiverData Thus, it is "wrong" that process() has a dependency on ReceiverData . 因此, process()依赖于ReceiverData是“错误的”。 Instead, a FrameProcessor implementation should use a Provider that can provide the instance of ReceiverData for the current frame on-demand. 相反, FrameProcessor实现应该使用可以按需为当前帧提供ReceiverData实例的Provider

The example below illustrates this. 以下示例说明了这一点。 I've used dependency injection for clarity, but you can just as well pass those objects in the constructors. 为了清晰起见,我使用了依赖注入,但你也可以在构造函数中传递这些对象。 The FrameContext would use ThreadLocal<T> s, much like suggested in the OP. FrameContext将使用ThreadLocal<T> ,就像在OP中建议的那样。 See this link for implementation hints. 请参阅此链接以获取实施提示。 A DIY Provider<T> implementation would likely depend on FrameContext directly. DIY Provider<T>实现可能直接依赖于FrameContext

If you want to go this route, consider using a DI-framework such as Google Guice or CDI . 如果您想采用这种方式,请考虑使用诸如Google GuiceCDI之类的DI框架。 Guice is probably easier when working with custom scopes. 使用自定义作用域时,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();
    }
}

This approach is very extensible; 这种方法非常易于扩展; future needed objects can be added to the context/scope object and corresponding providers injected into processors: 可以将未来所需的对象添加到上下文/范围对象以及注入处理器的相应提供程序:

public class MyProcessor ... {

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

As you can see from this example, this approach will also allow you to avoid future situations where you would add "sub-objects" to ReceiverData , leading to a kitchen-sink object situation (for example, ReceiverData.metaData , ReceiverData.frameSource , ... ). 正如您在此示例中所看到的,此方法还允许您避免将来向ReceiverData添加“子对象”的情况,从而导致厨房ReceiverData器对象情况(例如, ReceiverData.metaDataReceiverData.frameSource , ......)

Note: Ideally, you'd have processing objects with a lifetime equal to a single frame. 注意:理想情况下,您的处理对象的生命周期等于单个帧。 Then you could just declare (and inject!) the dependencies for processing a single frame in the constructor and create a new processor for each frame. 然后你可以声明(并注入!)依赖项来处理构造函数中的单个帧,并为每个帧创建一个新的处理器。 But I assume you are processing a lot of frames and therefore want to stick with the current approach for performance reasons. 但我认为你正在处理大量的帧,因此出于性能原因需要坚持使用当前的方法。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM