- 依照PHP的語法為主涮母,不同的編程語言,有不同的方式躁愿,但大體上都是一樣的叛本。
- 面向?qū)ο蟮乃枷朐谟诶斫夂褪褂谩?/li>
- 文章所說的接口,都是編程語言語法的接口(interface)彤钟,而不是前端調(diào)用的接口(api)来候。
為什么會(huì)有這么多的概念?知其所以然逸雹。
軟件营搅,是為了解決人類發(fā)展的問題,方便人類的工具梆砸,如果一個(gè)計(jì)算機(jī)转质,或者一個(gè)軟件只會(huì)算數(shù),那么遇見匯率換算怎么辦帖世?是不是可以說計(jì)算機(jī)本身如果沒有太多的固有功能休蟹,就無法完成某些事。而現(xiàn)在的軟件日矫,可以幫助人們購物赂弓,相親,娛樂哪轿。這么復(fù)雜的功能來說盈魁,如果軟件思想沒有發(fā)展出更高級(jí)的思想,它是無法為人類服務(wù)的窃诉,所以為了可維護(hù)性更強(qiáng)杨耙,具有更強(qiáng)的功能,必須在計(jì)算機(jī)軟件編程本身上提出一些更高級(jí)的概念褐奴,用于實(shí)現(xiàn)比算加減乘除更復(fù)雜的功能按脚,多樣化的規(guī)則成了一套實(shí)用高效的規(guī)范,那么就是下文看到的各種計(jì)算機(jī)名詞敦冬。
面向?qū)ο?/h2>
面向
“面向”此處可以理解為按照什么思路去編程辅搬。
對(duì)象
對(duì)象是一種依照事物為中心的編程思想,萬物皆對(duì)象,由實(shí)體引發(fā)事件堪遂,對(duì)象是真實(shí)存在的介蛉,對(duì)象也可以理解為是把數(shù)據(jù)結(jié)構(gòu)和處理它們的方法,組成對(duì)象溶褪。達(dá)到了軟件工程的三個(gè)目標(biāo)币旧,重用性靈活性和擴(kuò)展性。
重用性: 一個(gè)類里面的方法可以多次使用猿妈。
靈活性: 可以表現(xiàn)為多態(tài)吹菱,可重復(fù)調(diào)動(dòng)等特點(diǎn),自由度很高彭则,條條大路通羅馬鳍刷。
擴(kuò)展性: 多態(tài),繼承都有這個(gè)特性俯抖,可便于多樣化擴(kuò)展输瓜,進(jìn)行抽離,降低耦合芬萍。
類
可以理解為對(duì)象的抽象化概念尤揣,分離出相同的特點(diǎn),并加以歸類柬祠。
把相同行為的對(duì)象歸納為類北戏,一個(gè)人是一個(gè)對(duì)象,但是多個(gè)人可以歸納為一個(gè)人類瓶盛,人類指的不是某一個(gè)最欠,而是一個(gè)虛擬的實(shí)體概念。為什么有人這個(gè)類惩猫?因?yàn)槿擞须p手芝硬,會(huì)用火,有文明轧房,學(xué)習(xí)能力強(qiáng)等因素拌阴,如果想要從人類上說某個(gè)人,那就是舉一個(gè)實(shí)例奶镶,也就是實(shí)例化迟赃。
成員屬性
屬性可以理解數(shù)據(jù),數(shù)據(jù)是信息和表現(xiàn)形式的載體厂镇。依照人為例纤壁,人有鼻子有眼,這個(gè)就是人的屬性捺信∽妹剑可以用 有 來形容。
成員方法
方法可以理解為函數(shù)。方法是控制數(shù)據(jù)的管家秒咨。依照人為例喇辽,人會(huì)說話,會(huì)吃飯雨席,這就人的方法菩咨。可以用 做 來形容陡厘。
接口
可以理解為一個(gè)類中需要指定它需要做什么抽米,但是不需要去做,起到一個(gè)規(guī)范示例的作用雏亚,就是一個(gè)標(biāo)準(zhǔn)缨硝,需要其它的類去實(shí)現(xiàn)它。例如定義了usb接口的尺寸罢低,大小,數(shù)據(jù)線連接方法胖笛,可以類比成一個(gè)接口規(guī)范网持,全世界的usb接口都通用,無論是U盤长踊,充電器線功舀,鼠標(biāo),鍵盤身弊。這些實(shí)例都可以根據(jù)這個(gè)約束規(guī)范辟汰,去制造東西。接口的使用需要用 實(shí)現(xiàn) 兩個(gè)字形容阱佛。
類的三個(gè)特性
封裝: 通過封裝隱藏類的內(nèi)部方法或者數(shù)據(jù)帖汞,只允許訪問可以訪問的資源,保證安全性凑术。擬人化來說翩蘸,就是姓名是公開的,銀行密碼的信息是個(gè)人的淮逊,PHP可用public protected private去修飾催首。
繼承: 繼承使類得到泛化,兒子繼承爸爸泄鹏,可以獲得父級(jí)或者父級(jí)往上的非私有屬性郎任,或者方法,使用extends關(guān)鍵字备籽。
多態(tài): 可實(shí)現(xiàn)基于對(duì)象類型的動(dòng)態(tài)分派舶治,不同的人做同一件事,得到不同的結(jié)果。相親這件事:女生遇到流氓會(huì)說滾歼疮,遇到帥哥會(huì)說么么噠杂抽。
面向?qū)ο髨鼍跋戮幊?個(gè)原則,宗旨:面向接口編程韩脏,針對(duì)目標(biāo)缩麸。精簡代碼,降低耦合赡矢。靈活分離杭朱,減少影響。抽離相同的代碼吹散,便于復(fù)用維護(hù)弧械。
單一原則: 一個(gè)類就做一件事,職責(zé)被完整的封裝在一個(gè)類中空民。不會(huì)引起混亂刃唐,提高重用性,低耦合的設(shè)計(jì)可以降低軟件開發(fā)后期的維護(hù)界轩。
開閉原則: 對(duì)修改關(guān)閉画饥,對(duì)擴(kuò)展開放。因?yàn)樾枨笞兏腔\(yùn)行環(huán)境升級(jí)等原因需要改代碼抖甘,如果沒有出現(xiàn)bug,那么不推薦改原來的代碼葫慎,可以在原基礎(chǔ)上進(jìn)行擴(kuò)充衔彻。既增加了擴(kuò)展性,又保證原來的邏輯不出問題偷办。所謂的“祖?zhèn)鞔a艰额,勿動(dòng)”,也就是這么一回事爽篷。
里氏代換原則: 通俗講:所有能用到老爸的地方悴晰,兒子也能使用。軟件中能夠使用的基類對(duì)象逐工,那么就要做到任何地方能使用子類對(duì)象而不受影響铡溪。也就是子類能夠替換程序中父類出現(xiàn)的任何地方,并保證邏輯不變和正確泪喊,這里面暗含著不推薦重寫父類的意思棕硫。正是因?yàn)橛辛诉@個(gè)標(biāo)準(zhǔn),才能防止父級(jí)一旦修改袒啼,就會(huì)殃及子級(jí)發(fā)生故障的情況哈扮。這個(gè)原則實(shí)現(xiàn)了開閉原則的形式纬纪,子類就相當(dāng)于擴(kuò)展。實(shí)質(zhì)上這個(gè)原則是要告訴我們滑肉,繼承需要注意的問題和遵循的原則包各。但繼承是增加了父子類的耦合關(guān)系,為了解決依賴靶庙,可以適當(dāng)通過聚合问畅,組合,依賴等來解決六荒。
依賴倒轉(zhuǎn)原則: 就是面向接口編程护姆。把共用的可復(fù)用的方法放置到抽象類,抽象思維編程掏击,或者接口當(dāng)中卵皂,依照抽象耦合的方式是原則的關(guān)鍵,目的是降低耦合砚亭。高層模塊(舉個(gè)例子:高層模塊就是框架底層的代碼灯变,依照PHP為例,框架底層的代碼钠惩,好多都是抽象類或者接口)不依賴底層模塊柒凉,二者依賴于抽象。抽象不應(yīng)該依賴細(xì)節(jié)篓跛,細(xì)節(jié)應(yīng)該依賴抽象,也就是做到細(xì)節(jié)和非細(xì)節(jié)的分離坦刀,相對(duì)于細(xì)節(jié)的多變性愧沟,抽象的東西要穩(wěn)定的多。抽象為基礎(chǔ)搭建的架構(gòu)比細(xì)節(jié)為基礎(chǔ)的架構(gòu)要穩(wěn)定的多鲤遥。舉個(gè)例子沐寺,功能的大體實(shí)現(xiàn)可以使用抽象類,但是細(xì)節(jié)盖奈,可以使用具體的類去實(shí)現(xiàn)混坞。這里所謂的抽象,是用于定制規(guī)范和整體架構(gòu)钢坦。
舉個(gè)栗子:
//這段代碼適用于發(fā)送郵件究孕,但是要增加發(fā)送微信的功能,是不是就顯得麻煩了爹凹?這里不要抬杠說把“Email $email”去掉厨诸,如果項(xiàng)目中大量的業(yè)務(wù)邏輯已經(jīng)這樣寫了,把“Email $email”去掉禾酱,可能要出問題的微酬。
<?php
class Email {
public function send() {
return '發(fā)送電子郵件';
}
}
class WeChat {
public function send() {
return '發(fā)送微信消息';
}
}
class Person {
public function receive(Email $email) {
return $email->send();
}
}
$person = new Person();
echo $person->receive(new Email());
再看看 優(yōu)化的結(jié)果绘趋,所謂的抽象就是發(fā)送信息,細(xì)節(jié)就是發(fā)送信息的方式和內(nèi)容颗管,抽象和細(xì)節(jié)做了分離陷遮,降低耦合。
<?php
//接口定義大致的方法垦江,大方向
interface SendMsg {
public function send();
}
//具體的類去實(shí)現(xiàn)細(xì)節(jié)
class Email implements SendMsg {
public function send() {
return '發(fā)送電子郵件';
}
}
//具體的類去實(shí)現(xiàn)細(xì)節(jié)
class WeChat implements SendMsg {
public function send() {
return '發(fā)送微信消息';
}
}
//此處降低耦合
class Person {
public function receive(SendMsg $send_msg) {
return $send_msg->send();
}
}
$person = new Person();
//靈活調(diào)用
echo $person->receive(new Email());
echo $person->receive(new WeChat());
接口隔離原則: 還是為了降低耦合帽馋,減少接口類中臃腫的代碼段,一個(gè)類對(duì)另一個(gè)類的依賴應(yīng)該建立在最小的接口上,通俗的講就是需要什么就提供什么疫粥,不需要的就不要提供茬斧。比如接口類Test一共有func1,func2梗逮,func3项秉,func4,func5 5個(gè)方法慷彤,A類依賴Test接口 func1娄蔼,func2,func3三個(gè)方法底哗,B類依賴Test接口 func1岁诉,func2,func4三個(gè)方法跋选。這樣的接口func5就沒有用上涕癣,應(yīng)該做接口拆分,拆分成A類和B類依賴的接口僅僅夠用程度的接口即可前标,并且增加安全性坠韩,這里的拆,就是具有隔離不需要的代碼的作用炼列。
<?php
//接口定義了5個(gè)方法
interface TestInterface {
public function func1();
public function func2();
public function func3();
public function func4();
public function func5();
}
//A類依賴了接口中123三個(gè)方法只搁,但func4 和 func5用不上
class A implements TestInterface {
public function func1() { return '實(shí)現(xiàn)了TestInterface接口的func1方法';}
public function func2() { return '實(shí)現(xiàn)了TestInterface接口的func2方法';}
public function func3() { return '實(shí)現(xiàn)了TestInterface接口的func3方法';}
public function func4() { return '這兩個(gè)方法用不上,但是由于PHP語法問題俭尖,需要去實(shí)現(xiàn)';}
public function func5() { return '這兩個(gè)方法用不上氢惋,但是由于PHP語法問題,需要去實(shí)現(xiàn)';}
}
//A類依賴了接口中124三個(gè)方法稽犁,但func3 和 func5用不上
class B implements TestInterface {
public function func1() { return '實(shí)現(xiàn)了TestInterface接口的func1方法';}
public function func2() { return '實(shí)現(xiàn)了TestInterface接口的func2方法';}
public function func3() { return '這兩個(gè)方法用不上焰望,但是由于PHP語法問題,需要去實(shí)現(xiàn)';}
public function func4() { return '實(shí)現(xiàn)了TestInterface接口的func4方法';}
public function func5() { return '這兩個(gè)方法用不上缭付,但是由于PHP語法問題柿估,需要去實(shí)現(xiàn)';}
}
再看不臃腫的代碼:
<?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類實(shí)現(xiàn)了接口中123三個(gè)方法,無需依賴額外的方法
class A implements TestInterface_A {
public function func1() { return '實(shí)現(xiàn)了TestInterface接口的func1方法';}
public function func2() { return '實(shí)現(xiàn)了TestInterface接口的func2方法';}
public function func3() { return '實(shí)現(xiàn)了TestInterface接口的func3方法';}
}
//A類實(shí)現(xiàn)了接口中124三個(gè)方法陷猫,無需依賴額外的方法
class B implements TestInterface_B {
public function func1() { return '實(shí)現(xiàn)了TestInterface接口的func1方法';}
public function func2() { return '實(shí)現(xiàn)了TestInterface接口的func2方法';}
public function func4() { return '實(shí)現(xiàn)了TestInterface接口的func4方法';}
}
合成復(fù)用原則: 多用組合(has-a)秫舌,少用繼承(is-a)的妖,可以降低類與類之間的耦合程度。遇見額外增加的功能足陨,需要擴(kuò)展嫂粟,通過關(guān)聯(lián),而不是繼承墨缘。因?yàn)槭褂美^承星虹,后期改父級(jí)代碼可能會(huì)株連子級(jí),引起錯(cuò)誤镊讼。
繼承復(fù)用又稱之為白箱復(fù)用宽涌,組合聚合使用稱之為黑箱復(fù)用。
迪米特法則/最少知道原則 一個(gè)軟件實(shí)體盡可能少的與其它實(shí)體發(fā)生作用蝶棋,限制了編程中通信的寬度和深度卸亮,不管依賴的類有多么的復(fù)雜,都盡量把邏輯封裝到類內(nèi)部玩裙,除了對(duì)外提供public的方法兼贸,不對(duì)外泄露任何無關(guān)信息。只和朋友通信吃溅,朋友通常是當(dāng)前類溶诞、當(dāng)前對(duì)象、成員屬性决侈,成員方法參數(shù)螺垢,成員方法返回值這些。其余的都是陌生人赖歌,不可直接調(diào)用甩苛。
法則又可以分為狹義法則和廣義法則。狹義的說法是:類A和類B發(fā)生關(guān)聯(lián)俏站,類B和類C發(fā)生關(guān)聯(lián),那么類A和類C是不能直接訪問的痊土,需要通過B肄扎。優(yōu)點(diǎn)是降低耦合,缺點(diǎn)是需要大量的局部化設(shè)計(jì)赁酝,讓類A可以訪問類C犯祠,造成模塊間使用效率降低。廣義的說法是:盡量創(chuàng)建松耦合的類酌呆,控制信息的過載衡载,類之間的耦合度越低,越有利于復(fù)用隙袁,但是需要更高的抽象分離思想痰娱。
并由此出現(xiàn)了23種設(shè)計(jì)模式弃榨,設(shè)計(jì)模式用于解決經(jīng)典場景下的經(jīng)典問題而出來的通用實(shí)用規(guī)范。但23種有些并不適用于PHP語言梨睁,一旦強(qiáng)制使用鲸睛,就缺失了弱類型語言的優(yōu)點(diǎn)。
擴(kuò)展
面向過程
面向過程是依事件為中心坡贺,分析出解決問題的步驟官辈,將代碼分成若干個(gè)過程/函數(shù),一步步實(shí)現(xiàn)遍坟,其中拳亿,函數(shù)或過程是最小的模塊封裝單位,然后調(diào)用這些函數(shù)愿伴,可以稱之為方法肺魁。所謂的通俗講封裝就是把一堆代碼括起來。
面向?qū)ο笠彩腔诿嫦蜻^程的思想公般,面向?qū)ο髮?shí)現(xiàn)万搔,也必定有相應(yīng)的過程,所以有很多相同點(diǎn)官帘。
過程與函數(shù)的區(qū)別
過程: 無返回值瞬雹。
函數(shù): 有返回值。
類之間的6種關(guān)系:繼承刽虹、依賴酗捌、關(guān)聯(lián)、聚合涌哲、組合胖缤、泛化、實(shí)現(xiàn)
繼承: 兒子繼承爸爸阀圾,子類可以使用父類的任何非私有成員方法或成員屬性哪廓,且可以隨意擴(kuò)展,并且可以通過子類重寫放寬對(duì)資源的訪問修飾初烘,且可以重寫父類非私有的的方法或成員屬性涡真。很好理解,不多解釋肾筐。
依賴: 假設(shè)有兩個(gè)類:A和B哆料,類B的某個(gè)成員方法的參數(shù)有類A,則類B依賴類A (也就是常說的uses-a,這里應(yīng)該是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());
關(guān)聯(lián): 強(qiáng)依賴關(guān)系转砖,當(dāng)所依賴的類已經(jīng)成為了類成員的時(shí)候听哭,此時(shí)會(huì)被加載到內(nèi)存當(dāng)中的痊银,而不是參數(shù)的時(shí)候(如果是單純的依賴疾宏,那么依賴所在的方法如果不調(diào)用碟案,就不會(huì)發(fā)生什么)兽叮,關(guān)聯(lián)有一對(duì)多萄喳,多對(duì)多之分卒稳,也有單向雙向之分,比如單向一對(duì)多他巨。
<?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的關(guān)系是整體和局部的關(guān)系(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();
組合: 組合關(guān)系也是整體和部分的關(guān)系,但它是強(qiáng)聚合關(guān)系份企∫查可以理解為各個(gè)部分不能離開整體,離開了就要出問題司志。
泛化
就是繼承甜紫。
實(shí)現(xiàn)
實(shí)現(xiàn)就是一個(gè)抽象類被其它類實(shí)現(xiàn)。
面向接口編程
通俗講就是多用用接口骂远,把接口作為定義大骨架囚霸,來使用,剩下的細(xì)節(jié)激才,交給實(shí)現(xiàn)接口的類去使用拓型,也就是將定義與實(shí)現(xiàn)分離,需要開發(fā)者擁有分離的抽象思想瘸恼。
什么時(shí)候使用抽象類什么時(shí)候使用接口劣挫?
(以上內(nèi)容為原創(chuàng),以下內(nèi)容全部來源于網(wǎng)絡(luò)东帅,然后進(jìn)行整合压固,感謝各位網(wǎng)友的回復(fù)。)
接口應(yīng)有兩類:第一類是對(duì)一個(gè)體的抽象靠闭,它可對(duì)應(yīng)為一個(gè)抽象體(abstract class)邓夕;第二類是對(duì)一個(gè)體某一方面的抽象,即形成一個(gè)抽象面(interface)阎毅,一個(gè)體有可能有多個(gè)抽象面。
操作數(shù)據(jù)庫就必須會(huì)用到 Insert Update Select 点弯,所以Insert Update Select 做成接口
但是扇调,每個(gè)功能操作的內(nèi)容又不一樣,所以抢肛,做一個(gè)抽象類繼承接口然后抽象類的派生類去實(shí)現(xiàn)抽象類的具體方法狼钮。
接口是一組規(guī)則的集合碳柱,它規(guī)定了實(shí)現(xiàn)本接口的類或接口必須擁有的一組規(guī)則
抽象類和接口的區(qū)別在于使用動(dòng)機(jī)。使用抽象類是為了代碼的復(fù)用熬芜,而使用接口的動(dòng)機(jī)是為了實(shí)現(xiàn)多態(tài)性莲镣。
如果這個(gè)概念在我們腦子中是確確實(shí)實(shí)存在的,就用抽象類涎拉。
否則的話瑞侮,如果這個(gè)概念僅僅是一方面的特性,比如會(huì)飛的鼓拧,能跑的半火,這些我們就設(shè)置為接口。
兩個(gè)概念模糊季俩,不知道設(shè)置為抽象類還是接口的時(shí)候钮糖,一般我們?cè)O(shè)置為接口,原因是我們實(shí)現(xiàn)了這個(gè)接口還可以繼承酌住。
抽象類適合用來定義某個(gè)領(lǐng)域的固有屬性店归,也就是本質(zhì),接口適合用來定義某個(gè)領(lǐng)域的擴(kuò)展功能酪我。
當(dāng)需要為一些類提供公共的實(shí)現(xiàn)代碼時(shí)消痛,應(yīng)優(yōu)先考慮抽象類。因?yàn)槌橄箢愔械姆浅橄蠓椒梢员蛔宇惱^承下來祭示,使實(shí)現(xiàn)功能的代碼更簡單肄满。
當(dāng)注重代碼的擴(kuò)展性跟可維護(hù)性時(shí),應(yīng)當(dāng)優(yōu)先采用接口质涛。①接口與實(shí)現(xiàn)它的類之間可以不存在任何層次關(guān)系稠歉,接口可以實(shí)現(xiàn)毫不相關(guān)類的相同行為,比抽象類的使用更加方便靈活;②接口只關(guān)心對(duì)象之間的交互的方法汇陆,而不關(guān)心對(duì)象所對(duì)應(yīng)的具體類怒炸。接口是程序之間的一個(gè)協(xié)議,比抽象類的使用更安全毡代、清晰阅羹。一般使用接口的情況更多。
當(dāng)描述一組方法的時(shí)候使用接口
當(dāng)描述一個(gè)虛擬的物體的時(shí)候使用抽象類
抽象類是跟繼承類是“is”的關(guān)系教寂,接口和實(shí)現(xiàn)類是"like"的關(guān)系捏鱼,從這個(gè)角度出發(fā),應(yīng)該可以看出很多東西酪耕。其它的區(qū)別只是一些語法导梆,用法規(guī)范上的區(qū)別,核心思想就是這個(gè)is-like區(qū)別。