面向对象编程设计模式------工厂模式(简单工厂、工厂方法、抽象工厂)

1、简单工厂模式

  简单工厂模式又叫静态工厂方法模式(Static Factory Method Pattern),是通过专门定义一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类。
  一个简单的实例:要求实现一个计算机控制台程序,要求输入数的运算结果。最原始的解决方法如下:

/**
 * @Description:这里使用的是最基本的实现,并没有体现出面向对象的编程思想,代码的扩展性差,甚至连除数可能为0的情况也没有考虑
 */
public static void main(String[] args) {
    scanner = new Scanner(System.in);
    
    System.out.print("请输入第一个数字:");
    int firstNum = scanner.nextInt();
    
    System.out.print("请输入第二个数字:");
    int secondNum = scanner.nextInt();
    
    System.out.print("请输入运算符:");
    String operation = scanner.next();
    
    if(operation.equals("+")) {
        System.out.println("result:" + (firstNum + secondNum));
    } else if(operation.equals("-")) {
        System.out.println("result:" + (firstNum - secondNum));
    } else if(operation.equals("*")) {
        System.out.println("result:" + (firstNum * secondNum));
    } else if(operation.equals("/")){
        System.out.println("result:" + (firstNum / secondNum));
    }
}

  上面的写法实现虽然简单,但是却没有面向对象的特性,代码拓展性差,甚至没有考虑除数可能为0的特殊情况。
  在面向对象编程语言中,一切都是对象,所以上面运算符号也应当作对象来处理。因此我们首先建立一个运算接口,所有其他的运算都封装成类,并实现该运算接口。

/**
 * @Description: 定义一个运算接口,将所有的运算符号都封装成类,并实现本接口
 * @author: zxt
 * @time: 2018年7月6日 上午10:24:13
 */
public interface Operation {
    public double getResult(double firstNum, double secondNum);
}

public class AddOperation implements Operation {
    @Override
    public double getResult(double firstNum, double secondNum) {
        return firstNum + secondNum;
    }
}

public class SubOperation implements Operation {
    @Override
    public double getResult(double firstNum, double secondNum) {
        return firstNum - secondNum;
    }
}

public class MulOperation implements Operation {
    @Override
    public double getResult(double firstNum, double secondNum) {
        return firstNum * secondNum;
    }
}

public class DivOperation implements Operation {
    @Override
    public double getResult(double firstNum, double secondNum) {
        if(secondNum == 0) {
            try {
                throw new Exception("除数不能为0!");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        
        return firstNum / secondNum;
    }
}

  现在的问题的是,如何根据不同的情况创建不同的对象,这里就可以使用简单工厂模式来实现了,客户端只需要提供运算符,工厂类会判断并生成相应的运算类:

/**
 * @Description: 简单工厂模式:通过一个工厂类,根据情况创建不同的对象
 * @author: zxt
 * @time: 2018年7月6日 上午10:50:15
 */
public class OperationFactory {
    /**
     * @Description:根据运算符得到具体的运算类
     * @param operationStr
     */
    public static Operation getOperation(String operationStr) {
        Operation result = null;
        
        switch(operationStr) {
        case "+":
            result = new AddOperation();
            break;
        case "-":
            result = new SubOperation();
            break;
        case "*":
            result = new MulOperation();
            break;
        case "/":
            result = new DivOperation();
            break;
        }
        
        return result;
    }
}

// 客户端调用
Operation oper = OperationFactory.getOperation(operation);
double result = oper.getResult(firstNum, secondNum);
System.out.println(result);

  简单工厂将对象的创建过程进行了封装,用户不需要知道具体的创建过程,只需要调用工厂类获取对象即可。
  这种简单工厂的写法是通过switch-case来判断对象创建过程的。在实际使用过程中,违背了开放-关闭原则(例如,当需要扩展一个新的运算符之后,简单工厂创建的对象也必须多一种,这就需要修改原来的代码了,违背了对修改关闭的原则),当然有些情况下可以通过反射调用来弥补这种不足。

2、工厂方法模式

  简单工厂模式的最大优点在于工厂类中包含了必要的逻辑判断,根据客户端的选择条件动态实例化相关的类,对于客户端来说,去除了与具体产品的依赖。但是每扩展一个类时,都需要改变工厂类里的方法,这就违背了开放-封闭原则。于是工厂方法模式来了:
  工厂方法模式(Factory Method),定义一个用于创建对象的接口,让子类决定实例化哪一个类,工厂方法使一个类的实例化延迟到其子类。 继续上一个计算器的例子,简单工厂模式由工厂类直接生成相应的运算类对象,判断的逻辑在工厂类中,而工厂方法模式的实现则是定义一个工厂接口,然后每个运算类都对应一个工厂类来创建,然后在客户端判断使用哪个工厂类来创建运算类。

/**
 * @Description: 工厂的接口
 * @author: zxt
 * @time: 2019年2月21日 下午2:49:43
 */
public interface IFactory {
    
    public Operation createOperation();
}

/**
 * @Description: 加法类工厂
 */
public class AddFactory implements IFactory {
    @Override
    public AddOperation createOperation() {
        return new AddOperation();
    }
}

/**
 * @Description: 减法类工厂
 */
public class SubFactory implements IFactory {
    @Override
    public SubOperation createOperation() {
        return new SubOperation();
    }
}

/**
 * @Description: 乘法类工厂
 */
public class MulFactory implements IFactory {
    @Override
    public MulOperation createOperation() {
        return new MulOperation();
    }
}

/**
 * @Description: 除法类工厂
 */
public class DivFactory implements IFactory {

    @Override
    public DivOperation createOperation() {
        return new DivOperation();
    }
}

  工厂方法模式实现时,客户端需要决定实例化哪一个工厂来实现运算类,选择判断的问题还是存在的,也就是说,工厂方法把简单工厂的内部逻辑判断移到了客户端代码来进行。你想要加功能,本来是改工厂类的,而现在是修改客户端。

/**
 * @Description: 实现一个简单的计算器功能,使用工厂方法模式
 * @author: zxt
 * @time: 2018年7月6日 上午10:11:50
 */
public class Computer {

    private static Scanner scanner;
    
    public static void main(String[] args) {
        scanner = new Scanner(System.in);
        
        System.out.print("请输入第一个数字:");
        int firstNum = scanner.nextInt();
        
        System.out.print("请输入第二个数字:");
        int secondNum = scanner.nextInt();
        
        System.out.print("请输入运算符:");
        String operation = scanner.next();
        
        IFactory operFactory = null;
        if(operation.equals("+")) {
            operFactory = new AddFactory();
        } else if(operation.equals("-")) {
            operFactory = new SubFactory();
        } else if(operation.equals("*")) {
            operFactory = new MulFactory();
        } else if(operation.equals("/")){
            operFactory = new DivFactory();
        }
        Operation oper = operFactory.createOperation();
        double result = oper.getResult(firstNum, secondNum);
        System.out.println("result = " + result);
    }
}

  增加新功能时,工厂方法模式比简单工厂模式修改的代码量更小,工厂方法克服了简单工厂违背开放封闭原则的缺点,又保持了封装对象创建过程的优点。但是工厂方法的缺点就是每加一个产品,就需要加一个产品工厂的类,增加了额外的开发量。当然这两种模式都还不是最佳的做法。

3、抽象工厂模式

  抽象工厂模式是所有形态的工厂模式中最为抽象和最具一般性的一种形态。抽象工厂模式是指当有多个抽象角色时,使用的一种工厂模式。抽象工厂模式可以向客户端提供一个接口,使客户端在不必指定产品的具体的情况下,创建多个产品族中的产品对象。根据里氏替换原则,任何接受父类型的地方,都应当能够接受子类型。因此,实际上系统所需要的,仅仅是类型与这些抽象产品角色相同的一些实例,而不是这些抽象产品的实例。换言之,也就是这些抽象产品的具体子类的实例。工厂类负责创建抽象产品的具体子类的实例。
  抽象工厂模式(Abstract Factory):提供一个创建一系列相关或相互依赖对象的接口,而无需指定他们具体的类。
  实例场景:对数据库(各种不同的数据库)中的表进行修改,此时,使用工厂模式结构图如下:

1、User表的定义:

public class User {
    private int id;
    private String name;

    public int getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

2、定义一个对User表进行操作的接口:

/**
 * @Description: 对User类操作的接口
 * @author: zxt
 * @time: 2019年2月24日 下午7:00:20
 */
public interface IUser {
    
    void insert(User user);
    
    User getUser(int id);
}

3、实现Sql Server数据库对User表的操作:

/**
 * @Description:  SQL Server数据库中对User表的操作
 * @author: zxt
 * @time: 2019年2月24日 下午7:04:39
 */
public class SqlServerUser implements IUser {
    @Override
    public void insert(User user) {
        System.out.println("在  SQL Server 中给 User 表增加一条记录!");
    }

    @Override
    public User getUser(int id) {
        System.out.println("在  SQL Server 中根据ID得到  User 表的一条记录!");
        return null;
    }
}

实现Oracle数据库对User表的操作:

/**
 * @Description: Oracle数据库中对User表的操作
 * @author: zxt
 * @time: 2019年2月24日 下午7:05:07
 */
public class OracleUser implements IUser {

    @Override
    public void insert(User user) {
        System.out.println("在  Oracle 中给 User 表增加一条记录!");
    }

    @Override
    public User getUser(int id) {
        System.out.println("在 Oracle 中根据ID得到  User 表的一条记录!");
        return null;
    }
}

4、定义一个抽象工厂接口,用于生成对User表的操作的对象:

/**
 * @Description: 得到对User表操作的IUser对象的抽象工厂接口
 * @author: zxt
 * @time: 2019年2月24日 下午7:06:37
 */
public interface IFactory {
    
    public IUser createUser();
}

5、SQLServerFactory工厂用于生成操作Sql Server数据库的SqlServerUser对象:

public class SQLServerFactory implements IFactory {

    @Override
    public IUser createUser() {
        return new SqlServerUser();
    }
}

OracleFactory工厂用于生成操作Oracle数据库的OracleUser对象:

public class OracleFactory implements IFactory {

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

6、客户端的使用:

public class FactoryMethodTest {

    public static void main(String[] args) {
        User user = new User();
        
        // 若要改成Oracle数据库,只需要将这句改成OracleFactory即可
        IFactory ifactory = new SQLServerFactory();
        
        IUser iu = ifactory.createUser();
        
        iu.insert(user);
        iu.getUser(1);
    }
}


  到此为止,工厂模式都可以很好的解决,由于多态的关系,IFactory在声明对象之前都不知道在访问哪个数据库,却可以在运行时很好的完成任务,这就是业务逻辑与数据访问的解耦。
  但是,当数据库中不止一个表的时候该怎么解决问题呢,此时就可以引入抽象工厂模式了,结构图如下:


例如增加了部门表Department:

public class Department {
    private int id;
    private String deptName;
    
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getDeptName() {
        return deptName;
    }
    public void setDeptName(String deptName) {
        this.deptName = deptName;
    }
}

则增加相应的对Department表操作的接口:

public interface IDepartment {
    
    public void insert(Department department);
    
    public Department getDepartment(int id);
}

实现Sql Server数据库对Department表的操作:

/**
 * @Description:  SQL Server数据库中对Department表的操作
 * @author: zxt
 * @time: 2019年2月24日 下午7:04:39
 */
public class SqlServerDepartment implements IDepartment {
    @Override
    public void insert(Department user) {
        System.out.println("在  SQL Server 中给 Department 表增加一条记录!");
    }

    @Override
    public Department getDepartment(int id) {
        System.out.println("在  SQL Server 中根据ID得到  Department 表的一条记录!");
        return null;
    }
}

实现Oracle数据库对Department表的操作:

/**
 * @Description: Oracle数据库中对Department表的操作
 * @author: zxt
 * @time: 2019年2月24日 下午7:05:07
 */
public class OracleDepartment implements IDepartment {
    @Override
    public void insert(Department user) {
        System.out.println("在  Oracle 中给 Department 表增加一条记录!");
    }

    @Override
    public Department getDepartment(int id) {
        System.out.println("在 Oracle 中根据ID得到  Department 表的一条记录!");
        return null;
    }
}

IFactory抽象工厂中增加生成对Department表操作的对象:

/**
 * @Description: 得到对User表操作的IUser对象的抽象工厂接口
 * @author: zxt
 * @time: 2019年2月24日 下午7:06:37
 */
public interface IFactory {
    
    public IUser createUser();
    
    public IDepartment createDepartment();
}

SQLServerFactory工厂增加生成操作Sql Server数据库的SqlServerDepartment对象:

public class SQLServerFactory implements IFactory {
    @Override
    public IUser createUser() {
        return new SqlServerUser();
    }

    @Override
    public IDepartment createDepartment() {
        return new SqlServerDepartment();
    }
}

OracleFactory工厂增加生成操作Oracle数据库的OracleDepartment对象:

public class OracleFactory implements IFactory {
    @Override
    public IUser createUser() {
        return new OracleUser();
    }

    @Override
    public IDepartment createDepartment() {
        return new OracleDepartment();
    }
}

客户端的使用:

public class AbstractFactoryTest {

    public static void main(String[] args) {
        User user = new User();
        Department department = new Department();
        
        // 若要改成SQL Server数据库,只需要将这句改成SqlServerFactory即可
        IFactory ifactory = new OracleFactory();
        
        IUser iu = ifactory.createUser();
        iu.insert(user);
        iu.getUser(1);
        
        IDepartment id = ifactory.createDepartment();
        id.insert(department);
        id.getDepartment(1);
    }
}



  所以抽象工厂与工厂方法模式的区别在于:抽象工厂是可以生产多个产品的,例如OracleFactory 里可以生产 OracleUser以及 OracleDepartment两个产品,而这两个产品又是属于一个系列的,因为它们都是属于Oracle数据库的表。而工厂方法模式则只能生产一个产品,例如之前的 OracleFactory里就只可以生产一个 OracleUser产品。


  抽象工厂模式的优缺点:
  优点:
  1、抽象工厂模式最大的好处是易于交换产品系列,由于具体工厂类,例如 IFactory factory = new OracleFactory(); 在一个应用中只需要在初始化的时候出现一次,这就使得改变一个应用的具体工厂变得非常容易,它只需要改变具体工厂即可使用不同的产品配置。不管是任何人的设计都无法去完全防止需求的更改,或者项目的维护,那么我们的理想便是让改动变得最小、最容易。
  2、抽象工厂模式的另一个好处就是它让具体的创建实例过程与客户端分离,客户端是通过它们的抽象接口操作实例,产品实现类的具体类名也被具体的工厂实现类分离,不会出现在客户端代码中。就像我们上面的例子,客户端只认识IUser和IDepartment,至于它是Sql Server里的表还是Oracle里的表就不知道了。
  缺点:
  1、如果你的需求来自增加功能,比如增加Department表,就有点太烦了。首先需要增加IDepartment,SQLServerDepartment,OracleDepartment。然后我们还要去修改工厂类:IFactory,SQLServerFactory,OracleFactory才可以实现,需要修改三个类,实在是有点麻烦。
  2、还有就是,客户端程序肯定不止一个,每次都需要声明IFactory factory = new OracleFactory(),如果有100个调用数据库的类,就需要更改100次IFactory factory = new OracleFactory()。

3.1、抽象工厂模式的改进1(简单工厂+抽象工厂)

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

public class EasyFactory {

    private static String db = "SqlServer";
    // private static String db = "Oracle";

    public static IUser createUser() {
        IUser result = null;
        switch (db) {
        case "SqlServer":
            result = new SqlServerUser();
            break;
        case "Oracle":
            result = new OracleUser();
            break;
        }

        return result;
    }

    public static IDepartment createDepartment() {
        IDepartment result = null;
        switch (db) {
        case "SqlServer":
            result = new SqlServerDepartment();
            break;
        case "Oracle":
            result = new OracleDepartment();
            break;
        }

        return result;
    }
}

客户端:

public class EasyClient {

    public static void main(String[] args) {
        User user = new User();
        Department department = new Department();

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

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

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

        departmentOperation.insert(department);
        departmentOperation.getDepartment(1);
    }
}

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

3.2、抽象工厂的改进2(反射+简单工厂)

  使用反射的话,我们就可以不需要使用switch,因为使用switch的话,我添加一个Mysql数据库的话,又要switch的话又需要添加case条件。
  我们可以根据选择的数据库名称,如“mysql”,利用反射技术自动的获得所需要的实例:

public class EasyFactoryReflect {

    private static String packName = "com.zxt.abstractfactory";
    private static String sqlName = "Oracle";

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

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

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

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

  在使用反射之后,我们还是需要进EasyFactory中修改数据库类型,还不是完全符合开-闭原则。我们可以通过配置文件来达到目的,每次通过读取配置文件来知道我们应该使用哪种数据库。
  如下是一个json类型的配置文件,也可以使用xml类型的配置文件:

{
    "packName": " com.zxt.abstractfactory",
    "DB": "Oracle"
}

  之后就可以通过这个配置文件去找需要加载的类是哪一个。我们通过反射机制+配置文件+简单工厂模式解决了数据库访问时的可维护、可扩展的问题。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,530评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,403评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,120评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,770评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,758评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,649评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,021评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,675评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,931评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,751评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,410评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,004评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,969评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,042评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,493评论 2 343

推荐阅读更多精彩内容

  • 设计模式汇总 一、基础知识 1. 设计模式概述 定义:设计模式(Design Pattern)是一套被反复使用、多...
    MinoyJet阅读 3,897评论 1 15
  • 国家电网公司企业标准(Q/GDW)- 面向对象的用电信息数据交换协议 - 报批稿:20170802 前言: 排版 ...
    庭说阅读 10,863评论 6 13
  • 设计模式基本原则 开放-封闭原则(OCP),是说软件实体(类、模块、函数等等)应该可以拓展,但是不可修改。开-闭原...
    西山薄凉阅读 3,748评论 3 13
  • 链接:https://github.com/WiKi123/DesignPattern作者: WiKi123(gi...
    树懒啊树懒阅读 3,455评论 0 2
  • 不记得有多久没去过清吧酒吧活动了,今晚去听歌的时候,有一种找回了年轻的记忆感觉,也曾经跟好友定期去玩,那时感觉无忧...
    圆圆_04b9阅读 223评论 0 0