学习目标
- [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种对于战争中某些场合的可行性计谋战术一一"围魏救赵""声东击西""走为上"等,可以说三十六计中的每一计都是一种模式。
- 创建型模式(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;
}
// 其他方法...
}
- 结构型模式(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();
}
}
- 行为型模式(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. 单一责任原则(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方法,我们可以在方法调用前后添加自定义的逻辑。