zoukankan      html  css  js  c++  java
  • 设计模式 | 简单工厂模式(static factory method)

    按理说应该把书全都看完一遍,再开始写博客比较科学,会有比较全面的认识。
    但是既然都决定要按规律更新博客了,只能看完一个设计模式写一篇了。
    也算是逼自己思考了,不是看完就过,至少得把代码自己都敲一遍。
    刚开始可能写的比较浅显,更像是读书笔记,只能未来回来完善了。
    废话啰嗦到这,开始正题。

    文章是以一个面试中,面试官要求写一个计算器开头的。
    巧的是,在之前的一次面试中,刚好面试官也问了这个问题,当时我并不能给出令人满意的答案,只恨没有早点学习设计模式啊。
    代码不光是要完成功能就完事了,还要考虑健壮性、复用性、可扩展性等等。
    学习设计模式的意义也在于此,帮助我们写出更加优雅的代码。
     
    那么回到之前的问题,如果现在要你写一个计算器的程序,你会怎么写呢?
    我的第一反应和书中的菜鸟一样,特别还是在面试中,时间紧迫,当然是把功能完成了就OK,为什么要考虑那么复杂,写一个方法直接搞定:
    (这里为了代码的简洁性,暂时不考虑用户不按规范输入,除零、浮点失精这种健壮性问题。)
     1 package designpattern.staticfactorymethod;
     2 import java.util.Scanner;
     3 public class Calculator {
     4      public static void main(String[] args) {
     5            Scanner scanner = new Scanner(System.in);
     6            System.out.println("请输入一个数字");
     7            double num1 = scanner.nextDouble();
     8            System.out.println("请输入一个运算符:+、-、*、/");
     9            String operator = scanner.next();// 不能用nextLine(),会把上一个回车给吸收
    10            System.out.println("请输入一个数字");
    11            double num2 = scanner.nextDouble();
    12            switch (operator) {
    13            case "+":
    14                 System.out.println(num1 + num2);
    15                 break;
    16            case "-":
    17                 System.out.println(num1 - num2);
    18                 break;
    19            case "*":
    20                 System.out.println(num1 * num2);
    21                 break;
    22            case "/":
    23                 System.out.println(num1 / num2);
    24                 break;
    25            default:
    26                 break;
    27            }
    28            scanner.close();
    29      }
    30 }

    最多把计算的方法单拉出来:

    package designpattern.staticfactorymethod;
    import java.util.Scanner;
    public class Calculator2 {
         public static void main(String[] args) {
               Scanner scanner = new Scanner(System.in);
               System.out.println("请输入一个数字");
               double num1 = scanner.nextDouble();
               System.out.println("请输入一个运算符:+、-、*、/");
               String operator = scanner.next();// 不能用nextLine(),会把上一个回车给吸收
               System.out.println("请输入一个数字");
               double num2 = scanner.nextDouble();
               switch (operator) {
               case "+":
                    System.out.println(plus(num1, num2));
                    break;
               case "-":
                    System.out.println(minus(num1, num2));
                    break;
               case "*":
                    System.out.println(multiply(num1, num2));
                    break;
               case "/":
                    System.out.println(divide(num1, num2));
                    break;
               default:
                    break;
               }
               scanner.close();
         }
         static double plus(double num1, double num2) {
               return num1 + num2;
         }
         static double minus(double num1, double num2) {
               return num1 - num2;
         }
         static double multiply(double num1, double num2) {
               return num1 * num2;
         }
         static double divide(double num1, double num2) {
               return num1 / num2;
         }
    }
    这样虽然运算方法可以复用,但是显示和运算放在一起,你说它是个工具类吧,也不是,显得有些不伦不类。
    要我来改,我最多把运算方法单拉成一个工具类,就结束了:
    package designpattern.staticfactorymethod;
    public class CalculatorUtil {
         static double plus(double num1, double num2) {
               return num1 + num2;
         }
         static double minus(double num1, double num2) {
               return num1 - num2;
         }
         static double multiply(double num1, double num2) {
               return num1 * num2;
         }
         static double divide(double num1, double num2) {
               return num1 / num2;
         }
    }
    我发现自己用java写了这么长时间的代码,好歹是个面向对象的语言,但是封装用的很熟练,但是继承和多态几乎就没有用过。
    虽然《Thinking In Java》的作者说不能滥用继承:
    但是完全不用也是有问题的。
    书中写的例子对我蛮有启发的。
    假如我需要增加一个运算开根(sqrt)运算,怎么办?
    只能去改那个工具类,但是这会带来问题。
    首先,你增加一个方法,要让之前已经写好的加减乘除方法一起参与编译,增加了你不小心影响到之前方法的风险。
    其次,可能之前的方法含有敏感信息,你并没有权限看(这个问题我之前还真的从来没有思考过)
    那应该怎么办?
    自然是把所有的运算方法单独放在一个类里,这样就互相之间不影响,想新增直接新增类就可以了,不用看之前的代码。
    第一反应是写成这样,只是单纯的写成单独的类:(为了方便显示,写成了内部类;同样为了简洁setter、getter我就不写了)
    package designpattern.staticfactorymethod;
    import java.util.Scanner;
    public class Calculator3 {
         public static void main(String[] args) {
               Scanner scanner = new Scanner(System.in);
               System.out.println("请输入一个数字");
               double num1 = scanner.nextDouble();
               System.out.println("请输入一个运算符:+、-、*、/");
               String operator = scanner.next();// 不能用nextLine(),会把上一个回车给吸收
               System.out.println("请输入一个数字");
               double num2 = scanner.nextDouble();
               Calculator3 calculator3 = new Calculator3();
               switch (operator) {
               case "+":
                    System.out.println(calculator3.new Plus(num1, num2).calculate());
                    break;
               case "-":
                    System.out.println(calculator3.new Minus(num1, num2).calculate());
                    break;
               case "*":
                    System.out.println(calculator3.new Multiply(num1, num2).calculate());
                    break;
               case "/":
                    System.out.println(calculator3.new Divide(num1, num2).calculate());
                    break;
               default:
                    break;
               }
               scanner.close();
         }
         class Plus {
               double num1;
               double num2;
               Plus(double num1, double num2) {
                    this.num1 = num1;
                    this.num2 = num2;
               }
               double calculate() {
                    return num1 + num2;
               }
         }
         class Minus {
               double num1;
               double num2;
               Minus(double num1, double num2) {
                    this.num1 = num1;
                    this.num2 = num2;
               }
               double calculate() {
                    return num1 - num2;
               }
         }
         class Multiply {
               double num1;
               double num2;
               Multiply(double num1, double num2) {
                    this.num1 = num1;
                    this.num2 = num2;
               }
               double calculate() {
                    return num1 * num2;
               }
         }
         class Divide {
               double num1;
               double num2;
               Divide(double num1, double num2) {
                    this.num1 = num1;
                    this.num2 = num2;
               }
               double calculate() {
                    return num1 / num2;
               }
         }
    }
    你会发现这几个类结构都是一模一样的,我几乎也是复制粘贴出来的,咱们是不是可以提取出一个共同的父类来?
    package designpattern.staticfactorymethod;
    public abstract class Calculate {
         double num1;
         double num2;
         Calculate() {
         }
         Calculate(double num1, double num2) {
               this.num1 = num1;
               this.num2 = num2;
         }
         public abstract double calculate();
    }

    具体运算子类们:

    package designpattern.staticfactorymethod;
    public class Plus extends Calculate {
         Plus() {
         }
         Plus(double num1, double num2) {
               super(num1, num2);
         }
         @Override
         public double calculate() {
               return num1 + num2;
         }
    }
    package designpattern.staticfactorymethod;
    public class Minus extends Calculate {
         Minus() {
         }
         Minus(double num1, double num2) {
               super(num1, num2);
         }
         @Override
         public double calculate() {
               return num1 - num2;
         }
    }
    package designpattern.staticfactorymethod;
    public class Multiply extends Calculate {
         Multiply() {
         }
         Multiply(double num1, double num2) {
               super(num1, num2);
         }
         @Override
         public double calculate() {
               return num1 * num2;
         }
    }
    package designpattern.staticfactorymethod;
    public class Divide extends Calculate {
         Divide() {
         }
         Divide(double num1, double num2) {
               super(num1, num2);
         }
         @Override
         public double calculate() {
               return num1 / num2;
         }
    }
    继承终于派上用场了,你会发现这样还是没办法使用父类,想要具体的运算还是得直接去实例化子类的对象,和提取父类之前没有什么区别。
     
    接下来,终于,今天的主题要出场了,简单工厂设计模式。
    先上一个百度百科解释:
    简单工厂模式是属于创建型模式,又叫做静态工厂方法(Static Factory Method)模式,但不属于23种GOF设计模式之一。简单工厂模式是由一个工厂对象决定创建出哪一种产品类的实例。简单工厂模式是工厂模式家族中最简单实用的模式,可以理解为是不同工厂模式的一个特殊实现。
     
    简单来说,它就是用来创建对象的,假设有那么一家神秘的工厂,什么都能生产,你只要对它说你想要什么,它就会给你生产出来,你并不需要关心,工厂内部具体是怎么生产的。像这样:

    当然实际代码中,不能像上面这样天马行空,术业还是要有专攻的,比如一个工厂专门生产各种各样的女朋友,额。。

    比如iphon的工厂专门生产各种型号的ipone。

    把这个思想用到我们的程序中,我们需要一个工厂,接受一个参数,然后它就能返回相应的对象:

    package designpattern.staticfactorymethod;
    public class CalculateFactory {
         public static Calculate create(String operate) {
               switch (operate) {
               case "+":
                    return new Plus();
               case "-":
                    return new Minus();
               case "*":
                    return new Multiply();
               case "/":
                    return new Divide();
               }
               return null;
         }
    }
    前面的类都新增了一个默认的构造方法,就是为了这里用,我也思考过,可不可以用统一的带参数构造方法,但我看好像没有人这么写,肯定有它的道理,这里写下自己粗浅的理解,欢迎交流、指正:
    工厂的任务只是生产对象,加上参数就相当于和业务搅在一起,变得很不灵活,今天你只想初始化汽车的品牌,明天你又想初始化汽车的颜色,这样,你汽车类、工厂类、调用工厂的类全要改,如果只是默认的构造方法的话,至少你的工厂类是不用改的。
     
    接下来看调用的方法:
    package designpattern.staticfactorymethod;
    import java.util.Scanner;
    public class Calculator4 {
         public static void main(String[] args) {
               Scanner scanner = new Scanner(System.in);
               System.out.println("请输入一个数字");
               double num1 = scanner.nextDouble();
               System.out.println("请输入一个运算符:+、-、*、/");
               String operator = scanner.next();// 不能用nextLine(),会把上一个回车给吸收
               System.out.println("请输入一个数字");
               double num2 = scanner.nextDouble();
               Calculate calculate = CalculateFactory.create(operator);
               calculate.num1 = num1;
               calculate.num2 = num2;
               System.out.println(calculate.calculate());
               scanner.close();
         }
    }

     这回多态也出场了,通过多态,统一用父类接受创建的具体子类,当需要增加运算方式时,只需要修改工厂类和具体的运算类,调用的地方不用变,降低了模块间的耦合性,提高了系统的灵活性。

  • 相关阅读:
    BZOJ 4059 [Cerc2012]Non-boring sequences(启发式分治)
    牛客练习赛1 A
    2018年全国多校算法寒假训练营练习比赛(第一场)J
    2018年全国多校算法寒假训练营练习比赛(第一场)C
    2018年全国多校算法寒假训练营练习比赛(第一场)D
    2018年全国多校算法寒假训练营练习比赛(第一场)E
    Wannafly挑战赛7 E
    Wannafly挑战赛7 C
    Wannafly挑战赛7 B
    Wannafly挑战赛7 D
  • 原文地址:https://www.cnblogs.com/imoqian/p/10244577.html
Copyright © 2011-2022 走看看