设计模式-工厂模式、策略模式、代理模式、责任链模式
目录
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 {@Overridepublic int calc(int num1, int num2) {// TODO Auto-generated method stubreturn num1 + num2;}}
public class SubstractStrategy implements Strategy {@Overridepublic int calc(int num1, int num2) {// TODO Auto-generated method stubreturn 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 为啥要用代理模式
-
中介隔离:
-
客户类不想或不能直接访问目标对象时,代理类作为中介。
-
示例:买房时,中介帮你联系房东、办理手续。
-
-
功能扩展:
-
代理类可以在不修改目标对象的情况下,增加额外功能(如日志、缓存)。
-
示例:买房前中介帮你检查房屋质量,买房后帮你装修。
-
-
符合开闭原则:
-
对扩展开放,对修改关闭。通过代理类扩展功能,无需修改目标对象。
-
3.3 代理模式分类
3.3.1 静态代理
-
定义:静态代理是在编译时就已经确定代理类和目标类的关系。代理类和目标类实现相同的接口,代理类通过持有目标类的引用来控制对目标类的访问。
-
特点:
-
代理类和目标类的关系在编译时确定。
-
每个目标类都需要一个对应的代理类。
-
-
适用场景:目标类较少,功能扩展简单。
-
优点:
-
简单直观,易于理解。
-
符合开闭原则,扩展功能方便。
-
-
缺点:
-
每个目标对象都需要一个代理类,代码冗余。
-
接口变动时,代理类也需要修改。
-
public interface BuyHouse {void buyHouse();
}public class BuyHouseImpl implements BuyHouse {@Overridepublic void buyHouse() {System.out.println("我要买房");}
}public class BuyHouseProxy implements BuyHouse {private BuyHouse buyHouse;public BuyHouseProxy(BuyHouse buyHouse) {this.buyHouse = buyHouse;}@Overridepublic 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 {@Overridepublic 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);}@Overridepublic 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 {@Overridepublic 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);}@Overridepublic 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);}@Overridepublic 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 责任链模式的应用场景
-
审批流程:比如报销审批、请假审批等。
-
日志处理:比如将日志信息传递给多个日志处理器(控制台、文件、数据库等)。
-
过滤器链:比如Web开发中的过滤器链,每个过滤器可以决定是否处理请求,或者将请求传递给下一个过滤器。
4.5 责任链模式的优点
-
解耦:请求的发送者和处理者之间没有直接依赖,发送者不需要知道是谁处理了请求。
-
灵活性:可以动态调整链上的处理者,比如增加、删除或调整顺序。
-
单一职责:每个处理者只负责自己能处理的部分,符合单一职责原则。
4.6. 责任链模式的缺点
-
性能问题:如果链太长,请求可能需要经过多个处理者才能被处理,影响性能。
-
请求可能未被处理:如果链上的所有处理者都无法处理请求,请求可能会丢失。
场景:你负责项目的时候遇到了哪些比较棘手的问题?
在我们的在线判题系统(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 {@Overridepublic void doJudge(JudgeContext context) {// Java语言的判题逻辑}
}
public class CppLanguageJudgeStrategy implements JudgeStrategy {@Overridepublic 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 接口,然后在 JudgeManager 的 Map 中添加对应的映射关系即可,不会对现有的代码造成影响。
(2) 然后,我们使用工厂模式简化代码沙箱调用实例的获取
定义代码沙箱接口
首先,我定义了一个代码沙箱接口 CodeSandbox。这个接口规定了代码沙箱必须具备的核心功能,即 execute 方法,该方法接收代码和输入作为参数,返回执行结果。通过这个接口,我们可以为不同类型的代码沙箱提供统一的调用方式。
public interface CodeSandbox {ExecutionResult execute(String code, String input);
}
实现具体的代码沙箱类
我实现了具体的代码沙箱类。分别有 LocalCodeSandbox 用于本地代码沙箱执行,RemoteCodeSandbox 用于远程代码沙箱执行,以及 ThirdPartyCodeSandbox 用于调用第三方代码沙箱服务。每个类都实现了 CodeSandbox 接口,并在 execute 方法中编写了各自对应的执行逻辑。
public class LocalCodeSandbox implements CodeSandbox {@Overridepublic ExecutionResult execute(String code, String input) {// 本地沙箱执行逻辑}
}
public class RemoteCodeSandbox implements CodeSandbox {@Overridepublic ExecutionResult execute(String code, String input) {// 远程沙箱执行逻辑}
}
public class ThirdPartyCodeSandbox implements CodeSandbox {@Overridepublic 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);}}
}
通过这种工厂模式的实现,我们在需要获取代码沙箱实例时,只需要调用 CodeSandboxFactory 的 getCodeSandbox 方法并传入相应的类型即可,无需关心具体的实例创建过程。而且,如果后续需要添加新的代码沙箱类型,只需要在工厂类的 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 作为参数传递给 CodeSandboxFactory 的 getCodeSandbox 方法,就能获取到对应的代码沙箱实例。
(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;}
@Overridepublic 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);
}
通过代理模式,我们可以在不改变代码沙箱实现类的前提下,集中地为代码沙箱添加新的功能,比如日志记录、性能监控等。这样,代码沙箱的原有功能不会受到影响,同时又能轻松地扩展其能力。
代理模式将日志记录逻辑与代码沙箱的执行逻辑分离开来。代码沙箱的实现类只需要专注于代码执行的核心逻辑,而日志记录等额外功能由代理类负责。这使得每个类的职责更加清晰明确,提高了代码的可维护性和可扩展性,也让代码结构更加合理。
相关文章:
设计模式-工厂模式、策略模式、代理模式、责任链模式
目录 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 动态代理…...
nginx中间件部署
普通权限账户安装NGINX中间件 1、拥有高级权限的账户安装必要的插件 sudo yum install -y gcc-c make pcre pcre-devel zlib zlib-devel openssl openssl-devel 2、普通账户进行NGINX的脚本式安装 vi nginx_intall.sh #!/bin/bash TAR_NAME"$1" TAR_NAME_DIRba…...
PentestGPT 下载
PentestGPT 下载 PentestGPT 介绍 PentestGPT(Penetration Testing GPT)是一个基于大语言模型(LLM)的智能渗透测试助手。它结合了 ChatGPT(或其他 GPT 模型)与渗透测试工具,帮助安全研究人员自…...
JVM 2015/3/15
定义:Java Virtual Machine -java程序的运行环境(java二进制字节码的运行环境) 好处: 一次编写,到处运行 自动内存管理,垃圾回收 数组下标越界检测 多态 比较:jvm/jre/jdk 常见的JVM&…...
Java中接口隔离原则简介和代码举例
简介: 接口隔离原则(Interface Segregation Principle,ISP)是面向对象设计SOLID原则中的“I”,其核心思想是: 定义 客户端不应被迫依赖它不使用的方法。即,一个类对另一个类的依赖应建立在最…...
基于自定义线程池手写一个异步任务管理器
我们在后端执行某些耗时逻辑操作时往往会导致长时间的线程阻塞,在这种情况之下,我们往往会引一条异步线程去处理这些异步任务,如果每次都创建新的线程来处理这些任务,不仅会增加代码冗余,还可能造成线程管理混乱&#…...
sql靶场-时间盲注(第九、十关)保姆级教程
目录 时间盲注(第九、十关) 1.判断 2.确认时间盲注 2.手工尝试时间盲注 数据库名长度 数据库名字符 表数 表名长度 表名字符 字段数 字段名长度 字段名字符 4.脚本时间盲注注入 5.第十关 时间盲注(第九、十关) 1.判…...
Vuex 高级技巧与最佳实践
使用 map 辅助函数简化代码: javascript import { mapState, mapGetters } from vuexexport default {computed: {...mapState([num]),...mapGetters([doubleNum])} }模块化开发: javascript // modules/student.js export default {namespaced: true,st…...
51c自动驾驶~合集54
我自己的原文哦~ https://blog.51cto.com/whaosoft/13517811 #Chameleon 快慢双系统!清华&博世最新:无需训练即可解决复杂道路拓扑 在自动驾驶技术中,车道拓扑提取是实现无地图导航的核心任务之一。它要求系统不仅能检测出车道和交…...
大模型推理:LM Studio在Mac上部署Deepseek-R1模型
LM Studio LM Studio是一款支持离线大模型部署的推理服务框架,提供了易用的大模型部署web框架,支持Linux、Mac、Windows等平台,并提供了OpenAI兼容的SDK接口,主要使用LLama.cpp和MLX推理后端,在Mac上部署时选择MLX推理…...
扩散模型:AIGC领域的核心引擎,解锁图像生成新维度
一、扩散模型技术原理 扩散模型是一类生成模型,它运用了物理热力学中的扩散思想, 主要包括前向扩散和反向扩散两个过程。 1.1、生成模型 在深度学习中,生成模型的目标是根据给定的样本(训练数据) 生成新样本。首先给…...
Java多线程与高并发专题——原子类和 volatile、synchronized 有什么异同?
原子类和 volatile异同 首先,通过我们对原子类和的了解,原子类和volatile 都能保证多线程环境下的数据可见性。在多线程程序中,每个线程都有自己的工作内存,当多个线程访问共享变量时,可能会出现一个线程修改了共享变…...
//要求:将输入的字符串中的数字转换为罗马数字,长度小于9(运用方法:Switch方法)
import java.util.Scanner;public class Num2 {public static void main(String[] args){ // I II III IV V VI VII VIII IX//要求:将输入的字符串中的数字转换为罗马数字,长度小于9(运用方法:查表法)//1输入数字//2有效字符判断/…...
【数据结构】数据结构,算法 概念
0.本篇问题: 数据、数据元素、数据对象、数据项之间的基本关系?ADT是什么?数据结构的三要素?数据的逻辑结构有哪些?数据的存储结构有哪些?算法的五个特征?O(1) O(logn) O(n^n) O(n) O(n^2…...
pytest 框架学习总结
视频:pytest01-快速上手_哔哩哔哩_bilibili 资料:pytest 框架 - 白月黑羽 基于 Python 语言的自动化测试框架 最知名的 有如下 3 款unittest、pytest、robotframework 前两款框架主要(或者说很大程度上)是 聚焦 在 白盒单元测试…...
总结 HTTP 协议的基本格式, 相关知识以及抓包工具fiddler的使用
目录 1 HTTP是什么 2 HTTP协议格式 3 HTTP请求(Request) 3.1 认识URL 3.2 方法 3.3 认识请求"报头"(header) 4 HTTP响应详解 4.1 认识"状态码"(statuscode) 4.2 认识响应"报头"(header) 4.3 认识响应"正⽂"(body) 5 通过f…...
python中的max(),需要注意的点
words ["apple", "banana", "grape", "cherry"] 对每个单词,keylambda x: len(x) 会计算它的长度: "apple" 长度是 5"banana" 长度是 6"grape" 长度是 5"cherry" 长度…...
DeepSeek-R1大模型微调技术深度解析:架构、方法与应用全解析
1. DeepSeek-R1大模型架构设计与技术特性 1.1 架构设计 DeepSeek-R1作为超大规模语言模型,其核心架构设计包含以下创新: 专家混合架构(MoE) 采用6710亿参数的混合专家架构(MoE),每个推理过程仅激活370亿参数,实现计算效率与资源利用率的突破性提升。 Transformer框架…...
探索Maas平台与阿里 QWQ 技术:AI调参的魔法世界
摘要:本文介绍了蓝耘 Maas 平台在人工智能领域的表现及其核心优势,包括强大的模型支持、高效的资源调度和友好的操作界面。文章还探讨了蓝耘 Maas 平台与阿里 QWQ 技术的融合亮点及应用拓展实例,并提供了调参实战指南,最后对蓝耘 …...
Linux第三次练习
1、创建根目录结构中的所有的普通文件 首先在根目录下面新创建一个test目录,然后将查找到的普通文件新建到test目录下 2、列出所有账号的账号名 3、将/etc/passwd中内容按照冒号隔开的第三个字符从大到小排序后输出所有内容 4、列出/etc/passwd中的第20行-25行内容…...
软件测试知识总结
1、黑盒测试、白盒测试、灰盒测试 1.1 黑盒测试 黑盒测试又叫功能测试、数据驱动测试 或 基于需求规格说明书的功能测试。该类测试注重于测试软件的功能性需求。 采用这种测试方法,测试工程师把测试对象看作一个黑盒子,完全不考虑程序内部的逻辑结构和…...
JConsole 监控线程池状态
JConsole 可以用来监控 Java 线程池(ThreadPoolExecutor)的状态,包括线程数量、任务执行情况、CPU 及内存使用情况等。下面是具体的操作步骤: 一、启动 JConsole 1. 启动 JConsole Windows:在 JDK bin 目录下找到 j…...
【HTML】三、表单与布局标签
文章目录 1、input1.1 input的占位文案1.2 单选框1.3 上传文件1.4 多选框 2、 下拉菜单3、文本域:多行输入4、label标签:说明与增大点击范围5、按钮与form表单6、无语义布局标签7、有语义的布局标签8、字符实体9、练习:注册页面 1、input in…...
OpenBMC:BmcWeb添加路由1 getParameterTag
BmcWeb对于路由的设计其实是参考了Crow BMCWEB_ROUTE(app, "/upload/image/<str>").privileges({{"ConfigureComponents", "ConfigureManager"}}).methods(boost::beast::http::verb::post, boost::beast::http::verb::put)([](const cro…...
【结构设计】3D打印创想三维Ender 3 v2
【结构设计】3D打印创想三维Ender 3 v2 文章目录 前言一、Creality Slicer1.2.3打印参数设置二、配件更换1.捆扎绑扎线2.气动接头3D打印机配件插头3.3D打印机配件Ender3pro/V2喷头套件4.读卡器 TF卡5.micro sd卡 三、调平四、参考文章总结 前言 使用工具: 1.创想三…...
嵌入式web服务器实现上传下载储存研究
标题:嵌入式web服务器实现上传下载储存研究 内容:1.摘要 随着互联网与嵌入式系统的不断融合,嵌入式设备对数据上传、下载及储存功能的需求日益增长。本文旨在研究嵌入式web服务器实现上传、下载和储存功能的有效方法。通过分析常见的嵌入式web服务器架构࿰…...
UE小:UE5.5 PixelStreamingInfrastructure 使用时注意事项
1、鼠标默认显示 player.ts中的Config中添加HoveringMouse:true 然后运行typescript\package.json中的"build":npx webpack --config webpack.prod.js...
Anaconda 入门指南
Anaconda 入门指南 一、下载安装 Anaconda 1、下载地址:Anaconda 推荐下载 python3 版本, 毕竟未来 python2 是要停止维护的。 2、安装 Anaconda 按照安装程序提示一步步安装就好了, 安装完成之后会多几个应用: Anaconda Navigtor :用于管…...
web组态可视化编辑器
Web组态可视化编辑器是一种用于创建和配置工业自动化、物联网(IoT)和智能建筑等领域的图形化用户界面(GUI)的工具。它允许用户通过拖放组件、配置参数和连接数据源来设计和部署实时监控和控制界面。以下是一些常见的Web组态可视化…...
CTA重建:脑血管重建,CT三维重建,三维建模 技术,实现
CTA(CT血管造影)是一种基于CT扫描的医学成像技术,主要用于血管系统的三维重建和可视化。脑血管重建是CTA的重要应用之一,能够帮助医生诊断脑血管疾病(如动脉瘤、狭窄、畸形等)。以下是实现CTA脑血管重建、C…...
