在软件系统中,经常面临着“某个对象”的创建工作,由于需求的变化,这个对象的具体实现经常面临着剧烈的变化,但是它却拥有比较稳定的接口。如何应对这种变化?提供一种封装机制来隔离出“这个易变对象”的变化,从而保持系统中“其它依赖该对象的对象”不随着需求的改变而改变?
引子
程序架构应该尽可能保证主模块和各个子模块、高层模块和底层模块为松耦合的关系。底层模块和主模块以接口的方式联接起来。
现在有一个汽车测试程序架构:
汽车测试架构
public class CarTestFramework
{
public void BuildTestContext()
{
car car1 = new car();
//
}
public void DoTest()
{
car car1 = new car();
//
}
public void GetTestData()
{
car car1 = new car();
//
}
}
主程序:
class Program
{
static void Main(string[] args)
{
CarTestFramework test = new CarTestFramework();
test.BuildTestContext();
test.DoTest();
test.GetTestData();
}
}
可以看出发生了new的紧耦合关系。所以我们想到这样改造:
AbstractCar car1 = new Volvo();
右边还是紧耦合的关系,再次改造:
public void BuildTestFramework(AbstractCar car1, AbstractCar car2);
主程序:
test.BuildTestFramework(Volvo, Ford);
好像看起来蛮不错,平时我们也是尽量这样做的,写多个BuildTestFramwork函数实现重载。可是系统的扩展性并不好。如果我的context需要加一种新的车,就要改底层的buildtestframwork?
注意:我们要尽量做到扩展,而不是更改。
看看工厂方法模式怎么做:
工厂方法模式实现
//volvoFactory.cs
public class VolvoFactory:CarFactory
{
public override Car CreateCar()
{
return new VolvoCar();
}
}
//Volvo.cs
public class VolvoCar: Car
{
public override void StartUp()
{
}
public override void Run()
{
}
public override void Turn()
{
}
public override void Stop()
{
}
}
// CarTestFramework.vs
public class CarTestFramework
{
public void BuildTestContext(CarFactory factory)
{
Car c1 = factory.CreateCar();
Car c2 = factory.CreateCar();
}
public void DoTest( CarFactory factory )
{
Car c1 = factory.CreateCar();
Car c2 = factory.CreateCar();
}
public void GetTestData( CarFactory factory )
{
Car c1 = factory.CreateCar();
Car c2 = factory.CreateCar();
}
}
//主程序
class Program
{
static void Main(string[] args)
{
CarTestFramework test = new CarTestFramework();
test.BuildTestContext(new VolvoFactory());
}
}
如果需要测试新车Toyota,只需要
增加Toyota, ToyotaFactory
主程序增加:test.BuildTestContext(new ToyotaFactory());
结构图
和Abstract Factory来对比,工厂方法模式的图是这样的:
可以看出,工厂方法模式中,工厂类与产品类往往具有平行的等级结构,它们之间一一对应。
工厂方法类的核心是一个抽象工厂类,而简单工厂模式把核心放在一个具体类上。
动机
在软件系统中,经常面临着“某个对象”的创建工作,由于需求的变化,这个对象的具体实现经常面临着剧烈的变化,但是它却拥有比较稳定的接口。如何应对这种变化?提供一种封装机制来隔离出“这个易变对象”的变化,从而保持系统中“其它依赖该对象的对象”不随着需求的改变而改变?
意图
定义一个用户创建对象的接口,让子类决定实例化哪一个类。Factory Method使一个类的实例化延迟到其子类。
工厂方法模式实例
Factory method offering flexibility in creating different documents.
The derived Document classes Report and Resume instantiate extended versions of the Document class.
Here, the Factory Method is called in the constructor of the Document base class
Factory Method pattern -- Real World example
using System;
using System.Collections;
// "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
{
// Fields
protected ArrayList pages = new ArrayList();
// Constructor
public Document()
{
this.CreatePages();
}
// Properties
public ArrayList Pages
{
get{ return pages; }
}
// Factory Method
abstract public void CreatePages();
}
// "ConcreteCreator"
class Resume : Document
{
// Factory Method implementation
override public void CreatePages()
{
pages.Add( new SkillsPage() );
pages.Add( new EducationPage() );
pages.Add( new ExperiencePage() );
}
}
// "ConcreteCreator"
class Report : Document
{
// Factory Method implementation
override public void CreatePages()
{
pages.Add( new IntroductionPage() );
pages.Add( new ResultsPage() );
pages.Add( new ConclusionPage() );
pages.Add( new SummaryPage() );
pages.Add( new BibliographyPage() );
}
}
/**//// <summary>
/// FactoryMethodApp test
/// </summary>
class FactoryMethodApp
{
public static void Main( string[] args )
{
Document[] docs = new Document[ 2 ];
// Note: constructors call Factory Method
docs[0] = new Resume();
docs[1] = new Report();
// Display document pages
foreach( Document document in docs )
{
Console.WriteLine( " " + document + " ------- " );
foreach( Page page in document.Pages )
Console.WriteLine( " " + page );
}
}
}
Output
Resume -------
SkillsPage
EducationPage
ExperiencePage
Report -------
IntroductionPage
ResultsPage
ConclusionPage
SummaryPage
BibliographyPage
工厂模式在组件开发中(吕震宇)
1)由系统架构师设计好抽象产品和抽象工厂。
2.1)多组并行开发具体产品和具体工厂。
2.2)与此同时另外一组使用依赖注入技术开发主程序。在1的工作完成后2.1与2.2是可以并行的。
按上面方案很可能产生3个Assembly。最后通过配置文件完成组装。
从重构来理解Log实例
这个例子在wayfarer的封装变化和TerryLee的工厂方法模式里都提到过。我觉得是重构的一个很典型,并且易于理解的例子。我也放在这里说一下吧。
在程序中我们经常要对发生的异常错误或者关键操作进行日志记录。最简单的日志记录是这样的:
first log class
public class Log
{
public void Write(string target, string log)
{
//实现内容;
using (FileStream fs = new FileStream(target, FileMode.Append))
{
using (StreamWriter sw = new StreamWriter(fs, Encoding.UTF8))
{
sw.WriteLine(log);
}
}
}
}
程序:
//do something, retrun error, then
Log lg = new Log();
lg.Write(@"c:\log.txt","error, refer to handbook P.xxx");
存在的问题很明显,紧耦合,难以扩展。为了扩展为XmlLog, TxtLog,很容易想到的是接口。
第一次重构:
接口形式
Log2类依赖于ILog
public class Log2
{
private ILog log;
public Log2(){}
public Log2(ILog log)
{
this.log = log;
}
public void Write(string target, string logValue)
{
log.Execute(target, logValue);
}
}
程序:
Log2 log = new Log2(new Log2XmlFile());
log.Write(@"c:\log.xml","error,refer to ");
执行结果: <error>error,refer to </error>
这样的实现,给扩展性带来了一些好处,如果要扩展Log2DBFile, 只需要增加一个实现ILog的子类。
但是如果要换一种日志记录方式,把程序原来写好的部分,例如上面的Log2XmlFile改为Log2DBFile呢?就不是很方便了。
第二次重构:
注意:下面的应该是虚线箭头,表示依赖关系
LogFactroy类
public abstract class LogFactory
{
public abstract Log Create();
}
public class XmlFactory : LogFactory
{
public override Log Create()
{
return new XmlLog();
}
}
public class TxtFactory : LogFactory
{
public override Log Create()
{
return new TxtLog();
}
}
Log代码下载