• 小酌重构系列[8]——提取接口


    前言

    世间唯一“不变”的是“变化”本身,这句话同样适用于软件设计和开发。
    在软件系统中,模块(类、方法)应该依赖于抽象,而不应该依赖于实现。

    当需求发生“变化”时,如果模块(类、方法)依赖于具体实现,具体实现也需要修改;
    如果模块(类、方法)依赖于接口,则无需修改现有实现,而是基于接口扩展新的实现。

    面向实现?面向接口?

    接口可以被复用,但接口的实现却不一定能被复用。

    面向实现编程,意味着软件的模块(类、方法)之间的耦合性非常高,每次遭遇“变化”,都会涉及到修改,并且可能是牵一发而动全身的。
    每次修改,都需要对原有的代码重新测试,也可能给旧的代码引入新的错误。

    面向接口编程,是为了应对软件设计和开发中的“变化”,它是一种“以不变应万变”的思维模式。
    只要确保我们的抽象(接口)是不变的,无论需求怎么变化,我们总能通过扩展新的实现自如地应对。

    接口是稳定的,关闭的,但接口的实现是可变的,开放的。

    开闭原则

    “依赖于抽象,而不是具体实现”,它同时也是开闭原则的一种体现。

    开闭原则的定义:当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化。它是面向对象的基本原则之一。

    开闭原则主要有两个特征:
    (1)扩展开放(Open for extension)

    (2)修改关闭(Closed for modification)
    开闭原则要求模块(类、方法)应具备良好的扩展性,同时对现有的功能具有一定的保护能力。

    开闭原则是一个比较模糊的一个原则,它没有告诉你如何才能对扩展开放,以及如何才能对修改关闭。
    这需要借助我们自身的经验,以及对需求的理解程度,去分析软件系统中抽象的部分,识别其中的“变化”和“不变”。

    提取接口

    “提取接口”是面向对象编程常用的解耦策略,将一些可能发生变化的具体实现提取为接口,将“变化”封装起来,从而达到依赖接口、而非具体实现的目的。

    示例

    重构前

    以下是一个课程注册的场景,这段代码提供了2个类:ClassRegistration和RegistrationProcessor,RegistrationProcessor依赖于ClassRegistration的Create()方法和Total属性。

    public class ClassRegistration
    {
        public void Create()
        {
            // create registration code
        }
    
        public decimal Total { get; private set; }
    }
    
    public class RegistrationProcessor
    {
        public decimal ProcessRegistration(ClassRegistration registration)
        {
            registration.Create();
            return registration.Total;
        }
    }
    

    假如系统的业务发生了变化,ClassRegistration类的Create()方法已经不能满足新的业务了,我们需要使用另外的注册方法。
    这意味着,我们需要修改ClassRegistration类的Create()方法,且需要让其同时满足旧业务和新业务。
    image

    RegistrationProcessor依赖于ClassRegistration,既然ClassRegistration存在着诸多变数,我们可以使用“提取接口”的重构策略,让RegistrationProcessor依赖于某个接口。

    重构后

    重构后,RegistrationProcessor依赖于IClassRegistration接口,RegistrationProcessor不必去关心IClassRegistration的具体实现是什么。
    新的业务要求不同的Create()方式时,我们无需更改现有的ClassRegistration,而是添加新class并实现IClassRegistration接口。

    另外,在不同场景下,旧业务和新业务的实现可能在不同场景下被使用。
    这时,我们可以借助IoC框架将IClassRegistration接口的实例注入到指定场景。

    public interface IClassRegistration
    {
        void Create();
    
        decimal Total { get; }
    }
    
    public class ClassRegistration : IClassRegistration
    {
        public void Create()
        {
            // create registration code
        }
    
        public decimal Total { get; private set; }
    }
    
    public class RegistrationProcessor
    {
        public decimal ProcessRegistration(IClassRegistration registration)
        {
            registration.Create();
            return registration.Total;
        }
    }
    

    最后,下面这幅图描述了这次重构过程。

    image

  • 相关阅读:
    Ubuntu下设置静态网址
    Ubuntu下qemu环境搭建vexpress开发平台
    Ubuntu管理员密码设置
    学习MFC创建界面
    opencv3.0之后IPLimage转换成MAT的问题
    C++的ofstream与ifstream使用
    PCA主成分分析
    pycharm但多行注释快捷键
    fiddler不同代理模式的区别
    Python 常用模块大全(整理)
  • 原文地址:https://www.cnblogs.com/keepfool/p/5472217.html
Copyright © 2020-2023  润新知