zoukankan      html  css  js  c++  java
  • 设计模式之美

    设计模式之美 - 面向对象六大原则

    设计模式之美目录:https://www.cnblogs.com/binarylei/p/8999236.html

    这是设计模式系列开篇的第一篇文章。也是我学习设计模式过程中的总结。这篇文章主要讲的是面向对象设计中,我们应该遵循的六大原则。只有掌握了这些原则,我们才能更好的理解设计模式。

    1. 单一职责原则(SRP):一个类应该仅有一个引起他变化的原因。
    2. 开闭原则(OCP):对扩展是开放的,但是对修改是关闭的。
    3. 里式替换原则(LSP):子类可以去扩展父类的功能,但是不能改变父类原有的功能。
    4. 接口隔离原则(ISP):类间的依赖关系应该建立在最小的接口上。
    5. 依赖倒置原则(DIP):面向接口编程,而不是具体实现。
    6. 迪米特原则(LOD):一个对象应该对其他对象保持最小的了解。

    1. 单一职责原则(Single Responsibility Principle)

    单一职责原则:一个类只负责完成一个职责或者功能。如果一处修改引起了多处功能性质类似的类的变动 ,或者多个类中的代码有重复,可以考虑合并为同一个类。

    但实际工作中,评价一个类的职责是否足够单一,很难有一个非常明确的、可以量化的标准,甚至是一件非常主观、仁者见仁智者见智的事情。真正的软件开发中,我们也没必要过于未雨绸缪,过度设计。所以,我们可以先写一个粗粒度的类,满足业务需求。随着业务的发展,如果粗粒度的类越来越庞大,代码越来越多,这个时候,我们就可以将这个粗粒度的类,拆分成几个更细粒度的类。这就是所谓的持续重构。

    单一职责原则的好处如下:

    1. 可以降低类的复杂度,一个类只负责一项职责,这样逻辑也简单很多。
    2. 提高类的可读性,和系统的维护性,因为不会有其他奇怪的方法来干扰我们理解这个类的含义。
    3. 当发生变化的时候,能将变化的影响降到最小,因为只会在这个类中做出修改。

    2. 开闭原则(Open Closed Principle)

    开闭原则:对扩展是开放,对修改是关闭。通俗的讲,添加一个新的功能时,尽量在已有代码基础上扩展代码(新增模块、类、方法等),而非修改已有代码(修改模块、类、方法等)。

    如 Spring 的 ConversionService 的实现类 CompositeConversionService,如果要扩展一种新的转换方式,只需要实现 ConversionService 接口即可,不需要修改原来的代码。

    private final List<ConversionService> delegates;
    
    @Override
    public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
        for (int i = 0; i < this.delegates.size() - 1; i++) {
            ConversionService delegate = this.delegates.get(i);
            if (delegate.canConvert(sourceType, targetType)) {
                return delegate.convert(source, sourceType, targetType);
            }
        }
        return this.delegates.get(this.delegates.size() - 1).convert(source, sourceType, targetType);
    }
    

    总结: 开闭原则说起来容易实施起来却很难,这要求我们要时刻具备扩展意识、抽象意识、封装意识。很多设计原则、设计思想、设计模式,都是以提高代码的扩展性为最终目的的。特别是 23 种经典设计模式,大部分都是为了解决代码的扩展性问题而总结出来的,都是以开闭原则为指导原则的。最常用来提高代码扩展性的方法有:多态、依赖注入、基于接口而非实现编程,以及大部分的设计模式(比如,装饰、策略、模板、职责链、状态)。

    3. 里氏替换原则(Liskov Substitution Principle)

    里氏替换原则:子类对象能够替换程序中父类对象出现的任何地方,并且保证原来程序的逻辑行为不变及正确性不被破坏。通俗的讲,子类可以去扩展父类的功能,但是不能改变父类原有的功能。包含以下几层意思:

    1. 子类可以实现父类的抽象方法,但是不能覆盖父类的非抽象方法。
    2. 子类可以增加自己独有的方法。
    3. 当子类的方法重载父类的方法时候,方法的形参要比父类的方法的输入参数更加宽松。
    4. 当子类的方法实现父类的抽象方法时,方法的返回值要比父类更严格。

    既然有了继承和多态,那什么还会提出里氏替换原则呢?之所以这样要求,是因为继承有很多缺点,虽然是复用代码的一种方法,但同时继承在一定程度上违反了封装。父类的属性和方法对子类都是透明的,子类可以随意修改父类的成员。这也导致了,如果需求变更,子类对父类的方法进行一些复写的时候,其他的子类无法正常工作。所以里氏替换法则被提出来。

    如果确保里氏替换原则落地?这要求我们的程序建立抽象,通过抽象去建立规范,然后用实现去扩展细节。里式替换原则更有意义的落地方案是 "Design By Contract"(按照协议来设计)。即子类在设计的时候,要遵守父类的行为约定(或者叫协议)。父类定义了函数的行为约定,那子类可以改变函数的内部实现逻辑,但不能改变函数原有的行为约定。这里的行为约定包括:函数声明要实现的功能;对输入、输出、异常的约定;甚至包括注释中所罗列的任何特殊说明。实际上,定义中父类和子类之间的关系,也可以替换成接口和实现类之间的关系。

    4. 接口隔离原则(Interface Segregation Principle)

    接口隔离原则:客户端不应该依赖他不需要的接口。通俗的讲,类间的依赖关系应该建立在最小的接口上。

    比如 Spring 将 BeanFactory 暴露给用户,将 ConfigurableListableBeanFactory 暴露给开发者。

    5. 依赖倒置原则(Dependency Inversion Principle)

    依赖倒置原则:一种特殊的解耦方式,使得高层次的模块不应该依赖于低层次的模块的实现细节的目的,依赖模块被颠倒了。这也是一个让人难懂的定义,简单来说就是面向接口编程,而不是具体实现

    1. 上层模块不应该依赖于下层模块,两者都应该依赖于抽象;
    2. 抽象不应该依赖于实现,实现应该依赖于抽象。

    在 Java 中抽象指的是接口或者抽象类,两者皆不能实例化。而细节就是实现类,也就是实现了接口或者继承了抽象类的类,是可以被实例化的。上层模块指的是调用端,下层模块是具体的实现类。在 Java 中,依赖倒置原则是指模块间的依赖是通过抽象来发生的,实现类之间不发生直接的依赖关系,其依赖关系是通过接口是来实现的。这就是俗称的面向接口编程。

    6. 迪米特原则(Least Knowledge Principle)

    迪米特原则:也被称为最小知识原则,一个对象应该对其他对象保持最小的了解。通俗的讲,不该有直接依赖关系的类之间,不要有依赖;有依赖关系的类之间,尽量只依赖必要的接口。迪米特法则是希望减少类之间的耦合,让类越独立越好。每个类都应该少了解系统的其他部分,一旦发生变化,需要了解这一变化的类就会比较少。

    利用这个原则,能够帮我们实现代码的 "高内聚、松耦合"。实际上,"高内聚、松耦合" 是一个比较通用的设计思想,可以用来指导不同粒度代码的设计与开发,比如系统、模块、类,甚至是函数,也可以应用到不同的开发场景中,比如微服务、框架、组件、类库等。"高内聚、松耦合" 能够有效提高代码的可读性和可维护性,缩小功能改动导致的代码改动范围。高内聚用来指导类本身的设计,松耦合用来指导类与类之间依赖关系的设计。

    KISS 原则(Keep It Simple and Stupid)

    KISS 原则是保持代码可读和可维护的重要手段。KISS 原则中的 "简单" 并不是以代码行数来考量的。代码行数越少并不代表代码越简单,我们还要考虑逻辑复杂度、实现难度、代码的可读性等。而且,本身就复杂的问题,用复杂的方法解决,并不违背 KISS 原则。除此之外,同样的代码,在某个业务场景下满足 KISS 原则,换一个应用场景可能就不满足了。对于如何写出满足 KISS 原则的代码,有下面几条指导原则:

    1. 不要使用同事可能不懂的技术来实现代码;
    2. 不要重复造轮子,要善于使用已经有的工具类库;
    3. 不要过度优化。

    YAGNI 原则(You Ain’t Gonna Need It)

    YAGNI 原则直译过来就是:你不需要它。这条原则也算是万金油了。当用在软件开发过程中,不要去设计当前用不到的功能;不要去编写当前用不到的代码。实际上,这条原则的核心思想就是:不要做过度设计

    DRY 原则(Don’t Repeat Yourself)

    DRY 原则(Don’t Repeat Yourself)三种典型的代码重复:实现逻辑重复、功能语义重复和代码执行重复。这三种代码重复,有的看似违反 DRY,实际上并不违反;有的看似不违反,实际上却违反了。

    (1)实现逻辑重复:比如 AutowiredAnnotationBeanPostProcessor 和 CommonAnnotationBeanPostProcessor 分别处理 @Autowired 和 @Resource 依赖注入时,实现的逻辑都差不多。它们的逻辑虽然重复,但并不违反 DRY 原则,反而有一种重复之美,正如那句老话,好代码千篇一律,烂代码花样百出。

    AutowiredAnnotationBeanPostProcessor#postProcessMergedBeanDefinition
        -> findAutowiringMetadata
            -> buildAutowiringMetadata
        -> InjectionMetadata#checkConfigMembers
    AutowiredAnnotationBeanPostProcessor#postProcessProperties
        -> InjectionMetadata#inject
            -> AutowiredFieldElement#inject
            -> AutowiredMethodElement#inject
        
    CommonAnnotationBeanPostProcessor#postProcessMergedBeanDefinition
        -> findResourceMetadata
            -> buildResourceMetadata
        -> InjectionMetadata#checkConfigMembers
    CommonAnnotationBeanPostProcessor#postProcessPropertyValues
        -> InjectionMetadata#inject
            -> ResourceElement#inject
            -> WebServiceRefElement#inject
            -> EjbRefElement#inject
    

    (2)功能语义重复:比如两个功能相同的方法,虽然代码没有重复,但毫无疑问肯定违反 DRY 原则。

    (3)代码执行重复:方法可能被没有必要的多次调用。


    每天用心记录一点点。内容也许不重要,但习惯很重要!

  • 相关阅读:
    JSP指令简介(转)
    test markdown
    10个值得前端收藏的CSS3动效库(工具)
    停止不必要的UI动效设计
    UI新手学配色
    改网页鼠标指针、改指定元素指针(2)——小白也能自绘指针
    CSS改网页鼠标指针、改指定元素指针(1)——代码部分
    更高的效率、管理你的文件:Listary!!
    对js操作html的实践【2】——随机标题与滚动标题
    对js操作html的实践【1】——实现网页假崩溃吸引网友注意力
  • 原文地址:https://www.cnblogs.com/binarylei/p/8999659.html
Copyright © 2011-2022 走看看