简单工厂模式、工厂模式以及抽象工厂模式(具体)

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

一、简单工厂模式:

实例化对象的时候不再使用 new Object()形式,可以根据用户的选择条件来实例化相关的类。对于客户端来说,去除了具体的类的依赖。只需要给出具体实例的描述给工厂,工厂就会自动返回具体的实例对象。


具体实现如下:

1. 定义一个操作接口:

public interface Operation {

    public double getResult(double numberA,double numberB) throws Exception;

}

2. 定义具体的操作类:

public class Add implements Operation{

    // 加法计算
    public double getResult(double numberA, double numberB) {

        return numberA + numberB;
    }
}


public class Sub implements Operation{

    // 减法计算
    public double getResult(double numberA, double numberB) {
        return numberA-numberB;
    }
}


public class Mul implements Operation{

    // 乘法计算
    public double getResult(double numberA, double numberB) {
        return numberA * numberB;
    }
}


public class Div implements Operation {

    // 除法计算
    public double getResult(double numberA, double numberB) throws Exception {
        if (numberB == 0) {
            throw new Exception("除数不能为0!");
        }
        return numberA / numberB;
    }
}

3. 定义简单工厂类:

public class EasyFactory {

    // 简单工厂,根据字符串创建相应的对象
    public static Operation createOperation(String name) {
        Operation operationObj = null;
        switch (name) {
            case "+":
                operationObj = new Add();
                break;
            case "-":
                operationObj = new Sub();
                break;
            case "*":
                operationObj = new Mul();
                break;
            case "/":
                operationObj = new Div();
                break;
        }
        return operationObj;
    }
}

4. 用户端代码:

public class Client {

    public static void main(String[] args) throws Exception {

        Operation add = EasyFactory.createOperation("+");
        Operation sub = EasyFactory.createOperation("-");
        Operation mul = EasyFactory.createOperation("*");
        Operation div = EasyFactory.createOperation("/");

        System.out.println(add.getResult(1, 1));
        System.out.println(sub.getResult(1, 1));
        System.out.println(mul.getResult(1, 1));
        System.out.println(div.getResult(1, 1));
    }
}

Result:

2.0
0.0
1.0
1.0

我们无需提供具体的子类类名,只需要提供一个字符串即可得到相应的实例对象。这样的话,当子类的类名更换或者增加子类时我们都无需修改客户端代码,只需要在简单工厂类上增加一个分支判断代码即可。

使用这种模式,我们在生成工厂的时候可以加一些业务代码,如日志、判断业务等,这时候可以直接在switch case中加上去就行了,如下:

public class EasyFactory {

    private static Operation operationObj = null;

    private static Operation add(){
        System.out.println("加法运算");
        return new Add();
    }
    private static Operation sub(){
        System.out.println("减法运算");
        return new Sub();
    }
    private static Operation mul(){
        System.out.println("乘法运算");
        return new Mul();
    }
    private static Operation div(){
        System.out.println("除法运算");
        return new Div();
    }

    // 简单工厂,根据字符串创建相应的对象
    public static Operation createOperation(String name) {

        switch (name) {
            case "+":
                operationObj = add();
                break;
            case "-":
                operationObj = sub();
                break;
            case "*":
                operationObj = mul();
                break;
            case "/":
                operationObj = div();
                break;
        }
        return operationObj;
    }
}
这样做的优点:我们可以对创建的对象进行一些 “加工” ,而且客户端并不知道,因为工厂隐藏了这些细节。如果,没有工厂的话,那我们是不是就得自己在客户端上写这些代码,这就好比本来可以在工厂里生产的东西,拿来自己手工制作,不仅麻烦以后还不好维护。


但是缺点也很明显:如果需要在方法里写很多与对象创建有关的业务代码,而且需要的创建的对象还不少的话,我们要在这个简单工厂类里编写很多个方法,每个方法里都得写很多相应的业务代码,而每次增加子类或者删除子类对象的创建都需要打开这简单工厂类来进行修改。这会导致这个简单工厂类很庞大臃肿、耦合性高,而且增加、删除某个子类对象的创建都需要打开简单工厂类来进行修改代码也违反了开-闭原则。


二、工厂模式

这时候就需要使用工厂模式了。工厂方法模式是对简单工厂模式进一步的解耦,因为在工厂方法模式中是一个子类对应一个工厂类,而这些工厂类都实现于一个抽象接口。这相当于是把原本会因为业务代码而庞大的简单工厂类,拆分成了一个个的工厂类,这样代码就不会都耦合在同一个类里了。


这时上面那个例子的结构图为:


1. 首先定义一个工厂接口:

import org.zero01.operation.Operation;

public interface Factory {

    public Operation createOperation() ;

}

2. 然后是具体的工厂类:

// 加法类工厂
public class AddFactory implements Factory{

    public Operation createOperation() {
        System.out.println("加法运算");
        return new Add();
    }
}

// 减法类工厂
public class SubFactory implements Factory{

    public Operation createOperation() {
        System.out.println("减法运算");
        return new Sub();
    }
}
........

3. 运算类跟简单工厂一样。

4. 客户端代码:

public class Client {

    public static void main(String[] args) throws Exception {

        // 使用反射机制实例化工厂对象,因为字符串是可以通过变量改变的
        Factory addFactory = (Factory) Class.forName("org.zero01.factory.AddFactory").newInstance();
        Factory subFactory=(Factory) Class.forName("org.zero01.factory.SubFactory").newInstance();

        // 通过工厂对象创建相应的实例对象
        Operation add = addFactory.createOperation();
        Operation sub = subFactory.createOperation();

        System.out.println(add.getResult(1, 1));
        System.out.println(sub.getResult(1, 1));
    }
}

比较:

工厂模式中,要增加产品类时也要相应地增加工厂类,客户端的代码也增加了不少。工厂方法把简单工厂的内部逻辑判断转移到了客户端代码来进行。

你想要加功能,本来是改工厂类的,而现在是修改客户端。而且各个不同功能的实例对象的创建代码,也没有耦合在同一个工厂类里,这也是工厂方法模式对简单工厂模式解耦的一个体现。工厂方法模式克服了简单工厂会违背开-闭原则的缺点,又保持了封装对象创建过程的优点。

但工厂方法模式的缺点是每增加一个产品类,就需要增加一个对应的工厂类,增加了额外的开发量。


3. 抽象工厂模式:

场景:对数据库中的表进行修改

此时,使用工厂模式结构图如下:


1. 我们现在要对mysql/oracle数据库中的User表进行操作,User表定义如下:

public class User {
    private int uid;
    private String uname;

    public int getUid() {
        return uid;
    }

    public void setUid(int uid) {
        this.uid = uid;
    }

    public String getUname() {
        return uname;
    }

    public void setUname(String uname) {
        this.uname = uname;
    }
}

2. 接下来我们定义一个对User进行操作的接口:

public interface IUser {
    public void insert(User user);
    public User getUser(int uid);
}

3. 实现一个对mysql中User进行操作的类:

public class mysqlUser implements IUser{

    public void insert(User user){
        System.out.println("在mysql中的user表中插入一条元素");
    }

    public User getUser(int id){
        System.out.println("在mysql中的user表得到id为"+id+"的一条数据");
        return null;
    }
}

  实现对oracle中User进行操作的类:

public class oracleUser implements IUser{

    @Override
    public void insert(User user) {
        System.out.println("在oracle中的user表中插入一条元素");
    }

    @Override
    public User getUser(int uid) {
        System.out.println("在oracle中的user表得到id为"+uid+"的一条数据");
        return null;
    }
}

4. 接下来定义一个工厂接口,用于生产访问User表的对象

public interface sqlFactory {
    public IUser createUser();     //用于访问User表的对象
}

5. 生产mysqlUser对象的mysql工厂类:

public class mysqlFactory implements sqlFactory {
    @Override
    public IUser createUser() {
        return new mysqlUser();  //访问mysql中User表的对象
    }
}

生成oracleUser对象的oracle工厂类:

public class oracleFactory implements sqlFactory {
    @Override
    public IUser createUser() {
        return new oracleUser();   //访问oracle中User表的对象
    }
}

6. 最后用户测试类如下:

public class test_abstractFactory {
    public static void main(String[] args) {
        sqlFactory factory1 = new mysqlFactory();
        IUser userOperator = factory1.createUser();
        userOperator.getUser(1);
        userOperator.insert(new User());
    }
}
结果为:
在mysql中的user表得到id为1的一条数据
在mysql中的user表中插入一条元素


到此为止,工厂模式都可以很好的解决,由于多态的关系,sqlFactory在声明对象之前都不知道在访问哪个数据库,却可以在运行时很好的完成任务,这就是业务逻辑与数据访问的解耦。

但是,当数据库中不止一个表的时候该怎么解决问题呢,此时就可以引入抽象工厂模式了,结构图如下:


1. 比如说现在增加了一个Login类,用于记录登陆信息:

package DesignPattern.abstractFactory;

import java.util.Date;

public class Login {
    private int id;
    private Date date;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public Date getDate() {
        return date;
    }

    public void setDate(Date date) {
        this.date = date;
    }
}

2. 此时就要相应地添加 对login表操作的Ilogin接口,mysqlLogin类,oracleLogin类:

public interface ILogin {

    public void insert(Login login);
    public Login getLogin(int id);

}
public class MysqlLogin implements ILogin{

    public void insert(Login login) {
        System.out.println("对 MySQL 里的 Login 表插入了一条数据");
    }

    public Login getLogin(int id) {
        System.out.println("通过 uid 在 MySQL 里的 Login 表得到了一条数据");
        return null;
    }
}
public class OracleLogin implements ILogin{

    public void insert(Login login) {
        System.out.println("对 Oracle 里的 Login 表插入了一条数据");
    }

    public Login getLogin(int id) {
        System.out.println("通过 uid 在 Oracle 里的 Login 表得到了一条数据");
        return null;
    }
}

3. 修改Factory接口及Factory实现类的内容:

IFactory,定义一个抽象的工厂接口,该工厂用于生产访问User表以及Login表的对象:

public interface IFactory {

    public IUser createUser();
    public ILogin createLogin();
}
public class MysqlFactory implements IFactory{

    public IUser createUser() {
        return new MysqlUser();
    }

    public ILogin createLogin() {
        return new MysqlLogin();
    }
}
public class OracleFactory implements IFactory{

    public IUser createUser() {
        return new OracleUser();
    }

    public ILogin createLogin() {
        return new OracleLogin();
    }
}
4. 客户端代码:
public class Client {

    public static void main(String[] args){

        User user=new User();
        Login login = new Login();

        // 只需要确定实例化哪一个数据库访问对象给factory
        // IFactory factory=new MysqlFactory();
        IFactory factory=new OracleFactory();

        // 已与具体的数据库访问解除了耦合
        IUser userOperation=factory.createUser();

        userOperation.getUser(1);
        userOperation.insert(user);

        // 已与具体的数据库访问解除了耦合
        ILogin loginOperation=factory.createLogin();

        loginOperation.insert(login);
        loginOperation.getLogin(1);

    }
}

结果:

通过 uid 在 Oracle 里的 User 表得到了一条数据
对 Oracle 里的 User 表插入了一条数据
对 Oracle 里的 Login 表插入了一条数据
通过 uid 在 Oracle 里的 Login 表得到了一条数据
所以抽象工厂与工厂方法模式的区别在于:抽象工厂是可以生产多个产品的,例如 MysqlFactory 里可以生产 MysqlUser 以及 MysqlLogin 两个产品,而这两个产品又是属于一个系列的,因为它们都是属于MySQL数据库的表。而工厂方法模式则只能生产一个产品,例如之前的 MysqlFactory 里就只可以生产一个 MysqlUser 产品。



抽象工厂模式的优缺点:

优点:

1. 抽象工厂模式最大的好处是易于交换产品系列,由于具体工厂类,例如 IFactory factory=new OracleFactory(); 在一个应用中只需要在初始化的时候出现一次,这就使得改变一个应用的具体工厂变得非常容易,它只需要改变具体工厂即可使用不同的产品配置。不管是任何人的设计都无法去完全防止需求的更改,或者项目的维护,那么我们的理想便是让改动变得最小、最容易,例如我现在要更改以上代码的数据库访问时,只需要更改具体的工厂即可。

2. 抽象工厂模式的另一个好处就是它让具体的创建实例过程与客户端分离,客户端是通过它们的抽象接口操作实例,产品实现类的具体类名也被具体的工厂实现类分离,不会出现在客户端代码中。就像我们上面的例子,客户端只认识IUser和ILogin,至于它是MySQl里的表还是Oracle里的表就不知道了。

缺点:

1. 如果你的需求来自增加功能,比如增加Login表,就有点太烦了。首先需要增加 ILogin,mysqlLogin,oracleLogin。 然后我们还要去修改工厂类: sqlFactory, mysqlFactory, oracleFactory 才可以实现,需要修改三个类,实在是有点麻烦。

2. 还有就是,客户端程序肯定不止一个,每次都需要声明sqlFactory factory=new MysqlFactory(), 如果有100个调用数据库的类,就需要更改100次sqlFactory factory=new oracleFactory()。


4. 抽象工厂模式的改进1。(简单工厂+抽象工厂)

我们将IFactory、MySQLFactory以及OracleFactory三个工厂类都抛弃掉,取而代之的是一个简单工厂类EasyFactory,如下:


EasyFactory类,简单工厂:

public class EasyFactory {

    // 数据库名称
    private static String db="MySQL";
    // private static String db="Oracle";

    public static IUser createUser(){

        IUser user=null;
        switch (db){
            case "MySQL":
                user=new MysqlUser();
                break;

            case "Oracle":
                user=new OracleUser();
                break;
        }
        return user;
    }

    public static ILogin createLogin(){

        ILogin login=null;
        switch (db){
            case "MySQL":
                login=new MysqlLogin();
                break;

            case "Oracle":
                login=new OracleLogin();
                break;
        }
        return login;
    }
}

客户端代码:

public class Client {

    public static void main(String[] args){

        User user=new User();
        Login login = new Login();

        // 直接得到实际的数据库访问实例,而不存在任何依赖
        IUser userOperation= EasyFactory.createUser();

        userOperation.getUser(1);
        userOperation.insert(user);

        // 直接得到实际的数据库访问实例,而不存在任何依赖
        ILogin loginOperation=EasyFactory.createLogin();

        loginOperation.insert(login);
        loginOperation.getLogin(1);

    }
}

由于事先在简单工厂类里设置好了db的值,所以简单工厂的方法都不需要由客户端来输入参数,这样在客户端就只需要使用 EasyFactory.createUser(); 和 EasyFactory.createLogin(); 方法来获得具体的数据库访问类的实例,客户端代码上没有出现任何一个 MySQL 或 Oracle 的字样,达到了解耦的目的,客户端已经不再受改动数据库访问的影响了。


5. 抽象工厂的改进2(反射+简单工厂)

使用反射的话,我们就可以不需要使用switch,因为使用switch的话,我添加一个sql server数据库的话,又要switch的话又需要添加case条件。

我们可以根据 选择的数据库名称,如 “mysql”, 利用反射技术自动的获得所需要的实例:

public class easyFactory1 {

    private static String packName = "DesignPattern.abstractFactory";
    private static String sqlName = "mysql";

    public static IUser createUser() throws Exception{
        String className = packName+"."+sqlName+"User";
        return (IUser)Class.forName(className).newInstance();
    }

    public static ILogin createLogin() throws Exception{
        String className = packName+"."+sqlName+"Login";
        return (ILogin)Class.forName(className).newInstance();
    }
}

以上我们使用简单工厂模式设计的代码中,是用一个字符串类型的db变量来存储数据库名称的,所以变量的值到底是 MySQL 还是 Oracle ,完全可以由事先设置的那个db变量来决定,而我们又可以通过反射来去获取实例,这样就可以去除switch语句了。


6. 抽象工厂的改进3(反射+配置文件+简单工厂)

在使用反射之后,我们还是需要进easyFactory中修改数据库类型,还不是完全符合开-闭原则。

我们可以通过配置文件来达到目的,每次通过读取配置文件来知道我们应该使用哪种数据库。

如下是一个json类型的配置文件,也可以使用xml类型的配置文件:

{
  "packName": "DesignPattern.abstractFactory",
  "DB": "Mysql"
}

之后就可以通过这个配置文件去找需要加载的类是哪一个。

我们通过反射机制+配置文件+简单工厂模式解决了数据库访问时的可维护、可扩展的问题。


参考:《大话设计模式》

               http://blog.51cto.com/zero01/2067822

               http://blog.51cto.com/zero01/2070033

展开阅读全文

没有更多推荐了,返回首页