A developer’s end goal is to make an application that meets the requirements of the customer. But the code must be easily extensible, maintainable, and stable enough to meet future needs too. It is tough to write the best version of a program on the very first attempt. You may need to analyze the code and refactor it several times. In this chapter, you will see such a scenario and learn how to use factories. To simplify the discussion, I start with a small program. We’ll keep analyzing the program and modifying it accordingly.
To make a stable application, an expert programmer attempts to make a loosely coupled system. The programmer tries to identify the code that can vary a bit. Once this is done, the programmer separates that part of the code from the remaining part of the codebase. Factories are best in this type of scenario.
The obvious question is, what is a factory? In simple words, a factory is a code segment that handles the details of an object creation process. Any method that uses a factory is called a client of the factory. You should note that a factory can have many clients. These clients can use the factory to get an object, and later as per their needs, they invoke the methods of the object. So, it is up to a client how to use the object that is received from a factory. This is why separating the object creation process into a shared place is beneficial. Otherwise, you end up with duplicate code in multiple clients.
The Problem Statement
You want to hide the instantiation logic from a client. You know that “change” is the only constant in the programming world. Let’s see what happens if the object’s instantiation logic resides on the client side. When you enhance your application to support a new type of object, you need to update the client code too. This demands retesting the client code as well.
There may be separate classes with methods that can also create cats or tigers. So, it is better to separate the code that instantiates a cat or a tiger in a shared place. In such a case, it does not matter how many clients use this code. Every client can refer to the common location to instantiate an object.
So, how can you separate the instantiation logic from the client code? You’ll see this in the upcoming demonstrations.
Initial Program
In this program, the AnimalFactory class is responsible for instantiating an object. It contains a method, called CreateAnimal(), to create a tiger or a cat instance.
The CreateAnimal() method is nonstatic. However, you can make it static. I discuss the pros and cons of using a static method in Chapter 14 of this book.
The AnimalFactory class acts like a factory class in this example. It contains a method, called CreateAnimal(), to create instances. So, you can say that this is the factory method in this example.
Inside the client code, you instantiate this factory class to get an animal. This is why the client uses the following code to get an animal and display its behavior:
Demonstration 1
Output
Analysis
You may need to enhance this application, because this application may need to create a different type of animal, say, a monkey, in the future. How can you proceed? You need to modify the AnimalFactory class and increase the if-else chain to consider the monkeys. But when you do this, you violate the OCP, and as a result, you need to retest the AnimalFactory class.
When you see the switch statements, or an if-else chain to create different types of objects in a similar example, you get a clue that you may need to reopen the code to accommodate future changes. In the worst case, this code is replicated in several parts of an application. As a result, you keep violating the OCP, which can cause a serious maintenance problem in the future.
In this program, Visual Studio shows you a message that says, “CA1822: Member 'CreateAnimal' does not access instance data and can be marked as static. It also keeps saying: Members that do not access instance data or call instance methods can be marked as static. After you mark the methods as static, the compiler will emit non-virtual call sites to these members. This can give you a measurable performance gain for performance-sensitive code.”
I do not consider taking this suggestion now. The reasons are simple. First, performance sensitivity is not the key goal right now. Second, though a static method allows me to call the method without instantiating an object, it has disadvantages too. For example, you cannot change the behavior of a static method at runtime. As I said earlier, you’ll learn about this more in Chapter 14.
Better Program
Create a Monkey class that will implement the IAnimal interface
Create a MonkeyFactory that will implement the AnimalFactory and provide the implementation for the CreateAnimal() method
So, it is enough for you to test the new classes only. Your existing code is untouched, and it is closed for modification.
Demonstration 2
Output
Analysis
Inside the client code, you decide which animal factory to use, a CatFactory or a TigerFactory.
The subclasses of AnimalFactory create a Cat instance or a Tiger instance.
Following this, we are supporting the OCP. As a result, it is a better and more extensible solution.
A New Requirement
In Chapter 4, I said that it is not always easy to fully implement this principle, but partial OCP compliance too can generate greater benefits for you. A new requirement can demand many changes in an application. In such a case, based on the situation, you have to choose one technique over another.
In the previous demonstrations, each factory could produce only one type of object, either a cat or a tiger, but there is no variation. For example, it makes perfect sense if a cat factory makes cats of different colors. Similarly, a tiger factory can produce tigers of different colors. If you receive such a requirement, how can you proceed? One option is to pass a color attribute inside the constructor and update the program accordingly.
The following is a sample demonstration for this. I have highlighted the important changes in bold.
Demonstration 3
Output
Analysis
You can see that lots of changes are required in this case. Is there any alternative way? I think so. This is why I have made demonstration 4 for you.
In the client code, you call the MakeAnimal() method instead of CreateAnimal() to see the effect of the updated code. Demonstration 4 shows you the complete example.
Demonstration 4
Output
Summary
Factories provide an alternative way to create objects. In this chapter, you saw the advantage of using factories. This chapter started with a simple factory class, which helps you to separate the code that is likely to vary from the other parts of the code. You put the instantiation logic inside the factory class to provide a uniform way to create objects.
Following the OCP principle, you further modified the application. In demonstration 2, you used a new hierarchy for factories where all concrete factories inherit from AnimalFactory, and you passed the details of object creation to a concrete factory (CatFactory or TigerFactory). Since you are following the OCP principle, you can add a new concrete factory, say MonkeyFactory, to create monkeys. And when you consider this scenario, you do not need to reopen the existing code. Instead, the new MonkeyFactory class can inherit from AnimalFactory, and following the rules, it can create monkeys. In this case, you need to create a Monkey class using the same approach I used for the Tiger class or the Cat class. Notice that you do not need to reopen the existing code.
I created demonstration 3 and demonstration 4 to support a new requirement. Maintaining the OCP in the current structure to accommodate the new requirement was tough because the color attribute was not considered at the beginning. In demonstration 3, you saw the use of a parameterized factory method to accommodate a new requirement. Finally, in demonstration 4, you saw that inside the abstract factory class, you can set a common rule that all derived concrete factories must follow. This approach helped you accommodate the particular change requirement with minimum changes.