您的位置:首页 > 其它

设计模式 之 工厂方法

2008-09-26 19:29 162 查看
工厂方法
当要创建某 一个 系列的产品时,我们可以优先考虑用工厂方法来创建这系列的产品。要特别理解好“系列产品”是什么意思,注意工厂方法是针对“一个”系列,或者说单系列产品的。

如: 假设我们需要为SQL Server和Oracle提供一个数据库连接Connection(只是假设而已^_^),并为其实现打开连接、关闭连接这两个操作。
注意:这里的SQL Server数据库连接、Oracle数据库连接可以说是一个系列产品,因为它们归属于“数据库连接”这一类型(或说这一系列)。
如果我们这么写:
public class Connection
{
protected void OpenSQLServerConnection()
{
//...打开SQL Server数据库连接
}
protected void OpenOracleConnection()
{
//...打开Oracle数据库连接
}
public void Open(string databaseType)
{
if (databaseType == "SQLServer")
{
this.OpenSQLServerConnection();
}
else if (databaseType == "Oracle")
{
this.OpenOracleConnection();
}
}

protected void CloseSQLServerConnection()
{
//...关闭SQL Server数据库连接
}
protected void CloseOracleConnection()
{
//...关闭Oracle数据库连接
}
public void Close(string databaseType)
{
if (databaseType == "SQLServer")
{
this.CloseSQLServerConnection();
}
else if (databaseType == "Oracle")
{
this.CloseOracleConnection();
}
}
}
写过操作数据库相关代码的朋友可能会觉得这种写法有点怪异,因为你已经习惯了正规的写法。这里,我想说的是在不再增加数据库类型的前提下,这种写法完全是可以的,而且,层次也比较清晰。但如果我们哪天真要为MySql数据库也添加一个数据库连接,也要有打开连接、关闭连接的操作,那我们肯定要修改上面Connection类,相信你也能很快改好这个类(因为它写得还算清晰^_^)。
有一天,幸运之星看好了你,你写的这个Connection类变成了一套国际标准(我这里所说的标准是每个数据库连接都要有打开连接、关闭连接的操作),别人都要遵循你这套标准(真是太爽了o(∩_∩)o…哈哈)。这时,有人向你申请添加支持MySql数据库的连接,这是对你标准的认可,相信你不会拒绝的J。
当你很乐意的答应别人,你也不辞辛苦地在Open(string databaseType)、Close(string databaseType)方法中加上对MySql的判断(哪怕是还有其他的Access、DB2、Sqlite等数据库也要加上),并在你权威的Connection类中加上OpenMySqlConnection()、CloseMySqlConnection()。这时,你会发现问题来了:你必须让第三方把OpenMySqlConnection()方法和CloseMySqlConnection()方法的实现放到你的Connection类中,这可是牵涉到知识产权的问题,大哥,俺只能提醒提醒你了,知识产权的问题俺也不大清楚。当然,你可能会想到很多解决这个问题的方案,你可以用代理的方式,给客户程序提供一个函数指针,函数内部具体实现由客户代码去实现即可。其实,这种做法是比较麻烦的,每添加一个种新的类型的数据库,就得修改Connection类。这时,你会开始思索有没有不改动Connection类 就能达到我们要求的方案。
  既然是一套标准那就应该public出来,而且其他具体的数据库连接必须实现好这套标准。所以我们可以定义一个接口:
public interface IConnection
{
void Open();//接口中的方法是默认public的,因为接口就是一套标准。
void Close();
}
这样,我们再让SQLServer和Oracle来实现这套标准:
public class SqlServerConnection : IConnection
{
public void Open()
{
//...打开SQL Server数据库连接
}
public void Close()
{
//...关闭SQL Server数据库连接
}
}

public class OracleConnection : IConnection
{
public void Open()
{
//...打开Oracle数据库连接
}
public void Close()
{
//...关闭Oracle数据库连接
}
}
当我们要增加一类新数据库连接时,如MySql数据库连接:
public class MySqlConnection : IConnection
{
public void Open()
{
//...打开MySql数据库连接
}
public void Close()
{
//...关闭MySql数据库连接
}
}
我们并没有改我们的标准(即IConnection接口),而是以扩展的方式添加了一个MySqlConnection类。MySqlConnection的Open()和Close() 我想理所当然的应该由MySqlConnection自己来做,我们不能把标准的东西(所谓的抽象)和具体实现糅杂在一起。
当然,如果我们自始至终只有SqlServer或只有Oracle数据库,那么就没有标准可言了,因为标准就意味着要大家去遵守,既然是要“大家”去遵守,也就意味着有多个成员,而且成员还可以再增加,就像我们的数据库连接有SqlServer数据库连接、Oracle数据库连接、MySql数据库连接等成员(或称产品),以后可能还有其他新类型的数据库的数据库连接。但不管怎样,这些都属于一类产品(或者说同属于一系列的产品),那就是它们都是数据库连接^_^。当要创建某 一个 系列的产品时,我们可以优先考虑用工厂方法来创建这系列的产品。要特别理解好“系列产品”是什么意思,注意工厂方法是针对“一个”系列,或者说单系列产品的。
  上面我们写的继承自IConnection接口的SqlServerConnection 、OracleConnection 、MySqlConnection都是数据库连接这个系列产品中的具体产品,还剩下生产这些产品的工厂:
public class ConnectionFactory
{
private IConnection _connection;
public IConnection Create()
{
string databaseType = ReadAppConfig();//通过读取配置文件获取数据库类型

//通过数据库类型创建该类型下的数据库连接。
switch (databaseType)
{
case "SqlServer":
_connection = new SqlServerConnection();
break;
case "Oracle":
_connection = new OracleConnection();
break;
}
return _connection;
}
}
这样,当我们新增其他数据库的数据库连接时,要修改的是ConnectFactory类中swith-case语句,当然我们可以用反射机制做到连ConnectFactory类也不修改。

整个代码如下:
public interface IConnection
{
void Open();//接口中的方法是默认public的,因为接口就是一套标准。
void Close();
}

public class SqlServerConnection : IConnection
{
public void Open()
{
//...打开SQL Server数据库连接
}
public void Close()
{
//...关闭SQL Server数据库连接
}
}

public class OracleConnection : IConnection
{
public void Open()
{
//...打开Oracle数据库连接
}
public void Close()
{
//...关闭Oracle数据库连接
}
}

public class ConnectionFactory
{
private IConnection _connection;
public IConnection Create()
{
string databaseType = ReadAppConfig();//通过读取配置文件获取数据库类型

//通过数据库类型创建该类型下的数据库连接。
switch (databaseType)
{
case "SqlServer":
_connection = new SqlServerConnection();
break;
case "Oracle":
_connection = new OracleConnection();
break;
}
return _connection;
}

SqlServerConnection
void Open()
void Close()
ConnectionFactory
IConnection Create()
IConnection
void Open()
void Close()
}

类图:



工厂模式局限性:[/b]当IConnection接口有变动时,如新增一个接口方法,所有子类都得修改。
大众观点^_^:
(1)“改就改呗”观点,可能你也认为改就改呗。但在面向对象中,我们一贯的原则是以扩展的方式来写代码,如果我们已有的代码继承了微软类库中的某个接口,哪天微软将这个接口添加了一个方法,而且必须实现该方法才能达到某种我们需要的效果,而我们的代码已经打包发布出去了。怎么办? 只能找到我们的源码,实现相应的接口方法,然后再重新编译,部署发布。然后,我们还会担心修改后的代码会不会影响已有的代码,可能我们还得重新测试。真麻烦!这时,我们会想,有没有不用修改已有代码,不用重新编译部署发布的办法。我们可能希望通过添加一个新的dll到已发布的程序中或只是修改一下配置文件就能解决问题。
(2)“接口不行用抽象类”^_^,有朋友可能会想到这个方法。
不错,抽象类中的方法子类可以不用重载,那么这样可以避免不修改子类吗?试想一下,你在基类中添加的新方法是做什么用的?肯定是用来实现新功能或达到什么效果,如果你只是在基类中定义了,而没在其他地方调用(或者说没在子类的调用J),那岂不白写了。修改抽象层的东西(或者说是修改基类、接口)不可避免的要修改其他代码。所以,我们平常Coding时,讲究的是具体实现依赖于抽象,我们只希望修改具体的实现,而不想动我们的框架。因为动框架牵动太多了。

关联设计模式:[/b]
(1)如果在IConnection中,我们一定、必须、非得要加一个方法用于实现某中功能,而且这个方法过几个月可能就要改动改动^_^,客户太挑了,没办法,苦命的程序员。这时,我们可能会想到预留一个接口,这就要求我们对客户需求非常清楚,这样才能做到有备无患o(∩_∩)o…哈哈,访问者模式就是应对这种情形的。访问者嘛,过几个月,我们的代码用一个“访问者”,再过几个月又改换另外一个“访问者”^_^
(2)抽象工厂,前面我们强调工厂方法是针对单系列产品的,那么对应就有个多系列。
在数据库中,除了数据库连接IConnection外,还有执行Sql语句的Command,还有数据适配器等,而这些Command、数据适配器 和 数据库连接一样也分SqlServer、Oracle、MySql。所以Command对应有一个系列,数据适配器对应有一个系列。而我们操作数据库往往是综合使用这些对象,当我们要创建多个系列的对象时,就应该想到使用抽象工厂模式。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: