Another important paradigm is to separate what changes from what stays the same and encapsulate what changes.
We've seen both of these at work in every pattern so far. In the strategy pattern we encapsulated object behavior to allow it to change flexibly during runtime.
Now we're going to look at object creation and apply the same principles.
Taking the car example again, let's say before a car can be shipped out it needs to be prepared.
public abstract class Car { //Each type of car handles these differently. public abstract void ConstructCar(); public abstract void Paint(); public abstract void ApplyGloss(); public void ApplyNewCarSmell() { //They really do this! Console.WriteLine("Spraying new car smell into car..."); } }Every car that comes off the lot has to have these methods called on it before it's ready.
So often times when we get to creating the cars we get code that looks like this:
public Car BuyCar(string type) { Car myCar; if (type == "Honda") { myCar = new Honda(); } else if (type == "ElCamino") { myCar = new ElCamino(); } else if (type == "Toyota") { myCar = new Toyota(); } //etc... //prepare the car for purchase. myCar.ConstructCar(); myCar.Paint(); myCar.ApplyGloss(); myCar.ApplyNewCarSmell(); return myCar; }This clearly gets out of hand but what we can see is that the type of car varies but the initialization of the car is the same.
Each type of car knows how to paint itself and apply gloss, and spraying new car smell is the same for every car.
So we should try to separate out the creation and encapsulate it.
Let's do this by creating a class that handles Car creation.
public class SimpleCarFactory { public Car CreateCar(string type) { Car myCar; if (type == "Honda") { myCar = new Honda(); } else if (type == "ElCamino") { myCar = new ElCamino(); } else if (type == "Toyota") { myCar = new Toyota(); } //etc... return myCar; } }Now let's create a client class to buy cars from.
public class CarLot { //Contains a reference to a factory object. SimpleCarFactory _carFactory; public CarLot(SimpleCarFactory carFactory) { _carFactory = carFactory; } public Car BuyCar(string type) { //Delegates creation to the factory object. Car product = _carFactory.CreateCar(type); //This is the same... product.ConstructCar(); product.Paint(); product.ApplyGloss(); product.ApplyNewCarSmell(); return product; } }This is called a Simple Factory. It's commonly used but isn't technically considered a design pattern.
What we would ideally like is a higher level of abstraction in the Creator class. There may be different CarLots that prepare there cars differently. Maybe certain brands of paint aren't available in certain regions or worse the steering wheel is on a different side!
This conveniently brings us to the Factory Method Pattern. Let's change our CarLot class a little:
public abstract class CarLot { public Car BuyCar(string type) { //Now this calls a method from within our CarLot. Console.WriteLine("Creating your " + GetType().ToString()); Car product = CreateCar(type); product.ConstructCar(); product.Paint(); product.ApplyGloss(); product.ApplyNewCarSmell(); return product; } //Each CarLot handles their own Car creation. protected abstract Car CreateCar(string type); }And make two different lots, a Unitited States CarLot and a United Kingdoms CarLot.
public class USCarLot : CarLot { protected override Car CreateCar(string type) { Car product; if (type == "Honda") { //We have new subclasses for US Cars now. product = new USHonda(); } else if (type == "Toyota") { product = new USToyota(); } //etc... else { return null } return product; } } public class UKCarLot : CarLot { protected override Car CreateCar(string type) { Car product; if (type == "Honda") { //And new subclasses for UK Cars now. product = new UKHonda(); } else if (type == "Toyota") { product = new UKToyota(); } //etc... else { return null } return product; } }Now for the cars.
public class USHonda : Car { public override ConstructCar() { Console.WriteLine("Steering wheel goes on the left."); } public override Paint() { Console.WriteLine("Painting car..."); } public override ApplyGloss() { Console.WriteLine("Applying glossy finish..."); } } public class UKHonda : Car { public override ConstructCar() { Console.WriteLine("Steering wheel goes on the right."); } public override Paint() { Console.WriteLine("Painting car..."); } public override ApplyGloss() { Console.WriteLine("Applying glossy finish..."); } } //In our simple example, the Toyota classes look similar...Now let's say we want to buy a car. First we choose a type of CarLot to make, then we choose the type of car we want, finally we call the the CarLot's BuyCar method passing in the appropriate string. The type of care that comes out depends on the CarLot that we chose.
class Program { static void Main(string[] args) { CarLot usCarLot = new USCarLot(); CarLot ukCarLot = new UKCarLot(); Car myUSHonda = usCarLot.BuyCar("Honda"); Car myUKHonda = ukCarLot.BuyCar("Honda"); Console.ReadLine(); } }And the output should read:
Creating your USHonda
Steering wheel goes on the left.
Painting car...
Applying glossy finish...
Creating your UKHonda
Steering wheel goes on the right.
Painting car...
Applying glossy finish...
That's the Factory Method pattern and it, like all factories, encapsulates object creation. It gives us an interface for creating objects while allowing specific instances of that interface to decide which class to instantiate.
To wrap up, let's take a look at the class diagram.
In the Creator we have a 'factory method' that all subclasses must implement so the ConcreteCreator actually makes the product and is responsible for which ConcreteProduct to make. Each ConcreteProduct must implement the Product interface so that classes dealing with the products all have a common interface they know how to manipulate. That's it for this article and thanks for reading!
No comments :
Post a Comment