小白理解——封裝繼承多型
一、封裝
是什麼:首先是抽象,把事物抽象成一個類,其次才是封裝。對外表示為一個物件,隱藏物件的屬性和動作實現的細節,僅對外公開介面。
為什麼:對外簡化程式設計。高內聚低耦合使用者不需要了解具體的實現細節,而是要通過外部介面,一特定的訪問許可權來使用類的成員(包括成員函式和成員變數)。對內保護資料安全,使用者不可隨意修改屬性值。此外,封裝符合面向物件設計原則的第一條:單一性原則,一個類把自己該做的事情封裝起來,當內部的邏輯發生改變時,外部呼叫不用因此而修改。
怎麼做:基本要求是把所有的成員變數(物件的屬性)私有化。對每個屬性提供get和set方法。
補充:與C++相比,Java的封裝更徹底(C++向後相容C,而C是面向過程的程式設計,所以C++允許把函式和變數寫在類外,而java不允許)。Java成員函式的實現是直接寫在class裡面的(不管這個成員函式的實現有多複雜),而C++是在class裡面寫簡短的函式宣告,然後在class外面寫函式具體的實現。當然C++也可以在類內實現,只是當時老師上課要求統一寫在外面。在class裡只寫宣告,在類外通過作用域符::寫實現。
師兄補充:程式設計師封裝類的好壞的評定標準:職責單一、擴充套件性、執行結果是否清晰;
簡言之,封裝就是無需暴露細節,暴露該暴露的,隱藏該隱藏的,讓任意兩者之間的耦合恰當。(
二、繼承
是什麼:當兩個類具有相同的特徵(屬性,比如售票員和乘客都是“人”)和行為(方法,比如售票員和乘客都要吃飯和睡覺)時,可以將相同的部分抽取出來放到一個類中作為父類(上面的例子中,“人”就是父類),其他兩個類繼承這個父類。繼承後子類自動擁有了父類的屬性和方法,目的是實現功能的擴充套件,子類也可以複寫父類的方法,即方法的重寫(這裡穿插著多型的內容)。子類不能訪問父類中訪問許可權為private的成員變數和方法。子類可以重寫父類的方法,即命名與父類同名的成員變數。
為什麼:繼承可以重複使用程式碼,降低冗餘。程式碼重用是一點,最重要的一點是可以向上轉型(將匯出類看為它的基類的過程),這是Java面向物件最重要的特性——多型的基礎。
怎麼做:子類繼承父類所有,只是訪問受約束。通過關鍵字extends繼承。
補充:java類可以分為三類
1.類:使用class定義,沒有抽象方法
2.抽象類(不能被例項化 比如“水果”是一個抽象類,但無法例項化它,拿不出一個具體的東西):只能被繼承用abstract class定義
3.介面(對抽象類進一步的抽象,比如飛機和鳥都會飛 “非”就可以被設計成一個介面):使用interface定義,只能有抽象方法,所有的屬性都是static final來定義的。
師兄補充:簡單地說就是為了程式碼複用,為多型提供基礎。
三、多型
是什麼:首先要知道方法的唯一性標識是方法名和引數(引數個數 型別 順序)。同一操作作用於不同的物件,可以有不同的解釋,產生不同的執行結果,這就是多型性。
多型可以說是“一個介面,多種實現”或者說是父類的引用變數可以指向子類的例項,被引用物件的型別決定呼叫誰的方法。
為什麼:程式的可擴充套件性及可維護性增強。這裡我覺得可以類比於C++中的模板類來理解,針對使用者輸入引數的個數和型別來執行相應的操作。我看其他論壇上也有觀點是多型為了介面重用(以我現在的理解來看,就是子類可以複用父類的方法)。
怎麼做:首先java實現多型需要有三個條件:繼承(在多型中必須存在有繼承關係的子類和父類)、重寫(子類對父類的某些方法進行重新定義,在呼叫這些方法時就會呼叫子類的方法)、向上轉型。
具備以上的前提條件後,實現多型的方法(C++中實現多型的方法有函式重寫…重寫是指子類重新定義父類虛擬函式的方法…與過載…指允許存在多個同名函式,而這些函式的引數列表不同 …)有編譯時多型(方法的過載)和執行時多型(繼承時方法的重寫)。
編譯時多型很好理解,就是編譯器在編譯的時候會根據函式不同的引數表,對同名函式的不同名稱做不同的修飾,這些同名函式就成了不同的函式。也正是因為這一點,師兄說函式過載不算多型。
執行時多型依賴於繼承、重寫和向上轉型(簡單的理解為,匯出類可以看成它的基類)。基於繼承實現多型:
public class Toilet{
public void toilet(){
system.out.print("廁所")
}
}
public class M extend Toilet{
public void toilet(){
system.out.print("男廁")
}
}
public class W extend Toilet{
public void toilet(){
system.out.print("女廁")
}
}
class A {
public static void main(String[] args){
Toilet mToilet = new M(); //父類引用指向子類物件
Toilet wToilet = new W(); //父類引用指向子類物件
mToilet.toilet(); //執行M的toilet()方法
wToilet.toilet(); //執行W的toilet()方法
}
}
程式執行結果: 男廁女廁
程式碼連結:https://www.jianshu.com/p/1d6219ad43a1
程式碼來源:簡書
基於介面實現多型:
public interface Toilet{
public void toilet();
}
//介面實現類
public class M implement Toilet{
public void toilet(){ system.out.print("男廁")
}
}
//介面實現類
public class W implement Toilet{
public void toilet(){
system.out.print("女廁")
}
}
class A{
public void static main(String[] args){
Toilet mToilet = new M(); //介面引用變數指向介面實現類物件
Toilet wToilet = newW(); //介面引用變數指向介面實現類物件
mToilet.toilet();
wToilet.toilet();
}
}
程式執行結果: 男廁女廁
程式碼連結:https://www.jianshu.com/p/1d6219ad43a1
程式碼來源:簡書
很棒的例子:
class Human {
public void fun1() {
System.out.println("Human fun1");
fun2();
}
public void fun2() {
System.out.println("Human fun2");
}
}
class Programmer extends Human {
//函式的過載
public void fun1(String name) {
System.out.println("Programmer's fun1");
}
//函式的重寫
public void fun2() {
System.out.println("Programmer's fun2");
}
}
public class Test {
public static void main(String[] args) {
Human human = new Programmer();//出現“型別不匹配時”,判斷口訣:變數多型看左邊,方法多型看右邊,靜態多型看左邊
human.fun1();//沒有對應的fun1()函式所以向上轉型human型別,執行human中的fun1()函式。而funl()中含有fun2(),而此函式在子類中被重寫了,所以執行子類的fun2(); }
/*
* Output:
* Human fun1
* Programmer's fun2
*/
}
師兄補充:跳出多型的細節,從整體上看多型就是一種表現形式有多種結果呈現。
四、結語
剛剛告訴師兄我的作業名“多型繼承封裝”的時候,師兄告訴我說,我說反了。我聽了之後當時還沒有意識到這樣的順序有什麼問題,但是經過我週末的總結之後,坐在工位上恍然大悟。封裝->繼承->多型這不單單是三個名詞的簡單羅列,更是一步步遞進的關係。對於面向物件的程式設計,封裝是基礎,繼承是多型的前提條件,三者邏輯順序不可顛倒。
這三個思想是設計模式的基礎,也是整個Java的基礎,值得學而時習之。