不该使用 RxJava 的一些情况
zccv2873
8年前
<p>Reactive programming 是一种改变游戏规则的技术。如果您正确的使用它,则会改变您的编程方式。一年之前笔者(原文作者,下同)开始接触 RxJava 并尝试使用 RxJava 来处理 UI 事件(并且成为了 <a href="/misc/goto?guid=4959675807323568936" rel="nofollow,noindex">RxJavaFX</a> 的管理者)。在使用 RxJava 一段时间后,笔者发现 RxJava 能干很多事。 并且改变了很多编程的方式和方法,从 并发到 IO 以及 业务逻辑和算法。</p> <p>笔者开始到处使用 RxJava ,只要是可以用的地方就开始使用 RxJava,这样也可以更快的掌握 RxJava 的用法。</p> <p>一年以后,笔者发现 RxJava 并不总是最佳的解决方案。虽然,现在笔者所写的每个应用都是 reactive 的,但是现在在有些地方可能会选择不使用 reactive 的方式来编程。需要注意的是,把任何东西转换为 Observable 是很容易的。 所以本篇文章主要介绍何时您提供的 API 应该返回非 Observable 的数据类型,如果客户端调用你的 API 的时候想使用 Observable ,则他们自己可以很容易的转换为 Observable。</p> <h2>第一种情况:简单的常量集合数据</h2> <p>这是最简单的一种不适合使用 Observable 的情况。例如有个如下的 enum 类型定义:</p> <pre> <code class="language-java">public enum EmployeeType { FULL_TIME, CONTRACTOR, INTERN } </code></pre> <p>如果你想遍历这个枚举类型,通过下面的方式把它转换成 Observable 是不是适用所有情况呢?</p> <pre> <code class="language-java">Observable<EmployeeType> employeeTypes = Observable.from(Employee.values()); </code></pre> <p>如果你已经在使用 Observable 的操作符来操作数据了,这个时候使用 Observable 版本的 EmployeeType 比较合适。但是通常情况下不是这样的。</p> <p>简单的常量数据并不太适合转换为 Observable 。从 API 的角度来说, 使用传统的方式来返回这种数据类型是比较好的情况。如果调用 API 的人想使用 Observable 则可以很容易的完成转换。</p> <h2>第二种情况:开销比较大的、缓存的对象</h2> <p>假设有个用来执行正则表达式搜索的 ActionQualifier 类,由于编译正则表达式是非常耗时的,所以不停的创建新的 ActionQualifier 对象是很不明智的:</p> <pre> <code class="language-java">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; } } </code></pre> <p>如果你使用 <a href="/misc/goto?guid=4959675807409514862" rel="nofollow,noindex">RxJava-JDBC</a> 并且在操作过程中使用了 ActionQualifier 对象,当有多个订阅者订阅到这个查询数据的 Observable 上的时候,由于每个订阅都会重新查询数据库,所以创建新的 ActionQualifier 是非常耗时的:</p> <pre> <code class="language-java">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"))); </code></pre> <p>为了避免每个订阅者订阅的时候都查询一次,你可以选择使用 cache() 来缓存数据。这样的话,actionQualifiers 就没法更新了并且可能长期持有,导致虚拟机无法回收该对象。</p> <pre> <code class="language-java"> 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(); </code></pre> <p>Dave Moten 提供了一个 <a href="/misc/goto?guid=4959675807481908811" rel="nofollow,noindex">巧妙的解决方式</a> ,缓存设定过期时间,然后重新订阅到源 Observable。但是最终你可能会问,是否可以把 actionQualifiers 保存在一个 List 中,然后手工的去刷新。</p> <pre> <code class="language-java">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(); </code></pre> <p>当你需要使用 Observable 的时候,则可以很容易的把 List 转换为 Observable :</p> <pre> <code class="language-java"> Observable.from(actionQualifiers).filter(aq -> aq.qualify("TXB.*")); </code></pre> <p>不管使用哪种方式,缓存大量的消耗资源的对象都是很不好处理的。并且使用 Observable 的 cache 还会导致内存占用,根据您的具体情况,你可以灵活选择使用哪种方式。</p> <h2>第三种情况:简单的查看和单步的操作(Simple “Lookups” and Single-Step Monads)</h2> <p>RxJava 的优势之一是可以很容易的组合很多操作函数。Take these, then filter that, map to this, and reduce to that.</p> <pre> <code class="language-java">Observable<Product> products = ... Observable<Int> totalSoldUnits = products .filter(pd -> pd.getCategoryId() == 3) .map(pd -> pd.getSoldUnits()) .reduce(0,(x,y) -> x + y) </code></pre> <p>如果只是简单的一步操作呢?</p> <pre> <code class="language-java">Observable<Category> category = Category.forId(263); </code></pre> <p>这种情况是不是滥用 Observable 呢? 直接返回数据是不是更好呢?</p> <pre> <code class="language-java">Categorycategory = Category.forId(263); </code></pre> <p>如果返回的结果有多个 Category 、或者你不想处理返回数据为 null 的情况,则会使用 Observable 。但是在下面的示例中会看到,这种过度的使用 Observable 会导致更多的模板代码出现。</p> <p>如果你非要这样用,则使用的时候,可以很容易的转换为 Observable:</p> <pre> <code class="language-java">Observable<Category> category = Observable.just(Category.forId(263)) .filter(c -> c != null); </code></pre> <h2>第四种情况:经常使用到的属性(Frequently Qualified Properties)</h2> <p>先解释下上面提到的情况,比如有下面一个 Product 类</p> <pre> <code class="language-java">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); } } </code></pre> <p>上面的 getCategory() 返回的是 Observable 。如果你经常使用这个函数,则用起来可能相当麻烦。假设每个 Category 中有个 getGroup() 函数返回一个整数,代表所在的分组。可以根据这个分组来过滤每个组的分类:</p> <pre> <code class="language-java">Observable<Product> products = ... Observable<Product> productsInGroup5 = products.flatMap(p -> p.getCategory().filter(c -> c.getGroup() == 5).map(p -> c)); </code></pre> <p>如此简单的一个需求,代码看起来居然这么复杂到使用了 FlatMap 。如果 getCategory() 返回的是 category 对象则用起来就相当简单了:</p> <pre> <code class="language-java">public CategorygetCategory() { return Category.forId(categoryId); } Observable<Product> productsInGroup5 = products.filter(p -> p.getCategory().getGroup() == 5); </code></pre> <p>所以针对这种经常使用到的函数或者属性,最好不要返回 Observable 的形式。</p> <h2>第五种情况:有状态的对象(Capturing State)</h2> <p>RxJava 中的数据通常是无状态的。这样可以方便并行处理多个操作。但是在实际的业务逻辑中,通常需要保留一些状态。比如打印价格时候,需要保留历史价格信息:</p> <pre> <code class="language-java">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; } } </code></pre> <p>然后通过 Observable 的方式来获取历史信息:</p> <pre> <code class="language-java">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); } } </code></pre> <p>但是如果这个操作是比较消耗资源的,则又回到了第二种情况。</p> <p>针对这种有状态的数据,还是使用传统的查询方式比较好。</p> <h2>总结</h2> <p>使用了 RxJava,尝到了 Rxjava 的好处,您会到处使用它,但是 RxJava 是用来解决比较复杂或者非常复杂情况的,对于简单的情况还是简单处理吧。凡事要把握好度,过犹不及! 上面的一些情况,对于有经验的 RxJava 开发者可能很容易避免这种情况,对于初学者可能还处于 使用 RxJava 的蜜月期,看问题没这么透彻很容易陷入到过度使用 RxJava 的情况。</p> <p>再次提醒,以上只是笔者自己的主观意见,如果有不同见解,欢迎一起讨论交流。</p> <p> </p> <p>来自:http://blog.chengyunfeng.com/?p=1009</p> <p> </p>