【Java设计模式-5】装饰模式:给咖啡加点“佐料”

今天咱们要探索一下Java世界里的装饰模式(Decorator Pattern)。为了让这个过程更加生动易懂,咱们就以大家都熟悉的咖啡饮品来举例吧,想象一下,你就是那个咖啡大师,要给顾客调制出各种独特口味的咖啡哦!

一、咖啡的基础:简单的一杯咖啡

在我们的咖啡世界里,首先得有一杯基础的咖啡呀。就好比是Java里的一个简单类,它具备最基本的功能------能让你尝到咖啡的原味。

java 复制代码
// 抽象的咖啡类,定义了咖啡的基本行为(获取描述和价格)
abstract class Coffee {
    public abstract String getDescription();
    public abstract double getCost();
}

// 具体的咖啡实现类,这里是简单的黑咖啡
class BlackCoffee extends Coffee {
    @Override
    public String getDescription() {
        return "黑咖啡";
    }

    @Override
    public double getCost() {
        return 2.0; // 假设黑咖啡的价格是2元
    }
}

看,这里我们有了一个抽象的 Coffee 类,它规定了所有咖啡都应该能告诉我们它的描述(是什么咖啡)以及价格。然后 BlackCoffee 就是最基础的那种黑咖啡啦,原汁原味,价格也相对简单。

二、装饰模式登场:给咖啡加点料

现在呢,顾客们可不会满足于仅仅只有黑咖啡呀,他们可能想要加糖、加冰或者加牛奶,来调出自己喜欢的口味。这时候,装饰模式就该闪亮登场啦!

装饰模式的核心思想就是在不改变原有对象(这里就是黑咖啡)结构的基础上,动态地给它添加一些额外的功能(比如加糖、加冰等)。

我们先创建一个抽象的装饰者类,它和咖啡类一样,也实现了 Coffee 接口,这样它就能"伪装"成一杯咖啡啦。

java 复制代码
// 抽象的咖啡装饰者类,继承自Coffee类,可以用来装饰其他咖啡对象
abstract class CoffeeDecorator extends Coffee {
    protected Coffee decoratedCoffee;

    public CoffeeDecorator(Coffee coffee) {
        this.decoratedCoffee = coffee;
    }

    @Override
    public String getDescription() {
        return decoratedCoffee.getDescription();
    }

    @Override
    public double getCost() {
        return decoratedCoffee.getCost();
    }
}

这个抽象装饰者类有一个很重要的成员变量 decoratedCoffee,它用来保存被装饰的那个咖啡对象哦。而且注意啦,它的 getDescriptiongetCost 方法默认是返回被装饰咖啡的描述和价格,因为我们还没开始添加额外的东西嘛。

三、具体的装饰者:糖、冰、牛奶来啦

接下来,咱们就可以创建具体的装饰者类啦,分别对应着加糖、加冰和加牛奶。

加糖装饰者

java 复制代码
// 加糖装饰者类,给咖啡加糖
class SugarDecorator extends CoffeeDecorator {
    public SugarDecorator(Coffee coffee) {
        super(coffee);
    }

    @Override
    public String getDescription() {
        return decoratedCoffee.getDescription() + ", 加了糖";
    }

    @Override
    public double getCost() {
        return decoratedCoffee.getCost() + 0.5; // 假设糖的价格是0.5元
    }
}

看呀,当我们用 SugarDecorator 去装饰一杯咖啡(比如黑咖啡)的时候,它会在原来咖啡的描述后面加上",加了糖",而且价格也会相应地增加0.5元哦,就好像真的给咖啡加了一份甜蜜的魔法呢!

加冰装饰者

java 复制代码
// 加冰装饰者类,给咖啡加冰
class IceDecorator extends CoffeeDecorator {
    public IceDecorator(Coffee coffee) {
        super(coffee);
    }

    @Override
    public String getDescription() {
        return decoratedCoffee.getDescription() + ", 加了冰";
    }

    @Override
    public double getCost() {
        return decoratedCoffee.getCost() + 0.3; // 假设冰的价格是0.3元
    }
}

哇哦,IceDecorator 一上场,咖啡就变得清凉爽口啦,描述里多了",加了冰",价格也稍微涨了一点点,毕竟冰块也是有成本的嘛。

加牛奶装饰者

java 复制代码
// 加牛奶装饰者类,给咖啡加牛奶
class MilkDecorator extends CoffeeDecorator {
    public MilkDecorator(Coffee coffee) {
        super(coffee);
    }

    @Override
    public String getDescription() {
        return decoratedCoffee.getDescription() + ", 加了牛奶";
    }

    @Override
    public double getCost() {
        return decoratedCoffee.getCost() + 1.0; // 假设牛奶的价格是1元
    }
}

嘿嘿,加了牛奶的咖啡感觉更加香浓醇厚了呢,描述变得更诱人,价格也因为加了牛奶而有所增加啦。

四、调制一杯独特的咖啡

现在,咱们就可以像个真正的咖啡大师一样,开始调制各种独特口味的咖啡啦!

java 复制代码
public class CoffeeShop {
    public static void main(String[] args) {
        // 先制作一杯黑咖啡
        Coffee coffee = new BlackCoffee();
        System.out.println("您点了一杯:" + coffee.getDescription() + ",价格是:" + coffee.getCost() + "元");

        // 给黑咖啡加糖
        coffee = new SugarDecorator(coffee);
        System.out.println("现在您的咖啡变成了:" + coffee.getDescription() + ",价格是:" + coffee.getCost() + "元");

        // 再加冰
        coffee = new IceDecorator(coffee);
        System.out.println("哇哦,又加了冰,现在是:" + coffee.getDescription() + ",价格是:" + coffee.getCost() + "元");

        // 最后再加牛奶
        coffee = new MilkDecorator(coffee);
        System.out.println("终极版咖啡来啦:" + coffee.getDescription() + ",价格是:" + coffee.getCost() + "元");
    }
}

运行上面的代码,你就会看到一杯普通的黑咖啡是如何一步步变成一杯超级豪华、口味独特的咖啡的哦。就像这样:

复制代码
您点了一杯:黑咖啡,价格是:2.0元
现在您的咖啡变成了:黑咖啡, 加了糖,价格是:2.5元
哇哦,又加了冰,现在是:黑咖啡, 加了糖, 加了冰,价格是:2.8元
终极版咖啡来啦:黑咖啡, 加了糖, 加了冰, 加了牛奶,价格是:3.8元

是不是很有趣呀?通过装饰模式,我们可以非常灵活地根据顾客的需求,给咖啡添加各种各样的配料,而且每添加一种配料,咖啡的描述和价格都会相应地发生变化,就好像真的在现实生活中的咖啡店里调制咖啡一样呢!

五、装饰模式在SpringBoot中的应用场景

聊完了装饰模式的基本概念和示例,咱们再来说说它在SpringBoot这个强大的框架中是怎么大展身手的吧。

场景一:日志增强

在一个SpringBoot应用中,我们经常需要记录各种操作的日志。假设我们有一个简单的服务接口 UserService,它提供了一些用户相关的操作方法,比如 addUser()(添加用户)、updateUser()(更新用户信息)等等。

java 复制代码
public interface UserService {
    void addUser(User user);
    void updateUser(User user);
}

public class UserServiceImpl implements UserService {
    @Override
    public void addUser(User user) {
        // 实际添加用户的逻辑
        System.out.println("添加用户:" + user.getName());
    }

    @Override
    public void updateUser(User user) {
        // 实际更新用户信息的逻辑
        System.out.println("更新用户:" + user.getName());
    }
}

现在,我们想要在每个方法调用前后都记录详细的日志,包括方法名、传入的参数、执行时间等等。如果直接在 UserServiceImpl 类里面添加日志记录的代码,会让这个服务类变得很杂乱,而且不符合单一职责原则。这时候,装饰模式就可以派上用场啦!

我们可以创建一个日志装饰器 LoggingDecorator,它实现了 UserService 接口,并且持有一个 UserService 的引用。

java 复制代码
public class LoggingDecorator implements UserService {
    private final UserService userService;

    public LoggingDecorator(UserService userService) {
        this.userService = userService;
    }

    @Override
    public void addUser(User user) {
        long startTime = System.currentTimeMillis();
        System.out.println("开始执行 addUser 方法,参数:" + user);
        userService.addUser(user);
        long endTime = System.currentTimeMillis();
        System.out.println("addUser 方法执行完毕,耗时:" + (endTime - startTime) + " 毫秒");
    }

    @Override
    public void updateUser(User user) {
        long startTime = System.currentTimeMillis();
        System.out.println("开始执行 updateUser 方法,参数:" + user);
        userService.updateUser(user);
        long endTime = System.currentTimeMillis();
        System.out.println("updateUser 方法执行完毕,耗时:" + (endTime - startTime) + " 毫秒");
    }
}

然后,在配置SpringBoot的Bean时,我们可以这样来使用这个装饰器:

java 复制代码
@Configuration
public class AppConfig {
    @Bean
    public UserService userService() {
        UserService userServiceImpl = new UserServiceImpl();
        return new LoggingDecorator(userServiceImpl);
    }
}

这样,当我们在其他地方注入 UserService 并调用它的方法时,就会自动记录详细的日志啦,而且 UserServiceImpl 类本身的代码依然保持简洁,专注于实现用户服务的核心逻辑。

场景二:权限验证增强

再比如,我们的应用中有一些需要权限验证的接口,只有具有特定权限的用户才能访问。假设我们有一个 OrderService,它提供了一些订单相关的操作方法,比如 createOrder()(创建订单)、cancelOrder()(取消订单)等等。

java 复制代码
public interface OrderService {
    void createOrder(Order order);
    void cancelOrder(Order order);
}

public class OrderServiceImpl implements OrderService {
    @Override
    public void createOrder(Order order) {
        // 实际创建订单的逻辑
        System.out.println("创建订单:" + order.getOrderId());
    }

    @Override
    public void cancelOrder(Order order) {
        // 实际取消订单的逻辑
        System.out.println("取消订单:" + order.getOrderId());
    }
}

现在,我们想要在调用这些订单操作方法之前,先进行权限验证。同样的,我们可以创建一个权限验证装饰器 PermissionDecorator

java 复制代码
public class PermissionDecorator implements OrderService {
    private final OrderService orderService;

    public PermissionDecorator(OrderService orderService) {
        this.orderService = orderService;
    }

    @Override
    public void createOrder(Order order) {
        if (hasPermission()) { // 这里假设已经有一个方法来判断是否有权限
            orderService.createOrder(order);
        } else {
            throw new RuntimeException("没有权限创建订单");
        }
    }

    @Override
    public void cancelOrder(Order order) {
        if (hasPermission()) {
            orderService.cancelOrder(order);
        } else {
            throw new RuntimeException("没有权限取消订单");
        }
    }

    private boolean hasPermission() {
        // 实际的权限验证逻辑,这里简单返回true模拟有权限
        return true;
    }
}

然后,在SpringBoot的配置中这样使用:

java 复制代码
@Configuration
public class AppConfig {
    @Bean
    public OrderService orderService() {
        OrderService orderServiceImpl = new OrderServiceImpl();
        return new PermissionDecorator(orderServiceImpl);
    }
}

这样,每次调用订单服务的方法时,都会先进行权限验证,如果没有权限就会抛出异常,而 OrderServiceImpl 类本身不需要关心权限验证的事情,只专注于订单业务逻辑的实现。

通过这两个例子,我们可以看到在SpringBoot中,装饰模式可以很好地帮助我们在不修改原有业务逻辑类的基础上,对其功能进行增强,比如添加日志记录、权限验证等额外的职责,让我们的代码更加清晰、可维护和可扩展。

六、总结一下

好啦,咱们这次探索了Java设计模式中的装饰模式。这个模式的优点可不少哦,它让我们可以在不修改原有类的基础上,动态地扩展对象的功能,非常符合开闭原则(对扩展开放,对修改关闭)。就像我们给咖啡添加配料一样,不需要去改动原来的黑咖啡类,只需要创建新的装饰者类就可以啦。


相关推荐
张张张3129 分钟前
4.2学习总结 Java:list系列集合
java·学习
KATA~12 分钟前
解决MyBatis-Plus枚举映射错误:No enum constant问题
java·数据库·mybatis
xyliiiiiL27 分钟前
一文总结常见项目排查
java·服务器·数据库
shaoing29 分钟前
MySQL 错误 报错:Table ‘performance_schema.session_variables’ Doesn’t Exist
java·开发语言·数据库
腥臭腐朽的日子熠熠生辉1 小时前
解决maven失效问题(现象:maven中只有jdk的工具包,没有springboot的包)
java·spring boot·maven
ejinxian1 小时前
Spring AI Alibaba 快速开发生成式 Java AI 应用
java·人工智能·spring
杉之1 小时前
SpringBlade 数据库字段的自动填充
java·笔记·学习·spring·tomcat
圈圈编码2 小时前
Spring Task 定时任务
java·前端·spring
俏布斯2 小时前
算法日常记录
java·算法·leetcode
27669582922 小时前
美团民宿 mtgsig 小程序 mtgsig1.2 分析
java·python·小程序·美团·mtgsig·mtgsig1.2·美团民宿