写点什么

如何使用 Java8 改造模板方法模式!

  • 2023-01-30
    湖南
  • 本文字数:4406 字

    阅读完需:约 14 分钟

如何使用 Java8 改造模板方法模式!

我们在日常开发中,经常会遇到类似的场景:当要做一件事儿的时候,这件事儿的步骤是固定好的,但是每一个步骤的具体实现方式是不一定的。通常,遇到这种情况,我们会把所有要做的事儿抽象到一个抽象类中,并在该类中定义一个模板方法。这就是所谓的模板方法模式。


以前的模板方法在我之前的一篇《设计模式——模板方法设计模式》https://www.hollischuang.com/archives/420 文章中举过一个例子:当我们去银行的营业厅办理业务需要以下步骤:1.取号、2.办业务、3.评价。三个步骤中取号和评价都是固定的流程,每个人要做的事儿都是一样的。但是办业务这个步骤根据每个人要办的事情不同所以需要有不同的实现。我们可以将整个办业务这件事儿封装成一个抽象类:/**


  • 模板方法设计模式的抽象类

  • @author hollis


*/


public abstract class AbstractBusinessHandler {


/**
* 模板方法
*/
public final void execute(){
getNumber();
handle();
judge();
}
/**
* 取号
* @return
*/
private void getNumber(){
System.out.println("number-00" + RandomUtils.nextInt());
}
/**
* 办理业务
*/
public abstract void handle(); //抽象的办理业务方法,由子类实现
/**
* 评价
*/
private void judge(){
System.out.println("give a praised");
}
复制代码


}我们在类中定义了一个 execute 类,这个类编排了 getNumber、handle 和 judge 三个方法。这就是一个模板方法。其中 getNumber 和 judge 都有通用的实现,只有 handle 方法是个抽象的,需要子类根据实际要办的业务的内容去重写。有了这个抽象类和模板方法,当我们想要实现一个"存钱业务"的时候,只需要继承该 AbstractBusinessHandeler 并且重写 handle 方法即可:public class SaveMoneyHandler extends AbstractBusinessHandeler {


@Override
public void handle() {
System.out.println("save 1000");
}
复制代码


}这样,我们在执行存钱的业务逻辑的时候,只需要调用 SaveMoneyHandler 的 execute 方法即可:public static void main(String []args){


SaveMoneyHandler saveMoneyHandler = new SaveMoneyHandler();
saveMoneyHandler.execute();
复制代码


}输出结果:number-00958442164


save 1000


give a praised 以上,就是一个简单的模板方法的实现。通过使用模板方法,可以帮助我们很大程度的复用代码。因为我们要在银行办理很多业务,所以可能需要定义很多的实现类://取钱业务的实现类


public class DrawMoneyHandler extends AbstractBusinessHandeler {


@Override
public void handle() {
System.out.println("draw 1000");
}
复制代码


}


//理财业务的实现类


public class MoneyManageHandler extends AbstractBusinessHandeler{


@Override
public void handle() {
System.out.println("money manage");
}
复制代码


}一直以来,开发者们在使用模板方法的时候基本都是像上面这个例子一样:需要准备一个抽象类,将部分逻辑以具体方法以及具体构造函数的形式实现,然后声明一些抽象方法来让子类实现剩余的逻辑。不同的子类可以以不同的方式实现这些抽象方法,从而对剩余的逻辑有不同的实现。但是,有了 Java 8 以后,模板方法有了另外一种实现方式,不需要定义特别多的实现类了。


Java 8 的函数式编程 2014 年,Oracle 发布了 Java 8,在 Java 8 中最大的新特性就是提供了对函数式编程的支持。Java 8 在 java.util.function 下面增加增加一系列的函数接口。其中主要有 Consumer、Supplier、Predicate、Function 等。本文主要想要介绍一下 Supplier 和 Consumer 这两个,使用者两个接口,可以帮我们很好的改造模板方法。这里只是简单介绍下他们的用法,并不会深入展开,如果大家想要学习更多用法,可以自行 google 一下。SupplierSupplier 是一个供给型的接口,简单点说,这就是一个返回某些值的方法。最简单的一个 Supplier 就是下面这段代码:public List<String> getList() {


return new ArrayList();
复制代码


}


使用 Supplier 表示就是:Supplier<List<String>> listSupplier = ArrayList::new;


ConsumerConsumer 接口消费型接口,简单点说,这就是一个使用某些值(如方法参数)并对其进行操作的方法。最简单的一个 Consumer 就是下面这段代码:public void sum(String a1) {


System.out.println(a1);
复制代码


}


使用 Consumer 表示就是:Consumer<String> printConsumer = a1 -> System.out.println(a1);


Consumer 的用法,最见的的例子就是是 Stream.forEach(Consumer)这样的用法,它接受一个 Consumer,该 Consumer 消费正在迭代的流中的元素,并对每个元素执行一些操作,比如打印:Consumer<String> stringConsumer = (s) -> System.out.println(s.length());


Arrays.asList("ab", "abc", "a", "abcd").stream().forEach(stringConsumer);


Java 8 以后的模板方法在介绍过了 Java 8 中的 Consumer、Supplier 之后,我们来看下怎么改造之前我们介绍过的模板方法。首先,我们定义一个 BankBusinessHandler 类,并且重新定义一个 execute 方法,这个方法有一个入参,是 Consumer 类型的,然后移除 handle 方法,重新编排后的模板方法内容如下:/**


  • @author Hollis


*/


public class BankBusinessHandler {


private void execute(Consumer<BigDecimal> consumer) {
getNumber();
consumer.accept(null);
judge();
}
private void getNumber() {
System.out.println("number-00" + RandomUtils.nextInt());
}
private void judge() {
System.out.println("give a praised");
}
复制代码


}


我们实现的模板方法 execute 中,编排了 getNumber、judge 以及 consumer.accept,这里面 consumer.accept 就是具体的业务逻辑,可能是存钱、取钱、理财等。需要由其他方法调用 execute 的时候传入。这时候,我们想要实现"存钱"业务的时候,需要 BankBusinessHandler 类中增加以下方法:/**


  • @author Hollis


*/


public class BankBusinessHandler {


public void save(BigDecimal amount) {
execute(a -> System.out.println("save " + amount));
}
复制代码


}


在 save 方法中,调用 execute 方法,并且在入参处传入一个实现了"存钱"的业务逻辑的 Comsumer。这样,我们在执行存钱的业务逻辑的时候,只需要调用 BankBusinessHandler 的 save 方法即可:public static void main(String[] args) throws {


BankBusinessHandler businessHandler = new BankBusinessHandler();
businessHandler.save(new BigDecimal("1000"));
复制代码


}


输出结果:number-001736151440


save1000


give a praised


如上,当我们想要实现取钱、理财等业务逻辑的时候,和存钱类似:/**


  • @author Hollis


*/


public class BankBusinessHandler {


public void save(BigDecimal amount) {
execute(a -> System.out.println("save " + amount));
}
public void draw(BigDecimal amount) {
execute(a -> System.out.println("draw " + amount));
}
public void moneyManage(BigDecimal amount) {
execute(a -> System.out.println("draw " + amount));
}
复制代码


}


可以看到,通过使用 Java 8 中的 Comsumer,我们把模板方法改造了,改造之后不再需要抽象类、抽象方法,也不再需要为每一个业务都创建一个实现类了。我们可以把所有的业务逻辑内聚在同一个业务类中。这样非常方便这段代码的后期运维。前面介绍如何使用 Consumer 进行改造模板方法,那么 Supplier 有什么用呢?我们的例子中,在取号、办业务、评价这三个步骤中,办业务是需要根据业务情况进行定制的,所以,我们在模板方法中,把办业务这个作为扩展点开放给外部。有这样一种情况,那就是现在我们办业务的时候,取号的方式也不一样,可能是到银行网点取号、在网上取号或者银行客户经理预约的无需取号等。无论取号的方式如何,最终结果都是取一个号;而取到的号的种类不同,可能接收到的具体服务也不同,比如 vip 号会到 VIP 柜台办理业务等。想要实现这样的业务逻辑,就需要使用到 Supplier,Supplier 是一个"供给者",他可以用来定制"取号逻辑"。首先,我们需要改造下模板方法:/**


  • 模板方法


*/


protected void execute(Supplier<String> supplier, Consumer<BigDecimal> consumer) {


String number = supplier.get();
System.out.println(number);

if (number.startsWith("vip")) {
//Vip号分配到VIP柜台
System.out.println("Assign To Vip Counter");
}
else if (number.startsWith("reservation")) {
//预约号分配到专属客户经理
System.out.println("Assign To Exclusive Customer Manager");
}else{
//默认分配到普通柜台
System.out.println("Assign To Usual Manager");
}
consumer.accept(null);
judge();
复制代码


}


经过改造,execute 的入参增加了一个 supplier,这个 supplier 可以提供一个号码。至于如何取号的,交给调用 execute 的方法来执行。之后,我们可以定义多个存钱方法,分别是 Vip 存钱、预约存钱和普通存钱:public class BankBusinessHandler extends AbstractBusinessHandler {


public void saveVip(BigDecimal amount) {
execute(() -> "vipNumber-00" + RandomUtils.nextInt(), a -> System.out.println("save " + amount));
}
public void save(BigDecimal amount) {
execute(() -> "number-00" + RandomUtils.nextInt(), a -> System.out.println("save " + amount));
}
public void saveReservation(BigDecimal amount) {
execute(() -> "reservationNumber-00" + RandomUtils.nextInt(), a -> System.out.println("save " + amount));
}
复制代码


}


在多个不同的存钱方法中,实现不同的取号逻辑,把取号逻辑封装在 supplier 中,然后传入 execute 方法即可。测试代码如下:BankBusinessHandler businessHandler = new BankBusinessHandler();


businessHandler.saveVip(new BigDecimal("1000"));


输出结果:vipNumber-001638110566


Assign To Vip Counter


save 1000


give a praised


以上,我们就是用 Comsumer 和 Supplier 改造了模板方法模式。使用 Java 8 对模板方法进行改造之后,可以进一步的减少代码量,至少可少创建很多实现类,大大的减少重复代码,提升可维护性。当然,这种做法也不是十全十美的,有一个小小的缺点,那就是理解成本稍微高一点,对于那些对函数式编程不太熟悉的开发者来说, 上手成本稍微高了一些。。。


总结以上,我们介绍了什么是模板方法模式,以及如何使用 Comsumer 和 Supplier 改造模板方法模式。这样的做法是我们日常开发中经常会用到的,其实,我觉得本文中的例子并不是完完全全能表达出来我想表达的意思,但是我们的真实业务中的逻辑讲起来又比较复杂。所以,这就需要大家能够多多理解并且实践一下。如果你代码中用到过模板方法模式,那一定是可以通过本文中的方法进行改造的。如果你还没用过模板方法模式,那说明你的应用中一定有很多重复代码,那就赶紧用起来。作为一个开发工程师,我们要尽最大努力的消灭应用中的重复代码,功在当代,利在千秋!

用户头像

欢迎关注公众号:灵风的架构笔记 2022-10-21 加入

Java后端架构领域丨面题面经丨学习路线丨职业规划丨专业知识丨互联网资讯等分享

评论

发布
暂无评论
如何使用 Java8 改造模板方法模式!_Java_风铃架构日知录_InfoQ写作社区