七大原则是程序员架构之路上躲不开的一道槛,只有掌握了七大原则和二十四设计模式才有能对代码进行高水平的重构,对源码的解读也会更加的轻松!
用抽象构建框架,用实现扩展细节是所有代码设计模式的基础。设计原则的出现是为了提高系统的可维护性和可复用性,提高系统的高内聚和低耦合! 每种设计原则的组合和使用都是在业务场景和需求量中进行取舍!
==========================================================================================
Debug内存分析:利用开发工具对代码的分析和内存数据的变化来跟踪数据的移动和内部规律,常用技巧如下:
--------Debug调试,ctrl+U(代码评估),断点调试,步入,步出,步过,下一步~
控制台打印或者内部打印
模拟参数设定
Junit的相关测试
Ctrl+T:查看类或者方法的实现
Ctrl+U:Debug时候可以直接查看代码内存细节
Ctrl+Shif+T:全局查找类,查看源码时候使用
Ctrl+O:查看类结构
---------------------------------------------------------------------------------------------------------------------------------------------------------------------
1--------开闭原则:一个软件实体如类,模块和函数应该对扩展开放,修改关闭!
背景:一般情况下,在系统相对稳定的时候,一个Dao类都是通过实现一个抽象接口来完成一个Dao类,但是当我们对于某个Dao类的实现并不满足。我们需要在原来的Dao基础上新增方法或者模块。对于专属专用的接口自然比较容易,但是对于公共的接口或者职能清晰的接口,每一次的改变都是需要对系统的稳定和架构进行分析,对我们并不是那么随便可以改的!
>>>>开闭原则则是指:在这种背景下,为了不破坏系统的稳定性,遵循其理对其进行扩展,而拒绝修改!
运用:
解析:ICourse与javaCourse是系统设计之初正常业务下的接口与实现关系,其作为初始系统的考虑已经是丰满而足够的,现在因为业务的扩展不再满足,我们需要对当前的方法进行扩展!一般情况下,我们一般是不再去修改其代码,而是对当期实现类进行扩展,根据需要对原来方法的代码进行重载(重载而非重写)
扩展:对类进行扩展后,我们的调用也是使用接口引用指向扩展类对象的方法 这样做的原理是利用运行时多态的特性。当父类引用指向子类对象的时候,被声明的是父类对象,被编译的却是子类对象,子类在被实例化的时候会将父类也一同编译!因此当使用父类引用去调用静态时,静态变量是属于父类的,当调用方法的时候,因为父类引用是属于父类的,调用不了子类的方法,只能调用父类的方法。唯有当该父类声明强转为子类对象后,则可以调用到子类的方法,如下:
解析:接口类引用 指向 子类声明。调用的静资源是接口类的实现类。
---------------------------------------------------------------------------------------------------------------------------------------------------------------------
2-----依赖倒置原则:上层模块不应该依赖下层模块 ,二者都应该依赖其抽象!
背景:依赖倒置的情况比较特殊,但是效果的改变是最劲爆的,属于针对接口编程的经典! 一般而言,针对一个用户的能力,如学习,我们可以定义一个用户类,并通过赋予这个用户类一个成员方法来实现这种行为。随着用户类的不断发展,用户类每拥有一个方法我们则需要修改或者扩展用户类一次,最后的结果必然导致系统类爆炸。
如果用户扩展的这种能力是属于某种类型的实现之一,如学习与学习数学,学语文,学英语之间的关系,那么我们可以通过针对学习这个类型进行抽象,让用户类去引用这个类型接口。也即是用户类通过有参构造器或者setXXX(学习接口类型 xxx)这样的方式来指向接口,而我们针对不同的学习类型对学习接口类型进行实现,利用多态的特性进行引用,从而把用户与学习种类解耦,实现跟学习类型这个接口进行低耦合。
解析:本图的图例需要忽略掉ICourse和User之间的直联关系(这个由于个人将多个版本集中在一起演化而没删除的关系),通过图例我们可以看到User类只与ICourse有聚合关系,而子课程在实现接口类后,可以跟随业务需要而自由扩展!User类在需要的时候,直接传入声明则可以对应调用!如下:
---------------------------------------------------------------------------------------------------------------------------------------------------------------------
3-----单一职责原则:不要存在多于一个导致类变更的原因
背景:单一职责是指在类层面,方法层面,接口层面的职能都是单一的。在类中,职责一和职责二发生改变都会影响到同一个class类,那么久有可能造成当职责过多的时候,牵一发而动全身,系统耦合度太高而不便于维护!
接口层面:职责单一是指接口都应该归属于同一种类型的接口,不可过于少也不可以过于多地划分接口,而是根据职责来分!而且在完成后可以根据依赖倒置原则来执行修改引用
方法层面:方法和功能服务单一且清晰
在开发中,尽可能地维护好接口和方法的单一职责,而对于类的单一职责则看情况分析,以不引起类爆炸为准!
---------------------------------------------------------------------------------------------------------------------------------------------------------------------
4-----接口隔离原则:用多个职能专一的接口,而不是使用将多种职能的方法都集中在一起的接口!一个类对一个类的依赖应该建立在最小的接口上。
注意:接口隔离原则理论上是对接口进行细化,但是过于细化的接口必然会导致代码过于复杂,因此在接口创建过程中即要防止接口声明过多,也要防止接口里面方法体太多从而导致某些类出现空实现!
现实:虽然不想说明,但是此接口是往往被默认使用最多的情况,目前的三层架构都是以"职能"为模块做接口,无形中符合了此设计!
上面三个接口都是从下面的集成接口里面拆分的,根据动物类型拆分而成!
---------------------------------------------------------------------------------------------------------------------------------------------------------------------
5------迪米特原则:一个对象应该对其他对象保持最少的了解。一个对象只需要保持对其朋友对象的关注就好,其他的交给中介类!
背景:无论什么类,当一个类中加载过多类的引用或者对象声明,那么这个类将会与其被引用的类产生高度的耦合,非常不利于代码的维护和扩展。因此在一个类对另外一个类进行引用的时候,如非必要,尽可能不要引入其类。就比如少用继承多用聚合或者组合也是这个道理!
注:迪米特主要指定了一个边界,提醒我们,重点关注的有以下:
一个类的成员变量,入参或出参所涉及到的对象都是该类的朋友类,在方法体里面的类则不算其朋友类。因此我们需要尽可能保持不要与其他非朋友类对话!
---------------------------------------------------------------------------------------------------------------------------------------------------------------------
6-----里氏替换:如果对每一个类型为T1的对象o1,都有类型为T2的对象o2,使得以T1定义的所有程序P在所有的对象哦
都替换成o2时,程序P的行为没有发生改变,那么类型T2位T1的子类型!
扩展:一个软件实体如果使用一个父类,那一定适用其子类,所有引用父类的地方必须能够透明底使用其子类的对象,子类能够替换父类对线下而程序逻辑不变!
里氏替换是对开闭原则的扩展,他主要是制定了当我们队父类进行扩展时候的一些原则,同时这些原则也约束了继承的泛滥!
里式替换原则支持通过对现有类,现有接口的扩展来满足新增的业务需求,而不支持直接去修改基础类。但是里式替换原则却不太支持对父类进行重写,更多的要求对其进行重载,因此在这方面的约束也颇多:
-----子类可以扩展父类的功能,但是不能改变父类的功能!
-----对于抽象类(存在空白方法体的类),子类可以实现父类的抽象,但是不能覆盖父类的非抽象方法
-----一个 子类可以增加自己特有的方法
这三条规则的结合,可以看出里式替换相对来说是支持重载的。并且有对其提出了诸多要求:
当子类的方法重载父类的方法时候,方法的前置条件,也即方法的输入/入参要比父类方法的输入参数更加宽松!如果子类的参数与父类相同,那么就会变成重写,如果入参类型小于父类,那么父类方法永远不会被执行到!
当子类的方法实现父类的方法时(重写/重载/实现抽象方法),方法的后置条件(方法的输出/返回值)要比父类更加严格或者相等,可以理解为范围!
注:这也是我们在面试的时候说到重写和重载各自条件的来源!
---------------------------------------------------------------------------------------------------------------------------------------------------------------------
7-----合成/复用原则:尽量使用对象组合,而不是继承来达到复用的目的
在面向设计过程中对于关联关系首先要通过组合/聚合关系来实现复用功能,其次才是通过继承和实现关系来实现复用。因为继承对类间关系的绑定,会造成高度耦合,而组合聚合则可以使得系统更加灵活,从而降低类间的耦合度。
继承复用会破坏类间的封装性,这种容易将父类的内部细节暴露给子类的行为,称为白箱操作。并且对于继承而言,子类其从父类继承而来的方法基本是基于静态实现,一旦父类发生改变,子类也需要随着变动。类与类间没有足够的灵活性可言!
注入:将已有的对象纳入到新对象中,使之成为新对象的一部分,新对象可以调用成员对象的功能,对成员对象内部细节却不可见!甚至可以利用依赖倒置或者多态动态调用,这种行为被称为"黑箱操作"。在实现复用的时候应该多用关联而少用继承,关联可以理解为内部对象调用等,如将原本为继承的代码修改为setter方法注入(依赖倒置原则)
扩展:
组合:组合是指将多个对象组合在一起,具有相同的生命周期,主类维护了对子类的引用等信息,当任何一个子类出现问题或者子类遗失,主类都会受到影响!
聚合:聚合是一种弱的关联关系,不具备相同的生命周期,主类或许是通过set方法维护了对子类的调用,当子类遗失的时候,主类并不回受到影响!(类似于依赖倒置原则中的用户类和课程子类)