不該使用 RxJava 的一些情況

zccv2873 8年前發布 | 20K 次閱讀 RxJava Android開發 移動開發

Reactive programming 是一種改變游戲規則的技術。如果您正確的使用它,則會改變您的編程方式。一年之前筆者(原文作者,下同)開始接觸 RxJava 并嘗試使用 RxJava 來處理 UI 事件(并且成為了 RxJavaFX 的管理者)。在使用 RxJava 一段時間后,筆者發現 RxJava 能干很多事。 并且改變了很多編程的方式和方法,從 并發到 IO 以及 業務邏輯和算法。

筆者開始到處使用 RxJava ,只要是可以用的地方就開始使用 RxJava,這樣也可以更快的掌握 RxJava 的用法。

一年以后,筆者發現 RxJava 并不總是最佳的解決方案。雖然,現在筆者所寫的每個應用都是 reactive 的,但是現在在有些地方可能會選擇不使用 reactive 的方式來編程。需要注意的是,把任何東西轉換為 Observable 是很容易的。 所以本篇文章主要介紹何時您提供的 API 應該返回非 Observable 的數據類型,如果客戶端調用你的 API 的時候想使用 Observable ,則他們自己可以很容易的轉換為 Observable。

第一種情況:簡單的常量集合數據

這是最簡單的一種不適合使用 Observable 的情況。例如有個如下的 enum 類型定義:

public enum EmployeeType {
    FULL_TIME,
    CONTRACTOR,
    INTERN
}
 

如果你想遍歷這個枚舉類型,通過下面的方式把它轉換成 Observable 是不是適用所有情況呢?

Observable<EmployeeType> employeeTypes = Observable.from(Employee.values());
 

如果你已經在使用 Observable 的操作符來操作數據了,這個時候使用 Observable 版本的 EmployeeType 比較合適。但是通常情況下不是這樣的。

簡單的常量數據并不太適合轉換為 Observable 。從 API 的角度來說, 使用傳統的方式來返回這種數據類型是比較好的情況。如果調用 API 的人想使用 Observable 則可以很容易的完成轉換。

第二種情況:開銷比較大的、緩存的對象

假設有個用來執行正則表達式搜索的 ActionQualifier 類,由于編譯正則表達式是非常耗時的,所以不停的創建新的 ActionQualifier 對象是很不明智的:

public final class ActionQualifier {
 
    private final PatterncodeRegexPattern;
    private final int actionNumber;
 
    ActionQualifier(String codeRegex, int actionNumber) {
        this.codeRegexPattern = Pattern.compile(codeRegex);
        this.actionNumber = actionNumber;
    }
 
    public boolean qualify(String code) {
        return codeRegexPattern.matcher(code).find();
    }
    public int getActionCode() {
        return actionNumber;
    }
}
 

如果你使用 RxJava-JDBC 并且在操作過程中使用了 ActionQualifier 對象,當有多個訂閱者訂閱到這個查詢數據的 Observable 上的時候,由于每個訂閱都會重新查詢數據庫,所以創建新的 ActionQualifier 是非常耗時的:

Observable<ActionQualifier> actionQualifiers = db
    .select("SELECT CODE_REGEX, ACTION_NUMBER FROM ACTION_MAPPING")
    .get(rs -> new ActionQualifier(rs.getString("CODE_REGEX"), rs.getInt("ACTION_NUMBER")));
 

為了避免每個訂閱者訂閱的時候都查詢一次,你可以選擇使用 cache() 來緩存數據。這樣的話,actionQualifiers 就沒法更新了并且可能長期持有,導致虛擬機無法回收該對象。

 Observable<ActionQualifier> actionQualifiers = db
    .select("SELECT CODE_REGEX, ACTION_NUMBER FROM ACTION_MAPPING")
    .get(rs -> new ActionQualifier(rs.getString("CODE_REGEX"), rs.getInt("ACTION_NUMBER")))
    .cache();
 

Dave Moten 提供了一個 巧妙的解決方式 ,緩存設定過期時間,然后重新訂閱到源 Observable。但是最終你可能會問,是否可以把 actionQualifiers 保存在一個 List 中,然后手工的去刷新。

List<ActionQualifier> actionQualifiers = db
    .select("SELECT CODE_REGEX, ACTION_NUMBER FROM ACTION_MAPPING")
    .get(rs -> new ActionQualifier(rs.getString("CODE_REGEX"), rs.getInt("ACTION_NUMBER")))
    .toList().toBlocking().first();
 

當你需要使用 Observable 的時候,則可以很容易的把 List 轉換為 Observable :

 Observable.from(actionQualifiers).filter(aq -> aq.qualify("TXB.*"));
 

不管使用哪種方式,緩存大量的消耗資源的對象都是很不好處理的。并且使用 Observable 的 cache 還會導致內存占用,根據您的具體情況,你可以靈活選擇使用哪種方式。

第三種情況:簡單的查看和單步的操作(Simple “Lookups” and Single-Step Monads)

RxJava 的優勢之一是可以很容易的組合很多操作函數。Take these, then filter that, map to this, and reduce to that.

Observable<Product> products = ...
 
Observable<Int> totalSoldUnits = products
    .filter(pd -> pd.getCategoryId() == 3)
    .map(pd -> pd.getSoldUnits())
    .reduce(0,(x,y) -> x + y)
 

如果只是簡單的一步操作呢?

Observable<Category> category = Category.forId(263);
 

這種情況是不是濫用 Observable 呢? 直接返回數據是不是更好呢?

Categorycategory = Category.forId(263);
 

如果返回的結果有多個 Category 、或者你不想處理返回數據為 null 的情況,則會使用 Observable 。但是在下面的示例中會看到,這種過度的使用 Observable 會導致更多的模板代碼出現。

如果你非要這樣用,則使用的時候,可以很容易的轉換為 Observable:

Observable<Category> category = Observable.just(Category.forId(263))
    .filter(c -> c != null);
 

第四種情況:經常使用到的屬性(Frequently Qualified Properties)

先解釋下上面提到的情況,比如有下面一個 Product 類

public final class Product { 
    private final int id;
    private final String description;
    private final int categoryId;
 
    public Product(int id, String description, int categoryId) { 
        this.id = id;
        this.description = description;
        this.categoryId = categoryId;
    }
    public Observable<Category> getCategory() { 
        return Category.forId(categoryId);
    }
}
 

上面的 getCategory() 返回的是 Observable 。如果你經常使用這個函數,則用起來可能相當麻煩。假設每個 Category 中有個 getGroup() 函數返回一個整數,代表所在的分組。可以根據這個分組來過濾每個組的分類:

Observable<Product> products = ...
 
Observable<Product> productsInGroup5 = 
    products.flatMap(p -> p.getCategory().filter(c -> c.getGroup() == 5).map(p -> c));
 

如此簡單的一個需求,代碼看起來居然這么復雜到使用了 FlatMap 。如果 getCategory() 返回的是 category 對象則用起來就相當簡單了:

public CategorygetCategory() { 
    return Category.forId(categoryId);
}
 
Observable<Product> productsInGroup5 = 
    products.filter(p -> p.getCategory().getGroup() == 5);
 

所以針對這種經常使用到的函數或者屬性,最好不要返回 Observable 的形式。

第五種情況:有狀態的對象(Capturing State)

RxJava 中的數據通常是無狀態的。這樣可以方便并行處理多個操作。但是在實際的業務邏輯中,通常需要保留一些狀態。比如打印價格時候,需要保留歷史價格信息:

public final class PricePoint { 
    private final int id;
    private final int productId;
    private final BigDecimalprice;
    private final ImmutableList<BigDecimal> historicalPricePoints;
 
    public PricePoint(int id, int productId, BigDecimalprice) { 
        this.id = id;
        this.productId = productId;
        this.price = price;
        historicalPricePoints = HistoricalPricePoints.forProductId(productId);
    }
    public ImmutableList<BigDecimal> getHistoricalPricePoints() { 
        return historicalPricePoints;
    }
}
 

然后通過 Observable 的方式來獲取歷史信息:

public final class PricePoint { 
    private final int id;
    private final int productId;
    private final BigDecimalprice;
 
    public PricePoint(int id, int productId, BigDecimalprice) { 
        this.id = id;
        this.productId = productId;
        this.price = price;
    }
    public Observable<BigDecimal> getHistoricalPricePoints() { 
        return HistoricalPricePoints.forProductId(productId);
    }
}
 

但是如果這個操作是比較消耗資源的,則又回到了第二種情況。

針對這種有狀態的數據,還是使用傳統的查詢方式比較好。

總結

使用了 RxJava,嘗到了 Rxjava 的好處,您會到處使用它,但是 RxJava 是用來解決比較復雜或者非常復雜情況的,對于簡單的情況還是簡單處理吧。凡事要把握好度,過猶不及! 上面的一些情況,對于有經驗的 RxJava 開發者可能很容易避免這種情況,對于初學者可能還處于 使用 RxJava 的蜜月期,看問題沒這么透徹很容易陷入到過度使用 RxJava 的情況。

再次提醒,以上只是筆者自己的主觀意見,如果有不同見解,歡迎一起討論交流。

 

來自:http://blog.chengyunfeng.com/?p=1009

 

 本文由用戶 zccv2873 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!