一、单一职责原则
解释:顾名思义,就是一个类只负责一个职责
优点:
1、代码的粒度降低了,类的复杂度降低了。
2、可读性提高了,每个类的职责都很明确,可读性自然更好。
3、可维护性提高了,可读性提高了,一旦出现 bug ,自然更容易找到他问题所在。
4、改动代码所消耗的资源降低了,更改的风险也降低了。
例如:以下代码场景中需要创建鱼移动方式就会出现问题
/**
* 动物类
* Class Animal
* @package app\api\controller
*/
class Animal{
/**
* 移动方式
* @param $animal
* @return string
*/
public function move($animal): string
{
return $animal."会爬行";
}
}
//使用场景
$animal = new Animal();
echo $animal->move("蜘蛛");
/**比如现在有一条鱼 那么就不是爬行因此不能调用$animal->move("鱼");*/
根据上面情况可以使用单一职责原则修改如下
/**单一职责具体修正如下*/
/**
* 陆地生物类
* Class Land
* @package app\api\controller
*/
Class Land{
/**
* 移动方式
* @param $animal
* @return string
*/
public function move($animal): string
{
return $animal."会爬行";
}
}
/**
* 海洋生物类
* Class Ocean
* @package app\api\controller
*/
Class Ocean{
/**
* 移动方式
* @param $animal
* @return string
*/
public function move($animal): string
{
return $animal."会游泳";
}
}
/**使用场景*/
$animal = new Land();
echo $animal->move("蜘蛛");
$ocean = new Ocean();
echo $ocean->move("鱼");
二、里氏替换原则
解释:在子类中尽量不要重写和重载父类的方法
优点:增强程序的健壮性,版本升级是也可以保持非常好的兼容性.即使增加子类,原有的子类还可以继续运行.在实际项目中,每个子类对应不同的业务含义,使用父类作为参数,传递不同的子类完成不同的业务逻辑
例如下面示例中Operation2类add函数违背里氏替换原则不应该重写父类函数,应该采用抽象方法
//里氏替换原则
Class Operation1{
public function add($a, $b)
{
return $a+$b;
}
}
Class Operation2 extends Operation1 {
public function add($a, $b)
{
return $a+$b+3;
}
}
根据上面情况可以使用里氏替换原则修改如下
/**里氏替换原则修正如下*/
abstract Class Operation{
public abstract function add($a,$b);
}
Class Add extends Operation {
public function add($a, $b)
{
return $a+$b;
}
}
Class Add2 extends Operation{
public function add($a, $b)
{
return $a+$b+2;
}
}
三、依赖倒置原则
解释:上层模块不应该依赖底层模块,它们都应该依赖于抽象。抽象不应该依赖于细节,细节应该依赖于抽象
优点:采用依赖倒置原则可以减少类间的耦合性,提高系统的稳定性,降低并行开发引起的风险,提高代码的可读性和可维护性
例如以下示例中当我们需要阅读ipad的时候就不能通过Read类的read函数实现
/**
* 书籍类
* Class Book
* @package app\api\controller
*/
Class Book{
public function getContent()
{
return "book";
}
}
/**
* 阅读类
* Class Read
* @package app\api\controller
*/
Class Read{
public function read(Book $book)
{
return $book->getContent();
}
}
//阅读书
(new Read)->read(new Book());
根据上面情况可以使用依赖倒置原则修改如下
/**
* 读取内容接口
* Interface ReadInterface
* @package app\api\controller
*/
interface ReadInterface{
public function getContent();
}
/**
* 书籍类
* Class Book
* @package app\api\controller
*/
Class Book implements ReadInterface {
public function getContent()
{
return "book";
}
}
/**
* Ipad类
* Class Ipad
* @package app\api\controller
*/
Class Ipad implements ReadInterface {
public function getContent()
{
return "ipad";
}
}
/**
* 阅读类
* Class Read
* @package app\api\controller
*/
Class Read{
public function read(ReadInterface $book)
{
return $book->getContent();
}
}
//这样就可以使用Read类的read函数使用Ipad类
(new Read)->read(new Book());
(new Read)->read(new Ipad());
四、接口隔离原则
解释:客户端不应该依赖它不需要的接口。类间的依赖关系应该建立在最小的接口上
优点:
- 将臃肿庞大的接口分解为多个粒度小的接口,可以预防外来变更的扩散,提高系统的灵活性和可维护性。
- 接口隔离提高了系统的内聚性,减少了对外交互,降低了系统的耦合性。
- 如果接口的粒度大小定义合理,能够保证系统的稳定性;但是,如果定义过小,则会造成接口数量过多,使设计复杂化;如果定义太大,灵活性降低,无法提供定制服务,给整体项目带来无法预料的风险。
- 使用多个专门的接口还能够体现对象的层次,因为可以通过接口的继承,实现对总接口的定义。
- 能减少项目工程中的代码冗余。过大的大接口里面通常放置许多不用的方法,当实现这个接口的时候,*设计冗余的代码。
例如以下示例中
interface Base{
public function func1();
public function func2();
}
Class A implements Base{
public function func1()
{
return "A1";
}
public function func2()
{
//此类中并未使用该方法
}
}
Class B implements Base{
public function func1()
{
//此类中并未使用该方法
}
public function func2()
{
return "B2";
}
}
接口隔离原则修改后如下
interface BaseA{
public function func1();
}
interface BaseB{
public function func2();
}
Class A implements BaseA{
public function func1()
{
return "A1";
}
}
Class B implements BaseB{
public function func2()
{
return "B2";
}
}
五、迪米特原则
解释:如果两个软件实体无须直接通信,那么就不应当发生直接的相互调用,可以通过第三方转发该调用。其目的是降低类之间的耦合度,提高模块的相对独立性
优点:
- 降低了类之间的耦合度,提高了模块的相对独立性。
- 由于亲合度降低,从而提高了类的可复用率和系统的扩展性。
例如以下示例中
Class C{
function a()
{
$a = 10;
$a += 100;
return $a;
}
}
Class D{
function d(C $c)
{
$d = 100;
$a = $c->a();
//$a变量从C类中a方法中获取后还对$a进行操作
$a = $a*10;
$d +=$a;
return $d;
}
}
Class C{
function a()
{
$a = 10;
$a += 100;
$a = $a*10;
return $a;
}
}
Class D{
function d(C $c)
{
$d = 100;
$a = $c->a();
//此处迁移到C类中a方法
//$a = $a*10;
$d +=$a;
return $d;
}
}
六、开闭原则
解释:类、模块和函数应该对扩展开放,对修改关闭