Click here to Skip to main content
15,867,956 members
Articles / All Topics

Writing Readable Code - Complex Object Construction

Rate me:
Please Sign up or sign in to vote.
4.00/5 (2 votes)
16 Apr 2010CDDL3 min read 14.9K   4   6
Writing Readable Code - Complex Object Construction

(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

C#
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 you 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 it a Builder.

Here is how it should look:

C#
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 each of 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:"

C#
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.

This article was originally posted at http://urilavi.blogspot.com/feeds/posts/default

License

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


Written By
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 Pin
taras_b16-Feb-11 13:33
taras_b16-Feb-11 13:33 
GeneralI still don't see why we need this. Pin
Member 389508821-Oct-10 20:41
Member 389508821-Oct-10 20:41 
GeneralRe: I still don't see why we need this. Pin
taras_b16-Feb-11 13:32
taras_b16-Feb-11 13:32 
GeneralVariables of type Invoice.Builder Pin
supercat916-Apr-10 12:09
supercat916-Apr-10 12:09 
GeneralRe: Variables of type Invoice.Builder Pin
Uri Lavi16-Apr-10 18:39
Uri Lavi16-Apr-10 18:39 
GeneralRe: Variables of type Invoice.Builder Pin
supercat919-Apr-10 5:22
supercat919-Apr-10 5:22 

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

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