• Java:面向抽象编程实现OCP


    title: Java:面向抽象编程实现OCP
    copyright: true
    tags: 单例模式
    categories:

    涉及技术基础点:

    1. 反射
    2. 泛型
    3. 抽象
    4. 工厂模式
    5. interface

    一、代码优化原则

    1. 代码复杂性的根本

    代码编写的出发点:不啰嗦 自描述性的代码 可维护性 好代码

    总结,所有软件的复杂性,都是为了可维护性。
    
    1. 开闭原则 OCP

      为什么java写出来的代码是可维护的,一个非常重要的原则就是开闭原则,也就是OCP。可能会听过里式替换原则,还有迪米特法则,这些都不是最重要的,最重要的是开闭原则,这是代码可维护的基础。其它的原则都是开闭原则的子原则,其他的原则都是为了实现开闭原则。

    除了里式替换原则,迪米特法则,还有控制反转,依赖注入,其实也是为了实现开闭原则。

    OCP的英文:Open Closed Principle。

    不管是软件,还是函数,还是类,都要对扩展是开放的,对修改是封闭的。

    当要修改的时候,我们通过新增一业务模块或类,来代替原来的类

    注意: 不要面对一个具体的类去编程,要面向抽象。

    示例:

    public class A{
        private C c ;
        
        public void print(){
            C c = new C();
                c.text1();
            System.out.println("This is Class A");
        }
    
    1. 为什么要面向抽象编程?

    interface abstract

    使用抽象接口,可以避免因新增实现类而改动控制代码。

    因为如果面向实体类去新建对象,并调用方法,在后续维护(指出现新需求)的时候,改动一个类,会影响到其他依赖此类的类做出改动 new语句以及调用方法的语句,这将是灾难性的修改。

    1. 如何能够忽略掉一个具体类,而面向抽象编程?

    就是用的interface(接口)

    IOC DI 的过程:

    1. interface
    2. 设计模式:工厂模式
    3. IOC,DI

    目的:

    =》面向对象 =》OCP =》可维护的代码

    二、代码优化之路(从强耦合到稳定)

    创建 Diana、Irelia、Camille三个类,分别有多个相同的方法可供调用,例子:

    public class Irelia {
        public void g(){
            System.out.println("Irelia Q");
        }
    
        public void w(){
            System.out.println("Irelia W");
        }
    
        public void e(){
            System.out.println("Irelia E");
        }
    
        public void r() {
            System.out.println("Irelia R");
        }
    }
    
    1. 案例代码一:

    多个类创建多个对象来调用各自的方法,耦合度高,当有新的类加入时,需要更改main中的代码;

       public static void main(String[] args) {
            String name = Main.getPlayerInput();
            switch (name) {
                case "Diana":
                    Diana diana = new Diana();
                    diana.r();
                case "Irelia":
                    Irelia irelia = new Irelia();
                    irelia.r();
                case "Camille":
                    Camille camille = new Camille();
                    camille.r();
            }
        }
    
    当前的案例中,每个方法的调用需要分别使用所属类的实例去调用,代码的耦合度太高,变更太频繁;
    

    2.案例代码二:

    使用 interface 来统一调用方法,这里创建 ISkill 接口,Diana、Irelia、Camille实现该接口;

        public static void main(String[] args) throws Exception {
            String name = Main.getPlayerInput();
            ISkill iSkill;
            switch (name) {
                case "Diana":
                    iSkill = new Diana();
                    break;
                case "Irelia":
                    iSkill = new Irelia();
                    break;
                case "Camille":
                    iSkill = new Camille();
                    break;
                default:
                    throw new Exception();
            }
            iSkill.r();
        }
    
    从以上的代码中,可以看出,确实在原有代码上进行了优化。
    

    但是:

    • 单纯的interface可以统一方法的调用,但是他不能统一对象的实例化

    • 面向对象在做两件事情
      a)实例化对象
      b)调用方法(完成业务逻辑)

    • 只有一段代码中没有new的出现,才能保证代码的相对稳定,才能逐步实现OCP

    • 上面这句话的只是表象,实质是一段代码如果要保持稳定,就不应该负责对象的实例化

    • 对象实例化的过程不可能消除掉

    • 把对象实例化的过程,转移到其他的代码片段里

      方法统一的意义在哪?
      1、某方法被大量调用时很繁琐,方法统一后一行代码搞定;
      2、事项功能的单一性,便于后面的提取封装

    多个类实现同一个接口,通过反射与多态的方式进行向上转型,从而可以使用一个统一的出口进行方法的调用。代码的耦合度较低,只需要更改要实例化的对象,对于具体调用方法的实例本身是不变的。

    1. 案例代码三:

    在案例代码二的基础上,将主要进行 new 的switch-case 代码进行抽取,通过 Factory 的方式来生成具体的实例:

    public class HeroFactory {
    
        public static ISkill getHero(String name) throws Exception {
            ISkill iSkill;
            switch (name) {
                case "Diana":
                    iSkill = new Diana();
                    break;
                case "Irelia":
                    iSkill = new Irelia();
                    break;
                case "Camille":
                    iSkill = new Camille();
                    break;
                default:
                    throw new Exception();
            }
            return iSkill;
        }
    }
    

    Main.java

        public static void main(String[] args) throws Exception {
            String name = Main.getPlayerInput();
            ISkill iSkill = HeroFactory.getHero(name);
            iSkill.r();
        }
    

    工厂类的作用就是负责创建类的实例,而不影响主业务代码,主业务代码甚至不需要知道工厂类的内部实现,满足了功能也实现了代码分离

    当前方式的优化:

    1. 代码中总会存在不稳定,所以我们要做的就是隔离不稳定,保证其他的代码是稳定的。
    2. 把散落在各处的不稳定性全部抽取和集中起来管理,使得绝大多数代码保持稳定,这就是IOC的意义。

    弊端:

       当前的代码中存在的不稳定是变化,工厂模式类 HeroFactory 依旧存在 new 这一个过程。
    
    1. 案例代码四:

    基于反射 new 对象,消除变化

    public class HeroFactory {
    
        public static ISkill getHero(String name) throws Exception {
            ISkill iSkill;
    
            //反射
            //元素
            // C# 、Python
            // 类 是对象的抽象
            // 对象 类 元素
            // reflect.hero.Irelia
            String classStr = "reflect.hero." + name;
            Class<?> cla = Class.forName(name);
            Object obj = cla.newInstance();
            return (ISkill)obj;
        }
    }
    
    反射机制,一般情况,我们可以通过用户输入的名字去new一个对象,但是,如果用反射机制,可以根据用户输入的名字,去创建一个对象,注意:使用反射机制接收到的参数是要求是完整的包路径。       
    

    知识点:

    java9以上废弃了newInstance,由clazz.getDeclaredConstructor().newInstance()代替

    结束: 到这一步,代码基本上就消除了变化。

    现在的写法有一定的缺点:

    每一次调用都需要重新反射生成对象,性能不好;
    

    而spring是可以把生成的对象加到缓存里面,就不再重复生成;

    Spring的配置文件变更属不属于违反OCP原则:

    配置文件是属于系统外部的,而不是代码本身的,不违反OCP原则。
    

    面向对象中变化的应对方案?有两种

    1.制定一个interface,然后用多个类实现同一个interface ---策略模式

    2.一个类,属性 配置类中配置变化的属性值--- 修改数据库链接、端口号 。

    更改配置文件,在配置文件中更改,比如修改数据库链接、端口号 支付账号等。
    前者更灵活,后者属性值是固定的,扩展性没有第一种强
  • 相关阅读:
    FFmpeg入门,简单播放器
    Linux系统编译Win32版本adb
    检测目标程序ELF bit是32还是64
    Swift编程资料全集
    Swift编程资料总结
    cocos2d-html5学习之三-为sprite添加触摸事件
    Cocos2d-html5学习笔记二
    cocos2d-x学习笔记一
    NSViewAnimation进行视图和窗口动画
    Cocoa中NSAnimation动画简介
  • 原文地址:https://www.cnblogs.com/wushaopei/p/15948211.html
Copyright © 2020-2023  润新知