Click here to Skip to main content
Click here to Skip to main content

Writing Readable Code - Complex Object Construction

By , 16 Apr 2010
 

(Cross post from IRefactor)

Once upon a time, there was a class called Invoice. Its responsibility was to calculate a final price being presented to the customer.

Time went on; The autumn passed, the winter fade out and the spring was already at the door and our class started to rust. Each time a developer found a new set of relevant parameters (that should have been passed to the Invoice class) he added a new constructor, to support them.

And so it happened, that after a while, two fellows stumbled on the class:

"What's that rusty thing, my dear?" said G.Ollum "I don't understand; What constructor should I invoke on the Invoice class? Does it matter how should I create the Invoice object? The class has more than 20 constructors; How in the world, somebody would understand what to do?"

"Let me see, what's the problem" said D.Eagol

  public class Invoice
   {
      public Customer Customer { get; private set; }
      public DateTime ContractDate { get; private set; }
      public TimeSpan ServiceContinuum { get; private set; }
      public double BaseFee { get; private set; }
      public double ServiceFee { get; private set; }
       //... a lot of other members ...
       public Invoice(Customer customer)
      {
         Customer = customer;
      }
       public Invoice(Customer customer, DateTime contractDate)
         : this(customer)
      {
      }
       public Invoice(Customer customer, DateTime contractDate, double baseFee)
         : this(customer, contractDate)
      {
      }
       public Invoice(Customer customer, TimeSpan serviceContinuum, double serviceFee)
         : this(customer)
      {
      }
       //... a lot of other constructors ...
   }

"It seems that you are right G.Olum", said D.Eagol after looking at the code, "You have a lot of different constructors, each instantiates a slightly different type of Invoice object, mainly because there are a lot of optional parameters to the Invoice class. There are also required ones; Look at the Customer parameter being accepted in each and every constructor."

"Moreover", continued D.Eagol, "The world has changed and something that shouldn't have been forgotten, was lost."

"What's that, my dear?", inquired G.Olum

"Well, you cannot name your constructors. That's why it is so difficult for your to find how to instantiate the class. If you were supplied methods with meaningful, intention revealing names then you would be able to create a required object", explained D.Eagol

"So, what can I do?", cried G.Olum

"Let's summarise what you really want to do and then see how to accomplish it", said D.Eagol

You want To

  • Create a complex object, with:
    • A few required parameters
    • A few (or many) optional parameters

You Don't Want To

  • Use constructors - they are nameless and we fear of them

So, let's create an internal class whose sole responsibility will be to shadow that complex creation.

Our inner class will provide a meaningful method name for each optional parameter of the Invoice class. The required parameters will be passed to one(and only one) inner's class constructor. Since, that inner class deals with construction, let's call him a Builder.

Here is, how it should look:

public sealed class Invoice
   {
      public Customer Customer { get; private set; }
      public DateTime ContractDate { get; private set; }
      public TimeSpan ServiceContinuum { get; private set; }
      public double BaseFee { get; private set; }
      public double ServiceFee { get; private set; }
       private Invoice()
      {
      }
       public class Builder
      {
         private Customer customer;
         private DateTime contractDate;
         private TimeSpan serviceContinuum;
         private double baseFee;
         private double serviceFee;
           public Builder(Customer customer)
         {
            this.customer = customer;
         }
          public Builder SignedOn(DateTime signDate)
         {
            this.contractDate = signDate;
             return this;
         }
          public Builder ServiceAgreementForPeriodOf(TimeSpan serviceContinuum)
         {
            this.serviceContinuum = serviceContinuum;
             return this;
         }
          public Builder ServiceBaseFee(double baseFee)
         {
            this.baseFee = baseFee;
             return this;
         }
          public Builder ServiceFee(double serviceFee)
         {
            this.serviceFee = serviceFee;
             return this;
         }
          public Invoice Build()
         {
            Invoice invoice = new Invoice 
            { 
               Customer = this.customer, 
               ContractDate = this.contractDate, 
               ServiceContinuum = this.serviceContinuum, 
               BaseFee = this.baseFee, 
               ServiceFee = this.serviceFee 
            };
             return invoice;
          }
      }
   }

"You see", continued D.Eagol, "That's what you have accomplished:"

  • The Invoice's constructor is private - only the Builder can return an Invoice, through its Build method
  • The Builder's constructor receives the Invoice's required parameter: Customer
  • The Builder provides meaningful method names for eachof the Invoice's optional parameters and utilizes Fluent interface in order to provide a more readable form of setting those parameters
"And here, how the instantiation looks like:"
 Invoice.Builder invoiceBuilder = new Invoice.Builder(Customer.CustomerA);
         Invoice invoice = invoiceBuilder
                           .SignedOn(Date("4/7/2010 08:00:00 PM"))
                           .ServiceAgreementForPeriodOf(Days(30))
                           .ServiceBaseFee(1000)
                         .Build();

"Now it really shines!", finished D.Eagol, "What do you say G.Olum?"

"Give us that, D.eagol, my love... It's my birthday...and I wants it!", gurgled G.Olum.

License

This article, along with any associated source code and files, is licensed under The Common Development and Distribution License (CDDL)

About the Author

Uri Lavi
Other
Israel Israel
Member
Uri Lavi is a development lead with extensive experience in Data Intensive, Business Compound, Distributed and Scalable Software Systems. Uri specializes in mentoring, coaching and consulting for complex software engineering topics, among which: Software Architecture, Design Patterns & Refactoring.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralMy vote of 3membertaras_b16 Feb '11 - 13:33 
The reason is in my response to Member 3895008 below.
GeneralI still don't see why we need this.memberMember 389508821 Oct '10 - 20:41 
The main purpose for adding constructors is for one-time initialization. So instead of creating an object with default values, only to overwrite those values in the next cycles, you simply initialize them to the given values. Using a builder, you nullify the advantage of a constructor and add complexity. Or maybe I missed something.
 
I'm not really sure when this technique would be more useful than simply introducing an empty constructor and converting the other constructors to dedicated methods. Actually, the latter seems it would be faster and easier to understand.
 
instead of:
 Invoice.Builder invoiceBuilder = new Invoice.Builder(Customer.CustomerA);
         Invoice invoice = invoiceBuilder
                           .SignedOn(Date("4/7/2010 08:00:00 PM"))
                           .ServiceAgreementForPeriodOf(Days(30))
                           .ServiceBaseFee(1000)
                         .Build();
 
you could have:
Invoice invoice = new Invoice(Customer.CustomerA);
        invoice.SignedOn(Date("4/7/2010 08:00:00 PM"));
        invoice.ServiceAgreementForPeriodOf(Days(30));
        invoice.ServiceBaseFee(1000);
 
Feels about the same to me (if you indent it), and the second method is a lot easier to understand.
GeneralRe: I still don't see why we need this.membertaras_b16 Feb '11 - 13:32 
Absolutelly agree.
 
Love Fluent Interface. It does indeed improve readability a lot. However the builder class proposed by the author works completely against the readability. Thus when he combines Fluent with the ugly Builder there is no real advantage in terms of readability. Your code snippets perfectly illustrate this.
 
Though you approach can be improved further with Fluent too:
 
Invoice invoice = new Invoice(Customer.CustomerA)        
                              .SignedOn(Date("4/7/2010 08:00:00 PM"))        
                              .ServiceAgreementForPeriodOf(Days(30))        
                              .ServiceBaseFee(1000);
 
And of course named params and initializers can do the "readability" job as good if not better.
 
Invoice invoice = new Invoice
                      {  
                          Customer = Customer.CustomerA, 
                          ContractDate = Date("4/7/2010 08:00:00 PM"),        
                          ServiceContinuum = Days(30),        
                          ServiceFee = 1000
                      };
 
Thus I am afraid the author succeeded in illustrating how to implement ObjectFactory but failed to illustrate the very subject of the article: "Writing Readable Code".
GeneralVariables of type Invoice.Buildermembersupercat916 Apr '10 - 12:09 
I hadn't seen the "Fluent Interface" used before, but it does seem sorta neat. A few caveats, though:
 
I don't think one can prevent the creation of variables of type Invoice.Builder, but I don't think one should encourage it either. If one creates one variable of type Invoice.Builder and then uses it twice, and some fields are defined for the first object but not the second, the second object will have those fields set to the first object's values.
 
Rather than creating such a variable and then using it, I would think it would be nicer to code a function Invoice.Create which returned an object of type Invoice.Builder, and then define a widening conversion from Invoice.Builder to Invoice.
 
If one wishes to create many similar objects without having to define all the properties for each of them, one could define an Invoice.Template class which wrapped Invoice.Builder; a CreateInvoice function would clone the Invoice.Builder.
GeneralRe: Variables of type Invoice.BuildermemberUri Lavi16 Apr '10 - 18:39 
Thanks for the comment.
I don't know whether you have seen this remark, so I am posting it again (with slight modification):
 
You are right, one cannot prevent from crating a Invoice.Builder variable, otherwise it implies that Invoice would implement the Fluent Interface (and some how will hide the building operations).
 
Single Responsibility will tell us, that it's better to transfer the responsibility of building a complex type to some sort of factory, i.e. Builder.
 
Thus Builder has its own place, as it specializes in building Invoices, while Invoice specializes in calculating the price.
 
Therefore having Builder will drive a cleaner design.
 
There is one more thing I didn't explained yet (I hope I will do in the next post) - but each time a Build method executed it returns a new Invoice.
 
Invoice class is immutable (sealed, private ctor and no mutators).
Hence it's impossible to use the same Builder to continue to modify the Invoice class and it's impossible to have the fields set from the first object.
 
Actually, the Builder uses Builder Pattern. Using Template pattern demands everybody to instantiate the Invoice in the same way, while Builder Pattern allows to have a lot of optional arguments.
 
Thanks again,
 
Thanks,
Uri

GeneralRe: Variables of type Invoice.Buildermembersupercat919 Apr '10 - 5:22 
Uri Lavi wrote:
Hence it's impossible to use the same Builder to continue to modify the Invoice class and it's impossible to have the fields set from the first object.

 
Agreed. The idea with my example was that the static CreateNew method of Invoice would return an object of type Invoice.Builder, which would in turn support an implicit widening conversion back to Invoice (the "conversion" creating a new immutable invoice type). Thus, saying anInvoice = Invoice.CreateNew.withPurchases(somePurchases).withCoupon(someCoupon) would create an Invoice.Builder object, perform withPurchases(somePurchases) on it, then perform withCoupon(someCoupon) on it, and then build an (immutable) invoice from the Invoice.Builder object.
 
While one might favor a slightly more verbose syntax anInvoice = Invoice.Builder.CreateNew.withPurchases(somePurchases).withCoupon(someCoupon).Build I'm not sure the extra verbosity is helpful. Yes, it shows more accurately what's really going on, but I would think the idea of the Fluent interface is to make things look like what one is seeking to accomplish, rather than like the sequence of steps that will be executed to get there.

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web04 | 2.6.130523.1 | Last Updated 16 Apr 2010
Article Copyright 2010 by Uri Lavi
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid