donderdag 21 februari 2013

Fluent builder pattern

In this article we'll see how we can construct an object in a fluent way. This pattern is called Fluent Builder or Fluent interface.

In the previous article I've refactored a class that does too much, into a class that does one thing (Single Responsibility Principle). Now we take a look at how we can improve a specific part of the code with the Fluent Builder pattern.

This is the class we start with:


public class SourceXDocumentCreator
{
   public XDocument AddMetadata(XDocument xdoc, string metadata)
   {
      // Add metadata to XDocument
      // return XDocument
   }

   public XDocument AddHeader(XDocument xdoc, string title)
   {
      // Add header with title  to XDocument
      // return XDocument
   }

   public XDocument AddLogo(XDocument xdoc, string name,
                            double width, double height)
   {
      // Add logo to XDocument
      // return XDocument
   }

   // etc.
}

If we call this class to create a SourceXDocument, it could look like this:

var xdoc = GetXDocumentById(1);
var creator = new SourceXDocumentCreator();
xdoc = creator.AddMetadata(xdoc, metadata);
xdoc = creator.AddHeader(xdoc, "This is the title");
xdoc = creator.AddLogo(xdoc, "name", 7.2, 4.3);

As you can see every first variable of each method call is xdoc. If we need that variable in each method, we can just as well add it to the constructor of SourceXDocumentCreator:

public class SourceXDocumentCreator
{
   private XDocument _xdoc;

   public SourceXDocumentCreator(XDocument xdoc)
   {
      _xdoc = xdoc;
   }

   public XDocument AddMetadata(string metadata)
   {
      // Add metadata to _xdoc
      // return _xdoc
   }

   public XDocument AddHeader(string title)
   {
      // Add header with title to _xdoc
      // return _xdoc
   }

   public XDocument AddLogo(string name, double width,
                            double height)
   {
      // Add logo to _xdoc
      // return _xdoc
   }

   // etc.
}

Now we have the private variable _xdoc, it's not neccesary anymore to pass xdoc to each and every method. And we can use SourceXDocumentCreator like this:

var xdoc = GetXDocumentById(1);
var creator = new SourceXDocumentCreator(xdoc);
xdoc = creator.AddMetadata(metadata);
xdoc = creator.AddHeader("This is the title");
xdoc = creator.AddLogo("name", 7.2, 4.3);

This looks a lot cleaner, but still we can reduce the amount of code we need. We actually only need the final xdoc, and not semi-finished product:

var xdoc GetXDocumentById(1);
var creator = new SourceXDocumentCreator(xdoc);
creator.AddMetadata(metadata);
creator.AddHeader("This is the title");
xdoc = creator.AddLogo("name", 7.2, 4.3);

And now only the creator variable is redundant. But we can reduce that too, like this:

var xdoc GetXDocumentById(1);
var creator = new SourceXDocumentCreator(xdoc)
   .AddMetadata(metadata)
   .AddHeader("This is the title")
   .AddLogo("name", 7.2, 4.3)
   .Build();

For the code above to work, we have to change our SourceXDocumentCreator class, so each of the 'add'-methods returns the current instance of SourceXDocumentCreator:


public class SourceXDocumentCreator
{
   private XDocument _xdoc;


   public SourceXDocumentCreator(XDocument xdoc)
   {
      _xdoc = xdoc;
   }

   public SourceXDocumentCreator AddMetadata(string metadata)
   {
      // Add metadata to _xdoc
      return this;
   }

   public SourceXDocumentCreator AddHeader(string title)
   {
      // Add header with title to _xdoc
      return this;
   }

   public SourceXDocumentCreator AddLogo(string name,
                                         double width,
                                         double height)
   {
      // Add logo to _xdoc
      return this;
   }

   public XDocument Build()
   {
      return _xdoc;
   }

}

Because each Add-method returns and instance of SourceXDocumentCreator, we can call another Add-method on it again:


var creator = new SourceXDocumentCreator(xdoc)
   .AddMetadata(metadata)
   .AddHeader("This is the title");

And we can continue to call some other methoda:

creator = creator
   .AddLogo("name", 7.2, 4.3)
   .WithSomethingElse()
   .AndAnotherThing()
   .ThisToo();

But how do we get the final XDocument of each method returns an instance of SourceXDocumentCreator? for this, we add a public Build method, which returns the _xdoc variable. And to complete that class, we rename it to SourceXDocumentBuilder, to express better that the fluent builder  pattern is used.

This is the final SourceXDocumentBuilder:


public class SourceXDocumentBuilder
{
   private XDocument _xdoc;

   public SourceXDocumentCreator(XDocument xdoc)
   {
      _xdoc = xdoc;
   }

   public SourceXDocumentCreator AddMetadata(string metadata)
   {
      // Add metadata to _xdoc
      return this;
   }

   public SourceXDocumentCreator AddHeader(string title)
   {
      // Add header with title to _xdoc
      return this;
   }

   public SourceXDocumentCreator AddLogo(string name,
                                         double width,
                                         double height)
   {
      // Add logo to _xdoc
      return this;
   }

   public XDocument Build()
   {
      return _xdoc;
   }
}


Geen opmerkingen:

Een reactie posten