How to develop a general class of actions subject to the rules of pure programming?

The challenge is to create part of my Java web application that will allow me to easily execute small pieces of code in composite form. The challenge is to allow the user to compose the "actions" in any order. I am trying to pass parameters to my actions.

It all starts with the action interface:

public interface Action { void resolve(Context context); } 

When an action is allowed, its code is executed. The code can be anything: calling a method in Java, executing some Javascript ...

Here, "context" is a problem for me. Each action is performed in a specific context. The idea is that the user creating the action can specify which object to get from the concept, for example, the user who permits the current action, or other objects specified in the specific interface of the action.

Example, look at this action:

 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); } } } 

This is a simple wrapper that triggers an action in Javascript using Nashorn. Parameters can be any: objects with specific identifiers in the database, int / String / boolean values ​​configured by the user ...

The code from the action may look like this:

 public class DummyAction implements Action { @Override public void resolve(Context context) { User userExecutingTheAction = context.get("ExecutingUser"); System.out.println("User " + userExecutingTheAction); } } 

Thus, an action can extract runtime parameters (for example: the user performing the action ...) and static parameters (created when loading the action - for example, from a configuration file) and all this from the concept. In addition, the user can, for example, specify references to the object in the parameters that will be entered at run time.

Actions can be nested / decorated to achieve a complete composition. For example:

 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"); } } 

This makes it easy to create actions:

 // 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())); 

Knowing all this, what is the cleanest well for developing a context class? Should it be divided into several elements? The struggle is to introduce everything that is necessary for the class at runtime, and be sure not to conflict with potential wrapped up actions.

The main implementation of the context is responsible for:

  • getting any object from the database at runtime
  • providing static parameters to the developers of the actions that perform the transaction, and providing
  • this is access to the merge of the user and the static concept of the user, and even in the wrapper (for example: if the parent action calls the child action, the user performing the action must still be known, so the context must merge the static child parameters and the run-time parameters, such as user)

I feel that is too much. Design is influenced by Concept classes, which have great responsibility, and should be fragmented (right now, every part of the application is associated with a concept). But how? Here is what I'm trying to achieve in pure coding:

  • the action interface should be simple (1 max method, with a reasonable amount of parameters)
  • lack of use of static mechanisms
  • maximum immutability

In a true object-oriented and method-oriented, how to solve this specific design problem?

edit: remote public declarator in a method in an interface

edit 2: I had many interesting solutions, but the one that gave me more meaning was one in which each action is parameterized by a specific Context. I reorganized things as such:

  • The action interface has only one method to allow an action with context.
  • Static parameters of actions, whether they can be DB or otherwise, are loaded when the action is created (user session)
  • A context is simply a facade for various operations (transaction ...) + specialized operations such as user actions
+8
java design oop
source share
2 answers

I made a generic Action interface on my Context :

 public interface Action<C extends Context> { void resolve(C context); // no need to use 'public' modifier here // interface methods are always public } 

And then Context can be either a marker interface, or an interface that enforces a contract, or an abstract class with a default method implementation:

 public interface Context { User get(String user); // other default methods here } 

Then you can do:

 public class LogUserContext implements Context { @Override public User get(String user) { // materialize user here } } 

And the Action that registers the user could be:

 public class LogUserAction implements Action<LogUserContext> { @Override public void resolve(LogUserContext context) { User user = context.get("theUser"); // log the user in } } 

For a wrapped Action , I would use the WrapperContext context:

 public interface WrapperContext extends Context { Context getWrappedContext(); } 

Implementation:

 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. } 

So now your DummyWrapperAction can be as follows:

 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"); } } 

The idea is to also wrap contexts. You can improve this design by allowing chaining or embellishing contexts, and using abstract classes that will be responsible for tasks common to all contexts.

+2
source share

The interpreter of the GoF-template, the interpreted tree may be somehow supplanted. You can decorate any node and will respond well to your request. One more thing: the action interface should have two methods, at least if the view is external (i.e., made by the user): one to check the validity of the tree (for example, a node requiring two children must have these children) and one to perform a tree.

Edit: here is an example

 import java.util.*; //context can be further complicated if you want visibility blocks (ieyou 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 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 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 implement your Action Script, some stuff left out. // we suppose that the action produces some string // that 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, ie 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")); } } 

Hope this is clear now.

+1
source share

All Articles