面向对象的三大特性:封装、继承和多态
1. 单一职责原则 SRP(Single Responsibility Principle)
类的功能应该单一,不应该包罗万象
2. 开放封闭原则 OCP(Open-Close Principle )
一个模块对于拓展是开放的,对于修改是封闭的,想要增加功能热烈欢迎,想要修改万万不愿意。
面向拓展开发,面向修改封闭
3. 里式替换原则 LSP(The Liskov Substitution Principle)
子类可以替换父类能够出现的地方 ,例如你替你爸去你奶奶家干活
4. 依赖倒置原则 DIP(The Dependency Inversion Principle)
高层次的模块不应该依赖于低层次的模块,他们都应该依赖于抽象;抽象不依赖于具体实现,具体实现应该依赖于抽象。例如你出国需要说你是中国人,而不是哪个村的人。这里中国人就是抽象,哪个村是具体实现。
5. 接口隔离原则 ISP(The Interface Segregation Principle)
设计时采用多个与特定客户类有关的接口比一个通用接口要好。例如手机有打电话,发短信,视频等功能,将这些功能拆分成多个独立的接口,比放在一个接口里要好。
6. 迪米特法则 ( Law of Demeter)
又叫最少知识原则。一个对象应该与其他对象尽可能少的发生接触和了解。
7. 组合/聚合复用原则 (Composite/Aggregate Reuse Principle CARP)
尽量使用合成/聚合达到复用,尽量少用继承。
原则:一个类中有另一个类的对象。
8. 具体原则内容
1. 单一职责原则 SRP (Single Responsibility Principle)
这里拿类作为举例。当然可以是一个属性,一个方法,也可以是一个模块或者工程。
可以降低类的复杂度,一个类 只负责一个功能,其逻辑必然比负责多个功能要简化许多。在提高类的可读性,可维护性的同时,也降低了变更引起的风险。如果单一职责原则维护得好的话,在变更这个类 的时候,会降低对其他功能的影响。
单一职责原则并不是面向对象的特有原则,只要是程序的模块化设计,都需要适用单一职责原则。
例子:
T1 类负责 P1的功能, T2类负责 P2的功能,当后续需要对 P1 功能进行迭代升级的时候,只需要修改T1类,同时并不会影响到 P2的功能。
2. 开放封闭原则 OCP(Open-Close Principle)
开放封闭原则又可以简称开闭原则。
开闭原则主要体现在对拓展性进行开放,对修改进行封闭。当有新的需求或者变化时,需要对现有的代码进行拓展,以便于适应新的需求。
可以通过Template Method模式和Strategy模式进行重构,实现对修改封闭,对扩展开放的设计思路。
封装变化,是实现开放封闭原则的重要手段,对于经常发生变化的状态,一般将其封装为一个抽象,拒绝滥用抽象,只将经常变化的部分进行抽象。
//定义一个接口
public interface MyInterface {
string do();
}
@Service("item的值")
public class MyWork1 implements MyInterface {
@Override
public String do() {
// do 业务1
System.out.println("My work1");
return "success";
}
}
@Service("item的值2")
public class MyWork2 implements MyInterface {
@Override
public String do() {
// do 业务2
System.out.println("My work2");
return "success";
}
}
@RestController
public class MyController {
@AutoWried
Map<String,MyInterface> workMap;
// 当后续业务线拓展,只需要增加 MyInterface的实现类即可
@GetMapping("/api/work/{item}")
public String getDo(@PathVariable String item) {
return workMap.get(item).do();
}
}
3. 里式替换原则 LSP(The Liskov Substitution Principle)
里式替换原则是子类可以替换父类能够出现的地方。
这个原则下,软件中的父类对象可以替换成它的子类对象,程序不会产生错误和异常。反之,则不能成立。如果软件中实体使用的是子类对象,那么它不一定能够使用父类对象,里式替换原则是实现开闭原则的重要实现方式之一。由于使用父类对象的地方都可以被子类对象所替换,因此在程序中尽量使用父类类型来对对象进行定义,而在运行期间再确定使用子类的类型,用子类对象来替换父类对象。
使用里式替换原则需要注意的是:子类的所有方法必须在父类中声明,或子类必须实现父类中声明的所有方法。尽量把父类设计为抽象类或者接口,让子类继承或者实现父类中声明的方法。运行时,子类实例替换父类实例,可以方便地拓展系统的功能,同时无需修改原有的子类代码,需要增加新的功能可以通过增加新的子类来实现。
Java 中的多态即属于这个原则。
4. 依赖倒置原则 DIP(The Dependency Inversion Principle)
- 高层模块不应该依赖于低层模块,二者都应该依赖于抽象类
- 抽象不应该依赖于细节,细节应该依赖于抽象
- 依赖倒置的中心思想是面向接口编程
- 依赖倒置原则的设计理念:相对于细节的多变性,抽象的东西要稳定许多,以抽象类为基础搭建的架构比以细节为基础搭建的架构要稳定的多,在java中,抽象指的是接口或者抽象类,细节就是具体的实现类
- 使用接口或抽象类的目的是制定好规范,而不涉及具体的操作,展现细节的任务交给实现类去完成
具体实现类依赖于抽象,上层依赖于下层。假设B是A较低级的模块,但是B需要用到A的功能,这个时候,B不应该直接使用A的具体类。而应当由B定义一抽象接口,并由A来实现这个抽象接口,B只使用这个抽象接口。B既解除了对A的依赖,反过来A依赖于B定义的抽象接口,通过上层模块难以避免的依赖于下层模块。假如B也直接依赖A的实现,那么可能造成循环依赖。
采用依赖倒置原则可以减少类之间的耦合性,提高系统的稳定性,也可以提高代码的维护性。
Java多态就是属于这个原则。
// 定义好一个接口
public interface IReceiver {
String getInfo();
}
// Email实现类
public class Email implements IReceiver {
public String getInfo() {
return "电子邮件:hello";
}
}
// 微信实现类
public class WeChat implements IReceiver {
public String getInfo() {
return "微信:hello";
}
}
public class Person {
// 对接口的依赖
public void receive(IReceiver receiver) {
System.out.println(receiver.getInfo());
}
}
public class DependecyInversion {
public static void main(String[] args) {
Person person1 = new Person();
person1.receive(new Email());
person1.receive(new WeChat());
}
}
5. 接口隔离原则 ISP(The Interface Segregation Principle)
提供尽可能小的单独接口,而不要提供大的一个接口。暴露行为让后面的实现类知道的越少越好。
建立单一接口,而不要建立庞大的接口,尽可能的细化接口,每个接口中的方法尽量少,形成一个接口实现一个单独的功能。依赖几个专用的功能接口要比依赖一个综合的庞大接口要灵活。通过分散定义多个接口,可以有效地预防后续的变更带来的扩散,提供系统的灵活性和可维护性。
Java的接口可以实现多继承就是接口隔离原则的基础保障。
6. 迪米特法则 ( Law of Demeter)
当类与类之间的关系越来越密切,耦合度也会越来越大,只有降低类与类中间的耦合才符合设计模式。对于被依赖的类来说,无论多复杂的逻辑尽量封装在类内部,每个对象都会与其他的类产生耦合,我们称出现成员变量、方法参数、方法返回值中的类为直接耦合依赖,而出现在局部变量中的类则不是直接耦合依赖,也就是说不是直接耦合依赖的类最好不要作为局部变量的形式出现在类的内部。
一个对象对另一个对象了解的越少越好,也就是说一个软件实体应该尽可能的少与其他实体发生相互作用。在一个类中可以少用其他类就尽量少用,尤其是局部变量的依赖类,能省略就尽量省略。
如果两个类不彼此直接通信,那么这两个类不会发生直接的相互作用。当其中一个类需要调用另外一个类的某一个方法时,可以的话尽量通过第三者进行转达,而不是直接依赖这个类。
迪米特法则也叫作 最少知道原则,描述的是一个对象应当尽可能对其他对象产生较少的了解,不要和陌生人说话!!!
- 强调只和朋友说话,不和陌生人说话。 这里的朋友指的是 出现在成员变量,方法输入,输出参数中的类成为成员朋友。而出现在方法体内部的类不属于朋友类,是陌生人。
- 迪米特法则的初衷在于降低类之间的耦合。尽可能减少每个类对于其他类的依赖,因此会使得系统的功能比较独立,相互之间存在较少的依赖关系。
// 校长类,校长类想要喊某一个学生,那么他不应该直接找某个学生,而应该先找老师
public class SchoolMaster {
public void callStudent(Teacher teacher) {
teacher.callSomeOne();
}
}
// 教师类,教师类喊学生
public class Teacher {
public void callSomeOne() {
Student s = new Student();
s.sayHi();
}
}
// 学生类
public class Student {
public void sayHi() {
System.out.println("say hi");
}
}
public class Test {
public static void main(String [] args) {
SchoolMaster master = new SchoolMaster();
Teacher teacher = new Teacher();
master.callSomeOne(teacher);
}
}
7. 组合/聚合复用原则 (Composite/Aggregate Reuse Principle CARP)
-
聚合
A类持有B类的 弱引用,这里的弱引用指的是可以被垃圾回收器回收,只拥有短暂的生命周期,也就是说对象 A持有B的引用,但是对象B 不持有A的引用。B类和A类没有任何层次的关系。蚁群持有蚂蚁的弱引用,蚁群有多只蚂蚁,所以蚁群和蚂蚁就是聚合关系。 -
组合
A类持有B类的 强引用,也就是说A类由B类组合而成。B类是A类必不可少的一部分,并且B类不会被垃圾回收器回收,具有相同的生命周期。 蚂蚁持有触角的强引用,触角是蚂蚁的不可分割的一部分,所以触角和蚂蚁是组合关系。
组合/聚合复用优于继承
继承其实违背了开闭原则:继承打破了封装性。由于子类继承父类,那么父类的实现细节完全暴露在子类中,随着时间的推移,如果父类发生变化将会影响到子类。所以在使用继承前,应该考虑清楚,是不是is-a的关系,如果不是,就不应该使用继承。
无论是组合还是聚合,都是持有对象,在不继承的前提下可以在内部调用引用的方法,这叫做转发,可以降低耦合度。
适当使用组合/聚合的好处就是明确了每个对象的职责,滥用继承则是结构化的思想。
设计模式中的组合/聚合复用的体现:
-
使用组合原则:
组合模式中的 抽象节点 持有 容器节点 的集合引用
观察者模式中的 抽象主题类 持有 抽象观察者 的集合引用 -
使用聚合原则:
备忘录模式中的 careTaker 持有 Memento 的弱引用
迭代器模式,建造者模式和策略模式等等。
评论区