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
© Copyright 2024