设计模式之策略模式

策略模式定义了算法簇,分别封装起来,让它们之间可以相互替换,此模式让算法的变化独立于使用算法的客户。

来解析这个定义,“算法簇”&“相互替换”代表了是一组相同目的算法,肯定是利用多态能力实现。脑海中,下述的类图就出来了。

“变化独立于使用算法的客户”,这句话的意思是,使用这些算法的客户端代码不需要因为算法的变化而修改自身,什么意思,如何能做到改变算法而不修改自身,那就是客户端代码只依赖于算法的抽象(接口)而不是具体的实现

通过类图,可以清晰的看到两者的区别。

关联抽象
关联具体

学习设计模式,一个关键的自我校验,是能够自己给自己出题,构建场景,并通过目标设计模式来解决。

💡
以支付场景来举例,具体的支付方式有支付宝和微信,需要支持用户在实际付款时动态切换。
  1. 定义算法接口:定义一个 PaymentStrategy 接口,表示支付方式的抽象。
public interface PaymentStrategy {
    void pay(int amount);
}
  1. 实现具体算法AlipayStrategy 和 WechatPayStrategy 分别实现 PaymentStrategy 接口。
public class AlipayStrategy implements PaymentStrategy {
    @Override
    public void pay(int amount) {
        System.out.println("使用支付宝支付:" + amount + "元");
    }
}

public class WechatPayStrategy implements PaymentStrategy {
    @Override
    public void pay(int amount) {
        System.out.println("使用微信支付:" + amount + "元");
    }
}
  1. 动态替换算法:客户端可以根据用户的选择,使用支付宝或微信支付,通过持有算法接口的引用,可以动态切换具体的算法实现。
public class Client {
    public static void main(String[] args) {
    	// 使用支付宝支付
    	PaymentStrategy strategy = new AlipayStrategy();
    	strategy.pay(100);

    	// 使用微信支付
    	PaymentStrategy strategy = new WechatPayStrategy();
    	strategy.pay(300);
    }
}

至此,一个简单的策略模式模式实现就完成了。

但是,这个实现并不完美,让我们再回顾一遍策略模式的定义,策略模式定义了算法簇,分别封装起来,让它们之间可以相互替换,此模式让算法的变化独立于使用算法的客户。现有的实现能够让算法的变化独立于使用算法的客户吗?

一方面,如果支付的方法发生了变化,如pay()变成了pay(int mount),那么client的代码就要发生变化,实际应用中,可不止client一个,还有client-1、client-2....client-10,所有的client方调用代码都要进行修改,明显不符合“算法的变化独立于使用算法的客户”这个要求。

另一方面,client除了关联抽象类,还关联了具体类,没有完全解耦。

最后,client除了要负责策略的创建,还要负责策略的调用,职责过多,可以考虑将一部分职责抽离出去。

一个简单的实现,就发现了三个方面的不完美,能够发现并理解这些问题,才知道如何改进,才知道代码为何要如此设计,理解是最重要的一步。再次强调,设计模式不能靠死记硬背,而要基于理解,通过基石加上原则来反复考量现有设计。

开始改良,很明显,需要抽离出一个类,来讲client与具体策略解耦,并负责策略的创建,当pay()发生改动时,只需要这一个类即可。

新增定义上下文类:提供一个方法供客户端调用。

// 上下文类
public class PaymentContext {
    private PaymentStrategy strategy;

    // 设置具体策略
    public void setStrategy(PaymentStrategy strategy) {
        this.strategy = strategy;
    }

    // 执行支付
    public void executePayment(int amount) {
        if (strategy != null) {
            strategy.pay(amount);
        } else {
            System.out.println("未设置支付策略!");
        }
    }
}

重写客户端代码。

// 客户端代码
public class Client {
    public static void main(String[] args) {
        // 创建上下文对象
        PaymentContext context = new PaymentContext();

        // 使用支付宝支付
        context.setStrategy(new AlipayStrategy());
        context.executePayment(100); // 输出:使用支付宝支付:100元

        // 使用微信支付
        context.setStrategy(new WechatPayStrategy());
        context.executePayment(200); // 输出:使用微信支付:200元

        // 动态切换策略
        context.setStrategy(new AlipayStrategy());
        context.executePayment(300); // 输出:使用支付宝支付:300元
    }
}

针对这个实现,再来考核之前三个方面的问题。

  1. 如果pay()变成了pay(int mount),只需要修改PaymentContext的代码,客户端client-1、client-2...client-100都不用改变。符合要求。
  2. client关联了具体类,没有完全解耦。这个问题似乎还存在,client依然关联到了具体类,但是注意,这与前一版的实现,有很大的区别,前一版中客户端代码除了知道具体类的存在,还调用到了具体的方法,而改良后,客户端代码只知道具体类的存在而已,耦合深度大大降低,在实际实现中,可通过配置文件实现解耦。如果要完全解除对具体类的关联,可通过工厂模式或依赖注入的方式实现。
  3. 改良后客户端代码只负责设置策略和触发支付,具体的支付逻辑由 PaymentContext和策略类负责。

并且,后续增加策略时,客户端代码只需要设置新的策略对象,而不需要修改调用逻辑。

Read more

痛风带来的思考

昨晚一罐冰啤酒下去,睡觉时就感觉脚踝隐隐发作,果然早上起床直接下不来地。跟崴脚的感觉十分相似,无法行走,只能坐在一起上滑动,公司上班也去不了了,呆呆得躺在家里,下午疼痛感加剧,整个心思都在左脚的疼痛上,没有其他任何多余的精力去关注其他事情,而此刻的阳台,乃最美人间四月天,春日的微风吹拂着阳台的花儿,温暖的阳光抛洒下来,一切都如此惬意,而我却无心欣赏。 人在健康时,生活中有好多问题,但人在不健康时,生活中只剩一个问题。 我对这句话的理解更深刻了。人是健忘的,在疫情期间、在手术期间,这种感悟其实已经很深刻了,但是病情好转之后,人还是会被日常的琐碎、工作的烦扰搅乱心绪,没有专注的去享受生活本身的美好。 幸福的秘密在于,去享受我们所拥有的,而不是顽固的去追求所没有拥有的。阳光、草木、微风,都是幸福的玩意儿,应尽情的享受。 再等两天,脚完全恢复好了,身体健健康康后,我要以更轻盈的姿态去生活,不纠结他人的看法,不执着别人的认可,关注自己的能力,享受拥有的生后。 还有一个反省,针对咖啡、酒、烟,

By 李浩

设计模式之命令模式

命令模式将“请求”封装成对象,以便使用不同的请求、队列或者日志来参数化其他对象,命令模式也支持可撤销的操作。 来解析这个定义: 1. “将一个请求封装为一个对象”,请求原本是一个方法,现在要封装成一个对象,说明要新增类来完成。 2. “可以用不同的请求对客户进行参数化”,说明是将命令对象作为参数进行传递。 3. “队列”说明需要维护命令多个命令的列表队列。 4. “撤销”说明有命令对象有undo撤销方法。 命令模式在设计模式中,算是一个比较不好理解的模式,很重要的原因是不清楚设计意图,不清楚不用这个模式前有何问题,这个模式带了哪些好处,能解决什么问题。 上一篇状态模式中,看到了状态模式抽离的是状态(属性),向上提成状态对象。有了这个基础,再来理解命令模式就相对简单了。命令模式抽离的是行为(方法),向上提成命令对象。 两者都通过“对象化”来解耦和扩展系统,但解决的问题不同: * 状态模式:处理对象内部状态驱动的行为变化。 * 命令模式:处理行为请求的封装与调度。 💡智能家居遥控器,假设我们有一个智能家居遥控器,可以控制 灯(Light) 和

By 李浩

设计模式之状态模式

状态模式允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类。 来解析这个定义: 1. “内部状态”表明对象内部有一个属性来表示状态。 2. “内部状态改变时改变它的行为,对象看起来好像修改了它的类”说明状态改变后对象的行为发生了非常大的变化,不像是同一类的行为。 从目前的分析中似乎无法推导出状态模式的类图结构。 从实际的例子出来,来看看状态模式是如何演进而来。 💡我们有一个文档审批系统,文档有以下状态和转换: 1. 草稿(Draft) → 提交 → 待审批(PendingReview) 2. 待审批 → 批准 → 已发布(Published) 3. 待审批 → 拒绝 → 草稿 4. 已发布 → 撤回 → 草稿 从直觉出发,会使用条件语句实现需求逻辑。 public class Document { private String state = "DRAFT"; // 初始状态为草稿 public void submit(

By 李浩

设计模式之代理模式

代理模式为另一个对象提供一个替身或占位符以控制对这个对象的访问。 解析这个定义: 1. “替身”表明在客户端看来,代理类与被代理类是同一类别,对客户端来说看上去没什么区别,依然能够满足诉求。如此可以看出代理类与被代理类来自同一个超类。 2. “控制对这个对象的访问”,能够控制访问,说明前提是能够访问,才能在访问之前做这个限制,即代理持有对真实对象的引用(或能创建它)。 当然可能会质疑,用继承的方式不是也能完成目标吗,用代理类去继承被代理类,然后重写方法,加入控制逻辑。但这违背了"组合优于继承"原则,代理类与被代理类强耦合。 💡我们要开发一个图片查看器,需求如下: 1. 图片加载开销大(从磁盘或网络加载耗时),希望首次显示时才加载(延迟加载)。 2. 某些图片需要权限校验,只有授权用户才能查看。 3. 客户端代码应统一接口,无需关心是直接加载图片还是通过代理。 // 1. 抽象接口(Subject) interface Image { void display(); } // 2. 真实对象(RealSubject)

By 李浩