Abstract Factory Design Pattern

In the earlier posts I covered Factory Design Pattern. This post is about Abstract Factory Design Pattern. At times it is confusing to understand the need for two patterns which are related to the concept of factories. In simple terms Abstract Factory is like a factory of factories. Lets examine the details of this pattern and try to compare it with others that I have already covered in the previous posts.

Problem statement

Lets assume that we have some data with us in the form of a collection. It can be anything like list of people and their details, bank transaction details, employee salary details etc. I am not really worried about the format and exact representation of the data. What I know for sure is there is a collection of data in my possession. I need to export this data as report into different formats. Currently the supported formats are HTML and plain text. The report should have a header row displaying column name. Then should be actual content in the report details section and finally a footer to display summary information.

Implementation

Note that for simplicity I am not going to actually build any report. I’ll build the skeleton which can be extended to build a proper report if you really wish to. Instead of discussing the theory lets look at some code to get started.

    public class ReportWriter

    {

        public void GenerateReport(ReportOptions reportOptions)

        {

            if (reportOptions.ReportType == ReportType.HTML)

            {

                HTMLReportHeader htmlHeader = new HTMLReportHeader();

                htmlHeader.WriteHeaderDetails();

 

                HTMLReportContent htmlContent = new HTMLReportContent();

                htmlContent.WriteContents();

 

                HTMLReportFooter htmlFooter = new HTMLReportFooter();

                htmlFooter.WriteFooterDetails();

            }

            else if (reportOptions.ReportType == ReportType.Text)

            {

                TextReportHeader textReportHeader = new TextReportHeader();

                textReportHeader.WriteHeaderDetails();

 

                TextReportContent textReportContent = new TextReportContent();

                textReportContent.WriteContents();

 

                TextReportFooter textReportFooter = new TextReportFooter();

                textReportFooter.WriteFooterDetails();

            }

        }

    }

This is a skeleton which consists of a set of classes for specific purpose like the HTMReportLHeader, HTMLReportContent and HTMLReportFooter for handling HTML report. Similarly there is text equivalent of this in the form of TextReportHeader, TextReportContent and TextReportFooter. In real life these methods would be much more complex and would require various inputs like the actual report data, the header details and footer details. Once again I have omitted them for demo purposes.

Refactoring towards Abstract Factory

The above solution works for simple cases. In most cases what starts as a simplest requirement ends up complicating things in future as business users change their requirements. It is always better to build software which is easier to change in future with minimal efforts. The problems with the above solution is similar to the ones we have seen in earlier posts. The biggest of them all is the extensibility.

Imagine the users of the system also want a report in Excel or PDF or XML, or for that matter any other format which they might be comfortable with. We can keep adding the else if cases or refactor this logical tree into a switch case statement. One thing to remember is every time we add a type of report we’ll end up adding at least 3 classes for header, report details and the  footer. And in most cases only one set of these related classes would be used at a point of time.

Instead of building a big chunk of if else or switch case construct, we can simply things by abstracting these classes into a family of closely related classes. If we look closely all we need is an interface which can be called by the report write and an abstraction for each of the specific section of the report which are header, contents or details and the footer. Lets start by building the interface which will help us with these abstractions.

    public interface IReportGenerator

    {

        IReportHeader GetReportHeader();

 

        IReportContent GetReportContent();

 

        IReportFooter GetReportFooter();

    }

A simple interface with 3 methods. The magic lies in this abstraction. Note that the interface does not expose the concrete types but in turn it exposes the abstractions of the related or family of objects. Lets look at an implementation of this interface.

    public class HTMLReportGenerator : IReportGenerator

    {

        public IReportHeader GetReportHeader()

        {

            return new HTMLReportHeader();

        }

 

        public IReportContent GetReportContent()

        {

            return new HTMLReportContent();

        }

 

        public IReportFooter GetReportFooter()

        {

            return new HTMLReportFooter();

        }

    }

This is the implementation of HTMLReportGenerator. In this we return the concrete instance of the other interfaces like HTMLReportHeader, HTMLReportContent and HTMLReportFooter. I’ll not show the implementation of each and every class here. Lets see what changes are required in the client code

    public class ReportWriter

    {

        public void GenerateReport(ReportOptions reportOptions)

        {

            IReportGenerator reportGenerator = ReportGeneratorFactory.GetReportGenerator(reportOptions.ReportType);

 

            reportGenerator.GetReportHeader().WriteHeaderDetails();

 

            reportGenerator.GetReportContent().WriteContents();

 

            reportGenerator.GetReportFooter().WriteFooterDetails();

        }

    }

I refactored the ReportWriter class to work with the IReportGenerator abstraction. We obtain the concrete instance of this abstraction using the ReportGeneratorFactory class which is responsible for creating the concrete instance based on the input parameter of ReportType.

    public class ReportGeneratorFactory

    {

        public static IReportGenerator GetReportGenerator(ReportType reportType)

        {

            switch (reportType)

            {

                case ReportType.HTML:

                    return new HTMLReportGenerator();

                case ReportType.Text:

                    return new TextReportGenerator();

            }

        }

    }

We can always counter these refactoring's as not a big deal. If you think so, just try implementing the report writer for only the formats I have accounted here which include HTML report, text report, XML report and Excel report.

By abstracting the interface we have made our solution quite extensible and simple to maintain. Imagine the business users come up with a new requirement to generate a report in XPS format. All we need is to implement a case statement which returns a XPSReportGenerator. Off course the dependent interfaces will need to be developed. But the client code is totally unaware of which exact type is being used at runtime. As long as the IReportGenerator interface is implemented by the deriving classes client will continue to generate reports of all types.

This allows us the flexibility to control features on demand basis. Imagine you want to expose selective functionality to specific users. Lets say the software being developed has a free or lite version and also a paid version. We can provide different sets of reports based on their needs. In lite version only HTML and Text reports can be supported and in the full blown version all types of reports can be supported.

Example of Abstract Factory used within .Net Framework

One of the best example of Abstract factory used within .Net framework is the ADO.NET. Look at the interfaces provided over the database specific types like IDbConnection, IDataReader, IDbCommand etc. Specific providers implement these interfaces like SqlConnection, SQLCommand, SqlDataReader in case of SQL server. Similarly Oracle provides OracleConnection, OracleDataReader and OracleConnection. Same is the case with other database providers like OLEDb, Sybase etc. All these vendors implement the generic interfaces. This is what allows .Net framework to provide an abstract factory in the form of DbProviderFactory which can build provider specific instances of the respective classes.

Conclusion

I might not have written the complete implementation of all the concrete classes, but I hope the skeleton was good enough to put my thoughts across. Abstract factory design pattern is very helpful when we want a set of classes to work together. These classes are always related to one another and very rarely be used outside the context of their boundary. For example the HTML report related classes will be used in the HTML report generator. I cannot imagine an Excel report generator using a HTMLFooter class to write an excel footer row. When we have such scenario of closely related classes working together in unison its always a better idea to group them using an Abstract Factory.

In many ways the code might look similar to the one we used during the demonstration of Strategy Design Pattern. We can say that report writer class is just another strategy like a HTMLStrategy, TextStrategy, XMLStrategy, PDFStrategy etc etc. Its not like we cannot implement the same pieces as a strategy. One very important thing to remember while implementing and using design patterns is their intent and the applicability. For the same problem statement we can possibly apply more than one design pattern. We need to pick and choose the best one. This is when the intent helps us a lot.

The intent of a Strategy pattern is to swap an algorithm. Where as Abstract Factory design pattern is used to abstract the creation of families of objects which work closely with one another. Strategy is used when there are multiple ways of achieving the same results but using different mechanisms which are essentially abstracted in the form of algorithms. In case of abstract factory the intent is to hide the concrete implementations behind a common interface for a set or related classes. In this case I have used an interface but the same can be implemented using an abstract base class. Another difference between the two patterns is that Strategy is a behavioural pattern while Abstract Factory is a creational pattern.

As always I have uploaded the code used during this demo to Dropbox.

Until next time Happy Programming.

Further reading

 

Share:
spacer

No comments:

Post a Comment