面向对象SOLID原则的学习心得
单一职责原则:Single Responsibility Principle
开闭原则 :Open Closed Principle
里氏替换原则:Liskov Substitution Principle
接口隔离原则:Interface Segregation Principle
依赖倒置原则:Dependence Inversion Principle
1. S:Single Responsibility Principle(单一职责原则)
“一个类只能由一个原因使其改变” 。
或者说,一个类只实行一个职责,如果有多种变化原因导致一个类要被修改,那么这个类,就违反了单一职责原则。其中的“多种原因”可能是可能是这个类的所实现的某个需求发生了变化。
2. O:Open-close Principle(开闭原则)
“一个类应该对扩展开放,对修改关闭”。
假如windows系统是一个不遵守OCP原则的模块,那么在我们使用了多年的win7之后,假如我们更新到win10,由于对win7扩展成win10的时候,对win7的内容进行了较多的修改,导致我们升级到win10之后,win7的原有功能都被修改了,这就会使我们很困惑。明明只是升级,结果就好像用了一个新系统一样。而实际上,从win7到win10,我认为尽可能地做到了“对扩展开放,对修改关闭”,这样就会让使用者在享受新版本特性的同时也能继续保持自己以往的使用习惯,使用户更轻松。但是,我也认为由于我们无法预测未来我们将要做如何种类的扩展,所以我们或许永远也无法完全做到开闭原则,或许我们能做的,就是以SRP原则为基础,运用接口,抽象类,或者其他的设计模式来尽可能降低模块与模块之间的耦合性,尽可能向伟大的OCP原则靠拢。
3. L:The Liskov Substitution Principle(里氏替换原则)
“子类对象可以替换其父类对象被使用” 。
简单地说,就是父类能出现的地方子类就可以出现,且不会出现异常。假如 Dog 是Animal 的子类 那么我们可以说 Dog dog = new Dog();根据LSP原则,我们也可以说 Animal dog = new Dog(); 即狗是动物。不过我们不能反过来说 Dog dog = new Animal(), 这显然是离谱的,而且编译器会报错。如果我们的设计满足了LSP原则,那么子类就可以完全替换父类并进行扩展,促进“对扩展开放,对修改关闭”的OCP原则的实现。
4. I:Interface Segregation Principle(接口隔离原则)
“客户端不应该被迫依赖于它不需要的接口,类与类之间的依赖关系应该建立在最小的接口上”。
也就是说,我们要尽量创建一个“苗条”的接口,而不是一个里面包含多种方法集合的臃肿接口;尽量要减少客户端对不需要接口的依赖。假如现在有两种人,一种是热衷于电子竞技从不敲代码的的游戏玩家,一种是从来不打游戏的编程学习者。而笔记本电脑又有两种,一种是搭载了高配置显卡可以畅玩3A大作,一种是搭载了低压处理器能够高续航的敲代码。(以上均为假设)
//笔记本电脑接口
interface Laptopinterface{
public void playAAAGame();
public void code();
}
//笔记本电脑实现类
class Laptop implements Laptopinterface{
@Override
public void playAAAGame() {
System.out.println("畅玩3A大作");
}
@Override
public void code() {
System.out.println("高续航的打代码");
}
}
//从不打代码的电子竞技玩家
class Player{
public Laptopinterface laptop = null;
public Player(Laptopinterface laptop) { this.laptop = laptop; }
}
//测试类
public class Test {
public static void main(String[] args) {
Player player = new Player(new Laptop());
player.laptop.playAAAGame();
//明明游戏玩家从来不打代码,可是还被迫依赖了自己不需要的方法
player.laptop.code();
}
}
可以看出,这样的设计是违反了接口隔离原则的,电子竞技玩家被迫依赖了自己不需要的打代码方法。将来对电脑接口的code()方法进行修改的时候都会影响到本来不需要code()方法的Player实例。为了实现ISP原则,我们应该将接口分解,使得需求不同的用户去调用他们对应需求的接口。
//电子竞技玩家接口
interface PlayAAAGame{
public void play();
}
//编程学习者接口
interface InputCode{
public void code();
}
//笔记本电脑实现类
class Laptop implements PlayAAAGame,InputCode{
@Override
public void play() {
System.out.println("畅玩3A大作");
}
@Override
public void code() {
System.out.println("高续航的打代码");
}
}
//从不打代码的电子竞技玩家
class Player{
//调用电竞玩家所需要的PlayAAAGame接口
public PlayAAAGame laptop = null;
public Player(PlayAAAGame laptop) { this.laptop = laptop; }
}
//从不电竞的编程学习者
class Coder{
//调用编程学习者所需要的InputCode接口
public InputCode laptop = null;
public Coder(InputCode laptop){
this.laptop = laptop;
}
}
//测试类
public class Test {
public static void main(String[] args) {
Player player = new Player(new Laptop());
Coder coder = new Coder(new Laptop());
player.laptop.play();
coder.laptop.code();
}
}
这样就消除了Player和Coder对自己不需要接口的依赖。
我发现,接口隔离原则,本质上也是单一职责原则的一种体现.
5. D:Dependence Inversion Principle(依赖倒转原则)
“上层模块不应该依赖底层模块,他们都应该依赖于抽象。抽象不应该依赖于细节,细节应该依赖于抽象”。
从上面的例子我发现,用户Player和Coder在使用电脑时也并没有依赖笔记本电脑Laptop的实体类,而是Laptop实现的接口PlayAAAGame和InputCode。当我们编写好接口之后,即使Laptop类出现了可以面向其他类型用户的需求,我们只需要持有这一接口引用就可以进行操作了。我认为在设计的过程中遵循DIP原则的同时,也是在默默的提醒我们要更好的实现ISP原则,实现了接口隔离原则之后我们又可以正确的实现里氏替换原则,同时实现了上述原则,我认为才有可能尽可能的实现开闭原则。或许只有掌握了SOLID原则才能使我们写出更加solid(可靠的)代码吧。