物件導向程式設計,是一種程式設計正規化,也是一種程式語言的分類。它以物件作為程式的基本單元,將演演算法和資料封裝其中,程式可以存取和修改物件關聯的資料。這就像我們在真實世界中操作各種物體一樣,比如我們可以開啟電視、調整音量、切換頻道,而不需要知道電視的內部如何工作。同樣,在物件導向程式設計中,我們可以操作物件,而不需要關心物件的內部結構和實現。
物件導向程式設計的主要組成部分是類和物件。類是一組具有相同屬性和功能的物件的抽象,就好比我們說的「汽車」這個概念,它具有顏色、型號、速度等屬性,有啟動、加速、剎車等功能。而物件則是類的範例,它是具體的,就像你家那輛紅色的賓士車,它就是汽車這個類的一個範例。
物件導向程式設計有三大特性,封裝、繼承和多型。
封裝是把客觀事物封裝成抽象的類,並隱藏實現細節,使得程式碼模組化。比如,我們可以把「汽車」這個客觀事物封裝成一個類,這個類有顏色、型號等屬性,有啟動、加速、剎車等方法,而這些屬性和方法的具體實現則被隱藏起來,使用者只需要知道這個類有哪些屬性和方法,不需要知道這些方法是如何實現的。
繼承是物件導向程式設計的另一個重要特性,它提供了一種無需重新編寫,使用現有類的所有功能並進行擴充套件的能力。比如,我們可以定義一個「電動車」類,它繼承了「汽車」類,就自動擁有了「汽車」類的所有屬性和方法,比如顏色、型號等屬性,啟動、加速、剎車等方法,然後我們還可以在「電動車」類上增加一些新的屬性和方法,比如電池容量、充電方法等。
多型是指同一操作作用於不同的物件,可以有不同的解釋,產生不同的執行結果。比如,我們定義了一個「汽車」類,它有一個「啟動」方法,然後我們又定義了一個「電動車」類,它繼承了「汽車」類,也有一個「啟動」方法,但是「電動車」類的「啟動」方法的實現可能與「汽車」類的不同,這就是多型。
物件導向程式設計有兩個主要的理念,基於介面程式設計和組合優於繼承。
基於介面程式設計的理念是,使用者不需要知道資料型別、結構和演演算法的細節,只需要知道呼叫介面能夠實現功能。這就像我們使用電視遙控器一樣,我們不需要知道遙控器內部的電路設計和工作原理,只需要知道按哪個按鈕可以開啟電視,按哪個按鈕可以調節音量。
基於介面程式設計有很多好處,這裡簡單列幾條。
首先,基於介面程式設計可以提高程式碼的靈活性。因為我們的程式碼不依賴於具體的實現,所以當實現變化時,我們的呼叫程式碼不需要做任何修改。比如有一個程式需要讀取資料,資料可能來自於資料庫、檔案或者網路,無論資料來自哪裡,呼叫方只存取「資料讀取」介面,實現可以根據場景任意調整。
其次,基於介面程式設計可以提高程式碼的可測試性。因為介面只是一個規範,沒有具體的實現,所以我們可以方便地為介面建立模擬物件(Mock Object),這樣就可以在沒有實際環境的情況下進行單元測試。比如說,我們可以建立一個模擬的「資料讀取」介面,讓它返回一些預設的資料,然後我們就可以在沒有資料庫或者檔案的情況下測試我們的程式碼。
最後,基於介面程式設計也可以提高程式碼的可讀性。因為介面清晰地定義了功能,所以只要看介面,就可以知道程式碼應該做什麼,而不需要關心程式碼是怎麼做的。這就像我們使用電視遙控器,我們不需要知道遙控器是怎麼工作的,只需要知道按這個按鈕可以換臺,按那個按鈕可以調節音量。
使用介面有利於抽象、封裝和多型。
儘管繼承可以使我們更容易地重用和擴充套件程式碼,但是如果繼承層次過深、繼承關係過於複雜,就會嚴重影響程式碼的可讀性和可維護性。比如我們修改了基礎類別,就可能影響到繼承它的子類,這會增加迭代的風險。因此,我們更傾向於使用組合而不是繼承。比如,我們可以定義一個「電動車」類,它包含「電池系統」、「制動系統」、「車身系統」、「轉向系統」等元件,而不是繼承「汽車」類。
這裡我們再列舉下組合的幾個好處:
首先,組合可以讓我們的程式碼更加靈活。因為我們可以隨時新增、刪除或者替換元件,而不需要修改元件的內部實現。比如,如果我們想要改變汽車的發動機,只需要換掉髮動機這個元件就可以了,而不需要修改汽車或者發動機的程式碼。
其次,組合可以讓我們的程式碼更容易理解。因為每個元件都是獨立的,有明確的功能,所以我們可以分別理解和測試每個元件,而不需要理解整個系統。
最後,組合可以減少程式碼的複雜性。因為我們不需要建立複雜的類層次結構,所以我們的程式碼會更簡單,更易於維護。
總的來說,「組合優於繼承」是一種程式設計實踐,它鼓勵我們使用更簡單、更靈活的組合,而不是更復雜、更脆弱的繼承。這並不是說繼承是壞的,而是說在許多情況下,組合可能是一個更好的選擇。
具體到程式設計中,很多同學可能使用過控制反轉或者依賴注入,控制反轉就是一種基於介面的組合程式設計思想。在傳統的程式設計模式中,我們通常是在需要的地方建立物件,然後呼叫物件的方法來完成一些任務。但是在使用了控制反轉之後,物件的建立和管理工作不再由我們自己控制,而是交給了一個外部的容器(也就是所謂的平臺),我們只需要在需要的地方宣告我們需要什麼,然後容器會自動為我們建立和注入需要的物件。這就是所謂的依賴注入(Dependency Injection,簡稱DI),它是實現控制反轉的一種方法。
為了讓大家更好理解依賴注入,我這裡貼一個Java的例子,程式基於 Spring Boot 框架。
在這個例子中,我們有一個 MessageService 介面和一個實現類 EmailService。然後我們有一個MessageClient類,它依賴於MessageService來傳送訊息。
首先,定義一個MessageService介面:
public interface MessageService {
void sendMessage(String message, String receiver);
}
然後,建立實現類,在Spring Boot中,我們可以使用@Component或@Service等註解來讓Spring自動建立Bean。然後在需要注入的地方,使用@Autowired註解來自動注入Bean。
我們將MessageService的實現類標記為@Service:
@Service
public class EmailService implements MessageService {
public void sendMessage(String message, String receiver) {
System.out.println("Email sent to " + receiver + " with Message=" + message);
}
}
我們在MessageClient中使用@Autowired來注入MessageService:
@Component
public class MessageClient {
private MessageService messageService;
@Autowired
public MessageClient(MessageService messageService) {
this.messageService = messageService;
}
public void processMessage(String message, String receiver){
this.messageService.sendMessage(message, receiver);
}
}
最後,在主程式中,我們可以直接獲取MessageClient的Bean,而不需要手動建立:
@SpringBootApplication
public class Main {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(Main.class, args);
MessageClient emailClient = context.getBean(MessageClient.class);
emailClient.processMessage("Hello", "[email protected]");
}
}
在這個例子中,Spring Boot會自動掃描@Service和@Component註解的類,並建立對應的Bean。然後在需要注入的地方,Spring Boot會自動找到對應的Bean並注入。
控制反轉是一種非常強大的設計原則,它可以幫助我們寫出更靈活、更易於維護和測試的程式碼。如果你還沒有嘗試過,我強烈建議你試試!
物件導向程式設計有五個基本原則,也被稱為SOLID原則。
單一原則是指一個類應該僅具有隻與他職責相關的東西,這樣可以降低類的複雜度,提高可讀性和可維護性。
這個原則就像是你在廚房裡做飯,你有各種各樣的廚具,每個廚具都有它特定的用途,比如刀用來切菜,鍋用來煮食物,勺子用來攪拌。你不會用刀去攪拌,也不會用勺子去切菜。這樣每個廚具都只負責一項任務,使得廚房的運作更加順暢。
開閉原則是指軟體中的類、屬性和函數對擴充套件是開放的,對修改是封閉的。這樣可以避免對原有程式碼的修改導致的很多工程工作。
這個原則就像是你的房子,你可以在房子裡面新增更多的傢俱,比如椅子、桌子、床等,但你不會去改變房子的結構,比如拆掉牆壁或者增加門窗。這樣你的房子對於新增傢俱是開放的,對於修改結構是關閉的。
在計算機體系中,最符合開閉原則的就是馮諾依曼體系架構,在這個架構中,CPU是封閉的、穩定的,然後通過IO操作對外開放,支援各種無窮無盡的輸入輸出裝置。這是開閉原則的最好最基礎的體現。
里氏替換原則是指子類可以實現父類別的抽象方法,但不能覆蓋父類別的非抽象方法。這樣可以讓高層次模組能夠依賴抽象類,而不是具體的實現。
這個原則就像是你的電視遙控器,無論你的電視是老款的CRT電視,還是新款的LED電視,你都可以用同一個遙控器來控制。這是因為所有的電視都遵循了同樣的介面,即遙控器可以傳送的訊號。所以你可以用新的電視來替換老的電視,而不需要改變遙控器。
介面隔離原則是指類間的依賴關係應該建立在最小的介面之上,這樣可以減少類間的耦合度。
舉個例子,假設我們有一個Animal介面,它包含了eat(), sleep(), fly()等方法。現在我們要設計一個Dog類來實現這個介面,但是狗並不能飛,所以fly()方法對於Dog類來說是不需要的。如果我們按照介面隔離原則來設計,那麼我們可以將Animal介面拆分為AnimalBasic(包含eat()和sleep()方法)和AnimalFly(包含fly()方法)兩個介面,然後讓Dog類只實現AnimalBasic介面,這樣就避免了實現不需要的方法。
依賴反轉原則是指高層次模組不應該依賴於低層次模組的具體實現,兩者都應該依賴其抽象。這樣可以提高程式碼的可延伸性。
舉個例子,假設我們有一個高階模組HighLevelModule和一個低階模組LowLevelModule。HighLevelModule直接依賴於LowLevelModule的具體實現。現在,如果我們遵循依賴反轉原則,我們可以定義一個抽象的介面AbstractModule,然後讓HighLevelModule依賴於AbstractModule,同時讓LowLevelModule也實現AbstractModule。這樣,無論是HighLevelModule還是LowLevelModule,它們都只依賴於抽象,而不再直接依賴於對方的具體實現。這樣就可以提高程式碼的可延伸性和可維護性。
物件導向程式設計的優點主要有兩個:
然而,物件導向程式設計也並非完美,它也有一些缺點,比如:
總的來說,物件導向程式設計是一種強大而靈活的程式設計正規化,它可以幫助我們更好地組織和管理程式碼,提高程式碼的可讀性和可維護性,這使得它特別適合用在大型工程專案中。然而,我們也需要注意其可能帶來的問題,尤其是在並行和複雜系統中。
關注螢火架構,技術提升不迷路!