• 对比总结三个工厂模式(简单工厂,工厂方法,抽象工厂)


    前言

    简单工厂模式,工厂方法模式,抽象工厂模式,这三个模式,当然还有单例模式,建造者模式等等,应该是日常工作中常用的,尤其是工厂模式,应该是最最常见的模式,对理解面向对象有重要的实际意义。

    简单工厂模式

    最简单,最直接,能满足大部分日常需求,不足是工厂类太简单——无法满足开闭原则,对多个产品的扩展不利

    工厂方法模式——交给子类去创建

    工厂方法模式,有了进步,把工厂类进行改进,提升为一个抽象类(接口),把对具体产品的实现交给对应的具体的子类去做,解耦多个产品之间的业务逻辑。

    前面都是针对一个产品族的设计,如果有多个产品族的话,就可以使用抽象工厂模式

    抽象工厂模式

    抽象工厂模式的工厂,不再维护一个产品等级的某个产品(或说一个产品结构的某个产品更好理解),而是维护产品结构里的所有产品(横向x轴),具体到代码就是多个抽象方法去对应产品等级结构的各个产品实例

    具体的工厂类实现抽象工厂接口,去对应各个产品族,每一个具体工厂对一个产品族,获得该产品族的产品结构(所有产品)

    抽象工厂模式中的方法对应产品等级结构(每个类型中的具体产品),具体子工厂对应不同的产品族(产品类型)

    面试题——计算器

     想到一个面试题: 写一个简单的计算器,满足加减乘除运算。逻辑比较简单:接受计算数据的输入,进行计算,返回结果。用Java实现。

    面向过程版

    面向过程的设计本身没有错,但是如果面试的是 java 的相关职位,使用一门面向对象的语言这样写是非常危险的。因为这样写,谁都会,但凡学过编程的,没有不会的。更重要的问题是,这样写的目的仅仅是为了完成任务,没有任何面向对象的思维体现。实际业务中,类似的程序一旦扩展,这样的代码是没有办法维护的。

    缺点:完全面向过程设计,所有逻辑都集中在一个类(方法、函数里),缺少代码的重用……

    这里的除 0 异常检测也是考点之一。

    public class Main {
        public static void main(String[] args) {
            // 1、先接受数据的输入
            // 2、进行计算
            // 3、返回计算结果
            System.out.println("******计算器********
     请输入第一个数:");
            Scanner scanner = new Scanner(System.in);
            String num1 = scanner.nextLine();
    
            System.out.println("请输入运算符:");
            String operation = scanner.nextLine();
    
            System.out.println("请输入第二个数:");
            String num2 = scanner.nextLine();
    
            System.out.println("开始计算。。。。。。");
            double result = 0;
    
            if ("+".equals(operation)) {
                result = Double.parseDouble(num1) + Double.parseDouble(num2);
            } else if ("-".equals(operation)) {
                result = Double.parseDouble(num1) - Double.parseDouble(num2);
            } else if ("*".equals(operation)) {
                result = Double.parseDouble(num1) * Double.parseDouble(num2);
            } else if ("/".equals(operation)) {
                if (Double.parseDouble(num2) != 0) {
                    result = Double.parseDouble(num1) / Double.parseDouble(num2);
                } else {
                    System.out.println("除数不能为0!");
    
                    return;
                }
            }
    
            System.out.println(num1 + operation + num2 + " = " + result);
        }
    }

    简单面向对象版

    面向对象的设计就是把各个操作抽象为一个个的类,加法类,减法类,乘法类,除法类……每个运算类的职责就是进行属于自己的运算符的计算,客户端去调用对应的运算类即可。

    代码如下:

    public abstract class Operation {
        private double num1;
        private double num2;
    
        public double getNum1() {
            return num1;
        }
    
        public void setNum1(double num1) {
            this.num1 = num1;
        }
    
        public double getNum2() {
            return num2;
        }
    
        public void setNum2(double num2) {
            this.num2 = num2;
        }
    
        public abstract double getResult();
    }

    具体的运算符子类,只用加法举例,其他省略。

    public class Add extends Operation {
        @Override
        public double getResult() {
            return this.getNum1() + this.getNum2();
        }
    }

    客户端代码

            // 1、计算数据的输入
            // 2、进行计算
            // 3、返回计算结果
            System.out.println("******计算器********
     请输入第一个数:");
            Scanner scanner = new Scanner(System.in);
            String num1 = scanner.nextLine();
    
            System.out.println("请输入运算符:");
            String operation = scanner.nextLine();
    
            System.out.println("请输入第二个数:");
            String num2 = scanner.nextLine();
    
            System.out.println("开始计算。。。。。。");
            double result = 0;
            // 类型转换
            double a = Double.parseDouble(num1);
            double b = Double.parseDouble(num2);
    
            if ("+".equals(operation)) {
                Operation o = new Add();
                o.setNum1(a);
                o.setNum2(b);
                result = o.getResult();
            }

    写到这里,貌似比之前也没什么大的改变,只是使用了面向对象的一丢丢,使用了类……在客户端还是需要显式的去 new 对应的运算类对象进行计算,客户端里还是维护了大量的业务逻辑……

    继续改进,使用工厂模式——简单工厂模式

    简单工厂模式版

    一般学过的人,会立即想到该模式

    public abstract class Operation {
        private double num1;
        private double num2;
    
        public double getNum1() {
            return num1;
        }
    
        public void setNum1(double num1) {
            this.num1 = num1;
        }
    
        public double getNum2() {
            return num2;
        }
    
        public void setNum2(double num2) {
            this.num2 = num2;
        }
    
        public abstract double getResult();
    }
    
    public class Add extends Operation {
        @Override
        public double getResult() {
            return this.getNum1() + this.getNum2();
        }
    }
    
    public class Sub extends Operation {
        @Override
        public double getResult() {
            return this.getNum1() - this.getNum2();
        }
    }
     
    ///////////////  简单工厂类(也可以使用反射机制)
    public class OpreationFactory {
        public static Operation getOperation(String operation) {
            if ("+".equals(operation)) {
                return new Add();
            } else if ("-".equals(operation)) {
                return new Sub();
            }
    
            return null;
        }
    }
     
    ////////////// 调用者(客户端)
    public class Main {
        public static void main(String[] args) {
            System.out.println("******计算器********
    请输入第一个数:");
            Scanner scanner = new Scanner(System.in);
            String num1 = scanner.nextLine();
    
            System.out.println("请输入运算符:");
            String operation = scanner.nextLine();
    
            System.out.println("请输入第二个数:");
            String num2 = scanner.nextLine();
    
            System.out.println("开始计算。。。。。。");
            double result = 0;
            // 类型转换
            double a = Double.parseDouble(num1);
            double b = Double.parseDouble(num2);
    
            Operation oper = OpreationFactory.getOperation(operation);
            // TODO 有空指针异常隐患
            oper.setNum1(a);
            oper.setNum2(b);
            result = oper.getResult();
            System.out.println(num1 + operation + num2 + " = " + result);
        }
    }

    这样写,客户端(调用者)无需反复修改程序,也不需要关注底层实现,调用者只需要简单了解或者指定一个工厂的接口,然后去调用即可一劳永逸,而底层的修改不会影响调用者的代码结构——实现了解耦。且每个操作符类都各司其职,单一职责,看着还可以

    但是这时候面试官说了,给我增加开平方运算,回想之前的简单工厂设计模式,每次增加新的产品都需要去修改原来的工厂代码——这样不符合OCP,那么自然想到了工厂方法模式

    工厂方法模式版

    只举个加法的例子得了

    // 将工厂,又抽象了一层
    public interface OpreationFactory {
        Operation getOperation();
    }
    
    ////// 具体工厂类,生产不同的产品,比如加减乘除,开平方等
    public class AddFactory implements OpreationFactory {
        @Override
        public Operation getOperation() {
            return new Add();
        }
    }
     
    ///// 抽象的产品实体类
    public abstract class Operation {
        private double num1;
        private double num2;
    
        public double getNum1() {
            return num1;
        }
    
        public void setNum1(double num1) {
            this.num1 = num1;
        }
    
        public double getNum2() {
            return num2;
        }
    
        public void setNum2(double num2) {
            this.num2 = num2;
        }
    
        public abstract double getResult();
    }
    
    public class Add extends Operation {
        @Override
        public double getResult() {
            return this.getNum1() + this.getNum2();
        }
    }
     
    /////////// 客户端
    public class Main {
        public static void main(String[] args) {
            System.out.println("******计算器********
     请输入第一个数:");
            Scanner scanner = new Scanner(System.in);
            String num1 = scanner.nextLine();
    
            System.out.println("请输入运算符:");
            String operation = scanner.nextLine();
    
            System.out.println("请输入第二个数:");
            String num2 = scanner.nextLine();
    
            System.out.println("开始计算。。。。。。");
            double result = 0;
            // 类型转换
            double a = Double.parseDouble(num1);
            double b = Double.parseDouble(num2);
    
            // TODO 这里又需要判断了
            if ("+".equals(operation)) {
                // 得到加法工厂
                OpreationFactory opreationFactory = new AddFactory();
                // 计算 +
                Operation oper = opreationFactory.getOperation();
                oper.setNum1(a);
                oper.setNum2(b);
                result = oper.getResult();
            }
    
            // ......
            System.out.println(num1 + operation + num2 + " = " + result);
        }
    }

    工厂方法模式虽然避免了每次扩展运算符的时候,都修改工厂类,但是把判断的业务逻辑放到了客户端里,各有缺点吧……不要为了面向对象而面向对象。

    改进的工厂方法模式版

    不过,还是可以改进的……使用反射动态加载类 + 配置文件/注解 等等,且对重复代码进行提炼和封装……其实框架就这么一步步来的。

    代码如下(+和-):

    public interface OpreationFactory {
        Operation getOperation();
    }
    
    public class AddFactory implements OpreationFactory {
        @Override
        public Operation getOperation() {
            return new Add();
        }
    }
    
    public class SubFactory implements OpreationFactory {
        @Override
        public Operation getOperation() {
            return new Sub();
        }
    }
    
    public abstract class Operation {
        private double num1;
        private double num2;
    
        public double getNum1() {
            return num1;
        }
    
        public void setNum1(double num1) {
            this.num1 = num1;
        }
    
        public double getNum2() {
            return num2;
        }
    
        public void setNum2(double num2) {
            this.num2 = num2;
        }
    
        public abstract double getResult();
    }
    
    public class Add extends Operation {
        @Override
        public double getResult() {
            return this.getNum1() + this.getNum2();
        }
    }
    
    public class Sub extends Operation {
        @Override
        public double getResult() {
            return this.getNum1() - this.getNum2();
        }
    }
    
    ////////////// 封装了一些操作
    public enum  Util {
        MAP {
            // TODO 写在配置文件里
            @Override
            public Map<String, String> getMap() {
                Map<String, String> hashMap = new HashMap<>();
                hashMap.put("+", "compute.object.AddFactory");
                hashMap.put("-", "compute.object.SubFactory");
    
                return hashMap;
            }
    
            public double compute(double a, double b, OpreationFactory opreationFactory) {
                Operation oper = opreationFactory.getOperation();
                oper.setNum1(a);
                oper.setNum2(b);
    
                return oper.getResult();
            }
        };
    
        public abstract Map<String, String> getMap();
        public abstract double compute(double a, double b, OpreationFactory opreationFactory);
    }
    
    //////////// 客户端
    public class Main {
        private static double result = 0;
        private static double a;
        private static double b;
    
        public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
            System.out.println("******计算器********
     请输入第一个数:");
            Scanner scanner = new Scanner(System.in);
            String num1 = scanner.nextLine();
            System.out.println("请输入运算符:");
            String operation = scanner.nextLine();
            System.out.println("请输入第二个数:");
            String num2 = scanner.nextLine();
            System.out.println("开始计算。。。。。。");
            a = Double.parseDouble(num1);
            b = Double.parseDouble(num2);
            Class clazz = Class.forName(MAP.getMap().get(operation));
            result = MAP.compute(a, b, (OpreationFactory) clazz.newInstance());
            System.out.println(num1 + operation + num2 + " = " + result);
        }
    }

    到这里也差不多了,虽然还有很多问题……关键是思想的掌握 

    欢迎关注

    dashuai的博客是终身学习践行者,大厂程序员,且专注于工作经验、学习笔记的分享和日常吐槽,包括但不限于互联网行业,附带分享一些PDF电子书,资料,帮忙内推,欢迎拍砖!

  • 相关阅读:
    【ASP.NET开发】ASP.NET(MVC)三层架构知识的学习总结
    网络工作室暑假后第二次培训资料(SQLServer存储过程和ADO.NET访问存储过程)整理(一)
    【ASP.NET开发】ASP.NET对SQLServer的通用数据库访问类 分类: ASP.NET 20120920 11:17 2768人阅读 评论(0) 收藏
    网络工作室暑假后第一次培训资料(ADO.NET创建访问数据集)整理 分类: ASP.NET 20121005 20:10 911人阅读 评论(0) 收藏
    网络工作室暑假后第二次培训资料(SQLServer存储过程和ADO.NET访问存储过程)整理(二)
    RabbitMQ(二)队列与消息的持久化
    实用的与坐标位置相关的js
    prototype、JQuery中跳出each循环的方法
    PHP获取当前日期所在星期(月份)的开始日期与结束日期
    Document Viewer解决中文乱码
  • 原文地址:https://www.cnblogs.com/kubixuesheng/p/10353209.html
Copyright © 2020-2023  润新知