23种设计模式(3)-工厂方法模式

时间:2022-10-02 15:02:18

一,设计原则解释

        本节要学的工厂方法模式涉及两个设计原则:开-闭原则和依赖倒转原则。我感觉还是先把设计原则简单理解一下比较好,这样才能对设计模式的学习理解更深刻。

1,开-闭原则

开-闭原则:一个软件实体应当对扩展开放,对修改关闭。

        应该如何理解这句话呢?
        这句话意思是,当设计一个模块的时候,应该考虑到在以后我们能够不修改模块已有源码的情况下扩展该模块的行为。满足开闭原则很重要,因为我们项目的需求可能随时都有可能变化,我们不可能项目需求修改一次,就对现有代码大改一次。对于一个很复杂很大型的系统,这样的做法是完全不可取的。所以,一定要考虑目前的模块设计是否具有高的扩展性,但是又不能以破坏现有代码的情况去实现扩展。而开闭原则则是为解决这个问题的。那么如何做到开闭原则呢?

  1. 用抽象类或接口设计抽象层,并在抽象层设计出所有可能的扩展。这样的话抽象层和已有的具体子类则不需要修改(修改关闭)。
  2. 具体的行为在具体的子类中去实现。如果需要新的行为,则另建扩展于抽象层的新类即可(扩展打开)。

2,依赖倒转原则

依赖倒转原则:
a,高层模块不应该依赖低层模块,两者都应该依赖其抽象;
b,抽象不应该依赖细节;
c,细节应该依赖抽象。

        我还是简单理解一下。传统的结构化编程,都是高层模块直接调用具体的低层模块,也就是高层模块直接依赖于具体的低层模块。这样导致的结果就是低层模块做任何修改,都会直接对高层模块产生影响。原因就是高层模块太依赖于低层模块了,这两层之间的耦合度太高啦。为了降低这种耦合度,把具体的低层模块向上抽取出抽象层,这种抽象层高度归结了低层模块的宏观业务逻辑。这样的话低层模块依赖于抽象层。然后把传统高层模块直接依赖低层模块的行为,转向于依赖低层模块上面的抽象层。由于抽象层是低层模块具体业务逻辑向上高度归纳的抽象业务逻辑,所以当修改具体的低层模块并不会对高层模块产生太大影响。如果想改变低层模块的行为,并不需要直接修改底层模块的源码,而是创建新的低层模块,去扩展于那个抽象层。这就形成依赖倒转的现象。换句话说,这个依赖倒转原则的关键就是,不要依赖具体的实现类,要依赖于接口或抽象类。要依赖于接口或抽象类,那么的话就需要使用接口或者抽象类进行变量类型的声明、参数类型的声明、返回值类型的声明和方法类型的转换。再进一步来看,这样的话每个具体的类都必须实现某个接口或者继承某个抽象类;而这个要求则是开闭原则的基础,开闭原则是正是通过实现接口或继承于抽象类来实现扩展与关闭的。更具体神深入的学习,可以参考《OO设计原则 – Dependency Inversion Principle:OO设计的 DIP依赖倒置原则》《依赖倒置原则》《架构设计之依赖倒置、控制反转与依赖注入》和《java与模式》(书)。

        综上来看,两个设计原则之间是有关系的。开闭原则是依赖倒转原则的基础。之所以能够实现依赖的倒转,是因为开闭原则要求具体类要实现接口或者继承抽象类。


二,修改简单工厂模式为工厂方法模式

        根据上面设计原则的学习,我在此纠正一个我前面一篇关于简单工厂模式的一个小错误,我前面几个具体的运算类继承于一个具体类,这个不符合开闭原则和依赖倒转原则。在本节中依旧使用这个计算器案例,但是在此把那个具体运算器父类修改为抽象类。

父类运算器类Computer3.java

package com.factory.simplef;

import java.util.Scanner;

/**
*
*
* @author DC
*
*/

public abstract class Computer3 {

/**
* 数据1
*/

private int number1;

/**
* 数据2
*/

private int number2;

/**
* 获得计算结果
* @return
*/

abstract int getComputedResult();

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;
}
}

扩展加减程序功能的运算类

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();
}
}

工厂类Computer3Factory.java

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.java

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());
}
}

        在上一篇讲解简单工厂模式的博文中已经提到过,简单工厂模式是存在一些问题的。简单工厂类中的静态方法封装了对象创建的逻辑,降低了耦合;并且包含必要的逻辑判断,根据客户端的输入来选择动态创建哪个类实例。简单工厂模式也去除了客户端类与具体产品的依赖。但是当我们想为计算器扩展一个新功能,比如求number1的number2次方。那么的话我们就需要在简单工厂类的核心方法中添加新的判断逻辑。这点严重违背了开闭原则。针对这个问题的解决,工厂方法模式则出现啦

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

        先来画一个工厂方法模式的类图,如下:
        23种设计模式(3)-工厂方法模式

        观察上图,可知工厂方法模式把简单工厂模式中的工厂类修改成一个工厂接口和一系列具体的产品工厂类。工厂接口只是给出每一个具体子类应该实现的接口方法,而把每种具体产品的实例化交给每种具体产品对应的具体产品工厂类。这样的修改保证了不用修改现有的工厂接口和具体工厂子类源码的基础上,扩展新的产品工厂类增加创建新产品的行为。这不仅保留了简单工厂模式的优点(简单工厂类中的静态方法封装了对象创建的逻辑,降低了耦合;并且包含必要的逻辑判断,根据客户端的输入来选择动态创建哪个类实例。简单工厂模式也去除了客户端类与具体产品的依赖),还改正了简单工厂模式的缺点(如果新添加一个类实例,则需要修改源码增添判断逻辑)。现在在工厂方法模式中,如果想扩展一种新产品类,则只需要扩展该产品对应的具体工厂子类即可,这个过程并不需要修改已有的源码。
        先来看下本工厂方法模式的代码书写情况。

工厂接口IComputer3Factory.java

package com.factory.simplef;
/**
* 工厂接口
* @author DC
*/

public interface IComputer3Factory {

Computer3 createComputer3();
}

几个具体的加减程序计算器工厂类

package com.factory.simplef;
/**
* 带有+功能的计算器工厂类
* @author DC
*
*/

public class Computer3WithAddFactory implements IComputer3Factory{

@Override
public Computer3 createComputer3() {
return new Computer3WithAdd();
}
}

package com.factory.simplef;
/**
* 带有-功能的计算器工厂类
* @author DC
*
*/

public class Computer3WithSubFactory implements IComputer3Factory{

@Override
public Computer3 createComputer3() {
return new Computer3WithSub();
}
}

package com.factory.simplef;
/**
* 带有-功能的计算器工厂类
* @author DC
*
*/

public class Computer3WithMulFactory implements IComputer3Factory{

@Override
public Computer3 createComputer3() {
return new Computer3WithMul();
}
}

package com.factory.simplef;
/**
* 带有+功能的计算器工厂类
* @author DC
*
*/

public class Computer3WithDivFactory implements IComputer3Factory{

@Override
public Computer3 createComputer3() {
return new Computer3WithDiv();
}
}

客户端类Client4.java

package com.factory.simplef;

import java.util.Scanner;

/**
* 版本4,测试类
* @author DC
*
*/

public class Client4 {

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();//在此不考虑不输入的情况

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

        运行是ok的。
        仔细观察上面的工厂方法模式的实现与测试。你会发现现在我们不用在工厂中增加判断逻辑来添加新的实例创建逻辑,只需要扩展工厂接口创建一个新的产品工厂类,并且在该工厂类方法中创建该具体产品即可。这实现了开闭原则。你会发现在客户端类中,工厂类和产品类变量声明也符合依赖倒转原则的。但是仔细观察,工厂方法还是存在一个问题,就是虽然工厂接口和工厂类中不需要判断逻辑不需要修改源码了。但是客户端类必须知道选择具体的哪一个产品工厂类。如果添加新功能,别的地方都不需要修改已有源码,但是客户端源码需要稍作修改。这个问题用反射就可以解决了,在此先不说,后面会加入。


三,工厂方法模式

        23种设计模式(3)-工厂方法模式
        工厂方法模式涉及的几个角色:
        抽象工厂角色:是工厂方法模式的核心,这个角色可以是接口或者抽象类。所有的具体工厂角色要实现这个工厂接口或者继承这个工厂抽象类。
        具体工厂角色:与具体应用程序有关,受具体应用程序调用来创建具体产品实例。这个具体工厂角色要扩展于抽象工厂角色。
        抽象产品角色:工厂方法模式中工厂方法的返回类型。所有具体的产品角色要扩展于这个抽象产品角色。通常为接口或者抽象类。
        具体产品角色:工厂方法模式中,每一种具体工厂类的工厂方法所创建的具体类实例。这个具体产品角色要扩展于抽象产品角色。
        具体的理论,优点,缺点等等,先不写(TODO)。


个人声明:本博文是学习23设计模式时,根据自己的理解所写。由于个人理解和专业素养有限,如若发现错误,请纠正,在此多谢!!!


1,《OO设计原则 – Dependency Inversion Principle:OO设计的 DIP依赖倒置原则》
2,《依赖倒置原则》
3,《架构设计之依赖倒置、控制反转与依赖注入》
4,《大话设计模式》(书,适合入门)
5,《Head First设计模式》(书,适合入门)
6,《设计模式-可复用面向对象软件的基础》(书,很好,适合进阶)
7,《java与模式》(书,很好,适合进阶)