学习设计模式第七 - 工厂方法模式
本文摘取自TerryLee(李会军)老师的设计模式系列文章,版权归TerryLee,仅供个人学习参考。转载请标明原作者TerryLee。部分示例代码来自DoFactory。
概述
在软件系统中,经常面临着"某个对象"的创建工作,由于需求的变化,这个对象的具体实现经常面临着剧烈的变化,但是它却拥有比较稳定的接口。如何应对这种变化?提供一种封装机制来隔离出"这个易变对象"的变化,从而保持系统中"其它依赖该对象的对象"不随着需求的改变而改变。这就是要说的Factory Method模式了。
意图
定义一个用于创建对象的接口,让子类决定实例化哪一个类。Factory Method使一个类的实例化延迟到其子类。
UML
图1.工厂方法模式UML图
参与者
这个模式涉及的类或对象:
-
Product
-
定义工厂方法创建的对象所遵守的接口
-
ConcreteProduct
-
实现Product接口
-
Creator
-
声明工厂方法,其返回Product类一个对象。Creator也可以定义一个默认实现,返回一个默认的ConcreteProduct对象。
-
可以调用工厂方法来创建一个Product对象。
-
ConcreteCreator
-
重写工厂方法来返回一个ConcreteProduct的实例。
适用性
某些情况下,客户端不知道或不应知道应该初始化几个候选类中的哪一个。此时使用工厂方法模式,客户端可以使用统一的接口创建对象,同时仍然可以控制实例化哪一个具体类的对象。工厂方法模式的核心在于可扩展性。工厂方法模式常用于管理,维护或操作应用程序中一系列同时存在的有很多共同特征但不相同的对象。例如,一个文档管理系统,当多个文档被作为IDocument对象的集合时扩展性会更强。这些文档可能有文本,Word文档或Visio文档。但它们的共同点是都有作者,标题,类型,尺寸,位置及页数等属性。当新引入的文档类型实现了IDocument接口后,这些公共的属性会保持一致。为了支持新引入的类型的创建,工厂类的方法可能需要调整,一般来说,当工厂方法含有参数时,需要进行调整,反之可能不需要调整。
工厂方法常用于一些起"管理"作用的组件,如文档管理、账户管理及权限管理等。具体到代码中,怎么判断什么时候应该使用或现有代码已使用了工厂方法模式呢?当一个方法返回一系列实现了抽象类或接口的对象时其就是工厂方法。
在以下情况下,适合使用工厂方法模式:
-
当一个类不知道它所必须创建的对象的类的时候。
-
当一个类希望由它的子类来指定它所创建的对象的时候。
-
当类将创建对象的职责委托给多个帮助子类中的某一个,并且你希望将哪一个帮助子类是代理者这一信息局部化的时候。
DoFactory GoF代码
通过代码可以看出给不同对象的创建提供了巨大的灵活性。抽象创建者提供一个默认的对象,其每个子类可以初始化一个具有更多功能的对象。
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 | // Factory Method pattern // Structural example using System; namespace DoFactory.GangOfFour.Factory.Structural { class MainApp { static void Main() { // An array of creators Creator[] creators = new Creator[2]; creators[0] = new ConcreteCreatorA(); creators[1] = new ConcreteCreatorB(); // Iterate over creators and create products foreach (Creator creator in creators) { Product product = creator.FactoryMethod(); Console.WriteLine( "Created {0}" , product.GetType().Name); } // Wait for user Console.ReadKey(); } } // "Product" abstract class Product { } // "ConcreteProductA" class ConcreteProductA : Product { } // "ConcreteProductB" class ConcreteProductB : Product { } // "Creator" abstract class Creator { public abstract Product FactoryMethod(); } // "ConcreteCreator" class ConcreteCreatorA : Creator { public override Product FactoryMethod() { return new ConcreteProductA(); } } // "ConcreteCreator" class ConcreteCreatorB : Creator { public override Product FactoryMethod() { return new ConcreteProductB(); } } } |
下面代码演示了在创建不同文档的程序中工厂方法提供的灵活性。Report和Resume两个具体创建者是Document这个创建者的子类。值得注意的是工厂方法由基类构造函数中被调用。
例子中涉及到的类与工厂方法模式中标准的类对应关系如下:
-
Product - Page
-
ConcreteProduct - SkillsPage, EducationPage, ExperiencePage
-
Creator - Document
-
ConcreteCreator - Report, Resume
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 | // Factory Method pattern // Real World example using System; using System.Collections.Generic; namespace DoFactory.GangOfFour.Factory.RealWorld { class MainApp { static void Main() { // Note: constructors call Factory Method Document[] documents = new Document[2]; documents[0] = new Resume(); documents[1] = new Report(); // Display document pages foreach (Document document in documents) { Console.WriteLine( "\n" + document.GetType().Name + "--" ); foreach (Page page in document.Pages) { Console.WriteLine( " " + page.GetType().Name); } } // Wait for user Console.ReadKey(); } } // "Product" abstract class Page { } // "ConcreteProduct" class SkillsPage : Page { } // "ConcreteProduct" class EducationPage : Page { } // "ConcreteProduct" class ExperiencePage : Page { } // "ConcreteProduct" class IntroductionPage : Page { } // "ConcreteProduct" class ResultsPage : Page { } // "ConcreteProduct" class ConclusionPage : Page { } // "ConcreteProduct" class SummaryPage : Page { } // "ConcreteProduct" class BibliographyPage : Page { } // "Creator" abstract class Document { private List<Page> _pages = new List<Page>(); // Constructor calls abstract Factory method public Document() { this .CreatePages(); } public List<Page> Pages { get { return _pages; } } // Factory Method public abstract void CreatePages(); } // "ConcreteCreator" class Resume : Document { // Factory Method implementation public override void CreatePages() { Pages.Add( new SkillsPage()); Pages.Add( new EducationPage()); Pages.Add( new ExperiencePage()); } } // "ConcreteCreator" class Report : Document { // Factory Method implementation public override void CreatePages() { Pages.Add( new IntroductionPage()); Pages.Add( new ResultsPage()); Pages.Add( new ConclusionPage()); Pages.Add( new SummaryPage()); Pages.Add( new BibliographyPage()); } } } |
.NET优化版的例子中,数组被List<T>列表替换,并使用C# 3.0的列表初始化器来初始化。
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 | // Factory Method Design Pattern // .NET Optimized example using System; using System.Collections.Generic; namespace DoFactory.GangOfFour.Factory.NETOptimized { class MainApp { static void Main() { // Document constructors call Factory Method var documents = new List<Document> { new Resume(), new Report() }; // Display document pages foreach ( var document in documents) { Console.WriteLine(document + "--" ); foreach ( var page in document.Pages) { Console.WriteLine( " " + page); } Console.WriteLine(); } // Wait for user Console.ReadKey(); } } // "Product" abstract class Page { // Override. Display class name public override string ToString() { return this .GetType().Name; } } // "ConcreteProduct" class SkillsPage : Page { } // "ConcreteProduct" class EducationPage : Page { } // "ConcreteProduct" class ExperiencePage : Page { } // "ConcreteProduct" class IntroductionPage : Page { } // "ConcreteProduct" class ResultsPage : Page { } // "ConcreteProduct" class ConclusionPage : Page { } // "ConcreteProduct" class SummaryPage : Page { } // "ConcreteProduct" class BibliographyPage : Page { } // "Creator" abstract class Document { // Constructor invokes Factory Method public Document() { this .CreatePages(); } // Gets list of document pages public List<Page> Pages { get ; protected set ; } // Factory Method public abstract void CreatePages(); // Override public override string ToString() { return this .GetType().Name; } } // "ConcreteCreator" class Resume : Document { // Factory Method implementation public override void CreatePages() { Pages = new List<Page> { new SkillsPage(), new EducationPage(), new ExperiencePage() }; } } // "ConcreteCreator" class Report : Document { // Factory Method implementation public override void CreatePages() { Pages = new List<Page> { new IntroductionPage(), new ResultsPage(), new ConclusionPage(), new SummaryPage(), new BibliographyPage() }; } } } |
来自《深入浅出设计模式》的例子
这个例子使用工厂方式模式实现了一个比萨店联盟,不同的具体工厂表示不同的比萨店,而不同的比萨店可以有不同的比萨品种,这个选择由客户端来决定,并由具体工厂负责生产具体产品的对象。
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 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 | using System; using System.Collections.Generic; using System.Text; namespace DoFactory.HeadFirst.Factory.Pizza { class PizzaTestDrive { static void Main( string [] args) { PizzaStore nyStore = new NYPizzaStore(); PizzaStore chicagoStore = new ChicagoPizzaStore(); Pizza pizza = nyStore.OrderPizza( "cheese" ); Console.WriteLine( "Ethan ordered a " + pizza.Name + "\n" ); pizza = chicagoStore.OrderPizza( "cheese" ); Console.WriteLine( "Joel ordered a " + pizza.Name + "\n" ); pizza = nyStore.OrderPizza( "clam" ); Console.WriteLine( "Ethan ordered a " + pizza.Name + "\n" ); pizza = chicagoStore.OrderPizza( "clam" ); Console.WriteLine( "Joel ordered a " + pizza.Name + "\n" ); pizza = nyStore.OrderPizza( "pepperoni" ); Console.WriteLine( "Ethan ordered a " + pizza.Name + "\n" ); pizza = chicagoStore.OrderPizza( "pepperoni" ); Console.WriteLine( "Joel ordered a " + pizza.Name + "\n" ); pizza = nyStore.OrderPizza( "veggie" ); Console.WriteLine( "Ethan ordered a " + pizza.Name + "\n" ); pizza = chicagoStore.OrderPizza( "veggie" ); Console.WriteLine( "Joel ordered a " + pizza.Name + "\n" ); // Wait for user Console.ReadKey(); } } #region Pizza Stores public abstract class PizzaStore { public abstract Pizza CreatePizza( string item); public Pizza OrderPizza( string type) { Pizza pizza = CreatePizza(type); Console.WriteLine( "--- Making a " + pizza.Name + " ---" ); pizza.Prepare(); pizza.Bake(); pizza.Cut(); pizza.Box(); return pizza; } } public class NYPizzaStore : PizzaStore { public override Pizza CreatePizza(String item) { Pizza pizza = null ; switch (item) { case "cheese" : pizza = new NYStyleCheesePizza(); break ; case "veggie" : pizza = new NYStyleVeggiePizza(); break ; case "clam" : pizza = new NYStyleClamPizza(); break ; case "pepperoni" : pizza = new NYStylePepperoniPizza(); break ; } return pizza; } } public class ChicagoPizzaStore : PizzaStore { public override Pizza CreatePizza(String item) { Pizza pizza = null ; switch (item) { case "cheese" : pizza = new ChicagoStyleCheesePizza(); break ; case "veggie" : pizza = new ChicagoStyleVeggiePizza(); break ; case "clam" : pizza = new ChicagoStyleClamPizza(); break ; case "pepperoni" : pizza = new ChicagoStylePepperoniPizza(); break ; } return pizza; } } // Alternatively use this store public class DependentPizzaStore { public Pizza CreatePizza( string style, string type) { Pizza pizza = null ; if (style == "NY" ) { switch (type) { case "cheese" : pizza = new NYStyleCheesePizza(); break ; case "veggie" : pizza = new NYStyleVeggiePizza(); break ; case "clam" : pizza = new NYStyleClamPizza(); break ; case "pepperoni" : pizza = new NYStylePepperoniPizza(); break ; } } else if (style == "Chicago" ) { switch (type) { case "cheese" : pizza = new ChicagoStyleCheesePizza(); break ; case "veggie" : pizza = new ChicagoStyleVeggiePizza(); break ; case "clam" : pizza = new ChicagoStyleClamPizza(); break ; case "pepperoni" : pizza = new ChicagoStylePepperoniPizza(); break ; } } else { Console.WriteLine( "Error: invalid store" ); return null ; } pizza.Prepare(); pizza.Bake(); pizza.Cut(); pizza.Box(); return pizza; } } #endregion #region Pizzas public abstract class Pizza { public Pizza() { Toppings = new List< string >(); } public string Name { get ; set ; } public string Dough { get ; set ; } public string Sauce { get ; set ; } public List< string > Toppings { get ; set ; } public void Prepare() { Console.WriteLine( "Preparing " + Name); Console.WriteLine( "Tossing dough..." ); Console.WriteLine( "Adding sauce..." ); Console.WriteLine( "Adding toppings: " ); foreach ( string topping in Toppings) { Console.WriteLine( " " + topping); } } public void Bake() { Console.WriteLine( "Bake for 25 minutes at 350" ); } public virtual void Cut() { Console.WriteLine( "Cutting the pizza into diagonal slices" ); } public void Box() { Console.WriteLine( "Place pizza in official PizzaStore box" ); } public override string ToString() { StringBuilder display = new StringBuilder(); display.Append( "---- " + Name + " ----\n" ); display.Append(Dough + "\n" ); display.Append(Sauce + "\n" ); foreach ( string topping in Toppings) { display.Append(topping.ToString() + "\n" ); } return display.ToString(); } } public class ChicagoStyleVeggiePizza : Pizza { public ChicagoStyleVeggiePizza() { Name = "Chicago Deep Dish Veggie Pizza" ; Dough = "Extra Thick Crust Dough" ; Sauce = "Plum Tomato Sauce" ; Toppings.Add( "Shredded Mozzarella Cheese" ); Toppings.Add( "Black Olives" ); Toppings.Add( "Spinach" ); Toppings.Add( "Eggplant" ); } public override void Cut() { Console.WriteLine( "Cutting the pizza into square slices" ); } } public class ChicagoStylePepperoniPizza : Pizza { public ChicagoStylePepperoniPizza() { Name = "Chicago Style Pepperoni Pizza" ; Dough = "Extra Thick Crust Dough" ; Sauce = "Plum Tomato Sauce" ; Toppings.Add( "Shredded Mozzarella Cheese" ); Toppings.Add( "Black Olives" ); Toppings.Add( "Spinach" ); Toppings.Add( "Eggplant" ); Toppings.Add( "Sliced Pepperoni" ); } public override void Cut() { Console.WriteLine( "Cutting the pizza into square slices" ); } } public class ChicagoStyleClamPizza : Pizza { public ChicagoStyleClamPizza() { Name = "Chicago Style Clam Pizza" ; Dough = "Extra Thick Crust Dough" ; Sauce = "Plum Tomato Sauce" ; Toppings.Add( "Shredded Mozzarella Cheese" ); Toppings.Add( "Frozen Clams from Chesapeake Bay" ); } public override void Cut() { Console.WriteLine( "Cutting the pizza into square slices" ); } } public class ChicagoStyleCheesePizza : Pizza { public ChicagoStyleCheesePizza() { Name = "Chicago Style Deep Dish Cheese Pizza" ; Dough = "Extra Thick Crust Dough" ; Sauce = "Plum Tomato Sauce" ; Toppings.Add( "Shredded Mozzarella Cheese" ); } public override void Cut() { Console.WriteLine( "Cutting the pizza into square slices" ); } } public class NYStyleVeggiePizza : Pizza { public NYStyleVeggiePizza() { Name = "NY Style Veggie Pizza" ; Dough = "Thin Crust Dough" ; Sauce = "Marinara Sauce" ; Toppings.Add( "Grated Reggiano Cheese" ); Toppings.Add( "Garlic" ); Toppings.Add( "Onion" ); Toppings.Add( "Mushrooms" ); Toppings.Add( "Red Pepper" ); } } public class NYStylePepperoniPizza : Pizza { public NYStylePepperoniPizza() { Name = "NY Style Pepperoni Pizza" ; Dough = "Thin Crust Dough" ; Sauce = "Marinara Sauce" ; Toppings.Add( "Grated Reggiano Cheese" ); Toppings.Add( "Sliced Pepperoni" ); Toppings.Add( "Garlic" ); Toppings.Add( "Onion" ); Toppings.Add( "Mushrooms" ); Toppings.Add( "Red Pepper" ); } } public class NYStyleClamPizza : Pizza { public NYStyleClamPizza() { Name = "NY Style Clam Pizza" ; Dough = "Thin Crust Dough" ; Sauce = "Marinara Sauce" ; Toppings.Add( "Grated Reggiano Cheese" ); Toppings.Add( "Fresh Clams from Long Island Sound" ); } } public class NYStyleCheesePizza : Pizza { public NYStyleCheesePizza() { Name = "NY Style Sauce and Cheese Pizza" ; Dough = "Thin Crust Dough" ; Sauce = "Marinara Sauce" ; Toppings.Add( "Grated Reggiano Cheese" ); } } #endregion } |
工厂方法解说
在工厂方法模式中,核心的工厂类不再负责所有产品的创建,而是将要创建哪一个具体工厂类的选择交给客户端,将具体创建工作交给具体工厂类去做。这个核心类仅仅负责给出具体工厂必须实现的接口,而不接触哪一个产品类被实例化这种细节。这使得工厂方法模式可以允许系统在不修改工厂角色的情况下引进新产品。在Factory Method模式中,工厂类与产品类往往具有平行的等级结构,它们之间一一对应。而选择问题依然存在只是由简单工厂下的工厂类内部移到客户端代码进行。如果需要扩展功能原来是修改工厂类,现在需要修改客户端。基本上工厂方法克服了简单工厂违背开发-封闭原则的缺点,又保持了封装对象创建过程的优点。客户端选择了一个具体工厂就会得到对应具体的产品。
现在我们考虑一个日志记录的例子(这里我们只是为了说明Factory Method模式,实际项目中的日志记录不会这么去做,也要比这复杂一些)。假定我们要设计日志记录的类,支持记录的方法有FileLog和EventLog两种方式。在这里我们先不谈设计模式,那么这个日志记录的类就很好实现了:
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 | // 日志记录类 public class Log { public void WriteEvent() { Console.WriteLine( "EventLog Success!" ); } public void WriteFile() { Console.WriteLine( "FileLog Success!" ); } public void Write( string LogType) { switch (LogType.ToLower()) { case "event" : WriteEvent(); break ; case "file" : WriteFile(); break ; default : break ; } } } |
这样的程序结构显然不能符合我们的要求,如果我们增加一种新的日志记录的方式DatabaseLog,那就要修改Log类,随着记录方式的变化,switch语句在不断的变化,这样就引起了整个应用程序的不稳定,进一步分析上面的代码,发现对于EventLog和FileLog是两种完全不同的记录方式,它们之间不应该存在必然的联系,而应该把它们分别作为单独的对象来对待。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | // EventLog类 public class EventLog { public void Write() { Console.WriteLine( "EventLog Write Success!" ); } } // FileLog类 public class FileLog { public void Write() { Console.WriteLine( "FileLog Write Success!" ); } } |
进一步抽象,为它们抽象出一个共同的父类,结构图如下:
图2. 工厂方法模式示例产品类模型图
实现代码:
1 2 3 4 5 | // Log类 public abstract class Log { public abstract void Write(); } |
此时EventLog和FileLog类的代码应该如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | // EventLog类 public class EventLog : Log { public override void Write() { Console.WriteLine( "EventLog Write Success!" ); } } // FileLog类 public class FileLog : Log { public override void Write() { Console.WriteLine( "FileLog Write Success!" ); } } |
此时我们再看增加新的记录日志方式DatabaseLog的时候,需要做哪些事情?只需要增加一个继承父类Log的子类来实现,而无需再去修改EventLog和FileLog类,这样的设计满足了类之间的层次关系,又很好的符合了面向对象设计中的单一职责原则,每一个类都只负责一件具体的事情。到这里似乎我们的设计很完美了,事实上我们还没有看客户程序如何去调用。在应用程序中,我们要使用某一种日志记录方式,也许会用到如下这样的语句:
1 2 | EventLog eventlog = new EventLog(); eventlog.Write(); |
当日志记录的方式从EventLog变化为FileLog,我们就得修改所有程序代码中出现上面语句的部分,这样的工作量是可想而知的。此时就需要解耦具体的日志记录方式和应用程序。这就要引入Factory Method模式了,每一个日志记录的对象就是工厂所生成的产品,既然有两种记录方式,那就需要两个不同的工厂去生产了,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | // EventFactory类 public class EventFactory { public EventLog Create() { return new EventLog(); } } // FileFactory类 public class FileFactory { public FileLog Create() { return new FileLog(); } } |
这两个工厂和具体的产品之间是平行的结构,并一一对应,并在它们的基础上抽象出一个公用的接口,结构图如下:
图3. 工厂方法模式示例工厂类模型图
实现代码如下:
1 2 3 4 5 | // LogFactory类 public abstract class LogFactory { public abstract Log Create(); } |
此时两个具体工厂的代码应该如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | // EventFactory类 public class EventFactory : LogFactory { public override EventLog Create() { return new EventLog(); } } // FileFactory类 public class FileFactory : LogFactory { public override FileLog Create() { return new FileLog(); } } |
这样通过工厂方法模式我们把上面那对象创建工作封装在了工厂中,此时我们似乎完成了整个Factory Method的过程。这样达到了我们应用程序和具体日志记录对象之间解耦的目的了吗?看一下此时客户端程序代码:
1 2 3 4 5 6 7 8 9 10 | // Client类 public class Client { public static void Main( string [] args) { LogFactory factory = new EventFactory(); Log log = factory.Create(); log.Write(); } } |
在客户程序中,我们有效地避免了具体产品对象和应用程序之间的耦合,可是我们也看到,增加了具体工厂对象和应用程序之间的耦合。那这样究竟带来什么好处呢?我们知道,在应用程序中,Log对象的创建是频繁的,在这里我们可以把
1 | LogFactory factory = new EventFactory(); |
这句话放在一个类模块中,任何需要用到Log对象的地方仍然不变。要是换一种日志记录方式,只要修改一处为:
1 | LogFactory factory = new FileFactory(); |
其余的任何地方我们都不需要去修改。有人会说那还是修改代码,其实在开发中我们很难避免修改,但是我们可以尽量做到只修改一处。
其实利用.NET的特性,我们可以避免这种不必要的修改。下面我们利用.NET中的反射机制来进一步修改我们的程序,这时就要用到配置文件了,如果我们想使用哪一种日志记录方式,则在相应的配置文件中设置如下:
1 2 3 | <appSettings> <add key= "factoryName" value= "EventFactory" ></add> </appSettings> |
此时客户端代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | // Client类 public class Client { public static void Main( string [] args) { string strfactoryName = ConfigurationSettings.AppSettings[ "factoryName" ]; LogFactory factory; factory = (LogFactory)Assembly.Load( "FactoryMethod" ).CreateInstance( "FactoryMethod." + strfactoryName); Log log = factory.Create(); log.Write(); } } |
现在我们看到,在引进新产品(日志记录方式)的情况下,我们并不需要去修改工厂类,而只是增加新的产品类和新的工厂类(注意:这是任何时候都不能避免的),这样很好的符合了开放封闭原则。
ASP.NET HTTP通道中的应用
Factory Method模式在ASP.NET HTTP通道中我们可以找到很多的例子。ASP.NET HTTP通道是System.Web命名空间下的一个类,WEB Server使用该类处理接收到的HTTP请求,并给客户端发送响应。HTTP通道主要的工作有Session管理,应用程序池管理,缓存管理,安全等。
System.Web.HttpApplicationFactory
HttpRuntime是HTTP通道的入口点,它根据每一个具体的请求创建一个HttpContext实例,HttpRuntime并没有确定它将要处理请求的HttpApplication对象的类型,它调用了一个静态的工厂方法HttpApplicationFactory.GetApplicationInstance,通过它来创建HttpContext实例。GetApplicationInstance使用HttpContext实例来确定针对这个请求该响应哪个虚拟路径,如果这个虚拟路径以前请求过,HttpApplication(或者一个继承于ASP.Global_asax的类的实例)将直接从应用程序池中返回,否则针对该虚拟路径将创建一个新的HttpApplication对象并返回。如下图所示:
图4. ASP.NET中工厂方法模式的应用
HttpApplicationFactory.GetApplicationInstance带有一个类型为HttpContext的参数,创建的所有对象(产品)都是HttpApplication的类型,通过反编译,来看一下GetApplicationInstance的实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | internal static IHttpHandler GetApplicationInstance(HttpContext context) { if (HttpApplicationFactory._customApplication != null ) { return HttpApplicationFactory._customApplication; } if (HttpDebugHandler.IsDebuggingRequest(context)) { return new HttpDebugHandler(); } if (!HttpApplicationFactory._theApplicationFactory._inited) { lock (HttpApplicationFactory._theApplicationFactory) { if (!HttpApplicationFactory._theApplicationFactory._inited) { HttpApplicationFactory._theApplicationFactory.Init(context); HttpApplicationFactory._theApplicationFactory._inited = true ; } } } return HttpApplicationFactory._theApplicationFactory.GetNormalApplicationInstance(context); } |
System.Web.IHttpHandlerFactory
我们来做进一步的探索,HttpApplication实例需要一个Handler对象来处理资源请求,HttpApplication的主要任务就是找到真正处理请求的类。HttpApplication首先确定了一个创建Handler对象的工厂,来看一下在Machine.config文件中的配置区<httphandlers>,在配置文件注册了应用程序的具体处理类。例如在Machine.config中对*.aspx的处理将映射到System.Web.UI.PageHandlerFactory类,而对*.ashx的处理将映射到System.Web.UI.SimpleHandlerFactory类,这两个类都是继承于IhttpHandlerFactory接口的具体类:
1 2 3 4 5 | <httpHandlers> <add verb= "*" path= "*.aspx" type= "System.Web.UI.PageHandlerFactory" /> <add verb= "*" path= "*.ashx" type= "System.Web.UI.SimpleHandlerFactory" /> … … </httpHandlers> |
这个配置区建立了资源请求的类型和处理请求的类之间的一个映射集。如果一个.aspx页面发出了请求,将会调用System.Web.UI.PageHandlerFactory类,HttpApplication调用接口IHttpHandlerFactory中的工厂方法GetHandler来创建一个Handler对象。当一个名为sample.aspx的页面发出请求时,通过PageHandlerFactory将返回一个ASP.SamplePage_aspx对象(具体产品),如下图:
图5. ASP.NET中工厂方法模式的应用(集成Handler在内)
IHttpHandlerFactory工厂:
1 2 3 4 5 6 | public interface IHttpHandlerFactory { // Methods IHttpHandler GetHandler(HttpContext context, string requestType, string url, string pathTranslated); void ReleaseHandler(IHttpHandler handler); } |
IHttpHandlerFactory.GetHandler是一个工厂方法模式的典型例子,在这个应用中,各个角色的设置如下:
-
抽象工厂角色:IHttpHandlerFactory
-
具体工厂角色:PageHandlerFactory
-
抽象产品角色:IHttpHandler
-
具体产品角色:ASP.SamplePage_aspx
进一步去理解
理解上面所说的之后,我们就可以去自定义工厂类来对特定的资源类型进行处理。第一步我们需要创建两个类去分别实现IHttpHandlerFactory和IHttpHandler这两个接口。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | public class HttpHandlerFactoryImpl : IHttpHandlerFactory { IHttpHandler IHttpHandlerFactory.GetHandler(HttpContext context, String requestType, String url, String pathTranslated) { return new HttpHandlerImpl(); } //IHttpHandlerFactory.GetHandler void IHttpHandlerFactory.ReleaseHandler(IHttpHandler handler) { /*no-op*/ } } //HttpHandlerFactoryImpl public class HttpHandlerImpl : IHttpHandler { void IHttpHandler.ProcessRequest(HttpContext context) { context.Response.Write( "sample handler invoked " ); } //ProcessRequest bool IHttpHandler.IsReusable { get { return false ; } } } //HttpHandlerImpl |
第二步需要在配置文件中建立资源请求类型和处理程序之间的映射。我们希望当请求的类型为*.sample时进入我们自定义的处理程序,如下:
1 2 3 | <httpHandlers> <add verb= "*" path= "*.sample" type= "HttpHandlerFactoryImpl,SampleHandler" /> </httpHandlers> |
最后一步我们需要把文件扩展*.sample映射到ASP.NET ISAPI扩展DLL(aspnet_isapi.dll)上。由于我们已经建立了用于处理新扩展文件的处理程序了,我们还需要把这个扩展名告诉IIS并把它映射到ASP.NET。如果你不执行这个步骤而试图访问*.sample文件,IIS将简单地返回该文件而不是把它传递给ASP.NET运行时。其结果是该HTTP处理程序不会被调用。
运行Internet服务管理器,右键点击默认Web站点,选择属性,移动到主目录选项页,并点击配置按钮。应用程序配置对话框弹出来了。点击添加按钮并在可执行字段输入aspnet_isapi.dll文件路径,在扩展字段输入.sample。其它字段不用处理;该对话框如下所示:
图6. ASP.NET中自定义Handler的注册
在.NET Framework中,关于工厂模式的使用有很多的例子,例如:
-
IEnumerable和IEnumerator就是一个Creator和一个Product
-
System.Security.Cryptography中关于加密算法的选择,SymmetricAlgorithm, AsymmetricAlgorithm, 和HashAlgorithm分别是三个工厂,他们各有一个静态的工厂方法Create
-
System.Net.WebRequest是 .NET Framework 的用于访问 Internet 数据的请求/响应模型的抽象基类。使用该请求/响应模型的应用程序可以用协议不可知的方式从 Internet 请求数据。在这种方式下,应用程序处理WebRequest类的实例,而协议特定的子类则执行请求的具体细节。请求从应用程序发送到某个特定的URI,如服务器上的Web页。URI从一个为应用程序注册的WebRequest子类列表中确定要创建的适当子类。注册 WebRequest 子类通常是为了处理某个特定的协议(如HTTP或FTP),但是也可以注册它以处理对特定服务器或服务器上的路径的请求。
实现要点
-
Factory Method模式的两种情况:一是Creator类是一个抽象类且它不提供它所声明的工厂方法的实现;二是Creator是一个具体的类且它提供一个工厂方法的缺省实现。
-
工厂方法是可以带参数的。
-
工厂的作用并不仅仅只是创建一个对象,它还可以做对象的初始化,参数的设置等。
效果
-
用工厂方法在一个类的内部创建对象通常比直接创建对象更灵活。
-
Factory Method模式通过面向对象的手法,将所要创建的具体对象的创建工作延迟到了子类,从而提供了一种扩展的策略,较好的解决了这种紧耦合的关系。
.NET中的应用
.NET Framework中工厂方法模式的应用很常见。一个例子是System.Convert类,其暴露许多静态方法,这些方法接受一个类型的对象并返回另一个类型的对象。例如,Convert.ToBoolean方法接受一个字符串并根据字符串为"true"或"false"返回一个布尔值(true或false)。同样的,许多内置值类型(如Int32,Double等)的Parse方法也是相同道理的例子。
在.NET中工厂方法被实现为静态方法,其创建一个在编译时决定的特定类型的对象。换句话说,这些方法不返回实现了统一基类或接口的对象,这些对象的真实类型只有在运行时才能确定。这正是抽象工厂与工厂方法的不同:抽象方法是虚拟的或抽象的,并且返回抽象类或接口对象。工厂方法是抽象的并返回类对象。.NET中两个静态工厂方法的例子是File.Open与Activator.Create。
总结
Factory Method模式是设计模式中应用最为广泛的模式,通过本文,相信读者已经对它有了一定的认识。然而我们要明确的是:在面向对象的编程中,对象的创建工作非常简单,对象的创建时机却很重要。Factory Method要解决的就是对象的创建时机问题,它提供了一种扩展的策略,很好地符合了开放封闭原则。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异