作为程序媛,必须懂得的7大设计原则!

开闭原则

一个软件实体如类,模块和函数应该对扩展开放,修改关闭!

背景

一般情况下,在系统相对稳定的时候,一个Dao类都是通过实现一个抽象接口来完成,但是当我们对于某个Dao类的实现并不满足。需要在原来的Dao基础上新增方法或者模块。对于专属专用的接口自然比较容易,但是对于公共的接口或者职能清晰的接口,每一次的改变都是需要对系统的稳定和架构进行分析,对我们并不是那么随便可以改的!

开闭原则是指在这种背景下,为了不破坏系统的稳定性,遵循其理对其进行扩展,而拒绝修改类本身!

运用

作为程序媛,必须懂得的7大设计原则!

解析

ICourse与javaCourse是系统设计之初正常业务下的接口与实现关系,其作为初始系统的考虑已经是丰满而足够的,现在因为业务的扩展不再满足,我们需要对当前的方法进行扩展!一般情况下,我们一般是不再去修改其代码,而是对当期实现类进行扩展,根据需要对原来方法的代码进行重载(重载而非重写)

扩展

对类进行扩展后,我们的调用也是使用接口引用指向扩展类对象的方法 (面向接口编程)这样做的原理是利用运行时多态的特性。当父类引用指向子类对象的时候,被声明的是父类对象,被编译的却是子类对象,子类在被实例化的时候会将父类也一同编译!因此当使用父类引用去调用静态时,静态变量是属于父类的,当调用成员方法的时候,因为父类引用是属于父类的,调用不了子类的方法,只能调用父类的方法。唯有当该父类声明强转为子类对象后,则可以调用到子类的方法,如下:

作为程序媛,必须懂得的7大设计原则!

解析

接口类引用 指向 子类声明。调用的静资源是接口类的实现类。

依赖倒置原则

上层模块不应该依赖下层模块 ,二者都应该依赖其抽象!

背景

依赖倒置的情况比较特殊,但是效果的改变是最劲爆的,属于接口编程的经典! 一般而言,针对一个用户的能力,如学习,我们可以定义一个用户类,并通过赋予这个用户类一个成员方法来实现这种行为。但随着用户类的不断发展,用户类每拥有一个方法我们则需要修改或者扩展用户类一次,最后的结果必然导致系统类爆炸。
如果用户扩展的这种能力是属于某种类型的实现之一,如学习与学习数学、学语文、学英语之间的关系,那么我们可以通过针对学习这个类型进行抽象,让用户类去引用这个类型接口。比如用户类通过有参构造器或者setXXX(学习接口类型 xxx)这样的方式来指向接口,而我们针对不同的学习类型对学习接口类型进行实现,利用多态的特性进行引用,从而把用户与学习种类解耦,实现跟学习类型这个接口进行低耦合。
作为程序媛,必须懂得的7大设计原则!

解析

本图的图例需要忽略掉ICourse和User之间的直联关系(这个由于个人将多个版本集中在一起演化而没删除的关系),通过图例我们可以看到User类只与ICourse有聚合关系,而子课程在实现接口类后,可以跟随业务需要而*扩展!User类在需要的时候,直接传入声明则可以对应调用!如下:
作为程序媛,必须懂得的7大设计原则!

单一职责原则

不要存在多于一个导致类变更的原因

背景

单一职责是指在类层面,方法层面,接口层面的职能都是单一的。在类中,职责一和职责二发生改变都会影响到同一个class类,那么久有可能造成当职责过多的时候,牵一发而动全身,系统耦合度太高而不便于维护!

接口层面

职责单一是指接口都应该归属于同一种类型的接口,不可过于少也不可以过于多地划分接口,而是根据职责来分!而且在完成后可以根据依赖倒置原则来执行修改引用

方法层面

方法和功能服务单一且清晰。在开发中,尽可能地维护好接口和方法的单一职责,而对于类的单一职责则看情况分析,以不引起类爆炸为准!
作为程序媛,必须懂得的7大设计原则!

接口隔离原则

用多个职能专一的接口,而不是使用将多种职能的方法都集中在一起的接口!一个类对一个类的依赖应该建立在最小的接口上。

注意

接口隔离原则理论上是对接口进行细化,但是过于细化的接口必然会导致代码过于复杂,因此在接口创建过程中即要防止接口声明过多,也要防止接口里面方法体太多从而导致某些类出现空实现!

现实

虽然不想说明,但是此接口是往往被默认使用最多的情况,目前的三层架构都是以"职能"为模块做接口,无形中符合了此设计!
作为程序媛,必须懂得的7大设计原则!

上面三个接口都是从下面的集成接口里面拆分的,根据动物类型拆分而成!

迪米特原则

一个对象应该对其他对象保持最少的了解。一个对象只需要保持对其朋友对象的关注就好,其他的交给中介类!

背景

无论什么类,当一个类中加载过多类的引用或者对象声明,那么这个类将会与其被引用的类产生高度的耦合,非常不利于代码的维护和扩展。因此在一个类对另外一个类进行引用的时候,如非必要,尽可能不要引入其类。就比如少用继承多用聚合或者组合也是这个道理!
注:迪米特主要指定了一个边界,提醒我们,重点关注的有以下:
一个类的成员变量,入参或出参所涉及到的对象都是该类的朋友类,在方法体里面的类则不算其朋友类。因此我们需要尽可能保持不要与其他非朋友类对话!
作为程序媛,必须懂得的7大设计原则!

里氏替换

如果对每一个类型为T1的对象o1,都有类型为T2的对象o2,使得以T1定义的所有程序P在所有的对象哦都替换成o2时,程序P的行为没有发生改变,那么类型T2位T1的子类型!

扩展

一个软件实体如果使用一个父类,那一定适用其子类,所有引用父类的地方必须能够透明底使用其子类的对象,子类能够替换父类对线下而程序逻辑不变!
里氏替换是对开闭原则的扩展,他主要是制定了当我们队父类进行扩展时候的一些原则,同时这些原则也约束了继承的泛滥!

里式替换原则支持通过对现有类,现有接口的扩展来满足新增的业务需求,而不支持直接去修改基础类。但是里式替换原则却不太支持对父类进行重写,更多的要求对其进行重载,因此在这方面的约束也颇多:

-----子类可以扩展父类的功能,但是不能改变父类的功能!
-----对于抽象类(存在空白方法体的类),子类可以实现父类的抽象,但是不能覆盖父类的非抽象方法
-----一个 子类可以增加自己特有的方法

这三条规则的结合,可以看出里式替换相对来说是支持重载的。并且有对其提出了诸多要求:
当子类的方法重载父类的方法时候,方法的前置条件,也即方法的输入/入参要比父类方法的输入参数更加宽松!如果子类的参数与父类相同,那么就会变成重写,如果入参类型小于父类,那么父类方法永远不会被执行到!
当子类的方法实现父类的方法时(重写/重载/实现抽象方法),方法的后置条件(方法的输出/返回值)要比父类更加严格或者相等,可以理解为范围

注: 这也是我们在面试的时候说到重写和重载各自条件的来源!

合成/复用原则

尽量使用对象组合,而不是继承来达到复用的目的

在面向设计过程中对于关联关系首先要通过组合/聚合关系来实现复用功能,其次才是通过继承和实现关系来实现复用。因为继承对类间关系的绑定,会造成高度耦合,而组合聚合则可以使得系统更加灵活,从而降低类间的耦合度。
继承复用会破坏类间的封装性,这种容易将父类的内部细节暴露给子类的行为,称为白箱操作。并且对于继承而言,子类其从父类继承而来的方法基本是基于静态实现,一旦父类发生改变,子类也需要随着变动。类与类间没有足够的灵活性可言!

注入

将已有的对象纳入到新对象中,使之成为新对象的一部分,新对象可以调用成员对象的功能,对成员对象内部细节却不可见!甚至可以利用依赖倒置或者多态动态调用,这种行为被称为"黑箱操作"。在实现复用的时候应该多用关联而少用继承,关联可以理解为内部对象调用等,如将原本为继承的代码修改为setter方法注入(依赖倒置原则)

扩展

组合

组合是指将多个对象组合在一起,具有相同的生命周期,主类维护了对子类的引用等信息,当任何一个子类出现问题或者子类遗失,主类都会受到影响!

聚合

聚合是一种弱的关联关系,不具备相同的生命周期,主类或许是通过set方法维护了对子类的调用,当子类遗失的时候,主类并不回受到影响!(类似于依赖倒置原则中的用户类和课程子类)

PS:我最近新整理了海量资料,包含面经分享、模拟试题、还有视频干货。可以私信我免费领取噢!