这些原则,结合在一起能够方便程序员开发易于维护和扩展的软件,也让开发人员轻松避免代码异味,易于重构代码,也是敏捷或自适应软件开发的一部分。
五大原则(S.O.L.I.D)都代表什么?
* S - 单一职责原则
* O - 开发封闭原则
* L - 里氏替换原则
* I - 接口隔离原则
* D - 依赖倒置原则
单一职责原则
单一职责原则(简称:S.R.P)指出:一个类应该有且只有一个去改变它的理由,这意味着一个类应该只有一项工作,只完成一个功能。
例如,我们需要去计算一些图形的面积,申明图形类,并提供计算面积方法
<?php
class Circle {
public $radius;
public function __construct($radius)
{
$this->radius = $radius;
}
}
class Square {
public $length;
public function __construct($length)
{
$this->length = $length;
}
}
然后,我们输出面积(我们申明一个输出类):
class AreaOutPut {
protected $shape;
public function __construct($shape)
{
$this->shape = $shape;
}
public function caculator()
{
//求面积的逻辑
}
public function outputResult()
{
echo 'Area is :'.$this->caculator();
echo "<br>";
}
}
我们计算图形面积,直接调用就可以了
$circle_area = new AreaOutPut(new Circle(2));
$circle_area->outputResult();
$square_area = new AreaOutPut(new Square(5));
$square_area->outputResult();
假如我们需要输出的是JSON数据或者其他形式的数据,那该怎么办?如果也写在AreaOutPut类里面,则违反了单一职责原则(SRP),AreaOutPut不仅仅需要计算和,还需要负责用户需要什么形式的数据.
为了解决这个问题,我们需要新建一个类,解决客户端需要何种形式数据的问题.
class OutPutArea {
protected $area;
public function __construct($area)
{
$this->area = $area;
}
public function outputJSON()
{
echo json_encode($this->area->caculator);
}
public function outputXML()
{
//略
}
}
这样就可以可以获得我们需要的形式的数据了
$circle_area = new AreaOutPut(new Circle(2));
$circle_output = new OutPutArea($circle_area);
$circle_output->outputJSON();
$circle_output->outputXML();
开发封闭原则
开发封闭原则指的是:对象或实体应该对扩展开放,对修改封闭。也就是说一个类应该是可以扩展的,而不可修改的
我们看下上面的求面积类,计算方法,我们可以这么写
public function caculator()
{
//求面积的逻辑
if (is_a($this->shape,'Circle')) {
return 3.14 * $this->shape->radius * $this->shape->radius;
} elseif (is_a($this->shape,'Square')) {
return $this->shape->length * $this->shape->length;
}
}
但是如果,我需要新增图形的种类,比如三角形,菱形等等,我们需要添加很多的if else,这就违背了开放封闭原则.
但是我们有一种更好的方式去实现,把计算面积从AreaCalculator中移除,移到图形类中去,像这样:
<?php
class Circle {
public $radius;
public function __construct($radius)
{
$this->radius = $radius;
}
public function getArea()
{
return 3.14 * $this->shape->radius * $this->shape->radius;
}
}
class Square {
public $length;
public function __construct($length)
{
$this->length = $length;
}
public function getArea()
{
return $this->shape->length * $this->shape->length;
}
}
这样看上去是不是就比较科学了,但是我们还可以进一步改进,比如,我们需要保证所有的图形类都有getArea()方法,面对接口编程而不是面对实现编程,
<?php
interface Shape {
public function getArea();
}
class Circle implements Shape {
public $radius;
public function __construct($radius)
{
$this->radius = $radius;
}
public function getArea()
{
return 3.14 * $this->shape->radius * $this->shape->radius;
}
}
class Square implements Shape {
public $length;
public function __construct($length)
{
$this->length = $length;
}
public function getArea()
{
return $this->shape->length * $this->shape->length;
}
}
这样我们就保证了所有的图形类都有方法 getArea(),我们再看下AreaCalculator中的计算方法,就可以这么改进了
public function caculator()
{
//求面积的逻辑
if (is_a($this->shape,'ShapeInterface')) {
return $this->shape->getArea();
}
}
里氏替换原则
里氏替换原则指的是:如果对每一个类型为 X1的对象 y1,都有类型为 X2 的对象y2,使得以 X1定义的所有程序 P 在所有的对象 y1 都代换成y2 时,程序 P 的行为没有发生变化,那么类型 X2 是类型 X1 的子类型。也就是说所有引用基类的地方必须能透明地使用其子类的对象。
通俗的讲,里氏替换原则说的就是:子类可以扩展父类的功能,但不能改变父类原有的功能,包含4层含义:
* 子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法
* 子类中可以增加自己特有的方法
* 当子类的方法重载父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松
* 当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格
接口隔离原则
接口隔离原则指的是:客户端不应该依赖它不需要的接口;一个类对另一个类的依赖应该建立在最小的接口上
以鸟类为例子:定义一个接口类,鸟都有翅膀,都能飞
<?php
interface BirdInterface {
public function hasWing();
public function fly();
}
麻雀,鸵鸟都实现接口:
class Sparrow implements BirdInterface {
public function hasWing()
{
echo ' my has two wing';
}
public function fly()
{
echo 'my is sparrow i can fly';
}
}
class Ostrich implements BirdInterface {
public function hasWing()
{
echo ' my has two wing';
}
public function fly()
{
echo 'my is Ostrich i can\'t fly';
}
}
但是鸵鸟并不会飞,它不需要这个接口.这就违反了接口隔离原则(ISP).
所以我们可以这样:把会飞的特性独立出来,新申明一个接口
<?php
interface BirdInterface {
public function hasWing();
}
interface FlyBirdInterface {
public function fly();
}
class Sparrow implements BirdInterface,FlyBirdInterface {
public function hasWing()
{
echo ' my has two wing';
}
public function fly()
{
echo 'my is sparrow i can fly';
}
}
class Ostrich implements BirdInterface {
public function hasWing()
{
echo ' my has two wing';
}
}
这样就不会违反接口隔离原则了.
依赖倒置原则
依赖反转原则指的是:高层次的模块不应该依赖于低层次的模块,他们都应该依赖于抽象.抽象不应该依赖于具体实现,具体实现应该依赖于抽象.
例如:我们框架比较常见的模型中,定义的驱动类
<?php
class Model {
protected $driver;
public function __construct(MysqlDriver $mysqldriver)
{
$this->driver = $mysqldriver;
}
}
这边我们的Model类是高层次的模块,$mysqldriver是低层次的模块,但是这边是强依赖的,如果我数据库引擎换成了sqlite 则必须修改model 类,这就违反了开放封闭原则.
Model并不需要关心你使用的是什么引擎,为了解决这个问题,我们再一次使面向对接口编程,因为高层次和低层次模块应该依赖于抽象.
我们申明驱动接口:
<?php
interface DbInterface {
public function dbConnect();
}
所有的数据库驱动必须实现驱动接口:
class MysqlDriver implements DbInterface {
public function dbConnect()
{
//mysql的连接
}
}
class SqliteDriver implements DbInterface {
public function dbConnect()
{
//sqllite的连接
}
}
我们再看下我们的Model类:
class Model {
protected $driver;
public function __construct(DbInterface $dbdriver)
{
$this->driver = $mysqldriver;
}
}
这样,无论我们使用的是何种的数据库,只要是实现了接口DbInterface的,就不会影响到我们应用的运行.
S.O.L.I.D刚刚学可能会比较吃力,然后还不太讨好,但是只要坚持下去写,它就可以让你的代码很容易地扩展、修改、测试和重构,对于后期项目的维护和修改帮助是非常大的。