[英]Java Design: dealing with data objects used by many classes
首先,簡要介紹了提出這個問題的圖書館:
我有一個庫,它在一個提供的串口上連續監聽,讀取字節塊並傳遞它們以一些有意義的方式處理(細節對問題並不重要)。 為了使庫更可重用,處理這些字節被抽象出一個接口(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,然后根據需要將其提供給處理器而無需傳遞它。
這肯定是一個迂腐的問題,因為我已經有一個解決方案可以正常工作。 它只是困擾我。 思考? 更好的方法?
我最喜歡你當前的方法。 它本質上是線程安全的(因為無狀態)。 它允許多個線程使用相同的處理器。 它易於理解和使用。 它與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
。
如果您想采用這種方式,請考慮使用諸如Google Guice或CDI之類的DI框架。 使用自定義作用域時,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
器對象情況(例如, ReceiverData.metaData
, ReceiverData.frameSource
, ......)
注意:理想情況下,您的處理對象的生命周期等於單個幀。 然后你可以聲明(並注入!)依賴項來處理構造函數中的單個幀,並為每個幀創建一個新的處理器。 但我認為你正在處理大量的幀,因此出於性能原因需要堅持使用當前的方法。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.