Java 8函数式编程模式:使用枚举的方法


假设有三种电影类型,每种类型都有自己的计算公式,该公式是根据借出的天数计算价格:

class Movie {
        enum Type {
                REGULAR, NEW_RELEASE, CHILDREN
        }
        private final Type type;
        public Movie(Type type) {
                this.type = type;
        }
        public int computePrice(int days) {
                switch (type) {
                case REGULAR: return days + 1;
                case NEW_RELEASE: return days * 2;
                case CHILDREN: return 5;
                default: throw new IllegalArgumentException(); // Always have this here!
                }
        }
}

测试:
System.out.println(new Movie(Movie.Type.REGULAR).computePrice(2));
System.out.println(new Movie(Movie.Type.NEW_RELEASE).computePrice(2));
System.out.println(new Movie(Movie.Type.CHILDREN).computePrice(2));

结果:
3
4
5

上面代码中的问题可能是switch:无论何时向枚举添加新值,都需要搜索所有开关并确保处理新情况。
但这很脆弱。会弹出IllegalArgumentException,简而言之,虽然任何人都可以阅读此代码,但它有点冒险。

避免风险的一种方法是OOP解决方案:

abstract class Movie {
        public abstract int computePrice(int days);
}
class RegularMovie extends Movie {
        public int computePrice(int days) {
                return days+1;
        }
}
class NewReleaseMovie extends Movie {
        public int computePrice(int days) {
                return days*2;
        }
}
class ChildrenMovie extends Movie {
        public int computePrice(int days) {
                return 5;
        }
}

如果您创建一种新类型的影片,实际上是一个新的子类,但是继承问题来了,如果你想按照另一个标准对电影进行分类,比如发行年份怎么办?或者几个月后你会如何处理从电影Type.NEW_RELEASE到Type.REGULAR电影的“降级” ?

让我们寻找其他方法来实现它,使用枚举抽象方法实现此逻辑。
这里如果直接提出更改需求:“ 公式中的因子:新发行电影的价格(在我们的示例中为2)必须通过数据库更新。”
这意味着我必须从一些注入的存储库中获取此因子。但是,由于我不能在我的Movie实体中注入repos ,让我们将逻辑移到一个单独的类中:

public class PriceService {
        private final NewReleasePriceRepo repo;
        public PriceService(NewReleasePriceRepo repo) {
                this.repo = repo;
        }
        public int computeNewReleasePrice(int days) {
                return (int) (days * repo.getFactor());
        }
        public int computeRegularPrice(int days) {
                return days + 1;
        }
        public int computeChildrenPrice(int days) {
                return 5;
        }
        public int computePrice(Movie.Type type, int days) {
                switch (type) {
                case REGULAR: return computeRegularPrice(days);
                case NEW_RELEASE: return computeNewReleasePrice(days);
                case CHILDREN: return computeChildrenPrice(days);
                default: thrownew IllegalArgumentException();
                }
        }
}

switch又回来聊,带来了固有的风险


public class Movie { 
       public enum Type {
              REGULAR(PriceService::computeRegularPrice),
              NEW_RELEASE(PriceService::computeNewReleasePrice),
              CHILDREN(PriceService::computeChildrenPrice);
              public final BiFunction<PriceService, Integer, Integer> priceAlgo;
              private Type(BiFunction<PriceService, Integer, Integer> priceAlgo) {
                     this.priceAlgo = priceAlgo;
              }
       }
       ...
}

我在每个枚举值中存储一个方法引用到相应的实例方法PriceService.
这样不需要switch:

class PriceService {
       ...
       public int computePrice(Movie.Type type, int days) {
              return type.priceAlgo.apply(this, days);
       }
}

于我以静态方式(from PriceService::)引用实例方法,因此我需要PriceService在调用时提供实例作为第一个参数,这里使用了this,这样,我可以从枚举值定义的静态上下文中有效地引用任何[Spring] bean的方法。

您可以使用方法引用将类型特定的逻辑挂钩到枚举,以确保每个枚举值与相应的逻辑位相关联。