「设计模式」面向对象7大设计原则(开闭,单一职责,里氏替换)
Posted 程序编织梦想
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了「设计模式」面向对象7大设计原则(开闭,单一职责,里氏替换)相关的知识,希望对你有一定的参考价值。
一、前言
设计模式是各位前辈们总结的优秀经验,学好设计模式对大家的编程都有非常好的帮助。很多人可能会有疑问:我不会设计模式写的也很不错,为什么还要学习呢? 确实,你不会设计模式也能写代码,但是学会了设计模式你会有更多的解决思路,并且阅读一些开源框架也会更容易一些。
说起设计模式,很多人都知道GOF的23种设计模式。但是我们要知道的是:GOF的23种设计模式只是常用的集中,设计模式还有很多,很多。
万丈高楼平地起,再讲设计模式之前,我们先来聊聊“面向对象七大设计原则”。如果把设计模式比作高楼大厦的话,那么面向对象七大设计原则就是大楼的地基,可见它的重要性,其实所有的设计模式都是按照这七大设计原则来做的。
二、七大设计原则
原则一:开闭原则(总纲和核心)
开闭原则是所有设计原则中最重要的一个,其他的设计原则都基于这个原则的基础上来实现。
开闭原则规定软件中的对象(类、模块、函数等)对扩展开放,对修改封闭。也就是说针对需求的修改,我们要用扩展来实现,而不是通过修改已有代码来实现。
为了方便大家理解,我用王者荣耀来举例,王者荣耀中这么多英雄,如果把所有英雄都集中在一个类中来实现那是不现实的。你想想每次增加新英雄或者修改英雄的属性都要修改这一个类,那程序员还不疯了!时间长了,里面的逻辑之复杂,功能之紊乱真会要人命的。因此英雄联盟每次增加新英雄都会扩展增加一个新英雄类,这个新英雄类当然是继承“抽象英雄类”,然后实现里面的方法来定制自己的属性和功能。这就是对扩展开放,而修改关闭。
开闭原则有什么好处呢?
(1).方便测试。我们只能以新增加的类进行测试,而不需要管其他的类。
(2).提高复用性。我们把功能拆分成一个一个小功能,是方便我们重复使用的。
(3).提高可维护性。想一下:如果有需求增加或者修改,你是希望重新扩展个类重新写呢?还是阅读别人的代码修改逻辑?
(4).符合面向对象的开发要求。
原则二:单一职责原则
“不要让类太累!”。单一职责原则规定一个类只有一个职责。如果有多个职责(功能)被设计在一个类中,这个类就违反了单一职责原则。其实这不难理解,生活中我们也是这样:专门人做专业事,即使放到公司和国家中也是这样。都会分好几个部门来处理不同的事情,不可能一个部门处理公司所有事情。
单一职责原则有什么好处呢?
(1). 类的复杂性降低了,各个类都有明确清晰的职责。
(2).类的可读性提高了,我就见过这么一个同事,把好多的逻辑处理都放在一个方法中完成,看着那好几百行的方法,我真想打死他。
(3).可维护性提高了,类的功能简单了,可读性高了,自然可维护性就提高了。
虽然理论说起来简单,但是和业务结合起来这个就不是太容易了,所以要结合业务来权衡,对于这个原则,我建议是接口一定要做到单一职责,类的设计尽量做到只有一个原因引起变化。
原则三:里氏替换原则
这个原则比较难理解,下面我会花费很大篇幅来讲解这个原则。
里氏替换原则是针对继承中出现的问题进行规范,它要求:任何基类可以出现的地方,子类一定可以出现。通俗点讲:子类可以扩展父类的功能,可以实现父类抽象方法,但不能改变父类原有的功能或者非抽象方法。
子类可以扩展父类的功能我们可以理解,就是说子类可以添加父类没有的功能。但是子类不能改变父类原有的功能能或者非抽象方法如何理解呢?
我们先想想加入子类更改的父类原有的功能会出现什么问题。我们举例说明:
假设长方形类:
public class Rectangle
setWidth(int width)
this.width=width;
setHeight(int height)
this.height=height
我们知道,正方形属于长方形,所以正方形应该继承长方形,注意继承的时候:正方形不能更改父类原有的功能,如果改了,那正方形就不再属于长方形了。因为它违背了:任何基类可以出现的地方,子类一定可以出现。
正方形类:
public class Square extends Rectangle
setWidth(int width)
this.width=width;
this. height=width;
setHeight(int height)
this.setWidth(height);
这个正方形已经将继承来的setWidth方法和setHeight方法更改了,这就坏了,我们来使用一下你就看明白了。
//重新设置尺寸
public void resize(Rectangle r)
if(r.getHeight()<=r.getWidth)
r.setHeight(r.getWidth+1);
看的出当我们重新设置尺寸的时候,把Rectangle 的对象传进去 和 Square 传进去,运行的效果是不一致的,这也就违背了“任何基类可以出现的地方,子类一定可以出现”的原则。
相信说到这里大家能明白了吧。
具体在使用这个原则的时候有以下几点要求:
- 子类必须实现父类的抽象方法,但不得重写(覆盖)父类的非抽象(已实现)方法。
- 子类中可以增加自己特有的方法。
- 当子类覆盖或实现父类的方法时,方法形参要比父类方法的形参更宽松。
- 当子类的方法实现父类的抽象方法时,方法的返回值要比父类更严格。
下面我着重来讲解一下第三条:当子类覆盖或实现父类的方法时,方法形参要比父类方法的形参更宽松。
父类一个方法func(HashMap map); 子类方法func(Map map);
父类实例fu,子类实例zi,里氏替换原则要求fu.func(HashMap或子类) 必须和zi.func(HashMap或子类) 调用的是同一方法,试想如果子类方法func参数范围比父类的窄的话,zi.func就会优先调用子类的方法,这样就违背了里氏替换原则的基本定义了。
第四条也是同样的道理,如果子类方法的返回值要比父类更宽松的话,那么会优先调用子类的方法,这样也就违背了里氏替换原则的基本定义了。
结尾
好了,就讲到这里吧,这章的篇幅有些长了,太长了大家不太好阅读。
有什么问题留言,也可以去我的微信公众号留言。
大家帮忙微信关注我的微信公众号,每天提供优质java知识!
这是我的微信公众号,我会持续的讲设计模式更新完,感谢大家关注!
以上是关于「设计模式」面向对象7大设计原则(开闭,单一职责,里氏替换)的主要内容,如果未能解决你的问题,请参考以下文章