标签:工作 str 工厂方法 面向 load 直接 sharp 转换 cto
简单工厂模式虽然简单,但是存在一个很严重的问题:由于静态工厂方法是根据传入的参数不同来创建不同的产品的,所以当系统中需要引入新产品时,就需要修改工厂类的源代码,这将违背开闭原则。为了实现增加新产品而不修改原有代码,工厂方法模式应运而生。
A科技公司欲开发一个系统运行日志记录器(Logger),该记录器可以通过多种途径保存系统的运行日志,例如通过文件或数据库记录,用户可以通过修改配置文件灵活地更换日志记录方式。在设计各类日志记录器时,A科技公司的开发人员发现需要对日志记录器进行一些初始化工作,初始化参数的设置过程较为复杂,而且某些参数的设置有严格的先后次序,否则可能会发生记录失败。如何封装记录器的初始化过程并保证多种记录器切换的灵活性是A科技公司开发人员面临的一个难题。
开发人员对需求进行分析,发现该日志记录器有如下两个设计要点:
开发人员最开始使用简单工厂模式对日志记录器进行了设计,结构图如下:
LoggerFactory 充当创建日志记录器的工厂,CreateLogger() 负责创建日志记录日,ILogger 是抽象日志记录器的接口,FileLogger 和 DatabaseLogger 是具体的日志记录器。工厂类 LoggerFactory 的代码如下:
public class LoggerFactory
{
public static ILogger CreateLogger(string args)
{
ILogger logger = null;
if (args.Equals("db", StringComparison.OrdinalIgnoreCase))
{
//连接数据库,代码省略
//创建数据库日志记录器对象
logger = new DatabaseLogger();
//初始化数据库日志记录器,代码省略
}
else if (args.Equals("file", StringComparison.OrdinalIgnoreCase))
{
//创建日志文件,代码省略
//创建文件日志记录器队形
logger = new FileLogger();
//初始化文件日志记录器,代码省略
}
return logger;
}
}
虽然从上边的代码可以看出简单工厂模式实现了对象的创建和使用分离,但是仍然存在两个问题:
工厂方法模式的动机之一就是为了解决以上两个问题。
在简单工厂模式中只提供一个工厂类,这个工厂类处于实例化产品类的中心位置,他需要知道每个产品类的创建细节,并决定在何时实例化哪一个产品类。简单工厂最大的缺点就是每当有新的产品要加入系统的时候,就必须修改工厂类,在静态工厂方法中添加新产品的业务逻辑,这就违反了开闭原则。另外,简单工厂模式中,所有产品的创建都由同一个工厂类负责,工厂类的职责过于繁重,业务逻辑较为复杂,具体产品和工厂类之间的耦合度较高,严重影响了系统的扩展性和灵活性。工厂方法模式刚好就解决了这一点。
工厂方法模式,不再提供一个统一的工厂类来创建所有的产品对象,而是针对不同的产品提供不同的工厂,系统提供一个与产品等级结构对应的工厂等级结构。定义如下:
工厂方法模式(Factory Method Pattern):定义一个用于创建对象的接口,让子类决定将哪一个类实例化。工厂方法模式让一个类的实例化延迟到其子类。工厂方法模式又简称为工厂模式(Factory Patter),又可称作虚拟构造器模式(Virtual Constructor Pattern)或多态工厂模式(Polymorphic Factory Pattern)。工厂方法模式是一种类创建型模式。
工厂方法模式提供一个抽象工厂接口来声明抽象工厂方法,而由其子类来具体实现工厂方法,创建具体的产品对象。结构图如下:
从上图可以看出,工厂方法模式包含以下4个角色:
与简单工厂模式相比,工厂方法模式的主要区别就是引入了抽象工厂角色。抽象工厂可以是接口、抽象类或具体类。典型代码如下:
public interface IFactory{
IProduct FactoryMethod();
}
抽象工厂中声明了工厂方法但并未实现工厂方法,具体产品对象的创建由其子类负责。客户端针对抽象工厂编程,可以在运行时再指定具体工厂类。不同的具体工厂类可以创建不同的具体产品。
public class ConcreteFactory : IFactory{
public IProduct FactoryMethod(){
return new ConcreteProduct
}
}
具体工厂类除了创建具体产品对象之外,还可以负责产品对象的初始化工作以及一些资源和环境配置工,比如:连接数据库、创建文件等。
开发人员决定是使用工厂方法模式来设计日志记录器,基本结构如图:
ILogger 充当抽象产品,其子类 FileLogger 、DatabaseLogger 是具体产品。ILoggerFactory 充当抽象工厂,FileLoggerFactory、DatabaseLoggerFactory 充当具体工厂。
完整代码如下:
/// <summary>
/// 日志记录器接口:抽象产品
/// </summary>
public interface ILogger
{
void WriteLog();
}
/// <summary>
/// 文件日志记录器:具体产品
/// </summary>
public class FileLogger : ILogger
{
public void WriteLog()
{
Console.WriteLine("文件日志记录!");
}
}
/// <summary>
/// 数据库日志记录器:具体产品
/// </summary>
public class DatabaseLogger : ILogger
{
public void WriteLog()
{
Console.WriteLine("数据库日志记录!");
}
}
/// <summary>
/// 日志记录器工厂接口:抽象工厂
/// </summary>
public interface ILoggerFactory
{
ILogger CreateLogger();
}
/// <summary>
/// 文件日志记录器工厂类:具体工厂
/// </summary>
public class FileLoggerFactory : ILoggerFactory
{
public ILogger CreateLogger()
{
//创建文件日志记录器对象
var logger = new FileLogger();
//创建文件,省略代码
return logger;
}
}
/// <summary>
/// 数据库日志记录器工厂类:具体工厂
/// </summary>
public class DatabaseLoggerFactory : ILoggerFactory
{
public ILogger CreateLogger()
{
//连接数据库,代码省略
//创建数据库日志记录器对象
var logger = new DatabaseLogger();
//初始化数据库日志记录器,代码省略
return logger;
}
}
客户端测试代码:
class Program
{
static void Main(string[] args)
{
var factory = new FileLoggerFactory();//可引入配置文件实现
var logger = factory.CreateLogger();
logger.WriteLog();
}
}
输出:
目前来说代码还存在一些问题,就是如果客户端要更换具体的日志记录器,就需要修改客户端的具体日志记录器工厂类的创建,这一点上来说违背了开闭原则。
为了让系统具有更好的灵活性和可扩展性,开发人员决定对日志记录器客户端代码进行重构,希望最终可以达到在不修改客户端任何代码的情况下更换或增加新的日志记录方式。
在客户端代码中将不再使用 new 关键字来创建工厂对象,而是将具体工厂类的类名存储在配置文件中(比如XML中),通过读取配置文件获取工厂类的类名字符串,然后再借助 .NET 的反射机制,根据类名字符串生成对象。
有这么一句话 反射反射,程序员的快乐
,由此可见反射在开发中有着举足轻重的地位,在很多框架中都可以看到它的身影。
反射的定义:
反射是指在程序运行时获取已知名称的类或已有对象的相关信息的一种机制,包括类的方法、属性、父类等信息,还包括实例的创建和实例类型的判断等。
创建配置文件 App.config:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<!--value为具体工厂类的完全限定名(命名空间+类名)-->
<add key="LoggerFactory" value="LXP.DesignPattern.FactoryMethod.v2.FileLoggerFactory"/>
</appSettings>
</configuration>
创建一个配置文件的帮助类 AppConfigHelper 代码如下:
/// <summary>
/// 配置文件帮助类
/// </summary>
public class AppConfigHelper
{
/// <summary>
/// 获取具体日志工厂方法
/// </summary>
/// <returns></returns>
public static object GetLoggerFactory()
{
try
{
var loggerFactoryName = ConfigurationManager.AppSettings["LoggerFactory"];
var type = Type.GetType(loggerFactoryName);
return type == null ? null : Activator.CreateInstance(type);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
return null;
}
}
客户端测试代码:
class Program
{
static void Main(string[] args)
{
//var factory = new FileLoggerFactory();//可引入配置文件实现
var factory = (ILoggerFactory) AppConfigHelper.GetLoggerFactory();
var logger = factory.CreateLogger();
logger.WriteLog();
}
}
后期如果要新增一个日志记录器,就创建一个具体的日志记录器(需要实现ILogger接口),然后新增一个具体的日志记录器的工厂类,然后将该工厂类的完全限定名(命名空间+类名)替换配置文件中原有工厂类类名字符串即可。原有类库代码和客户端无需做任何修改,完全符合开闭原则。
.NET 中反射有多种方法:
1.如果要反射一个 DLL 中的类,并且程序并没有引用该 DLL(即对该程序来说,这个DLL中的类是一个未知的类型),可通过以下方法:
Assembly assembly = Assembly.LoadFile("程序集路径,不能是相对路径"); // 加载程序集(EXE 或 DLL)
object obj = assembly.CreateInstance("类的完全限定名(即包括命名空间)"); // 创建类的实例
2.如果要反射当前项目中的类
//方法1
Assembly assembly = Assembly.GetExecutingAssembly(); // 获取当前程序集
object obj = assembly.CreateInstance("类的完全限定名(命名空间 + 类名)"); // 创建类的实例,返回为 object 类型,需要强制类型转换
//方法2
Type type = Type.GetType("类的完全限定名(命名空间 + 类名)");
object obj = type.Assembly.CreateInstance(type);
//方法3
Type type = Type.GetType("类的完全限定名(命名空间 + 类名)");
object obj = Activator.CreateInstance(type);
详情的使用这里就不展开了,大家可以自行搜索。
有时候,为了进一步简化客户端的使用,还可以对客户端隐藏工厂方法。此时,在工厂类中将直接调用产品类的业务方法,客户端无需调用工厂方法创建具体的产品,直接通过工厂即可使用所创建的对象中的业务方法。
这时,需要需要将原来的工厂接口改为抽象工厂类,在抽象类中添加一个方法,在该方法中创建了具体的产品,并调用产品的业务方法。具体代码如下:
/// <summary>
/// 将工厂接口改为抽象类
/// </summary>
public abstract class LoggerFactory
{
/// <summary>
/// 在工厂类中直接调用日志记录器类的业务方法 WriteLog()
/// </summary>
public void WriteLog()
{
var logger = this.CreateLogger();
logger.WriteLog();
}
public abstract ILogger CreateLogger();
}
具体的工厂类需要将实现 ILoggerFactory
修改为继承抽象类 LoggerFactory
。
客户端代码修改:
class Program
{
static void Main(string[] args)
{
var factory = (ILoggerFactory) AppConfigHelper.GetLoggerFactory();
factory.WriteLog();//直接使用工厂对象来调用产品对象的业务方法
}
}
既继承了简单工厂模式的优点,又弥补了简单工厂模式的不足。
工厂方法模式是使用频率最高的设计模式之一。
如果您觉得这篇文章有帮助到你,欢迎推荐,也欢迎关注我的公众号。
https://github.com/crazyliuxp/DesignPattern.Simples.CSharp
标签:工作 str 工厂方法 面向 load 直接 sharp 转换 cto
原文地址:https://www.cnblogs.com/imlxp/p/14457789.html