PHP代碼簡潔之道——SOLID原則
來源:原創(chuàng) 時間:2017-10-24 瀏覽:0 次SOLID 是Michael Feathers引薦的便于回憶的首字母簡寫,它代表了Robert Martin命名的最重要的五個面臨目標編碼規(guī)劃準則
S: 單一責任準則 (SRP)
O: 開閉準則 (OCP)
L: 里氏替換準則 (LSP)
I: 接口阻隔準則 (ISP)
D: 依靠回轉準則 (DIP)
單一責任準則 Single Responsibility Principle (SRP)
"修正一個類應該只為一個理由"。人們總是易于用一堆辦法塞滿一個類,好像我們在飛機上只能帶著一個行李箱(把一切的東西都塞到箱子里)。這樣做的問題是:從概念上這樣的類不是高內聚的,而且留下了許多理由去修正它。將你需求修正類的次數(shù)降低到最小很重要。這是由于,當有許多辦法在類中時,修正其間一處,你很難知曉在代碼庫中哪些依靠的模塊會被影響到。
Bad:
class UserSettings{
private $user;
public function __construct($user)
{
$this->user = $user;
}
public function changeSettings($settings)
{
if ($this->verifyCredentials()) {
// ...
}
}
private function verifyCredentials()
{
// ...
}
}
Good:
class UserAuth {
private $user;
public function __construct($user){
$this->user = $user;
}
public function verifyCredentials(){
// ...
}
}
class UserSettings {
private $user;
private $auth;
public function __construct($user) {
$this->user = $user;
$this->auth = new UserAuth($user);
}
public function changeSettings($settings){
if ($this->auth->verifyCredentials()) {
// ...
}
}
}
開閉準則 Open/Closed Principle (OCP)
正如Bertrand Meyer所述,"軟件的實體(類, 模塊, 函數(shù),等)應該對擴展敞開,對修正封閉。"這個準則是在闡明應該答應用戶在不改動已有代碼的狀況下添加新的功用。
Bad:
abstract class Adapter{
protected $name;
public function getName(){
return $this->name;
}
}
class AjaxAdapter extends Adapter{
public function __construct(){
parent::__construct();
$this->name = 'ajaxAdapter';
}
}
class NodeAdapter extends Adapter{
public function __construct(){
parent::__construct();
$this->name = 'nodeAdapter';
}
}
class HttpRequester{
private $adapter;
public function __construct($adapter)
{
$this->adapter = $adapter;
}
public function fetch($url)
{
$adapterName = $this->adapter->getName();
if ($adapterName === 'ajaxAdapter') {
return $this->makeAjaxCall($url);
}
elseif ($adapterName === 'httpNodeAdapter') {
return $this->makeHttpCall($url);
}
}
private function makeAjaxCall($url)
{ // request and return promise
}
private function makeHttpCall($url)
{ // request and return promise
}
}
在上面的代碼中,關于HttpRequester類中的fetch辦法,如果我新增了一個新的xxxAdapter類而且要在fetch辦法中用到的話,就需求在HttpRequester類中去修正類(如加上一個elseif 判別),而經過下面的代碼,就可很好的處理這個問題。下面代碼很好的闡明了如安在不改動原有代碼的狀況下添加新功用。
Good:
interface Adapter{
public function request($url);
}
class AjaxAdapter implements Adapter{
public function request($url)
{ // request and return promise
}
}
class NodeAdapter implements Adapter{
public function request($url)
{ // request and return promise
}
}
class HttpRequester{
private $adapter;
public function __construct(Adapter $adapter)
{ $this->adapter = $adapter;
}
public function fetch($url)
{ return $this->adapter->request($url);
}
}
里氏替換準則 Liskov Substitution Principle (LSP)
對這個概念最好的解說是:如果你有一個父類和一個子類,在不改動原有成果正確性的前提下父類和子類能夠交換。這個聽起來讓人有些利誘,所以讓我們來看一個經典的正方形-長方形的比如。從數(shù)學上講,正方形是一種長方形,可是當你的模型經過承繼運用了"is-a"的聯(lián)系時,就不對了。
Bad:
class Rectangle{
protected $width = 0;
protected $height = 0;
public function render($area)
{ // ...
}
public function setWidth($width)
{ $this->width = $width;
}
public function setHeight($height)
{ $this->height = $height;
}
public function getArea()
{ return $this->width * $this->height;
}
}
class Square extends Rectangle{
public function setWidth($width)
{
$this->width = $this->height = $width;
}
public function setHeight(height)
{ $this->width = $this->height = $height;
}
}
function renderLargeRectangles($rectangles){
foreach ($rectangles as $rectangle) {
$rectangle->setWidth(4);
$rectangle->setHeight(5);
$area = $rectangle->getArea(); // BAD: Will return 25 for Square. Should be 20.
$rectangle->render($area);
}
}
$rectangles =
[new Rectangle(), new Rectangle(), new Square()];
renderLargeRectangles($rectangles);
Good:
abstract class Shape{
protected $width = 0;
protected $height = 0;
abstract public function getArea();
public function render($area) { // ...
}
}
class Rectangle extends Shape{
public function setWidth($width)
{ $this->width = $width;
}
public function setHeight($height)
{ $this->height = $height;
}
public function getArea()
{ return $this->width * $this->height;
}
}
class Square extends Shape{
private $length = 0;
public function setLength($length)
{ $this->length = $length;
}
public function getArea()
{ return pow($this->length, 2);
}
}
function renderLargeRectangles($rectangles){
foreach ($rectangles as $rectangle) {
if ($rectangle instanceof Square) {
$rectangle->setLength(5);
} elseif ($rectangle instanceof Rectangle) {
$rectangle->setWidth(4);
$rectangle->setHeight(5);
}
$area = $rectangle->getArea();
$rectangle->render($area);
}
}
$shapes = [new Rectangle(), new Rectangle(), new Square()];
renderLargeRectangles($shapes);
接口阻隔準則
接口阻隔準則:"客戶端不該該被強制去完成于它不需求的接口"。
有一個明晰的比如來闡明演示這條準則。當一個類需求一個很多的設置項,為了便利不會要求客戶端去設置很多的選項,由于在一般他們不需求一切的設置項。使設置項可選有助于我們防止發(fā)生"胖接口"
Bad:
interface Employee{
public function work();
public function eat();
}
class Human implements Employee{
public function work()
{ // ....working
}
public function eat()
{ // ...... eating in lunch break
}
}class Robot implements Employee{
public function work()
{ //.... working much more
}
public function eat()
{ //.... robot can't eat, but it must implement this method
}
}
上面的代碼中,Robot類并不需求eat()這個辦法,可是完成了Emplyee接口,所以只能完成一切的辦法了,這使得Robot完成了它并不需求的辦法。所以在這里應該對Emplyee接口進行拆分,正確的代碼如下:
Good:
interface Workable{
public function work();
}
interface Feedable{
public function eat();
}
interface Employee extends Feedable, Workable{
}
class Human implements Employee{
public function work()
{ // ....working
}
public function eat()
{ //.... eating in lunch break
}
}// robot can only work
class Robot implements Workable{
public function work()
{ // ....working
}
}
依靠回轉準則 Dependency Inversion Principle (DIP)
這條準則闡明兩個根本的關鍵:
高階的模塊不該該依靠低階的模塊,它們都應該依靠于籠統(tǒng)
籠統(tǒng)不該該依靠于完成,完成應該依靠于籠統(tǒng)
這條起先看起來有點不流暢難明,可是如果你運用過php結構(例如 Symfony),你應該見過依靠注入(DI)對這個概念的完成。盡管它們不是徹底相通的概念,依靠倒置準則使高階模塊與低階模塊的完成細節(jié)和創(chuàng)立別離。能夠運用依靠注入(DI)這種方法來完成它。更多的優(yōu)點是它使模塊之間解耦。耦合會導致你難于重構,它是一種十分糟糕的的開發(fā)形式。
Bad:
class Employee{
public function work()