Java Design: Working with Data Objects Used by Many Classes

First, a brief description of the library that produced this:

I have a library that constantly listens on the provided serial port, reads in byte blocks and passes them to handle them in some meaningful way (details are not important for the question). To make the library somewhat more reusable, the processing of these bytes was abstracted with an interface (FrameProcessor). In the library itself, there are several default implementations for processing processing, which will always occur regardless of the application used. However, there is support for adding to user processors to do what is especially relevant for the application.

In addition to the bytes transmitted to these processors, there is a data object (ReceiverData) that contains information that may be of interest to most (but not guaranteed to all) processors. It is fully supported by the library itself (i.e. it is not the responsibility of the application to configure / maintain any instances of ReceiverData. They do not need to worry about how the data becomes available, just to make it available).

Currently, ReceiverData is passed as a parameter for each processor:

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

However, I really do not like this approach, since it requires transferring data to something that may not necessarily take care of it. In addition, for processors that care about ReceiverData, they need to pass an object reference in all other method calls that they make (provided that these method calls must access this data).

I examined the possibility of changing FrameProcessor to an abstract class, and then defining a setter for the protected member of ReceiverData. But this also seems rude - the need to iterate over the list of all FrameProcessors and install an instance of ReceiverData.

I also thought of some kind of static streaming context object (always numbered, since the library supports listening on several ports at once). Essentially, you 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); } } 

Thus, when each thread in the library started, it could simply add a link to its ReceiverData in the ThreadedContext, which would then be available as needed for processors without the need to transfer it.

This is definitely a pedantic question, as I already have a solution that works great. It only bothered me. Thoughts? Best approaches?

+4
source share
3 answers

I like your current approach. It is essentially thread safe (because it is stateless). It allows you to use the same processor in multiple threads. It is easy to understand and use. It is very similar, for example, to how the servlet works: the request and response objects are passed to the servlet, even if they do not care about them. And it is also very easy for unit test, because you do not need to configure the local thread context in order to be able to test the processor. You just pass ReceiverData (real or fake) and what it is.

You could mix both in one argument instead of passing a byte array and ReceiverData.

+3
source

Encapsulate byte[] and ReceiverData into a new class and pass this to the frame processors. This not only means that they can transmit the same single object according to their methods, but if necessary, it can expand the future.

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

Although this may seem redundant and require processors to make unnecessary method calls, you may find that you do not want to provide access to an array of raw bytes. You might want to use ByteChannel instead and provide read-only access. It depends on your library and how it is intended to be used, but you can find a more convenient API inside Frame than a simple byte array.

+1
source

As the OP points out, the problem with process(byte[] frame, ReceiverData data) is that ReceiverData may or may not be used by the implementation. Thus, it is โ€œfalseโ€ that process() is dependent on ReceiverData . Instead, the implementation of FrameProcessor should use a Provider , which can provide an instance of ReceiverData for the current frame on request.

The example below illustrates this. I used dependency injection for clarity, but you can just pass these objects in the constructors. FrameContext will use ThreadLocal<T> s, as suggested in the OP. See this link for implementation tips. The implementation of the DIY Provider<T> is likely to be directly dependent on the FrameContext .

If you want to go this route, consider using a DI framework like Google Guice or CDI . Obviously, when working with custom areas, itโ€™s easier to use 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 required objects can be added to the context / scope object and the corresponding providers embedded in the 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 when you add โ€œsub-objectsโ€ to ReceiverData , which will lead to a situation with a kitchen sink object (for example, ReceiverData.metaData , ReceiverData.frameSource , ... )

Note. Ideally, you will have processing objects with a lifetime of one frame. Then you can simply declare (and enter!) The Dependencies for processing one frame in the constructor and create a new processor for each frame. But I assume that you process a lot of frames and therefore want to stick with the current approach for performance reasons.

0
source

Source: https://habr.com/ru/post/1413434/


All Articles