23种设计模式(2)-简单工厂模式

        这些设计模式都是在这么多年的软件开发中,先辈对软件重构的经验总结提炼的结果,这些设计模式要遵循软件设计的六原则。每一种设计模式都有相应的需求场景的。有了这些设计模式的思想和面向对象的思想,在软件设计特定的需求中会给你解决思路。


一,需求场景

        在此,我也借用书上看到的一个例子。计算器工厂给我们留了一些任务,设计一个计算器做成成品卖给买家。但是这个任务是分两个阶段让我们实现的。如下:
        阶段一:买家目前只需要计算器具有加减程序的功能即可。别的功能待市场需求再做设计。也就是阶段二的任务。
        阶段二:阶段一做成的成品投放到市场发现并不能满足小学生市场的需求。现在小学生可牛逼了,有的班级都开始学习求模运算了。


二,不采用工厂模式

1,版本一

        作为程序员的你看到这个阶段一需求肯定觉得很简单,然后就开始动手写代码了:

package com.factory.simplef;

import java.util.Scanner;

/**
 * 代码版本一
 * @author DC
 *
 */
public class Computer1 {

    /**
     * 模拟计算器过程
     * @param args
     */
    public static void main(String[] args) {

        System.out.println("计算器开启,欢迎使用!");

        Scanner in=new Scanner(System.in);

        System.out.print("请输入第一个数:");
        int number1=in.nextInt();//在此不考虑不输入的情况

        System.out.print("\n请输入第二个数:");
        int number2=in.nextInt();//在此不考虑不输入的情况

        System.out.print("\n请选择运算符:");
        String operate=in.next();//在此不考虑不输入的情况

        int result=0;

        switch (operate) {
        case "+":
            result=number1+number2;
            break;
        case "-":
            result=number1-number2;
            break;
        case "*":
            result=number1*number2;
            break;
        case "/":
            while(number2==0){
                System.out.print("\n不能输入0,请重新输入:");
                number2=in.nextInt();
            }
            result=number1/number2;
            break;
        default:
            System.out.println("不具有此功能");
            break;
        }

        System.out.print("计算结果为:"+result+"\n");

    }
}

        运行一下:
        这里写图片描述

        看到这个运行结果,你可能很开心,但是你的项目经理估计要不开心啦。项目经理肯定要问你,如果买家需要求模运算也加进去,你是不是再把你的代码拿过来修改一下?如果再有别的功能再加进来,你是不是还要再把你的代码拿过来再修改一下?项目经理的意思就是你代码的扩展性太差了。另外代码中还有一个问题,就是那些代码是面向过程而非面向对象的,业务逻辑和界面代码混在了一起。那么如何写出面向对象化的具有高扩展性的代码呢?

2,版本二

        面向对象具有封装,继承和多态的特征。那我们就用这三个特征对以上代码进行处理。计算器是个对象,先来设计一个计算器类Computer2。再在Computer2类中设置两个存储数据的成员变量。Computer2如下:

package com.factory.simplef;

import java.util.Scanner;

/**
 * 
 * 计算机器类--版本2
 * @author DC
 *
 */
public class Computer2 {

    /**
     * 数据1
     */
    private int number1;

    /**
     * 数据2
     */
    private int number2;

    /**
     * 获得计算结果
     * @return
     */
    public static int getComputedResult(int number1,int number2,String operate){
        int result=0;

        switch (operate) {
        case "+":
            result=number1+number2;
            break;
        case "-":
            result=number1-number2;
            break;
        case "*":
            result=number1*number2;
            break;
        case "/":
            while(number2==0){
                Scanner in=new Scanner(System.in);
                System.out.print("\n不能输入0,请重新输入:");
                number2=in.nextInt();
            }
            result=number1/number2;
            break;
        default:
            System.out.println("不具有此功能");
            break;
        }

        return result;
    }



    public int getNumber1() {
        return number1;
    }

    public void setNumber1(int number1) {
        this.number1 = number1;
    }

    public int getNumber2() {
        return number2;
    }

    public void setNumber2(int number2) {
        this.number2 = number2;
    }


}

        上面的计算器类已经封装好了,现在再写一个Client类测试一下。Client类代码如下:

package com.factory.simplef;

import java.util.Scanner;

/**
 * 版本二,测试类
 * @author DC
 *
 */
public class Client {

    public static void main(String[] args) {

        System.out.println("计算器开启,欢迎使用!");

        Scanner in=new Scanner(System.in);

        System.out.print("请输入第一个数:");
        int number1=in.nextInt();//在此不考虑不输入的情况

        System.out.print("\n请输入第二个数:");
        int number2=in.nextInt();//在此不考虑不输入的情况

        System.out.print("\n请选择运算符:");
        String operate=in.next();//在此不考虑不输入的情况

        System.out.println("\n计算结果为:"+Computer2.getComputedResult(number1, number2, operate));
    }
}

        运行测试一下,很Ok,看下图:
        这里写图片描述

        仔细的分析一下,以上的版本2的两个类代码已经是实现了业务逻辑和界面代码分开。并且Computer2类也实现了封装,但是其实并不具有扩展性,当我们需要我们设计的计算器具有求模等运算功能时,我们依旧需要修改Computer2中的getComputedResult()方法的代码。并且是,每次增加一个运算功能,就要新修改一次。这并没有达到扩展性的需求。现在我们利用面向对象的继承与多态特性实现这个扩展性需求。

3,版本三

        修改一下现有的版本二的Computer2类,我们把不变的部分放到父类Computer3,也就是两个成员变量,把具体的求值方法的具体实现getComputedResult()下放给子类去完成,这里的Computer3类我们既可以设置为类,也可以设置为抽象类,在此我们设置为具体类。代码如下:

package com.factory.simplef;

import java.util.Scanner;

/**
 * 
 * 计算机器类--版本3
 * @author DC
 *
 */
public class Computer3 {

    /**
     * 数据1
     */
    private int number1;

    /**
     * 数据2
     */
    private int number2;

    /**
     * 获得计算结果
     * @return
     */
    public int getComputedResult(){
        return 0;
    }



    public int getNumber1() {
        return number1;
    }

    public void setNumber1(int number1) {
        this.number1 = number1;
    }

    public int getNumber2() {
        return number2;
    }

    public void setNumber2(int number2) {
        this.number2 = number2;
    }


}

        现在这个Computer3类已经写好,仔细观察,这个类并不具有什么运算功能。现在我们写几个子类来扩展该计算器功能,代码分别如下:

加法功能计算器:

package com.factory.simplef;
/**
 * 带有+功能的计算器
 * @author DC
 *
 */
public class Computer3WithAdd extends Computer3{

    /**
     * 利用方法覆盖,扩展这个计算器的功能
     */
    @Override
    public int getComputedResult() {
        return super.getNumber1()+super.getNumber2();
    }

}

减法功能计算器:

package com.factory.simplef;
/**
 * 带有-功能的计算器
 * @author DC
 *
 */
public class Computer3WithSub extends Computer3{

    /**
     * 利用方法覆盖,扩展这个计算器的功能
     */
    @Override
    public int getComputedResult() {
        return super.getNumber1()-super.getNumber2();
    }

}

乘法功能计算器:

package com.factory.simplef;
/**
 * 带有*功能的计算器
 * @author DC
 *
 */
public class Computer3WithMul extends Computer3{

    /**
     * 利用方法覆盖,扩展这个计算器的功能
     */
    @Override
    public int getComputedResult() {
        return super.getNumber1()*super.getNumber2();
    }

}

除法功能计算器:

package com.factory.simplef;

import java.util.Scanner;

/**
 * 带有/功能的计算器
 * @author DC
 *
 */
public class Computer3WithDiv extends Computer3{

    /**
     * 利用方法覆盖,扩展这个计算器的功能
     */
    @Override
    public int getComputedResult() {
        if(super.getNumber2()==0){
            System.out.print("除数不能为0,重新输入:");
            Scanner in=new Scanner(System.in);
            super.setNumber2(in.nextInt());
        }
        return super.getNumber1()/super.getNumber2();
    }

}

        测试一下吧,测试代码Client2类如下:

package com.factory.simplef;

import java.util.Scanner;

/**
 * 版本3,测试类
 * @author DC
 *
 */
public class Client2 {

    public static void main(String[] args) {

        System.out.println("计算器开启,欢迎使用!");

        Scanner in=new Scanner(System.in);

        System.out.print("请输入第一个数:");
        int number1=in.nextInt();//在此不考虑不输入的情况

        System.out.print("\n请输入第二个数:");
        int number2=in.nextInt();//在此不考虑不输入的情况

        System.out.print("\n请选择运算符:");
        String operate=in.next();//在此不考虑不输入的情况

        //根据输入运算符,获得具有相应功能的计算机,并计算输出结果
        Computer3 computer=null;
        switch (operate) {
        case "+":
            computer=new Computer3WithAdd();
            break;
        case "-":
            computer=new Computer3WithSub();
            break;
        case "*":
            computer=new Computer3WithMul();
            break;
        case "/":
            computer=new Computer3WithDiv();
            break;

        default:
            System.out.println("暂时不具有此功能呢");
            System.exit(0);
            break;
        }
        computer.setNumber1(number1);
        computer.setNumber2(number2);
        System.out.println("\n计算结果为:"+computer.getComputedResult());
    }
}

这里写图片描述

        一切ok,那么我们的这个计算机器也具有了扩展性。假如我们要实现买家的阶段二的需求,我们不需要改变原计算器类代码,只需要再为Computer3扩展一个子类即可。但是客户端类Client2里面的代码就变得有些麻烦了,解决这个问题,简单工厂模式也就该上场了。


三,采用简单工厂模式

        设计一个计算器的工厂类,根据输入的运算符来创建具有相应运算功能的计算器对象。特别注意简单工厂模式的内部工厂方法一般为静态方法,也因此也把简单工厂模式称为静态工厂模式。工厂类Computer3Factory代码如下:

package com.factory.simplef;
/**
 * 工厂类,简单工厂模式的核心类,一般内部方法为静态方法
 * @author DC
 *
 */
public class Computer3Factory {

    public static Computer3 createComputer3(String operate){
        Computer3 computer=null;
        switch (operate) {
        case "+":
            computer=new Computer3WithAdd();
            break;
        case "-":
            computer=new Computer3WithSub();
            break;
        case "*":
            computer=new Computer3WithMul();
            break;
        case "/":
            computer=new Computer3WithDiv();
            break;

        default:
            System.out.println("暂时不具有此功能呢");
            break;
        }
        return computer;
    }
}

        再来修改一下上个版本的测试类为Client3,具体代码如下:

package com.factory.simplef;

import java.util.Scanner;

/**
 * 版本3,测试类
 * @author DC
 *
 */
public class Client3 {

    public static void main(String[] args) {

        System.out.println("计算器开启,欢迎使用!");

        Scanner in=new Scanner(System.in);

        System.out.print("请输入第一个数:");
        int number1=in.nextInt();//在此不考虑不输入的情况

        System.out.print("\n请输入第二个数:");
        int number2=in.nextInt();//在此不考虑不输入的情况

        System.out.print("\n请选择运算符:");
        String operate=in.next();//在此不考虑不输入的情况

        //利用工厂类获得具有相应功能的计算机,并计算输出结果
        Computer3 computer=Computer3Factory.createComputer3(operate);
        computer.setNumber1(number1);
        computer.setNumber2(number2);
        System.out.println("\n计算结果为:"+computer.getComputedResult());
    }
}

        运行测试:
这里写图片描述

        现在我们的代码就都ok了,既利用面向对象的思想使我们的代码具有扩展性,又利用了简单工厂模式。再仔细思考一下,你会发现简单工厂模式并不是完美,比如假如我们扩展了Computer3类,又多了一个子类,那么我们需要修改简单工厂类Computer3Factory内部的静态工厂方法。解决方法就是再添加一个判断逻辑,创建相应的子类;其实,利用反射去创建相应的实例是最好的选择。这个就是后来我要学习的工厂方法模式和抽象工厂模式要解决的问题之一。这里先不讲。


四,简单工厂模式理论讲解

        这里把别的博友的图借了一张过来,最下面会附上他的博客地址。
        这里写图片描述

        上面我们提到过,简单工厂模式的内部工厂方法一般为静态,也称简单工厂模式为静态工厂模式。它是一个工具类,是专门为别的具有共同接口或者父类的的一系列类提供创建实例的。如上图所示,简单工厂模式涉及几个角色:

  • 工厂角色:简单工厂模式的核心类。它负责创建别的类的实例的内部逻辑,并向外提供一个获得这些实例的调用接口。
  • 抽象产品角色:简单工厂模式所创建的所有类实例的实现接口或者继承的父类,它负责描述所有实例所共有的公共接口。
  • 具体产品角色:简单工厂所创建的具体实例对象,这些具体的产品往往扩展于共同的父类或接口。

        简单工厂模式的核心是,用一个专门的类去创建一个合适的类实例并返回给调用方,以此来满足调用方的具体需求。其中,具体产品一般是一系列类的集合,这些类有共同的实现接口或继承父类。那么我们该如何选择是继承父类还是实现接口呢?一般,如果简单工厂模式所涉及到的具体产品之间没有共同的逻辑,那么我们就可以使用接口来扮演抽象产品的角色;如果具体产品之间有功能的逻辑或,我们就必须把这些共同的东西提取出来,放在一个父类中,然后让具体产品继承父类,以实现代码的复用。

        关于简单工厂模式的优缺点,这里我参考别的大神的总结,关于比较专业的理论方面我还有待学习:

  • 优点:工厂类是简单工厂模式的核心类。它包含必要的判断逻辑,能够根据外界给定的信息,决定究竟应该创建哪个具体类的对象。用户在使用时可以直接根据工厂类去创建所需的实例,而无需了解这些对象是如何创建以及如何组织的。有利于整个软件体系结构的优化。
  • 缺点:由于工厂类集中了所有实例的创建逻辑,这就直接导致一旦这个工厂出了问题,所有的客户端都会受到牵连;而且由于简单工厂模式的产品是基于一个共同的父类或者接口,这样一来,但产品的种类增加的时候,即有不同的产品接口或者抽象类的时候,工厂类就需要判断何时创建何种种类的产品,这就和创建何种种类产品的产品相互混淆在了一起,违背了单一职责,导致系统丧失灵活性和可维护性。而且更重要的是,简单工厂模式违背了“开放封闭原则”,就是违背了“系统对扩展开放,对修改关闭”的原则,因为当我新增加一个产品的时候必须修改工厂类,相应的工厂类就需要重新编译一遍。

        总结一下:简单工厂模式分离产品的创建者和消费者,有利于软件系统结构的优化;但是由于一切逻辑都集中在一个工厂类中,导致了没有很高的内聚性,同时也违背了“开放封闭原则”。另外,简单工厂模式的方法一般都是静态的,而静态工厂方法是无法让子类继承的,因此,简单工厂模式无法形成基于基类的继承树结构。


个人声明:这些都是自己学习的时候,根据自己的理解所写,其中部分理论是借鉴大神的。由于理解和专业素养有限,望指教,在此多谢!


主要参考资料:
1,《简单工厂模式》(博文)
2,《GoF23种设计模式 》
3,《Java开发中的23种设计模式详解(转)》
4,《大话设计模式》(书籍)
5,《Head First 设计模式》(书籍)
6,《设计模式-可复用面向对象软件的基础》(书籍)

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值