Abstract Factory Abstract Factory anti-pattern Object - creational

Abstract Factory
Object - creational
Abstract Factory anti-pattern
Problem: Creating and manipulating families of related objects
Client
Client:
…
if product set 1 is used then
a = new ConcProdA1(…)
b = new ConcProdB1(…)
else
c = new ConcProdA2(…)
d = new ConcProdB2(…)
end;
...
if product set 1 is used then
a.operx(...);
else c.opery(...);
...
ConcProdA1
ConcProdB1
Family 1
ConcProdA2
ConcProdB2
Family 2
Symptoms:
- Clients are dependent on fixed set of families
- Hard to introduce a new family
- Errors in using conflicting families in clients
1
Abstract Factory design pattern
Name: Abstract Factory
Intent: Provide an interface for creating families of related or dependent
objects without specifying their concrete classes
Structure:
<<interface>>
AbsProductA
Client
operAx
operAy
<<interface>>
AbsProductB
operBx
operBy
<<interface>>
AbsFactory
CreateProductA
CreateProductB
ConcProdA1
ConcFactory1
ConcFactory2
CreateProductA
CreateProductB
CreateProductA
CreateProductB
ConcProdA2
ConcProdB1
ConcProdB2
Abstract Factory design pattern (cont.)
Applicable when:
• a client should be independent on how the products are created,
composed, and presented
• a system should be configured for a particular product family
• you need to enforce that only members of the same family are used
together
• you want to provide a class library of products but reveal only the
interfaces, not their implementations
2
Abstract Factory: Example
<<interface>>
MenuBar
<<interface>>
Button
Client
draw()
setName(String)
draw()
insertItem(...)
<<interface>>
WidgetFactory
CreateButton(): Button
CreateMenuBar(): MenuBar
WinButton
WinFactory
XFactory
CreateWinButton
CreateWinMenuBar
CreateXButton
CreateXMenuBar
XButton
WinMenuBar
XMenubar
Participants & Collaborations
• Abstract Factory
• declares an interface for operations to create abstract products
• ConcreteFactory
• implements the operations to create products
• AbstractProduct
• declares an interface for a type of product objects
• ConcreteProduct
• defines a product object to be created by the corresponding concrete
factory
• Implements the abstract product interface
• Client
• uses only interfaces declared by AbstractFactory and AbstractProduct
• Collaborations
• Normally, a single instance of a ConcreteFactory class is created at runtime. The concrete factory creates product objects having a particular
implementation.
• AbstractFactory defers creation of product objects to its ConcreteFactory
subclass.
3
Consequences
•
Isolation of concrete classes
•
•
•
concrete classes appear in ConcreteFactories not in client's code,
clients manipulate instances through their abstract interfaces
Exchanging of product families becomes easy
•
a ConcreteFactory appears only once, that is where it is instantiated
• easy to change, the whole product family changes
• all products in a family change at once, and change together
•
Promotes consistency among products
•
makes it easy to enforce that an application uses objects designed to work
together from only one family at a time.
• Supporting new kinds of products is difficult
•
•
•
Extending abstract factories to produce new kinds of products requires a change in
the interface of AbstractFactory
... and consequentely all subclasses
… and affects client code
Implementation Issues
• Creating the Products
• collection of Factory Methods
• A common way to implement the creation of products is to define a factory
method for each product in a concrete factory. It requires a new concrete
factory subclass for each product family, even if the product families would
differ only slightly
• can be also implemented using Prototype
• define a prototypical instance for each product in ConcreteFactory
• This eliminates the need for a new concrete factory for each product family
• Defining Extensible Factories
• AbstractFactory usually defines a different operation for each kind of
product it can produce. Adding a new kind of product requires changing
the AbstractFactory interface and all the classes that depend on it.
• A more flexible but less safe design is to add a parameter to operations
that create objects. This parameter could be a class identifier, an
integer, or anything else that specifies the kind of product to be created.
• Difficult to use in statically typed language, requires that all created
objects have the same abstract base class, and the client is not able to
access safely subclass specific operations as they will not be accessible
through the abstract interface
• This is a common trade-off for a highly flexible and extensible interface.
4
Creating The Maze with Abstract Factory
1. Define an abstract factory MazeFactory that can create components
of mazes.
•
•
•
Programs that build mazes take a MazeFactory as an argument so that
the programmer can specify the classes of objects to construct a maze.
The MazeFactory is just a collection of factory methods.
MazeFactory has a default implementations for factory methods, thus it
acts as both the AbstractFactory and the ConcreteFactory.
2. Rewrite CreateMaze to use these factory methods of MazeFactory.
•
CreateMaze takes a MazeFactory as a parameter to allow its client to
choose any desired maze factory.
3. Redefine some or all of the operations to specify variations in product
families by subclassing MazeFactory.
•
•
A BombedMazeFactory can redefine the MakeRoom and MakeWall
operations to return bombed varieties.
An EnchantedMazeFactory can redefine the MakeRoom and
MakeDoor operations to return enchanted varieties.
4. To build a simple maze that contains bombs, we simply call
CreateMaze with a BombedMazeFactory.
Creating The Maze with Abstract Factory
5
Factory Method (from book GoF)
Solving the Maze problem....
// default implementation needed, thus
// abstract and concrete factory
class MazeFactory {
public:
MazeFactory();
virtual Maze* MakeMaze() const
{ return new Maze; }
virtual Wall* MakeWall() const
{ return new Wall; }
virtual Room* MakeRoom(int n) const
{ return new Room(n); }
virtual Door* MakeDoor(Room* r1, Room* r2) const
{ return new Door(r1, r2); }
};
6
Solving the Maze problem....
Maze* MazeGame::CreateMaze (MazeFactory& factory) {
Maze* aMaze = factory.MakeMaze();
Room* r1 = factory.MakeRoom(1);
Room* r2 = factory.MakeRoom(2);
Door* aDoor = factory.MakeDoor(r1, r2);
aMaze->AddRoom(r1);
aMaze->AddRoom(r2);
r1->SetSide(North, factory.MakeWall());
r1->SetSide(East, aDoor);
r1->SetSide(South, factory.MakeWall());
r1->SetSide(West, factory.MakeWall());
r2->SetSide(North, factory.MakeWall());
r2->SetSide(East, factory.MakeWall());
r2->SetSide(South, factory.MakeWall());
r2->SetSide(West, aDoor);
return aMaze;
}
Solving the Maze problem....
// Bombed version only needs to redefine two functions
Class BombedMazeFactory : public MazeFactory {
Public:
BombedMazeFactory();
virtual Wall* MakeWall() const {
return new BombedWall;
}
virtual Room* MakeRoom(int n) const {
return new RoomWithABomb(n);
}
}
//To build a maze with bombs we simply call CreateMaze with
//a bombed maze factory as a parameter
MazeGame game;
BombedMazeFactory bfactory;
game.CreateMaze(bfactory);
7
Comments on AF
•
Abstract factory decomposes the problem by responsibility
•
•
•
Reasons for defining families
•
•
•
•
•
who is using particular objects
who is deciding upon which particular objects to use
different operating systems (cross platform applications)
different performance guidelines
different versions of application
different traits of users of the application
Variations not requiring subclassing of the base factory
•
•
use a configuration file that specifies which objects to instantiate
In Java you can have the configuration file contain class names to
use javas class Class to instantiate the objects (reflection)
8