工厂方法模式(Factory Method Pattern)
- 工厂方法模式概述
工厂方法模式是为了弥补简单工厂模式的不足并且继承它的优点而延生出的一种设计模式,属于GoF中的一种。它能更好的符合开闭原则的要求。
定义:定义了一个用于创建对象的接口,但是让子类决定将哪一个类实例化。即让类的实例化延迟到子类
举个例子:大众汽车公司想必大家都不陌生,它旗下也有不少汽车品牌。大众汽车公司就好比一个汽车工厂,负责生产和销售汽车。它可以为客户提供一个客户需要的汽车。但是,如果客户需要的汽车大众公司目前还没有,但是公司想要盈利,就必须为此而设计汽车,在这种情况下,大众公司就要新添加一种汽车,同时要修改公司内部的生产环境(也就是工厂类的代码)。这就是简单工厂模式的运行情况。简单而言,就是工厂类(汽车公司)什么都要干,要修改必须大动干戈。因而一定程度上违背了开闭原则。而工厂方法模式则不一样,大众汽车公司不在总公司生产汽车,而是成立分公司,收购别的公司,成立具有针对性的汽车工厂专门生产对应的汽车。若客户的大量需求得不到满足,则总公司就另外成立新的二级公司(新品牌汽车的工厂)生产汽车,从而在不修改具体工厂的情况下引进新的产品。正如大众集团的收购一样。以下为简单工厂模式和工厂方法模式的区别:
- 工厂方法模式的结构与实现
结构:
- Product(抽象产品):定义产品的接口,是工厂方法模式所创建对象的公共父类(生产汽车)
- ConcreteProduct(具体产品):实现了抽象产品的接口,某类型的具体产品由专门的工厂创建(如具体类型的汽车)
- Factory(抽象工厂):它声明了工厂方法,用于返回一个产品。工厂方法模式的核心,所有创建对象的工厂必须实现该接口(创建生产汽车的工厂)
- ConcreteFactory(具体工厂):抽象工厂类的子类,实现了抽象工厂中声明的工厂方法,返回一个具体产品类的实例(对应具体的某一个汽车工厂)
实现:(日志记录器)
实现:
1 //Logger.cs 日志记录器接口 充当抽象产品接口 2 namespace FactoryMethodSample 3 { 4 interface Logger 5 { 6 void WriteLog();//抽象产品方法 7 } 8 } 9 10 //DatabaseLogger 数据库日志记录器 具体产品 11 namespace FactoryMethodSample 12 { 13 class DatabaseLogger : Logger 14 { 15 public void WriteLog() 16 { 17 //数据库日志记录 18 } 19 } 20 } 21 22 //FileLogger 文件日志记录器 具体产品 23 namespace FactoryMethodSample 24 { 25 class FileLogger : Logger 26 { 27 public void WriteLog() 28 { 29 //文件日志记录 30 } 31 } 32 } 33 34 //日志记录器工厂接口,充当抽象工厂 35 namespace FactoryMethodSample 36 { 37 interface LoggerFactory 38 { 39 Logger CreateLogger();//抽象工厂方法 40 } 41 } 42 //DatabaseLoggerFactory 数据库日志记录器工厂类,具体工厂 43 namespace FactoryMethodSample 44 { 45 class DatabaseLoggerFactory : LoggerFactory 46 { 47 public Logger CreateLogger() 48 { 49 //连接数据库 50 //创建记录器对象 51 Logger logger = new DatabaseLogger(); 52 ... 53 return logger; 54 } 55 } 56 } 57 //FileLoggerFactory 文件日志记录器工厂类,具体工厂 58 namespace FactoryMethodSample 59 { 60 class FileLoggerFactory : LoggerFactory 61 { 62 public Logger CreateLogger() 63 { 64 //创建文件日志记录器 65 Logger logger = new FileLogger(); 66 ... 67 return logger; 68 } 69 } 70 } 71 //Program 客户端测试 72 namespace FactoryMethodSample 73 { 74 class Program 75 { 76 77 static void Main(string [] args) 78 { 79 LoggerFactory factory;//抽象工厂 80 Logger logger;//抽象产品 81 factory = new FileLoggerFactory();//new DatabaseLoggerFactory 可以更换为数据库日志记录器 82 logger = factory.CreateLogger();//抽象工厂方法 83 logger.WriteLog();//抽象产品方法 84 .... 85 } 86 //如果要添加新的日志记录器,只要增加新的具体工厂类,并在客户端中修改具体工厂的类名便可以,从而避免了对原类的修改 87 } 88 }
- 工厂方法模式的重载
在某种情况下,可以用不同的方式来初始化一个产品类,在Logger记录器中,连接数据库的操作可以不用在每次CreateLog后再传入。而是可以将相关参数封装在一个object中,通过object对象将参数传入工厂类中,或者在参数列表中给出连接方式来连接数据库。为此,可以提供一组重载的工厂方法,以不同的方式创建产品。
代码修改如下:
1 namespace FactoryMethodSample 2 { 3 interface LoggerFactory 4 { 5 Logger CreateLogger();//抽象工厂方法 6 Logger CreateLogger(string args); 7 Logger CreateLogger(object obj); 8 } 9 } 10 11 //DatabaseLoggerFactory 数据库日志记录器工厂类,具体工厂 12 namespace FactoryMethodSample 13 { 14 class DatabaseLoggerFactory : LoggerFactory 15 { 16 public Logger CreateLogger() 17 { 18 //连接数据库 19 //创建记录器对象 20 Logger logger = new DatabaseLogger(); 21 ... 22 return logger; 23 } 24 } 25 26 class DatabaseLoggerFactory : LoggerFactory 27 { 28 public Logger CreateLogger(string args) 29 { 30 //用args连接数据库 31 // 32 Logger logger = new DatabaseLogger(); 33 ... 34 return logger; 35 } 36 } 37 38 class DatabaseLoggerFactory : LoggerFactory 39 { 40 public Logger CreateLogger(object obj) 41 { 42 //将参数封装在obj中来连接数据库 43 Logger logger = new DatabaseLogger(); 44 ... 45 return logger; 46 } 47 } 48 }
- 工厂方法模式的隐藏
在客户端中,为简化使用,可以隐藏工厂方法。在工厂类调直接调用产品类的方法,在客户端中无需用工厂方法创建产品对象,直接使用工厂对象即可调用所创建的产品对象中的业务方法
代码修改如下:
1 //LoggerFactory 修改 2 abstract class LoggerFactory//改接口为抽象类 3 { 4 public void WriteLog()//工厂类直接调用日志记录器的WriteLog(); 5 { 6 Logger logger = this.CreateLogger(); 7 logger.WriteLog(); 8 } 9 public abstract Logger CreateLogger(); 10 } 11 12 //客户端修改如下 13 namespace FactoryMethodSample 14 { 15 class Program 16 { 17 static void Main(string [] args) 18 { 19 LoggerFactory factory; 20 //Load configuration file; 21 string factoryString = ConfigurationManager.AppSettings["factory"]; 22 //反射生成对象 23 factory = (LoggerFactory)Assembly.Load("FactoryMethodSample").CreateInstance(factoryString); 24 factory.WriteLog();//直接使用工厂对象调用产品对象的业务方法 25 } 26 } 27 }
- 工厂方法模式的优缺点和适用环境
(1)工厂方法模式的优点
(1)用户只需要关心产品对应的工厂,甚至无需关心创建细节或具体产品类的类名
(2)基于工厂角色和产品的多态性设计是工厂模式的关键。它能自主决定如何创建哪种产品对象,而创建细节都封装在具体工厂内部
(3)在系统要添加新的产品时,无需修改抽象工厂和抽象产品提供的接口,无需修改客户端,也无需修改其他的具体工厂和具体产品,只要添加一个具体工厂和具体产品即可,从而提高系统的可扩展性(符合开闭原则)
(2)工厂方法模式的缺点
(1)在添加新产品时,要编写新的具体产品类,并要提供与之对应的具体工厂类。系统软件个数也成对增加,从而增加了系统的复杂度,带来更多开销
(2)由于系统的可扩展性,在客户端中要引入抽象层进行定义,从而增加了系统的抽象性和理解难度
(3)工厂方法模式的适用环境
(1)客户端不需要知道它需要的对象的类。只需知道对应的工厂即可
(2)抽象工厂类通过子类来指定创建那哪个对象。即抽象工厂类只需要提供一个创建产品的接口,而由其子类确定具体要创建的对象,利用多态性和里氏代换原则,子类对象将覆盖父类对象,从而使系统更容易扩展
- 在日志记录器实例中,在更换日志记录器时需要修改客户端代码。为了按照开闭原则的要求执行,可以在不修改任何客户端代码的基础上更换或增加新的日志记录方式。即通过配置文件和程序集的反射机制,读取配置文件中存储的类名字符串生成对象。这里没有进行讲解,可以自行搜索..~_~!!