Presentation Transcript
Design Patterns : Design Patterns Chapter 4
Controlling Object Creation : Controlling Object Creation Ultimately, the exact object of a type must be known in order to create it (duh)
Foo f;
Bar b = new Bar('baz');
But this violates an important principle:
Program to an interface, not an implementation
Or, program to abstractions, not concrete types
Concrete object creation is best localized in one place
So introducing new types doesn’t cause maintenance trauma
A C++-specific Creation Issue : A C++-specific Creation Issue C++ allows objects everywhere
Not just on the heap (new is not required)
Suppose we have a context that wants to force objects to be on the heap only
Example: 3370 Program 2 (Memory Pool)
How can we prevent non-heap object creation?
Static Factory : Static Factory Provide a static method that returns a pointer to a heap object
Make all constructors non-public
(Visit Pool example)
Another Scenario : Another Scenario Suppose you need to serialize objects
And you don’t have such facilities on your platform
To de-serialize objects requires creating the correct type on-the-fly
Based on some data in the file entry for the object
(Visit 1350 Serialization example)
Parametric Factories : Parametric Factories The type of object created depends on dynamic information
Either read from an input source or provided as a parameter to a factory method
The 1350 example has a major faux pas:
Employee::retrieve( ) knows about subclasses
Violates the DIP
Can’t always be avoided
But can be moved to a better place
Employee Hierarchy : Employee Hierarchy
Dependency Inversion Principlewww.objectmentor.com/omReports/articles/dip.pdf : Dependency Inversion Principle www.objectmentor.com/omReports/articles/dip.pdf High-level modules should not depend on low-level modules
Both should depend on abstractions
Abstractions should not depend on details
Details should depend on abstractions
Why?
So clients of abstractions are insulated from changes
Rules of Thumb for the DIP : Rules of Thumb for the DIP No variable in an abstraction should hold a pointer to a concrete class
No class should derive from a concrete class
No method should override an implemented method of any of its base classes
Only override abstract methods
These rules can’t be followed all the time!
Emphasis on thumb :-)
The key is how volatile the lower-level module is
N-tier Architecture : N-tier Architecture
N-tier ArchitectureCritique : N-tier Architecture Critique Appears to violate the DIP
This is solved by having high-level modules write to an abstraction of its lower-level service module
So implementation can vary and not disturb the high-level client
(See next slide)
Following the DIP : Following the DIP
Factories : Factories Allow a (client) higher-level module to defer the details of object creation to its (server) lower-level service module
Including determining the type of the object that needs to be created
'Polymorphic Factory'
Another DIP Violation : Another DIP Violation
A Better Shape Scenario : A Better Shape Scenario
Pizza in Objectville : Pizza in Objectville public Pizza orderPizza() {
Pizza pizza = new Pizza(); // 1 type only!
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
} What if we want to make multiple types of pizza?
More Pizza in Objectville : More Pizza in Objectville public Pizza orderPizza(String type) {
Pizza pizza;
if (type.equals('cheese'))
pizza = new CheesePizza();
else if (type.equals('greek'))
pizza = new GreekPizza();
(etc.)
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
Here We Go Again! : Here We Go Again! As new pizza types arrive, we must change this code
If this is the only place it occurs, it’s not the end of the world
Nonetheless, let’s try to…
… Separate those things that vary from those that don’t (again)
And now that we know about Open-Closed:
Let’s try to make this code extensible while being closed to modification
A Pizza-Creating Class : A Pizza-Creating Class public class SimplePizzaFactory {
public Pizza createPizza(String type) {
Pizza pizza = null;
if (type.equals('cheese')) {
pizza = new CheesePizza();
} else if (type.equals('pepperoni')) {
pizza = new PepperoniPizza();
} else if (type.equals('clam')) {
pizza = new ClamPizza();
} else if (type.equals('veggie')) {
pizza = new VeggiePizza();
}
return pizza;
}
}
Refactoring orderPizza( ) : Refactoring orderPizza( ) public class PizzaStore {
SimplePizzaFactory factory;
public PizzaStore(SimplePizzaFactory factory) {
this.factory = factory;
}
public Pizza orderPizza(String type) {
Pizza pizza = factory.createPizza(type);
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
}
Revisiting Employee : Revisiting Employee We were uncomfortable with the switch-based factory inside the abstract Employee
Good call!
Move it out to its own class
EmployeeFactory
Thereby we separate object creation from object use (decoupling clients from future factory changes)
See next slide
A Better Employee Hierarchy : A Better Employee Hierarchy See code now…
A Variation on SimplePizzaFactory : A Variation on SimplePizzaFactory We could just use a static method to return the correct type of Pizza:
SimplePizzaFactory.createPizza(String)
In any case, it should be available to other potential clients
PizzaShopMenu, for example
So it should be in its own class
So changes are isolated from clients
See page 117
Limitations of a Static Factory : Limitations of a Static Factory You can’t override a static method
Therefore you can’t have polymorphic creation
But you can have parametric creation
(We’ll do both soon)
Take your Pick
And Now… Life Happens : And Now… Life Happens There are different 'styles' of pizza
A New York style Cheese differs from a Chicago style
Your business savvy says you should support both
One “solution” : One 'solution' if (style == 'New York') {
if (type == 'cheese')
return new NewYorkCheesePizza();
else if (…)
…
}
else if (style == 'Chicago') {
if (type == 'cheese')
return new ChicagoCheesePizza();
else if (…)
…
Don’t Repeat Yourself! : Don’t Repeat Yourself! We’re cutting and pasting code again
Stop it!
Create different types of factories instead
One for each style
But they of course implement a common interface
Where should the factories reside?
That is, how shall they be called polymorphically?
A Polymorphic Factory : A Polymorphic Factory The correct createPizza( ) is called by polymorphism.
Using the Factory Method : Using the Factory Method public abstract class PizzaStore {
protected abstract Pizza createPizza(String type);
public Pizza orderPizza(String type) {
Pizza pizza = createPizza(type);
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
}
But Not Much Change Here : But Not Much Change Here public class NYPizzaStore extends PizzaStore {
Pizza createPizza(String item) {
if (item.equals('cheese')) {
return new NYStyleCheesePizza();
} else if (item.equals('veggie')) {
return new NYStyleVeggiePizza();
} else if (item.equals('clam')) {
return new NYStyleClamPizza();
} else if (item.equals('pepperoni')) {
return new NYStylePepperoniPizza();
} else return null;
}
} That’s the breaks with parametric factories. It allows us to vary the concrete Pizza type in one place, however.
Factory MethodCreational : Factory Method Creational Intent:
Lets a class defer object instantiation to subclasses.
Context:
A module uses an abstraction, so you want to follow the DIP and not depend on concrete details. The client module may not even know which class to instantiate.
Solution:
Define an interface for creating a family of objects, but let subclasses decide which class to instantiate.
Factory MethodClass Sketch : Factory Method Class Sketch
Parallel Hierarchies : Parallel Hierarchies A hierarchy for the products
A hierarchy for the factories
With a polymorphic factory method
See page 132
More Changes Afoot : More Changes Afoot Franchisees have been using inferior ingredients
Infidels!
So you want more control over how pizzas get made
While still allowing for stylistic differences
So we’re drilling down a little deeper to how pizzas are actually made
Controlling Ingredients : Controlling Ingredients Pizza ingredients come in families:
Sauce, cheese, dough, toppings
They all have these components
But with differences (e.g., parmesan vs. mozarella)
You want to ship ingredients together
So we need a factory to build families of ingredients
Note: my diagrams differ from the book’s
They’re better!
PizzaIngredientFactory : PizzaIngredientFactory Will have a factory method for each ingredient type
createDough, createSauce, etc.
These will be specialized in concrete subclasses by region
NYPizzaIngredientFactory, etc.
So we’re talking families of factory methods
The Ingredient Factories : The Ingredient Factories
Refactoring Pizza : Refactoring Pizza Pizza needs to collect the right kind of ingredients
We’ll just put this into the prepare method
But we’ll make it abstract so subclasses can override it
(This actually isn’t totally necessary, as you’ll see. It could just be implemented in Pizza)
What is necessary is that the correct ingredients are obtained
Getting the Right Ingredients : Getting the Right Ingredients Somewhere (in prepare for now) we say:
sauce = ingredientFactory.createSauce( );
cheese = ingredientFactory.createCheese( );
etc.
These are fields in Pizza
Before we just had a list called toppings
Now we’re taking over what those toppings are
The Pizza HierarchyNo more NYCheesePizza, ChicagoCheesePizza… : The Pizza Hierarchy No more NYCheesePizza, ChicagoCheesePizza…
What about PizzaStore? : What about PizzaStore? This is what establishes the ingredientFactory
And passes it to the pizza it creates with its (independent) Factory Method
The PizzaStore Hierarchy : The PizzaStore Hierarchy
NYPizzaStore.createPizza( )A parametric factory method : NYPizzaStore.createPizza( ) A parametric factory method protected Pizza createPizza(String item) {
Pizza pizza = null;
if (item.equals('cheese')) {
pizza = new CheesePizza(ingredientFactory);
pizza.setName('New York Style Cheese Pizza');
} else if (item.equals('veggie')) {
pizza = new VeggiePizza(ingredientFactory);
pizza.setName('New York Style Veggie Pizza');
} else if (item.equals('clam')) {
pizza = new ClamPizza(ingredientFactory);
pizza.setName('New York Style Clam Pizza');
} else if (item.equals('pepperoni')) {
pizza = new PepperoniPizza(ingredientFactory);
pizza.setName('New York Style Pepperoni Pizza');
}
return pizza;
}
The Process : The Process createPizza passes the correct ingredientFactory to CheesePizza
Abstract FactoryCreational : Abstract Factory Creational Intent:
Provide an interface for creating families of related or dependent objects without specifying their concrete classes.
Context:
Your system needs to be configured with one of multiple families of products, but it should be independent of how its products are created and assembled.
Solution:
Declare an abstract type with methods for creating each type of component. Subclasses create the correct concrete types for the family they represent.
Abstract FactoryClass Sketch : Abstract Factory Class Sketch
Factory Method vs. Abstract Factory : Factory Method vs. Abstract Factory Factory Method creates a single object
It can decay into a single static method
The abstract root often has a processing method that calls the factory method
i.e., the factory method is in the key abstraction the client uses
Abstract Factory creates families of related objects
It requires multiple polymorphic methods
Therefore clients must use a factory object
See pages 156-157
What Factories Accomplish : What Factories Accomplish They separate the creation of objects from their use
This minimizes coupling between clients and abstractions
Disposal Methods : Disposal Methods It is often necessary to destroy objects
Especially if they own resources other than memory
Mirror of Factory Method
Can have a static destroy
Can have a polymorphic destroy
Can have an 'automatic' destroy in C++
RAII – uses destructors
shared_ptr: a pointer proxy that deletes its data
Catch the
buzz on authorSTREAM
Copyright © 2002-2008 authorSTREAM. All rights reserved.