Software Design Pattern (1) - Creational, Factory

When programming in object-oriented languages, design patterns are usually used to structure the relationships between classes and objects. It is often used in refactoring.

If you ask ChatGPT, it will tell you:

Screenshot of chat with chatGPT

There are mainly three types of design patterns, including creational, structural, and behavioral patterns. Each type consists of many subtypes. Here we will talk about the factory method, and abstract factory under creational type.

1. Creational

If one only uses the basic ways to create objects, it could bring technique debts in the software design in the long term. Creational design patterns try to solve the potential problems by creating objects in a way suitable to the situation.

Creational design pattern contains mainly five methods, including the factory method pattern, abstract factory pattern, builder pattern, prototype pattern, and singleton pattern. There is also one pattern called simple factory pattern which is related to the factory method pattern and abstract factory pattern, but simpler.

1.1. Simple Factory Pattern

If you want to make a product, e.g. book, that expresses a story, and there are many stories. For example, Harry Potter has seven books and Iron Man only has one. If you create a class for each specific story, you may have to write some same code over and over again for all those classes as they are all the same kind of products (they may have similar production procedure).

Simple factory pattern can solve this by simply exact a common abstract base class for the same type of products (books):

1
2
3
4
public abstract class Book
{
public abstract void printBook();
}

And each specified product can implement it:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class HarryPotter : Book
{
public override void printBook()
{
Console.WriteLine("print seven books");
}
}

class IronMan : Book
{
public override void printBook()
{
Console.WriteLine("print one book");
}
}

Then it uses a static factory method to create different instances (specific products) out of this base class:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class SimpleBookFactory
{
public static Book makeBook(string name)
{
switch (name)
{
case "Harry Potter":
return new HarryPotter();
case "Iron Man":
return new IronMan();
default:
throw new ApplicationException(string.Format("Book '{0}' cannot be made", name));
}
}
}

Finally, in the application, the method can be used as:

1
2
3
4
5
public static void main(String[] args) 
{
Book book = SimpleBookFactory.makeBook("Harry Potter");
book.printBook();
}

Through the simple factory pattern, one doesn’t need to create instance of a class in the application, which lowers the coupling. However, every time there is a new story, the SimpleBookFactory.makeBook method needs to be changed, which we do not want (If we did anything wrong when changing this method, all instances cannot be created properly). To overcome this problem, factory method pattern was created.

1.2. Factory Method Pattern

To not change the existing instance creating code, one can define a factory interface which contains the methods to produce products. Each product has their own factory which implements the interface.

The UML class diagram for factory method pattern can be seen as below:

So in the previous example, we keep the product classes:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public abstract class Book
{
public abstract void printBook();
}

class HarryPotter : Book
{
public override void printBook()
{
Console.WriteLine("print seven books");
}
}

class IronMan : Book
{
public override void printBook()
{
Console.WriteLine("print one book");
}
}

But instead of SimpleBookFactory, now we have:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public interface IFactory
{
Book makeBook();
}

public class HarryPotterFactory : IFactory
{
public Book makeBook()
{
return new HarryPotter();
}
}

public class IronManFactory : IFactory
{
public Book makeBook()
{
return new IronMan();
}
}

In this case, when creating instances in the application, we can directly call from the implemented specific factories:

1
2
3
4
5
6
7
8
9
10
public static void main(String[] args) 
{
// produce Harry Potter books
HarryPotterFactory hp_factory = new HarryPotterFactory();
hp_factory.makeBook().printBook();

// produce Iron Man books
IronManFactory im_factory = new IronManFactory();
im_factory.makeBook().printBook();
}

With the factory method pattern, except that we do not need to create instances of the products (stories) in the application, we also do not need to change existing factory methods when adding new products. But it increases the number of classes, since for every new story, there are a new product class and its corresponding factory class.

What if there is a need for the book factory to also produce comic books of Harry Potter and Iron Man?

1.3. Abstract Factory Pattern

Abstract factory pattern is more complicated and not as widely used as the previous two. It can be seen as a factory of factories.

The UML class diagram for factory method pattern can be seen as below:

For the same example, we still keep the product classes:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public abstract class Book
{
public abstract void printBook();
}

class HarryPotter : Book
{
public override void printBook()
{
Console.WriteLine("print seven books");
}
}

class IronMan : Book
{
public override void printBook()
{
Console.WriteLine("print one book");
}
}

And now we need to make comic books, too:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public abstract class ComicBook
{
public abstract void printBook();
}

class HarryPotterComic : ComicBook
{
public override void printBook()
{
Console.WriteLine("print seven books with all images");
}
}

class IronManComic : ComicBook
{
public override void printBook()
{
Console.WriteLine("print one book with all images");
}
}

For the factory classes, since there are two main categories i.e., book and comic book, we can define a factory for each story. Each factory is responsible for making both categories:

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
public interface AbstractFactory
{
Book makeBook();
ComicBook makeComicBook();
}

public class HarryPotterFactory : AbstractFactory
{
public Book makeBook()
{
return new HarryPotter();
}
public ComicBook makeComicBook()
{
return new HarryPotterComic();
}
}

public class IronManFactory : AbstractFactory
{
public Book makeBook()
{
return new IronMan();
}

public ComicBook makeComicBook()
{
return new IronManComic();
}
}

Finally in the application, we can produce products based on story:

1
2
3
4
5
6
7
8
9
10
11
12
public static void main(String[] args) 
{
// produce Harry Potter related
HarryPotterFactory hp_factory = new HarryPotterFactory();
hp_factory.makeBook().printBook();
hp_factory.makeComicBook().printBook();

// produce Iron Man related
IronManFactory im_factory = new IronManFactory();
im_factory.makeBook().printBook();
im_factory.makeComicBook().printBook();
}

The core idea of abstract factory pattern is that it performs one more abstraction on the factory classes, which decreases the number of factory classes. It is particularly helpful if there is a great demand in products. However, the limitation of this pattern is also obvious. If there is a need to make, for example audio books, the AbstractFactory interface and all its implementation classes need to be changed too.