Java 函数式编程

1.

在命令式编程中,控制结构(if、switch/while、do-while等)定义了一个范围,在这个范围内,它们通常会做一些事情,这意味着它们有一定的效果/作用(effect)产生。这种效果可以只在控制结构的范围内可见,当然也可以在封闭范围内可见。控制结构也可以访问封闭的范围来读取值。下面以Email验证来解释:

3.1
   //邮件的校验格式
    final Pattern emailPattern =
            Pattern.compile("^[a-z0-9._%+-]+@[a-z0-9.-]+\\.[a-z]{2,4}$");

    void testMail(String email) {
        if (emailPattern.matcher(email).matches()) {
            sendVerificationMail(email);
        } else {
            logError("email " + email + " is invalid.");
        }
    }
    void sendVerificationMail(String s) {
        System.out.println("Email Sending... " + s);
    }
    private static void logError(String s) {
        System.err.println("Error message logged: " + s);

在这个样例中,if…else结构可以访问封闭范围(enclosing scope)内的emailPattern变量。从Java语法的角度来看,这里没有必要将emailPattern设置为final的,但是如果你想要将该方法筹够为函数的话,那么则必须将其声明为final。另外一种解决方法是讲emailPattern放在方法体内,但是缺点是对于每一次方法调用,都需要编译它。如果这个Email匹配模式需要改变,那么可以将其作为入参,如果条件匹配结果为True,那么就会为email应用sendVerificationMail这个作用效果。这个作用效果可能包括向该email发送验证邮件,来验证该邮箱的正确性。在本样例中只是通过打印消息到标准输出,来模拟了邮件的发送。当条件匹配结果为False,那么会对包含email的错误信息应用一个不同的作用效果。

2. 抽象控制结构

上面3.1的代码是纯命令式的代码,在函数式代码中,你是不会看到这种代码。尽管testMail方法看起来是一个纯作用,因为他没有返回值,他将数据处理以及作用混杂在了一起,而这时你需要避免的,因为这种代码不方便测试。我们来看看如何让代码整洁。
你需要做的第一件事就是分离计算和作用,这样你就可以对计算结果进行测试。这可以以命令式方式实现,但是我更建议你使用函数形式,正如下面列出的这样:

    public final Pattern emailPattern =
            Pattern.compile("^[a-z0-9._%+-]+@[a-z0-9.-]+\\.[a-z]{2,4}$");
    //Java8
    public Function<String,Boolean> emailChecker8 = email -> emailPattern.matcher(email).matches();
    //Java7
    public Function<String,Boolean> emailChecker7 = new Function<String, Boolean>() {
        @Override
        public Boolean apply(String email) {
            return emailPattern.matcher(email).matches();
        }
    };

    void sendVerificationMail(String s) {
        System.out.println("Email Sending... " + s);
    }
    void logError(String s) {
        System.err.println("Error message logged: " + s);
    }

    public void validation(String email){
        if(emailChecker8.apply(email)){
            sendVerificationMail(email);
        }else{
            logError("email " + email + " is invalid.");
        }
    }

现在你可以测试你程序的数据处理部分(即验证email这个字符串),因为你已经将他与作用(effect)分离开来了。但是这仍然有很多问题。其中一个便是只处理字符串不验证的情况。但是如果收到的字符串是null,那么就会跑出NullPointerException(NPE)异常。看下面的测试样例:

testMail("john.doe@acme.com");
testMail(null);
testMail("paul.smith@acme.com");

第三行是不会被运行的。因为第二个邮件地址是不可用的,跑出NPE异常,终止掉当前线程。比较好的解决方案是打印错误消息,指示发生的情况,并继续处理下一个email地址。
另外一个问题是验证空字符串的问题:

testMail("");

这并不会导致产生error,但是这个地址是验证不通过的,然后会打印出下面的消息:

email is invalid.

email和is之间的双空格,表示这个消息是个空消息。但是也许下面这样的消息更加符合场景需要:

email must not be empty.

为了处理这个问题,你需要首先定义一个Result/Try的组件来处理计算的结果。


public interface Result {

    public class Success implements Result{}

    public class Failed implements Result{
        String errorMessage;

        public Failed(String errorMessage) {
            this.errorMessage = errorMessage;
        }

        public String getErrorMessage() {
            return errorMessage;
        }

        public void setErrorMessage(String errorMessage) {
            this.errorMessage = errorMessage;
        }
    }
}

现在代码可以重构为下面的版本:

    public final Pattern emailPattern =
            Pattern.compile("^[a-z0-9._%+-]+@[a-z0-9.-]+\\.[a-z]{2,4}$");
    //Java8
    public Function<String,Boolean> emailChecker8 = email -> emailPattern.matcher(email).matches();
    //Java7
    public Function<String,Result> emailChecker7 = new Function<String, Result>() {
        @Override
        public Result apply(String email) {
            return email == null 
                    ? new Result.Failed("email must not be null") 
                    : email.equals("") 
                        ? new Result.Failed("email must not be empty") 
                        : emailPattern.matcher(email).matches() == true 
                            ? new Result.Success() 
                            : new Result.Failed("email not valid");

        }
    };


    public void validation(String email){
        if(emailChecker7.apply(email) instanceof Result.Success){
            sendVerificationMail(email);
        }else{
            logError(((Result.Failed)result).getErrorMessage());
        }
    }
    public static void main(String... args) {
        EmailValidation3 emailValidation3 = new EmailValidation3();
        emailValidation3.validation("this.is@my.email");
        emailValidation3.validation(null);
        emailValidation3.validation("");
        emailValidation3.validation("john.doe@acme.com");
    }

运行产生的日志:

Error message logged: email this.is@my.email is invalid.
Error message logged: email null is invalid.
Error message logged: email  is invalid.
Email Sending... john.doe@acme.com

但这还不是能令人满意,使用instanceOf来决定结果是否为Success,这代码看起来很丑。并且在结果为Failed时,我们需要使用强转得到Failed,以获取errormessage交给作用/效果方法(effect method).而且更糟糕的是,你在validation方法中有一些无法测试的程序逻辑。这是因为这些方法是一种作用/效果方法(effect method),这意味着它不会返回一个值,而只是会改变外部世界。
有办法修复吗?当然,我们可以返回一段小的程序来做同样的事。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值