物件導向程式設計入門 - Janos Pasztor
你已經程式設計了一段時間,你仍然難以接受物件導向程式設計的實際情況?那麼這可能是你的指南。我們將偏離傳統的解釋,並尋找一種解釋OOP的新方法。
我們馬上就會從一些程式碼開始。請記住,本文中的示例是用Java-esque表示法編寫的,但是所有內容都可以輕鬆應用於任何OOP程式語言,無論是PHP,Python還是Go。
那麼,讓我們來看一個類:
class Student { string name; } |
一個類就像一個藍圖(banq注:blueprint是藍圖 原圖 計劃大綱,做事之前的計劃和主觀觀念)。如果您想獲取此藍圖並建立實際例項,請按以下步驟操作:
Student myStudent = new Student(); myStudent.name = "Janos"; |
myStudent變數現在包含一個Student類的副本,其name變數設定為我的名字。您也可以輕鬆建立第二個副本:
Student myStudent = new Student(); myStudent.name = "Janos"; Student yourStudent = new Student(); yourStudent.name = "Your name"; |
如果執行此程式碼,這兩個例項將存在於不同的記憶體空間中; 你可以獨立修改它們。
到現在為止還挺好?好吧,那就讓我們做一些更先進的事情吧。到目前為止我們所做的是建立一個資料結構。您可以根據需要新增任意數量的變數,但這不僅僅是一種組織資料的方法。讓我們透過向我們的類新增方法來改變它。
class Student { string name; void setName(string name) { this.name = name; } } |
如您所見,該setName方法設定名稱變數。你問為什麼要這麼做?讓我們假設您想要檢查名稱是否為空的情況:
class Student { string name; void setName(string name) { if (name == "") { throw new InvalidArgumentException("The name must not be empty!"); } this.name = name; } } |
這很不錯,但正如你所看到的,這個name領域仍然可以解開。我們需要一些方法來強制執行name欄位的初始化。這就是建構函式發揮作用的地方。
建構函式是一個特殊函式,在例項化類時執行。通常,它與類共享名稱,但在某些語言(如PHP)中,它具有不同的名稱。建立建構函式就像建立方法一樣簡單:
class Student { string name; Student(string name) { this.setName(name); } void setName(string name) { if (name == "") { throw new InvalidArgumentException("The name must not be empty!"); } this.name = name; } } Student myStudent = new Student("Janos"); |
換句話說,您在例項建立時輸入的引數自動最終作為建構函式的引數。
保衛資料!(封裝)
如您所見,我們將資料繫結到功能。一個好的類可以讓你使用它,而不必知道它是如何實現的或如何在內部儲存資料。但是,我們有一個小問題。透過當前設定,我們可以透過name直接設定變數來輕鬆繞過驗證邏輯:
Student myStudent = new Student("Janos"); myStudent.name = ""; |
幸運的是,大多數OOP語言為我們提供了禁用對成員變數和方法的外部訪問的工具,稱為 可見性關鍵字。通常,我們會區分這些級別的可見性:
- public:每個人都可以訪問使用此關鍵字標記的方法。在大多數OOP語言中,這是預設語言。
- protected:只有子類可以訪問方法或變數(我們稍後會討論這個)。
- private:只有當前類可以訪問方法或變數。(注意,同一類的其他例項通常也可以訪問它!)
使用這些關鍵字,我們可以使程式碼更安全:
class Student { private string name; public void setName(string name) { if (name == "") { throw new InvalidArgumentException("The name must not be empty!"); } this.name = name; } } |
如果我們現在嘗試name直接訪問成員變數,我們將從編譯器中得到錯誤。除此之外,具有明確的可見性標記使我們的程式碼更具描述性並且更易於閱讀。
作為一般規則,您不應該將類用作函式容器; 它應該用於儲存與類相關的資料。
類協作
要使OOP有用,您將需要多個類。如果您正在編寫顯示文章的網站,您可能希望擁有一個可以從資料庫中獲取原始資料的類。您還需要一個將原始資料(例如markdown或LaTeX)轉換為輸出格式的類,例如HTML或PDF。
作為一種自然的方法,我們可以做這樣的事情:
class HTMLOutputConverter { private MySQLArticleDatabaseConnector db; public HTMLOutputConverter() { this.db = new MySQLArticleDatabaseConnector(); } } |
如您所見,HTMLOutputConverter依賴於MySQLArticleDatabaseConnector我們在輸出轉換器的建構函式中建立資料庫聯結器的例項。為什麼這麼糟糕?
- 你不能用不同的類替換MySQLArticleDatabaseConnector。
- 由於MySQLArticleDatabaseConnector建立了例項HTMLOutputConverter,該類需要知道需要傳遞給的所有引數MySQLArticleDatabaseConnector。
- 在檢視類定義時,依賴性不會立即顯現出來。你必須檢視程式碼才能發現存在依賴關係。
讓我們看看我們是否可以做得更好。我們不是建立例項MySQLArticleDatabaseConnector,而是將其作為引數請求。像這樣的東西:
class HTMLOutputConverter { private MySQLArticleDatabaseConnector db; public HTMLOutputConverter(MySQLArticleDatabaseConnector db) { this.db = db; } } |
此構造稱為依賴注入。您依賴的類是注入的,而不是直接建立的。必須手動建立依賴項似乎很麻煩,但有一些工具可以幫助您; 它們被稱為依賴注入容器。值得注意的例子包括Google Guice, Auryn和Python DIC。
依賴注入解決了第二和第三個問題,但沒有解決第一個問題。所以讓我們建立一個新的語言結構並將其稱為介面。介面將描述類需要實現的方法,而不實際指定它們的程式碼。像這樣的東西:
interface ArticleDatabaseConnector { public Article getArticleBySlug(string slug); } |
因此,介面將描述類可以實現的功能。在這種情況下,我們將描述一個接受slug引數並返回Article一個響應的方法。您可以像這樣編寫實現類:
class MySQLArticleDatabaseConnector implements ArticleDatabaseConnector { public Article getArticleBySlug(string slug) { //Query data from the MySQL database and return the article object. } } |
如您所見,MySQLArticleDatabaseConnector實現ArticleDatabaseConnector,需要實現其行為。這使我們能夠修改HTMLOutputConverter依賴於介面,而不是實際的實現:
class HTMLOutputConverter { private ArticleDatabaseConnector db; public HTMLOutputConverter(ArticleDatabaseConnector db) { this.db = db; } } |
由於HTMLOutputConverter依賴於介面,我們可以自由地為我們喜歡的介面建立任何實現,無論是MySQL,Cassandra還是Google Cloud SQL。當一個抽象可以有許多形式,許多實際實現時,我們稱之為多型。
當我們以這樣的方式使用多型時,你的類依賴於抽象而不是具體,我們也將它稱為依賴性反轉,以突出我們已經顛倒了依賴性的事實。
但這只是花哨的極客 - 說的是你不應該把你的類焊接在一起。您可以將介面想象為實現它的類與使用它的類之間的契約。此契約描述了實現類必須提供的功能,並且使用類可以依賴於它。
讓我們概括介面!(抽象)
您可能已經猜到,介面並不是建立抽象的唯一工具。事實上,介面被發明為Java中的一種解決方法,用於稱為多重繼承。這帶來了一個顯而易見的問題:什麼是繼承?
讓我們想象一下,當強制具體指定某個特定行為是不夠的時候,你真的想要將“一些程式碼”傳遞給你現有的抽象。實際上,您的抽象必須為您提供實現“某些方法”的可能性,這些方法介面顯然無法實現。
這就是繼承發揮作用的地方。從本質上講,繼承意味著如果一個類Foo擴充套件了類Bar,它將繼承它的所有方法和變數。換句話說,您可以編寫如下程式碼:
class Bar { protected string baz; } class Foo extends Bar { public void setBaz(string baz) { this.baz = baz; } } |
如您所見,子類宣告瞭一個方法,該方法設定從父類繼承的變數,這是可能的,因為可見性rules(protected)允許它。如果我們將變數設定baz為private,則此程式碼將不起作用。
有趣的是,在上面的例子中,你可以例項化Bar和Foo。如果你想限制它,你必須宣告Bar該類abstract。除此之外,您還可以新增沒有正文的抽象方法,並且必須由子類實現:
abstract class Bar { protected string baz; abstract void setBaz(string baz); } class Foo extends Bar { public void setBaz(string baz) { this.baz = baz; } } |
那麼一個類可以有多少父類?一?二?五?答案是:這取決於。有些語言,比如C ++,已經解決了多重繼承的問題。因此,介面語言構造甚至不存在於C ++中。其他語言,如Java或PHP,決定不處理這個問題,而是發明介面。換句話說,介面只是抽象類,只有抽象方法,沒有變數來規避必須解決多重繼承。
謹防錯誤的抽象!許多OOP教程都帶有從矩形繼承的正方形的例子。這僅在數學意義上是正確的。在程式設計中,您希望子類的行為與它們的父類相同,因為矩形具有兩個獨立的邊,而正方形則沒有。
避免全域性狀態
某些語言(如Java)引入了一個名為的特殊關鍵字static。它顛倒了這樣一個事實,即每個例項都有自己的記憶體空間,並在所有例項中建立共享記憶體空間。有很多種方法可以使用它。
一個值得注意的例子是單例模式:
class DatabaseConnection { private static DatabaseConnection instance; public static DatabaseConnection getInstance() { if (!self::instance) { self::instance = new DatabaseConnection(); } return self::instance; } } DatabaseConnection db = DatabaseConnection::getInstance(); |
第一次呼叫時getInstance,將建立一個例項。任何進一步的呼叫都將返回該初始例項。
使用static的問題在於它建立了一個有時隱藏的全域性狀態。您無法建立一個真正獨立的類例項,這使得測試和其他操作變得棘手。
通常,您應該儘可能避免全域性狀態。雖然靜態不是建立全域性狀態的唯一方法,但它是最相關的方式之一。如果可能的話,最好避免使用靜態,並且如上所述進行依賴注入。
提示: static確實有一些合法的用途,但一般來說,應始終考慮替代方案。
類責任
擁有狀態的物件與經典的基於函式的程式設計不同,您只需傳遞資料。學習OOP時,儘量避免使物件成為純函式容器,並將資料與功能整合。
但是,在建立類時,請始終考慮其責任。雖然很容易將與一項任務相關的所有內容都放入類中,但這樣做可能並不明智。如果你有學生管理軟體,你可能會想做這樣的事情:
class Student { private string id; private string name; public void setId(string id) { ... } public void setName(string name) { ... } public void save() { ... } } |
正如您在此場景中所看到的那樣,處理學生資料並將其儲存到某種型別的資料庫將屬於同一個類。實際上,這些是完全獨立的兩個任務,並且沒有任何業務存在。雖然我們不會在本文中詳細介紹,但建議您將課程簡潔明瞭並專注於單個任務。
未來的步驟
這些只是最基本的OOP概念。實際上,人們可以遵循許多想法和設計模式,但很少有程式設計師可以用心命名。不要害怕嘗試,更重要的是,不要害怕失敗。OOP和編寫可維護程式碼一般都很難,所以在對結果感到滿意之前,您可能需要嘗試幾次。在以後的文章中,我們將詳細介紹可以幫助您編寫更好,更易維護的程式碼的概念和想法,因此請務必保持關注。
相關文章
- Python基礎入門(6)- 物件導向程式設計Python物件程式設計
- 全網最適合入門的物件導向程式設計教程:00 物件導向設計方法導論物件程式設計
- C#快速入門教程(1)——物件導向程式設計C#物件程式設計
- 全網最適合入門的物件導向程式設計教程:01 物件導向程式設計的基本概念物件程式設計
- 程式設計師程式設計入門,物件導向需要知道這6點!程式設計師物件
- 物件導向程式設計物件程式設計
- 清潔程式碼:職責 — Janos Pasztor
- Python物件導向程式設計Python物件程式設計
- 程式設計思想 物件導向程式設計物件
- js物件導向程式設計JS物件程式設計
- 十三、物件導向程式設計物件程式設計
- 十六、物件導向程式設計物件程式設計
- Python 物件導向程式設計Python物件程式設計
- JS物件導向程式設計(一):物件JS物件程式設計
- Cookie Cutter架構 - Janos PasztorCookie架構
- 從零學Python:第十六課-物件導向程式設計入門Python物件程式設計
- 物件導向程式設計C++物件程式設計C++
- Python OOP 物件導向程式設計PythonOOP物件程式設計
- python技能--物件導向程式設計Python物件程式設計
- javascript:物件導向的程式設計JavaScript物件程式設計
- JS物件導向的程式設計JS物件程式設計
- Javascript 物件導向程式設計(一)JavaScript物件程式設計
- Javascript 物件導向程式設計(二)JavaScript物件程式設計
- Javascript 物件導向程式設計(三)JavaScript物件程式設計
- 06 物件導向程式設計 (續)物件程式設計
- Python物件導向程式設計(1)Python物件程式設計
- Scala的物件導向程式設計物件程式設計
- Python - 物件導向程式設計 - super()Python物件程式設計
- Python - 物件導向程式設計 - @propertyPython物件程式設計
- JavaScript物件導向程式設計理解!JavaScript物件程式設計
- Python入門教程100天:Day08-物件導向程式設計基礎Python物件程式設計
- python-物件導向入門Python物件
- JavaScript-設計模式-物件導向程式設計JavaScript設計模式物件程式設計
- JavaScript設計模式之物件導向程式設計JavaScript設計模式物件程式設計
- 史上最全 Python 物件導向程式設計Python物件程式設計
- 淺談PHP物件導向程式設計PHP物件程式設計
- JS物件導向程式設計(三):原型JS物件程式設計原型
- [筆記]物件導向的程式設計筆記物件程式設計