简体   繁体   中英

Implementing Fluent Builder Pattern for nested clases

I am trying to understand fluent builder patterns and in my case, I have an object that has child objects too. Most of the online examples are based on a single object. There are some examples for nested objects but they all are different. So I am here to find out what is the correct way to implement a fluent builder pattern for my case.

public class Controller
{
  private Robot _robot;
  private Table _table;              
  public Controller() {}
  public void Place() {}
  public void Action() {}
}

public class Robot
{
  private Point _currentLocation;
  public Robot(Point point) {}
  public void Move(){}
}

public class Table
{
  private int _width;
  private int _length;
  public Table(int width, int length){}
  public bool IsValidLocation(Point point){}
}

------------ Edited ----------

I have approached implementing this as below. Any expert advice on the below approach?

public class TableBuilder
{
    private readonly Table _table = new();

    public Table Build() => _table;

    public TableBuilder WithWidth(int width)
    {
        _table.Width = width;
        return this;
    }

    public TableBuilder WithLength(int length)
    {
        _table.Length = length;
        return this;
    }
} 

public class RobotBuilder
{
    private readonly Robot _robot = new();

    public Robot Build() => _robot;

    public RobotBuilder WithLocation(Point point)
    {
        _robot.CurrentLocation = point;
        return this;
    }
}

public class ControllerBuilder
{
    private readonly Controller _controller = new();
    private readonly TableBuilder _tableBuilder = new();
    private readonly RobotBuilder _robotBuilder = new();


    public Controller Build() => _controller;

    public ControllerBuilder WithTable(int width, int length)
    {
        _controller.Table = _tableBuilder.WithWidth(width)
                                        .WithLength(length)
                                        .Build();
        return this;
    }

    public ControllerBuilder WithRobot(Point point)
    {
        _controller.Robot = _robotBuilder.WithLocation(point)
                                        .Build();
        return this;
    }
}

Finally I used below code to declare the Controller

var _controller = new ControllerBuilder()
                    .WithRobot(new Point(x, y))
                    .WithTable(5, 5)
                    .Build();

Fluent pattern works as class returns the type of class. So we can return this .

Let me show an example. ControllerBuilder would look like this:

public class ControllerBuilder
{
    Controller _controller = new();

    public ControllerBuilder WithRobot(Point point) 
    {

        return this;
    }

    public ControllerBuilder WithTable(int width, int length)
    {
        // your code here
        return this;
    }

    public ControllerBuilder Build() 
    {
        // your code here
        return _controller;
    }
}

and Controller class:

public class Controller
{
    private Robot _robot;
    private Table _table;
    public Controller() { }
    public void Place() { }
    public void Action() { }
}

and other classes:

public class Robot
{
    private Point _currentLocation;
    public Robot(Point point) { }
    public void Move() { }
}

public class Table
{
    private int _width;
    private int _length;
    public Table(int width, int length) { }
    public bool IsValidLocation(Point point) { return true; }
}

public class Point { }

And then you can chain your methods like this:

var result = new ControllerBuilder()
    .WithRobot(new Point())
    .WithTable(1, 2)
    .Build();

What I usually do in this cases is to use nested builders with lambdas, which are useful when the nested classes have many parameters needed at construction time:

public class Controller
{
    private readonly Robot _robot;
    private readonly Table _table;

    public Controller(Robot robot, Table table)
    {
        _robot = robot;
        _table = table;
    }

    public void Place()
    {
    }

    public void Action()
    {
    }
}

public class Robot
{
    private Point _currentLocation;

    public Robot(Point point)
    {
    }

    public void Move()
    {
    }
}

public class Table
{
    private readonly int _width;
    private readonly int _length;

    public Table(int width, int length)
    {
        _width = width;
        _length = length;
    }


    public bool IsValidLocation(Point point)
    {
        return false;
    }
}

public class TableBuilder
{
    private int _width;
    private int _length;
    public TableBuilder WithWidth(int width)
    {
        _width = width;
        return this;
    }

    public TableBuilder WithLength(int length)
    {
        _length = length;
        return this;
    }

    public Table Build() => new Table(_width, _length);
}

public class RobotBuilder
{
    private Point _location;

    public Robot Build() => new Robot(_location);

    public RobotBuilder WithLocation(Point location)
    {
        _location = location;
        return this;
    }
}

public class ControllerBuilder
{
    private Robot _robot;
    private Table _table;


    public Controller Build() => new Controller(_robot, _table);

    public ControllerBuilder WithRobot(Func<RobotBuilder, RobotBuilder> builderDirector)
    {
        _robot = builderDirector.Invoke(new RobotBuilder()).Build();
        return this;
    }

    public ControllerBuilder WithTable(Func<TableBuilder, TableBuilder> builderDirector)
    {
        _table = builderDirector(new TableBuilder()).Build();
        return this;
    }
}

public class Program
{
    public static void Main()
    {
        var controller = new ControllerBuilder()
            .WithTable(builder => builder.WithLength(1).WithWidth(2))
            .WithRobot(builder => builder.WithLocation(new Point(1,1)))
            .Build();
    }
}

To ease the maintenance of a fluent API I created a tool, Java::Geci.

Using this tool you create a simple class with methods that set the values and the tool will generate a wrapper class for chaining and all the interfaces that restrict the order of the methods you can apply one after the other.

You can define your fluent API grammar over the methods using fluent API (well, eat your own dog food), but it can also be specified in a String using a "regular expression" over the methods instead of characters.

Obviously, I am a bit biased as I created this tool (Apache 2.0 license), but here it is:

https://github.com/verhas/javageci/blob/master/FLUENT.md

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM