簡體   English   中英

Java Design:處理許多類使用的數據對象

[英]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 GuiceCDI之類的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.metaDataReceiverData.frameSource , ......)

注意:理想情況下,您的處理對象的生命周期等於單個幀。 然后你可以聲明(並注入!)依賴項來處理構造函數中的單個幀,並為每個幀創建一個新的處理器。 但我認為你正在處理大量的幀,因此出於性能原因需要堅持使用當前的方法。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM