Tuesday, June 28, 2011

Resharper series - Extracting factory classes

You may have come across code that creates a new instance of an object and setting it to a default state before working on it. Sometimes however the creation requirements of this object may grow and clouds the original code that was used to create the object. This is where a Factory class comes into play. For e.g. have a look into the below given test cases and classes.

[TestMethod]
public void ZoomLensesCanHaveMacroModeAlso()
{
    const string lensDescription = "Canon EF-S 55-250mm f/4.0-5.6 IS Telephoto Zoom Lens";
    var zoomLens = new ZoomLens(lensDescription);

    zoomLens.SetCloseupConversion(true);
    Assert.IsTrue(zoomLens.HasMacroMode());
}

[TestMethod]
public void WideAngleLensesHaveUltraWideView()
{
    const string lensDesription = "Canon EF 35mm f/2 Wide Angle Lens";
    var wideAngleLens = new WideAngleLens(lensDesription);

    wideAngleLens.SetUltraWideView(true);
    Assert.IsTrue(wideAngleLens.HasUltraWideView());
}

The classes used in the sample are as given below.
public class ZoomLens
{
    private readonly string _lensDescription;
    private bool _closeUpConversionEnabled;

    public ZoomLens(string lensDescription)
    {
        _lensDescription = lensDescription;
        _closeUpConversionEnabled = false;
    }

    public void SetCloseupConversion(bool closeUpConversion)
    {
        _closeUpConversionEnabled = closeUpConversion;
    }

    public bool HasMacroMode()
    {
        return _closeUpConversionEnabled;
    }
}

public class WideAngleLens
{
    private readonly string _lensDesription;
    private bool _enableUltraWideView;

    public WideAngleLens(string lensDesription)
    {
        _lensDesription = lensDesription;
        _enableUltraWideView = false;
    }

    public void SetUltraWideView(bool enableUltraWideView)
    {
        _enableUltraWideView = enableUltraWideView;
    }

    public bool HasUltraWideView()
    {
        return _enableUltraWideView;
    }
}

As you can see from the classes used in the sample, I can implement a class hierarchy by creating a superclass or an interface for the camera lenses and move the common methods to that type and make the subclasses implement/ inherit the superclass. This provides the situation for usage of a Factory method that makes it easy for programmers to extend the functionality of the framework by introducing polymorphic creations of objects.
First we need to extract all common methods and properties of these classes to a common base class. I have used the Extract Superclass refactoring to create a superclass for the classes in this sample.



After extracting the superclass, we need to create the constructor for the base class with the common properties.
public class CameraLens
{
    protected string _lensDesription;

    public CameraLens(string lensDescription)
    {
        _lensDesription = lensDescription;
    }
}

Refactor the base class to use this constructor during object creation.
public WideAngleLens(string lensDesription) : base(lensDesription)
{
    _enableUltraWideView = false;
}

Use the Pull members refactoring to move the methods as abstract to the parent class.



Repeat the steps for other inheriting classes also. Compile and run the test cases.
Next use the Extract Method refactoring to move the creation logic to creation methods in the test cases.

After applying the refactoring pattern the code looks like
public void ZoomLensesCanHaveMacroModeAlso()
{
    const string lensDescription = "Canon EF-S 55-250mm f/4.0-5.6 IS Telephoto Zoom Lens";
    CameraLens zoomLens = CreateCameraLens(lensDescription);

    zoomLens.SetCloseupConversion(true);
    Assert.IsTrue(zoomLens.HasMacroMode());
}

private static CameraLens CreateCameraLens(string lensDescription)
{
    return new ZoomLens(lensDescription);
}

Use the Move to another type refactoring pattern and specify the name of the new class in the type as given below

Make the Create method non- static and use the Extract Interface refactoring as given below.
Now you can use this interface as a dependency for your client classes that needs to create the Camera lenses or change the test cases as given below
private ICameraLensFactory _zoomCameraLensFactory;

[TestInitialize]
public void Initialize()
{
    _zoomCameraLensFactory = new ZoomCameraLensFactory();
}

[TestMethod]
public void ZoomLensesCanHaveMacroModeAlso()
{
    const string lensDescription = "Canon EF-S 55-250mm f/4.0-5.6 IS Telephoto Zoom Lens";
    var zoomLens = _zoomCameraLensFactory.CreateCameraLens(lensDescription);

    zoomLens.SetCloseupConversion(true);
    Assert.IsTrue(zoomLens.HasMacroMode());
}


No comments: