【设计模式】Java Fluent编程

背景

Fluent Api最早是由Martin Fowler1提出的一种简洁的编程风格, 其每一步的执行都返回一个对象并可用于进一步的方法调用. 这样的编程风格可以很好的简化某些领域的开发, 并显著地提高代码的可读性和可维护性. 无论是在Java的流式api中, 还是众多DLS中都有它的身影. 原因主要有以下几点:

  • 代码简化: 流式编程提供了一种链式调用的方法, 可以更加灵活地将多个操作组合在一起, 减少代码量
  • 代码封装: 流式编程可以将某些复杂操作封装起来, 屏蔽其实现细节, 进而带来良好的可维护性
  • 延迟计算: 流式编程中往往只有到达了某个状态才开始计算, 其过程可以对执行链路进行优化, 进而带来更好的系统性能

功能实现

流式编程在java中的示例: Stream

在java中, Stream是流式编程的一个非常典型的例子, 甚至"Stream"同"Fluent"一样都可以翻译成"流". 以下面的代码为例:

    public static void main(String[] args) {
        int[] array = new Random()
                .ints()
                .limit(100)
                .filter(x -> x > 10)
                .sorted()
                .toArray();
        System.out.println(Arrays.toString(array));
    }

上述例子可以非常容易地过滤掉100个小于10的随机数, 如果我们想要添加排序功能, 可以通过简单添加parallel一个关键字实现:

    public static void main(String[] args) {
        int[] array = new Random()
                .ints()
                .parallel()
                .limit(100)
                .filter(x -> x > 10)
                .sorted()
                .toArray();
        System.out.println(Arrays.toString(array));
    }

可见流式编程能够非常有效地简化代码, 进而带来非常好的可维护性.

一个订单的例子2

流式编程的本质其实是状态的转换[^3], 以餐馆销售三明治为例, 实现通过流式api购买三明治.2

餐馆的构成很简单, 可以购买三明治和饮料, 三明治和饮料又分别有不同的配料和种类; 在选择这些种类的时候应支持Fluent api风格. 主要需要实现的有以下几点:

  • 餐馆可以购买三明治, 三明治可以选择大小, 面包和馅料, 餐馆根据三明治的配料决定价格
  • 餐馆可以购买饮料, 饮料可以选择种类和大小, 餐馆可以根据饮料的种类决定价格
  • 客户可以购买无限多的三明治和饮料, 所有的三明治和饮料必须由上述三个部分组成
  • 餐馆可以打印账单

据此可以画出状态机图:
在这里插入图片描述

状态机实现

根据上面状态机图, 编写相应的接口

package com.passnight.javanote.design.fluent.sandwich;

public interface Order {
    interface SandwichOrder {
        interface BreadOrder {
            SizeOrder bread(BreadType bread);
        }

        interface SizeOrder {
            StyleOrder size(Size size);
        }

        interface StyleOrder {

            Order vegan();

            Order meat();
        }
    }

    interface DrinkOrder {
        interface DrinksStyleOrder {

            SizeOrder softDrink();

            SizeOrder cocktail();
        }

        interface SizeOrder {
            Order size(Size size);
        }
    }


    Order.SandwichOrder.BreadOrder sandwich();

    Order.DrinkOrder.DrinksStyleOrder drink();

    Checkout checkout();
}

状态转换的实现

编写OrderFluent, DrinkOrderFluent, SandwichOrderFluent类实现上面的接口, 这里注意 DrinkOrderFluentSandwichOrderFluentOrderFluent之间是组合的关系, 因为内部类的生命周期依赖于外部类而存在

package com.passnight.javanote.design.fluent.sandwich;


import java.util.ArrayList;
import java.util.List;

class OrderFluent implements Order {

    List<Sandwich> sandwiches = new ArrayList<>();
    List<Drink> drinks = new ArrayList<>();

    @Override
    public SandwichOrder.BreadOrder sandwich() {
        return new SandwichOrderFluent();
    }

    @Override
    public DrinkOrder.DrinksStyleOrder drink() {
        return new DrinkOrderFluent();
    }

    @Override
    public Checkout checkout() {
        return new Checkout(sandwiches, drinks);
    }

    class DrinkOrderFluent implements Order.DrinkOrder.DrinksStyleOrder,
            Order.DrinkOrder.SizeOrder {
        private Drink drink;

        @Override
        public Order.DrinkOrder.SizeOrder softDrink() {
            drink = new Drink();
            drink.setType(DrinkType.SOFT_DRINK);
            return this;
        }

        @Override
        public Order.DrinkOrder.SizeOrder cocktail() {
            drink = new Drink();
            drink.setType(DrinkType.COCKTAIL);
            return this;
        }

        @Override
        public Order size(Size size) {
            drink.setSize(size);
            OrderFluent.this.drinks.add(drink);
            return OrderFluent.this;
        }
    }

    class SandwichOrderFluent implements Order.SandwichOrder.BreadOrder,
            Order.SandwichOrder.SizeOrder,
            Order.SandwichOrder.StyleOrder {
        private Sandwich sandwich;

        @Override
        public Order.SandwichOrder.SizeOrder bread(BreadType bread) {
            sandwich = new Sandwich();
            sandwich.setBread(bread);
            return this;
        }

        @Override
        public Order.SandwichOrder.StyleOrder size(Size size) {
            sandwich.setSize(size);
            return this;
        }

        @Override
        public Order vegan() {
            sandwich.setStyle(SandwichStyle.VEGAN);
            OrderFluent.this.sandwiches.add(sandwich);
            return OrderFluent.this;
        }

        @Override
        public Order meat() {
            sandwich.setStyle(SandwichStyle.MEAT);
            OrderFluent.this.sandwiches.add(sandwich);
            return OrderFluent.this;
        }
    }
}

餐馆的实现

在定义了状态转换之后, 需要定义三明治以及饮料的种类

public enum BreadType {
    ITALIAN, PLAIN, GLUTEN_FREE
}

public enum SandwichStyle {
    VEGAN, MEAT
}

public enum Size {
    SMALL, MEDIUM, LARGE
}

public enum DrinkType {
    SOFT_DRINK, COCKTAIL
}

之后是三明治和饮料的定义

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Sandwich {
    SandwichStyle style;
    BreadType bread;
    Size size;
}

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Drink {
    DrinkType type;
    Size size;
}

有了三明治和饮料之后, 餐厅需要有菜单标注其价格, 这里菜单使用enum的单例模式, java虚拟机自动帮助我们完成单例对象的线程安全的懒加载

enum Menu {

    INSTANCE;

    private final Map<Size, Double> sizePrice;

    private final Map<SandwichStyle, Double> stylePrice;

    private final Map<DrinkType, Double> drinkPrice;

    private final Map<BreadType, Double> breadPrice;

    Menu() {
        this.sizePrice = new EnumMap<>(Size.class);
        this.sizePrice.put(Size.SMALL, 1d);
        this.sizePrice.put(Size.MEDIUM, 5d);
        this.sizePrice.put(Size.LARGE, 10d);

        this.stylePrice = new EnumMap<>(SandwichStyle.class);
        this.stylePrice.put(SandwichStyle.MEAT, 10d);
        this.stylePrice.put(SandwichStyle.VEGAN, 12d);

        this.drinkPrice = new EnumMap<>(DrinkType.class);
        this.drinkPrice.put(DrinkType.SOFT_DRINK, 1d);
        this.drinkPrice.put(DrinkType.COCKTAIL, 6d);

        this.breadPrice = new EnumMap<>(BreadType.class);
        this.breadPrice.put(BreadType.PLAIN, 1d);
        this.breadPrice.put(BreadType.ITALIAN, 2d);
        this.breadPrice.put(BreadType.GLUTEN_FREE, 3d);
    }

    double getPrice(DrinkType type) {
        return ofNullable(this.drinkPrice.get(type))
                .orElseThrow(() -> new IllegalArgumentException("There is not price to the drink " + type));
    }

    double getPrice(BreadType bread) {
        return ofNullable(this.breadPrice.get(bread))
                .orElseThrow(() -> new IllegalArgumentException("There is not price to the bread " + bread));
    }


    double getPrice(SandwichStyle style) {
        return ofNullable(this.stylePrice.get(style))
                .orElseThrow(() -> new IllegalArgumentException("There is not price to the sandwich style " + style));
    }

    double getPrice(Size size) {
        return ofNullable(this.sizePrice.get(size))
                .orElseThrow(() -> new IllegalArgumentException("There is not price to the size " + size));
    }
}

最后是订单, 可以打印最终的价格已经购买的菜品

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Checkout {

    private final static Menu menu = Menu.INSTANCE;
    List<Sandwich> sandwiches = new ArrayList<>();
    List<Drink> drinks = new ArrayList<>();

    public double checkout() {
        return sandwiches.stream()
                .mapToDouble(sandwich -> menu.getPrice(sandwich.getBread()) + menu.getPrice(sandwich.getStyle()) + menu.getPrice(sandwich.getSize()))
                .sum()
                +
                drinks.stream()
                        .mapToDouble(drink -> menu.getPrice(drink.getType()) + menu.getPrice(drink.getSize()))
                        .sum();
    }

    public String receipt() {
        return "sandwiches\n"
                + sandwiches.stream()
                .map(sandwich -> sandwich.toString() + ": " + (menu.getPrice(sandwich.getBread()) + menu.getPrice(sandwich.getStyle()) + menu.getPrice(sandwich.getSize())))
                .collect(Collectors.joining("\n"))
                + "\ndrinks\n"
                + drinks.stream()
                .map(drink -> drink.toString() + ": " + (menu.getPrice(drink.getType()) + menu.getPrice(drink.getSize())))
                .collect(Collectors.joining("\n"));
    }
}

以下是一个测试类

public class Restaurant {
    public static void main(String[] args) {
        Checkout checkout = new OrderFluent()
                .drink()
                .cocktail()
                .size(Size.LARGE)
                .drink()
                .cocktail()
                .size(Size.MEDIUM)
                .drink()
                .softDrink()
                .size(Size.SMALL)
                .sandwich()
                .bread(BreadType.ITALIAN)
                .size(Size.LARGE)
                .vegan()
                .sandwich()
                .bread(BreadType.GLUTEN_FREE)
                .size(Size.LARGE)
                .meat()
                .sandwich()
                .bread(BreadType.PLAIN)
                .size(Size.LARGE)
                .meat()
                .checkout();

        System.out.println(checkout.receipt());
        System.out.println(checkout.checkout());
    }
}

运行结果如下

sandwiches
Sandwich(style=VEGAN, bread=ITALIAN, size=LARGE): 24.0
Sandwich(style=MEAT, bread=GLUTEN_FREE, size=LARGE): 23.0
Sandwich(style=MEAT, bread=PLAIN, size=LARGE): 21.0
drinks
Drink(type=COCKTAIL, size=LARGE): 16.0
Drink(type=COCKTAIL, size=MEDIUM): 11.0
Drink(type=SOFT_DRINK, size=SMALL): 2.0
97.0

引用

引用


  1. Fluent interface - Wikipedia↩︎

  2. Fluent-API: Creating Easier, More Intuitive Code with a Fluent API | by Otavio Santana | xgeeks | Medium
    [^ 3]: https://blog.csdn.net/significantfrank/article/details/104996419 ↩︎ ↩︎

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

pass night

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值