策略模式

概述

定义与概念

策略模式(Strategy Pattern)属于对象行为型模式。其核心思想是针对一组算法,将每个算法封装到具有共同接口的独立类中,从而实现它们之间的互相替换。该模式将算法的责任和算法本身分隔开,委派给不同的对象管理,把算法包装到一系列的策略类里面,作为一个抽象策略类的子类。简单来说,就是准备一组算法,并将每一个算法封装起来,使它们可以互换。

模式角色

  • 环境类(Context):持有一个 Strategy 类的引用。它是策略模式的使用场景,负责调用具体策略类的算法接口来完成特定的任务。例如,在一个游戏中,游戏角色的移动行为可以看作是一个环境,它可能持有不同移动策略(如步行策略、飞行策略等)的引用,根据游戏场景的不同选择合适的移动策略。
  • 抽象策略类(Strategy):这是一个抽象角色,通常由一个接口或抽象类实现。此角色给出了所有具体策略类所需要的接口,定义了算法的规范。具体的策略类必须实现这个接口,以保证算法的一致性和可替换性。
  • 具体策略类(Concrete Strategy):包装了相关的算法和行为,实现了抽象策略类定义的接口,提供了具体的算法实现。比如,在上述游戏中,步行策略类和飞行策略类就是具体策略类,它们分别实现了移动算法的不同方式。

代码实现

  1. 环境类(Context)
public class Context {
    // 持有策略类的引用
    private Strategy strategy;

    // 设置策略类的方法
    public void setStrategy(Strategy strategy) {
        this.strategy = strategy;
    }

    // 调用策略类的算法接口
    public void contextInterface() {
        strategy.strategyInterface();
    }
}
  1. 抽象策略类(Strategy)
public abstract class Strategy {
    // 抽象的算法接口
    public abstract void strategyInterface();
}
  1. 具体策略类(ConcreteStrategy)
public class ConcreteStrategyA extends Strategy {
    @Override
    public void strategyInterface() {
        // 实现具体的算法 A
        System.out.println("执行算法 A");
    }
}

public class ConcreteStrategyB extends Strategy {
    @Override
    public void strategyInterface() {
        // 实现具体的算法 B
        System.out.println("执行算法 B");
    }
}

特点与优势

  • 算法独立性与可扩展性
    策略模式使得每个算法都独立封装在一个类中,与其他算法互不干扰。当需要添加新的算法时,只需要创建一个新的具体策略类并实现抽象策略接口即可,不会影响到现有的代码结构。例如,如果在游戏中要添加一种新的移动方式,如游泳策略,只需要创建一个 SwimmingStrategy 类并实现 Strategy 接口。
  • 高内聚低耦合
    策略模式实现了高内聚低耦合。每个策略类专注于实现自己的算法,职责单一,内聚性高。同时,环境类与具体策略类之间通过抽象策略接口耦合,降低了它们之间的依赖关系,使得系统更加灵活和易于维护。当业务需求发生变化时,只需要调整或替换相应的策略类,而不需要大规模修改环境类或其他相关代码。

应用场景

  • 游戏开发中的行为控制
    如前面提到的游戏角色的移动、攻击等行为可以采用策略模式。不同的角色可能有不同的攻击策略(如近战攻击、远程攻击等),通过策略模式可以方便地实现和切换这些攻击方式,提高游戏的可玩性和灵活性。
  • 电商系统中的促销策略
    在电商系统中,根据不同的促销活动,商品的计价方式可能不同。例如,有满减策略、折扣策略、赠品策略等。使用策略模式可以将这些不同的计价算法封装成具体策略类,根据促销活动的规则选择合适的策略来计算商品价格,方便系统的扩展和维护。

代理模式

概述

概念解释

代理模式可以用生活中的例子来理解,比如“我很忙,忙得没空理你,那你要找我呢就先找我的代理人吧”。在这个场景中,代理人和被代理人具备同一个接口,代理人虽然不能直接干活,但是知道被代理的人能做哪些事情、不能做哪些事情。代理模式就是在这种情况下,通过代理对象来控制对真实对象的访问。

模式结构

  • 抽象主题角色(Subject):声明了代理主题和真实主题的共同接口,这样一来在任何可以使用真实主题的地方都可以使用代理主题。它定义了真实主题和代理主题都需要实现的方法,使得客户端可以以统一的方式调用真实主题和代理主题的操作。
  • 代理主题角色(Proxy):代理主题角色内部含有对真实主题的引用,从而可以在任何时候操作真实的主题对象。它提供一个与真实主题角色相同的接口,以便在任何时候都可以替代真实的主体,控制对真实主题的引用,负责在需要的时候创建真实主题对象。代理角色通常在将客户端调用传递给真实的主题之前或者之后都要执行一些额外的操作,比如权限验证、日志记录等。
  • 真实主题角色(RealSubject):定义了代理角色所代表的真实对象,实现了抽象主题角色声明的接口,提供了真实的业务逻辑和功能实现。

代码实现(以简单的图片加载为例)

  1. 抽象主题角色(Image 接口)
public interface Image {
    void display();
}
  1. 真实主题角色(RealImage 类)
public class RealImage implements Image {
    private String fileName;

    public RealImage(String fileName) {
        this.fileName = fileName;
        loadFromDisk(fileName);
    }

    private void loadFromDisk(String fileName) {
        System.out.println("正在从磁盘加载图片:" + fileName);
    }

    @Override
    public void display() {
        System.out.println("显示图片:" + fileName);
    }
}
  1. 代理主题角色(ProxyImage 类)
public class ProxyImage implements Image {
    private RealImage realImage;
    private String fileName;

    public ProxyImage(String fileName) {
        this.fileName = fileName;
    }

    @Override
    public void display() {
        if (realImage == null) {
            realImage = new RealImage(fileName);
        }
        realImage.display();
    }
}

应用场景

  • 远程代理(Remote Proxy)
    在分布式系统中,当客户端需要访问远程对象时,可以使用代理模式。代理对象位于客户端,它代表远程对象接收客户端的请求,并将请求转发给远程对象,然后将远程对象的响应返回给客户端。例如,在一个企业级应用中,客户端可能需要访问位于远程服务器上的数据库对象,通过远程代理可以隐藏网络通信的细节,使得客户端可以像访问本地对象一样访问远程数据库对象。
  • 虚拟代理(Virtual Proxy)
    当创建一个对象的开销较大时,可以使用虚拟代理。虚拟代理先创建一个占位符对象,当真正需要使用对象时才创建实际对象。比如在一个图片浏览应用中,如果图片很大,一次性加载所有图片会消耗大量内存和时间。可以使用虚拟代理,先显示一个占位图片,当用户点击图片时再真正加载高清图片,提高应用的性能和用户体验。
  • 保护代理(Protection Proxy)
    用于控制对真实对象的访问权限。例如,在一个企业内部系统中,有一些敏感数据的访问需要特定权限。保护代理可以在代理对象中进行权限验证,只有具有相应权限的用户才能访问真实对象的相关操作,增强系统的安全性。

优势

  • 增强系统安全性
    通过保护代理,可以在代理对象中添加权限验证等安全机制,防止非法访问真实对象,确保系统的安全性和数据的保密性。例如,在企业资源规划(ERP)系统中,只有具有特定权限的管理员才能访问和修改关键业务数据,普通员工只能通过代理对象进行有限的查询操作。
  • 提高系统性能
    利用虚拟代理,能够延迟对象的创建,避免在不必要的时候创建复杂或资源消耗大的对象,从而节省系统资源,提高性能。如在大型游戏中,场景中的一些复杂模型可能只有在玩家靠近时才需要详细渲染,使用虚拟代理可以在玩家距离较远时仅用简单的占位模型代替,减少计算资源的占用,提高游戏的帧率和流畅度。
  • 实现解耦与灵活性
    代理模式将客户端与真实对象解耦,客户端只与代理对象交互,而代理对象负责管理真实对象的生命周期和访问控制。这样,当真实对象的实现发生变化时,只要代理对象的接口保持不变,客户端代码无需修改,提高了系统的灵活性和可维护性。例如,在一个电商系统中,如果商品库存管理模块的内部实现发生改变,只要库存查询代理对象的接口不变,与该代理对象交互的订单处理模块等其他部分就不需要调整。

模式对比

策略模式与代理模式在结构上虽有相似之处(都涉及封装与委托),但在设计意图与应用场景上存在显著差异。

对比维度策略模式 (Strategy)代理模式 (Proxy)
目的实现算法的可替换性。将不同的算法封装在独立的类中,使得算法的变化不会影响到使用算法的客户端代码。侧重于算法的多样化和切换。控制对真实对象的访问。通过代理对象来提供额外的功能(如权限控制、性能优化等),同时隐藏真实对象的实现细节。侧重于对对象访问的控制和管理。
结构环境类持有策略类的引用,并通过调用策略类的接口来执行具体算法。策略类之间相互独立,没有直接的关联关系。代理类和真实类都实现了相同的抽象主题接口。代理类内部持有真实类的引用,并在适当的时候调用真实类的方法,代理类与真实类之间存在明确的代理关系。
功能重点提供多种算法的选择和切换,以满足不同情况下的业务需求。关注的是算法的实现和选择逻辑。控制对象的访问过程,可能在访问真实对象之前或之后进行一些预处理或后处理操作(如权限检查、资源加载等)。关注的是对象访问的控制和增强。
典型示例排序算法切换(冒泡、快速、归并)。远程调用代理、图片懒加载代理、权限控制代理。

总结

策略模式和代理模式都是非常有用的设计模式,它们在不同的场景下发挥着重要作用。

  • 策略模式通过算法的封装和切换,提高了系统的灵活性和可扩展性,使得系统能够适应不同的业务规则和需求变化。
  • 代理模式则通过对对象访问的控制和代理,增强了系统的安全性、性能和可维护性,为系统提供了更好的管理和保护机制。

在实际的软件开发中,根据具体的业务需求和场景特点,合理选择和运用这两种设计模式,可以有效地提高软件系统的质量和开发效率,构建出更加健壮和灵活的软件架构。