一、单一职责原则
单一职责原则,这是六大原则中相对好理解的一个原则。
从字面上意思我们就已经可以理解个大概,没错,这个原则讲的就是说在OO中,我们每个类必须做到单一职责。职责,什么意思呢?我们都知道,类具有行为和属性,他是对象的抽象,每个类都会具有某一种职责,完成相应的功能。而单一职责原则强调的就是每个类都必须有且只有一个职责,比如类A,他有且只能有一个职责P1功能,一个boy类,他有且只有一个职责,就是描述boy类的行为和属性,比如说girl和boy都会运动,boy类应该只描述boy的运动行为,gril应该描述gril的运动行为,而不应该将两个行为绑定在一起(除非用接口,这就不属于我们讨论的范围了。)
class boy{
public void athletics(){
System.out.println("男孩运动。。");
}
}
class girl{
public void athletics(){
System.out.println("女孩运动。。");
}
}
而很多开发者为了简单,不顾及单一职责原则,都喜欢这么写。
class Person{
public void athletics(String sex){
if("男孩".equals(sex)){
System.out.println("男孩运动。。");
}else if("女孩".equals(sex)){
System.out.println("女孩运动。。");
}
}
这样写确实是相对来讲简单了多。但是维护起来就变得麻烦了,如果只是简单的功能,比如这里只有一个功能,那还好,那想一下,如果这个类有很多功能呢?这就让一个person类执行了很多不是属于这个类的功能。万一要修改代码,就会使得这个类的维护变得极其麻烦。比如职责扩散,男孩的运动分出了很多,篮球,排球,等等。那就必须修改这一整个方法了,这一不小心就可能会导致女孩运动这部分代码的异常。增大了维护难度。
遵循单一职责原的优点有:
可以降低类的复杂度,一个类只负责一项职责,其逻辑肯定要比负责多项职责简单的多;
提高类的可读性,提高系统的可维护性;
变更引起的风险降低,变更是必然的,如果单一职责原则遵守的好,当修改一个功能时,可以显著降低对其他功 能的影响。
需要说明的一点是单一职责原则不只是面向对象编程思想所特有的,只要是模块化的程序设计,都适用单一职责 原则。
二、里氏替换原则
里氏替换原则主要是针对继承,其核心就是:父类已经实现的方法,子类不要去重写!
继承包含这样一层含义:父类中凡是已经实现好的方法(相对于抽象方法而言),实际上是在设定一系列的规范和契约,虽然它不强制要求所有的子类必须遵从这些契约,但是如果子类对这些非抽象方法任意修改,就会对整个继承体系造成破坏。而里氏替换原则就是表达了这一层含义。
继承作为面向对象三大特性之一,在给程序设计带来巨大便利的同时,也带来了弊端。比如使用继承会给程序带来侵入性,程序的可移植性降低,增加了对象间的耦合性,如果一个类被其他的类所继承,则当这个类需要修改时,必须考虑到所有的子类,并且父类修改后,所有涉及到子类的功能都有可能会产生故障。
那么可能会有开发者会问了,既然不要去重写了,那继承的意义又何在呢?!是的,继承一个类,就是为了复用父类的方法和重写父类的抽象方法!
同样的,我们用代码来表示一下。
例如,我们有一个类A实现了两数相减的功能
class A{
public int func1(int a, int b){
return a-b;
}
}
public class Client{
public static void main(String[] args){
A a = new A();
System.out.println("100-50="+a.func1(100, 50));
System.out.println("100-80="+a.func1(100, 80));
}
}
运行结果为:
100-50=50
100-80=20
后来,我们需要增加一个新的功能:完成两数相加,然后再与100求和,由类B来负责。即类B需要完成两个功能:两数相减;两数相加在与100求和
由于A类已经实现了两数相减,所以我们只需要重写一个类B来继承类A,并且增加一个两数相加再与100求和的方法就行,因此。代码我们可能会这样写
class B extends A{
public int func1(int a, int b){
return a+b;
}
public int func2(int a, int b){
return func1(a,b)+100;
}
}
public class Client{
public static void main(String[] args){
B b = new B();
System.out.println("100-50="+b.func1(100, 50));
System.out.println("100-80="+b.func1(100, 80));
System.out.println("100+20+100="+b.func2(100, 20));
}
}
运行结果为:
100-50=150
100-80=120
100+20+100=220我们发现,由于开发者一个不小心,复写了父类的方法,导致了程序的出现了不可预计的错误,可能有开发者这是由于命名的问题,如果父类命名好了,子类怎么还会随意去复写实现的内容呢。可问题是,这个简单的类我们可以解释为命名的问题,可是你能保证程序中所有的方法命名完全无误吗?这个程序的所有开发者都有良好的命名规范吗?所以,为了避免类似的问题出现,只能尽量不要复写父类已实现的方法,
三、依赖倒置原则
定义:高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象。 而所谓抽象,在java中的体现就是接口 和抽象类。下面,我们就来捋一捋这定义 问题由来:有一类A依赖类B,后来需要改变了,需要依赖类C,而此时的只能修改类A的代码,类B和类C只是底层,负责原子操作,而类A作为高层,实现了很多复杂的业务逻辑,如果改动类A的代码,则会带来极大的错误风险。这里,就是依赖倒置原则的实现场景之一了。 解决方案:高层模块类A不要依赖于底层模块类B和类C,改成依赖于接口I,而类B和类C则实现接口I,这样利用接口作为中间件将连接高层模块类A和底层模块类B和类C,这样将会极大地降低了需求改变带来的风险。 下面以例子作为讲解: