深入理解物件導向,物件導向3個特性7個原則6種關係

小松聊PHP進階發表於2021-04-18
  1. 依照PHP的語法為主,不同的程式語言,有不同的方式,但大體上都是一樣的。
  2. 物件導向的思想在於理解和使用。
  3. 文章所說的介面,都是程式語言語法的介面(interface),而不是前端呼叫的介面(api)。

為什麼會有這麼多的概念?知其所以然。

軟體,是為了解決人類發展的問題,方便人類的工具,如果一個計算機,或者一個軟體只會算數,那麼遇見匯率換算怎麼辦?是不是可以說計算機本身如果沒有太多的固有功能,就無法完成某些事。而現在的軟體,可以幫助人們購物,相親,娛樂。這麼複雜的功能來說,如果軟體思想沒有發展出更高階的思想,它是無法為人類服務的,所以為了可維護性更強,具有更強的功能,必須在計算機軟體程式設計本身上提出一些更高階的概念,用於實現比算加減乘除更復雜的功能,多樣化的規則成了一套實用高效的規範,那麼就是下文看到的各種計算機名詞。

物件導向

面向

“面向”此處可以理解為按照什麼思路去程式設計。

物件

物件是一種依照事物為中心的程式設計思想,萬物皆物件,由實體引發事件,物件是真實存在的,物件也可以理解為是把資料結構和處理它們的方法,組成物件。達到了軟體工程的三個目標,重用性靈活性和擴充套件性。
重用性: 一個類裡面的方法可以多次使用。
靈活性: 可以表現為多型,可重複調動等特點,自由度很高,條條大路通羅馬。
擴充套件性: 多型,繼承都有這個特性,可便於多樣化擴充套件,進行抽離,降低耦合。

可以理解為物件的抽象化概念,分離出相同的特點,並加以歸類。
把相同行為的物件歸納為類,一個人是一個物件,但是多個人可以歸納為一個人類,人類指的不是某一個,而是一個虛擬的實體概念。為什麼有人這個類?因為人有雙手,會用火,有文明,學習能力強等因素,如果想要從人類上說某個人,那就是舉一個例項,也就是例項化。

成員屬性

屬性可以理解資料,資料是資訊和表現形式的載體。依照人為例,人有鼻子有眼,這個就是人的屬性。可以用 來形容。

成員方法

方法可以理解為函式。方法是控制資料的管家。依照人為例,人會說話,會吃飯,這就人的方法。可以用 來形容。

介面

可以理解為一個類中需要指定它需要做什麼,但是不需要去做,起到一個規範示例的作用,就是一個標準,需要其它的類去實現它。例如定義了usb介面的尺寸,大小,資料線連線方法,可以類比成一個介面規範,全世界的usb介面都通用,無論是U盤,充電器線,滑鼠,鍵盤。這些例項都可以根據這個約束規範,去製造東西。介面的使用需要用 實現 兩個字形容。

類的三個特性

封裝: 通過封裝隱藏類的內部方法或者資料,只允許訪問可以訪問的資源,保證安全性。擬人化來說,就是姓名是公開的,銀行密碼的資訊是個人的,PHP可用public protected private去修飾。

繼承: 繼承使類得到泛化,兒子繼承爸爸,可以獲得父級或者父級往上的非私有屬性,或者方法,使用extends關鍵字。

多型: 可實現基於物件型別的動態分派,不同的人做同一件事,得到不同的結果。相親這件事:女生遇到流氓會說滾,遇到帥哥會說麼麼噠。

物件導向場景下程式設計7個原則,宗旨:面向介面程式設計,針對目標。精簡程式碼,降低耦合。靈活分離,減少影響。抽離相同的程式碼,便於複用維護。

單一原則: 一個類就做一件事,職責被完整的封裝在一個類中。不會引起混亂,提高重用性,低耦合的設計可以降低軟體開發後期的維護。

開閉原則: 對修改關閉,對擴充套件開放。因為需求變更,執行環境升級等原因需要改程式碼,如果沒有出現bug,那麼不推薦改原來的程式碼,可以在原基礎上進行擴充。既增加了擴充套件性,又保證原來的邏輯不出問題。所謂的“祖傳程式碼,勿動”,也就是這麼一回事。

里氏代換原則: 通俗講:所有能用到老爸的地方,兒子也能使用。軟體中能夠使用的基類物件,那麼就要做到任何地方能使用子類物件而不受影響。也就是子類能夠替換程式中父類出現的任何地方,並保證邏輯不變和正確,這裡面暗含著不推薦重寫父類的意思。正是因為有了這個標準,才能防止父級一旦修改,就會殃及子級發生故障的情況。這個原則實現了開閉原則的形式,子類就相當於擴充套件。實質上這個原則是要告訴我們,繼承需要注意的問題和遵循的原則。但繼承是增加了父子類的耦合關係,為了解決依賴,可以適當通過聚合,組合,依賴等來解決。

依賴倒轉原則: 就是面向介面程式設計。把共用的可複用的方法放置到抽象類,抽象思維程式設計,或者介面當中,依照抽象耦合的方式是原則的關鍵,目的是降低耦合。高層模組(舉個例子:高層模組就是框架底層的程式碼,依照PHP為例,框架底層的程式碼,好多都是抽象類或者介面)不依賴底層模組,二者依賴於抽象。抽象不應該依賴細節,細節應該依賴抽象,也就是做到細節和非細節的分離,相對於細節的多變性,抽象的東西要穩定的多。抽象為基礎搭建的架構比細節為基礎的架構要穩定的多。舉個例子,功能的大體實現可以使用抽象類,但是細節,可以使用具體的類去實現。這裡所謂的抽象,是用於定製規範和整體架構。
舉個例子:

//這段程式碼適用於傳送郵件,但是要增加傳送微信的功能,是不是就顯得麻煩了?這裡不要抬槓說把“Email $email”去掉,如果專案中大量的業務邏輯已經這樣寫了,把“Email $email”去掉,可能要出問題的。
<?php
class Email {
   public function send() {
        return '傳送電子郵件';
   }
}

class WeChat {
	public function send() {
        return '傳送微信訊息';
   }
}

class Person {
	public function receive(Email $email) {
		return $email->send();
	}
}
$person = new Person();
echo $person->receive(new Email());

再看看 優化的結果,所謂的抽象就是傳送資訊,細節就是傳送資訊的方式和內容,抽象和細節做了分離,降低耦合。

<?php

//介面定義大致的方法,大方向
interface SendMsg {
	public function send();
}

//具體的類去實現細節
class Email implements SendMsg {
   public function send() {
        return '傳送電子郵件';
   }
}

//具體的類去實現細節
class WeChat implements SendMsg {
	public function send() {
        return '傳送微信訊息';
   }
}

//此處降低耦合
class Person {
	public function receive(SendMsg $send_msg) {
		return $send_msg->send();
	}
}


$person = new Person();
//靈活呼叫
echo $person->receive(new Email());
echo $person->receive(new WeChat());

介面隔離原則: 還是為了降低耦合,減少介面類中臃腫的程式碼段,一個類對另一個類的依賴應該建立在最小的介面上,通俗的講就是需要什麼就提供什麼,不需要的就不要提供。比如介面類Test一共有func1,func2,func3,func4,func5 5個方法,A類依賴Test介面 func1,func2,func3三個方法,B類依賴Test介面 func1,func2,func4三個方法。這樣的介面func5就沒有用上,應該做介面拆分,拆分成A類和B類依賴的介面僅僅夠用程度的介面即可,並且增加安全性,這裡的拆,就是具有隔離不需要的程式碼的作用。

<?php
//介面定義了5個方法
interface TestInterface {
   public function func1();
   public function func2();
   public function func3();
   public function func4();
   public function func5();
}

//A類依賴了介面中123三個方法,但func4 和 func5用不上
class A implements TestInterface {
   public function func1() { return '實現了TestInterface介面的func1方法';}
   public function func2() { return '實現了TestInterface介面的func2方法';}
   public function func3() { return '實現了TestInterface介面的func3方法';}
   public function func4() { return '這兩個方法用不上,但是由於PHP語法問題,需要去實現';}
   public function func5() { return '這兩個方法用不上,但是由於PHP語法問題,需要去實現';}
}


//A類依賴了介面中124三個方法,但func3 和 func5用不上
class B implements TestInterface {
   public function func1() { return '實現了TestInterface介面的func1方法';}
   public function func2() { return '實現了TestInterface介面的func2方法';}
   public function func3() { return '這兩個方法用不上,但是由於PHP語法問題,需要去實現';}
   public function func4() { return '實現了TestInterface介面的func4方法';}
   public function func5() { return '這兩個方法用不上,但是由於PHP語法問題,需要去實現';}
}

再看不臃腫的程式碼:

<?php
interface TestInterface_A {
	public function func1();
	public function func2();
	public function func3();
}

interface TestInterface_B {
    public function func1();
    public function func2();
    public function func4();
}

//A類實現了介面中123三個方法,無需依賴額外的方法
class A implements TestInterface_A {
	public function func1() { return '實現了TestInterface介面的func1方法';}
	public function func2() { return '實現了TestInterface介面的func2方法';}
	public function func3() { return '實現了TestInterface介面的func3方法';}
}


//A類實現了介面中124三個方法,無需依賴額外的方法
class B implements TestInterface_B {
    public function func1() { return '實現了TestInterface介面的func1方法';}
    public function func2() { return '實現了TestInterface介面的func2方法';}
    public function func4() { return '實現了TestInterface介面的func4方法';}
}

合成複用原則: 多用組合(has-a),少用繼承(is-a),可以降低類與類之間的耦合程度。遇見額外增加的功能,需要擴充套件,通過關聯,而不是繼承。因為使用繼承,後期改父級程式碼可能會株連子級,引起錯誤。
繼承複用又稱之為白箱複用,組合聚合使用稱之為黑箱複用。

迪米特法則/最少知道原則 一個軟體實體儘可能少的與其它實體發生作用,限制了程式設計中通訊的寬度和深度,不管依賴的類有多麼的複雜,都儘量把邏輯封裝到類內部,除了對外提供public的方法,不對外洩露任何無關資訊。只和朋友通訊,朋友通常是當前類、當前物件、成員屬性,成員方法引數,成員方法返回值這些。其餘的都是陌生人,不可直接呼叫。
法則又可以分為狹義法則和廣義法則。狹義的說法是:類A和類B發生關聯,類B和類C發生關聯,那麼類A和類C是不能直接訪問的,需要通過B。優點是降低耦合,缺點是需要大量的區域性化設計,讓類A可以訪問類C,造成模組間使用效率降低。廣義的說法是:儘量建立鬆耦合的類,控制資訊的過載,類之間的耦合度越低,越有利於複用,但是需要更高的抽象分離思想。

並由此出現了23種設計模式,設計模式用於解決經典場景下的經典問題而出來的通用實用規範。但23種有些並不適用於PHP語言,一旦強制使用,就缺失了弱型別語言的優點。

擴充套件

程式導向

程式導向是依事件為中心,分析出解決問題的步驟,將程式碼分成若干個過程/函式,一步步實現,其中,函式或過程是最小的模組封裝單位,然後呼叫這些函式,可以稱之為方法。所謂的通俗講封裝就是把一堆程式碼括起來。
物件導向也是基於程式導向的思想,物件導向實現,也必定有相應的過程,所以有很多相同點。

過程與函式的區別

過程: 無返回值。
函式: 有返回值。

類之間的6種關係:繼承、依賴、關聯、聚合、組合、泛化、實現

繼承: 兒子繼承爸爸,子類可以使用父類的任何非私有成員方法或成員屬性,且可以隨意擴充套件,並且可以通過子類重寫放寬對資源的訪問修飾,且可以重寫父類非私有的的方法或成員屬性。很好理解,不多解釋。

依賴: 假設有兩個類:A和B,類B的某個成員方法的引數有類A,則類B依賴類A (也就是常說的uses-a,這裡應該是uses-b)。

<?php
class A {
    public function one() {
        return 'one';
    }
}
class B {
    public function two(A $a) {
        return $a->one();
    }
}
$b_obj = new B();
echo $b_obj->two(new A());

關聯: 強依賴關係,當所依賴的類已經成為了類成員的時候,此時會被載入到記憶體當中的,而不是引數的時候(如果是單純的依賴,那麼依賴所在的方法如果不呼叫,就不會發生什麼),關聯有一對多,多對多之分,也有單向雙向之分,比如單向一對多。

<?php
class A {
    public function one() {
        return 'one';
    }
}
class B {
    public $obj_a;
    public function two() {
        $this->obj_a = new A();
        return $this->obj_a->one();
    }
}
$b_obj = new B();
print_r($b_obj->two());

聚合: A和B的關係是整體和區域性的關係(has-a),整體和區域性是可以分開的。DemoDateTime包含了DemoDate和DemoTime,也就是DemoDateTime聚合了DemoDate和DemoTime。

<?php
class DemoDate {
    public function getDate() {
        return date("Y/m/d");
    }
}
class DemoTime {
    public function getTime() {
        return date("H:i:s");
    }
}

class DemoDateTime {
    public function getDateTime() {
        $date_obj = new DemoDate();
        $time_obj = new DemoTime();
        $datetime = $date_obj->getDate() . ' ' . $time_obj->getTime();
        return $datetime;
    }
}
$datetime_obj = new DemoDateTime();
echo $datetime_obj->getDateTime();

組合: 組合關係也是整體和部分的關係,但它是強聚合關係。可以理解為各個部分不能離開整體,離開了就要出問題。

泛化

就是繼承。

實現

實現就是一個抽象類被其它類實現。

面向介面程式設計

通俗講就是多用用介面,把介面作為定義大骨架,來使用,剩下的細節,交給實現介面的類去使用,也就是將定義與實現分離,需要開發者擁有分離的抽象思想。

什麼時候使用抽象類什麼時候使用介面?

(以上內容為原創,以下內容全部來源於網路,然後進行整合,感謝各位網友的回覆。)
介面應有兩類:第一類是對一個體的抽象,它可對應為一個抽象體(abstract class);第二類是對一個體某一方面的抽象,即形成一個抽象面(interface),一個體有可能有多個抽象面。
 
運算元據庫就必須會用到 Insert Update Select ,所以Insert Update Select 做成介面
但是,每個功能操作的內容又不一樣,所以,做一個抽象類繼承介面然後抽象類的派生類去實現抽象類的具體方法。

介面是一組規則的集合,它規定了實現本介面的類或介面必須擁有的一組規則
抽象類和介面的區別在於使用動機。使用抽象類是為了程式碼的複用,而使用介面的動機是為了實現多型性。

如果這個概念在我們腦子中是確確實實存在的,就用抽象類。
否則的話,如果這個概念僅僅是一方面的特性,比如會飛的,能跑的,這些我們就設定為介面。
兩個概念模糊,不知道設定為抽象類還是介面的時候,一般我們設定為介面,原因是我們實現了這個介面還可以繼承。

抽象類適合用來定義某個領域的固有屬性,也就是本質,介面適合用來定義某個領域的擴充套件功能。
當需要為一些類提供公共的實現程式碼時,應優先考慮抽象類。因為抽象類中的非抽象方法可以被子類繼承下來,使實現功能的程式碼更簡單。
當注重程式碼的擴充套件性跟可維護性時,應當優先採用介面。①介面與實現它的類之間可以不存在任何層次關係,介面可以實現毫不相關類的相同行為,比抽象類的使用更加方便靈活;②介面只關心物件之間的互動的方法,而不關心物件所對應的具體類。介面是程式之間的一個協議,比抽象類的使用更安全、清晰。一般使用介面的情況更多。

當描述一組方法的時候使用介面
當描述一個虛擬的物體的時候使用抽象類

抽象類是跟繼承類是“is”的關係,介面和實現類是"like"的關係,從這個角度出發,應該可以看出很多東西。其它的區別只是一些語法,用法規範上的區別,核心思想就是這個is-like區別。

相關文章