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


相关推荐
prince056 分钟前
git仓库中提交上去了.idea文件夹内容怎么办?
java·git·intellij-idea
孑么7 分钟前
力扣 全排列
java·算法·leetcode·职场和发展
天之涯上上9 分钟前
Maven 在尝试连接到 Maven Central 仓库超时的解决方案和排查步骤
java·maven
dami_king10 分钟前
Apache Maven介绍|Maven安装
java·maven·apache
0xCC说逆向20 分钟前
Windows图形界面(GUI)-QT-C/C++ - QT信号与槽机制详解
java·c语言·开发语言·c++·windows·qt·mfc
master-dragon28 分钟前
mybatis-spring @MapperScan走读分析
java·spring·mybatis
努力学习java的哈吉米大王1 小时前
初识JAVA-面向对象的三大特征之多态
java·开发语言
KpLn_HJL1 小时前
leetcode - 3223. Minimum Length of String After Operations
java·算法·leetcode
okmacong1 小时前
04.计算机体系三层结构与优化(操作系统、计算机网络、)
java·服务器·计算机网络
kikyo哎哟喂2 小时前
Spring&SpringBoot常用注解总结
java·spring boot·spring