AdjustingForInflation
AdjustingForInflation

Reputation: 1611

Fluent Builder/DSL in Java by Example

I have a Java app where I need to represent a simple arithmetic expression as a tree. The operations I will be supporting include unary (!, unary negation, etc.), binary (+, -, *, /) and even tertiary (custom functions) operations. As such I've selected the DefaultMutableTreeNode as the structure to represents my math tree, because these trees can have 0+ child nodes.

For instance, the expression: 3 + 4 * someFunc(2, 6, 9) would be represented as the following tree:

        *
       / \
      /   \
     /     \
    +     someFunc
   / \       /|\
  /   \     / | \
 3     4    2 6  9

This is because of operator precedence (multiplication trumps addition and the someFunc method must resolve to a value before it can be an argument to the root multiplication).

Anyways, I need a way to programmatically-build up these trees easily, and would like to use a Java DSL (Fluent Builder pattern) to accomplish this.

For the nodes:

public abstract class MathNode {

}

public abstract class OperatorNode extends MathNode {

}

public class Addition extends OperatorNode {
    // Same for Multiplication, SomeFunc, and every other supported operator.
}

public class Number extends MathNode {
    // 3, 4, 2, 6, 9, etc.
}

Then, I might be able to have a fluent builder/DSL like so:

// Uses DefaultMutableTreeNode for internal structure.
MathTreeBuilder treeBuilder = new MathTreeBuilder();

treeBuilder.multiply()
    .add(3,4)
    .someFunc(2,6,9);

MathTree tree = treeBuilder.build();

However I'm having a tough time seeing the "forest" through the "trees" here. Do I need a builder at all, or can I just accomplish the same by having the DSL be apart of MathTree itself? What would the DSL look like - am I on track or have I misunderstood the use of the fluent builder pattern? And, most importantly, in each of the different DSL methods (add(), multiply(), someFunc(), etc.), how do I actually go about modifying the DefaultMutableTreeNode to accurately represent the tree?

Upvotes: 1

Views: 2091

Answers (3)

clemp6r
clemp6r

Reputation: 3723

I'm not sure a builder pattern would be relevant in this case. You're building a tree, and builders are not very suitable for building trees. I would use instead some static factory methods instead:

Math.multiply(
   Math.add(3, 4),
   Math.someFunc(2, 6, 9)
 ); 

That would be equivalent to:

new Multiply(new Add(3, 4), new SomeFunc(2, 6, 9));

PS: don't use DefaultMutableTreeNode: it is a Swing framework class, and you don't want to depend on a UI framework for making a mathematic expression framework. And worst, it is mutable. Create your own structure.

Upvotes: 0

Eric Stein
Eric Stein

Reputation: 13682

If you wanted to try a non-fluent solution, this might work:

interface Expression {
    public Node asTree();
}

public final class Value implements Expression {
    public Value(final int value) { .. }
    public Node asTree();
}

public final class Negate implements Expression {
    public Negate(final Expression e) { .. }
    public Node asTree();
}

public final class Multiply implements Expression {
    public Multiply(final Expression e1, final Expression e2) { .. }
    public Node asTree();
}

It doesn't read nearly as pretty as the fluent option, but if you're building piece-by-piece it should be easier to work with. I think it's also easy to unit test.

Upvotes: 1

Marko Topolnik
Marko Topolnik

Reputation: 200206

I think you should go about this the way an XML builder would work:

  • yes, use fluent API;
  • no need to strictly follow the Builder pattern: you don't have to finish building with an "export" into an immutable object;
  • internally to your builder, maintain the tree and your current position in it: the node which currently receives children;
  • have an explicit end() method which makes the current node's parent the new current node.

Then you can build your 3 + 4 * someFunc(2, 6, 9) as follows:

new MathBuilder()
.add()
  .const(3)
  .multiply()
     .const(4)
     .someFunc()
        .const(2)
        .const(6)
        .const(9)
     .end()
 .get();

All the trailing end() calls may be omitted. I have left one in to serve as an example.

Upvotes: 2

Related Questions