设计模式-工厂模式、策略模式、代理模式、责任链模式

发布于:2025-03-16 ⋅ 阅读:(17) ⋅ 点赞:(0)

目录

1 工厂模式

1.1 简单工厂模式

1.2 工厂方法模式

1.3 抽象工厂模式

1.4 工厂模式适用的场合

1.5  三种工厂模式的使用选择

2 策略模式

2.1 定义

2.2 结构

3 代理模式

3.1 啥是代理模式

3.2 为啥要用代理模式

3.3 代理模式分类

3.3.1 静态代理

3.3.2 动态代理

3.3.3 CGLIB代理

4 责任链模式

4.1 定义

4.2 责任链模式的核心思想

4.3 责任链模式的结构

4.4 责任链模式的应用场景

4.5 责任链模式的优点

4.6. 责任链模式的缺点

场景:你负责项目的时候遇到了哪些比较棘手的问题?

(1) 首先,我们用策略模式封装不同语言的判题算法

(2) 然后,我们使用工厂模式简化代码沙箱调用实例的获取

(3) 为了进一步优化判题机模块中代码沙箱调用的灵活性与可管理性,我采取了配置化代码沙箱类型的方案。

(4) 最后 我们使用代理模式增强代码沙箱的能力


1 工厂模式

1.1 简单工厂模式

定义:定义了一个创建对象的类,由这个类来封装实例化对象的行为。

适用场景:当产品种类不多,且不会增加时。

特点:实现简单,但不够灵活,增加新产品时需要修改工厂类。

举例:pizza工厂

public class SimplePizzaFactory {
       public Pizza CreatePizza(String ordertype) {
              Pizza pizza = null;
              if (ordertype.equals("cheese")) {
                     pizza = new CheesePizza();
              } else if (ordertype.equals("greek")) {
                     pizza = new GreekPizza();
              } else if (ordertype.equals("pepper")) {
                     pizza = new PepperPizza();
              }
              return pizza;
       }
}

简单工厂存在的问题与解决方法: 简单工厂模式有一个问题就是,类的创建依赖工厂类,也就是说,如果想要拓展程序,必须对工厂类进行修改,这违背了开闭原则,所以,从设计角度考虑,有一定的问题,如何解决?我们可以定义一个创建对象的抽象方法并创建多个不同的工厂类实现该抽象方法,这样一旦需要增加新的功能,直接增加新的工厂类就可以了,不需要修改之前的代码。这种方法也就是我们接下来要说的工厂方法模式。

1.2 工厂方法模式

定义:定义一个用于创建对象的接口,让子类决定实例化哪一个类。

适用场景:产品种类固定,但可能会增加新种类时。

特点:比简单工厂模式更灵活,增加新产品时只需增加新的具体工厂类。

工厂方法模式克服了简单工厂要修改代码的缺点,如果加了一个新的产地,用简单工厂模式的的话,我们要去修改工厂代码,并且会增加一堆的if else语句。而工厂方法模式它会直接创建两个工厂,纽约工厂和伦敦工厂,克服了简单工厂要修改代码的缺点。

abstract Pizza createPizza();
public class LDOrderPizza extends OrderPizza {
       Pizza createPizza(String ordertype) {
              Pizza pizza = null;
              if (ordertype.equals("cheese")) {
                     pizza = new LDCheesePizza();
              } else if (ordertype.equals("pepper")) {
                     pizza = new LDPepperPizza();
              }
              return pizza;
       }
}
public class NYOrderPizza extends OrderPizza {
 
	Pizza createPizza(String ordertype) {
		Pizza pizza = null;
 
		if (ordertype.equals("cheese")) {
			pizza = new NYCheesePizza();
		} else if (ordertype.equals("pepper")) {
			pizza = new NYPepperPizza();
		}
		return pizza;
 
	}
 
}

其实这个模式的好处就是,如果你现在想增加一个功能,只需做一个实现类就OK了,无需去改动现成的代码。这样做,拓展性较好!

工厂方法存在的问题与解决方法:客户端需要创建类的具体的实例。简单来说就是用户要订纽约工厂的披萨,他必须去纽约工厂,想订伦敦工厂的披萨,必须去伦敦工厂。 当伦敦工厂和纽约工厂发生变化了,用户也要跟着变化,这无疑就增加了用户的操作复杂性。

为了解决这一问题,我们可以把工厂类抽象为接口,用户只需要去找默认的工厂提出自己的需求(传入参数),便能得到自己想要产品,而不用根据产品去寻找不同的工厂,方便用户操作。这也就是我们接下来要说的抽象工厂模式。

1.3 抽象工厂模式

定义:定义了一个接口用于创建相关或有依赖关系的对象族,而无需明确指定具体类。

适用场景:产品族(即一组相关联的产品)很多,并且可能会增加新的产品族时。

特点:高度灵活,易于扩展,增加新的产品族时只需增加新的具体工厂类和对应的产品类。

例如要开发一个游戏,游戏中有不同风格的角色和武器,比如现代风格和魔幻风格。每个风格都有一系列相关的产品,即角色和武器,它们之间有一定的搭配关系。 

定义抽象工厂接口

public interface GameFactory {
    Character createCharacter();
    Weapon createWeapon();
}

现代风格工厂类 ModernGameFactory 

魔幻风格工厂类 MagicGameFactory

角色接口 Character

现代风格角色类 ModernCharacter

魔幻风格角色类 MagicCharacter

武器接口 Weapon

风格武器类 ModernWeapon

魔幻风格武器类 MagicWeapon

1.4 工厂模式适用的场合

大量的产品需要创建,并且这些产品具有共同的接口 。

1.5  三种工厂模式的使用选择

简单工厂 : 用来生产同一等级结构中的任意产品。(不支持拓展增加产品)

工厂方法 :用来生产同一等级结构中的固定产品。(支持拓展增加产品)   

抽象工厂 :用来生产不同产品族的全部产品。(支持拓展增加产品;支持增加产品族)  

简单工厂的适用场合:只有伦敦工厂(只有这一个等级),并且这个工厂只生产三种类型的pizza:chesse,pepper,greak(固定产品)。

工厂方法的适用场合:现在不光有伦敦工厂,还增设了纽约工厂(仍然是同一等级结构,但是支持了产品的拓展),这两个工厂依然只生产三种类型的pizza:chesse,pepper,greak(固定产品)。

抽象工厂的适用场合:不光增设了纽约工厂(仍然是同一等级结构,但是支持了产品的拓展),这两个工厂还增加了一种新的类型的pizza:chinese pizza(增加产品族)。

所以说抽象工厂就像工厂,而工厂方法则像是工厂的一种产品生产线。因此,我们可以用抽象工厂模式创建工厂,而用工厂方法模式创建生产线。比如,我们可以使用抽象工厂模式创建伦敦工厂和纽约工厂,使用工厂方法实现cheese pizza和greak pizza的生产。

总结一下三种模式:

简单工厂模式就是建立一个实例化对象的类,在该类中对多个对象实例化。工厂方法模式是定义了一个创建对象的抽象方法,由子类决定要实例化的类。这样做的好处是再有新的类型的对象需要实例化只要增加子类即可。抽象工厂模式定义了一个接口用于创建对象族,而无需明确指定具体类。抽象工厂也是把对象的实例化交给了子类,即支持拓展。同时提供给客户端接口,避免了用户直接操作子类工厂。

2 策略模式

2.1 定义

定义: 策略模式定义了一系列算法,并将每个算法封装起来,使他们可以相互替换,且算法的变化不会影响到使用算法的客户。

意图:定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换。

主要解决:在有多种算法相似的情况下,使用 if...else 所带来的复杂和难以维护。

何时使用:一个系统有许多许多类,而区分它们的只是他们直接的行为。

如何解决:将这些算法封装成一个一个的类,任意地替换。

关键代码:实现同一个接口。

优点: 1、算法可以自由切换。 2、避免使用多重条件判断。 3、扩展性良好。

缺点: 1、策略类会增多。 2、所有策略类都需要对外暴露。

2.2 结构

抽象策略角色: 这个是一个抽象的角色,通常情况下使用接口或者抽象类去实现。

具体策略角色: 包装了具体的算法和行为。

环境角色: 内部会持有一个抽象角色的引用,给客户端调用。

 定义抽象策略角色

public interface Strategy {
 
	public int calc(int num1,int num2);
}

 定义具体策略角色

public class AddStrategy implements Strategy {
 
	@Override
	public int calc(int num1, int num2) {
		// TODO Auto-generated method stub
		return num1 + num2;
	}
 
}
public class SubstractStrategy implements Strategy {
 
	@Override
	public int calc(int num1, int num2) {
		// TODO Auto-generated method stub
		return num1 - num2;
	}
 
}

 环境角色

public class Environment {
	private Strategy strategy;
 
	public Environment(Strategy strategy) {
		this.strategy = strategy;
	}
 
	public int calculate(int a, int b) {
		return strategy.calc(a, b);
	}
 
}

 测试

public class MainTest {
	public static void main(String[] args) {
		
		Environment environment=new Environment(new AddStrategy());
		int result=environment.calculate(20, 5);
		System.out.println(result);
		
		Environment environment1=new Environment(new SubstractStrategy());
		int result1=environment1.calculate(20, 5);
		System.out.println(result1);
	}
 
}

3 代理模式

3.1 啥是代理模式

定义:代理模式是为一个对象提供一个代理对象,由代理对象控制对原对象的访问。

通俗理解:就像找中介买房,你不用直接和房东打交道,中介帮你处理所有事情。

3.2 为啥要用代理模式

  1. 中介隔离

    • 客户类不想或不能直接访问目标对象时,代理类作为中介。

    • 示例:买房时,中介帮你联系房东、办理手续。

  2. 功能扩展

    • 代理类可以在不修改目标对象的情况下,增加额外功能(如日志、缓存)。

    • 示例:买房前中介帮你检查房屋质量,买房后帮你装修。

  3. 符合开闭原则

    • 对扩展开放,对修改关闭。通过代理类扩展功能,无需修改目标对象。

3.3 代理模式分类

3.3.1 静态代理

  • 定义:静态代理是在编译时就已经确定代理类和目标类的关系。代理类和目标类实现相同的接口,代理类通过持有目标类的引用来控制对目标类的访问。

  • 特点

    • 代理类和目标类的关系在编译时确定。

    • 每个目标类都需要一个对应的代理类。

  • 适用场景:目标类较少,功能扩展简单。

  • 优点

    • 简单直观,易于理解。

    • 符合开闭原则,扩展功能方便。

  • 缺点

    • 每个目标对象都需要一个代理类,代码冗余。

    • 接口变动时,代理类也需要修改。

public interface BuyHouse {
    void buyHouse();
}

public class BuyHouseImpl implements BuyHouse {
    @Override
    public void buyHouse() {
        System.out.println("我要买房");
    }
}

public class BuyHouseProxy implements BuyHouse {
    private BuyHouse buyHouse;

    public BuyHouseProxy(BuyHouse buyHouse) {
        this.buyHouse = buyHouse;
    }

    @Override
    public void buyHouse() {
        System.out.println("买房前准备");
        buyHouse.buyHouse(); // 调用真实对象的方法
        System.out.println("买房后装修");
    }
}

public class StaticProxyTest {
    public static void main(String[] args) {
        BuyHouse buyHouse = new BuyHouseImpl();
        BuyHouse proxy = new BuyHouseProxy(buyHouse);
        proxy.buyHouse();
    }
}

3.3.2 动态代理

  • 定义:动态代理是在运行时动态生成代理类,代理类不需要实现接口,而是通过反射机制调用目标类的方法。

  • 特点

    • 代理类在运行时动态生成。

    • 一个代理类可以代理多个目标类。

    • 目标类必须实现接口。只能代理实现了接口的类。

  • 适用场景:目标类较多,功能扩展复杂。

public interface UserService {
    void saveUser(String name);
}

public class UserServiceImpl implements UserService {
    @Override
    public void saveUser(String name) {
        System.out.println("保存用户: " + name);
    }
}

现在你想在调用 saveUser 方法前后打印日志,但你不想修改 UserServiceImpl 的代码。这时你可以使用JDK动态代理。

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class UserServiceProxy implements InvocationHandler {
    private Object target; // 被代理的对象

    public UserServiceProxy(Object target) {
        this.target = target;
    }

    // 生成代理对象
    public Object getProxy() {
        return Proxy.newProxyInstance(
            target.getClass().getClassLoader(),
            target.getClass().getInterfaces(),
            this
        );
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("方法调用前: " + method.getName());
        Object result = method.invoke(target, args); // 调用真实对象的方法
        System.out.println("方法调用后: " + method.getName());
        return result;
    }
}


public class Main {
    public static void main(String[] args) {
        UserService userService = new UserServiceImpl();
        UserService proxy = (UserService) new UserServiceProxy(userService).getProxy();
        proxy.saveUser("张三");
    }
}

//输出

方法调用前: saveUser
保存用户: 张三
方法调用后: saveUser

3.3.3 CGLIB代理

  • 定义:CGLIB代理是通过动态生成目标类的子类来实现代理,子类重写目标类的方法,并在方法调用前后加入额外的逻辑。

  • 特点

    • 代理类是目标类的子类。

    • 目标类不需要实现接口。CGLIB代理可以代理没有实现接口的类。

    • 无法代理final修饰的方法。

  • 适用场景:目标类无接口,性能要求高。

//假设你有一个类 UserService,它没有实现任何接口。
public class UserService {
    public void saveUser(String name) {
        System.out.println("保存用户: " + name);
    }
}

//现在你想在调用 saveUser 方法前后打印日志,但你不想修改 UserService 的代码。这时你可以使用CGLIB代理。

//首先,你需要引入CGLIB库(如果你使用Maven,可以添加依赖)。

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class UserServiceInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("方法调用前: " + method.getName());
        Object result = proxy.invokeSuper(obj, args); // 调用父类的方法
        System.out.println("方法调用后: " + method.getName());
        return result;
    }

    // 生成代理对象
    public Object getProxy(Class<?> clazz) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(clazz); // 设置父类
        enhancer.setCallback(this); // 设置回调
        return enhancer.create(); // 创建代理对象
    }
}

public class Main {
    public static void main(String[] args) {
        UserServiceInterceptor interceptor = new UserServiceInterceptor();
        UserService proxy = (UserService) interceptor.getProxy(UserService.class);
        proxy.saveUser("李四");
    }
}


//输出
方法调用前: saveUser
保存用户: 李四
方法调用后: saveUser

4 责任链模式

4.1 定义

责任链模式是一种行为设计模式,它允许多个对象有机会处理同一个请求。请求会沿着一条“链”传递,直到有一个对象处理它为止。

4.2 责任链模式的核心思想

  • 解耦:将请求的发送者和处理者解耦。发送者不需要知道是谁处理了请求,只需要把请求丢到链上就行。

  • 灵活性:可以动态地调整链上的处理者,比如增加新的审批人,或者调整审批顺序。

  • 责任分离:每个处理者只负责自己能处理的部分,处理不了的就交给下一个处理者。

4.3 责任链模式的结构

// 抽象处理者
public abstract class Approver {
    protected Approver successor;
    protected String name;

    public Approver(String name) {
        this.name = name;
    }

    public void setSuccessor(Approver successor) {
        this.successor = successor;
    }

    public abstract void processRequest(PurchaseRequest request);
}

// 具体处理者:组长
public class GroupApprover extends Approver {
    public GroupApprover(String name) {
        super(name);
    }

    @Override
    public void processRequest(PurchaseRequest request) {
        if (request.getAmount() < 5000) {
            System.out.println(name + " 处理了请求: " + request.getId());
        } else if (successor != null) {
            successor.processRequest(request);
        }
    }
}

// 具体处理者:部长
public class DepartmentApprover extends Approver {
    public DepartmentApprover(String name) {
        super(name);
    }

    @Override
    public void processRequest(PurchaseRequest request) {
        if (request.getAmount() >= 5000 && request.getAmount() < 10000) {
            System.out.println(name + " 处理了请求: " + request.getId());
        } else if (successor != null) {
            successor.processRequest(request);
        }
    }
}

// 请求类
public class PurchaseRequest {
    private int id;
    private double amount;

    public PurchaseRequest(int id, double amount) {
        this.id = id;
        this.amount = amount;
    }

    public int getId() {
        return id;
    }

    public double getAmount() {
        return amount;
    }
}

// 客户端
public class Client {
    public static void main(String[] args) {
        Approver groupLeader = new GroupApprover("Tom");
        Approver departmentLeader = new DepartmentApprover("Jerry");

        groupLeader.setSuccessor(departmentLeader);

        PurchaseRequest request1 = new PurchaseRequest(1, 4000);
        PurchaseRequest request2 = new PurchaseRequest(2, 8000);

        groupLeader.processRequest(request1); // Tom 处理
        groupLeader.processRequest(request2); // Jerry 处理
    }
}

4.4 责任链模式的应用场景

  1. 审批流程:比如报销审批、请假审批等。

  2. 日志处理:比如将日志信息传递给多个日志处理器(控制台、文件、数据库等)。

  3. 过滤器链:比如Web开发中的过滤器链,每个过滤器可以决定是否处理请求,或者将请求传递给下一个过滤器。

4.5 责任链模式的优点

  1. 解耦:请求的发送者和处理者之间没有直接依赖,发送者不需要知道是谁处理了请求。

  2. 灵活性:可以动态调整链上的处理者,比如增加、删除或调整顺序。

  3. 单一职责:每个处理者只负责自己能处理的部分,符合单一职责原则。

4.6. 责任链模式的缺点

  1. 性能问题:如果链太长,请求可能需要经过多个处理者才能被处理,影响性能。

  2. 请求可能未被处理:如果链上的所有处理者都无法处理请求,请求可能会丢失。

场景:你负责项目的时候遇到了哪些比较棘手的问题?

在我们的在线判题系统(OJ)项目中,不同编程语言的判题逻辑存在显著差异。例如,Java语言的内存和时间限制通常需要适当增加,而C++语言可能需要更严格的限制。此外,代码沙箱的调用也需要支持多种方式,如本地沙箱、远程沙箱和第三方沙箱。如果将所有这些逻辑都写在一个Service类中,通过大量的if...else语句来区分,代码的可读性和可维护性会变得很差,圈复杂度也会很高。因此,我选择了策略模式、工厂模式和代理模式来解决这些问题。

(1) 首先,我们用策略模式封装不同语言的判题算法

定义判题策略接口

这个接口就像是一个规范,规定了所有具体判题策略类都必须实现的方法。在这个接口里,有一个 doJudge 方法,它接收一个 JudgeContext 类型的参数,这个参数可以携带判题所需的各种信息。

public interface JudgeStrategy {
    void doJudge(JudgeContext context);
}

实现具体的策略类

以 Java 和 C++ 语言为例。对于 Java 语言,我创建了 JavaLanguageJudgeStrategy 类,它实现了 JudgeStrategy 接口,在 doJudge 方法里编写了 Java 语言特有的判题逻辑。同样地,对于 C++ 语言,我创建了 CppLanguageJudgeStrategy 类,也在其 doJudge 方法中实现了 C++ 语言的判题逻辑

public class JavaLanguageJudgeStrategy implements JudgeStrategy {
    @Override
    public void doJudge(JudgeContext context) {
        // Java语言的判题逻辑
    }
}
​
public class CppLanguageJudgeStrategy implements JudgeStrategy {
    @Override
    public void doJudge(JudgeContext context) {
        // C++语言的判题逻辑
    }
}

定义上下文类

为了方便传递判题所需的信息,我定义了一个上下文类 JudgeContext。在这个类中,包含了提交的代码、输入用例以及预期输出等信息,这些信息会在判题过程中被使用。

public class JudgeContext {
    private String submissionCode;
    private String input;
    private String expectedOutput;
    // 其他需要的信息
}

定义策略管理类

最后,为了管理这些不同的判题策略,我定义了一个策略管理类 JudgeManager。在这个类中,使用一个 Map 来存储不同语言对应的判题策略,键是语言的名称,值是对应的策略类实例。在构造函数中,我初始化了这个 Map,将 Java 和 C++ 等语言对应的策略类实例添加进去。同时,提供了一个 executeStrategy 方法,根据传入的语言名称从 Map 中获取对应的策略实例,如果存在就调用其 doJudge 方法进行判题,如果不存在则抛出异常。

public class JudgeManager {
    private Map<String, JudgeStrategy> strategyMap;
​
    public JudgeManager() {
        strategyMap = new HashMap<>();
        strategyMap.put("Java", new JavaLanguageJudgeStrategy());
        strategyMap.put("Cpp", new CppLanguageJudgeStrategy());
        // 添加其他语言的策略
    }
​
    public void executeStrategy(String language, JudgeContext context) {
        JudgeStrategy strategy = strategyMap.get(language);
        if (strategy != null) {
            strategy.doJudge(context);
        } else {
            throw new IllegalArgumentException("Unsupported language: " + language);
        }
    }
}

通过这种策略模式的实现,我们可以很方便地添加新的编程语言判题策略,同时也使得代码的可维护性和可扩展性得到了显著提升。如果后续需要增加新的语言判题逻辑,只需要创建一个新的策略类并实现 JudgeStrategy 接口,然后在 JudgeManagerMap 中添加对应的映射关系即可,不会对现有的代码造成影响。

(2) 然后,我们使用工厂模式简化代码沙箱调用实例的获取

定义代码沙箱接口

首先,我定义了一个代码沙箱接口 CodeSandbox。这个接口规定了代码沙箱必须具备的核心功能,即 execute 方法,该方法接收代码和输入作为参数,返回执行结果。通过这个接口,我们可以为不同类型的代码沙箱提供统一的调用方式。

public interface CodeSandbox {
    ExecutionResult execute(String code, String input);
}

实现具体的代码沙箱类

我实现了具体的代码沙箱类。分别有 LocalCodeSandbox 用于本地代码沙箱执行,RemoteCodeSandbox 用于远程代码沙箱执行,以及 ThirdPartyCodeSandbox 用于调用第三方代码沙箱服务。每个类都实现了 CodeSandbox 接口,并在 execute 方法中编写了各自对应的执行逻辑。

public class LocalCodeSandbox implements CodeSandbox {
    @Override
    public ExecutionResult execute(String code, String input) {
        // 本地沙箱执行逻辑
    }
}
​
public class RemoteCodeSandbox implements CodeSandbox {
    @Override
    public ExecutionResult execute(String code, String input) {
        // 远程沙箱执行逻辑
    }
}
​
public class ThirdPartyCodeSandbox implements CodeSandbox {
    @Override
    public ExecutionResult execute(String code, String input) {
        // 第三方沙箱执行逻辑
    }
}

定义工厂类

最后,为了方便获取不同类型的代码沙箱实例,我定义了一个工厂类CodeSandboxFactory。在这个工厂类中,有一个静态方法 getCodeSandbox,它接收一个表示沙箱类型的字符串作为参数。根据传入的类型,使用 switch 语句进行判断,然后返回相应的代码沙箱实例。如果传入的类型不被支持,会抛出一个 IllegalArgumentException 异常.

public class CodeSandboxFactory {
    public static CodeSandbox getCodeSandbox(String type) {
        switch (type) {
            case "local":
                return new LocalCodeSandbox();
            case "remote":
                return new RemoteCodeSandbox();
            case "thirdParty":
                return new ThirdPartyCodeSandbox();
            default:
                throw new IllegalArgumentException("Unsupported sandbox type: " + type);
        }
    }
}

通过这种工厂模式的实现,我们在需要获取代码沙箱实例时,只需要调用 CodeSandboxFactorygetCodeSandbox 方法并传入相应的类型即可,无需关心具体的实例创建过程。而且,如果后续需要添加新的代码沙箱类型,只需要在工厂类的 switch 语句中添加相应的分支,同时实现对应的代码沙箱类,不会对其他部分的代码造成影响,大大提高了代码的可扩展性和可维护性。

(3) 为了进一步优化判题机模块中代码沙箱调用的灵活性与可管理性,我采取了配置化代码沙箱类型的方案。

实现动态切换代码沙箱的实现,全程无需修改代码,大大提高了系统的灵活性与稳定性。

application.yml 配置文件中,我们添加了代码沙箱类型的配置项。

sandbox:
  type: remote

代码读取配置

在 Java 代码中,利用 Spring 框架提供的 @Value 注解,我们可以方便地读取配置文件中的 sandbox.type 值。示例代码如下:

@Value("${sandbox.type}")
private String sandboxType;

然后,在获取代码沙箱实例的方法中,我们利用前面提到的工厂类来获取相应类型的代码沙箱实例:

public CodeSandbox getCodeSandbox() {
    return CodeSandboxFactory.getCodeSandbox(sandboxType);
}

这里通过将读取到的 sandboxType 作为参数传递给 CodeSandboxFactorygetCodeSandbox 方法,就能获取到对应的代码沙箱实例。

(4) 最后 我们使用代理模式增强代码沙箱的能力

在调用代码沙箱前后进行日志记录等操作时,如果直接在代码沙箱调用实现类中编写,会导致代码耦合度高,后续修改和扩展困难。

定义代理类

我定义了一个代理类 CodeSandboxProxy,它实现了 CodeSandbox 接口。在这个代理类中,有一个成员变量 target,它是被代理的代码沙箱对象。通过构造函数将被代理的对象传入并赋值给 target。 在 execute 方法中,我在调用被代理对象的 execute 方法前后添加了日志记录的操作。具体来说,在调用之前,会调用 log 方法记录 “Executing code in sandbox...”,表示代码沙箱开始执行代码;在调用之后,再次调用 log 方法记录 “Execution completed.”,表示代码执行完成。最后返回执行结果。log 方法中封装了具体的日志记录逻辑。

public class CodeSandboxProxy implements CodeSandbox {
    private CodeSandbox target;
​
    public CodeSandboxProxy(CodeSandbox target) {
        this.target = target;
    }
​
    @Override
    public ExecutionResult execute(String code, String input) {
        log("Executing code in sandbox...");
        ExecutionResult result = target.execute(code, input);
        log("Execution completed.");
        return result;
    }
​
    private void log(String message) {
        // 日志记录逻辑
    }
}

使用代理类

在获取代码沙箱实例的方法 getCodeSandbox 中,首先通过 CodeSandboxFactory 工厂类根据配置的沙箱类型获取到具体的代码沙箱实例 target,然后将这个实例作为参数传递给 CodeSandboxProxy 代理类的构造函数,创建一个代理对象并返回。这样,后续调用代码沙箱的 execute 方法时,实际上调用的是代理对象的 execute 方法,从而实现了在代码沙箱执行前后添加日志记录等额外功能。

public CodeSandbox getCodeSandbox() {
    CodeSandbox target = CodeSandboxFactory.getCodeSandbox(sandboxType);
    return new CodeSandboxProxy(target);
}

通过代理模式,我们可以在不改变代码沙箱实现类的前提下,集中地为代码沙箱添加新的功能,比如日志记录、性能监控等。这样,代码沙箱的原有功能不会受到影响,同时又能轻松地扩展其能力。

代理模式将日志记录逻辑与代码沙箱的执行逻辑分离开来。代码沙箱的实现类只需要专注于代码执行的核心逻辑,而日志记录等额外功能由代理类负责。这使得每个类的职责更加清晰明确,提高了代码的可维护性和可扩展性,也让代码结构更加合理。


网站公告

今日签到

点亮在社区的每一天
去签到