概念
一个类应该只有一个引起它变化的原因,不要让一个类拥有多种变化的理由。即一个类只应该完成一个职责相关的业务,不要让一个类承担过多的职责。粒度大小根据业务来,即简单的职责可以让一个类兼任。复杂的职责必须独立
说人话
一个类、方法、接口,最好只干一件事
举例
定义一个people类,用来规定人类的基本行为,如:走、跑、跳、蹲等。但不能把开车、弹钢琴、射击等个性化行为全部加到people类中,因为这些不是所有人都会。当实例化people对象是一个儿童时,这些方法就用不到。接口和类是类似的。
定义一个drive方法,用来开车。但是车有很多种,驾驶方法也不一样,drive方法需要根据传入的参数,判断车型,然后执行不同的驾驶方式。所以我们最好把每种车的驾驶单独写一个方法,这样修改某种车的驾驶方法时,不会影响其他驾驶方法的代码。
概念
客户端只依赖于它所需要的接口;它需要什么接口就提供什么接口,把不需要的接口剔除掉。类间的依赖关系应建立在最小的接口上。
说人话
其实还是单一职责原则的体现,只不过是特指接口的。就是在设计接口时,尽量细化,不要在一个接口里设置大量方法。这会导致实现接口的类,不得不实现一些自己用不到方法,增加代码维护难度
概念
软件实现应该对扩展开放,对修改关闭
说人话
设计程序时,对于之后新增和修改需求,应该保证不修改底层代码,而是扩展底层代码,只在调用层做修改
开闭原则是设计原则的核心原则,其他设计原则都是开闭原则的体现和补充
软件系统是否有良好的接口(抽象)设计是判断软件系统是否满足开闭原则的一种重要的判断基准。现在多把开闭原则等同于面向接口的软件设计
举例
//定义一个商品接口
interface product{
function getName();
function getPrice();
}
//手机、电脑类实现了该接口
class phone implements product{
public function getName{ echo '我是手机';}
public function getPrice{ echo '我的价格是1000';}
}
class pc implements product{
public function getName{ echo '我是pc';}
public function getPrice{ echo '我的价格是5000';}
}
//调用层
$phone = new phone();
$phone->getName;
$phone->getPrice;
此时,若pc需要获取打折价格,如果按照常规思维,我么可能会有2种修改方式。
方式1,直接在接口中新增一个获取折扣价的方法,然后在pc类里实现这个方法。但是这样不仅修改了底层逻辑代码,不符合开闭原则;同时phone类也需要实现折扣方法,但phone并不需要折扣。
方式2,直接修改pc类的getPrice方法,获取折扣价。但这样还是违背了开闭原则,同时导致pc类无法获取原来的价格
正确的方法是,新建一个discountPc类,继承pc类后,重载getPrice方法,如下
class discountPc extends pc{
public function getPrice(){
echo '我的折扣价是4000';
}
}
这样,获取原价就用pc对象,获取折扣价就用discountPc对象。只需要在调用层修改代码即可。
但问题又来了,商品接口需要增加一个获取规格的方法getSku,所有实现类都会用到,而且getSku和原有功能没有任何关系,无法使用重载,那么代码该如何扩展呢。
显然,按上面底层逻辑代码的结构,是无法满足在开闭原则下进行扩展的。我们需要把接口粒度调整到最小,即只实现一个功能,如下
interface productInfo{
function execute();
}
class pcName implements productInfo{
public function execute(){
echo '我是pc';
}
}
class pcPrice implements productInfo{
public function execute(){
echo '我的价格是5000';
}
}
class phoneName implements productInfo{...}
class phonePrice implements productInfo{...}
//调用层
public getProductInfo(productInfo $info){
$info->execute();
}
getProductInfo(new pcName());
getProductInfo(new pcPrice());
这样的话,若需要新增需求或者修改需求,只需新增类,继承productInfo,然后在调用层调用即可。
不过这样也会导致接口的约束性变差,代码量增大,这就需要在设计程序架构时,根据需求进行取舍
概念
里氏替换原则强调的是设计和实现要依赖于抽象而非具体;子类只能去扩展基类,而不是隐藏或者覆盖基类。所有引用基类的地方必须能透明地使用其派生类的对象。
说人话
应用程序中任何父类对象出现的地方,我们都可以用其子类的对象来替换,并且可以保证原有程序的逻辑行为和正确性。
里氏替换原则要求子类不要覆盖父类的方法,如果实在要覆盖,需要遵守以下规则
1、 前置条件不能被加强
前置条件即输入参数是不能被加强的,比如父类方法没有限制输入参数必须为int,而子类内的方法限制了传入参数为int,此时在调用处替换父类对象为子类对象就可能引发异常。
也就是说,子类对输入的数据的校验比父类更加严格,那子类的设计就违背了里式替换原则。
2、后置条件不能被削弱
后置条件即输出,假设我们的父类方法约定输出参数要大于0,调用父类方法的程序根据约定对输出参数进行了大于0的验证。而子类在实现的时候却输出了小于等于0的值。此时子类的涉及就违背了里氏替换原则
3、不能违背对异常的约定
在父类中,某个函数约定,只会抛出 ArgumentNullException 异常, 那子类的设计实现中只允许抛出 ArgumentNullException 异常,任何其他异常的抛出,都会导致子类违背里式替换原则。
概念
低耦合就是依赖倒置原则。高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象。中心思想是面向接口编程
说人话
面向接口编程,类最好都有接口限定他们的行为,方便统一调用
举例
最直接的应用就是工厂模式。工厂负责根据条件生成并返回一个对象,调用端可以直接使用返回对象中的方法,而不需判断对象是否不同,对象是否有这个方法。这是因为工厂对象,遵循了依赖倒置原则,都实现了同一个接口中的方法
概念
迪米特法则(Law of Demeter )又叫做最少知识原则。如果两个软件实体无须直接通信,那么就不应当发生直接的相互调用。一个对象应当对其他对象尽可能少的了解
说人话
迪米特法则主要强调两点
1、 在一个类中,可以直接调用的类,只能是当前类的属性,或者方法的传入值或传出值(即在方法中生成了一个类)
2、调用外部类时,外部类暴露的方法越少越好
迪米特法则主要是为了降低类之间的耦合
第1点,是为了可以在外部控制依赖类
第2点是为了减少外部调用类的方法过多,导致耦合度过高
概念
合成复用原则(又叫组合/聚合复用原则)。它要求在软件复用时,要尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现
说人话
不要为了复用代码,随便使用继承
举例
一个订单类A,一个数据库类B
A需要连接数据库,操作数据,为了复用B的功能,继承了B。这显然是不合理的,而且当A需要使用一个新的数据库类C时,A的代码就需要改变,未被了开闭原则
依赖注入就是合成复用原则和迪米特法则的具体是想,把要用到的数据库类实例传入A中,A无论想用B还是C都可以在调用层修改即可
A 水果,B 苹果 ,B是A的一种,就属于继承关系(is a)
A人 ,B运动,A和B都有跑步功能,但A和B不是同一种类,属于聚合关系(has a)