调整通用创建的模型和使用模型的服务的类型

huangapple go评论65阅读模式
英文:

Aligning types of generically created model and service that consumes model

问题

好的,以下是翻译好的部分:


好的,这是一个复杂的问题,所以我会尽力将其简化为一个简单的格式。

我尝试做的主要目标是创建一个接口及其几个实现。该接口有一个带有void返回类型和一个参数的方法。

这个单一的参数是一个具有子类的POJO(普通Java对象)。接口的每个实现都会消耗POJO的一个版本:POJO的某些字段由所有实现共享,其他字段则特定于实现。

我目前的架构如下所示:

POJOs

public abstract class ExecutionContext {
    String name;
}

public class FirstExecutionContext extends ExecutionContext {
    int id;
}

public class SecondExecutionContext extends ExecutionContext {
    String description;
}

接口和实现

public interface Executor<T extends ExecutionContext> {
    void execute(T context);
}

public interface FirstExecutor implements Executor<FirstExecutionContext> {
    void execute(FirstExecutionContext context) { ... }
}

public interface SecondExecutor implements Executor<SecondExecutionContext> {
    void execute(SecondExecutionContext context) { ... }
}

那么,问题一:这个结构有意义吗?

接下来的问题出现在我尝试调用这个方法时。问题在于我需要通用地创建ExecutorContext,但是我无论如何都不能想出如何设计这个结构以在不出现错误的情况下链接这些调用。

我开始的调用类看起来有点像这样(只需忽略if条件逻辑,似乎比设置枚举要简单):

public class ExecutorWorker {
    public void buildAndCallExecutor(String name, int id, String description, String type) {
        if (type.equals("FIRST")) {
            FirstExecutor executor = new FirstExecutor();
            FirstExecutionContext executionContext = new FirstExecutionContext();
            executionContext.setName(name);
            executionContext.setId(id);

            executor.execute(executionContext);
        } else if (type.equals("SECOND")) {
            SecondExecutor executor = new SecondExecutor();
            SecondExecutionContext executionContext = new SecondExecutionContext();
            executionContext.setName(name);
            executionContext.setDescription(description);

            executor.execute(executionContext);
        }
    }
}

现在这个代码虽然能工作,但完全忽略了接口的抽象,结果会产生相当多的重复代码(特别是随着ExecutionContext的构建变得更加复杂)。然而,这就是我遇到困境的地方:我如何重构这段代码使其更加通用?

特别是,我一直遇到的问题是如何通用地构建ExecutionContextExecutor,然后使它们配合工作。下面是一个尝试使用通配符的例子(目前只关注重构Executor部分):

public class ExecutorWorker {
    public void buildAndCallExecutor(String name, int id, String description, String type) {
        Executor<? extends ExecutionContext> executor;
        ExecutionContext executionContext;

        if (type.equals("FIRST")) {
            executor = new FirstExecutor();
            FirstExecutionContext firstExecutionContext = new FirstExecutionContext();
            firstExecutionContext.setName(name);
            firstExecutionContext.setId(id);
        } else if (type.equals("SECOND")) {
            executor = new SecondExecutor();
            SecondExecutionContext secondExecutionContext = new SecondExecutionContext();
            secondExecutionContext.setName(name);
            secondExecutionContext.setDescription(description);
        }

        executor.execute(executionContext); // 编译错误:需要类型为 ? extends ExecutionContext 的捕获,提供的是 ExecutionContext
    }
}

我还尝试过使用泛型,但同样无济于事:

public class ExecutorWorker {
    public <T extends ExecutionContext> void buildAndCallExecutor(String name, int id, String description, String type) {
        Executor<T> executor;
        T executionContext;

        if (type.equals("FIRST")) {
            executor = new FirstExecutor(); // 编译错误:需要类型为 Executor<T>,提供的是 FirstExecutor
            FirstExecutionContext firstExecutionContext = new FirstExecutionContext();
            firstExecutionContext.setName(name);
            firstExecutionContext.setId(id);
        } else if (type.equals("SECOND")) {
            executor = new SecondExecutor(); // 编译错误:需要类型为 Executor<T>,提供的是 SecondExecutor
            SecondExecutionContext secondExecutionContext = new SecondExecutionContext();
            secondExecutionContext.setName(name);
            secondExecutionContext.setDescription(description);
        }

        executor.execute(executionContext); 
    }
}

每种方法的根本问题在于,我无法弄清楚如何在ExecutorContext之间对齐泛型 - 对于ExecutionContext的某个子类型,我想构造该子类型的实例,并实例化一个消耗该子类型的Executor子类型。如何使这两个动作对齐?还是我整个方法都错了?


如果你有更多内容需要翻译,请继续提供,我会继续为你进行翻译。

英文:

Okay, this is a complex problem, so I'm gonna try my best to distill it to a simple format.

The overarching goal of what I'm trying to do is to create an interface and a few implementations of said interface. The interface has a single method with a void return and a single argument.

The single argument is a POJO that has subclasses. Each implementation of the interface consumes a version of the POJO: some fields of the POJO are shared by all implementations, other fields are specific to the implementation.

The current architecture of this that I have going looks like this:

The POJOs

public abstract class ExecutionContext {
    String name;
}
public class FirstExecutionContext extends ExecutionContext {
    int id;
}
public class SecondExecutionContext extends ExecutionContext {
    String description;
}

The interface and implementations

public interface Executor&lt;T extends ExecutionContext&gt; {
    void execute(T context);
}
public interface FirstExecutor implements Executor&lt;FirstExecutionContext&gt; {
    void execute(FirstExecutionContext context) { ... }
}
public interface SecondExecutor implements Executor&lt;SecondExecutionContext&gt; {
    void execute(SecondExecutionContext context) { ... }
}

So, question one: does this structure make sense?

The next problem comes when I try to actually call this method. The problem here is that I need to generically create both the Executor and the Context, and I cannot for the life of me figure out how to architect this to chain the calls without errors of some sort.

I started with a calling class that looks something like this (just ignore the if conditional logic, seemed simpler than setting up an enum)

public class ExecutorWorker {
    public void buildAndCallExecutor(String name, int id, String description, String type) {
        if (type.equals(&quot;FIRST&quot;)) {
            FirstExecutor executor = new FirstExecutor();
            FirstExecutionContext executionContext = new FirstExecutionContext();
            executionContext.setName(name);
            executionContext.setId(id);

            executor.execute(executionContext);
        } else if (type.equals(&quot;SECOND&quot;)) {
            SecondExecutor executor = new SecondExecutor();
            SecondExecutionContext executionContext = new SecondExecutionContext();
            executionContext.setName(name);
            executionContext.setDescription(description);

            executor.execute(executionContext);
        }
    }
}

Now this works, but completely ignores the abstraction of the interfaces and ends up with a fair amount of duplicated code (especially as the construction of ExecutionContext becomes more complex). However, this is where I've hit a wall: how can I refactor this code to be more generic?

In particular, the issue I consistently run into is generically constructing both the ExecutionContext and the Executor, then having them get along. Here's an attempt with wildcards (just focusing on refactoring the Executor for now):

public class ExecutorWorker {
    public void buildAndCallExecutor(String name, int id, String description, String type) {
        Executor&lt;? extends ExecutionContext&gt; executor;
        ExecutionContext executionContext;

        if (type.equals(&quot;FIRST&quot;)) {
            executor = new FirstExecutor();
            FirstExecutionContext firstExecutionContext = new FirstExecutionContext();
            firstExecutionContext.setName(name);
            firstExecutionContext.setId(id);
        } else if (type.equals(&quot;SECOND&quot;)) {
            executor = new SecondExecutor();
            SecondExecutionContext secondExecutionContext = new SecondExecutionContext();
            secondExecutionContext.setName(name);
            secondExecutionContext.setDescription(description);
        }

        executor.execute(executionContext); // compile error: required: capture of ? extends ExecutionContext, provided: ExecutionContext
    }
}

I also tried this with generics, but also to no avail:

public class ExecutorWorker {
    public &lt;T extends ExecutionContext&gt; void buildAndCallExecutor(String name, int id, String description, String type) {
        Executor&lt;T&gt; executor;
        T executionContext;

        if (type.equals(&quot;FIRST&quot;)) {
            executor = new FirstExecutor(); // compile error: required type: Executor&lt;T&gt;, provided: FirstExecutor
            FirstExecutionContext firstExecutionContext = new FirstExecutionContext();
            firstExecutionContext.setName(name);
            firstExecutionContext.setId(id);
        } else if (type.equals(&quot;SECOND&quot;)) {
            executor = new SecondExecutor(); // compile error: required type: Executor&lt;T&gt;, provided: SecondExecutor
            SecondExecutionContext secondExecutionContext = new SecondExecutionContext();
            secondExecutionContext.setName(name);
            secondExecutionContext.setDescription(description);
        }

        executor.execute(executionContext); 
    }
}

The fundamental issue with each approach is that I can't figure out how to align the generics between the Executor and the Context - for some subtype of ExecutionContext, I want to construct an instance of that subtype and instantiate a subtype of Executor that consumes that subtype. How can I align these two actions? Or am I going about this all wrong?

答案1

得分: 1

以下是您要翻译的内容:

您可以使用Executor的重新实现,允许从执行器中调用所有的setter方法,例如使用一个实现这些setter方法的接口。

public interface setter<T extends ExecutionContext> {
    void execute(T context);
    public T setDescritpion(String description);
    public T setId(int id);
}

public abstract class ExecutionContext implements setter {
    String name;
}

现在,您将被迫在非抽象子类中实现缺失的方法,如果子类没有特定的字段,在特定的setter方法中不做任何操作。

然后,我认为一个工厂类会很好,这样您可以在运行时获取所需的上下文。

public class ExecutionContextFactory {
    public static ExecutionContext getExecutionContext(TypeEnum type) {
        switch (type) {
            case FIRST: return new FirstExecutionContext();
            case SECOND: return new SecondExecutionContext();
        }
    }
}

还有一个几乎相同的工厂类用于执行器。

现在,您可以简单地执行以下操作:

public void buildAndCallExecutor(String name, int id, String description, String type) {
    ExecutorFactory
        .getExecutor(type).execute(
             ExecutionContextFactory
                .getExecutionContext(type)
                .setName(name)
                .setDescription(description)
                .setId(id)
        );
}

我认为它应该可以工作,尽管没有经过测试。

另一件您可以做的事情是使用反射,您可以在超类中实现一些实用方法,以便访问字段和方法,并且可以通过字段的名称简单地设置这些字段。有许多可能性,这实际上取决于应用程序的上下文。

英文:

You could use a reImplementation of Executor that allows to call all setters from executor, for example using an interface that implements those setters

public interface setter&lt;T extends ExecutionContext&gt; {
    void execute(T context);
    public T setDescritpion(String description);
    public T setId(int id);
}

public abstract class ExecutionContext implements setter{
    String name;
}

Now you will be foced to implement the missing methods in the non-abstract childs, if a child doesent have a particular field just do nothing in the specific setter.

Then I think a factory would be nice so you can get the context you need at runtime

public class ExecutionContextFactory {
    public static ExecutionContext getExecutionContext(TypeEnum type) {
        switch (type){
            case FIRST: return new FirstExecutionContext ();
            case SECOND: return new SecondExecutionContext ();
        }
    }
}

and a nearly identical one for the executors.

Now you can simply do

public void buildAndCallExecutor(String name, int id, String description, String type) {
        ExecutorFactory
            .getExecutor(type).execute(
                 ExecutionContextFactory
                    .getExecutionContext(type)
                    .setName(name)
                    .setDescription(description)
                    .setId(id)
            )
}

I think it should work, not tested though.

Another thing you could do is reflection, you could implement in the superclasses some utility methods that allows to acces fields and methods and set those fields using simply the name of the Fields you want. There are many possibilities, it really depends on the application context

答案2

得分: 1

> "buildAndCallExecutor(…)"…

任何时候你在方法名中看到 "And",那就是一个重构该方法为两个更紧密方法的信号。

我的建议是将 "buildAndCallExecutor(…)" 的两个不同方面分离为两个独立的方法:(1) build() 和 (2) execute()

> "…应该有一种方法来声明接口类型的变量…"

这里有一个更紧密的实现演示

…
build(String name, U unambiguous, Type whichType) {

    /* 声明接口类型的变量 */
    Executioner&lt; U &gt; executioner;
    
    switch(whichType) { 
        case FIRST: 
            /* 分配变量 */
            executioner = new Executioner&lt; &gt;(new FirstExecutor&lt; U &gt;(), new FirstExecutionContext&lt; U &gt;(name, unambiguous));
            break;
        case SECOND:
            executioner = new Executioner&lt; &gt;(new SecondExecutor&lt; U &gt;(), new SecondExecutionContext&lt; U &gt;(name, unambiguous));
            break;
         …
    }
}
…

> "…在 if 块外调用该方法…"

我演示了 一个更简洁的实现方式…

public class ExecutorWorker {

    public &lt; U &gt; void execute(Set&lt; Executioner&lt; U &gt; &gt; executioners) {
        
        executioners.stream().forEach(Executioner::execute);
    }
}

然后你可以分别 build()call()

…
Builder builder = new Builder();

ExecutorWorker worker = new ExecutorWorker();
    
Set&lt; Executioner&lt; Integer &gt; &gt; work = builder.build(666);

worker.execute(work);
…

对此的一个实验性运行会打印出…

SecondExecutor&lt; S &gt; { } - 执行 SecondExecutionContext&lt; D &gt; { name: SECOND, description: 666 }
FirstExecutor&lt; F &gt; { } - 执行 FirstExecutionContext&lt; U &gt; { name: FIRST, id: 666 }

            实验成功

> "…好了,这是一个复杂的问题…"

为了好玩,我进行了额外的实验,以测试上述设计可以扩展到的复杂度程度。

我演示了如何将一个复杂的 ExecutionContext 提供给一个执行其他更简单的 Executors复杂 Executor 的工作实现…

…    
Set&lt; Executioner&lt; String &gt; &gt; woyk = builder.build("K.I.S.S.");
        
Executor&lt; ExecutionContext&lt; Set&lt; Executioner&lt; String &gt; &gt; &gt;, Set&lt; Executioner&lt; String &gt; &gt; &gt; executor = new ComplexWorker&lt; &gt; ();
        
ExecutionContext&lt; Set&lt; Executioner&lt; String &gt; &gt; &gt; complexWoyk = new ComplexExecutionCtx&lt; &gt;("It's complicated", woyk);
        
executor.execute(complexWoyk);
…

这会打印出…

…
ComplexWorker&lt; CW &gt;{ } - 执行 ComplexExecutionCtx&lt; E, CTX &gt; { name: It's complicated, complex: [executable.Executioner@27f674d, executable.Executioner@1d251891] }
FirstExecutor&lt; F &gt;{ } - 执行 FirstExecutionCtx&lt; U &gt; { name: FIRST, id: K.I.S.S. }
SecondExecutor&lt; S &gt;{ } - 执行 SecondExecutionCtx&lt; D &gt; { name: SECOND, description: K.I.S.S. }
…
英文:

> „&hellip;buildAndCallExecutor(…)…“

Anytime you have a method with And in its name, that's a cue to refactor that method into two more cohesive methods.

My advice is to spin off the two different aspects of buildAndCallExecutor(…) into two separate methods: (1) build() and (2) execute().

> „…There should be a way to declare the interface-typed variable…

Here's a working demo of a more cohesive way to do that

…
build( String name, U unambiguous, Type whichType ) {

    /* declare the interface-typed variable */
    Executioner&lt; U &gt; executioner;
    
    switch( whichType ) { 
        case FIRST: 
            /* assign the variable */
            executioner = new Executioner&lt; &gt;( new FirstExecutor&lt; U &gt;( ), new FirstExecutionContext&lt; U &gt;( name, unambiguous ) );
            break;
        case SECOND:
            executioner = new Executioner&lt; &gt;( new SecondExecutor&lt; U &gt;( ), new SecondExecutionContext&lt; U &gt;( name, unambiguous ) );
            break;
         …
    }
}
…

> „…call the method outside the if block…

I demonstrate a simple, more cohesive implementation of that as…

public class ExecutorWorker {

    public &lt; U &gt; void execute( Set&lt; Executioner&lt; U &gt; &gt; executioners ) {
        
        executioners.stream( ).forEach( Executioner::execute );
    }
}

Then you build() and call() separately…

…
Builder builder = new Builder( );

ExecutorWorker worker = new ExecutorWorker( );
    
Set&lt; Executioner&lt; Integer &gt; &gt; work = builder.build( 666 );

worker.execute( work );
…

An experimental run of that prints out…

SecondExecutor&lt; S &gt; { } - executing SecondExecutionContext&lt; D &gt; { name: SECOND, description: 666 }
FirstExecutor&lt; F &gt; { } - executing FirstExecutionContext&lt; U &gt; { name: FIRST, id: 666 }

            EXPERIMENT SUCCESSFUL

> „…Okay, this is a complex problem…

For fun, I did this additional experiment to test out the degree of complexity the above design can be extended to.

I demonstrate a working implementation of giving a complex ExecutionContext to a complex Executor that executes() other simpler Executors

…    
Set&lt; Executioner&lt; String &gt; &gt; woyk = builder.build( &quot;K.I.S.S.&quot; );
        
Executor&lt; ExecutionContext&lt; Set&lt; Executioner&lt; String &gt; &gt; &gt;, Set&lt; Executioner&lt; String &gt; &gt; &gt; executor = new ComplexWorker&lt; &gt; ( );
        
ExecutionContext&lt; Set&lt; Executioner&lt; String &gt; &gt; &gt; complexWoyk = new ComplexExecutionCtx&lt; &gt;(&quot;It&#39;s complicated&quot;, woyk );
        
executor.execute( complexWoyk );
…

That prints…

…
ComplexWorker&lt; CW &gt;{ } - executing ComplexExecutionCtx&lt; E, CTX &gt; { name: It&#39;s complicated, complex: [executable.Executioner@27f674d, executable.Executioner@1d251891] }
FirstExecutor&lt; F &gt;{ } - executing FirstExecutionCtx&lt; U &gt; { name: FIRST, id: K.I.S.S. }
SecondExecutor&lt; S &gt;{ } - executing SecondExecutionCtx&lt; D &gt; { name: SECOND, description: K.I.S.S. }
…

答案3

得分: 0

以下是您要翻译的内容:

我会这样做,如果是我的话…

public class ExecutorWorker {

    public void buildAndCallExecutor(String name, int id, String description, String type) {

        if (type.equals("FIRST")) {
            Executor<FirstExecutionContext> executor = new FirstExecutor();
            FirstExecutionContext firstExecutionContext = new FirstExecutionContext();
            firstExecutionContext.setName(name);
            firstExecutionContext.setId(id);
            
            executor.execute(firstExecutionContext);
            
        } else if (type.equals("SECOND")) {
            Executor<SecondExecutionContext> executor = new SecondExecutor();
            SecondExecutionContext secondExecutionContext = new SecondExecutionContext();
            secondExecutionContext.setName(name);
            secondExecutionContext.setDescription(description);

            executor.execute(secondExecutionContext);
        }
    }
}
英文:

I would do it like this if it were me&hellip;

public class ExecutorWorker {
    
    public void buildAndCallExecutor( String name, int id, String description, String type ) {

        if ( type.equals( &quot;FIRST&quot; ) ) {
            Executor&lt; FirstExecutionContext &gt; executor = new FirstExecutor( );
            FirstExecutionContext firstExecutionContext = new FirstExecutionContext( );
            firstExecutionContext.setName( name );
            firstExecutionContext.setId( id );
            
            executor.execute( firstExecutionContext );
            
        } else if ( type.equals( &quot;SECOND&quot; ) ) {
            Executor&lt; SecondExecutionContext &gt; executor = new SecondExecutor( );
            SecondExecutionContext secondExecutionContext = new SecondExecutionContext( );
            secondExecutionContext.setName( name );
            secondExecutionContext.setDescription( description );

            executor.execute( secondExecutionContext );
        }
    }
}

huangapple
  • 本文由 发表于 2020年9月10日 04:49:59
  • 转载请务必保留本文链接:https://go.coder-hub.com/63819356.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定