单一职责原则
基本介绍
一个类只负责一项职责,假如类A负责两个不同的职责:职责1和职责2;当职责1需求变更需要改变A时,可能对职责2的执行造成错误,所以要将类A的粒度分解为2,分别负责两个职责,这样类变更的风险就降低了,同时一个类对应一个职责也有更好的可读性
案例
不符合单一职责的设计
/***
* @author shaofan
* @Description 不符合单一职责的设计
*/
public class SingleResponsibility1 {
public static void main(String[] args) {
Vehicle motor = new Vehicle("摩托车");
Vehicle car = new Vehicle("轿车");
Vehicle plane = new Vehicle("飞机");
motor.run();
car.run();
plane.run();
}
}
class Vehicle{
private String name;
public Vehicle(String name){
this.name = name;
}
public void run(){
if("飞机".equals(name)){
System.out.println(name+"在天上飞");
}else{
System.out.println(name+"在路上跑");
}
}
}
在天上飞行和在地上行驶两个职责被写在了一个类的一个方法中,如果需要添加在水上航行,则需要修改run方法,并且这样的方式代码复杂,可维护性低
严格按照单一职责的设计
/***
* @author shaofan
* @Description 严格单一职责划分
*/
public class SingleResponsibility2 {
public static void main(String[] args) {
RoadVehicle motor = new RoadVehicle("摩托");
AirVehicle plane = new AirVehicle("热气球");
WaterVehicle boat = new WaterVehicle("木船");
motor.run();
plane.run();
boat.run();
}
}
class RoadVehicle{
private String name;
public RoadVehicle(String name){
this.name = name;
}
public void run(){
System.out.println(name+"在路上跑");
}
}
class AirVehicle{
private String name;
public AirVehicle(String name){
this.name = name;
}
public void run(){
System.out.println(name+"在天上飞");
}
}
class WaterVehicle{
private String name;
public WaterVehicle(String name){
this.name = name;
}
public void run(){
System.out.println(name+"在水里游");
}
}
将不同环境下的行驶放在了不同的类中这样每个类只具有一个职责,但是这样的方式改动较大,会产生较多的类
单一职责简化
class Vehicle2{
private String name;
public Vehicle2(String name){
this.name = name;
}
public void runOnRoad(){
System.out.println(name+"在路上跑");
}
public void RunOnAir(){
System.out.println(name+"在天上飞");
}
public void runOnWater(){
System.out.println(name+"在水里游");
}
}
这种方式并不是严格遵守单一职责原则的,一个类仍然有多个职责,但是在方法层面遵守了单一职责原则,如果类比较简单易理解,逻辑足够简单,可以使用这种方式
总结
- 降低代码的复杂度,一个类只负责一项职责
- 提高类的可读性,可维护性
- 降低变更引起的风险
- 通常情况下,应当严格遵守单一职责原则,只有逻辑足够简单,才可以在代码级违反单一职责原则,如果类中方法数量足够少,可以在方法级别保持单一职责原则,而非类级别
接口隔离原则
基本介绍
客户端不应该依赖它不需要的接口,即一个类对另一个类的依赖应该建立在最小的接口上;比如某个类没有用到某个接口中的方法,应该依赖作用范围最小的不包含该方法的接口
案例
违背接口隔离原则
interface Interface1{
void operation1();
void operation2();
void operation3();
}
class A implements Interface1{
@Override
public void operation1() {
System.out.println("A1");
}
@Override
public void operation2() {
System.out.println("A2");
}
@Override
public void operation3() {
System.out.println("A3");
}
}
class B implements Interface1{
@Override
public void operation1() {
System.out.println("B1");
}
@Override
public void operation2() {
System.out.println("B2");
}
@Override
public void operation3() {
System.out.println("B3");
}
}
/**
* C类使用A类,依赖接口Interface1的1,2方法
*/
class C {
public void operation1(Interface1 i) {
i.operation1();
}
public void operation2(Interface1 i) {
i.operation2();
}
}
/**
* D类使用B类,依赖Interface1的2,3方法
*/
class D {
public void operation2(Interface1 i) {
i.operation2();
}
public void operation3(Interface1 i) {
i.operation3();
}
}
这里只有一个接口Interface1,这个接口有两个实现A和B,类C只使用到了接口的1、2方法;类D只使用到了接口的2、3方法;所以Interface1对类C和类D来说都不是最小接口,都存在冗余的方法,就应该将接口拆分为更小的更小的接口,让类C和类D分别依赖他们需要的最小接口
遵守接口隔离原则
/***
* @author shaofan
* @Description 接口隔离原则
*/
public class Segregation2 {
public static void main(String[] args) {
C c = new C();
c.operation1(new A());
c.operation2(new A());
D d = new D();
d.operation2(new B());
d.operation3(new B());
}
}
interface Interface1{
void operation1();
}
interface Interface2{
void operation2();
}
interface Interface3{
void operation3();
}
class A implements Interface1,Interface2 {
@Override
public void operation1() {
System.out.println("A1");
}
@Override
public void operation2() {
System.out.println("A2");
}
}
class B implements Interface2,Interface3{
@Override
public void operation2() {
System.out.println("B2");
}
@Override
public void operation3() {
System.out.println("B3");
}
}
class C{
public void operation1(Interface1 i){
i.operation1();
}
public void operation2(Interface2 i){
i.operation2();
}
}
class D{
public void operation2(Interface2 i){
i.operation2();
}
public void operation3(Interface3 i){
i.operation3();
}
}
将Interface拆解成了三个接口,接口1表示C和D都要用到的方法,接口2表示只有C需要用到 的方法,接口3表示只有D需要用到的方;A类和B类实现了自己需要的接口,然后供C类和D类使用
依赖倒转原则
基本介绍
- 高层模块不应该依赖底层模块,二者都应该依赖其抽象
- 抽象不应该依赖细节,细节应该依赖抽象
- 依赖倒转的中心思想是面向接口编程
- 依赖倒转原则基于这样的是理念:相对于细节的多变性,抽象的东西要稳定的多。以抽象为基础搭建的架构比以细节为基础的架构要稳定的多。在Java中,抽象使用接口和抽象类来完成
- 使用接口或抽象类的目的是制定规范,而不涉及任何具体的操作,展现细节的任务交给他们的实现类去做
案例
违反依赖倒转原则
class Email{
public String getInfo(){
return "电子邮件信息";
}
}
class Person {
public void receive(Email email){
System.out.println(email.getInfo());
}
}
这里person直接依赖于Email类,依赖了细节,这样的代码扩展性低;如果现在需要增加一个接收手机短息消息,那么需要在Person中新加方法,增加了消息接收模块和用户模块的耦合度
遵守依赖倒转原则
interface IReceiver {
String getInfo();
}
class Email implements IReceiver{
@Override
public String getInfo() {
return "电子邮件";
}
}
class Person {
public void receive(IReceiver iReceiver){
System.out.println(iReceiver.getInfo());
}
}
使用IReceiver来抽象接收消息这一动作,Person依赖于这个接口,这样需要新增消息类型只用新建一个类来实现这个接口即可,Person模块不需要改动,耦合性降低
总结
- 底层模块尽量都要有抽象类和接口,或者两者都有,程序的稳定性更好
- 变量的声明类型尽量是抽象类或者接口,这样变量引用和实际对象之间,就存在一个缓冲层,利于程序扩展和优化,当实际对象改变时,调用的模块只要按照规范来写就不用再改
- 继承时遵循里氏替换原则
里氏替换原则
继承
- 继承包含这样一层含义:父类中凡是已经实现好的方法,实际上是在设定规范和契约(即改方法已经确定细节),虽然它不强制要求所有子类必须遵循这些契约,但是如果子类对这些已经实习的方法任意修改,就会对整个继承体系造成破坏
- 继承在给程序设计带来便利的同时,也带来了弊端,比如继承会给程序带来侵入性,可以移植性降低,增加了类之间的耦合度,如果一个类被其他类继承,如果这个类需要修改,必须考虑到所有的子类,并且父类修改后,所有涉及到的子类都有可能产生故障
基本介绍
- 如果对每个类型为T1的对象O1,都有类型为T2的对象O2,使得T1定义的所有程序P在所有的对象O1都换成O2时,程序P的行为没有发生变化,那么T2是T1的子类型。即所有使用父类的地方,都必须能够透明的使用其子类的对象, 子类的访问控制修饰符大于父类且子类没有重写父类已经实现的方法
- 在使用继承时,遵循里氏替换原则,在子类中尽量不要重写父类已经实现的方法
- 继承实际上让两个类的耦合性增强了,在适当情况下,可以通过聚合、组合、依赖来代替继承解决问题
案例
class A {
public int func1(int a,int b) {
return a + b;
}
public int func2(int a,int b){
return a * b;
}
}
class B extends A{
@Override
public int func1(int a, int b){
return a - b;
}
}
这里B重写了父类A已经实现的方法,在main函数中就不能将对象a替换成另一个B类型的对象b,因为替换后,func1的实现也发生了变化,违背了里氏替换原则
遵守里氏替换原则
class Base {
int func1(int a,int b){
return a+b;
}
}
class A extends Base{
public int func2(int a,int b){
return a-b;
}
}
class B extends Base{
private A a;
public B(A a){
this.a = a;
}
public int func2(int a, int b){
return this.a.func2(a,b);
}
}
增加一个更加基础的类,将更加基础的方法放在基类中,而B中如果需要用到A中的方法,使用聚合的方式代替继承,降低耦合度
开闭原则
基本介绍
- 开闭原则是编程中最基础、最重要的设计原则;其他原则都是为了达到开闭原则的效果
- 一个软件实体如类、模块和函数应该对扩展开放(提供方),对修改关闭(对使用方);用抽象构建框架,用实现扩展细节
- 当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化
- 编程中遵循其他原则,以及使用设计模式的目的就是为了遵循开闭原则
案例
违背开闭原则
/***
* @author shaofan
* @Description 违反开闭原则
*/
public class Ocp1 {
public static void main(String[] args) {
GraphicEditor editor = new GraphicEditor();
editor.drawShape(new Circle());
editor.drawShape(new Rectangle());
}
}
/***
* 这是一个绘图的类
*/
class GraphicEditor{
/**
* 通过传入的图形类型绘制不同的图形
* @param shape
*/
public void drawShape(Shape shape){
if(shape.type==1){
drawCircle();
}else if(shape.type==2){
drawRectangle();
}
}
private void drawCircle(){
System.out.println("圆形");
}
private void drawRectangle(){
System.out.println("长方形");
}
}
/***
* 图形父类
*/
class Shape{
int type;
}
/***
* 圆形
*/
class Circle extends Shape{
Circle(){
super.type = 1;
}
}
/***
* 矩形
*/
class Rectangle extends Shape{
Rectangle(){
super.type=2;
}
}
这种方式违反了ocp原则,当给类增加新功能的时候,要尽量不修改或少修改已有的代码
假如增加一个需要绘制的三角形,那么需要在GraphicEditor中修改drawShape方法,GraphicEditor就是使用各种Shape对象的使用方,在新增功能的时候,对这里的修改应该关闭
遵守关闭原则
/***
* 这是一个绘图的类
*/
class GraphicEditor{
/**
* 通过传入的图形类型绘制不同的图形
* @param shape
*/
public void drawShape(Shape shape){
shape.draw();
}
}
/***
* 图形父类
*/
abstract class Shape{
int type;
abstract void draw();
}
/***
* 圆形
*/
class Circle extends Shape {
Circle(){
super.type = 1;
}
@Override
void draw() {
System.out.println("圆形");
}
}
/***
* 矩形
*/
class Rectangle extends Shape {
Rectangle(){
super.type=2;
}
@Override
void draw() {
System.out.println("矩形");
}
}
将Shape抽象出来,增加一个方法draw,由各个图形子类来实现,这样每个图形类就自己维护自己的绘制方法,绘制逻辑不再由GraphicEditor来实现,GraphicEditor在绘制各个图形时只用调用他们自己的绘制方法即可,同时由于它依赖Shape抽象,遵守了依赖倒转原则,不论新增多少个图形,它都不会改变,新增图形的时候只用按照抽象Shape定义的draw规范来实现对应的方法即可(即对扩展开放)
迪米特法则
基本介绍
- 一个对象应该对其他对象保持最少的了解
- 类与类的关系越密切,耦合度越大
- 迪米特法则又叫最少知道原则,即一个类对自己依赖的类知道的越少越好。也就是说,对于被依赖的类不管多么复杂,都尽量将逻辑封装在类的内部。对外除了提供public方法,不对外泄露任何信息
- 迪米特法则还有个更简单的定义:只与直接的朋友通信
- 直接的朋友:每个类都会和其他类有耦合关系只要两个类之间有耦合关系,就说这两个类之间是朋友关系,耦合的方式有很多,如依赖、关联、组合、聚合等;其中成员变量、方法参数、方法返回值中的类为直接朋友,而出现在局部变量中的就不是直接的朋友,迪米特法则就是要求不让陌生的类出现在方法中
案例
违背迪米特原则
/***
* 学校总部的员工类
*/
class Employee{
private int id;
public void setId(int id){
this.id = id;
}
public int getId() {
return this.id;
}
}
/***
* 分学院的员工类
*/
class CollegeEmployee{
private int id;
public void setId(int id){
this.id = id;
}
public int getId() {
return this.id;
}
}
/***
* 学校总部的管理类
*/
class SchoolManager{
/**
* 返回学校总部的所有员工
* @return
*/
public List<Employee> getEmployee(){
List<Employee> employees = new LinkedList<>();
for (int i = 0; i < 10; i++) {
Employee employee = new Employee();
employee.setId(i+1);
employees.add(employee);
}
return employees;
}
/**
* 打印学校和分学院所有的员工
* @param collegeManager
*/
public void printAllEmployee(CollegeManager collegeManager){
System.out.println("学校总部的员工");
for (Employee employee : getEmployee()) {
System.out.println(employee.getId());
}
System.out.println("学院的员工");
for (CollegeEmployee employee : collegeManager.getEmployee()) {
System.out.println(employee.getId());
}
}
}
/***
* 分学院的管理类
*/
class CollegeManager{
/**
* 返回分学院的所有员工
* @return
*/
public List<CollegeEmployee> getEmployee(){
List<CollegeEmployee> employees = new LinkedList<>();
for(int i=0;i<10;i++){
CollegeEmployee employee = new CollegeEmployee();
employee.setId(i+1);
employees.add(employee);
}
return employees;
}
}
SchoolManager的直接朋友有Employee、CollegeManager,没有CollegeEmployee,CollegeEmployee的对象仅仅以局部变量出现在了SchoolManager的方法中;违背了迪米特法则
遵守迪米特原则
/***
* 学校总部的员工类
*/
class Employee{
private int id;
public void setId(int id){
this.id = id;
}
public int getId() {
return this.id;
}
}
/***
* 分学院的员工类
*/
class CollegeEmployee{
private int id;
public void setId(int id){
this.id = id;
}
public int getId() {
return this.id;
}
}
/***
* 学校总部的管理类
*/
class SchoolManager{
/**
* 返回学校总部的所有员工
* @return
*/
public List<Employee> getEmployee(){
List<Employee> employees = new LinkedList<>();
for (int i = 0; i < 10; i++) {
Employee employee = new Employee();
employee.setId(i+1);
employees.add(employee);
}
return employees;
}
/**
* 打印学校总部的员工
*/
public void printEmployee(){
for (Employee employee : getEmployee()) {
System.out.println(employee.getId());
}
}
/**
* 打印学校和分学院所有的员工
* @param collegeManager
*/
public void printAllEmployee(CollegeManager collegeManager){
System.out.println("学校总部的员工");
printEmployee();
System.out.println("学院的员工");
collegeManager.printEmployee();
}
}
/***
* 分学院的管理类
*/
class CollegeManager{
/**
* 返回分学院的所有员工
* @return
*/
public List<CollegeEmployee> getEmployee(){
List<CollegeEmployee> employees = new LinkedList<>();
for(int i=0;i<10;i++){
CollegeEmployee employee = new CollegeEmployee();
employee.setId(i+1);
employees.add(employee);
}
return employees;
}
/**
* 打印分学院的员工
*/
public void printEmployee(){
for (CollegeEmployee employee : getEmployee()) {
System.out.println(employee.getId());
}
}
}
CollegeManager和SchoolManager都自己维护自己的打印方法,School在调用的时候只需要使用他们暴露的接口,而不管他们内部的逻辑;即调用方不需要知道他们各自怎么实现打印,只需要调用他们提供的打印接口来打印即可,这样SchoolManager中就没有了CollegeEmployee
总结
- 迪米特法则之间的核心是降低类之间的耦合
- 但是注意:由于每个类型中减少了不必要的依赖,因此迪米特法则只是要求降低类之间的耦合度,并不是完全消除耦合
合成复用原则
基本介绍
能够使用合成/聚合的关系,就不要使用继承
案例
违背合成复用原则
class A {
public int operation1(){
return 1;
}
public int operation2(){
return 1;
}
}
class B extends A{
public int operation(){
return super.operation1()+super.operation2();
}
}
这里B需要使用到A的方法,而不需要实现A中的方法,使用B继承A的方式来获取A中的方法,这样A和B的耦合度较高
遵守合成复用原则
class A {
public int operation1(){
return 1;
}
public int operation2(){
return 1;
}
}
class B{
private A a = new A();
public int operation(){
return a.operation1()+a.operation2();
}
}
这里A组合到了B中,而不再是B继承A,这样如果A中添加了某个抽象方法,B也不用管他,降低了耦合度
设计原则核心思想
- 找出应用中可能会发生变化的地方,把他们独立出来,不要和那些不需要变化的代码混在一起
- 针对接口编程,而不是针对实现编程
- 为了互相交互的类之间的松耦合设计而努力