简单来讲,对于类来说,一个类应该只负责一项职责。如果类A负责两个不同的职责:职责1和职责2。当职责1需求变更而改变A时,可能造成职责2执行错误,所以需要将类A的粒度分解为A1和A2。
举例说明:一个交通工具类,他启动的方式有多种,可以天上飞,可以水里游,可以地上跑。可是一般来说,一个交通工具他启动的方式只有一种(排除特殊的交通工具),那么这个时候,我们就要将这个交通工具类进行拆解成具体的工具
方式一:
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、降低类的复杂度,一个类只负责一项职责 2、提高类的可读性,可维护性 3、降低变更引起的风险 4、通常情况下,我们应当遵守单一职责原则,只有逻辑足够简单,才可以在代码级别违反单一职责原则,只有类中的方法数量足够少,可以在方法级别上保持单一职责
客户端不应该依赖它不需要的接口,即一个类对于另一个类的依赖应该建立在最小的接口上,画图说明 从上面用例图可以看出来,类A和类B分别只用到了接口当中的部分方法,而我们实现的时候却要实现所有的方法,也就是说此接口对于类A和类C来说并不是最小接口。那么按照接口隔离原则,我们应该做如下的拆分
1、高层模块不应该依赖于低层模块,二者应该依赖抽象 2、抽象不应该依赖细节,细节应该依赖抽象 3、依赖倒置的中心思想是面向接口编程 4、依赖倒置设计理念:相对于细节的多边性,抽象的东西要稳定的多。以抽象为基础搭建的架构比以细节为基础搭建的架构要稳定的多 5、使用接口和抽象类的目的是制定好规范,而不涉及任何操作,把细节的任务交给他们的实现类去完成
需求: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()); } }1、低层模块尽量要有抽象类或者接口,或者两者都有,程序的稳定性会更好 2、变量的声明尽量是抽象类和接口,这样我们的变量引用和实际对象,就存在一个缓冲层,有利于程序扩展和优化
在使用继承时,我们应当遵循里氏替换原则,在子类中尽量不要重写父类的方法,继承实际上让两个类耦合性增强了,在适当情况下,可以通过聚合、组合来解决问题
这个时候要调用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中的减法,可以通过对象名.方法名来调用了
一个软件当中的实体,如类,模块和函数应该对扩展开放,对修改关闭
用代码来演示一下,先看用例图,未遵从开闭原则
//基类 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
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里
原则上尽量使用组合或者聚合的方式,而不是使用继承,这个和里氏替换原则有点像,所以很多书上都写的是设计模式六大原则,了解即可