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

Tagged as

Writing Readable Code - Complex Object Construction

, 16 Apr 2010
Rate this:
Please Sign up or sign in to vote.
(Cross post from IRefactor)Once upon a time, there was a class called Invoice. Its responsibility was to calculate a finalprice 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 develo

(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)

Share

About the Author

Uri Lavi
Other
Israel Israel
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.

Comments and Discussions

 
GeneralMy vote of 3 Pinmembertaras_b16-Feb-11 13:33 
GeneralI still don't see why we need this. PinmemberMember 389508821-Oct-10 20:41 
GeneralRe: I still don't see why we need this. Pinmembertaras_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.Builder Pinmembersupercat916-Apr-10 12:09 
GeneralRe: Variables of type Invoice.Builder PinmemberUri Lavi16-Apr-10 18:39 
GeneralRe: Variables of type Invoice.Builder Pinmembersupercat919-Apr-10 5:22 

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

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web01 | 2.8.140814.1 | Last Updated 16 Apr 2010
Article Copyright 2010 by Uri Lavi
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid