一. 抽象工厂&工厂方法&简单工厂方法
Posted Tiigoo
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一. 抽象工厂&工厂方法&简单工厂方法相关的知识,希望对你有一定的参考价值。
目录
1. 抽象工厂模式
参考《大话设计模式》第十五章
1.1 问题
用户可能用两种类型的数据库,这两种数据库有类似的方法,但如果一开始声明对象时,固定了它使用 SQL Server 数据库:SqlserverUser su=new SqlserverUser()
,那么要是后面需要改成Access数据库,它调用的各种方法都需要改变,需要修改的代码量很大。比如添加用户操作:su.Insert(User),两种类型的数据库都是执行这个操作,但具体实现的代码不同,这样需要全部改变。
此问题的特点:有多种模式可以用,但不同模式又有相似操作。虽然不同模式实现相似操作的具体代码不同,但对用户来说达到的效果是一样的。
1.2 UML图和代码
-
定义:抽象工厂模式——提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类
-
UML图
对上述问题的解决方法:
对用户来说:他只看得到抽象接口IUser,不用知道具体实现类(SqlserverUser和AccessUser)。这意味着他知道有哪些操作(IUer告诉他的),但不用考虑是在Sqlserver还是Access的环境中使用操作。那么当要从Sqlserver数据库改到Access数据库时,只要改变传给工厂的指令,但用户端的操作代码都不用变(操作是固定的,IUser中定义了固定的操作)。
对工厂来说:首先告诉工厂是在哪个环境,工厂根据指定的环境创造具体工厂(不同的具体工厂会创造不同的操作组合)。然后具体工厂将这些操作组合在一起,返回给用户。这样用户不用去挑选环境对应的各个操作,直接从工厂那里拿到一整套的操作。
-
代码
-
IUser和IDocument
interface IUser//定义了关于User的操作 { void Insert(User user); User GetUser(int id); } interface IDepartment//定义了关于department的操作 { void Insert(Department department); Department GetDepartment(int id); }
-
IUser和IDepartment的具体实现类:
//SqlserverUser,用于访问 SQL Server 的 User class SqlserverUser implements Iuser { public void Insert(User user) { ... } public void GetUser(int id) { ... } } //AccessUser,用于访问Access 的 User class AccessUser implements Iuser { public void Insert(User user) { ... } public void GetUser(int id) { ... } } //SqlserverDepartment,用于访问 SQL Server 的 Department class SqIserverDepartment implements IDepartment { ... } //AccessDepartment,用于访问Access 的 Department class AccessrDepartment implements IDepartment { ... }
-
IFactory接口
interface IFactory { IUser CreateUser(); }
-
IFactory的具体实现类
//SqlServerFactory类 class SqlServerFactory implements IFactory { public IUser CreateUser() { return new SqlserverUser(); } public IDocument CreateDocument() { return new SqlserverDocument(); } }
//AccessFactory类 class AccessFactory implements IFactory { public IUser CreateUser() { return new AccessUser(); } public IDocument CreateDocument() { return new AccessDcocument(); } }
-
客户端代码
static void Main(string[] args)
{
IFactory factory=new SqlServerFactory();//选择创造具体产品组合的具体工厂
IUser iu = factory.CreateUser();//通过具体子类创造产品
IDocument idoc = factory.CreateDocument();
User user=new User();
Document document=new Document();
iu.Insert(user);
iu.GetUser(1);
idoc.Insert(document);
idoc.GetDocument(1);
}
- 结构图
1.3 优点和缺点
-
优点
-
易于交换产品系列。由于选择具体工厂类(如 IFactory factory=new AccessFactory() )只需要在初始化的时候出现—次,这就使得改变—个应用的操作环境很容易。只用改变这一条指令,指定不同的具体工厂就可以改变操作环境。
-
让具体的创建实例过程与客户端分离。客户端只知道有抽象类 IUser 和IDepartment,抽象类告诉用户有哪些操作,而具体产品类的代码不会出现在客户端代码中。
-
-
缺点:
-
如果后期要增加产品——项目表Proiect(Project和User、Department同级)需要做以下改动:
(1)增加抽象接口IProject,以及增加IProject的具体实现类 SqlserverProject、AccessProject
(2)还需要更改抽象工厂接口IFactory,以及更改具体工厂SqlserverFactory和 AccessFactory
这也比较繁琐,这个问题可以用简单工厂解决,将抽象工厂和具体工厂统一成一个工厂DateAccess(如下图),这样就只用增加(1),以及在DataAccess里增加一个方法:CreateProject()。简单工厂在后文详述。
-
如果客户端程序有很多,每个客户端都有IFactory factory=new SqlserverFactory,当要换成Access数据库时,每个客户端都需要改变:IFactory factory=new AccessFactory()。这并没有实现我们的需求:只改一处,而所有的客户端都能变。但是这个问题可以通过反射解决。
-
1.4 反射+抽象工厂
还没仔细了解反射,这部分请跳过,复习javase后补充。
常规抽象工厂的写法 :IUser result = new SqlserverUser();
反射的写法:using System.Reflection;先引用System.Reflection 的命名空间
IUser result=(IUser)Assembly.Load(“抽 象 工厂模式”).CreateInstance(“抽 象 工.厂模式. SqlserverUser”);
当前"命名空间"名称要实例化的"类名"
当前"程序集"的名称
- 反射+配置文件
2. 简单工厂模式
参考:《大话设计模式》第一章
2.1 问题
从固定的印刷到活字印刷体现了面向对象的好处:通过封装、继承、多态把程序的耦合性降低
第一,要改印刷模板,只需更改对应的字,而不是重新印刷整个产品,此为可维护;
第二,印刷用到的模板字并非用完这次就无用,完全可以在后来的印刷中重复使用,此乃可复用;
第三,印刷模板若要加字,只需另刻字加入即可,这是可扩展;
第四字的排列其实可能是睡排可能是横排,此时只需将活字移动就可,做到满足排列需求,此是灵活性好;
此章面对的问题:计算器实现+-*/,这几个运算有相似和不同的部分,如果没有分离开,当我们要增加一个运算,或者给这几个类都增加一个功能,改动很大。所以采用封装实现低耦合,具体见下。
2.2 业务的封装
封装:就是让业务逻辑与界面逻辑分开,让它们之间的耦合度下降。业务逻辑是工厂和运算类来完成(比如工厂可以选择实现哪个具体运算,运算类可以进行具体操作),界面逻辑是客户端代码完成。只有分离开,才可以达到容易维护或扩展。
“+”、“-”、“*”、"/"算法也要分开,用一个抽象类(下面UML图中的“运算类”)来实现抽象步骤,而具体类(下面的加法类、乘法类…)来选择具体用哪个算法。
到底要实例化谁,将来会不会增加实例化的对象,比如增加开根运算——这是很容易变化的地方,应该考虑用一个単独的类(简单工厂类)来做这个创造实例的过程,这就是工厂。
这里工厂不是抽象的,它通过输入创造了类。
2.3 UML图和代码
- 简单工厂类代码
public class SimpleFacory//简单工厂类
{
public static Operation createOprate(String Operate)
{
Operation opr=null;
switch(opr)
{
case "+":
opr=new OperationAdd();
break;
case "-":
opr=new OperationSub();
break;
case "*":
opr=new OperationMul();
break;
case "/":
opr=new OperationDiv();
break;
}
return opr;
}
}
- 客户端代码:
Operation opr;
//例如想进行加法运算
opr=SimpleFactory.createOperate("+");//利用了多态性
//下面的属性和方法是抽象类固定的,具体选择哪个子类是由上面SimpleFactory决定的。
opr.NumberA=1;
opr.NumberB=2;
double result=opr.GetResult();
3.工厂方法模式
参考《大话设计模式》第八章
3.1 定义
工厂方法模式是定义一个用于创建对象的接口(IFactory),让子类(这里指的是工厂接口的子类,即具体工厂)决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。
3.2 UML图和代码
和 3.简单工厂模式 面对的问题一样,即对+ - * / 类的分离和封装。但这里,具体选择哪个类不是在工厂内决定,而是用户端决定。即switch case
不是在工厂内部了,而是在客户端代码,这样更能体现封闭性,后面会具体分析。
-
抽象工厂代码:
interface IFactory//抽象工厂是一个接口,需要子类实现接口,而不能直接调用方法 { Operation CreateOperation();//所有具体工厂都有这个方法。 }
-
具体工厂的代码:
class AddFactory implements IFactory { public Operation CrateOperation() { return new OperationAdd();//OperationAdd是Operation的子类,利用了多态性 } } class SubFactory implements IFactory { public Operation CrateOperation() { return new OperationAdd();//OperationSub是Operation的子类,利用了多态性 } } class MulFactory implements IFactory { public Operation CrateOperation() { return new OperationAdd();//OperationMul是Operation的子类,利用了多态性 } } class DivFactory implements IFactory { public Operation CrateOperation() { return new OperationAdd();//OperationDiv是Operation的子类,利用了多态性 } }
-
客户端代码:
IFactory oprFactory=new AddFactory();//例如想要实现加法类,这里是在客户端选择具体工厂,也可以用case,根据一个变量来选择具体工厂 Operation opr=oprFactory.CreateOperation();//这里得到的就是加法具体类了 opr.NumberA=1; opr.NumberB=2; double result=opr.GetResult();
3.3 工厂方法和简单工厂的比较
- 工厂方法的好处
对比二者的UML图,发现工厂方法多出来的是:四个具体工厂的分支去和具体运算类耦合,而简单工厂是一个工厂直接和四个具体运算类耦合。那么工厂方法的好处是什么呢?
考虑我们要增加一个运算:求 M N M^N MN,
在简单工厂类中,我们需要改变简单工厂:如图,在它 createOprate()方法内部增加一个case "^"
,这样才可以选择乘方运算。但这种对类的改变会破坏封闭原则,我们希望封装好的类不再做改变。
在工厂方法中,我们只用再增加一个具体工厂:
class PowerFactory implements IFactory
{
public Operation CrateOperation()
{
return new OperationPower();
}
}
如图:
这样既扩展了抽象工厂,又没有改变抽象工厂内部代码。其实这本质上是把switch case
交给了客户端代码,这样就会增加客户端代码的改动。
- 简单工厂的好处
简单工厂好处是对于客户端更友好。如上所说,简单工厂的客户端只需要传递一个字符给工厂,而switch case
(选择实例化哪个具体运算类)是工厂来做的。所以,对于客户端来说,去除了与具体产品的依赖,客户不用做逻辑判断。客户不用知道有哪些具体工厂,他只知道“+”这个字符。
我们可以对比一下二者客户端的代码:
//简单工厂:只用给简单工厂(SimpleFactory)一个字符“+”,就能得到想要的具体加法类。
Operation opr;
opr=SimpleFactory.createOperate("+");//给简单工厂一个字符“+”,返回了具体的加法类
opr.NumberA=1;//然后利用加法类得到结果
opr.NumberB=2;
double result=opr.GetResult();
//工厂方法:客户端需要选择具体工厂AddFactory,
// 如果想要选择不同的具体工厂,可以加上switch case。
// 所以这里是把逻辑判断(选择哪个工厂)交给了用户,
// 而简单工厂中是SimpleFactory来进行逻辑判断的
IFactory oprFactory=new AddFactory();//客户端选择具体工厂
Operation opr=oprFactory.CreateOperation();//再利用具体工厂得到想要的加法类
opr.NumberA=1;//然后利用加法类得到结果
opr.NumberB=2;
double result=opr.GetResult()
- 二者共同的好处:
二者都保持了封装对象创建过程的优点:易维护,易扩展,可复用,灵活性(相对于不用对象封装)
只是工厂方法更加体现封闭原则,更易扩展
3.4 工厂方法和抽象工厂的比较
-
结构图对比
抽象工厂:
工厂方法:
可见二者都有抽象工厂、具体工厂,抽象产品、具体产品,并且是具体工厂和具体产品耦合。
但是工厂方法中只有一个抽象产品,每个具体工厂和一个抽象产品的具体产品耦合;
抽象工厂有多个抽象产品,每个具体工厂和多个抽象产品的具体产品耦合,并且将不同具体产品进行组合。抽象工厂是工厂方法的扩展。
以上是关于一. 抽象工厂&工厂方法&简单工厂方法的主要内容,如果未能解决你的问题,请参考以下文章