第三章 设计模式(2023版本IDEA)

学习目标

  • [3.1 设计模式概述](#3.1 设计模式概述)
  • [3.2 软件可复用问题和面向对象设计原则](#3.2 软件可复用问题和面向对象设计原则)
      • 一、软件可复用问题
      • 二、面向对象设计原则
        • [1. 单一责任原则(Single Responsibility Principle, SRP)](#1. 单一责任原则(Single Responsibility Principle, SRP))
        • [2. 开放-封闭原则(Open-Closed Principle, OCP)](#2. 开放-封闭原则(Open-Closed Principle, OCP))
        • [3. 里氏替换原则(Liskov Substitution Principle, LSP)](#3. 里氏替换原则(Liskov Substitution Principle, LSP))
        • [4. 依赖倒置原则(Dependency Inversion Principle, DIP)](#4. 依赖倒置原则(Dependency Inversion Principle, DIP))
        • [5. 接口隔离原则(Interface Segregation Principle, ISP)](#5. 接口隔离原则(Interface Segregation Principle, ISP))
        • [6. 迪米特法则(Least Knowledge Principle,LKP)](#6. 迪米特法则(Least Knowledge Principle,LKP))
        • [7. 合成/聚合复用原则(Composite/Aggregate Reuse Principle, CARP)](#7. 合成/聚合复用原则(Composite/Aggregate Reuse Principle, CARP))
  • [3.3 设计模式的应用](#3.3 设计模式的应用)
    • [3.3.1 工厂方法模式](#3.3.1 工厂方法模式)
        • [1. 简单工厂模式(Simple Factory Pattern)](#1. 简单工厂模式(Simple Factory Pattern))
        • [2. 工厂方法模式(Factory Method Pattern)](#2. 工厂方法模式(Factory Method Pattern))
        • [3.抽象工厂模式(Abstract Factory Pattern)](#3.抽象工厂模式(Abstract Factory Pattern))
    • [3.3.2 代理模式](#3.3.2 代理模式)

前面课程中已经学习了面向对象的三大特征,在后续的学习过程中对面向对象的认识会不断深入,不断提高运用面向对象思想解决问题的能力。(如果没有了解可以去我主页看看Java开发之框架基础技术第1-2章(2023版本IEDA)来学习)本章学习面向对象的一些高级应用一一设计模式。设计模式被广泛运用在java框架技术中,学习设计模式对于理解框架的工作原理会有所帮助。

学习方法

设计模式虽有很多种,但总是可以从解锅台、提高复用性这些方向来理解。首先要明确每种设计模式的使用场景,明确其要解决的问题,进而理解其解决该问题的思路。

3.1 设计模式概述

设计模式(Design Pattern)是人们在长期的软件开发中对一些经验的总结,是对某些特定问题经过实践检验的特定解决方法。就像兵法中的三十六计,总结了36种对于战争中某些场合的可行性计谋战术一一"围魏救赵""声东击西""走为上"等,可以说三十六计中的每一计都是一种模式。

  1. 创建型模式(Creational Patterns)
    创建型模式主要用于对象的创建,它们通过隐藏对象的创建逻辑来提供更大的灵活性。

示例:单例模式(Singleton Pattern)

单例模式确保一个类仅有一个实例,并提供一个全局访问点。

java 复制代码
public class Singleton {  
    // 私有静态变量,保存类的唯一实例  
    private static Singleton instance;  
  
    // 私有构造函数,防止外部通过new创建实例  
    private Singleton() {}  
  
    // 提供一个全局的静态方法,返回唯一实例  
    public static Singleton getInstance() {  
        if (instance == null) {  
            instance = new Singleton();  
        }  
        return instance;  
    }  
  
    // 其他方法...  
}
  1. 结构型模式(Structural Patterns)
    结构型模式关注于类、接口和对象之间的组合关系,以创建更大的结构。

示例:适配器模式(Adapter Pattern)

适配器模式将一个类的接口转换成客户端期望的另一个接口形式,使类之间的接口不兼容问题可以通过一个中间类来解决。

java 复制代码
// 目标接口  
public interface Target {  
    void request();  
}  
  
// 需要适配的类  
public class Adaptee {  
    public void specificRequest() {  
        // 具体请求  
    }  
}  
  
// 适配器类  
public class Adapter implements Target {  
    private Adaptee adaptee;  
  
    public Adapter(Adaptee adaptee) {  
        this.adaptee = adaptee;  
    }  
  
    @Override  
    public void request() {  
        adaptee.specificRequest();  
    }  
}
  1. 行为型模式(Behavioral Patterns)
    行为型模式主要关注对象之间的通信和交互方式。

示例:观察者模式(Observer Pattern)

观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态发生变化时,会通知所有观察者对象,使它们能够自动更新自己。

java 复制代码
// 抽象主题类  
public abstract class Subject {  
    // 维护一个观察者列表  
    private List<Observer> observers = new ArrayList<>();  
  
    // 注册观察者  
    public void registerObserver(Observer o) {  
        observers.add(o);  
    }  
  
    // 移除观察者  
    public void removeObserver(Observer o) {  
        observers.remove(o);  
    }  
  
    // 通知所有观察者  
    protected void notifyObservers() {  
        for (Observer observer : observers) {  
            observer.update(this);  
        }  
    }  
  
    // 抽象的通知方法  
    public abstract void stateChanged();  
}  
  
// 抽象观察者类  
public interface Observer {  
    void update(Subject subject);  
}  
  
// 具体实现  
// ...(具体主题类和观察者类的实现)

注意

上述代码示例仅用于说明设计模式的基本思想和结构,并未包含完整的错误处理和优化逻辑。在实际应用中,您可能需要根据具体需求进行调整和完善。

设计模式的学习和应用需要结合具体的项目实践,通过不断尝试和反思来加深对设计模式的理解和应用能力。

3.2 软件可复用问题和面向对象设计原则

软件可复用问题和面向对象设计原则是两个紧密相连的概念。在软件开发中,可复用性是一个重要的目标,它旨在通过重用已有的软件组件来降低开发成本、提高开发效率和软件质量。面向对象设计原则则为实现软件的可复用性提供了指导和支持。

一、软件可复用问题

软件复用(Software Reuse)是使用已有的软件组件去实现或更新软件系统的过程。复用可以降低开发成本、缩短开发周期、提高软件质量,并促进软件标准化。然而,要实现软件的有效复用,需要解决以下几个关键问题:

  1. 组件的明确定义和标准化:可复用的组件需要具有明确的定义和标准化的接口,以便在不同的系统中被重用。
  2. 组件的独立性:组件之间应该尽可能少地相互依赖,以提高其独立性和可移植性。
  3. 组件的文档化和可理解性:良好的文档和易于理解的设计是复用组件的前提。
  4. 组件的测试和验证:复用前需要对组件进行充分的测试和验证,以确保其稳定性和可靠性。

二、面向对象设计原则

面向对象设计原则为软件的可复用性提供了指导和支持。以下是一些关键的面向对象设计原则:

1. 单一责任原则(Single Responsibility Principle, SRP)

一个类应该仅有一个引起它变化的原因。这有助于保持类的简洁和可维护性,从而提高其可复用性。

java 复制代码
// 示例:一个类只负责一个功能  
public class UserService {  
    // 负责用户注册的逻辑  
    public void registerUser(User user) {  
        // 注册逻辑...  
    }  
  
    // 如果有其他与用户相关的功能,应该放在其他类中  
}
2. 开放-封闭原则(Open-Closed Principle, OCP)

软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。这意味着在添加新功能时,应该通过扩展现有系统来实现,而不是修改现有代码。这有助于保持系统的稳定性和可复用性。

java 复制代码
// 示例:通过策略模式实现OCP  
interface PaymentStrategy {  
    void pay(double amount);  
}  
  
class CreditCardPayment implements PaymentStrategy {  
    public void pay(double amount) {  
        // 信用卡支付逻辑...  
    }  
}  
  
class CashPayment implements PaymentStrategy {  
    public void pay(double amount) {  
        // 现金支付逻辑...  
    }  
}  
  
class PaymentProcessor {  
    private PaymentStrategy strategy;  
  
    public PaymentProcessor(PaymentStrategy strategy) {  
        this.strategy = strategy;  
    }  
  
    public void processPayment(double amount) {  
        strategy.pay(amount);  
    }  
}  
  
// 客户端代码  
PaymentProcessor processor = new PaymentProcessor(new CreditCardPayment());  
processor.processPayment(100.0);
3. 里氏替换原则(Liskov Substitution Principle, LSP)

子类型必须能够替换掉它们的基类型。这要求子类在继承基类时,必须保持与基类相同的行为特性,以确保在父类出现的地方可以使用子类来替换。

java 复制代码
// 父类  
class Rectangle {  
    protected double width;  
    protected double height;  
  
    public Rectangle(double width, double height) {  
        this.width = width;  
        this.height = height;  
    }  
  
    // 计算面积  
    public double area() {  
        return width * height;  
    }  
}  
  
// 子类,尝试遵循里氏替换原则  
class Square extends Rectangle {  
    // 由于Square的特殊性,这里不能直接使用父类的构造器  
    // 因为Square的宽和高必须相等  
    public Square(double side) {  
        super(side, side); // 调用父类构造器,强制宽和高相等  
    }  
  
    // 尝试修改面积方法(但这样做可能违反里氏替换原则)  
    // @Override  
    // public double area() {  
    //     return width * width; // 直接使用width的平方,但这会破坏里氏替换原则  
    // }  
  
    // 为了保持里氏替换原则,我们不覆盖area方法  
    // 而是添加一个新的方法来计算正方形的特定属性(比如周长)  
    public double perimeter() {  
        return 4 * width; // 因为是正方形,所以周长是4倍的边长  
    }  
}  
  
// 客户端代码  
public class LiskovSubstitutionDemo {  
    public static void main(String[] args) {  
        Rectangle rect = new Rectangle(4, 5);  
        System.out.println("Rectangle area: " + rect.area()); // 输出矩形的面积  
  
        // 尝试用Square替换Rectangle  
        Rectangle square = new Square(5); // 这里Square被当作Rectangle使用,符合里氏替换原则  
        System.out.println("Square area (as Rectangle): " + square.area()); // 输出正方形的面积,通过Rectangle接口  
  
        // 如果需要调用Square特有的方法,需要进行类型转换  
        if (square instanceof Square) {  
            Square s = (Square) square;  
            System.out.println("Square perimeter: " + s.perimeter()); // 输出正方形的周长  
        }  
    }  
}
4. 依赖倒置原则(Dependency Inversion Principle, DIP)

高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象。这有助于减少模块之间的耦合度,提高系统的灵活性和可复用性。

java 复制代码
// 定义一个日志记录的接口  
interface Logger {  
    void log(String message);  
}  
  
// 实现日志记录接口的具体类  
class FileLogger implements Logger {  
    @Override  
    public void log(String message) {  
        System.out.println("Logging to file: " + message);  
    }  
}  
  
// 另一个实现日志记录接口的具体类  
class ConsoleLogger implements Logger {  
    @Override  
    public void log(String message) {  
        System.out.println("Logging to console: " + message);  
    }  
}  
  
// 定义一个依赖日志记录的类,这里依赖的是日志的抽象(接口)  
class Application {  
    private Logger logger;  
  
    // 通过构造器注入依赖的日志实现  
    public Application(Logger logger) {  
        this.logger = logger;  
    }  
  
    public void execute() {  
        // 使用日志记录功能,但不关心具体的实现  
        logger.log("Application is starting...");  
        // 其他业务逻辑...  
        logger.log("Application is ending...");  
    }  
}  
  
// 客户端代码  
public class DependencyInversionDemo {  
    public static void main(String[] args) {  
        // 可以在运行时根据需要选择日志记录的实现  
        Application appWithFileLogger = new Application(new FileLogger());  
        Application appWithConsoleLogger = new Application(new ConsoleLogger());  
  
        appWithFileLogger.execute();  
        appWithConsoleLogger.execute();  
    }  
}
5. 接口隔离原则(Interface Segregation Principle, ISP)

不应该强迫客户依赖于它们不使用的方法。这要求将大的接口拆分成更小的、更具体的接口,以便客户只需要知道它们感兴趣的方法。

java 复制代码
// 定义一个细粒度的接口,只包含打印功能  
interface Printable {  
    void print();  
}  
  
// 另一个细粒度的接口,只包含扫描功能  
interface Scannable {  
    void scan();  
}  
  
// 假设我们有一个多功能设备,它同时支持打印和扫描  
class MultiFunctionDevice implements Printable, Scannable {  
    @Override  
    public void print() {  
        System.out.println("Printing document...");  
    }  
  
    @Override  
    public void scan() {  
        System.out.println("Scanning document...");  
    }  
}  
  
// 一个只需要打印功能的类  
class Printer {  
    private Printable printable;  
  
    public Printer(Printable printable) {  
        this.printable = printable;  
    }  
  
    public void performPrintTask() {  
        printable.print();  
    }  
}  
  
// 一个只需要扫描功能的类  
class Scanner {  
    private Scannable scannable;  
  
    public Scanner(Scannable scannable) {  
        this.scannable = scannable;  
    }  
  
    public void performScanTask() {  
        scannable.scan();  
    }  
}  
  
// 客户端代码  
public class InterfaceSegregationDemo {  
    public static void main(String[] args) {  
        // 创建一个多功能设备实例  
        MultiFunctionDevice mfd = new MultiFunctionDevice();  
  
        // 创建一个Printer实例,只依赖打印功能  
        Printer printer = new Printer(mfd);  
        printer.performPrintTask();  
  
        // 创建一个Scanner实例,只依赖扫描功能  
        Scanner scanner = new Scanner(mfd);  
        scanner.performScanTask();  
    }  
}
6. 迪米特法则(Least Knowledge Principle,LKP)

迪米特法则又称为最少知道原则,是指一个软件实体应当尽可能少地与其他实体发生相互作用。

java 复制代码
// 示例:使用中介者模式减少类之间的直接通信  
interface Mediator {  
    void send(String message, Colleague colleague);  
}  
  
interface Colleague {  
    void receive(String message);  
}  
  
class ConcreteMediator implements Mediator {  
    private List<Colleague> colleagues = new ArrayList<>();  
  
    public void register(Colleague colleague) {  
        colleagues.add(colleague);  
    }  
  
    @Override  
    public void send(String message, Colleague colleague) {  
        for (Colleague c : colleagues) {  
            if (!c.equals(colleague)) {  
                c.receive(message);  
            }  
        }  
    }  
}  
  
class ConcreteColleague implements Colleague {  
    private Mediator mediator;  
  
    public ConcreteColleague(Mediator mediator) {  
        this.mediator = mediator;  
        mediator.register(this);  
    }  
  
    @Override  
    public void receive(String message) {  
        // 处理接收到的消息  
        System.out.println("Received: " + message);  
    }  
  
    public void send(String message) {  
        mediator.send(message, this);  
    }  
}  
  
// 客户端代码  
Mediator mediator = new ConcreteMediator();  
Colleague c1 = new ConcreteColleague(mediator);  
Colleague c2 = new ConcreteColleague(mediator);  
c1.send("Hello, World!"); // c2 会接收到这个消息,但c1不知道c2的存在
在这个例子中,ConcreteColleague 类通过 Mediator 类来与其他 Colleague 通信,而不是直接
7. 合成/聚合复用原则(Composite/Aggregate Reuse Principle, CARP)

尽量使用合成/聚合的方式来实现复用,而不是使用继承。这有助于保持类的独立性和灵活性,从而提高其可复用性。

java 复制代码
// 定义一个车辆接口  
interface Vehicle {  
    void move();  
}  
  
// 定义一个具体的车辆类,使用合成/聚合来复用  
class Car implements Vehicle {  
    private Engine engine; // 聚合关系  
  
    public Car(Engine engine) {  
        this.engine = engine;  
    }  
  
    @Override  
    public void move() {  
        System.out.println("Car is moving...");  
        engine.start();  
    }  
}  
  
// 定义一个引擎接口  
interface Engine {  
    void start();  
}  
  
// 定义一个具体的引擎类  
class GasolineEngine implements Engine {  
    @Override  
    public void start() {  
        System.out.println("Gasoline engine started.");  
    }  
}  
  
// 客户端代码  
public class CompositionDemo {  
    public static void main(String[] args) {  
        // 创建一个引擎实例  
        Engine engine = new GasolineEngine();  
  
        // 创建一个车辆实例,并注入引擎  
        Car car = new Car(engine);  
  
        // 调用车辆移动方法,间接调用引擎的启动方法  
        car.move();  
  
        // 如果需要,可以轻松地替换引擎实现  
        // Engine electricEngine = new ElectricEngine();  
        // car = new Car(electricEngine);  
        // car.move();  
    }  
}  
  
// 假设我们有一个电动引擎类(未实现,仅作为示例)  
// class ElectricEngine implements Engine {  
//     @Override  
//     public void start() {  
//         System.out.println("Electric engine started.");  
//     }  
// }

3.3 设计模式的应用

3.3.1 工厂方法模式

工厂方法模式(Factory Method Pattern)是一种创建型设计模式,它在创建对象时不会暴露创建逻辑给客户端,并且是通过使用一个共同的接口来指向新创建的对象。这个模式涉及到一个单一的工厂类,但它允许客户端代码决定要实例化哪一个类。工厂方法让类的实例化推迟到子类中进行。

在Java中,工厂方法模式通常通过定义一个创建对象的接口,让子类决定实例化哪一个类。工厂方法模式让类的实例化依赖于它们所含的类信息中,并且将实例化的工作延迟到子类中进行。

下面是一个简单的Java代码示例,展示了工厂方法模式:

java 复制代码
// 定义一个产品接口  
interface Product {  
    void use();  
}  
  
// 实现产品接口的具体产品类  
class ConcreteProductA implements Product {  
    @Override  
    public void use() {  
        System.out.println("Using ConcreteProductA");  
    }  
}  
  
class ConcreteProductB implements Product {  
    @Override  
    public void use() {  
        System.out.println("Using ConcreteProductB");  
    }  
}  
  
// 定义一个创建产品的抽象工厂类  
abstract class Creator {  
    // 工厂方法,由子类实现  
    abstract Product factoryMethod();  
  
    // 使用工厂方法创建产品  
    public void someOperation() {  
        Product product = factoryMethod();  
        product.use();  
    }  
}  
  
// 具体工厂类,实现了抽象工厂类中的工厂方法  
class ConcreteCreatorA extends Creator {  
    @Override  
    Product factoryMethod() {  
        return new ConcreteProductA();  
    }  
}  
  
class ConcreteCreatorB extends Creator {  
    @Override  
    Product factoryMethod() {  
        return new ConcreteProductB();  
    }  
}  
  
// 客户端代码  
public class FactoryMethodPatternDemo {  
    public static void main(String[] args) {  
        Creator creatorA = new ConcreteCreatorA();  
        creatorA.someOperation(); // 输出: Using ConcreteProductA  
  
        Creator creatorB = new ConcreteCreatorB();  
        creatorB.someOperation(); // 输出: Using ConcreteProductB  
    }  
}

在这个例子中,Product 是一个产品接口,ConcreteProductA 和 ConcreteProductB 是实现了这个接口的具体产品类。Creator 是一个抽象工厂类,它定义了一个工厂方法 factoryMethod(),该方法在子类中具体实现以返回不同类型的产品。ConcreteCreatorA 和 ConcreteCreatorB 是具体工厂类,它们分别实现了 factoryMethod() 方法以返回不同的产品实例。

客户端代码通过调用具体工厂类的 someOperation() 方法来创建和使用产品,而不需要知道具体的产品类是什么。这样,客户端代码与具体的产品类解耦,提高了系统的灵活性和可扩展性。

简答工厂模式包含如下角色

  • 工厂(Factory): 简单工厂模式的核心,复制实现创建有实例的逻辑。工厂类提供静态方法,根据传入参数创建所需的产品实例。
  • 抽象产品(Produt): 工厂创建的所有实例的父类型,是负责描述所有产品的公共接口。可以是接口或抽象类。
  • 具体产品(Conrete product): 抽象产品的实现类,是工厂的创建目标,工厂创建的实例就是某个具体产品类的实例。
    客户程序NewsServiceImpl只需要知道工厂和抽象的父类产品(NewsDao接口),不需要关心具体的产品如何创建(不需要知道接口的具体实现),内部如何变化。具体产品(接口实现类)被父类型(接口)包装,与客户程序解耦合,不影响客户程序(Service接口实现类)的复用。
1. 简单工厂模式(Simple Factory Pattern)

角色:
工厂类(Factory Class): 负责创建具体产品类的实例。
抽象产品类(Abstract Product Class): 定义产品的公共接口。
具体产品类(Concrete Product Classes): 实现了抽象产品类所定义的接口。

java 复制代码
// 抽象产品类  
interface Car {  
    void drive();  
}  
  
// 具体产品类1  
class Audi implements Car {  
    @Override  
    public void drive() {  
        System.out.println("Driving Audi");  
    }  
}  
  
// 具体产品类2  
class BMW implements Car {  
    @Override  
    public void drive() {  
        System.out.println("Driving BMW");  
    }  
}  
  
// 工厂类  
class CarFactory {  
    public static Car getCar(String type) {  
        if (type.equalsIgnoreCase("audi")) {  
            return new Audi();  
        } else if (type.equalsIgnoreCase("bmw")) {  
            return new BMW();  
        }  
        return null;  
    }  
}  
  
// 客户端代码  
public class FactoryPatternDemo {  
    public static void main(String[] args) {  
        Car audi = CarFactory.getCar("audi");  
        audi.drive();  
  
        Car bmw = CarFactory.getCar("bmw");  
        bmw.drive();  
    }  
}

简单工厂模式不适合创建逻辑比较复杂的情况,复杂的产品逻辑会导致工厂方法难以维护,并且增加新的产品就需要修改工厂方法判断逻辑,这与开闭原则相违背。而工厂方法模式是对简单工厂模式的进一步抽象化,工厂方法模式的主要角色如下(对工厂进一步抽象)。

  • 抽象产品(Product): 定义了产品的规范,描述了产品的主要特性和功能(Dao接口)。
  • 抽象工厂(Abstract Factory): 提供了创建产品的接口,声明创建方法,该方法返回值为抽象产品类型,调用者通过抽象工厂接口访问具体工厂的方法来创建产品(提供创建Dao接口实现类实例的接口)。
  • 具体工厂(Concrete Factory): 实现抽象工厂中的抽象创建方法,完成某个具体产品的创建,具体工厂和具体产品之间存在对应关系(负责创建Dao接口实现类的实例)。
2. 工厂方法模式(Factory Method Pattern)

角色:
抽象工厂类(Abstract Factory Class): 声明一个用于创建对象的操作接口。
具体工厂类(Concrete Factory Classes): 实现抽象工厂类声明的接口,创建具体产品的实例。
抽象产品类(Abstract Product Class): 定义产品的接口。
具体产品类(Concrete Product Classes): 实现了抽象产品类所定义的接口。

java 复制代码
// 抽象产品类  
interface Car {  
    void drive();  
}  
  
// 具体产品类1  
class Audi implements Car {  
    @Override  
    public void drive() {  
        System.out.println("Driving Audi");  
    }  
}  
  
// 具体产品类2  
class BMW implements Car {  
    @Override  
    public void drive() {  
        System.out.println("Driving BMW");  
    }  
}  
  
// 抽象工厂类  
interface CarFactory {  
    Car createCar();  
}  
  
// 具体工厂类1  
class AudiFactory implements CarFactory {  
    @Override  
    public Car createCar() {  
        return new Audi();  
    }  
}  
  
// 具体工厂类2  
class BMWFactory implements CarFactory {  
    @Override  
    public Car createCar() {  
        return new BMW();  
    }  
}  
  
// 客户端代码  
public class FactoryMethodPatternDemo {  
    public static void main(String[] args) {  
        CarFactory audiFactory = new AudiFactory();  
        Car audi = audiFactory.createCar();  
        audi.drive();  
  
        CarFactory bmwFactory = new BMWFactory();  
        Car bmw = bmwFactory.createCar();  
        bmw.drive();  
    }  
}
3.抽象工厂模式(Abstract Factory Pattern)

角色:
抽象工厂类(Abstract Factory Class): 提供一个创建一系列相关或相互依赖对象的接口。
具体工厂类(Concrete Factory Classes): 实现抽象工厂类声明的接口,创建具体产品的实例。
抽象产品类(Abstract Product Classes): 定义了一组产品的接口。
具体产品类(Concrete Product Classes): 实现了抽象产品类所定义的接口。

代码示例:

由于抽象工厂模式涉及多个产品族和多个等级结构的产品,代码示例会相对复杂,这里仅给出框架性的描述。

java 复制代码
// 抽象产品A  
interface ProductA {  
    void operationA();  
}  
  
// 具体产品A1  
class ConcreteProductA1 implements ProductA {  
    @Override  
    public void operationA() {  
        // 实现  
    }  
}  
  
// 抽象产品B  
interface ProductB {  
    void operationB();  
}  
  
// 具体产品B1

3.3.2 代理模式

在生活中,我们经常听说房产中介、婚介、经纪人等社会角色,这些都是代理模式的实现体现。这种模式其实也是单一职责原则的体现,就好像一个要买房,中间会涉及很多的环节,部分流程复杂而且专业。

代理模式包含如下角色
  • 抽象主题(Subject): 通过接口或抽象类声明业务方法(NewsDao接口)。
  • 真实主题(Real Subject): 实现了抽象主题中的具体业务,是实施代理的目标对象,即代理对象所代表的真实对象,是最终要引用的对象(NewsDao接口的实现类)。
  • 代理(Proxy): 提供了与真实主题相同的接口,其内部含有对真实主题的引用,可以访问、控制或扩展真实主题的功能。
    (在接口中定义一个买房的方法,真实对张三去实现买房操作。)

代理模式(Proxy Pattern)是一种结构型设计模式,它为其他对象提供一种代理以控制对这个对象的访问。代理模式通常用于在客户端和目标对象之间创建一个中介,以增加功能、控制访问、减少系统间耦合等。

代理模式主要有三种类型:静态代理、动态代理(包括JDK动态代理和CGLIB动态代理,主要基于Java语言)和远程代理。下面分别给出静态代理和JDK动态代理的简单代码示例。

静态代理

静态代理通常是在编译时就确定了代理类,代理类和被代理类都实现了相同的接口。

java 复制代码
// 接口  
interface Image {  
    void display();  
}  
  
// 被代理类  
class RealImage implements Image {  
    private String fileName;  
  
    public RealImage(String fileName) {  
        this.fileName = fileName;  
        loadFromDisk(fileName);  
    }  
  
    private void loadFromDisk(String fileName) {  
        System.out.println("Loading " + fileName);  
    }  
  
    @Override  
    public void display() {  
        System.out.println("Displaying " + fileName);  
    }  
}  
  
// 代理类  
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();  
    }  
}  
  
// 客户端  
public class ProxyPatternDemo {  
    public static void main(String[] args) {  
        Image image = new ProxyImage("test.jpg");  
  
        // 图像将从磁盘加载  
        image.display();  
    }  
}

JDK动态代理

JDK动态代理是在运行时动态地创建代理类,需要被代理的对象必须实现一个或多个接口。

java 复制代码
import java.lang.reflect.InvocationHandler;  
import java.lang.reflect.Method;  
import java.lang.reflect.Proxy;  
  
// 接口  
interface Image {  
    void display();  
}  
  
// 被代理类  
class RealImage implements Image {  
    private String fileName;  
  
    public RealImage(String fileName) {  
        this.fileName = fileName;  
    }  
  
    @Override  
    public void display() {  
        System.out.println("Displaying " + fileName);  
    }  
}  
  
// 代理类的InvocationHandler实现  
class ImageInvocationHandler implements InvocationHandler {  
    private Object target;  
  
    public ImageInvocationHandler(Object target) {  
        this.target = target;  
    }  
  
    @Override  
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  
        // 在方法调用之前可以添加额外的处理  
        System.out.println("Before method " + method.getName());  
  
        // 调用原始对象的方法  
        Object result = method.invoke(target, args);  
  
        // 在方法调用之后可以添加额外的处理  
        System.out.println("After method " + method.getName());  
  
        return result;  
    }  
}  
  
// 客户端  
public class DynamicProxyPatternDemo {  
    public static void main(String[] args) {  
        Image realImage = new RealImage("test.jpg");  
  
        // 创建代理对象  
        Image proxyImage = (Image) Proxy.newProxyInstance(  
                Image.class.getClassLoader(),  
                new Class[]{Image.class},  
                new ImageInvocationHandler(realImage)  
        );  
  
        // 调用代理对象的方法  
        proxyImage.display();  
    }  
}

在JDK动态代理中,Proxy.newProxyInstance() 方法用于动态地创建代理对象,它需要三个参数:类加载器、接口数组(被代理类实现的接口)和InvocationHandler实例。通过实现InvocationHandler接口的invoke方法,我们可以在方法调用前后添加自定义的逻辑。

相关推荐
儿时可乖了17 分钟前
使用 Java 操作 SQLite 数据库
java·数据库·sqlite
ruleslol18 分钟前
java基础概念37:正则表达式2-爬虫
java
xmh-sxh-131435 分钟前
jdk各个版本介绍
java
天天扭码1 小时前
五天SpringCloud计划——DAY2之单体架构和微服务架构的选择和转换原则
java·spring cloud·微服务·架构
程序猿进阶1 小时前
堆外内存泄露排查经历
java·jvm·后端·面试·性能优化·oom·内存泄露
FIN技术铺1 小时前
Spring Boot框架Starter组件整理
java·spring boot·后端
小曲程序1 小时前
vue3 封装request请求
java·前端·typescript·vue
陈王卜1 小时前
django+boostrap实现发布博客权限控制
java·前端·django