不该使用 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>