大话设计模式之工厂三姐妹

   版权声明:本文为博主原创文章,未经博主允许不得转载。

 工厂三姐妹一向受到了我们广大工人阶级的拥护。小妹简单工厂模式,二姐工厂方法模式,大姐抽象工厂模式。为了让大家混淆,特意将这三种设计模式放到一起总结,开玩笑啦,放在一起总结是为了方便比较异同。

简单工厂模式

    简单工厂就是帮我们来实例化对象的,当我们需要考虑用一个类来做这个创造实例的过程,这就是工厂。以一个简单计算器为例,有加、减、乘、除不同的算法,而且很可能会扩展开方、开根等其他运算,这时就可以考虑简单工厂来帮我们创建。首先看一下计算器的代码:

public class Operation{
    private double NumberA = 0;
    public double getNumberA()
    {
        return NumberA;
    }
    public void setNumberA(double numberA)
    {
        NumberA = numberA;
    }
    public double getNumberB()
    {
        return NumberB;
    }
    public void setNumberB(double numberB)
    {
        NumberB = numberB;
    }
    private double NumberB = 0;
    public double getResult(){
        double result = 0;
        return result;
    }
    
}

public class OperationAdd extends Operation
{

    @Override
    public double getResult()
    {
        double result = 0;
        result = numberA + numberB;
        return result;
    }
    
}

public class OperationSub extends Operation
{

    @Override
    public double getResult()
    {
        double result = 0;
        result = numberA - numberB;
        return result;
    }
    
}

public class OperationMul extends Operation
{

    @Override
    public double getResult()
    {
        double result = 0;
        result = numberA * numberB;
        return result;
    }
    
}

public class OperationDiv extends Operation
{

    @Override
    public double getResult()
    {
        double result = 0;
        if(numberB == 0)
        throw new Exception("除数不能为0")
        result = numberA / numberB;
        return result;
    }
    
}

    上述代码简单实现了计算器的加减乘除功能,可能有人会问为什么要用继承呢?直接在main函数里写一个switch不就搞定了吗?有这样想法的童鞋请去翻我上次总结的设计模式六大原则,首先,我们将运算逻辑和控制台逻辑放在一起写,就违反了单一职责原则,比如现在控制台类型换了,以前的代码是不能复用的,改需求就要更改大量的代码就违反了开放-封闭原则。因此我们应该讲运算逻辑和控制台逻辑分开,降低代码的耦合,这样我们如果控制台类型换了,运算逻辑的代码是可以复用的,也便于维护。其次,我们应该讲加减乘除单独分开写成类,分别继承运算类,重写getResult()方法来实现加减乘除同样是满足了单一职责原则(就一个类而言,应该仅有一个引起它变化的原因。如果一个类承担的职责过多,就等于把这些职责耦合在一起,一个职责的变化可能会削弱或者抑制这个类完成其他职责的能力)。

    截止到现在我们任务就是在控制台逻辑中去实例不同的实例来帮我们做加减乘除的运算。现在有一问题就是,目前只有四种运算,以后我们要是想增加开方、开根等运算的时候,这种易于变化的地方,应该考虑用一个单独的类来做这个创造实例的过程,这就是工厂。代码如下:

public class OperationFactory{
public static Operation creatOperate(String operate){
Operation oper = null;
switch(operate){
case: "+":
oper = new OperationAdd();
break;
case: "-":
oper = new OperationSub();
break;
case "*":
oper = new OperationMul();
break;
case "/":
oper = new OperationDiv();
break;
}
return oper;
}
}

这样,客户端代码就可以写成:

Operation oper;
oper = OperationFactory("+");
oper.NumberA = 1;
oper.NumberB = 2;
double result = oper.getResult();

    当我们增加一种新的运算的时候,只需要新写一个OperationXX类继承Operation并在修改工厂类即可(注意,对于开放封闭原则,不能考虑的过于绝对,我们要做的就是当需求改变的时候尽量用扩展解决问题,尽量减少更改)。


    以上是简单工厂的UML类图,至于类图的学习就不放在这里讲了,大家查资料自学一下。

    同样的功能要是二姐工厂模式来做的话,我们先看一下UML类图:


    通过UML图我们也能大概知道工厂模式的计算器代码实现了,在此仅写出客户端代码:

IFactory operFactory = new AddFactory();
Operation oper = operFactory.CreatOperation();
oper.numberA = 1;
oper.numberB = 2;
double result = oper.GetResult():

    前面讲到小妹简单工厂的时候说过,她的好处就是增加运算只需要增加一个类继承Operation类,改一下工厂类中的case条件就可以了,更改工厂类还是违背了我们的开放封闭原则,那么工厂模式呢?

工厂模式:定义一个用于常见对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。

    在工厂模式中,当我们想增加运算的时候,需要加一个工厂类和一个运算类去实现工厂类,这样整个工厂和产品体系其实都没有修改的变化,而只是扩展的变化,这就完全符合了开放封闭原则的精神。

    其实二姐工厂模式是小妹简单工厂的进一步抽象和推广。在保持了简单工厂模式优点的前提下,使得修改的代码更少了。

    虽然二姐工厂模式在小妹的基础上做了改进,但是当运算不断增加,增加的类也会越来越多,增加了额外的开发量。那么大姐抽象工厂会是什么样子呢?我们同样是先来看一下大姐的UML图。

    

    大姐已经是大人了,考虑问题已经和两个妹妹不在一个维度上了,抽象工厂模式考虑的不是针对一个产品类而是针对一个产品系列。举个例子,我们考虑一下数据库的增删改查操作,当我们需要更换数据库,即将原有的SqlServer改为Oracle,这时候如果是二姐和小妹的话可能是束手无策的,面对这么多的表,只有大姐抽象工厂才可以。比如User表和Department表等,那么UML图里的PruduceA 1就是UserSqlserver,ProductB 1就是DepartmentSqlserver。AbstractProductA就是IUser,接口中声明了对于user对象的增删改查方法,同理AbstractProductA就是IDepartment。增加了表的话,比如增加一个Company表,我们需要在IFactory里添加CreatCompany()方法,在工厂实现类中去实现。Factory 1的工厂实现其实就是SqlServer数据库,Factory 2就是Oracle数据库,它们针对User、Department、Company等这些表的增删改查有着不同的实现。我们来看一下客户端代码:

User user = new User():
Department dept = new Department();
//IFactory factory = new SqlServerFactory();
IFactory factory = new OracleFactory();
IUser iu = factory.CreatUser();
iu.Insert(user);
IDepartment id = factory.CreatDept();
id.Insert(dept);

    当将SqlServer数据库改成Oracle数据库时,我们只需更改一行客户端代码即可实现。但是当我们没增加一个表,就要增加产品抽象和产品实现,还要更改抽象工厂类和工厂实现类,这简直是太糟糕了,我们需要近一步改进。

    此时呢,大家回想一下小妹简单工厂的工厂类,里面定义了一个字符串,通过switch的匹配来决定具体实现。代码实现如下:

public class DataAccess{
private static final String db = "SqlServer";
//private static final String db = "Oracle";
public static IUser CreatUser(){
IUser result = null;
switch(db){
case "SqlServer":
result = new UserSqlServer();
break;
case "Oracle":
result = new UserOracle();
break;
}
return result;
}

public static IDepartment CreatDept(){
....
}
}

    客户端代码如下:

User user = new User();
Department dept = new Department();
IUser iu = DataAccess.CreatUser():
iu.Insert(User);
IDepartment id = DataAccess.CreatDept();
id.Insert(dept);
    到这里,我们看到可以通过一个字符串去决定实际的数据库访问实例。这时大姐抽象工厂听了表哥反射的建议,

采用反射技术代替我们上面的工厂类将数据的更换写进配置文件,我们通过读取配置文件中即可实现将程序由编译时转为运行时。这里可以总结的说:所有用到简单工厂的地方,都可以考虑使用反射技术来去除switch或if,解除分支判断带来的耦合。

    由于篇幅限制,大姐抽象工厂阐述的并不是很具体,大家可以根据UML类图自行写代码实现一下,会加深印象。具体的反射的实现,我们将在后序讲解Spring源码中具体去谈到,敬请期待。文中如有问题还望大家能不吝赐教,共同学习进步。

    


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章