Deciding which factory object is needed is really the same as determining which family of objects to use. For example, in the preceding driver problem, I had one family for low-resolution drivers and another family for high-resolution drivers. How do I know which set I want? In a case like this, it is most likely that a configuration file will tell me. I can then write a few lines of code that instantiate the proper factory object based on this configuration information.
I can also use an Abstract Factory so I can use a subsystem for different applications. In this case, the factory object will be passed to the subsystem, telling the subsystem which objects it is to use. In this case, it is usually known by the main system which family of objects the subsystem will need. Before the subsystem is called, the correct factory object would be instantiated.
Figure 10-8 shows a Client using objects derived from two different server classes (AbstractProductA and AbstractProductB). It is a design that simplifies, hides implementations, and makes a system more maintainable.
The client object does not know which particular concrete implementations of the server objects it has because the factory object has the responsibility to create them.
The client object does not even know which particular factory it uses since it only knows that it has an Abstract Factory object. It has a ConcreteFactory1 or a ConcreteFactory2 object, but it doesn't know which one.
I have hidden (encapsulated) from the Client the choice about which server objects are being used. This will make it easier in the future to make changes in the algorithm for making this choice because the Client is unaffected.
The Abstract Factory pattern affords us a new kind of decomposition—decomposition by responsibility. Using it decomposes our problem into
Who is using our particular objects (ApControl)
Who is deciding upon which particular objects to use (AbstractFactory)
Using the Abstract Factory is indicated when the problem domain has different families of objects present and each family is used under different circumstances.
You may define families according to any number of reasons. Examples include:
Different operating systems (when writing cross-platform applications)
Different performance guidelines
Different versions of applications
Different traits for users of the application
Once you have identified the families and the members for each family, you must decide how you are going to implement each case (that is, each family). In my example, I did this by defining an abstract class that specified which family member types could be instantiated. For each family, I then derived a class from this abstract class that would instantiate these family members.
Sometimes you will have families of objects but do not want to control their instantiation with a different derived class for each family. Perhaps you want something more dynamic.
Examples might be
You want to have a configuration file that specifies which objects to use. You can use a switch based on the information in the configuration file that instantiates the correct object.
Each family can have a record in a database that contains information about which objects it is to use. Each column (field) in the database indicates which specific class type to use for each make method in the Abstract Factory.
If you are working in Java, you can take the configuration file concept one step further. Have the information in the field names represent the class name to use. It does not need to be the full class name as long as you have a set convention. For example, you could have a set prefix or suffix to add to the name in the file. Using Java's Class class you can instantiate the correct object based on these names.[2]
[2] For a good description of Java's Class class see Eckel, B., Thinking in Java, Upper Saddle River, N.J.: Prentice Hall, 2000.
In real-world projects, members in different families do not always have a common parent. For example, in the earlier driver example, it is likely that the LRDD and HRDD driver classes are not derived from the same class. In cases like this, it is necessary to adapt them so an Abstract Factory pattern can work.