For testing purposes, I have a Factory
that produces Product
s using a Builder
. Each Product
can have a status (Available
/ InUse
/ Disposed
/ etc). I need to produce products in various states.
My problem is that, in order for me to produce a Product
with, let's say, Disposed
status, it first needs to be InUse
( must create it using new ProductBuilder().CheckIn().Dispose().Build();
not just new ProductBuilder().Dispose().Build();
)
How can I (or must I ?) enforce this precondition for a builder method and keep the cyclomatic complexity of 1 (so it doesn't need further testing).
I don't wish to use something like if (product.Status == Product.INUSE) {...}
and throwing exceptions
for each possible scenario (different states need different preconditions).
Since the builder is private, do I need to enforce this? do I just rely on the programmer to know the order in which the methods need to be called and just add some comments before each builder method? do I choose a different pattern (which?).
public static class ProductFactory
{
private class ProductBuilder
{
private Product product;
public ProductBuilder()
{
product = new Product {Status = product.AVAILABLE};
}
public ProductBuilder Dispose()
{
product.Status = product.DISPOSED; return this;
}
public ProductBuilder CheckIn()
{
product.Status = product.INUSE; return this;
}
public Product Build()
{
return product;
}
}
public static Product CreateAvailable()
{
return new ProductBuilder().Build();
}
public static Product CreateInUse()
{
return new ProductBuilder().CheckIn().Build();
}
public static Product CreateDisposed()
{
return new ProductBuilder().CheckIn().Dispose().Build();
}
}
First off, you'd have to segregate these methods (CheckIn
and Disposed
) across multiple interfaces:
public interface IBaseBuilder
{
Product Build();
}
public interface IProductBuilder : IBaseBuilder
{
ICheckedInProductBuilder CheckIn();
}
public interface ICheckedInProductBuilder : IBaseBuilder
{
IDisposedProductBuilder Dispose();
}
public interface IDisposedProductBuilder : IBaseBuilder
{
}
This way, given an initial IProductBuilder
:
CheckIn
-> Dispose
in that specific orderCheckIn
has been called, it cannot be called againDispose
has been called, it cannot be called againBuild
to create the product.To make things easier to implement, you can have one class implement all three interfaces, and inject it into its clients using the primary IProductBuilder
interface. Or, you could have different classes implement the interfaces. I'll leave that as an exercise.
As a real world example, this technique is widely used in Moq and FluentAssertions to achieve a fluent API.
Related: Making my class 'fluent'