组合器模式,java 函数 <e, t>
combinator pattern, java Function <e, t>
public interface CustomerRegVali extends Function<Customer, ValidationResult>
static CustomerRegVali isEmailValid(){
return customer -> customer.getEmail().contains("@") ?
SUCCESS : EMAIL_NOT_VALID;
}
static CustomerRegVali isDobValid(){
return customer -> Period.between(customer.getDob(), LocalDate.now()).getYears() > 16 ?
SUCCESS : NOT_ADULT;
}
static CustomerRegVali isPhoneValid(){
return customer -> customer.getPhone().startsWith("+0") ?
SUCCESS : PHONE_NOT_VALID;
}
default CustomerRegVali and(CustomerRegVali other){
return customer -> {
ValidationResult result = CustomerRegVali.this.apply(customer);
return result.equals(SUCCESS) ? other.apply(customer) : result;
};
}
@Main method
ValidationResult result = isEmailValid()
.and(isPhoneValid())
.and(isDobValid())
.apply(customer);
是这样.. 回顾一个旧的函数式 uni 项目 java,我偶然发现了这个组合器。我是一无所知还是 .apply 没有在基元上被调用两次?显得有些多余。
请注意,您正在构建一个函数链,链的结果也是一个函数。
正如@SilvioMayolo 也指出的那样,当您在链上执行 apply
时,如果验证结果为 SUCCESS
且链可以,则此函数将按照您指定的顺序依次调用继续,不同的,个人的,CustomerRegVali
apply
方法。
为了理解这种行为,请考虑以下接口定义。
import java.time.LocalDate;
import java.time.Period;
import java.util.function.Function;
public interface CustomerRegVali extends Function<Customer, ValidationResult> {
static CustomerRegVali isEmailValid(){
return customer -> {
System.out.println("isEmailValid called");
return customer.getEmail().contains("@") ?
SUCCESS : EMAIL_NOT_VALID;
};
}
static CustomerRegVali isDobValid(){
return customer -> {
System.out.println("isDobValid called");
return Period.between(customer.getDob(), LocalDate.now()).getYears() > 16 ?
SUCCESS : NOT_ADULT;
};
}
static CustomerRegVali isPhoneValid(){
return customer -> {
System.out.println("isPhoneValid called");
return customer.getPhone().startsWith("+0") ?
SUCCESS : PHONE_NOT_VALID;
};
}
default CustomerRegVali and(CustomerRegVali other){
return customer -> {
System.out.println("and called");
ValidationResult result = CustomerRegVali.this.apply(customer);
return result.equals(SUCCESS) ? other.apply(customer) : result;
};
}
}
这个简单的测试用例:
public static void main(String... args) {
Customer customer = new Customer();
customer.setEmail("email@company.org");
customer.setDob(LocalDate.of(2000, Month.JANUARY, 1));
customer.setPhone("+000000000");
final CustomerRegVali chain = isEmailValid()
.and(isPhoneValid())
.and(isDobValid());
System.out.println("valid result:");
ValidationResult result = chain.apply(customer);
System.out.println(result);
customer.setPhone("+900000000");
System.out.println("\ninvalid result:");
System.out.println(chain.apply(customer));
}
这将输出:
valid result:
and called
and called
isEmailValid called
isPhoneValid called
isDobValid called
SUCCESS
invalid result:
and called
and called
isEmailValid called
isPhoneValid called
PHONE_NOT_VALID
如您所见,由于函数链和您对这些函数的编程方式,当您在指定顺序并且仅当链应该继续时因为验证结果是 SUCCESS
.
准确地说,假设您的代码:
@Main method
ValidationResult result = isEmailValid()
.and(isPhoneValid())
.and(isDobValid())
.apply(customer);
- 当您调用
apply
时,您正在调用第二个 and
函数。
- 根据为
and
函数提供的实现,这将依次调用定义第二个 and
的函数的 apply
方法。在这种情况下,这意味着将调用第一个定义的 and
函数的 apply
方法。
- 反过来,再次根据为
and
函数提供的实现,将调用定义第一个and
函数的函数的apply
方法,这现在刚好是isEmailValid
。
isEmailValid
被调用。如果它 returns SUCCESS
那么,根据 and
函数中实现的逻辑——我们现在向前推进,从第一个验证器到最后一个——它将调用下一个验证器函数在链中,作为第一个 and
函数的参数提供的那个,isPhoneValid
,在这种情况下。
isPhoneValid
被调用。如果它 returns SUCCESS
那么,根据 and
函数中实现的逻辑,它将再次调用链中的下一个验证器函数,该函数作为第二个 [=] 的参数传递这次是 22=] 函数,在本例中是 isDobValid
。
isDobValid
被调用。如果存在新的验证器,该过程将像这样继续。
密切相关,前段时间我遇到了 this wonderful article 关于函数式编程和 partial
函数。我认为它可以提供帮助。
你写了
... during .and(isPhoneValid), is .this not referring to isEmailValid - and .other to isPhoneValid. With this in mind, next iteration would then be .this referring to isPhoneValid on which we will call .apply once again.
我认为您混淆了 this
和 CustomerRegVali.this
,这是不同的。您可能会想象您正在构建一个如下所示的结构:
但实际上是这样的:
在对 apply
的调用中,this
指向这些框之一,但如您所见,从来没有 this
指向 isEmailValid
而单独的 other
link 指向 isPhoneValid
.
在 and
的 apply
实现中,您不是在 this
上调用应用程序,而是在捕获的值 CustomerRegVali.this
上调用应用程序,它是你调用了 and
.
在设置验证器时,您:
- 呼叫
isEmailValid
.
- 在
isEmailValid
验证器上调用 and
,传递 isPhoneValid
验证器。这将创建一个新的 and
验证器,它保留对 isEmailValid
验证器和 other
. 的引用
- 最后在第一个
and
的结果上调用 and
,传递一个新的 isDobValid
验证器,构建一个新的 and
验证器,就像之前的验证器一样。
该过程生成第二张图中的结构。
所以总共有五个验证器,你对每个调用一次 apply
。
这三个 isXyzValid
值的类型是 CustomerRegVali
,这意味着它们是以 Customer
作为参数并且 return 和 ValidationResult
.
and
函数是一个将两个验证函数合二为一的组合器。 IE。它 return 是一个将客户作为参数的函数,return 是一个 ValidationResult,根据两个验证函数应用于给定客户时是否成功。它有一个短路,因此如果第一个验证函数失败,则不会调用第二个 - 组合函数只是 returns 失败的验证结果 return 由第一个函数编辑。
and
组合子在示例代码中被定义为 CustomerRegValin type. It could have been defined as a standalone static method, in which case it would take two function arguments (i.e.
CustomerRegVali` 值的非静态方法)并且看起来像这样:
CustomerRegVali and(CustomerRegVali thiz, CustomerRegVali other) {
return customer -> {
ValidationResult result = thiz.apply(customer);
return result.equals(SUCCESS) ? other.apply(customer) : result;
};
这里应该清楚,每个函数参数调用apply一次(最多)。
要使用此方法,您必须这样调用它:
CustomerRegVali isEmailandDobValid = and(isEmailValid(), isDobValid());
如果您想使用 and
链接另一个函数,那么它将如下所示:
CustomerRegVali isEmailandDobAndPhoneValid = and(and(isEmailValid(), isDobValid()), isPhoneValid());
显然可读性不强。如果我们可以使用 and
作为中缀运算符,我们可以改进这一点,例如:
isEmailValid() and isDobValid() and isPhoneValid()
不幸的是 Java 不支持用户定义的中缀函数,但是我们可以通过将 and
更改为 CustomerRegVali 上的非静态方法来获得相当接近的结果 - 这样我们就可以编写上面的表达式是这样的:
isEmailValid().and(isDobValid()).and(isPhoneValid())
为此,我们需要更改 and
定义,使其成为非静态方法,并删除第一个参数(因为该函数参数现在将成为我们调用的对象and
方法)。作为第一次尝试,我们可能认为这会奏效:
CustomerRegVali and(CustomerRegVali other) {
return customer -> {
ValidationResult result = this.apply(customer);
return result.equals(SUCCESS) ? other.apply(customer) : result;
};
然而这将无法编译。问题在于,在 lambda 表达式内部(内部一对大括号内的函数体),this
指的是直接封闭范围 - lambda 本身,而不是外部 CustomerRegVali
对象。为了解决这个问题,我们需要通过在 this
前面加上 CustomerRegVali.
:
来消除歧义
CustomerRegVali and(CustomerRegVali other) {
return customer -> {
ValidationResult result = CustomerRegVali.this.apply(customer);
return result.equals(SUCCESS) ? other.apply(customer) : result;
};
这现在与示例中提供的定义匹配。我们可以使用这个 and
方法将 3 个验证函数链接在一起:
CustomerRegVali isEmailandDobAndPhoneValid =
isEmailValid().and(isDobValid()).and(isPhoneValid())
如果我们想将该函数应用于实际的客户对象,那么我们需要调用 apply
方法:
ValidationResult result =
isEmailValid()
.and(isDobValid())
.and(isPhoneValid())
.apply(customer);
我希望现在可以清楚地知道在这个例子中每个验证函数只调用一次(最多)。
public interface CustomerRegVali extends Function<Customer, ValidationResult>
static CustomerRegVali isEmailValid(){
return customer -> customer.getEmail().contains("@") ?
SUCCESS : EMAIL_NOT_VALID;
}
static CustomerRegVali isDobValid(){
return customer -> Period.between(customer.getDob(), LocalDate.now()).getYears() > 16 ?
SUCCESS : NOT_ADULT;
}
static CustomerRegVali isPhoneValid(){
return customer -> customer.getPhone().startsWith("+0") ?
SUCCESS : PHONE_NOT_VALID;
}
default CustomerRegVali and(CustomerRegVali other){
return customer -> {
ValidationResult result = CustomerRegVali.this.apply(customer);
return result.equals(SUCCESS) ? other.apply(customer) : result;
};
}
@Main method
ValidationResult result = isEmailValid()
.and(isPhoneValid())
.and(isDobValid())
.apply(customer);
是这样.. 回顾一个旧的函数式 uni 项目 java,我偶然发现了这个组合器。我是一无所知还是 .apply 没有在基元上被调用两次?显得有些多余。
请注意,您正在构建一个函数链,链的结果也是一个函数。
正如@SilvioMayolo 也指出的那样,当您在链上执行 apply
时,如果验证结果为 SUCCESS
且链可以,则此函数将按照您指定的顺序依次调用继续,不同的,个人的,CustomerRegVali
apply
方法。
为了理解这种行为,请考虑以下接口定义。
import java.time.LocalDate;
import java.time.Period;
import java.util.function.Function;
public interface CustomerRegVali extends Function<Customer, ValidationResult> {
static CustomerRegVali isEmailValid(){
return customer -> {
System.out.println("isEmailValid called");
return customer.getEmail().contains("@") ?
SUCCESS : EMAIL_NOT_VALID;
};
}
static CustomerRegVali isDobValid(){
return customer -> {
System.out.println("isDobValid called");
return Period.between(customer.getDob(), LocalDate.now()).getYears() > 16 ?
SUCCESS : NOT_ADULT;
};
}
static CustomerRegVali isPhoneValid(){
return customer -> {
System.out.println("isPhoneValid called");
return customer.getPhone().startsWith("+0") ?
SUCCESS : PHONE_NOT_VALID;
};
}
default CustomerRegVali and(CustomerRegVali other){
return customer -> {
System.out.println("and called");
ValidationResult result = CustomerRegVali.this.apply(customer);
return result.equals(SUCCESS) ? other.apply(customer) : result;
};
}
}
这个简单的测试用例:
public static void main(String... args) {
Customer customer = new Customer();
customer.setEmail("email@company.org");
customer.setDob(LocalDate.of(2000, Month.JANUARY, 1));
customer.setPhone("+000000000");
final CustomerRegVali chain = isEmailValid()
.and(isPhoneValid())
.and(isDobValid());
System.out.println("valid result:");
ValidationResult result = chain.apply(customer);
System.out.println(result);
customer.setPhone("+900000000");
System.out.println("\ninvalid result:");
System.out.println(chain.apply(customer));
}
这将输出:
valid result:
and called
and called
isEmailValid called
isPhoneValid called
isDobValid called
SUCCESS
invalid result:
and called
and called
isEmailValid called
isPhoneValid called
PHONE_NOT_VALID
如您所见,由于函数链和您对这些函数的编程方式,当您在指定顺序并且仅当链应该继续时因为验证结果是 SUCCESS
.
准确地说,假设您的代码:
@Main method
ValidationResult result = isEmailValid()
.and(isPhoneValid())
.and(isDobValid())
.apply(customer);
- 当您调用
apply
时,您正在调用第二个and
函数。 - 根据为
and
函数提供的实现,这将依次调用定义第二个and
的函数的apply
方法。在这种情况下,这意味着将调用第一个定义的and
函数的apply
方法。 - 反过来,再次根据为
and
函数提供的实现,将调用定义第一个and
函数的函数的apply
方法,这现在刚好是isEmailValid
。 isEmailValid
被调用。如果它 returnsSUCCESS
那么,根据and
函数中实现的逻辑——我们现在向前推进,从第一个验证器到最后一个——它将调用下一个验证器函数在链中,作为第一个and
函数的参数提供的那个,isPhoneValid
,在这种情况下。isPhoneValid
被调用。如果它 returnsSUCCESS
那么,根据and
函数中实现的逻辑,它将再次调用链中的下一个验证器函数,该函数作为第二个 [=] 的参数传递这次是 22=] 函数,在本例中是isDobValid
。isDobValid
被调用。如果存在新的验证器,该过程将像这样继续。
密切相关,前段时间我遇到了 this wonderful article 关于函数式编程和 partial
函数。我认为它可以提供帮助。
你写了
... during .and(isPhoneValid), is .this not referring to isEmailValid - and .other to isPhoneValid. With this in mind, next iteration would then be .this referring to isPhoneValid on which we will call .apply once again.
我认为您混淆了 this
和 CustomerRegVali.this
,这是不同的。您可能会想象您正在构建一个如下所示的结构:
但实际上是这样的:
在对 apply
的调用中,this
指向这些框之一,但如您所见,从来没有 this
指向 isEmailValid
而单独的 other
link 指向 isPhoneValid
.
在 and
的 apply
实现中,您不是在 this
上调用应用程序,而是在捕获的值 CustomerRegVali.this
上调用应用程序,它是你调用了 and
.
在设置验证器时,您:
- 呼叫
isEmailValid
. - 在
isEmailValid
验证器上调用and
,传递isPhoneValid
验证器。这将创建一个新的and
验证器,它保留对isEmailValid
验证器和other
. 的引用
- 最后在第一个
and
的结果上调用and
,传递一个新的isDobValid
验证器,构建一个新的and
验证器,就像之前的验证器一样。
该过程生成第二张图中的结构。
所以总共有五个验证器,你对每个调用一次 apply
。
这三个 isXyzValid
值的类型是 CustomerRegVali
,这意味着它们是以 Customer
作为参数并且 return 和 ValidationResult
.
and
函数是一个将两个验证函数合二为一的组合器。 IE。它 return 是一个将客户作为参数的函数,return 是一个 ValidationResult,根据两个验证函数应用于给定客户时是否成功。它有一个短路,因此如果第一个验证函数失败,则不会调用第二个 - 组合函数只是 returns 失败的验证结果 return 由第一个函数编辑。
and
组合子在示例代码中被定义为 CustomerRegValin type. It could have been defined as a standalone static method, in which case it would take two function arguments (i.e.
CustomerRegVali` 值的非静态方法)并且看起来像这样:
CustomerRegVali and(CustomerRegVali thiz, CustomerRegVali other) {
return customer -> {
ValidationResult result = thiz.apply(customer);
return result.equals(SUCCESS) ? other.apply(customer) : result;
};
这里应该清楚,每个函数参数调用apply一次(最多)。
要使用此方法,您必须这样调用它:
CustomerRegVali isEmailandDobValid = and(isEmailValid(), isDobValid());
如果您想使用 and
链接另一个函数,那么它将如下所示:
CustomerRegVali isEmailandDobAndPhoneValid = and(and(isEmailValid(), isDobValid()), isPhoneValid());
显然可读性不强。如果我们可以使用 and
作为中缀运算符,我们可以改进这一点,例如:
isEmailValid() and isDobValid() and isPhoneValid()
不幸的是 Java 不支持用户定义的中缀函数,但是我们可以通过将 and
更改为 CustomerRegVali 上的非静态方法来获得相当接近的结果 - 这样我们就可以编写上面的表达式是这样的:
isEmailValid().and(isDobValid()).and(isPhoneValid())
为此,我们需要更改 and
定义,使其成为非静态方法,并删除第一个参数(因为该函数参数现在将成为我们调用的对象and
方法)。作为第一次尝试,我们可能认为这会奏效:
CustomerRegVali and(CustomerRegVali other) {
return customer -> {
ValidationResult result = this.apply(customer);
return result.equals(SUCCESS) ? other.apply(customer) : result;
};
然而这将无法编译。问题在于,在 lambda 表达式内部(内部一对大括号内的函数体),this
指的是直接封闭范围 - lambda 本身,而不是外部 CustomerRegVali
对象。为了解决这个问题,我们需要通过在 this
前面加上 CustomerRegVali.
:
CustomerRegVali and(CustomerRegVali other) {
return customer -> {
ValidationResult result = CustomerRegVali.this.apply(customer);
return result.equals(SUCCESS) ? other.apply(customer) : result;
};
这现在与示例中提供的定义匹配。我们可以使用这个 and
方法将 3 个验证函数链接在一起:
CustomerRegVali isEmailandDobAndPhoneValid =
isEmailValid().and(isDobValid()).and(isPhoneValid())
如果我们想将该函数应用于实际的客户对象,那么我们需要调用 apply
方法:
ValidationResult result =
isEmailValid()
.and(isDobValid())
.and(isPhoneValid())
.apply(customer);
我希望现在可以清楚地知道在这个例子中每个验证函数只调用一次(最多)。