⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 pat3a.htm

📁 Design Pattern 设计模式
💻 HTM
📖 第 1 页 / 共 2 页
字号:
create new instances on behalf of the concrete factory.  You define anew factory by initializing an instance of a concrete factory with<EM>classes</EM> of products rather than by subclassing.  This approachtakes advantage of language characteristics, whereas the purePrototype-based approach is language-independent.<A NAME="auto1053"></A><P>Like the Prototype-based factory in Smalltalk just discussed, theclass-based version will have a single instance variable<CODE>partCatalog</CODE>, which is a dictionary whose key is the name ofthe part.  Instead of storing prototypes to be cloned,<CODE>partCatalog</CODE> stores the classes of the products. The method<CODE>make:</CODE> now looks like this:</P><A NAME="auto1054"></A><PRE>    make: partName        ^ (partCatalog at: partName) new</PRE></LI><A NAME="auto1055"></A><P></P><A NAME="extensible"></A><LI><EM>Defining extensible factories.</EM>AbstractFactory usually defines a different operation for each kind ofproduct it can produce.  The kinds of products are encoded in theoperation signatures.  Adding a new kind of product requires changingthe AbstractFactory interface and all the classes that depend on it.<A NAME="auto1056"></A><P>A more flexible but less safe design is to add a parameter tooperations that create objects.  This parameter specifies the kind ofobject to be created.  It could be a class identifier, an integer, astring, or anything else that identifies the kind of product.  In factwith this approach, AbstractFactory only needs a single "Make"operation with a parameter indicating the kind of object to create.This is the technique used in the Prototype- and the class-basedabstract factories discussed earlier.</P><A NAME="auto1057"></A><P>This variation is easier to use in a dynamically typed language likeSmalltalk than in a statically typed language like C++.  You can useit in C++ only when all objects have the same abstract base class orwhen the product objects can be safely coerced to the correct type bythe client that requested them.  The implementation section of <A HREF="pat3cfs.htm" TARGET="_mainDisplayFrame">Factory Method (107)</A> shows how to implement suchparameterized operations in C++.</P><A NAME="downcast"></A><A NAME="dynamic_cast"></A><P>But even when no coercion is needed, an inherent problem remains: Allproducts are returned to the client with the <EM>same</EM> abstractinterface as given by the return type.  The client will not be able todifferentiate or make safe assumptions about the class of a product.If clients need to perform subclass-specific operations, they won't beaccessible through the abstract interface.  Although the client couldperform a downcast (e.g., with <CODE>dynamic_cast</CODE> in C++), that'snot always feasible or safe, because the downcast can fail.  This is theclassic trade-off for a highly flexible and extensible interface.</P></LI></OL><A NAME="samplecode"></A><H2><A HREF="#knownuses"><IMG SRC="gifsb/down3.gif" BORDER=0 ALT="next: Known Uses"></A> Sample Code</H2> <A NAME="auto1058"></A><P>We'll apply the Abstract Factory pattern to creating the mazes wediscussed at the beginning of this chapter.</P><A NAME="auto1059"></A><P>Class <CODE>MazeFactory</CODE> can create components of mazes.  It buildsrooms, walls, and doors between rooms.  It might be used by a programthat reads plans for mazes from a file and builds the correspondingmaze.  Or it might be used by a program that builds mazes randomly.Programs that build mazes take a <CODE>MazeFactory</CODE> as an argumentso that the programmer can specify the classes of rooms, walls, anddoors to construct.</P><A NAME="MazeFactory-def"></A><PRE>    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); }    };</PRE><A NAME="auto1060"></A><P>Recall that the member function<CODE>CreateMaze</CODE> (<a href="chap3fs.htm#CreateMaze-def" TARGET="_mainDisplayFrame">page 84</A>)builds a small maze consisting of two rooms with a door between them.<CODE>CreateMaze</CODE> hard-codes the class names, making it difficultto create mazes with different components.</P><A NAME="auto1061"></A><P>Here's a version of <CODE>CreateMaze</CODE> that remedies thatshortcoming by taking a <CODE>MazeFactory</CODE> as a parameter:</P><A NAME="auto1062"></A><PRE>    Maze* MazeGame::CreateMaze (MazeFactory&amp; 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;    }</PRE><A NAME="enchnt-maze-fac"></A><P>We can create <CODE>EnchantedMazeFactory</CODE>, a factory for enchantedmazes, by subclassing <CODE>MazeFactory</CODE>.<CODE>EnchantedMazeFactory</CODE> will override different memberfunctions and return different subclasses of<CODE>Room</CODE>, <CODE>Wall</CODE>, etc.</P><A NAME="auto1063"></A><PRE>    class EnchantedMazeFactory : public MazeFactory {    public:        EnchantedMazeFactory();            virtual Room* MakeRoom(int n)  const            { return new EnchantedRoom(n, CastSpell()); }            virtual Door* MakeDoor(Room* r1, Room* r2)  const            { return new DoorNeedingSpell(r1, r2); }        protected:        Spell* CastSpell() const;    };</PRE><A NAME="auto1064"></A><P>Now suppose we want to make a maze game in which a room can have abomb set in it.  If the bomb goes off, it will damage the walls (atleast).  We can make a subclass of <CODE>Room</CODE> keep track ofwhether the room has a bomb in it and whether the bomb has gone off.We'll also need a subclass of <CODE>Wall</CODE> to keep track of thedamage done to the wall.  We'll call these classes<CODE>RoomWithABomb</CODE> and <CODE>BombedWall</CODE>.</P><A NAME="auto1065"></A><P>The last class we'll define is <CODE>BombedMazeFactory</CODE>, a subclassof <CODE>MazeFactory</CODE> that ensures walls are of class<CODE>BombedWall</CODE> and rooms are of class <CODE>RoomWithABomb</CODE>.<CODE>BombedMazeFactory</CODE> only needs to override two functions:</P><A NAME="auto1066"></A><PRE>    Wall* BombedMazeFactory::MakeWall () const {        return new BombedWall;    }        Room* BombedMazeFactory::MakeRoom(int n) const {        return new RoomWithABomb(n);    }</PRE><A NAME="auto1067"></A><P>To build a simple maze that can contain bombs, we simply call<CODE>CreateMaze</CODE> with a <CODE>BombedMazeFactory</CODE>.</P><A NAME="auto1068"></A><PRE>    MazeGame game;    BombedMazeFactory factory;        game.CreateMaze(factory);</PRE><A NAME="auto1069"></A><P><CODE>CreateMaze</CODE> can take an instance of<CODE>EnchantedMazeFactory</CODE> just as well to build enchantedmazes.</P><A NAME="auto1070"></A><P>Notice that the <CODE>MazeFactory</CODE> is just a collection of factorymethods.  This is the most common way to implement the AbstractFactory pattern.  Also note that <CODE>MazeFactory</CODE> is not anabstract class; thus it acts as both the AbstractFactory <EM>and</EM> theConcreteFactory.  This is another common implementation for simpleapplications of the Abstract Factory pattern.  Because the <CODE>MazeFactory</CODE> is a concrete class consistingentirely of factory methods, it's easy to make a new<CODE>MazeFactory</CODE> by making a subclass and overriding theoperations that need to change.</P><CODE>CreateMaze</CODE> used the <CODE>SetSide</CODE> operation on rooms tospecify their sides.  If it creates rooms with a<CODE>BombedMazeFactory</CODE>, then the maze will be made up of<CODE>RoomWithABomb</CODE> objects with <CODE>BombedWall</CODE> sides.  If<CODE>RoomWithABomb</CODE> had to access a subclass-specific member of<CODE>BombedWall</CODE>, then it would have to cast areference to its walls from <CODE>Wall*</CODE> to<CODE>BombedWall*</CODE>.  This downcasting is safe as long as the argument<EM>is</EM> in fact a <CODE>BombedWall</CODE>, which is guaranteed to betrue if walls are built solely with a <CODE>BombedMazeFactory</CODE>.</P><A NAME="auto1072"></A><P>Dynamically typed languages such as Smalltalk don't require downcasting,of course, but they might produce run-time errors if they encounter a<CODE>Wall</CODE> where they expect a <EM>subclass</EM> of <CODE>Wall</CODE>.Using Abstract Factory to build walls helps prevent these run-timeerrors by ensuring that only certain kinds of walls can becreated.</P><A NAME="auto1073"></A><P>Let's consider a Smalltalk version of <CODE>MazeFactory</CODE>, one witha single <CODE>make</CODE> operation that takes the kind of object tomake as a parameter.  Moreover, the concrete factory stores theclasses of the products it creates.</P><A NAME="auto1074"></A><P>First, we'll write an equivalent of <CODE>CreateMaze</CODE> inSmalltalk:</P><A NAME="auto1075"></A><PRE>    createMaze: aFactory        | room1 room2 aDoor |        room1 := (aFactory make: #room) number: 1.        room2 := (aFactory make: #room) number: 2.        aDoor := (aFactory make: #door) from: room1 to: room2.        room1 atSide: #north put: (aFactory make: #wall).        room1 atSide: #east put: aDoor.        room1 atSide: #south put: (aFactory make: #wall).        room1 atSide: #west put: (aFactory make: #wall).        room2 atSide: #north put: (aFactory make: #wall).        room2 atSide: #east put: (aFactory make: #wall).        room2 atSide: #south put: (aFactory make: #wall).        room2 atSide: #west put: aDoor.        ^ Maze new addRoom: room1; addRoom: room2; yourself</PRE><A NAME="auto1076"></A><P>As we discussed in the Implementation section, <CODE>MazeFactory</CODE>needs only a single instance variable <CODE>partCatalog</CODE> to providea dictionary whose key is the class of the component.  Also recall howwe implemented the <CODE>make:</CODE> method:</P><A NAME="auto1077"></A><PRE>    make: partName        ^ (partCatalog at: partName) new</PRE><A NAME="auto1078"></A><P>Now we can create a <CODE>MazeFactory</CODE> and use it to implement<CODE>createMaze</CODE>.  We'll create the factory using a method<CODE>createMazeFactory</CODE> of class <CODE>MazeGame</CODE>.</P><A NAME="auto1079"></A><PRE>    createMazeFactory        ^ (MazeFactory new            addPart: Wall named: #wall;            addPart: Room named: #room;            addPart: Door named: #door;            yourself)</PRE><A NAME="auto1080"></A><P>A <CODE>BombedMazeFactory</CODE> or <CODE>EnchantedMazeFactory</CODE> iscreated by associating different classes with the keys. For example,an <CODE>EnchantedMazeFactory</CODE> could be created like this:</P><A NAME="auto1081"></A><PRE>    createMazeFactory        ^ (MazeFactory new            addPart: Wall named: #wall;            addPart: EnchantedRoom named: #room;            addPart: DoorNeedingSpell named: #door;            yourself)</PRE><A NAME="knownuses"></A><H2><A HREF="#relatedpatterns"><IMG SRC="gifsb/down3.gif" BORDER=0 ALT="next: Related Patterns"></A> Known Uses</H2> <A NAME="auto1082"></A><P>InterViews uses the "Kit" suffix [<a href="bibfs.htm#interviews_kit" TARGET="_mainDisplayFrame">Lin92</A>] to denote AbstractFactoryclasses.  It defines WidgetKit and DialogKit abstract factoriesfor generating look-and-feel-specific user interface objects.InterViews also includes a LayoutKit that generates differentcomposition objects depending on the layout desired.  For example,a layout that is conceptually horizontal may require differentcomposition objects depending on the document's orientation (portraitor landscape).</P><A NAME="use-ab-fac"></A><P>ET++ [<a href="bibfs.htm#et++" TARGET="_mainDisplayFrame">WGM88</A>] uses the Abstract Factory pattern to achieveportability across different window systems (X Windows and SunView,for example).  The WindowSystem abstract base class defines theinterface for creating objects that represent window system resources(MakeWindow, MakeFont, MakeColor, for example).  Concrete subclassesimplement the interfaces for a specific window system. At run-time,ET++ creates an instance of a concrete WindowSystem subclass thatcreates concrete system resource objects.</P><A NAME="relatedpatterns"></A><H2><A HREF="#last"><IMG SRC="gifsb/down3.gif" BORDER=0 ALT="next: navigation"></A> Related Patterns</H2> <A NAME="auto1083"></A><P>AbstractFactory classes are often implemented with factorymethods (<A HREF="pat3cfs.htm" TARGET="_mainDisplayFrame">FactoryMethod (107)</A>), but they can also be implemented using <AHREF="pat3dfs.htm" TARGET="_mainDisplayFrame">Prototype (117)</A>.</P><A NAME="auto1084"></A><P>A concrete factory is often a singleton (<A HREF="pat3efs.htm"TARGET="_mainDisplayFrame">Singleton (127)</A>).<A NAME="last"></A><P><A HREF="#intent"><IMG SRC="gifsb/up3.gif" BORDER=0></A><BR><A HREF="pat3bfs.htm" TARGET="_mainDisplayFrame"><IMG SRC="gifsb/rightar3.gif"	ALIGN=TOP BORDER=0></A> <A HREF="pat3bfs.htm"	TARGET="_mainDisplayFrame">Builder</A><BR><A HREF="chapfs.htm" TARGET="_mainDisplayFrame"><IMG SRC="gifsb/leftarr3.gif"	ALIGN=TOP BORDER=0></A> <A HREF="chap3fs.htm"	TARGET="_mainDisplayFrame">Creational Patterns</A></P></BODY></HTML>

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -