Java 如何在保持干净的编程实践的同时设计通用操作类?
手头的任务是创建我的Java web应用程序的一部分,它将允许我以合成的方式轻松地执行小块代码。手头的任务是允许用户以任何顺序编写“操作”。我正在努力解决的是将参数传递给我的操作 所有操作都从操作界面开始:Java 如何在保持干净的编程实践的同时设计通用操作类?,java,oop,Java,Oop,手头的任务是创建我的Java web应用程序的一部分,它将允许我以合成的方式轻松地执行小块代码。手头的任务是允许用户以任何顺序编写“操作”。我正在努力解决的是将参数传递给我的操作 所有操作都从操作界面开始: public interface Action { void resolve(Context context); } 解决该操作后,将执行其代码。代码可以是任何东西:在Java中调用方法,执行一些Javascript 在这里,“背景”是我的问题。每个操作都在特定的上下文中执行。其思
public interface Action {
void resolve(Context context);
}
解决该操作后,将执行其代码。代码可以是任何东西:在Java中调用方法,执行一些Javascript
在这里,“背景”是我的问题。每个操作都在特定的上下文中执行。其思想是,创建动作的用户可以指定要从概念中检索的对象,例如,正在解析当前动作的用户,或动作的特定界面中指定的其他对象
例如,让我们看看这个动作:
public final class ActionScript implements Action {
private final Parameters parameters;
private final String methodName;
private final ScriptsLibrary library;
public ActionScript(ScriptsLibrary library, String methodName, Parameters parameters) {
this.parameters = parameters;
this.library = library;
this.methodName = methodName;
}
@Override
public void resolve(Context context) {
try {
((Invocable) library.getEngine()).invokeFunction(methodName, context);
} catch (ScriptException | NoSuchMethodException ex) {
throw new RuntimeException(ex);
}
}
}
这是一个使用Nashorn在Javascript中调用操作的简单包装器。参数可以是任何内容:数据库中具有特定ID的对象、用户配置的int/String/boolean值
操作中的代码可以如下所示:
public class DummyAction implements Action {
@Override
public void resolve(Context context) {
User userExecutingTheAction = context.get("ExecutingUser");
System.out.println("User " + userExecutingTheAction);
}
}
因此,该操作可以检索运行时参数(例如:执行该操作的用户…)和静态参数(在加载该操作时创建,例如从配置文件),以及概念中的所有参数。此外,例如,用户可以在运行时注入的参数中指定对对象的引用
动作也可以嵌套/修饰,以实现完整的合成。例如:
public class DummyWrapperAction implements Action {
private final Action wrappedAction;
public DummyWrapperAction(Action wrappedAction) {
this.wrappedAction = wrappedAction;
}
@Override
public void resolve(Context context) {
System.out.println("Before");
wrappedAction.resolve(context);
System.out.println("After");
}
}
这样可以轻松创建操作:
// executes specific action 1 or specific action 2 based on a condition
Action myAction = new LoggingAction(new ConditionalAction(new Condition(3), new SpecificAction1(), new SpecificAction2()));
了解所有这些,设计上下文类最干净的方法是什么?它应该被分成几个元素吗?困难在于在运行时向类注入所需的所有内容,并确保不与潜在的包装操作冲突
上下文基本实现负责:
- 在运行时从数据库中检索任何对象
- 向持有事务的动作开发人员提供静态参数,并提供
- 它可以访问用户合并运行时和静态概念,以及 即使在包装器中(例如:如果父操作调用子操作 执行操作的用户必须仍然是已知的,以便上下文 合并静态子参数和运行时参数,例如 (用户)
- 操作界面应保持简单(最多1个方法,带有 合理的参数数量)
- 不使用静态机制
- 最大不变性
- Action接口仍然只有一个方法,用于使用上下文解析操作
- 在创建操作(用户会话)时加载操作的静态参数(可能是DB或其他)
- 上下文只是各种操作(事务…)和专门操作(如操作的用户)的外观
import java.util.*;
//context can be further complicated if you want visibility blocks (i.e.you need a stack of contexts)
class Context {
private Map<String, Object> variables;
Context() {
variables = new HashMap<java.lang.String, Object>();
}
//this is to retrieve stuff from your context (variables)
Object getVariable(String name) { return variables.get(name); }
// put here the shared structs
void setVariable(String name, Object value) { variables.put(name, value); }
}
interface Action<T> {
void verify(Context ctx);
void execute(Context ctx);
T getData();
void setData(T t);
List<Action<?>> getChildren();
void addChild(Action<?> action);
}
// we offer some default impl, but we keep it abstract
// note on design: you can split (derive) this in Terminal / Non-terminal nodes (careful at the above interface)
// however, it's not in my objective to follow the pattern to the letter, but to explain how the things can be done
// plus, I need to kepp this short, it's long enough
abstract class BaseAction<T> implements Action<T> {
private List<Action<?>> children;
private T data;
public BaseAction() {
children = new LinkedList<Action<?>>();
}
@Override
public void verify(Context ctx) {
for(Action<?> a : children) {
a.verify(ctx);
}
}
@Override
public void execute(Context ctx) {
for(Action<?> a : children) {
a.execute(ctx);
}
}
@Override
public T getData() {
return data;
}
@Override
public void setData(T t) {
this.data = t;
}
@Override
public List<Action<?>> getChildren() {
return Collections.unmodifiableList(children);
}
@Override
public void addChild(Action<?> action) {
children.add(action);
}
}
class BlockAction<T> extends BaseAction<T> {} //needs further refinement, including push/pop contexts if necessary
//let's implement your Action Script, some stuff left out.
// we suppose that the action produces some string
// that's a final node
final class ActionScript extends BaseAction<String>{
private final String library;
private final String methodName;
private final String aParameter;
public ActionScript(final String library, final String methodName, final String aParameter) {
this.library = library;
this.methodName = methodName;
this.aParameter = aParameter;
}
@Override
public void verify(Context ctx) {
if(!getChildren().isEmpty()) {
throw new RuntimeException("Terminal node with children ?!?");
}
}
@Override
public void execute(Context ctx) {
//do whatever here (your code)
String paramValue = (String) ctx.getVariable(aParameter);
setData(library + "." + methodName + "(" + paramValue + ")");
}
}
// this can be further complicated, i.e. to have 2 subnodes, but for simplicity:
final class AssignmentAction<T> extends BaseAction<T> {
private String variableName;
public AssignmentAction(String variableName) {
this.variableName = variableName;
}
@Override
public void verify(Context ctx) {
if(getChildren().size() != 1) {
throw new RuntimeException(String.format("= node with %d children ?!?", getChildren().size()));
}
super.verify(ctx);
}
@Override
public void execute(Context ctx) {
@SuppressWarnings("unchecked")
Action<T> child = (Action<T>) getChildren().get(0);
child.execute(ctx);
ctx.setVariable(variableName, child.getData());
}
}
public class IP {
public static void main(String []args) {
Context ctx = new Context();
ctx.setVariable("inputVar", "Hello world!");
Action<String> root = new BlockAction<String>();
root.addChild(new AssignmentAction<String>("var"));
root.getChildren().get(0).addChild(new ActionScript("myLib", "foo", "inputVar"));
root.verify(ctx);
root.execute(ctx);
System.out.println(ctx.getVariable("var"));
}
}
import java.util.*;
//如果需要可见性块(即,需要一堆上下文),则上下文可能会更复杂
类上下文{
私有映射变量;
上下文(){
变量=新的HashMap();
}
//这是从上下文(变量)中检索内容
对象getVariable(字符串名称){返回变量。get(名称);}
//把共享结构放在这里
void setVariable(字符串名称,对象值){variables.put(名称,值);}
}
界面作用{
无效验证(上下文ctx);
无效执行(上下文ctx);
T getData();
无效设置数据(T);
列表>儿童;
私有T数据;
公共行动(){
children=newlinkedlist>getChildren(){
返回集合。不可修改列表(子项);
}
@凌驾
公共无效添加子对象(操作){
儿童.添加(行动);
}
}
类BlockAction扩展了BaseAction{}//需要进一步细化,必要时包括push/pop上下文
//让我们来实现你的动作脚本,一些遗漏的东西。
//我们假设这个动作会产生一些字符串
//这是最后一个节点
最后一个类ActionScript扩展了BaseAction{
私有最终字符串库;
私有最终字符串methodName;
专用最终字符串参数;
公共操作脚本(最终字符串库、最终字符串方法名、最终字符串参数){
this.library=图书馆;
this.methodName=methodName;
this.apaparameter=apaparameter;
}
@凌驾
公共无效验证(上下文ctx){
如果(!getChildren().isEmpty()){
抛出新的RuntimeException(“带子节点的终端节点?!?”);
}
}
@凌驾
公共无效执行(上下文ctx){
//在这里做任何事(你的代码)
字符串paramValue=(字符串)ctx.getVariable(APParameter);
setData(库+“+methodName+”(“+paramValue+”));
}
}
//这可能更加复杂,即有2个子节点,但为简单起见:
最后一个类AssignmentAction扩展了BaseAction{
私有字符串变量名;
公共赋值操作(字符串变量名){
public interface Action<C extends Context> {
void resolve(C context); // no need to use 'public' modifier here
// interface methods are always public
}
public interface Context {
User get(String user);
// other default methods here
}
public class LogUserContext implements Context {
@Override
public User get(String user) {
// materialize user here
}
}
public class LogUserAction implements Action<LogUserContext> {
@Override
public void resolve(LogUserContext context) {
User user = context.get("theUser");
// log the user in
}
}
public interface WrapperContext extends Context {
Context getWrappedContext();
}
public class DummyWrappedContext implements WrapperContext {
private final Context wrappedContext;
public DummyWrapperContext(Context wrappedContext) {
this.wrappedContext = wrappedContext;
}
@Override
public Context getWrappedContext() {
return this.wrappedContext;
}
// TODO other methods from Context, etc.
}
public class DummyWrapperAction implements Action<WrapperContext> {
private final Action wrappedAction;
public DummyWrapperAction(Action wrappedAction) {
this.wrappedAction = wrappedAction;
}
@Override
public void resolve(WrapperContext context) {
System.out.println("Before");
Context wrappedContext = context.getWrappedContext();
wrappedAction.resolve(wrappedContext);
System.out.println("After");
}
}