2 AERONAVE MXP 1000
2.1 Características Geométricas
Simple Factory
Let’s build some smartphones. Let’s do it “wrong” first so we can see the problems with that way, and then we’ll move on to take a look at how factories can decouple your code and make things easier to maintain.
First, let’s define an enumerated type:
TSmartPhoneType = (CheapCrappy, Basic, Deluxe, SuperDuper);
This type will define the four different kinds of smartphones we will be assembling at the Nick Phone Assembly plant.
Here’s what our smartphones look like:
¹⁵http://www.codingindelphi.com
¹⁶http://www.nickhodges.com/post/Life-is-Too-Short-To-Call-Create.aspx
32
TBaseSmartPhone = class abstract protected
function GetName: string; virtual; abstract;
public
procedure GatherParts; virtual; abstract;
procedure Assemble; virtual; abstract;
property Name: string read GetName;
end;
Here are a few things to note about our smartphone classes:
• They all descend from the abstract classTBaseSmartPhone. Remember how I am always saying to code to an abstraction and not an implementation? Well,TBaseSmartPhoneis an abstraction. It’s declared as an abstract class, and it has three abstract methods. And we are going to code against it instead of the actual concrete phone classes.
• There are four descendants –TBasicSmartPhone,TCheapCrappySmartPhone,TDeluxeSmartPhone, andTSuperDuperSmartPhone, each of which descends fromTBaseSmartPhone. (At the Nick Smart-phone Company, we know how to segment the market – that is clear.) Each of them overrides the three abstract methods, providing different implementations.
• I’m not showing the implementations because they are simpleWriteLnstatements that describe what
each method does, identifying each with the name of the phone. You can see the code in the code repository for the book.
Okay, now that we have a set of phones, we can create an assembly plant object that will assemble them:
type
raise ENotImplemented.Create(‘aType was set to an undefined value’);
end;
Here are some things to note about the above code (and why it’s called a “bad” factory):
• It contains a bigcasestatement that creates the correct kind of phone based on the parameter passed to theAssembleSmartPhonemethod. If we add more phones, thatcasestatement would have to get bigger.
• From there, it uses polymorphism to call the methodsGatherPartsandAssembleto metaphorically put the phone together.
• However, there are some problems here. First, as your keen eye has probably noticed, the Assembly Plant object is tightly coupled to the smartphone objects. Second, if we want to add a phone, we have to violate the Open/Closed Principle and change the TBadSmartPhoneAssemblyPlant object, specifically by altering theAssembleSmartPhonemethod.
• In addition, the class has more than one reason to change. First, it might change because the assembly plant might need more functionality. Second, it might change because it might need to manufacture a new kind of smart phone, sayTSuperCheapSmartPhone. Thus, the class violates the Single Responsibility Principle.
What’s the solution for these problems? Well, let’s decouple the creation of smartphone objects from the assembly plant by creating some simple smartphone factories. (I know, it’s a little ironic to be creating a factory to pass to an assembly plant, but work with me here…)
Here’s the declaration of a base class for our smartphone factory:
TBaseSmartPhoneFactory = class abstract public
function GetSmartPhone: TBaseSmartPhone; virtual; abstract;
end;
It’s pretty simple, having but one abstract function calledGetSmartPhone. And I’m pretty sure you can figure out what is going on here – that method simply will create and return a descendant ofTBaseSmartPhone. Okay, so below is the declaration and implementation of TBasicSmartPhone. I show you only one of the three, because they are all declared basically the same way, and I don’t want to repeat myself and waste your time.
TBasicSmartPhoneFactory = class(TBaseSmartPhoneFactory) public
function GetSmartPhone: TBaseSmartPhone; override;
end;
function TBasicSmartPhoneFactory.GetSmartPhone: TBaseSmartPhone;
begin
Result := TBasicSmartPhone.Create;
end;
There’s nothing special going on here. It’s just a method that creates the type of phone that the factory is named to create.
Actually, there is something special going on here: by using the factory, we’ve alleviated the assembly plant of the responsibility of creating things. Thus, we’ve satisfied the Single Responsibility Principle. As a result, instead of passing an enumerated type toAssembleSmartPhone, we can simply pass a factory:
type
Notice that thecase statement is gone, and that we can create any kind of phone that we want with this assembly plant, even phones that haven’t been thought of yet. So if our marketing department decides to try to corner the market on cheap, crappy cell phones, we can create:
TCheapCrappySmartPhone = class(TBaseSmartPhone)
And you can pass that to theTGoodSmartPhoneAssemblyPlantwithout having to change a thing in that class. Thus, we’ve satisfied the Open/Closed Principle withTGoodSmartPhoneAssemblyPlant– it’s open for extension but closed for change. We extend it by enabling the passing of anyTBaseSmartPhoneFactory
descendant to it. Cool, huh?
Now we can assemble any phone we want:
procedure BuildASmartPhone(aFactory: TBaseSmartPhoneFactory);
Hmmm. Those last two methods work great – if you want to create aTBasicSmartPhone. If you want to create aTDeluxePhone, you have to tell theBuildPhoneroutine to create aTDeluxeSmartPhoneFactory
instead of aTBasicSmartPhoneFactory. That’s a bit clumsy, no?
Maybe you can see where this is headed – that is, for now, we are going to have to put that big uglycase
statement somewhere (we’ll get rid of it below, don’t worry). So if we have to do that, let’s isolate it as much as possible, and make it as easy as possible to add things to it. Okay, let’s just go ahead and do that. Basically, let’s encapsulate the notion of creating the correct factory like so:
type
CheapCrappy: Result := TCheapCrappySmartPhone.Create;
Basic: Result := TBasicSmartPhone.Create;
Deluxe: Result := TDeluxeSmartPhone.Create;
SuperDuper: Result := TSuperDuperSmartPhone.Create;
end;
end;
This factory has a constructor that takes and stores aTSmartPhoneTypeto define which smartphone should be created. Sure there’s still thecasestatement, but it’s been hidden away a bit inside the factory.
We can then modify the helper routine to take such a parameter:
procedure BuildPhone(aPhoneType: TSmartPhoneType);
var
SmartPhoneFactory: TBaseSmartPhoneFactory;
begin
SmartPhoneFactory := TBetterSmartPhoneFactory.Create(aPhoneType);
try
BuildASmartPhone(SmartPhoneFactory);
finally
SmartPhoneFactory.Free;
end;
end;
And there you go – you’ve got a factory that has decoupled the creation of objects from the use of those objects.
Getting Rid of theCaseStatement
So far so good. But that dangcasestatement is still bugging me. How about we design a system without it?
First, we’ll use the existing smartphone types, specificallyTBaseSmartPhoneand its descendants, as well as
TSmartPhoneType.
Then, we’ll create an anonymous method type that creates a smartphone on demand:
TSmartPhoneFunction = reference to function: TBaseSmartPhone;
We’ll then declare the factory:
TSmartPhoneFactory = class private
class var FDictionary: IDictionary<TSmartPhoneType, TSmartPhoneFunction>;
public
class constructor Create;
class procedure AddPhone(aType: TSmartPhoneType; aFunction: TSmartPhoneFunction);
class function GetPhone(aType: TSmartPhoneType): TBaseSmartPhone;
end;
This factory is kind of interesting. The first thing to note is that everything is class method or variable, including theIDictionaryvariable. It also has a class constructor. A class constructor is a special constructor used by the compiler. It looks to see if the class is actually used anywhere, and if it is, it adds a call to that constructor in the initialization section of the unit containing the class. (You can read more about class constructors in the Delphi help:http://docwiki.embarcadero.com/RADStudio/XE8/en/Methods¹⁷.)
Okay, so the implementation of the class constructor should be no surprise:
class constructor TSmartPhoneFactory.Create;
begin
FDictionary := TCollections.CreateDictionary<TSmartPhoneType, TSmartPhoneFunction>;
end;
It merely creates the internal class variable of type IDictionary from the Spring.Collections unit.
The dictionary uses the type of the smartphone as the key, and a function that creates the corresponding smartphone type as the value. It will thus store a way to create a given smartphone based on a variable of typeTSmartPhoneType.
TheAddPhonemethod is exactly what you’d think:
class procedure TSmartPhoneFactory.AddPhone(aType: TSmartPhoneType; aFunction: TSmartPhoneFunction);
begin
FDictionary.AddOrSetValue(aType, aFunction);
end;
but theGetPhonemethod requires a touch of explanation. Here’s the implementation:
class function TSmartPhoneFactory.GetPhone(aType: TSmartPhoneType): TBaseSmartPhone;
begin
Result := FDictionary.Items[aType]();
end;
There’s only one line of code here, but it’s a little tricky. First, the code takes the aType parameter and retrieves the anonymous function stored in the dictionary (we’ll get to how that happens in a minute). But there’s a little “trick” you need to note. See those two little parentheses there? That tells the compiler “Hey, actually execute the procedure and return the result.” Thus, theGetPhoneclass function returns an instance ofTBaseSmartPhonebased on the type passed to it. Neat, huh?
Okay, how does the dictionary get filled? Well, every time you declare a descendant ofTBaseSmartPhone, you call theAddPhone method for it in an initialization somewhere. In our demo app, the initialization for the unit that declaresTSmartPhoneFactorylooks like this:
¹⁷http://docwiki.embarcadero.com/RADStudio/XE8/en/Methods
var
Thus, in this way, all the smartphone types are registered and available for use at runtime.
Now that we have everything registered and ready to go, we can actually use the factory to create types on demand:
for SmartPhoneType := Low(TSmartPhoneType) to High(TSmartPhoneType) do begin
SmartPhone := TSmartPhoneFactory.GetPhone(SmartPhoneType);
WriteLn('About to assemble ', SmartPhone.Name);
SmartPhone.GatherParts;
SmartPhone.Assemble;
WriteLn;
end;
end;
and we get an output that looks like this:
This method is obviously better than an uglycasestatement. You can add functionality by putting all the new functionality in a separate unit, without having to touch any existing code. It even allows you to dynamically extend the functionality by registering things from an external library. It is generally more flexible, more easily maintained, and easier to extend.
Some General Thoughts on Factories
Factories abstract the notion of creation
Sometimes creation of objects is complicated, and you don’t want your main class coupled to the things that are required to create a class. This is where factories can come in to do the creation without the coupling.
They can encapsulate and abstract away the notion of object creation.
Factories can return interfaces
As we’ll see below, factories can return interfaces, something that a constructor cannot do. This enables you to create a factory that can return various, unrelated classes as long as they implement the required interface.
Thus, a class using a factory doesn’t have to care at all about how an interface gets implemented, as long as the interface is implemented.
Raising an exception in a factory doesn’t have side effects
When you raise an exception in a constructor, there are various ramifications that happen. For instance, an exception in a constructor results in the destructor being called. Raising an exception in a factory has no such side effects. You may want to have finer control over things when creating objects, and a factory can give you that.
Factories can return null objects
Well, a factory can actually return nil itself, but we never want to do that, right? What a factory can do is to return an object that follows the Null Object Pattern.
Factories can have generic type parameters
You cannot parameterize a constructor. You can parameterize a factory. Thus, you can take advantage of the power of generics with factories –- something a constructor cannot do.
Factories can create complex types
Factories can encapsulate the creation of complex types, enabling you to keep the construction of complex objects isolated. This can relieve a class’s constructor of the burden of having many dependencies and having to coordinate the construction of these complex types.