Abstract Factory Pattern with Examples
Today, I will talk about highly popular Abstract Factory Pattern. Please note I am not going to take any Animal or Shape example as they don’t fit in the real-world development. Rather, I would take two different examples, one from .NET framework itself and another from my own work. To start with let’s see the definition of the pattern first.
“Provides an interface for creating families of related or dependent objects without specifying their concrete classes.”
“Abstract Factory Pattern provides a way to encapsulate a group of individual factories that have a common theme.“
Remember the term related and dependent objects in the above definition. By the end of this blog you would have a good understanding of how objects are related.
Structural UML
- AbstractFactory defines the interface that all of the concrete factories will need to implement in order to serve Products.
- ConcreteFactoryA and ConcreteFactoryB have both implemented this interface creating two seperate families of product.
- AbstractProductA and AbstractProductB are interfaces for the different types of product. Each factory will create one of each of these AbstractProducts.
- Client deals with AbstractFactory, AbstractProductA and AbstractProductB. It doesn’t know anything about the implementations. The actual implementation of AbstractFactory that the Client uses is determined at runtime.
One of the very known example is from ADO.NET
- Here ADO.NET defines an abstract product class DbConnection. For which we have several concrete products SqlConnection and OracleConnection etc. And these classes implements DbConnection.
- Similarly, DO.NET defines an abstract product class DbCommand. For which we have several concrete products SqlCommand and OracleCommand etc. And these classes implements DbCommand.
- DbProviderFactory class is the abstract factory class to help us to implement a Provider-Independent. The abstract factory, DbProviderFactory, declares a set of CreateXxx Methods,like, CreateConnection, CreateCommand etc.
- Each of these methods return a specific abstract product, for example CreateConnection returns DbConnection, CreateCommnad return DbCommand etc…
- DbProviderFactory has a set of concrete factory classes such as SqlClientFactory, OracleClientFactory etc…These concrete factories implements the CreateXxx methods to return specific products-provider specific classes.
Now, let’s take another hands on example of TextReader and TextWriter.
Assume your project demands to read and write texts to and from CSV format and MS Excel format. If I ask a beginner or a less experienced developer to write code for this, then he would probably end up with creating to classes one for CSV and another for MS excel. Each having two methods for Read and Write operations. I am not saying this is wrong but rather we have a better and maintainable approach for such requirements.
From the above requirement, We have two abstract products TextReader and TextWriter. So I’ll create ITextWriter (Interface) and TextReader(AbstractClass). Just for the sake of example I am going to create two different types to showcase that abstraction can be achieved from both interface and abstract classes.
1 2 3 4 5 6 7 8 9 10 11 |
public abstract class TextReader { public abstract string Name { get; } public abstract string Description { get; } public virtual void ReadImplementation(string path) { Console.WriteLine("Abstract Reader"); } } |
Now let’s create four actual product implementation classes (concrete products). CSVTextReader, CSVTextWriter, ExcelTextReader and ExcelTextWriter which implements abstract product classes. These classes are required to override ReadImplementation and WriteImplementation methods
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 |
public class CSVTextWriter : ITextWriter { public string Description { get { return "CSV text writer"; } } public string Name { get { return "CSVTextWriter"; } } public void WriteImplementation(string path, string content) { Console.WriteLine("CSVTextWriter implementation"); } } public class ExcelTextWriter : ITextWriter { public string Description { get { return "Excel text writer"; } } public string Name { get { return "ExcelTextWriter"; } } public void WriteImplementation(string path, string content) { Console.WriteLine("ExcelTextWriter implementation"); } } public class CSVTextReader : TextReader { public override string Description { get { return " CSV text reader"; } } public override string Name { get { return "CSVTextReader"; } } public override void ReadImplementation(string path) { base.ReadImplementation(path); Console.WriteLine("CSVTextReader implementation"); } } public class ExcelTextReader : TextReader { public override string Description { get { return "Excel text Reader"; } } public override string Name { get { return "ExcelTextReader"; } } public override void ReadImplementation(string path) { base.ReadImplementation(path); Console.WriteLine("ExcelTextReader implementation"); } } |
Once our products classes are ready we can create two concrete factories CSVTextReaderWriterFactory and ExcelTextReaderWriterFactory each having two methods GetReader and GetWriter. Please note how factories are returning related objects to the client. This is similar to a scenario where two different garment brands producing Shirt and a Trouser. In our case CSV and Excel are brands and TextReader and TextWriter are shirt and trouser respectively.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
public class CSVReaderWriterFactory : ReaderWriterProviderFactory { public override TextReader GetReader() { return new CSVTextReader(); } public override ITextWriter GetWriter() { return new CSVTextWriter(); } } public class ExcelReaderWriterFactory : ReaderWriterProviderFactory { public override TextReader GetReader() { return new ExcelTextReader(); } public override ITextWriter GetWriter() { return new ExcelTextWriter(); } } //Abstract factory public abstract class ReaderWriterProviderFactory { public abstract ITextWriter GetWriter(); public abstract TextReader GetReader(); } |
And finally let’s write a method to run the code. I could have created a separate FactoryMaker to instantiate a factory. But I am leaving it up to you to write it.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
public static void abstractFactoryPattern() { ReaderWriterProviderFactory abstractFactory = null; abstractFactory = new CSVReaderWriterFactory(); ITextWriter csvWriter = abstractFactory.GetWriter(); TextReader csvReader = abstractFactory.GetReader(); csvWriter.WriteImplementation("path", "content"); csvReader.ReadImplementation(""); abstractFactory = new ExcelReaderWriterFactory(); ITextWriter excelWriter = abstractFactory.GetWriter(); TextReader excelReader = abstractFactory.GetReader(); excelWriter.WriteImplementation("path", "content"); excelReader.ReadImplementation(""); } |
There is one another great example which talks about building UI elements on different platforms. This will also help you to understand the pattern in more details. https://dzone.com/articles/design-patterns-abstract-factory
Advantages of Abstract Factory:
- Isolation of concrete classes:
The Abstract Factory pattern helps you control the classes of objects that an application creates. Because a factory encapsulates the responsibility and the process of creating product objects, it isolates clients from implementation classes. Clients manipulate instances through their abstract interfaces. Product class names are isolated in the implementation of the concrete factory; they do not appear in client code. - Exchanging Product Families easily:
The class of a concrete factory appears only once in an application, that is where it’s instantiated. This makes it easy to change the concrete factory an application uses. It can use various product configurations simply by changing the concrete factory. Because an abstract factory creates a complete family of products, the whole product family changes at once. - Promoting consistency among products:
When product objects in a family are designed to work together, it’s important that an application use objects from only one family at a time. AbstractFactory makes this easy to enforce.
Disadvantages of Abstract Factory:
- Difficult to support new kind of products:
Extending abstract factories to produce new kinds of Products isn’t easy. That’s because the AbstractFactory interface fixes the set of products that can be created. Supporting new kinds of products requires extending the factory interface, which involves changing the AbstractFactory class and all of its subclasses. But you can overcome this problem by using reflection in .Net Framework and defining CreateProduct method in the AbstractFactory class.
Shahab
says:Great article with nice example.
Looking for other patterns
Cheers ????
Ankit Sharma
says:Thanks. Yes i will post soon.