1、单一职责原则
1.1 什么是单一职责原则
简单来讲,对于类来说,一个类应该只负责一项职责。如果类A负责两个不同的职责:职责1和职责2。当职责1需求变更而改变A时,可能造成职责2执行错误,所以需要将类A的粒度分解为A1和A2。
举例说明:一个交通工具类,他启动的方式有多种,可以天上飞,可以水里游,可以地上跑。可是一般来说,一个交通工具他启动的方式只有一种(排除特殊的交通工具),那么这个时候,我们就要将这个交通工具类进行拆解成具体的工具
1.2 代码演示
方式一:
class Vehicle{
public void run(String vehicle){
System.out.println(vehicle+"在公路上运行");
}
}
方式一违反了单一职责原则,并不是所有的交通工具都在陆地上运行,这时候我们可以将这个类进行拆分
方式二:
class RoadVehicle{
public void run(String vehicle){
System.out.println(vehicle+"在公路上运行");
}
}
class AirVehicle{
public void run(String vehicle){
System.out.println(vehicle+"在天空上运行");
}
}
class WaterVehicle{
public void run(String vehicle){
System.out.println(vehicle+"在水上运行");
}
}
遵守了单一职责原则,不过改动很大
方式三:
class Vehicle{
public void runRoad(){
System.out.println("在公路上运行");
}
public void runAir(){
System.out.println("在天空上运行");
}
public void runWater(){
System.out.println("在海上运行");
}
}
这种方式没有对类做太大的修改,只是增加方法,虽然没有在类级别上遵守单一职责原则,但是在方法级别上,依然是遵守单一职责原则的
1.3 单一职责原则注意事项
1、降低类的复杂度,一个类只负责一项职责
2、提高类的可读性,可维护性
3、降低变更引起的风险
4、通常情况下,我们应当遵守单一职责原则,只有逻辑足够简单,才可以在代码级别违反单一职责原则,只有类中的方法数量足够少,可以在方法级别上保持单一职责
2、接口隔离原则
客户端不应该依赖它不需要的接口,即一个类对于另一个类的依赖应该建立在最小的接口上,画图说明
从上面用例图可以看出来,类A和类B分别只用到了接口当中的部分方法,而我们实现的时候却要实现所有的方法,也就是说此接口对于类A和类C来说并不是最小接口。那么按照接口隔离原则,我们应该做如下的拆分
3、依赖倒置原则
3.1 依赖倒置介绍
1、高层模块不应该依赖于低层模块,二者应该依赖抽象
2、抽象不应该依赖细节,细节应该依赖抽象
3、依赖倒置的中心思想是面向接口编程
4、依赖倒置设计理念:相对于细节的多边性,抽象的东西要稳定的多。以抽象为基础搭建的架构比以细节为基础搭建的架构要稳定的多
5、使用接口和抽象类的目的是制定好规范,而不涉及任何操作,把细节的任务交给他们的实现类去完成
3.2 代码演示
需求:Person接收信息
方案一:
class Email{
pubic String getInfo(){
return "email:hello";
}
}
class Person{
public void receive(Email email){
System.out.println(email.getInfo());
}
}
这种方式比较简单,但先在我们还想获取微信,短信等信息时,不仅要新增类还要增加Person中对应的接收方法。
方式二:
interface IReceiver(){
public String getInfo();
}
class Email implements IReceiver(){
public String getInfo(){
return "email:hello";
}
}
class WeChat implements IReceiver(){
public String getInfo(){
return "weChat:hello";
}
}
class Person{
public void receive(IReceiver receiver){
System.out.println(receiver.getInfo());
}
}
3.3 依赖倒置原则注意事项
1、低层模块尽量要有抽象类或者接口,或者两者都有,程序的稳定性会更好
2、变量的声明尽量是抽象类和接口,这样我们的变量引用和实际对象,就存在一个缓冲层,有利于程序扩展和优化
4、里氏替换原则
在使用继承时,我们应当遵循里氏替换原则,在子类中尽量不要重写父类的方法,继承实际上让两个类耦合性增强了,在适当情况下,可以通过聚合、组合来解决问题
4.1 代码演示
class A{
//返回两个数的差
public int function(int num1,int num2){
return num1-num2;
}
}
class B extends A{
//重写A中的方法,把减法改成加法
public int function(int num1,int num2){
return num1+num2
}
}
这个时候要调用A中的方法只能通过Super关键字来调用了。如果遵从里氏替换原则,该怎么写
class Base{
//将更加基础的方法和属性写到Base类
}
class A extends Base{
public int function(int num1,int num2){
return num1-num2;
}
}
class B extends Base{
private A a=new A();
public int function(int num1,int num2){
return num1+num2;
}
}
这个时候我们想在类B中调用A中的减法,可以通过对象名.方法名来调用了
5、开闭原则
一个软件当中的实体,如类,模块和函数应该对扩展开放,对修改关闭
用代码来演示一下,先看用例图,未遵从开闭原则
//基类
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;
}
}
//绘图的类
class GraphEditor{
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(){
System.out.println(“绘制矩形”);
}
public void drawCircle(){
System.out.println(“绘制圆”);
}
public void drawTriangle(){
System.out.println(“绘制三角形”);
}
}
此方式存在的问题是,当我们要新增加一个图形时,要修改GraphEditor类。再来看看下面这种方式
abstract class Shape{
int m_type;
public abstract void draw();
}
class Rectangle extends Shape{
Rectangle(){
super.m_type=1;
}
public void draw(){
System.out.println("绘制矩形")
}
}
class Circle extends Shape{
Circle(){
super.m_type=1;
}
public void draw(){
System.out.println("绘制圆")
}
}
class Triangle extends Shape{
Triangle(){
super.m_type=1;
}
public void draw(){
System.out.println("绘制三角形")
}
}
//绘图的类
class GraphEditor{
public void drawShape(Shape s){
s.draw();
}
}
采用这种方式,我们再新增一个图形,不影响原来的GraphEditor
6、迪米特法则
1、一个对象应该与其他的对象保持最少的了解,类与类关系越密切,耦合度就越大。
2、迪米特法则又称为最小知道原则,即一个类对自己依赖的类知道的越少越好。也就是说,对于被依赖的类不管多么复杂, 都应该逻辑的封装在内部。除了对外提供的public 方法,不泄露任何信息
3、只与直接朋友通信
4、迪米特法则的核心是降低类之间的耦合
需求分析:有一个学校,有各个学院和总部,要求打印学校总部的id和学院员工的id
//学校总部员工类
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<>();
for(int i=0;i<10;i++){
CollegeEmployee emp=new CollegeEmployee();
emp.setId("学院员工id"+i);
list.add(emp);
}
return list;
}
}
//学校管理类
class SchoolManager{
//返回学院所有的员工
public List<Employee> getAllEmployee(){
List<Employee> list=new ArrayList<>();
for(int i=0;i<10;i++){
Employee emp=new Employee();
emp.setId("学院员工id"+i);
list.add(emp);
}
return list;
}
void printAllEmployee(CollegeManager sub){
//....
//这里CollegeEmployee不是SchoolManaget的直接朋友,违反了迪米特法则
List<CollegeEmployee> list= sub.getAllEmployee();
//打印
}
}
如果要遵循迪米特法则,则需要将打印的方法,封装在CollegeManaget里
7、合成复用
原则上尽量使用组合或者聚合的方式,而不是使用继承,这个和里氏替换原则有点像,所以很多书上都写的是设计模式六大原则,了解即可