开闭原则 (Open Closed Principle)
介绍
- 开闭原则是编程中最基础、最重要的设计原则
- 一个软件实体如类,模块和函数应该对扩展开放(对提供方),对修改关闭(对使用方)(后面要求提供方添加新的类时,使用方不需要修改)。
用抽象构建框架,用实现扩展细节
当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化
(当需要增加功能时,尽量是增加代码,而不是修改已有的代码)
- 编程中遵循其它原则,以及使用设计模式的目的就是遵循开闭原则
案例
原始实现
package com.atguigu.principle.ocp; public class Ocp { public static void main(String[] args) { //使用看看存在的问题 GraphicEditor graphicEditor = new GraphicEditor(); graphicEditor.drawShape(new Rectangle()); graphicEditor.drawShape(new Circle()); graphicEditor.drawShape(new Triangle()); } }
//这是一个用于绘图的类 [使用方] class GraphicEditor { //接收Shape对象,然后根据type,来绘制不同的图形 public void drawShape(Shape s) { if (s.m_type == 1) drawRectangle(s); else if (s.m_type == 2) drawCircle(s); else if (s.m_type == 3) drawTriangle(s); } //绘制矩形 public void drawRectangle(Shape r) { System.out.println(" 绘制矩形 "); } //绘制圆形 public void drawCircle(Shape r) { System.out.println(" 绘制圆形 "); } //绘制三角形 public void drawTriangle(Shape r) { System.out.println(" 绘制三角形 "); } }
//Shape类,基类 class Shape { int m_type; }
class Rectangle extends Shape { Rectangle() { super.m_type = 1; } }
class Circle extends Shape { Circle() { super.m_type = 2; } }
//新增画三角形 class Triangle extends Shape { Triangle() { super.m_type = 3; } }
【分析】
- 优点是比较好理解,简单易操作
- 缺点是违反了设计模式的ocp原则,即对扩展开放,对修改关闭。即当我们给类增加新功能的时候,尽量不修改代码,或者尽可能少修改代码
- 比如我们这时要新增加一个三角形,我们需要做较多修改
【改进】
把创建Shape类做成抽象类,并提供一个抽象的draw方法,让子类去实现即可这样我们有新的图形种类时,只需要让新的图形类继承Shape,并实现draw方法即可,这样使用方的代码就不需要修 -> 满足了开闭原则
改进
package com.atguigu.principle.ocp.improve; public class Ocp { public static void main(String[] args) { //使用看看存在的问题 GraphicEditor graphicEditor = new GraphicEditor(); graphicEditor.drawShape(new Rectangle()); graphicEditor.drawShape(new Circle()); graphicEditor.drawShape(new Triangle()); graphicEditor.drawShape(new OtherGraphic()); } }
//这是一个用于绘图的类 [使用方] class GraphicEditor { //接收Shape对象,调用draw方法 public void drawShape(Shape s) { s.draw(); } }
//Shape类,基类 abstract class Shape { int m_type; public abstract void draw();//抽象方法 }
class Rectangle extends Shape { Rectangle() { super.m_type = 1; } @Override public void draw() { System.out.println(" 绘制矩形 "); } }
class Circle extends Shape { Circle() { super.m_type = 2; } @Override public void draw() { System.out.println(" 绘制圆形 "); } }
//新增画三角形 class Triangle extends Shape { Triangle() { super.m_type = 3; } @Override public void draw() { System.out.println(" 绘制三角形 "); } }
//新增一个图形 class OtherGraphic extends Shape { OtherGraphic() { super.m_type = 4; } @Override public void draw() { System.out.println(" 绘制其它图形 "); } }
迪米特法则(Demeter Principle)
介绍
- 一个对象应该对其他对象保持最少的了解
- 类与类关系越密切,耦合度越大
- 迪米特法则又叫最少知道原则,即一个类对自己依赖的类知道的越少越好。也就是说,对于被依赖的类不管多么复杂,都尽量将逻辑封装在类的内部。对外除了提供的public 方法,不对外泄露任何信息。如A类依赖于B类,只需要关注B类的public方法就可以,其他地方怎么实现的不需要关心
- 迪米特法则还有个更简单的定义: 只与直接的朋友通信(直接的朋友:每个对象都会与其他对象有耦合关系,只要两个对象之间有耦合关系,我们就说这两个对象之间是朋友关系。耦合的方式很多,依赖,关联,组合,聚合等。其中,我们作为成员变量、方法参数、方法返回值中的类为直接的朋友,而出现在局部变量(方法里面)中的类不是直接的朋友,如B类是A类的一个成员变量,则B类是A类的直接朋友。也就是说,陌生的类最好不要以局部变量的形式出现在类的内部)
案例
原始实现
package com.atguigu.principle.demeter; import java.util.ArrayList; import java.util.List; //客户端 public class Demeter1 { public static void main(String[] args) { //创建了一个 SchoolManager 对象 SchoolManager schoolManager = new SchoolManager(); //输出学院的员工id 和 学校总部的员工信息 schoolManager.printAllEmployee(new CollegeManager()); } }
//学校总部员工类 class Employee { private String id; public void setId(String id) { this.id = id; } public String getId() { return id; } }
//学院的员工类 class CollegeEmployee { private String id; public void setId(String id) { this.id = id; } public String getId() { return id; } }
//学院的员工类 class CollegeEmployee { private String id; public void setId(String id) { this.id = id; } public String getId() { return id; } }
//管理学院员工的管理类 class CollegeManager { //返回学院的所有员工,临时创建十个员工 public List<CollegeEmployee> getAllEmployee() { List<CollegeEmployee> list = new ArrayList<CollegeEmployee>(); for (int i = 0; i < 10; i++) { //这里我们增加了10个员工到 list CollegeEmployee emp = new CollegeEmployee(); emp.setId("学院员工id= " + i); list.add(emp); } return list; } }
//学校管理类 //分析 SchoolManager 类的直接朋友类有哪些 Employee、CollegeManager //CollegeEmployee 不是 直接朋友 而是一个陌生类,这样违背了 迪米特法则 class SchoolManager { //返回学校总部的员工,临时创建五个员工 public List<Employee> getAllEmployee() { List<Employee> list = new ArrayList<Employee>(); for (int i = 0; i < 5; i++) { //这里我们增加了5个员工到 list Employee emp = new Employee(); emp.setId("学校总部员工id= " + i); list.add(emp); } return list; } //该方法完成输出学校总部和学院员工信息(id) void printAllEmployee(CollegeManager sub) { //分析问题 //1. 这里的 CollegeEmployee 不是 SchoolManager的直接朋友 //2. CollegeEmployee 是以局部变量方式出现在 SchoolManager //3. 违反了 迪米特法则 //获取到学院员工 List<CollegeEmployee> list1 = sub.getAllEmployee(); System.out.println("------------学院员工------------"); for (CollegeEmployee e : list1) { System.out.println(e.getId()); } //获取到学校总部员工 List<Employee> list2 = this.getAllEmployee(); System.out.println("------------学校总部员工------------"); for (Employee e : list2) { System.out.println(e.getId()); } } }
【运行结果】
------------学院员工------------ 学院员工id= 0 学院员工id= 1 学院员工id= 2 学院员工id= 3 学院员工id= 4 学院员工id= 5 学院员工id= 6 学院员工id= 7 学院员工id= 8 学院员工id= 9 ------------学校总部员工------------ 学校总部员工id= 0 学校总部员工id= 1 学校总部员工id= 2 学校总部员工id= 3 学校总部员工id= 4
【分析】
分析 SchoolManager 类的直接朋友类有哪些
- 直接朋友类有 Employee、CollegeManager
- CollegeEmployee 不是
直接朋友
,CollegeEmployee 是以局部变量方式出现在 SchoolManager,这样违背了迪米特法则
【改进】
直接将这段代码放到CollegeManager类里面即可,不要在别人的类里面做你的事情,我只希望能调用你的public方法
//获取到学院员工 List<CollegeEmployee> list1 = getAllEmployee(); System.out.println("------------学院员工------------"); for (CollegeEmployee e : list1) { System.out.println(e.getId()); }
package com.atguigu.principle.demeter.improve; import java.util.ArrayList; import java.util.List; //客户端 public class Demeter1 { public static void main(String[] args) { System.out.println("~~~使用迪米特法则的改进~~~"); //创建了一个 SchoolManager 对象 SchoolManager schoolManager = new SchoolManager(); //输出学院的员工id 和 学校总部的员工信息 schoolManager.printAllEmployee(new CollegeManager()); } }
//学校总部员工类 class Employee { private String id; public void setId(String id) { this.id = id; } public String getId() { return id; } }
//学院的员工类 class CollegeEmployee { private String id; public void setId(String id) { this.id = id; } public String getId() { return id; } }
//管理学院员工的管理类 class CollegeManager { //返回学院的所有员工 public List<CollegeEmployee> getAllEmployee() { List<CollegeEmployee> list = new ArrayList<CollegeEmployee>(); for (int i = 0; i < 10; i++) { //这里我们增加了10个员工到 list CollegeEmployee emp = new CollegeEmployee(); emp.setId("学院员工id= " + i); list.add(emp); } return list; } //输出学院员工的信息 public void printEmployee() { //获取到学院员工 List<CollegeEmployee> list1 = getAllEmployee(); System.out.println("------------学院员工------------"); for (CollegeEmployee e : list1) { System.out.println(e.getId()); } } }
//学校管理类 //分析 SchoolManager 类的直接朋友类有哪些 Employee、CollegeManager //CollegeEmployee 不是 直接朋友 而是一个陌生类,这样违背了 迪米特法则 class SchoolManager { //返回学校总部的员工 public List<Employee> getAllEmployee() { List<Employee> list = new ArrayList<Employee>(); for (int i = 0; i < 5; i++) { //这里我们增加了5个员工到 list Employee emp = new Employee(); emp.setId("学校总部员工id= " + i); list.add(emp); } return list; } //该方法完成输出学校总部和学院员工信息(id) void printAllEmployee(CollegeManager sub) { //分析问题 //1. 将输出学院的员工方法,封装到CollegeManager sub.printEmployee(); //获取到学校总部员工 List<Employee> list2 = this.getAllEmployee(); System.out.println("------------学校总部员工------------"); for (Employee e : list2) { System.out.println(e.getId()); } } }
总结
- 迪米特法则的核心是降低类之间的耦合
- 由于每个类都减少了不必要的依赖,因此迪米特法则只是要求降低类间(对象间)耦合关系, 并不是要求完全没有依赖关系(只要A和B有关系,就会有依赖)
合成复用原则
介绍
尽量使用合成/聚合的方式,而不是使用继承
案例
【方案一】
如果想B类可以使用A类的方法,最简单的方式是,直接让B类继承A类,但是会出现如下问题
- B和A的耦合性较高
- A如果新增了方法,这个方法B不一定需要
【方案二:依赖】
【方案三:聚合】
【方案四:组合】
设计原则核心思想
- 找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起
- 针对接口编程,而不是针对实现编程
- 为了交互对象之间的松耦合设计而努力
文章说明
本文章为本人学习尚硅谷的学习笔记,文章中大部分内容来源于尚硅谷视频(点击学习尚硅谷相关课程),也有部分内容来自于自己的思考,发布文章是想帮助其他学习的人更方便地整理自己的笔记或者直接通过文章学习相关知识,如有侵权请联系删除,最后对尚硅谷的优质课程表示感谢。