解决Java接口中方法参数的歧义

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

Resolve Java Interface ambiguity in method parameters

问题

I have a tree of boolean operations from a boolean expression that I have to run in Java. The tree would be something like this:

boolean b = AND(isNumberEven("4"), AND(isNumberOdd("7"), isNumberEven("6")));

The problem is that I need to write the interfaces, since in my company we could create many classes like And, Or, isSomething, hasInArray (it could be any property needed by the other devs). We generally have two types of nodes: the logic ones, which have two branches, and the property checking ones, which are leafs of the tree.

What I tried to do below is something I think it's really wrong in Java, please help me doing it right.

My idea was to create two Interfaces, one for Logic operators and another for Property checking. The property checker needs to evaluate its property from the input, while the logic operator needs to evaluate the two branches and then compute something with the two results (like an AND or an OR, we don't care about the NOT operator for now). I was thinking of doing these interfaces

class PropertyCheckingInput {
   String s; // dummy property, this object is the thing we want to verify the property on (example, if this string is an even number)

   PropertyCheckingInput(...){...} // constructor for all params
}

interface PropertyChecking {
   boolean fire(PropertyCheckingInput i);
}

class LogicOperatorInput<T extends LogicOperator or PropertyChecking, Ti extends LogicOperatorInput or PropertyCheckingInput> {
    T left;
    Ti leftInput;
    T right;
    Ti rightInput;

    // probably the type of leftInput should act accordingly to the type of left, but I don't know how to enforce this in Java with generics

    LogicOperatorInput(...){...} // constructor for all params
}

interface LogicOperator {
   boolean fire(LogicOperatorInput i); 
}

In this case, if I want to implement something like the AND I could do it like

class And implements LogicOperator {
    boolean fire(LogicOperatorInput i) {
        i.left.fire(i.leftInput) && i.right.fire(i.rightInput);
    }

    And() {}

    public static void main(String[] args) {
        // expression: isNumberEven("4") AND isNumberOdd("7") AND isNumberEven("6")
        boolean b = new And().fire(new LogicOperatorInput(
            new isNumberEven(), 
            new PropertyCheckingInput("4"), 
            new And(), 
            new LogicOperatorInput(
                new isNumberOdd(), 
                new PropertyCheckingInput("7"), 
                new isNumberEven(), 
                new PropertyCheckingInput("6"))
        ));

        System.out.println(b);
    }
}

For each branch, I execute the function fire of both left and right with their input and care only about their result. Then if I want to create a boolean expression I concatenate the various nodes and inputs.

The compilers of course tells me this is wrong, because it cannot infer the correct type of the fire( ) functions.

I'm used to write in Javascript, which allows this type of stuff because it doesn't check what you're trying to do. Is there a way to do this in Java (I tried using some generics or abstract classes, but didn't work), or better yet a correct way to solve this problem (where we have a binary expression written as a binary tree and we want to resolve it by calling the class associated with each node)?

英文:

I have a tree of boolean operations from a boolean expression that I have to run in Java. The tree would be something like this:
解决Java接口中方法参数的歧义
And I would need to run it in Java iteratively so that it returns me a boolean value.

boolean b = AND(isNumberEven(&quot;4&quot;), AND(isNumberOdd(&quot;7&quot;), isNumberEven(&quot;6&quot;))

The problem is that I need to write the interfaces, since in my company we could create many classes like And, Or, isSomething, hasInArray (it could be any property needed by the other devs). We generally have two types of nodes: the logic ones, which have two branches, and the property checking ones, which are leafs of the tree.

What I tried to do below is something I think it's really wrong in Java, please help me doing it right.

My idea was to create two Interfaces, one for Logic operators and another for Property checking. The property checker needs to evaluate its property from the input, while the logic operator needs to evaluate the two branches and then compute something with the two results (like an AND or an OR, we don't care about the NOT operator for now). I was thinking of doing these interfaces

class PropertyCheckingInput {
   String s; // dummy property, this object is the thing we want to verify the property on (example, if this string is an even number)

   PropertyCheckingInput(...){...} // constructor for all params
}

interface PropertyChecking {
   boolean fire(PropertyCheckingInput i);
}

class LogicOperatorInput&lt;
   T extends LogicOperator or PropertyChecking, // it can be one or the other
   Ti extends LogicOperatorInput or PropertyCheckingInput
&gt; {
    T left;
    Ti leftInput;
    T right;
    Ti rightInput;

    // probabily the type of leftInput should act accordingly to the type of left, but I don&#39;t know how to enforce this in Java with generics

    LogicOperatorInput(...){...} // constructor for all params
}

interface LogicOperator{
   boolean fire(LogicOperatorInput i); 
}

In this case, if I want to implement something like the AND I could do it like

class And implements LogicOperator {
    boolean fire(LogicOperatorInput i) {
        i.left.fire(i.leftInput) &amp;&amp; i.right.fire(i.rightInput);
    }

    And() {}

    public static void main(String[] args) {
        // expression: isNumberEven(&quot;4&quot;) AND isNumberOdd(&quot;7&quot;) AND isNumberEven(&quot;6&quot;)
        boolean b = new And().fire(new LogicOperatorInput(
            new isNumberEven(), 
            new PropertyCheckingInput(&quot;4&quot;), 
            new And(), 
            new LogicOperatorInput(
                new isNumberOdd(), 
                new PropertyCheckingInput(&quot;7&quot;), 
                new isNumberEven(), 
                new PropertyCheckingInput(&quot;6&quot;))
        ));

        System.out.println(b);
    }
}

For each branch, I execute the function fire of both left and right with their input and care only about their result. Then if I want to create a boolean expression I concatenate the various nodes and inputs.

The compilers of course tells me this is wrong, because it cannot infer the correct type of the fire( ) functions.

I'm used to write in Javascript, which allows this type of stuff because it doesn't check what you're trying to do. Is there a way to do this in Java (I tried using some generics or abstract classes, but didn't work), or better yet a correct way to solve this problem (where we have a binary expression written as a binary tree and we want to resolve it by calling the class associated with each node)?

答案1

得分: 1

你可能正在过度复杂化这个问题。如果你想要一个像图片中那样的表达树,就创建一个表达树即可。你不需要区分“操作符”和“输入”。

// 这表示一个树节点
interface BooleanExpression {
    // 所有节点都应实现此方法以表示它们的计算方式
    boolean evaluate();
}

// 一个节点可以是以下任何类型之一
record IsNumberEven(int number) implements BooleanExpression {
    @Override
    public boolean evaluate() {
        return number() % 2 == 0;
    }
}

record IsNumberOdd(int number) implements BooleanExpression {
    @Override
    public boolean evaluate() {
        return number() % 2 == 1;
    }
}

record And(BooleanExpression left, BooleanExpression right) implements BooleanExpression {
    @Override
    public boolean evaluate() {
        return left().evaluate() && right().evaluate();
    }
}

用法:

boolean result = new And(
        new IsNumberEven(4),
        new And(
                new IsNumberOdd(7),
                new IsNumberEven(6)
        )
).evaluate();

如果你真的想将 IsNumberOdd/IsNumberEven 节点的“输入”分开,你可以这样做。只需将 BooleanExpression 泛化为 Expression<T>

interface Expression<T> {
    T evaluate();
}

record Constant<T>(T value) implements Expression<T> {
    @Override
    public T evaluate() {
        return value();
    }
}

record IsNumberEven(Expression<Integer> number) implements Expression<Boolean> {
    @Override
    public Boolean evaluate() {
        return number().evaluate() % 2 == 0;
    }
}

record IsNumberOdd(Expression<Integer> number) implements Expression<Boolean> {
    @Override
    public Boolean evaluate() {
        return number().evaluate() % 2 == 1;
    }
}

record And(Expression<Boolean> left, Expression<Boolean> right) implements Expression<Boolean> {
    @Override
    public Boolean evaluate() {
        return left().evaluate() && right().evaluate();
    }
}

用法:

boolean result = new And(
        new IsNumberEven(new Constant<>(4)),
        new And(
                new IsNumberOdd(new Constant<>(7)),
                new IsNumberEven(new Constant<>(6))
        )
).evaluate();
英文:

You might be overcomplicating this. If you want an expression tree like the one in the image, make an expression tree. You don't need to distinguish between "operators" and "input"s.

// this represents a tree &quot;node&quot;
interface BooleanExpression {
    // all nodes should implement this to represent how they are evaluated
    boolean evaluate();
}

// a node can be any of these types
record IsNumberEven(int number) implements BooleanExpression {
    @Override
    public boolean evaluate() {
        return number() % 2 == 0;
    }
}

record IsNumberOdd(int number) implements BooleanExpression {
    @Override
    public boolean evaluate() {
        return number() % 2 == 1;
    }
}

record And(BooleanExpression left, BooleanExpression right) implements BooleanExpression {
    @Override
    public boolean evaluate() {
        return left().evaluate() &amp;&amp; right().evaluate();
    }
}

Usage:

boolean result = new And(
        new IsNumberEven(4),
        new And(
                new IsNumberOdd(7),
                new IsNumberEven(6)
        )
).evaluate();

If you really want to separate out the "inputs" of the IsNumberOdd/IsNumberEven nodes, you can. Just generalise BooleanExpression to Expression&lt;T&gt;

interface Expression&lt;T&gt; {
    T evaluate();
}

record Constant&lt;T&gt;(T value) implements Expression&lt;T&gt; {
    @Override
    public T evaluate() {
        return value();
    }
}

record IsNumberEven(Expression&lt;Integer&gt; number) implements Expression&lt;Boolean&gt; {
    @Override
    public Boolean evaluate() {
        return number().evaluate() % 2 == 0;
    }
}

record IsNumberOdd(Expression&lt;Integer&gt; number) implements Expression&lt;Boolean&gt; {
    @Override
    public Boolean evaluate() {
        return number().evaluate() % 2 == 1;
    }
}

record And(Expression&lt;Boolean&gt; left, Expression&lt;Boolean&gt; right) implements Expression&lt;Boolean&gt; {
    @Override
    public Boolean evaluate() {
        return left().evaluate() &amp;&amp; right().evaluate();
    }
}

Usage:

boolean result = new And(
        new IsNumberEven(new Constant&lt;&gt;(4)),
        new And(
                new IsNumberOdd(new Constant&lt;&gt;(7)),
                new IsNumberEven(new Constant&lt;&gt;(6))
        )
).evaluate();

huangapple
  • 本文由 发表于 2023年7月7日 04:29:39
  • 转载请务必保留本文链接:https://go.coder-hub.com/76632348.html
匿名

发表评论

匿名网友

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

确定