A Tale of Two Factories: One New and One Reused
This is the first of three posts on the actual AIR ActionScript 3.0 Design Pattern catalog. Previously, I had discussed the thinking that went into the design of the Catalog, and here is the first actual AIR application with the Creational Patterns portion of the catalog all set up and ready to use. Figure 1 shows what each page looks like: (By the way, the actual image in the catalog is almost twice the size as what you see in Figure 1.)

Figure 1: Design Pattern Catalog Page
Before going on, you can download all of the source code and the AIR application by clicking the Download button. Additionally, you may want to access the Catalog Online.
![]()
I changed the color combinations. I concluded that Kuler’s 1944mustang color set was the best for this project because I needed a white background and I liked the orange for highlight. I used red for labeling on the diagrams, but it was used sparsely and provided a consistency.
After several false starts, I finally settled on the Factory Method, and by the time I was finished, I was glad I did. It’s an easy design pattern, keeps the classes loosely connected and is very flexible. In fact I used two Factory Method patterns—one was reused from an earlier application where the factory method was used to send DataProvider data to the Client. (See Refactoring with Multiple Design Patterns.)
The new Factory Method created instances of the pages assembled from several text fields and a graphic. Initially, I was going to have a single Product class and a different ConcreteProduct class for each pattern (23 in all), and that was exactly what I started doing. However, I soon discovered that the only thing that I needed to do was to pass a string parameter with the data name for each pattern, and a single ConcreteProduct class could handle the whole thing. Each of the classes representing a ConcreteProduct class was no smaller than the single general ConcreteProduct; so it did not necessitate bloating a class to cut down on the number of classes.
Finally, all of the data for the page are stored in folders for each individual design pattern. Each folder has a single PNG file and 12 text files. All of the contents of the 23 patterns have identical names and the pattern name is keyed to the folder name. Figure 2 shows an overview of the classes, the data folders and the Client connections to the data.

Figure 2: Files and Data Folders
As you can see in Figure 2, using multiple design patterns of the same type is no different than using single design patterns or different design patterns simultaneously. All you need is to have your Client hooked in where it needs to make the request.
Files, Files, Everywhere!
Figure 2 shows only five of the needed 23 folders have been completed. Fortunately, only five design patterns are part of the Creational category; so I get a break between stints of the mind-numbing chore of adding text to text files. Adding the remaining folders is a matter of copying and pasting the current ones and then changing most of the text content and replacing the PNG file with the class diagram. If you’re in a hurry for the rest of the patterns you can do it yourself—most of the summaries are direct quotes from The Gang of Four or paraphrased to save space. The same is true with the rest of the materials, but I added the kinds of things that are specific to ActionScript 3.0, and of course the code snippets are all ActionScript 3.0. Figure 3 shows the contents of each folder:

Figure 3: Text and Graphic files
Because all of the headers are the same, none of them have to be changed, and some of the text files are simply single words such as the name of the design pattern or the type of pattern. Using the WhatVaries AIR app, it’s easy to get what varies in each pattern as well. However, getting all of the text for components (participants), code examples and uses can be tricky because there’s only limited space for each.
While it may be tedious to enter all of the data for each and every design pattern, you’ll learn a lot—especially if you are using the Gang of Four’s book or ActionScript 3.0 Design Patterns. Also, there’s a lot of information on this blog you can use to fill out the data.
To add further design patterns here’s all you have to do:
- Copy and paste one of the current design pattern folders and all of its contents and then rename it using lower case no-space name. For example, the Chain of Responsibility can be named cor
- Add the label name and the data name of the design pattern in the CatData class. Use the same lowercase name for the data name as you used to name the folder for the pattern.
- Draw a class diagram with a width no greater than 500px and save it as a PNG file using the name, diagram.png in the folder where you have the other files for the pattern.
- Open the text files and change the content to reflect the characteristics of the design pattern. The files with “head” in the filename (e.g., codehead.txt) should be left unchanged but still have to be in the folder
That’s it. It also reflects what I like about design patterns. Once you get the basic structure set up; changes and reuse are easy.
The Page-Making Factory
Since we’re reusing a design pattern, you can review it at Refactoring with Multiple Design Patterns . However, to understand how the new use of the Factory Method works, each of the classes are reviewed here.
First of all, the Client is remarkably clean, and all requests, other than the initial one to provide data for the List component, relies on user-input in the List menu. Only a single conditional statement is used anywhere in this application, and it can be found in the Client where checking content in the DisplayObject. So you’ll find a very simple Client making requests for data and to see the content of a design pattern. The following listing shows what little code is required in the Client class:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | package { import flash.display.Sprite; import fl.controls.List; import fl.data.DataProvider; import fl.events.ListEvent; //Client Class public class Client extends Sprite { private var factory:Creator = new ConcreteCreator ; private var catalog:DataProvider; private var list:List = new List ; private var dp:String; private var dpDisplay:Sprite; public function Client() { setMenu(); } private function setMenu():void { //Re-use Factory that provides data for DataProvider var create:DPCreator = new ConcreteDPCreator ; var catalog:DataProvider = new DataProvider ; catalog = create.selectData(); //Set up the list list.setSize(126,650); list.x = 0,list.y = 0; list.dataProvider = catalog; addChild(list); list.addEventListener(ListEvent.ITEM_CLICK,choosePattern); } private function choosePattern(e:ListEvent):void { dp = String(e.item.data); //Only conditional in the entire program if (dpDisplay) { removeChild(dpDisplay); } dpDisplay = factory.makeDP(dp); addChild(dpDisplay); } } } |
As you can see, once the List menu is set up, the choosePattern() method does all the work. Further it is accomplished using very little code.
The next class is the Creator. It supplies the interface for the factory. As an abstract class, it cannot be instantiated, but you can include implemented methods. In this case, one method is implemented and the other is abstract.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | package { //Asbstract class //Creator import flash.display.Sprite; import flash.errors.IllegalOperationError; public class Creator { private var dpNow:Sprite; public function makeDP(dp:String):Sprite { dpNow = chooseDP(dp); return dpNow; } // ABSTRACT Method (must be overridden in a subclass) protected function chooseDP(dp:String):Sprite { throw new IllegalOperationError("Abstract method: must be overridden in a subclass"); return null; } } } |
The factory itself is a subclass of the abstract Creator class. It must implement the abstract method, and really nothing else. Originally when I had planned on individual ConcreteProduct classes for each design pattern, I had a big switch statement with 23 cases. However, by adding a parameter to the Catalog constructor (the ConcreteProduct class), all that’s passed is a string based on the List selected item’s data value.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | package { //Factory import flash.display.Sprite; import flash.errors.IllegalOperationError; public class ConcreteCreator extends Creator { override protected function chooseDP(dp:String):Sprite { return(new Catalog(dp)); } } } |
The Product and Concrete Product Classes
The big classes in this application are the two that handle the product elements. That should not come as a surprise because the product is pretty big—12 text fields with text and a graphic file. The abstract Product class has both abstract and implemented methods and lots of them. With 284 lines of code (including spaces and comments), this was the biggest abstract class I’d ever created. Half the methods are abstract and the other half are implemented. Essentially, it works like a giant getter/setter—or loader/placer to be more precise. Once each load is complete, a mirror function (method) “catches” the loaded materials and places it where it belongs on the DisplayObject (aka stage).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 | package { import flash.errors.IllegalOperationError; import flash.net.URLLoader; import flash.display.Sprite; import flash.text.TextField; import flash.text.TextFieldType; import flash.events.Event; import flash.text.TextFormat; //Abstract class public class Product extends Sprite { private var loadCheck:URLLoader; private var txtFld:TextField = new TextField(); private var typeFld:TextField = new TextField(); private var sumHeadFld:TextField = new TextField(); private var sumFld:TextField = new TextField(); private var varyHeadFld:TextField = new TextField(); private var varyFld:TextField = new TextField(); private var usesHeadFld:TextField = new TextField(); private var usesFld:TextField = new TextField(); private var codeHeadFld:TextField = new TextField(); private var codeFld:TextField = new TextField(); private var comHeadFld:TextField = new TextField(); private var comFld:TextField = new TextField(); private var textFormat:TextFormat = new TextFormat(); private const TEXTCOLOR:uint = 0x263248; private const TYPECOLOR:uint = 0xFF9800; private const HEADCOLOR:uint = 0x263248; private const BODYCOLOR:uint = 0x000000; protected function loadInfo(txtInfo:String):void { throw new IllegalOperationError("Abstract method: must be overridden in a subclass"); } protected function loadType(txtInfo:String):void { throw new IllegalOperationError("Abstract method: must be overridden in a subclass"); } protected function loadSumHead(txtInfo:String):void { throw new IllegalOperationError("Abstract method: must be overridden in a subclass"); } protected function loadSum(txtInfo:String):void { throw new IllegalOperationError("Abstract method: must be overridden in a subclass"); } protected function loadVaryHead(txtInfo:String):void { throw new IllegalOperationError("Abstract method: must be overridden in a subclass"); } protected function loadVary(txtInfo:String):void { throw new IllegalOperationError("Abstract method: must be overridden in a subclass"); } protected function loadUsesHead(txtInfo:String):void { throw new IllegalOperationError("Abstract method: must be overridden in a subclass"); } protected function loadUses(txtInfo:String):void { throw new IllegalOperationError("Abstract method: must be overridden in a subclass"); } protected function loadCodeHead(txtInfo:String):void { throw new IllegalOperationError("Abstract method: must be overridden in a subclass"); } protected function loadCode(txtInfo:String):void { throw new IllegalOperationError("Abstract method: must be overridden in a subclass"); } protected function loadComHead(txtInfo:String):void { throw new IllegalOperationError("Abstract method: must be overridden in a subclass"); } protected function loadCom(txtInfo:String):void { throw new IllegalOperationError("Abstract method: must be overridden in a subclass"); } //Already Loaded protected function nowLoaded(e:Event):void { loadCheck = URLLoader(e.target); textFormat.font = "Arial Black"; textFormat.size = 18; txtFld.type = TextFieldType.DYNAMIC; txtFld.width = 300; txtFld.wordWrap = true; txtFld.textColor = TEXTCOLOR; txtFld.defaultTextFormat = textFormat; txtFld.text = loadCheck.data; addChild(txtFld); txtFld.x = 128,txtFld.y = 10; } protected function nowType(e:Event):void { loadCheck = URLLoader(e.target); textFormat.font = "Arial Black"; textFormat.size = 14; typeFld.type = TextFieldType.DYNAMIC; typeFld.width = 300; typeFld.wordWrap = true; typeFld.textColor = TYPECOLOR; typeFld.defaultTextFormat = textFormat; typeFld.text = loadCheck.data; addChild(typeFld); typeFld.x = 368,typeFld.y = 16; } protected function nowSumHead(e:Event):void { loadCheck = URLLoader(e.target); textFormat.font = "Arial Black"; textFormat.size = 14; sumHeadFld.type = TextFieldType.DYNAMIC; sumHeadFld.width = 175; sumHeadFld.wordWrap = true; sumHeadFld.textColor = HEADCOLOR; sumHeadFld.defaultTextFormat = textFormat; sumHeadFld.text = loadCheck.data; addChild(sumHeadFld); sumHeadFld.x = 148,sumHeadFld.y = 409; } protected function nowSum(e:Event):void { loadCheck = URLLoader(e.target); textFormat.font = "Arial"; textFormat.size = 12; sumFld.type = TextFieldType.DYNAMIC; sumFld.width = 200; sumFld.wordWrap = true; sumFld.textColor = BODYCOLOR; sumFld.defaultTextFormat = textFormat; sumFld.text = loadCheck.data; addChild(sumFld); sumFld.x = 148,sumFld.y = 431; } protected function nowVaryHead(e:Event):void { loadCheck = URLLoader(e.target); textFormat.font = "Arial Black"; textFormat.size = 14; varyHeadFld.type = TextFieldType.DYNAMIC; varyHeadFld.width = 175; varyHeadFld.wordWrap = true; varyHeadFld.textColor = HEADCOLOR; varyHeadFld.defaultTextFormat = textFormat; varyHeadFld.text = loadCheck.data; addChild(varyHeadFld); varyHeadFld.x = 684,varyHeadFld.y = 409; } protected function nowVary(e:Event):void { loadCheck = URLLoader(e.target); textFormat.font = "Arial"; textFormat.size = 12; varyFld.type = TextFieldType.DYNAMIC; varyFld.width = 175; varyFld.wordWrap = true; varyFld.textColor = BODYCOLOR; varyFld.defaultTextFormat = textFormat; varyFld.text = loadCheck.data; addChild(varyFld); varyFld.x = 684,varyFld.y = 431; } protected function nowUsesHead(e:Event):void { loadCheck = URLLoader(e.target); textFormat.font = "Arial Black"; textFormat.size = 14; usesHeadFld.type = TextFieldType.DYNAMIC; usesHeadFld.width = 175; usesHeadFld.wordWrap = true; usesHeadFld.textColor = HEADCOLOR; usesHeadFld.defaultTextFormat = textFormat; usesHeadFld.text = loadCheck.data; addChild(usesHeadFld); usesHeadFld.x = 148,usesHeadFld.y = 504; } protected function nowUses(e:Event):void { loadCheck = URLLoader(e.target); textFormat.font = "Arial"; textFormat.size = 12; usesFld.type = TextFieldType.DYNAMIC; usesFld.width = 175; usesFld.wordWrap = true; usesFld.textColor = BODYCOLOR; usesFld.defaultTextFormat = textFormat; usesFld.text = loadCheck.data; addChild(usesFld); usesFld.x = 148,usesFld.y = 528; } protected function nowCodeHead(e:Event):void { loadCheck = URLLoader(e.target); textFormat.font = "Arial Black"; textFormat.size = 14; codeHeadFld.type = TextFieldType.DYNAMIC; codeHeadFld.width = 175; codeHeadFld.wordWrap = true; codeHeadFld.textColor = HEADCOLOR; codeHeadFld.defaultTextFormat = textFormat; codeHeadFld.text = loadCheck.data; addChild(codeHeadFld); codeHeadFld.x = 350,codeHeadFld.y = 409; } protected function nowCode(e:Event):void { loadCheck = URLLoader(e.target); textFormat.font = "Courier New"; textFormat.size = 12; codeFld.type = TextFieldType.DYNAMIC; codeFld.width = 323; codeFld.height = 215; codeFld.wordWrap = true; codeFld.textColor = BODYCOLOR; codeFld.defaultTextFormat = textFormat; codeFld.text = loadCheck.data; addChild(codeFld); codeFld.x = 350,codeFld.y = 430; } protected function nowComHead(e:Event):void { loadCheck = URLLoader(e.target); textFormat.font = "Arial Black"; textFormat.size = 14; comHeadFld.type = TextFieldType.DYNAMIC; comHeadFld.width = 175; comHeadFld.wordWrap = true; comHeadFld.textColor = HEADCOLOR; comHeadFld.defaultTextFormat = textFormat; comHeadFld.text = loadCheck.data; addChild(comHeadFld); comHeadFld.x = 684,comHeadFld.y = 458; } protected function nowCom(e:Event):void { loadCheck = URLLoader(e.target); textFormat.font = "Arial"; textFormat.size = 12; comFld.type = TextFieldType.DYNAMIC; comFld.width = 242; comFld.height = 164; comFld.wordWrap = true; comFld.textColor = BODYCOLOR; comFld.defaultTextFormat = textFormat; comFld.text = loadCheck.data; addChild(comFld); comFld.x = 684,comFld.y = 480; } //Load Image protected function loadImg(img:String):void { throw new IllegalOperationError("Abstract method: must be overridden in a subclass"); } } } |
Each implemented method works the same. It gets the Event instance from the loading sequence (implemented in the Catalog class—a Concrete Product) and then puts it where it belongs and formats it.
Finally, the ConcreteProduct is the Catalog class. It loads all of the materials it needs and then uses the methods it inherited from the Product class to place the text and graphic.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 | package { import flash.net.URLRequest; import flash.display.Loader; import flash.net.URLRequest; import flash.net.URLLoader; import flash.events.Event; public class Catalog extends Product { private var dp:String; private var nameText:String; private var typeText:String; private var sumHead:String; private var sumText:String; private var varyHead:String; private var varyText:String; private var usesHead:String; private var usesText:String; private var codeHead:String; private var codeText:String; private var comHead:String; private var comText:String; private var dpImage:String; private var url:URLRequest; private var loader:Loader; private var fileNow:URLRequest; private var txtLoader:URLLoader; private var typeLoader:URLLoader; private var sumHeadLoader:URLLoader; private var sumLoader:URLLoader; private var varyHeadLoader:URLLoader; private var varyLoader:URLLoader; private var usesHeadLoader:URLLoader; private var usesLoader:URLLoader; private var codeHeadLoader:URLLoader; private var codeLoader:URLLoader; private var comHeadLoader:URLLoader; private var comLoader:URLLoader; private var loadCheck:URLLoader; public function Catalog(dp:String) { this.dp=dp; dp+="/"; nameText = dp + "name.txt"; typeText = dp + "type.txt"; sumHead = dp + "sumhead.txt"; sumText = dp + "summary.txt"; varyHead = dp + "varyhead.txt"; varyText = dp + "vary.txt"; usesHead = dp + "useshead.txt"; usesText = dp + "uses.txt"; codeHead = dp + "codehead.txt"; codeText = dp + "code.txt"; comHead = dp + "comhead.txt"; comText = dp + "com.txt"; dpImage = dp + "diagram.png"; //All of these function are below loadInfo(nameText); loadType(typeText); loadSumHead(sumHead); loadSum(sumText); loadVaryHead(varyHead); loadVary(varyText); loadUsesHead(usesHead); loadUses(usesText); loadCodeHead(codeHead); loadCode(codeText); loadComHead(comHead); loadCom(comText); loadImg(dpImage); } override protected function loadInfo(txtInfo:String):void { txtLoader = new URLLoader(); txtLoader.addEventListener(Event.COMPLETE,nowLoaded); fileNow = new URLRequest(txtInfo); txtLoader.load(fileNow); } override protected function loadType(txtInfo:String):void { typeLoader = new URLLoader(); typeLoader.addEventListener(Event.COMPLETE,nowType); fileNow = new URLRequest(txtInfo); typeLoader.load(fileNow); } override protected function loadSumHead(txtInfo:String):void { sumHeadLoader = new URLLoader(); sumHeadLoader.addEventListener(Event.COMPLETE,nowSumHead); fileNow = new URLRequest(txtInfo); sumHeadLoader.load(fileNow); } override protected function loadSum(txtInfo:String):void { sumLoader = new URLLoader(); sumLoader.addEventListener(Event.COMPLETE,nowSum); fileNow = new URLRequest(txtInfo); sumLoader.load(fileNow); } override protected function loadVaryHead(txtInfo:String):void { varyHeadLoader = new URLLoader(); varyHeadLoader.addEventListener(Event.COMPLETE,nowVaryHead); fileNow = new URLRequest(txtInfo); varyHeadLoader.load(fileNow); } override protected function loadVary(txtInfo:String):void { varyLoader = new URLLoader(); varyLoader.addEventListener(Event.COMPLETE,nowVary); fileNow = new URLRequest(txtInfo); varyLoader.load(fileNow); } override protected function loadUsesHead(txtInfo:String):void { usesHeadLoader = new URLLoader(); usesHeadLoader.addEventListener(Event.COMPLETE,nowUsesHead); fileNow = new URLRequest(txtInfo); usesHeadLoader.load(fileNow); } override protected function loadUses(txtInfo:String):void { usesLoader = new URLLoader(); usesLoader.addEventListener(Event.COMPLETE,nowUses); fileNow = new URLRequest(txtInfo); usesLoader.load(fileNow); } override protected function loadCodeHead(txtInfo:String):void { codeHeadLoader = new URLLoader(); codeHeadLoader.addEventListener(Event.COMPLETE,nowCodeHead); fileNow = new URLRequest(txtInfo); codeHeadLoader.load(fileNow); } override protected function loadCode(txtInfo:String):void { codeLoader = new URLLoader(); codeLoader.addEventListener(Event.COMPLETE,nowCode); fileNow = new URLRequest(txtInfo); codeLoader.load(fileNow); } override protected function loadComHead(txtInfo:String):void { comHeadLoader = new URLLoader(); comHeadLoader.addEventListener(Event.COMPLETE,nowComHead); fileNow = new URLRequest(txtInfo); comHeadLoader.load(fileNow); } override protected function loadCom(txtInfo:String):void { comLoader = new URLLoader(); comLoader.addEventListener(Event.COMPLETE,nowCom); fileNow = new URLRequest(txtInfo); comLoader.load(fileNow); } //Load image override protected function loadImg(dpImage:String):void { url = new URLRequest(dpImage); loader=new Loader(); loader.load(url); addChild(loader); loader.x = 128,loader.y = 34; } } } |
That’s the whole enchilada, and it is a big one. Both of the product classes are much bigger than I like, but the nature of the application requires it. However, even with all of the methods and instances in two different Factory Method class sets, there’s only a single conditional statement, and it’s in the Client—where it doesn’t count as part of the design pattern. (I could even eliminate it if I put in a dummy display sprite—but why go fanatic?)
Finishing Up
All that’s left are 17 more design patterns and some kind of splash page. I think that the splash page should have the different elements of the UML class diagrams—what the different arrows and symbols mean. If you can think of anything else, let us know. Also, play around with the AIR version and let me know if you have any additional features that you think would help. Also, if you want to pitch in and help fill in the different design patterns data, contact me and I’ll be glad to recruit you! (Is there such a thing as an online Hero Medal?)

The ActionScript 3.0 Design Pattern Catalog: Creational Patterns by William B. Sanders, unless otherwise expressly stated, is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.
Related posts:

Bill Sanders
hi,
how about putting all the text data in a single xml file (or one xml file per design pattern) with different nodes for the different text types? and maybe a css style file with all text formating stuff. that would be much cleaner than a multitude of plain txt files and a lot of duplicated code with URLLoaders and textfield creation…
and further I hope that you are going to put in all the design pattern data for us as I think a “do it yourself” empty AIR app would be not very appealing and helpful for most users ;)
Hi Mystery Person,
I had thought of using XML files, but another project I am working uses XML files, and I felt better about using text files for this one. However, I still think it’s a good idea and it wouldn’t be a lot of work if you revised the Catalog project and re-did it with XML files. Please do so and send us your results. It would be dynamite!
As for this project and getting help I was sort of hoping that we’d get some better graphics for the Class Diagrams. Also, doing some of the grunt work on the information for the classes is just a matter of dropping it into a text file. I’ll take any help I can get still, but I won’t force you to do them all yourself.
Thanks for your great comment, and I hope to see your XML file idea put into action.
Kindest regards,
Bill