工厂模式的理论与实际应用
1.我编写代码的习惯
我在写代码时总是在需要创造对象的时候就毫不思索的使用new,创造一个对象,让这个对象帮我完成索要达到的目的。比如:在访问sql数据库时,很容易写,SqlConnection con=new SqlConnection("数据库连接字符串"),然后呢con.open(),再后来使用这个连接来访问数据库,最后con.Close()。在需要访问数据库时,在使用同样的方式new,open(),Close().频繁的打开数据库连接,使用,再关闭。我也是在网络上看的,没有实际意识到,说像我这样频繁打开数据库连接是一种耗费资源的做法,不是说这种做法不对,而是没有显示出代码之美。这种耗费资源的做法后来被一种叫单例的模式彻底解决了,代码的美也就体现出来了。再后来要访问Mysql,oracle了,完了,所有的数据库连接都是SqlConnections了,怎么才能成为MySqlConnection呢,再写一个Mysql版本呗,哼。工厂模式(本文把工厂模式和抽象工厂模式统称为工厂模式)就再一次体现了代码之美,根据一个provideName来判断你要使用的什么数据库,微软的DbProviderFactory,DbProviderFactories两个工厂类又让你的代码更容易复用。
2.让你看看我的代码
class BMW
{
void run()
{
Console.WriteLine("开奔驰车");
}
}
class Benz
{
void run()
{
Console.WriteLine("开奔驰车");
}
}
class Audi
{
void run()
{
Console.WriteLine("开奥迪车");
}
}
class Custom
main()
{
BMW bmw=new BMW()
bmw.run();
Benz benz=new Benz();
benz.run();
Audi audi=new Audi();
audi.run();
}
输出结果:开宝马车
开奔驰车
开奥迪车
我要结果写上,网络上有些人就是不写结果,碎片太严重,要么你就不要写博客,要么就认真写,给人真正的帮助!
这段代码只是个范例,但是我写代码的时候避免不了像这样写。包含main()的Custom类是不是有点繁忙啊,既要关心BMW,Benz,Auti类的创建,又要关心入口函数被系统调用。试想,如果有人帮你创建好了BMW,Benz,Auti实体类,这个Custom只需要把对象拿来消费,是不是分工就明确了,而且Custom也脱离了类的创造工作而专心行使自己的工作。工厂类就有了萌芽了。
3.比上面带吗稍微好看点的代码
public private protected 等等修饰符自己加吧,旨在说明过程,我不写了。我的是在同一个包内完成的,注意修饰符的问题,不要
怪我没提醒你。
interface ICar
{
run();
}
class BMW:ICar
{
void run()
{
Console.WriteLine("开奔驰车");
}
}
class Benz:ICar
{
void run()
{
Console.WriteLine("开奔驰车");
}
}
class Audi:ICar
{
void run()
{
Console.WriteLine("开奥迪车");
}
}
class Custom
{
ICar driver(string a)
{
ICar car=null;
if(a.Equals("BMW"))
{
car=new BMW();
}
else if(a.Equals("Benz"))
{
car=new Benz();
}
else
{
自己写吧,写不动了,你懂的!
}
return car;
}
main()
{
ICar c=driver("BMW");
c.run();
奔驰车,奥迪车如法炮制吧。
}
}
输出结果:开宝马车
我要结果写上,网络上有些人就是不写结果,碎片太严重,要么你就不要写博客,要么就认真写,给人真正的帮助!
结论:上面的代码是对工厂模式萌芽的进一步说明,还没有实际的工厂类产生。上面的代码,不是第一个示例哦,我说的是最近的示例。更能说明Custom类有点太累了,创造BMW,Bnez,Audi三种对象的判断逻辑都在Custom类中,在加上Custom的实际工作是入口函数,所以工厂,一个能帮我们创造对象的工厂成为了迫切希望,把对象的制造从Custom类中抽离出来。
4.简单工厂诞生
public private protected 等等修饰符自己加吧,旨在说明过程,我不写了。我的是在同一个包内完成的,要是在不同包,注意修饰符的问题,不要
怪我没提醒你。
interface ICar这里使用的是接口,也可以使用抽象类,接口是能实现多继承,所以用接口也是可以得,不影响继承关系,因为
抽象类再进一步抽象不就是接口了么。
{
run();
}
class BMW:ICar
{
void run()
{
Console.WriteLine("开奔驰车");
}
}
class Benz:ICar
{
void run()
{
Console.WriteLine("开奔驰车");
}
}
class Audi:ICar
{
void run()
{
Console.WriteLine("开奥迪车");
}
}
class DriverFactory
{
static ICar Driver(stirng a)
{
Icar car=null;
if(a=="BMW")
{
car=new BMW();
}
else if(a=="Benz")
{
car=new Benz();
}
else
{
car=Audi();
}
return car;
}
}
class Custom
{
main()
{
ICar c=null;
c=DriverFactory.Driver("BMW")
c.run();
c=DriverFactoty.Driver("Benz");
c.run();
c=DriverFactoty.Driver("Audi");
c.run();
}
}
输出结果:开宝马车
开奔驰车
开奥迪车
我要结果写上,网络上有些人就是不写结果,碎片太严重,要么你就不要写博客,要么就认真写,给人真正的帮助!
4.从我的代码到简单工厂的代码
从上面三个代码实例中看到优点
(1)BMW,Benz,Audi对象的创建逐渐从Custom主函数类中脱离出来了,而把对象的创建职能给了DriverFactory类。而Custom只使用DriverFactory类创建出来的对象,网上说这叫Custom类负责消费。
(2)代码复用性增加了,这里我没有准确的解释,所以不解释了。
同时我们要看到缺点
(1)工厂类DriverFactory包含了所有对象的创建逻辑,即例子中的判断逻辑。一旦工厂不能工作,整个系统都要受影响。
(2)扩展性依然不强,添加新的汽车产品,依然要修改工厂类的创建逻辑。
5.适用的范围
结合优缺点,简单工厂使用的范围就很小。
- 当工厂类负责创建的对象比较少时可以考虑使用简单工厂模式()
- 客户如果只知道传入工厂类的参数,对于如何创建对象的逻辑不关心时可以考虑使用简单工厂模式
在网络上找的别人的资料,我觉得有道理,加以引用,出自http://blog.jobbole.com/78062/
.NET中System.Text.Encoding类就是简单工厂类,可以创造图示的类。
6.工厂方法模式的引入
抽象工厂模式真能改变简单工厂的缺点么?缺点是什么,就是工厂类里存在判断逻辑,要添加新的对象时,要在工厂类的判断逻辑中去增加判断逻辑,来添加新对象,代码的复用性还是不尽如人意。
工厂方法模式之所以可以解决简单工厂的模式,是因为它的实现把具体产品的创建推迟到子类中,此时工厂类不再负责所有产品的创建,而只是给出具体工厂必须实现的接口,这样工厂方法模式就可以允许系统不修改工厂类逻辑的情况下来添加新产品,这样也就克服了简单工厂模式中缺点。
interface ICar
{
run();
}
class BMW:ICar
{
void run()
{
Console.WriteLine("开奔驰车");
}
}
class Benz:ICar
{
void run()
{
Console.WriteLine("开奔驰车");
}
}
class Audi:ICar
{
void run()
{
Console.WriteLine("开奥迪车");
}
}
public abstract class AbstractCarFactory
{
public abstract Icar createCarFactory();
}
class BMWFactory:AbstractCarFactory
{
public ICar createCarFactory()
{
return new BMW();
}
}
class BenzFactory:AbstractCarFactory
{
public ICar createCarFactory()
{
return new Benz();
}
}
class AudiFactory:AbstractCarFactory
{
public ICar createCarFactory()
{
return new Audi();
}
}
class Custom
{
main()
{
//代码说明:从AbstractCarFactory 这个抽象工厂中继承了三个子类BMWFactory,BenzFactory,AudiFactory
//子类完成具体对象的创建。工厂类是不是由简单工厂的逻辑判断中进一步抽离了,具体工厂类就只负责创造对象。
AbstractCarFactory bmwFactory=new BMWFactory();
ICar bmw=bmwFactory.createCarFactory();
bmw.run();
}
}
在工厂方法模式中,工厂类与具体产品类具有平行的等级结构,它们之间是一一对应的。
AbstractCarFactory类:充当抽象工厂角色,任何具体工厂都必须继承该抽象类
BMWFactory,BenzFactory,AudiFactory类:充当具体工厂角色,用来创建具体产品
ICar类:充当抽象产品角色,具体产品的抽象类。任何具体产品都应该继承该类
BMW,Benz,Audi:充当具体产品角色,实现抽象产品类对定义的抽象方法,由具体工厂类创建,它们之间有一一对应的关系。
回头看看刚才的缺点看来是改进了,工厂类没有了更具判断逻辑来创造对象,而是具体工厂类就创造具体对象。如果添加新的对象,就添加一个对象,添加一个具体工厂不就完事了么。
7.抽象工厂模式的引入。我以BMW,Benz,Audi对象来说,BMWFactory生产BMW,那么生产的BMW是轿车,还是商务车,还是卡车呢?还有BMWFactory生产出来的是北美版,是欧洲版,还是中东版呢?我们回头看看ICar,ICar代表的是BMW,Benz,Audi中的一种车,就以BMW这种车为例,显示生活中有BMW跑车,BMW商务车,BMW卡车。
工厂方法模式是这样的:BMWFactory{public ICar createCarFactory(){return new BMW();}}如果这个BMWFactory要生产BMW跑车,肯定在添加个方法
{public 跑车 createCarFactory(){return new 跑车()}}那么ICar这个类型和跑车这个类型怎么联系到一起呢,我们的BMW,Benz,Audi都实现了ICar,BMWFactory返回的是return new BMW(),宝马工厂生产的所有类型的统称宝马牌车,但我们要想返回宝马牌车系列中的跑车系列的车型怎么办?是不是问题的范围变了啊,工厂方法模式解决的是简单工厂的缺点,把对象的创建分离出来。而抽象工厂模式解决的是创造一个对象中一个系列的对象的问题。所以问题范围不一样,代码也就不一样。
interface 跑车
{
void run();
}
interface 商务车
{
void run();
}
public Class BMW跑车:跑车
{
public void run()
{
Console.WriteLine("开宝马跑车")
}
}
public Class BMW商务车:商务车
{
public void run()
{
Console.WriteLine("开宝马商务车")
}
}
public Class Benz跑车:跑车
{
public void run()
{
Console.WriteLine("开奔驰跑车")
}
}
public Class Benz商务车:商务车
{
public void run()
{
Console.WriteLine("开奔驰商务车")
}
}
奥迪就不写了吧,怪累的!
public abstract Class AbstractCarFactory
{
商务车 business();
跑车 sport();
}
public Class BMWFactory:AbstractCarFactory
{
商务车 business()
{
return new BMW商务车();
}
跑车 sport()
{
return new BMW跑车();
}
}
BenzFactory和AudiFactory就不写了吧 ,都是一样一样的。
pubic Class Custom
{
main()
{
CarFactory carfactory=new BMWFactory();
BMW商务车 b=carfactory.business();
b.run();
BMW跑车 c=carfactory.sport();
c.run();
}
}
输出结果:开宝马商务车
开宝马跑车
结论:抽象工厂模式看上去和工厂方法模式写法上没有什么区别,但是解决的问题的范围是不一样的,所以区别对待。
抽象工厂模式:提供一个创建产品的接口来负责创建相关或依赖的对象,而不具体明确指定具体类
如果有大众跑车或大众商务车,是不是就增加大众跑车,大众商务车两个实体类和一个大众工厂就可以了啊。
8.抽象工厂实际应用
.NET类库中也存在应用抽象工厂模式的类,这个类就是System.Data.Common.DbProviderFactory我也是想通过这个类来改变我的程序在数据库访问方面达到一个程序能访问多种数据库,而代码不变。
下面是DbProviderFactory类的部分代码:
public abstract class DbProviderFactory
{
// 提供了创建具体产品的接口方法
protected DbProviderFactory();
public virtual DbCommand CreateCommand();
public virtual DbCommandBuilder CreateCommandBuilder();
public virtual DbConnection CreateConnection();
public virtual DbConnectionStringBuilder CreateConnectionStringBuilder();
public virtual DbDataAdapter CreateDataAdapter();
public virtual DbDataSourceEnumerator CreateDataSourceEnumerator();
public virtual DbParameter CreateParameter();
public virtual CodeAccessPermission CreatePermission(PermissionState state);
}
就以public virtual DbCommand CreateCommand();为说明,返回类型是DbCommand类型,DbCommand是什么呢?
System.Object
System.MarshalByRefObject
System.ComponentModel.Component
System.Data.Common.DbCommand
System.Data.EntityClient.EntityCommand
System.Data.Odbc.OdbcCommand
System.Data.OleDb.OleDbCommand
System.Data.OracleClient.OracleCommand
System.Data.SqlClient.SqlCommand
可以看到他是OdbcCommand,OleDbCommand,OracleCommand,SqlCommand的父类啊,那么我的DbCommand指向的是SqlCommand就是访问Sql,指向Oracle就访问的是Oracle啊。看来抽象出来的接口有了,与我的示例对比,这个AbstractCarFactory是不是有了啊。那么具体的工厂类在哪里呢?OdbcFacroty,OleDbFacroty,OracleClientFacroty,SqlClientFactory。那么要创造一个对象呢还是一系列对象呢。肯定是一系列对象啊:如SqlConnection,SqlCommand等,这就是抽象工厂模式,如果要创造一个对象,那就是工厂方法模式。
DbProviderFactories类。它有四个方法getFactory(string),getFactory(DataRow),getFactory(DbConnection),getFactoryClasses(),以getFactory(string)为例,它返回的是一个DbProviderFactory类型的对象。System.Object
System.Data.Common.DbProviderFactory
System.Data.EntityClient.EntityProviderFactory
System.Data.Odbc.OdbcFactory
System.Data.OleDb.OleDbFactory
System.Data.OracleClient.OracleClientFactory
System.Data.SqlClient.SqlClientFactory
根据上面的继承关系,DbProviderFactory类型有可能是OdbcFacroty类型,或OleDbFacroty类型,或OracleClientFacroty类型,或SqlClientFactory类型。DbProviderFactories中的getFactory(string)返回什么类型和string有关。
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup>
<connectionStrings>
<add name="sqlcon" connectionString="server=.;uid=sa;pwd=123;database=zhangweiTest;" providerName="System.Data.SqlClient"/>
<add name ="accesscon" providerName="System.Data.OleDb" connectionString="PRovider=Microsoft.Jet.OLEDB.4.0;Data Source=D:\test.mdb;Jet OLEDB:Database PassWord=;"/>
</connectionStrings>
</configuration>
这个string是什么,就是配置文件中的providerName属性。有上面的配置文件可以看到,如果是System.Data.SqlClient,就可与访问Sql,如果为System.Data.OleDb,那么可以访问Access数据库。
具体是这样做的。
private static string defaultProviderName = ConfigurationManager.ConnectionStrings["sqlcon"].ProviderName.ToString();
DbProviderFactory dbfactory = DbProviderFactories.GetFactory(defaultProviderName );
如果现在defaultProviderName=System.Data.SqlClient,那么DbProviderFactory dbfactory = DbProviderFactories.GetFactory(defaultProviderName );得到的是什么呢?是一个指向SqlClientFactory的引用,这个引用类型是SqlClientFactory父类的,问父类的引用类型怎么指向子类,这个当然可以了,是继承的基础知识,不知道可以翻看语法。那么现在我们得到了一个SqlClientFactory工厂了,那么它继承DbProviderFactory的所有方法都变成了SqlConnection,SqlCommand等等了。
9.工厂方法模式与抽象工厂模式区别(个人意见)
以我举的实例为说明:我们要开奔驰牌汽车,就让奔驰厂生产奔驰汽车。我们要开宝马牌汽车,就让宝马厂生产宝马汽车。对于奔驰车,宝马车,他们能抽象出来的接口是不是“车”啊!interface 车。奔驰工厂和宝马工厂抽象出来的接口是不是生产汽车啊!-----------工厂方法模式
现在我们要开奔驰牌跑车,开奔驰牌商务车,那么我们的奔驰工厂就要生产奔驰牌跑车,奔驰牌商务车。开宝马牌跑车,宝马牌商务车,宝马工厂就生产宝马牌跑车,宝马牌商务车。现在对于跑车,商务车,抽象出来的接口是不是变了啊。interface 跑车,interface 商务车。奔驰工厂和宝马工厂抽象出来的接口变多了啊,既有生产跑车的接口,又有生产商务车的接口啊。---------抽象工厂模式。
现在我们看到DbProviderFactory工厂抽象的是所有OdbcFacroty,OleDbFacroty,OracleClientFacroty,SqlClientFactory具体工厂的接口啊。但是OdbcFacroty,OleDbFacroty,OracleClientFacroty,SqlClientFactory不是单一的创造一种对象吧,它既要生产xxxCommand对象,又要生产xxxConnection对象。所以DbProviderFactory是抽象工厂模式,不是工厂方式。
以上是个人愚见,如果有不同意见,特别愿意听,请一定转达给我!