2、七大原则
设计模式的原则其实大都是为了对象间的松耦合设计而产生的,它强调对接口编程。通过接口定义规则,再通过类去实现,并将其变化独立出来,便于扩展和复用。
2.1 开闭原则(OCP)
开闭原则(Open Close Principle),是设计模式的核心,其实开闭原则算是其他几个原则的一个综合体现。它说的是对象(类,模块,函数等)对扩展开放(对提供方来说可以扩展新功能),对修改关闭(对使用者来说不修改代码),用抽象构建框架(使用抽象类或者接口搭建框架指定规则),用实现扩展细节(用类去继承实现抽象类或接口,然后扩展具体的代码)。
可扩展,不可修改。其实下面的几种原则在一定程度上就是为了实现开闭的原则。
简单来说就是我们的软件如果要变化的时候,我们是通过制定好的框架(接口和抽象类)来去扩展实体行为(增加新的实现类,扩展新的方法),而非修改已经完成的代码(不修改以前已经完成的代码)。
2.2 单一职责
程序中,就一个类而言,应该仅有一个引起他变化的原因,一个类只负责一项职责。比如:在对应数据库时通常一张表对应一个实体类,一个类对应一个dao类,一个controller对应一种功能。
但是有些时候类过于简单的时候,在类级别使用单一原则,会导致成本提高,这时候也可以使用方法上遵循单一原则(一个类内部有多个不同职责的方法)(在类比较简单时使用)。
- 可以降低类的复杂度,一个类只负责一项职责,其逻辑要简单的多;
- 可以提高类的可读性,提高系统的可维护性;
- 变更引起的风险降低,变更是必然的,如果单一职责原则遵守的好,当修改一个功能时,可以显著降低对其他功能的影响。
2.3 里氏代换(LSP)
里式替换是建立在继承的基础上。子类可以修改父类已经实现好的方法,但是如果对其进行任意的修改,会造成整个继承体系的破坏。所以说继承有优点也有缺点。
- 优点
- 子类拥有父类的方法和属性,提高了代码的可复用性。
- 提高了代码的扩展性,子类除了父类的功能,还可以额外的添加自己的功能。
- 缺点
- 继承是侵入性的,父类的属性和方法对子类都是透明的,子类可以随意修改父类的成员
- 降低了代码的灵活性。
- 增强了耦合性。当对父类的代码进行修改时,必须考虑对子类产生的影响。可能父类改变一点点就会导致子类产生功能异常。
正是因为继承的一系列缺点,所以才引用了里式替换原则去解决。
里式替换原则:任何基类出现的地方,其子类也一定可以出现。
比如:如果定义一个Animal(动物类),其子类Dog、Fish、Bird都继承这个Animal类,那么我们不管再怎么去区分它们,不论是天上飞的还是地上跑的,这三者它们至少都是动物。==不管子类的功能如何扩展,都至少都是一个父类。==在编程中,把父类都替换成它的子类,程序的行为没有发生变化。
而里式替换的根本就是:子类可以扩展父类的功能,但是不能改变父类原有的功能,只有这样这样子类才能去替换父类。也正是这种替换父类模块无需修改就能实现扩展。
里氏代换原则是对开闭原则的补充。实现开闭原则的关键步骤就是抽象化,而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。
- 子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法,应该如实的去继承,尽量不要重写。
- 任何使用父类对象的方法都能透明的使用子类对象。(核心,子类去替换父类)
- 如果想使用父类的方法,那就尽量不要使用继承,可以使用依赖、聚合、组合等方式。(比如:可以利用set放出传递对象)。
- 子类可以增加自己的方法
- 当子类的方法重载父类的方法时,方法的形参要比父类方法的输入参数更宽松。
- 当子类的方法实现父类的抽象方法时,方法的返回值要比父类更严格。
2.4 依赖倒转(DIP)
俗称面向接口编程,依赖于抽象(接口、抽象类)不依赖于具体(实现类)。简单来说就是:使用接口或抽象类制定好规范,不涉及具体操作,把细节交给下面的实现类去做。依赖倒转原则是开闭原则的基础。
- 高层模块不应该依赖底层模块,两者都依赖抽象
- 抽象(抽象类或接口)不依赖细节(实现类),细节依赖抽象
- 底层的模块尽量都有抽象类或者接口,这样变量声明时,类型可以使用接口抽象类,去引用实际对象,增加一个缓冲层,有利于程序的扩展和优化。
- 在继承时要遵循里式替换原则
我们创建一个类为猫、一个类为狗,再创建一个人的类。如果要表示一个人养了狗或养了猫,那一个方法是不能表示的,需要去Person里更改传入的代码,或者重写方法,但是当我们再创建其他动物的对象时,Person中的方法就要增加一个,这样显然是不合理的,那么怎么解决呢?
对于上面的问题,就是运用依赖倒转原则,建立一个接口用接口来管理下面的实现类,比如我们创建一个动物的接口,让猫和狗去实现这个接口,这样在Person中直接通过传入接口就可以管理猫和狗甚至更多的实现了动物接口的类
我们也可以用set方法去注入一个对象
2.5 接口隔离(ISP)
接口隔离原则(Interface Segregation Principle):客户端不应依赖它不需要的接口,类间的依赖关系应该建立在最小的接口上(大接口拆分为小接口)。使用多个隔离的小接口,比使用单个大的接口要好。
比如一个才艺接口,内部有四个方法(唱歌、跳舞、蓝球、游戏),两个类:男孩(玩蓝球、游戏)和女孩(唱歌、跳舞),这两个类都实现才艺接口势必有两个方法是空的。
这就是接口臃肿的表现,如果将这个设计修改为符合接口隔离原则,就必须对接口进行拆分。
- 接口尽可能的小:在满足单一职责的前提下,接口内的方法尽可能的减少。
- 接口要高内聚:提高接口、类、模块的处理能力,减少对外的交互。少公布public的方法
- 定制服务:单独为个体提供优良服务
- 接口设计是有限度的:接口的设计粒度越小,系统越灵活,但是灵活也会使结构复杂化,维护性性降低。
2.6 迪米特法则(DP)
迪米特法则(Demeter Principle)又叫最少知道法则,一个类对自己所依赖的类知道的越少越好。实际上就是强调类之间的松耦合关系。松耦合越弱就越容易实现复用,同时在修改时造成的影响就越小。
当类与类之间的关系越密切,它的耦合性就越大,所以类无论是多复杂,都尽量降低成员的访问权限。比方说:一个类的基本属性,我们基本都会用private修饰,然后提供set和get方法进行修改,但是我们也仅仅是调用方法,传递参数,具体怎么修改只有类自己才知道。
简单来说就是类只和自己的直接朋友通信,使的模块之间相对独立。两个对象之间的耦合关系就是朋友关系,耦合的方式有:依赖、关联、组合、聚合等,但是其中有直接朋友和非直接朋友的区别。
- 类的直接朋友有:
- 成员变量
- 方法参数为对象的
- 返回值为对象的
- 非直接朋友
- 直接出现在局部变量中
这种作法就是为了减少耦合。这种作法的好处,就比如电脑,现在的高配电脑都是组装机,每次更新,买CPU和GPU等零部件更换即可,就是因为现在的电脑部件都是高内聚,低耦合的。以前的电脑各个部件之间互相缠绕,互相耦合,想达到自己换零部件的需求时很难很难的。
2.7 合成复用(CRP)
合成复用原则(Composite Reuse Principle):尽量使用合成/聚合的方式,而不是使用继承。它可以使系统更加灵活,降低类与类之间的耦合度,一个类的变化对其他类造成的影响相对较少。
我们思考一下,有两个类A和B,我们想在B中使用A类的方法时,有几种作法?除了第一种外另外三种都可以使用,第一种的依赖关系太强所以不推荐。
- B类直接去继承A类:耦合性太强
- B类新建一个方法,通过参数传递
- B类把A做成属性,通过set方法传递
- B类把A做成属性直接new一个对象。