(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; }
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)
{
}
}
"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:
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:"
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.
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.