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,《设计模式-可复用面向对象软件的基础》(书籍)

时间: 2024-08-09 23:52:59

23种设计模式(2)-简单工厂模式的相关文章

23种设计模式之简单工厂模式

模式是基于在面对对象的思想上实现的计算机编程模式思维面对对象的模式思维有四大好处:可维护,可复用,可拓展,灵活性好基于三个步骤,封装,继承,多态举个例子:计算机要完成两个数字之间的运算一.就要考虑到定义一个可被继承类1.需要两个数的字段并且将之属性化2. 一个virtual的虚方法在每一个运算方法中都可以实现调用 二.要将运算的方法类继承,完成运算方法 三.创建工厂类,将根据运算符号来判断使用哪种运算类 四.在客户端中使用多态调用工厂类,输出结果就OK了 具体代码如下: 原文地址:https:/

23种设计模式之简单工厂

简单工厂模式描述的是,通过类的继承关系,父类(工厂类)与子类(产品类),调用父类中的方法,实际干活儿的是子类中的方法:封装需求的不确定性,做出通用的编程,下面以常用的计算器为例: 最容易想到的写法是: 1 Console.WriteLine("请输入操作数1:"); 2 double a = double.Parse(Console.ReadLine()); 3 Console.WriteLine("请输入操作符:"); 4 string opt = Console

【Unity与23种设计模式】抽象工厂模式(Abstract Factory)

GoF中定义: "提供一个能够建立整个类群组或有关联的对象,而不必指明它们的具体类." 意思就是 根据不同的执行环境产生不同的抽象类子类 抽象工厂模式经常在面试中会涉及到 下面的例子为工厂1和工厂2 两个工厂都可以生成产品A和B 但是两个工厂工艺不同 所以工厂1只能生产产品A1和B1 工厂2只能生产产品A2和B2 //可生成各抽象成品对象的操作 public abstract class AbstractFactory{ public abstract AbstractProductA

23种设计模式之抽象工厂模式

抽象工厂模式(Abstract Factory):提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类. package designMode.abstractFactory; class User { private int id; private String name; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() {

重头开始学23种设计模式:三大工厂(简单工厂,工厂方法,抽象工厂)

在开发当中我们经常会使用三个设计模式,来帮我们解决项目代码的可扩展性. 在简单工厂,工厂方法,抽象工厂这三个设计模式当中,代码其实都很简单,主要是要理解运用. 简单工厂: 简单工厂说白了,就是利用Switch根据传递的参数,进行实例化. 工厂方法: 工厂方法,为解决每次都去增加Swicth的简单工厂的升级.为每一个产品提供一个工厂类. 抽象工厂: 抽象工厂,我觉得也是对工厂方法的再次升级,工厂方法每次只能创作一个产品,而抽象工厂就是产品线的产品族. 总结下,从网上找到一个大牛的回复: 我认为不能

[设计模式]两种方法实现简单工厂模式

<!--完整代码下载链接:点击下载完整代码示例--> 1.描述 在项目开发中经常会遇到根据不同的条件创建不同的对象,然后对该对象进行操作,一般都包括许多的switch -case分支如下: CBase* pBase(NULL); switch (type_variable) { case obj1: pBase = new CBaseDerivate1();break; case obj2: pBase = new CBaseDerivate2();break; ... case objN:

设计模式初探—简单工厂模式

为什么要学习设计模式? 可重用.可维护.可扩展.灵活性好 什么是简单工厂模式? 从设计模式的类型上来说,简单工厂模式是属于创建型模式,又叫做静态工厂方法(Static Factory Method)模式,但不属于23种GOF设计模式之一.简单工厂模式是由一个工厂对象决定创建出哪一种产品类的实例.简单工厂模式是工厂模式家族中最简单实用的模式,可以理解为是不同工厂模式的一个特殊实现. 简单工厂模式的好处? (1)将具体业务和实现进行分离 (2)将多个具体业务之间进行解耦 解决的问题? 单独的类来创造

Java经典23种设计模式之结构型模式(三)------附代理模式、适配器模式、外观模式区别

本文介绍7种结构型模式里的剩下两种:享元模式.代理模式. 一.享元模式FlyWeight 享元模式比较简单且重要,在很多场合都被用到,只不过封装起来了用户看不到.其概念:运用共享内存技术最大限度的支持大量细粒度的对象.这个概念给的有些抽象,说白了就是如果内存中存在某个对象A,如果再次需要使用对象A的时候如果内存中有A这个对象就直接使用它,不要再次new了.如果没有,则重新new一个.基于这个特点,享元模式使用时一般会给待访问对象传递一个Tag,用来标识这个对象,而且要同时使用抽象工厂的方法进行访

设计模式之简单工厂模式(Simply Factory)摘录

从设计模式的类型上来说,简单工厂模式是属于创建型模式,又叫静态工厂方法(Static Factory Method)模式,但不属于23种GOF设计模式之一.简单工厂模式是由一个工厂对象决定创建出哪一种产品类的实例.简单工厂模式是工厂模式家族中最简单实用的模式,可以理解为是不同工厂模式的一个特殊实现. 简单工厂模式的实质是由一个工厂类根据传入的参数,动态决定应该创建哪一个产品类(这些产品类继承自一个父类或接口)的实例. 该模式中包含的角色及其职责:(1).工厂(Creator)角色:简单工厂模式的