(创建型模式)Factory Method工厂方法模式

概述

在软件系统中,经常面临着“某个对象”的创建工作,由于需求的变化,这个对象的具体实现经常面临着剧烈的变化,但是它却拥有比较稳定的接口。

如何应对这种变化?提供一种封装机制来隔离出“这个易变对象”的变化,从而保持系统中“其它依赖该对象的对象”不随着需求的改变而改变?

 

意图

定义一个用户创建对象的接口,让子类决定实例化哪一个类。Factory Method使一个类的实例化延迟到其子类。

 

结构图


生活中的例子

工厂方法定义一个用于创建对象的接口,但是让子类决定实例化哪个类。压注成型演示了这种模式。塑料玩具制造商加工塑料粉,将塑料注入到希望形状的模具中。玩具的类别(车、人物等等)是由模具决定的。


工厂方法解说

在工厂方法模式中,核心的工厂类不再负责所有产品的创建,而是将具体创建工作交给子类去做。这个核心类仅仅负责给出具体工厂必须实现的接口,而不接触哪一个产品类被实例化这种细节。这使得工厂方法模式可以允许系统在不修改工厂角色的情况下引进新产品。在Factory Method模式中,工厂类与产品类往往具有平行的等级结构,它们之间一一对应。

现在我们考虑一个日志记录的例子(这里我们只是为了说明Factory Method模式,实际项目中的日志记录不会这么去做,也要比这复杂一些)。假定我们要设计日志记录的类,支持记录的方法有FileLogEventLog两种方式。在这里我们先不谈设计模式,那么这个日志记录的类就很好实现了:

/// <summary>
/// 功能:日志记录类
/// 编写:Kalen_Chen
/// 日期:2007年5月9日
/// </summary>

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语句在不断的变化,这样就引起了整个应用程序的不稳定,进一步分析上面的代码,发现对于EventLogFileLog是两种完全不同的记录方式,它们之间不应该存在必然的联系,而应该把它们分别作为单独的对象来对待。

/// <summary>
/// 功能:EventLog类
/// 编写:Kalen_Chen
/// 日期:2007年5月9日
/// </summary>

public   class  EventLog
{
    public void Write()
    {
        Console.WriteLine(
"EventLog Write Success!");
    }

}


/// <summary>
/// 功能:FileLog类
/// 编写:Kalen_Chen
/// 日期:2007年5月9日
/// </summary>

public   class  FileLog
{
    public void Write()
    {
        Console.WriteLine(
"FileLog Write Success!");
    }

}


进一步抽象,为它们抽象出一个共同的父类,结构图如下:



实现代码:

/// <summary>
///功能: Log类
/// 编写:Kalen_Chen
/// 日期:2007年5月9日
/// </summary>

public   abstract   class  Log
{
    public abstract void Write();
}



此时EventLog和FileLog类的代码应该如下:
/// <summary>
/// 功能:EventLog类
/// 编写:Kalen_Chen
/// 日期:2007年5月9日
/// </summary>

public   class  EventLog:Log
{
    
public override void Write()
..    
{
        Console.WriteLine(
"EventLog Write Success!");
    }

}


/// <summary>
/// 功能:FileLog类
/// 编写:Kalen_Chen
/// 日期:2007年5月9日
/// </summary>

public   class  FileLog:Log
{
    public override void Write()
    {
        Console.WriteLine(
"FileLog Write Success!");
    }

}



此时我们再看增加新的记录日志方式DatabaseLog的时候,需要做些事情?只需要增加一个继承父类Log的子类来实现,而无需再去修改EventLogFileLog类,这样的设计满足了类之间的层次关系,又很好的符合了面向对象设计中的单一职责原则,每一个类都只负责一件具体的事情。到这里似乎我们的设计很完美了,事实上我们还没看客户程序如何去调用。在应用程序中,我们要使用某一种日志记录方式,也许会用到如下这样的语句:

EventLog eventlog  =   new  EventLog();
eventlog.Write();

当日志记录的方式从EventLog变化为FileLog,我们就得修改所有程序代码中出现上面语句的部分,这样的工作量是可想而知的。此时就需要解藕具体的日志记录方式和应用程序。这就要引入Factory Method模式了,每一个日志记录的对象就是工厂所生产的产品,既然有两种记录方式,那就需要两个不同的工厂去生产了,代码如下:

/// <summary>
/// 功能:EventFactory类
/// 编写:Kalen_Chen
/// 日期:2007年5月9日
/// </summary>

public   class  EventFactory
{
    public EventLog Create()
    {
        return new EventLog();
    }

}


/// <summary>
/// 功能:FileFactory类
/// 编写:Kalen_Chen
/// 日期:2007年5月9日
/// </summary>

public   class  FileFactory
{
    public FileLog Create()
    {
        return new FileLog();
    }

}


这两个工厂和具体的产品之间是平行的结构,并一一对应,并在它们的基础上抽象出一个公用的接口,结构图如下:



实现代码如下:

/// <summary>
/// 功能:LogFactory类
/// 编写:Kalen_Chen
/// 日期:2007年5月9日
/// </summary>

public   abstract   class  LogFactory
{
    public abstract Log Create();
}


此时两个具体工厂的代码应该如下:

/// <summary>
/// 功能:EventFactory类
/// 编写:Kalen_Chen
/// 日期:2007年5月9日
/// </summary>

public   class  EventFactory:LogFactory
{
    public override Log Create()
    {
        return new EventLog();
    }

}


/// <summary>
/// 功能:FileFactory类
/// 编写:Kalen_Chen
/// 日期:2007年5月9日
/// </summary>

public   class  FileFactory:LogFactory
{
    public override Log Create()
    {
        return new FileLog();
    }

}


这样通过工厂模式我们把上面那对象创建工作封装在了工厂中,此时我们似乎完成了整个Factory Method的过程。这样达到了我们应用程序和具体日志记录对象之间解藕的目的了吗?看一下此时客户段程序代码:

 

/// <summary>
/// 功能:App类
/// 编写:Kalen_Chen
/// 日期:2007年5月9日
/// </summary>

public   class  App
{
    public static void Main(string[] args)
    {
        LogFactory factory 
= new EventFactory();
        Log log 
= factory.Create();
        log.Write();
    }

}

在客户程序中,我们有效地避免了具体产品对象和应用程序之间的耦合,可是我们也看到,增加了具体工厂对象和应用程序之间的耦合。那这样究竟带来什么好处呢?我们知道,在应用程序中,Log对象的创建是频繁的,在这里我们可以把

LogFactory factory = new EventFactory();

这句话放在一个类模块中,任何需要用到Log对象的地方仍然不变。要是换一种日志记录方式,只要修改一处为:

LogFactory factory = new FileFactory();

其余的任何地方我们都不需要去修改。有人会说那还是修改代码,其实在开发中我们很难避免修改,但是我们可以尽量做到只修改一处。

其实利用.NET的特性,我们可以避免这种不必要的修改。下面我们利用.NET中的反射机制来进一步修改我们的程序,这时候要用到配置文件了,如果我们向使用哪一种日志记录方式,则在相应的配置文件中设置如下:

< appSettings >
    < add  key ="factoryName"  value ="EventFactory" ></ add >
</ appSettings >

此时客户端代码如下:

/// <summary>
/// 功能:App类
/// 编写:Kalen_Chen
/// 日期:2007年5月9日
/// </summary>

public   class  App
{
    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();
    }

}


现在我们看到,在引进新产品(日志记录方式)的情况下,我们并不需要去修改工厂类,而只是增加新的产品类和新的工厂类(注意:这是任何时候都不能避免的),这样很好的符合了开放封闭原则。

 

实现要点

1.      Factory Method模式的两种情况:一是Creator类是一个抽象类且它不提供它所声明的工厂方法的实现;二是Creator是一个具体的类且它提供一个工厂方法的缺省实现。

2.      工厂方法是可以带参数的。

3.      工厂的作用并不仅仅只是创建一个对象,它还可以做对象的初始化,参数的设置等。

 

效果

1.      用工厂方法在一个类的内部创建对象通常比直接创建对象更灵活。

2.      Factory Method模式通过面向对象的手法,将所要创建的具体对象的创建工作延迟到了子类,从而提供了一种扩展的策略,较好的解决了这种紧耦合的关系。

 

适用性

在一下情况下,适用于工厂方法模式:

1.      在一个类不知道它所必须创建的对象的类的时候。

2.      当一个类希望由它的子类来指定它所创建的对象的时候。

3.      当类将创建对象的职责委托给多个帮助子类中的某一个,并且你希望将哪一个帮助子类是代理者这一信息局部化的时候。

 

总结

Factory Method模式是设计模式中应用最为广泛的模式,通过本文,相信读者已经对它有了一定的认识。然而我们要明确的是:在面向对象的编程中,对象的创建工作非常简单,对象的创建时机却很重要。Factory Method要解决的就是对象的创建时机问题,它提供了一种扩展的策略,很好地符合了开放封闭原则。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值