常见设计模式解读(二)

文章目录

    • 4.策略模式(Strategy Pattern)
      • 4-1.定义
      • 4-2.实现示例
      • 4-3.应用场景
    • 5.代理模式(Proxy Pattern)
      • 5-1.定义
      • 5-2.实现示例
      • 5-3.应用场景
      • 5-4.注意事项
    • 6.装饰器模式(Decorator Pattern)
      • 6-1.定义
      • 6-2.实现示例
      • 6-3.应用场景
    • 7.适配器模式(Adapter Pattern)
      • 7-1.定义
      • 7-2.实现示例

4.策略模式(Strategy Pattern)

4-1.定义

策略模式(Strategy Pattern) 是一种行为设计模式,它允许在运行时选择算法或行为。通过将算法封装在独立的类中,使得它们可以相互替换。策略模式让算法的变化独立于使用它的客户端。

意图:定义一系列算法,把它们一个个封装起来,并且使它们可以互相替换。该模式使得算法可独立于使用它的客户而变化。

主要角色:

  • Strategy(策略接口):定义所有支持算法的公共操作接口。Context使用这个接口来调用ConcreteStrategy定义的算法。
  • ConcreteStrategy(具体策略):实现Strategy接口的具体算法或行为。
    Context(上下文):持有一个对Strategy对象的引用,并且可以通过构造函数或者设置方法来接受一个具体的策略对象。Context不负责选择具体的策略,而是由外部决定。

应用场景

4-2.实现示例

下面是一个简单的Java示例,展示如何使用策略模式来实现不同类型的支付方法。

// 1. 定义策略接口 PaymentStrategy
// Strategy接口
public interface PaymentStrategy {
    void pay(int amount);
}

// 2. 实现具体策略类
// ConcreteStrategy A: 使用信用卡支付
public class CreditCardPayment implements PaymentStrategy {
    private String name;
    private String cardNumber;
    private String cvv;

    public CreditCardPayment(String name, String cardNumber, String cvv) {
        this.name = name;
        this.cardNumber = cardNumber;
        this.cvv = cvv;
    }

    @Override
    public void pay(int amount) {
        System.out.println(amount + " paid using credit/debit card.");
    }
}

// ConcreteStrategy B: 使用PayPal支付
public class PayPalPayment implements PaymentStrategy {
    private String emailId;
    private String password;

    public PayPalPayment(String emailId, String password) {
        this.emailId = emailId;
        this.password = password;
    }

    @Override
    public void pay(int amount) {
        System.out.println(amount + " paid using PayPal.");
    }
}
// 3. 定义上下文类 ShoppingCart
// Context类
public class ShoppingCart {
    private PaymentStrategy paymentStrategy;

    // 设置支付策略
    public void setPaymentStrategy(PaymentStrategy paymentStrategy) {
        this.paymentStrategy = paymentStrategy;
    }

    // 根据设置的策略进行支付
    public void checkout(int amount) {
        if (paymentStrategy == null) {
            throw new IllegalStateException("Payment strategy not set");
        }
        paymentStrategy.pay(amount);
    }
}

使用不同手段支付:

public class Main {
    public static void main(String[] args) {
        ShoppingCart cart = new ShoppingCart();

        // 使用信用卡支付
        PaymentStrategy creditCardPayment = new CreditCardPayment("John Doe", "1234567890123456", "123");
        cart.setPaymentStrategy(creditCardPayment);
        cart.checkout(100);

        // 使用PayPal支付
        PaymentStrategy paypalPayment = new PayPalPayment("[email protected]", "password");
        cart.setPaymentStrategy(paypalPayment);
        cart.checkout(50);
    }
}

4-3.应用场景

支付系统:不同的支付方式(如信用卡、PayPal、Apple Pay等),每种支付方式有不同的处理逻辑。
压缩算法:不同的文件压缩算法(如ZIP、RAR、7z等),用户可以选择不同的压缩方式。
排序算法:不同的排序算法(如快速排序、归并排序、堆排序等),根据数据量和特性选择合适的算法。

5.代理模式(Proxy Pattern)

5-1.定义

代理模式(Proxy Pattern) 是一种结构型设计模式,它为其他对象提供一个代理以控制对这个对象的访问。代理模式可以在不改变原始对象的前提下增加额外的功能,如延迟初始化、访问控制、日志记录等。

意图:为其他对象提供一种代理以控制对这个对象的访问。
主要角色:
Subject(抽象主题):定义了RealSubject和Proxy的公共接口,这样就可以在任何使用RealSubject的地方使用Proxy。
RealSubject(真实主题):定义了Proxy所代表的真实对象,是最终要引用的对象。
Proxy(代理):持有对RealSubject的引用,并且在调用RealSubject的方法之前或之后可以进行一些额外的操作,比如权限检查、延迟加载等。

5-2.实现示例

// 抽象主题
// 1. 定义抽象主题接口 Image
public interface Image {
    void display();
}

// 2. 实现具体主题类 RealImage
// 具体主题:真实的图片类
public class RealImage implements Image {
    private String fileName;

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

    @Override
    public void display() {
        System.out.println("Displaying " + fileName);
    }

    private void loadFromDisk(String fileName) {
        System.out.println("Loading " + fileName);
    }
}

// 代理:虚拟代理类
// 3. 实现代理类 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();
    }
}

// 4. 客户端代码
public class Main {
    public static void main(String[] args) {
        // 使用代理类而不是直接使用RealImage
        Image image = new ProxyImage("test_10mb.jpg");

        // 图像将不会立即加载
        image.display(); // 第一次调用会加载并显示图像
        System.out.println("");

        // 图像已经加载,因此不会再加载
        image.display(); // 直接显示图像
    }
}

在这个例子中:

Image 接口定义了 display() 方法,这是所有图像类(包括代理和真实图像)都需要实现的接口。
RealImage 类实现了 Image 接口,并负责实际加载和显示图像。每次实例化 RealImage 时,都会从磁盘加载图像。
ProxyImage 类也实现了 Image 接口,但它并不直接处理图像加载。相反,它在第一次调用 display() 方法时才创建 RealImage 实例,并委托给它来完成实际的工作。这实现了延迟加载的效果,即只有在真正需要显示图像时才会加载图像。

5-3.应用场景

代理模式适用于以下情况:

远程代理:为位于不同地址空间的对象提供本地代表。例如,RMI(Remote Method Invocation)中的Stub和Skeleton。
虚拟代理:根据需要创建开销很大的对象。例如,图像加载器在实际加载大图之前先显示一个小的占位符图像。
保护代理:基于调用者身份控制对原始对象的访问。例如,只有管理员才能访问某些敏感数据。
智能引用:当调用对象时执行一些额外操作,如计算对象被引用的次数、在对象销毁前进行资源清理等。

5-4.注意事项

Spring AOP(Aspect-Oriented Programming,面向切面编程) 在实现时确实大量使用了代理模式。Spring AOP 通过动态代理机制来实现横切关注点(如日志记录、事务管理、安全性等)与业务逻辑的分离。

在 Spring 中,AOP 主要通过以下两种方式实现:

  1. JDK 动态代理:如果目标对象实现了接口,Spring 默认会使用 JDK 提供的 java.lang.reflect.Proxy 类来创建代理对象。JDK 动态代理只能代理接口,不能直接代理类。
  2. CGLIB 代理:如果目标对象没有实现任何接口,Spring 会使用 CGLIB 库来生成目标类的子类,并覆盖其方法以实现代理功能。这种方式可以代理具体的类,而不仅仅是接口。

Spring AOP 的工作流程如下:

  1. 定义切面(Aspect):切面包含了通知(Advice,如前置通知、后置通知等)和切入点(Pointcut),用于指定在哪些连接点(Join Point,如方法调用)上应用这些通知。
  2. 创建代理对象:Spring 容器根据配置决定使用哪种代理方式(JDK 动态代理或 CGLIB 代理)来创建代理对象。
  3. 织入通知:当代理对象的方法被调用时,Spring AOP 会在适当的位置(如方法执行前、后等)插入相应的通知逻辑。

下面是一个简单的示例,展示了如何在 Spring 中使用 AOP 来实现日志记录功能。这个例子中,我们将使用 JDK 动态代理。

// 1. 定义业务接口和服务类
public interface Service {
    void execute();
}

// 具体服务类
public class ServiceImpl implements Service {
    @Override
    public void execute() {
        System.out.println("Executing business logic.");
    }
}
// 2. 定义切面类
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class LoggingAspect {

    @Before("execution(* com.example.Service.execute(..))")
    public void logBefore() {
        System.out.println("Logging before method execution.");
    }
}

在 Spring Boot 项目中,通常只需要在主类上添加 @EnableAspectJAutoProxy 注解即可启用 AOP 支持。

// 3. 配置AOP
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@SpringBootApplication
@EnableAspectJAutoProxy
public class AopDemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(AopDemoApplication.class, args);
    }
}
//  4. 使用上下文运行应用程序
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Main {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(AopDemoApplication.class);
        Service service = context.getBean(ServiceImpl.class);
        service.execute();
    }
}

假设运行上述代码,输出结果将会是:

Logging before method execution.
Executing business logic.
  • Service 接口和实现类:这是我们要代理的目标对象。
  • LoggingAspect 切面类:包含了一个前置通知 logBefore(),它将在 Service.execute() 方法执行之前被调用。
  • AopDemoApplication 主类:启用了 AOP 自动代理支持。
  • Main 类:从 Spring 上下文中获取代理后的 Service 实例并调用其 execute() 方法。

Spring AOP 借助代理模式实现了对业务逻辑的增强。具体来说:

  • JDK 动态代理:适用于实现了接口的目标对象,通过反射机制生成代理对象并在代理对象上调用目标对象的方法。
  • CGLIB 代理:适用于未实现接口的目标对象,通过生成目标对象的子类并在子类中覆盖目标对象的方法来实现代理。

无论是哪种代理方式,Spring AOP 都能够有效地将横切关注点(如日志、事务管理等)与核心业务逻辑分离开来,从而提高代码的模块化程度和可维护性。

6.装饰器模式(Decorator Pattern)

6-1.定义

装饰器模式(Decorator Pattern) 是一种结构型设计模式,它允许在不改变对象类的前提下动态地给对象添加功能。通过将每个功能封装在一个独立的类中,并使用组合的方式将这些功能附加到对象上,从而实现灵活的功能扩展。

主要角色:
Component(组件接口):定义了被装饰对象和装饰器的公共接口。
ConcreteComponent(具体组件):实现了Component接口,是被装饰的对象。
Decorator(抽象装饰器):持有一个对Component对象的引用,并且实现了Component接口。这样可以确保所有装饰器都能与原始组件兼容。
ConcreteDecorator(具体装饰器):实现了Decorator接口,并为组件添加额外的行为或功能。

6-2.实现示例

下面是一个简单的Java示例,展示如何使用装饰器模式来实现咖啡店中的饮料系统。每种饮料都可以添加不同的调料(如糖浆、奶泡等),并且价格会根据添加的调料而变化。

  1. 定义组件接口 Beverage
// 组件接口
public abstract class Beverage {
    public String description = "Unknown Beverage";

    public String getDescription() {
        return description;
    }

    public abstract double cost();
}
  1. 实现具体组件类 Espresso 和 HouseBlend
// 具体组件:浓缩咖啡
public class Espresso extends Beverage {
    public Espresso() {
        description = "Espresso";
    }

    @Override
    public double cost() {
        return 1.99;
    }
}

// 具体组件:家庭混合咖啡
public class HouseBlend extends Beverage {
    public HouseBlend() {
        description = "House Blend Coffee";
    }

    @Override
    public double cost() {
        return 0.89;
    }
}
  1. 定义抽象装饰器类 CondimentDecorator
// 抽象装饰器:调料装饰器
public abstract class CondimentDecorator extends Beverage {
    public abstract String getDescription();
}
  1. 实现具体装饰器类 Mocha 和 Whip
// 具体装饰器:摩卡调料
public class Mocha extends CondimentDecorator {
    private Beverage beverage;

    public Mocha(Beverage beverage) {
        this.beverage = beverage;
    }

    @Override
    public String getDescription() {
        return beverage.getDescription() + ", Mocha";
    }

    @Override
    public double cost() {
        return 0.20 + beverage.cost();
    }
}

// 具体装饰器:奶泡调料
public class Whip extends CondimentDecorator {
    private Beverage beverage;

    public Whip(Beverage beverage) {
        this.beverage = beverage;
    }

    @Override
    public String getDescription() {
        return beverage.getDescription() + ", Whip";
    }

    @Override
    public double cost() {
        return 0.10 + beverage.cost();
    }
}
  1. 客户端代码
深色版本
public class Main {
    public static void main(String[] args) {
        Beverage beverage = new Espresso();
        System.out.println(beverage.getDescription() + " $" + beverage.cost());

        Beverage beverage2 = new HouseBlend();
        beverage2 = new Mocha(beverage2);
        beverage2 = new Mocha(beverage2);
        beverage2 = new Whip(beverage2);
        System.out.println(beverage2.getDescription() + " $" + beverage2.cost());
    }
}

输出结果
假设运行上述代码,输出结果将会是:

Espresso $1.99
House Blend Coffee, Mocha, Mocha, Whip $1.29

解释
Beverage 接口定义了饮料的基本行为,包括获取描述和计算价格。
Espresso 和 HouseBlend 类实现了 Beverage 接口,代表具体的饮料类型。
CondimentDecorator 抽象类实现了 Beverage 接口,并持有一个对 Beverage 对象的引用,以便它可以装饰任何类型的饮料。
Mocha 和 Whip 类是具体的装饰器,它们在原有饮料的基础上增加了新的调料,并相应地调整了价格。
在客户端代码中,我们可以通过组合的方式来动态地给饮料添加调料,而不需要修改现有的饮料类。

6-3.应用场景

应用场景
装饰器模式适用于以下情况:

需要在运行时动态地给对象添加功能,而不是通过继承来实现。
当需要避免创建过多的子类来实现不同功能组合时。
希望保持代码的简洁性和可维护性,同时允许灵活地扩展功能。
例如:

图形用户界面(GUI):如在按钮上添加边框、背景颜色等属性。
输入输出流处理:如Java I/O库中的BufferedInputStream和DataInputStream等,它们都是基于装饰器模式实现的。
日志记录:在方法调用前后添加日志记录功能。

7.适配器模式(Adapter Pattern)

7-1.定义

适配器模式(Adapter Pattern) 是一种结构型设计模式,它允许原本由于接口不兼容而无法一起工作的类能够协同工作。适配器模式通过将一个类的接口转换成客户端所期望的另一个接口来实现这一目标。

Target(目标接口):定义了客户端使用的特定领域接口。
Adaptee(被适配者):包含现有接口的类,但其接口与客户端所需的接口不兼容。
Adapter(适配器):负责将 Adaptee 的接口转换为 Target 接口,从而使客户端可以使用 Adaptee 类的功能。

7-2.实现示例

下面是一个简单的Java示例,展示如何使用适配器模式来集成两个不兼容的接口。假设我们有两个类 MediaPlayer 和 AdvancedMediaPlayer,其中 MediaPlayer 只支持播放MP3文件,而 AdvancedMediaPlayer 支持播放VLC和MP4文件。我们将使用适配器模式来让 MediaPlayer 能够播放VLC和MP4文件。

  1. 定义目标接口 MediaPlayer
// 目标接口
public interface MediaPlayer {
    void play(String audioType, String fileName);
}
  1. 定义具体类 AudioPlayer
// 具体类实现了目标接口
public class AudioPlayer implements MediaPlayer {
    MediaAdapter mediaAdapter;

    @Override
    public void play(String audioType, String fileName) {
        if (audioType.equalsIgnoreCase("mp3")) {
            System.out.println("Playing mp3 file. Name: " + fileName);
        } else if (audioType.equalsIgnoreCase("vlc") || audioType.equalsIgnoreCase("mp4")) {
            // 如果是VLC或MP4,则使用适配器
            if (mediaAdapter == null) {
                mediaAdapter = new MediaAdapter(audioType);
            }
            mediaAdapter.play(audioType, fileName);
        } else {
            System.out.println("Invalid media. " + audioType + " format not supported");
        }
    }
}
  1. 定义被适配者接口 AdvancedMediaPlayer
// 被适配者的接口
public interface AdvancedMediaPlayer {
    void playVlc(String fileName);
    void playMp4(String fileName);
}
  1. 实现具体的被适配者类 VlcPlayer 和 Mp4Player
// 具体的被适配者类
public class VlcPlayer implements AdvancedMediaPlayer {
    @Override
    public void playVlc(String fileName) {
        System.out.println("Playing vlc file. Name: " + fileName);
    }

    @Override
    public void playMp4(String fileName) {
        // 不支持MP4播放
    }
}

public class Mp4Player implements AdvancedMediaPlayer {
    @Override
    public void playVlc(String fileName) {
        // 不支持VLC播放
    }

    @Override
    public void playMp4(String fileName) {
        System.out.println("Playing mp4 file. Name: " + fileName);
    }
}
  1. 实现适配器类 MediaAdapter
// 适配器类实现了目标接口,并持有一个对被适配者的引用
public class MediaAdapter implements MediaPlayer {
    private AdvancedMediaPlayer advancedMusicPlayer;

    public MediaAdapter(String audioType) {
        if (audioType.equalsIgnoreCase("vlc")) {
            advancedMusicPlayer = new VlcPlayer();
        } else if (audioType.equalsIgnoreCase("mp4")) {
            advancedMusicPlayer = new Mp4Player();
        }
    }

    @Override
    public void play(String audioType, String fileName) {
        if (audioType.equalsIgnoreCase("vlc")) {
            advancedMusicPlayer.playVlc(fileName);
        } else if (audioType.equalsIgnoreCase("mp4")) {
            advancedMusicPlayer.playMp4(fileName);
        }
    }
}
  1. 客户端代码
public class Main {
    public static void main(String[] args) {
        AudioPlayer audioPlayer = new AudioPlayer();

        audioPlayer.play("mp3", "beyond the horizon.mp3");
        audioPlayer.play("mp4", "alone.mp4");
        audioPlayer.play("vlc", "far far away.vlc");
        audioPlayer.play("avi", "mind me.avi");
    }
}
``
输出结果
假设运行上述代码,输出结果将会是:

Playing mp3 file. Name: beyond the horizon.mp3
Playing mp4 file. Name: alone.mp4
Playing vlc file. Name: far far away.vlc
Invalid media. avi format not supported

解释
MediaPlayer 接口定义了客户端期望的播放音频的方法。
AudioPlayer 类实现了 MediaPlayer 接口,并且可以播放MP3文件。对于VLC和MP4文件,它会使用 MediaAdapter 来处理。
AdvancedMediaPlayer 接口定义了高级播放器的功能,包括播放VLC和MP4文件。
VlcPlayer 和 Mp4Player 类分别实现了 AdvancedMediaPlayer 接口,并提供了相应的播放功能。
MediaAdapter 类实现了 MediaPlayer 接口,并持有对 AdvancedMediaPlayer 的引用。它根据传入的音频类型选择合适的播放器。
在客户端代码中,我们创建了一个 AudioPlayer 实例,并调用了它的 play() 方法来播放不同类型的音频文件。
总结
适配器模式提供了一种灵活的方式来整合不同接口的类,使其能够协同工作。通过引入适配器类,我们可以避免直接修改现有类的接口,从而保持代码的稳定性和可维护性。这种模式特别适用于需要集成第三方库或旧系统的场景,在这些情况下,接口的不兼容性是一个常见的问题。

适配器模式的核心思想是“转换接口”,即通过适配器类将一个接口转换为另一个接口,从而使原本不兼容的类能够一起工作。这种设计模式不仅提高了代码的灵活性,还增强了系统的扩展能力。
### 7-3.应用场景
适配器模式适用于以下情况:

当你希望重用现有的类,但其接口与你的需求不匹配时。
当你需要创建一个可复用的类,该类能够与其他不相关的或不可预见的类协同工作时。
当你需要使用一些已经存在的子类,但不可能对每一个子类都进行子类化以匹配它们的接口时。
例如:

硬件适配器:如电源适配器将不同电压和插头类型的电源转换为设备所需的格式。
软件系统集成:在不同的API或库之间进行桥接,使它们可以无缝地协同工作。
第三方库集成:当你需要使用第三方库中的某个类,但该类的接口不符合你的应用程序的需求时。


你可能感兴趣的:(计算机面经,设计模式,开发语言,java,spring,boot)