前言
虽说使用设计模式可以让复杂的业务代码变得清晰且易于维护,但是某些情况下,开发可能会遇到我为了简单的业务逻辑去适配设计模式的情况,本文笔者就以四种常见的设计模式为例,演示如何基于lambda
来简化设计模式的实现。
策略模式
我们的项目中会涉及各种各样的校验,可能是校验电话号码、单双数、字符串长度等,为此我们希望通过策略模式来封装这些校验规则。
第一步自然是通过接口来定义策略,编写一个名为execute方法,让用户传入字符串,返回校验结果的布尔值:
/**
* 定义策略模式的接口
*/
public interface ValidationStrategy {
/**
* 校验该字符串是否符合要求,若符合则返回true
* @param str
* @return
*/
boolean execute(String str);
}
然后将这个策略接口聚合到我们的校验器中,后续我们就可以按照需求传入对应的校验策略即可:
/**
* 校验工具,将策略接口成员成员属性,起到依赖抽象的作用
*/
public class Validator {
private ValidationStrategy strategy;
public Validator() {
}
public Validator(ValidationStrategy strategy) {
this.strategy = strategy;
}
public boolean validate(String str) {
return strategy.execute(str);
}
}
假如我们需要校验这个字符串是否全为字符串小写,那么我们就可以封装这样一个类:
/**
* 判断是否全为小写
*/
public class IsAllLowerCase implements ValidationStrategy {
@Override
public boolean execute(String str) {
return str.matches("[a-z]+");
}
}
同理,如果我们需要判断是否全为数字,则可以这样写:
/**
* 判断传入字符是否全为数字
*/
public class IsNumeric implements ValidationStrategy {
@Override
public boolean execute(String str) {
return str.matches("\\d+");
}
}
使用时,我们只需按需传入校验规则即可:
public class Main {
public static void main(String[] args) {
//校验是否全为数字
Validator v1 = new Validator(new IsNumeric());
System.out.println(v1.validate("1234"));
//校验是否全是小写
Validator v2 = new Validator(new IsAllLowerCase());
System.out.println(v2.validate("dalhl"));
}
}
输出结果如下:
true
true
不知道读者是否可以发现问题,规则的校验往往只是一两行代码,为了适配规则校验所用到的策略模式,开发者往往需要对此额外创建一个类,要知道字符校验的规则是成百上千的,并且很多校验规则很可能仅仅是某个业务才会用到的。
所以我们是否有办法做到既能适配策略模式,又避免为了一段简单的校验代码而去创建一个类呢?
查看我们校验策略接口ValidationStrategy
的定义,它要求传入一个String
返回一个boolean
,由此我们想到了java8
提供的函数时接口Function
,其定义如下所示,可以根据泛型要求要指明泛型T
和R
,apply
方法要求传入一个T
,这里可以直接理解为我们的String
,然后返回一个R
,同理代入我们的boolean
:
@FunctionalInterface
public interface Function<T, R> {
/**
* Applies this function to the given argument.
*
* @param t the function argument
* @return the function result
*/
R apply(T t);
.......
}
按照java8
的lambda
语法糖,Function<T, R>
只有一个需要实现的方法R apply(T t)
,我们完全可以表面类的创建,取而代之的是这样一段表达式:
t->R
查看我们ValidationStrategy
的定义,它也是只有一个方法execute
,我们完全可以将其视为Function<String, Boolean>
,即可得表达式s->boolean
:
由此我们得出下面这段代码,可以看到根据接口的定义匹配java8
对应的函数时接口,然后基于lambda
表达式即可完成创建,这样做法避免了类的生命,避免了简单逻辑复杂化实现的问题:
public static void main(String[] args) {
//校验是否全为数字
Validator v1 = new Validator((s) -> s.matches("\\d+"));
System.out.println(v1.validate("1234"));
//校验是否全是小写
Validator v2 = new Validator(s -> s.matches("[a-z]+"));
System.out.println(v2.validate("dalhl"));
}
模板方法
银行接待VIP
顾客的核心流程为:
- 查询顾客是否是
VIP
。 - 招待顾客,为顾客办理业务。
所有银行的大体流程都是这样,唯一的区别就是第2步,对此我们可以使用模板方法模式,创建一个抽象类,将第1步抽出来,而第2步按照不同银行进行不同的实现:
public abstract class Banking {
public void processCustomer(int id) {
//查询会员名
String customer = getCustomerWithId(id);
//招待会员
makeCustomerHappy(customer);
}
private String getCustomerWithId(int id) {
return RandomUtil.randomString(5);
}
protected abstract void makeCustomerHappy(String customer);
}
对应两个银行的实现代码,先来看看BankingA
的招待逻辑:
public class BankingA extends Banking {
@Override
protected void makeCustomerHappy(String customer) {
System.out.println("请"+customer+"吃饭,并为其办理业务");
}
}
BankingB
的招待逻辑:
public class BankingB extends Banking {
@Override
protected void makeCustomerHappy(String customer) {
System.out.println("请" + customer + "喝茶,并为其办理业务");
}
}
测试代码如下:
public static void main(String[] args) {
BankingA bankingA = new BankingA();
bankingA.processCustomer(1);
BankingB bankingB= new BankingB();
bankingB.processCustomer(1);
}
对应输出结果:
请6brkb吃饭,并为其办理业务
请autjm喝茶,并为其办理业务
还是一样的问题,找到会员是一段无返回值的简单输出,为了适配模板方法,这一行代码也还是要创建一个类,所以我们还是需要用lambda
对其进行简化。
查看抽象方法makeCustomerHappy
的定义,它要求传入一个传入而返回一个void,查阅java8对应的函数式接口,我们找到了Consumer
:
@FunctionalInterface
public interface Consumer<T> {
/**
* Performs this operation on the given argument.
*
* @param t the input argument
*/
void accept(T t);
于是我们得出公式s->Void
:
对此我们将抽象类Banking 加以改造,将抽象方法makeCustomerHappy
改为Consumer
接口:
public abstract class Banking {
public void processCustomer(int id, Consumer<String> makeCustomerHappy) {
//查询会员名
String customer = getCustomerWithId(id);
//招待会员
makeCustomerHappy.accept(customer);
}
private String getCustomerWithId(int id) {
return RandomUtil.randomString(5);
}
}
这样一来,后续的调用即可用一段lambda
实现:
public class Main {
public static void main(String[] args) {
Banking bankingA = new Banking();
bankingA.processCustomer(1,customer-> System.out.println("请"+customer+"吃饭,并为其办理业务"));
Banking bankingB = new Banking();
bankingB.processCustomer(1,customer-> System.out.println("请"+customer+"喝茶,并为其办理业务"));
}
}
观察者模式
观察者模式算是最经典也最好理解的设计模式,观察者只需将自己注册到感兴趣的主题上,一旦有主题更新就会及时通知观察者,观察者按照自己的需要进行响应处理。
对此我们首先定义观察者的接口:
/**
* 观察者
*/
public interface Observer {
void inform(String msg);
}
接下来就是主题:
public interface Subject {
void registerObserver(Observer observer);
void notifyObserver();
}
创建一个观察者1以及观察者2以及实现他们对自己感兴趣主题时会做出的反馈输出方法inform
:
public class Observer1 implements Observer {
@Override
public void inform(String msg) {
System.out.println("观察者1收到通知,内容为:" + msg);
}
}
public class Observer2 implements Observer {
@Override
public void inform(String msg) {
System.out.println("观察者2收到通知,内容为:" + msg);
}
}
最后就是主题类的实现,我们将观察者聚合,如果观察者对SubJect1
感兴趣,则通过registerObserver
完成注册,一旦主题要发布新消息就可以通过notifyObserver
及时通知每一个订阅者:
public class SubJect1 implements Subject {
private String msg;
public SubJect1(String msg) {
this.msg = msg;
}
private List<Observer> observerList = new ArrayList<>();
@Override
public void registerObserver(Observer observer) {
observerList.add(observer);
}
@Override
public void notifyObserver() {
observerList.forEach(o -> o.inform(msg));
}
}
测试代码和对应输出结果如下所示:
public static void main(String[] args) {
SubJect1 subJect1 = new SubJect1("请大家学习《基于lambda简化设计模式》");
//注册订阅者
subJect1.registerObserver(new Observer1());
subJect1.registerObserver(new Observer2());
//主题发起通知
subJect1.notifyObserver();
}
输出结果:
观察者1收到通知,内容为:请大家学习《基于lambda简化设计模式》
观察者2收到通知,内容为:请大家学习《基于lambda简化设计模式》
很明显的Observer的inform是典型的Consumer接口,我们直接将其简化:
public static void main(String[] args) {
SubJect1 subJect1 = new SubJect1("请大家学习《基于lambda简化设计模式》");
//注册订阅者
subJect1.registerObserver(s -> System.out.println("观察者1收到消息" + s));
subJect1.registerObserver(s -> System.out.println("观察者2收到消息" + s));
//主题发起通知
subJect1.notifyObserver();
}
责任链模式
我们希望字符串被对象1处理完成之后要转交给对象2处理,并且我们后续可能还会交给更多的对象处理,通过对需求的梳理和抽象,这个功能完全可以通过责任链模式来实现。
首先声明公共抽象类,可以看到考虑类的通用性笔者将这个类的入参设置为泛型,并且公共方法handle
的步骤为:
- 调用自己的
handWork
处理输入数据,handWork
交给实现类自行编写。 - 若
successor
不为空,则将处理结果交给下一个处理器处理,由此构成一条处理链。
public abstract class ProcessingObject<T> {
/**
* 下一个处理器
*/
private ProcessingObject<T> successor;
public ProcessingObject<T> getSuccessor() {
return successor;
}
public void setSuccessor(ProcessingObject<T> successor) {
this.successor = successor;
}
public T handle(T input) {
//先自己处理完,如果有后继责任链,则交给后面的责任链处理,递归下去
T t = handWork(input);
if (successor != null) {
return successor.handWork(t);
}
return t;
}
/**
* 自己的处理逻辑
*
* @param intput
* @return
*/
abstract T handWork(T intput);
}
对应的我们基于这个抽象类实现两个字符处理器,ProcessingStr1
会将收到的中文逗号换位英文逗号:
public class ProcessingStr1 extends ProcessingObject<String> {
@Override
String handWork(String intput) {
return intput.replace(",", ",");
}
}
而ProcessingStr2
会将中文句号替换为英文句号:
public class ProcessingStr2 extends ProcessingObject<String> {
@Override
String handWork(String intput) {
return intput.replace("。", ".");
}
}
测试代码和输出结果如下:
public static void main(String[] args) {
ProcessingObject<String> p1 = new ProcessingStr1();
ProcessingObject<String> p2 = new ProcessingStr2();
p1.setSuccessor(p2);
System.out.println(p1.handle("hello,world。"));
}
可以看到所有的中文符号都被替换成英文符号了:
hello,world.
话不多说,不难看出上文这种传入String
返回String
的方法,我们完全可以使用UnaryOperator
函数式接口实现表达式。
从UnaryOperator
源码可知,它继承Function
,我们只需传入泛型T
即可得到一个Function<T, T>
,从而让我们得到一个T->T
的Function
表达式:
@FunctionalInterface
public interface UnaryOperator<T> extends Function<T, T> {
/**
* Returns a unary operator that always returns its input argument.
*
* @param <T> the type of the input and output of the operator
* @return a unary operator that always returns its input argument
*/
static <T> UnaryOperator<T> identity() {
return t -> t;
}
}
而责任连的方式也很简单,因为UnaryOperator
是Function的子类,这意味着我们可以使用Function
的andThen
将所有的UnaryOperator
完成衔接:
UnaryOperator<String> p1 = i -> i.replace(",", ",");
UnaryOperator<String> p2 = i -> i.replace("。", ".");
p1.andThen(p2);
System.out.println(p1.apply("hello,world。"));
小结
为了适配设计模式常会出现为了一段简单的逻辑,而去编写大量实现类的情况,所以我们建议,对于逻辑比较简单且需要适配设计模式的功能,可以尝试找到合适的函数式接口简化功能的实现,避免大量类文件的声明。
参考
Java 8 in Action