面向對象再探究

在還不清楚怎樣面向對象?一文中,已經簡單介紹了面向對象的基本思想和三大特性,但是不夠詳細。本文再來具體探究一下面向對象

1. 概述

1.1. 面向過程編程

面向過程編程(Procedure Oriented Programming,POP)是一種以過程為中心的編程思想,開發人員在解決問題時更專註於過程

當我們遇到一個問題時,只需要分析出第1步要做什麼、第2步要做什麼……直到解決問題,然後把這些步驟一步步地實現即可。

比如,我現在要編寫一個面向過程的程序來模擬「我要讓我的好朋友行小觀去幫我買瓶水」這個問題,如下:

  1. 給行小觀10塊錢
  2. 告訴行小觀去哪買水
  3. 行小觀找到我要的那瓶水
  4. 付錢,找零錢
  5. 把水帶回來給我

在從「給行小觀錢」到「行小觀把水給我」的整個過程,我和行小觀都專註於完成每一個步驟(事件),行小觀只是一個執行我事先描述好的步驟的無思想的工具人而已。

如果我還有其他問題需要行小觀幫忙,那麼我就還得把如何完成這些問題的詳細步驟全都告訴他,這麼麻煩那我還找別人幫忙干什麼呢?還不如我自己去做。而且有些問題我自己也不會做,那怎麼辦?所以行小觀並不是一個合格的工具人。

1.2. 面向對象編程

與面向過程編程不同,面向對象編程(Object Oriented Programming,OOP)是一種以對象為中心的編程思想,對象是現實世界中的一個個事物的實體。

對象包含了用戶可以使用(公開)的功能部分和對用戶隱藏的實現部分。

在開發過程中,我們可以使用功能部分來解決問題,但是並不關心功能是怎樣實現的。

還是上面的那個買水的例子,使用面向對象來實現:

  1. 我給行小觀10塊錢,讓他幫我買瓶水
  2. 行小觀把水和零錢帶回來給我

在這個例子中,我只需要給行小觀錢,然後他就能幫我買水。我相信我請行小觀有「買水的能力」,他一定能幫我買到水。至於去哪買?怎麼買?行小觀自己知道,我並不關心他是怎麼買到水的,因為我的目的很簡單:「我想要一瓶水」。

如果我有其他問題需要行小觀幫忙,無論這些問題我會不會做,直接告訴他就好了,他會幫我完成。

我只專註於問題本身,具體的操作我並不關心。現在行小觀是一個合格的工具人了。

2. 類和對象

2.1. 二者之間的關係

這裏通過一個大家耳熟能詳的神話——女媧造人,來說明類和對象之間的關係。

相傳女媧以泥土仿照自己摶土造人,創造並構建人類社會。

在這個神話里,「女媧」是一個藍圖、模板,「人」是依據該藍圖被創造出來的個體。

「女媧」可以看做類(Class),「人」可以看做對象(Object)

跳出神話,來到真實世界。

我們目能所及的事物都可以看做是「對象」,比如說你用的桌子、坐的椅子、玩的電腦、養的狗……這些一個個真實存在,你能摸到的物品都是對象。

狗有千千萬……高的、矮的、胖的、瘦的、黑色的、白色的等各不相同,但是總能在這些不同的狗之中找到相同的特性,這些不同品種的狗我們把它統稱為「狗」。「狗」即為類,而我們養的真實存在的狗為對象。

總結一下:

  • 類是對一類具有共同特徵的事物的抽象,是一類事物的統稱,是一個抽象概念(比如“人類”這個名詞)。

  • 對象是這類事物相對應的具體存在的實體,是一個具體存在的事物(比如“行小觀”這個具體的人)。

  • 類是創建單個對象時的藍圖、模板。

2.2. 類

當我們說到「狗」這個類的時候,會很自然地想到和狗相關的一些特點和習性。

比如,名字、品種、顏色、年齡等,這些是屬性

還有,吠叫、看門等,這些是行為

一個類包括了屬性和行為,行為可以操縱屬性。

對應到代碼中,屬性即為成員變量,行為即為成員方法,成員方法可以操縱成員變量。

下面是一個具體的類:

程序2-1
/**
 * 狗類
 * @author Xing Xiaoguan
 */

public class Dog {
    //屬性——成員變量
    String name;
    int age;
    int legs;

    //行為——成員方法
    //行為——吠叫
    public void say() {
        System.out.println("我是" + name + "汪汪汪");
    }

    //行為——看門
    public void watchDoor() {
        System.out.println("趕走陌生人");
    }
}

2.3. 對象

類是一個抽象概念,而對象則是一個具體的實例。

以狗為例,我們養的不可能是「一類狗」,而是在和一隻「具體的狗」玩耍,比如說哮天犬。

回到女媧(類)造人(對象)這個神話中,人是以女媧為模板被造出來的,女媧造人的過程,即由類構造對象的過程稱為創建類的實例(instance)。

程序2-2
public static void main(String[] args) {
   Dog dog = new Dog();//創建類的實例,對象dog
   dog.name = "哮天犬";
   dog.age = 2;
   dog.legs = 4;
   dog.say();
}

對於被創建出來的對象而言,它們都不一樣,每一個特定的對象(實例)都有一組特定的屬性值(成員變量),這些屬性值的集合就是這個對象的當前狀態,只要對象使用者通過行為(成員方法)向該對象發送消息,這些狀態就可能被改變。

上面這句話怎麼理解?

現在有兩隻狗(兩個對象):哮天犬和哮地犬,這兩個對象的名字、年齡等屬性不同,即當前狀態不同。每隻狗都有一個行為:可以「每過一年,年齡增長1歲」,當通過該行為向哮天犬發送消息時,哮天犬的狀態就被改變了。

可以看出,一個對象由狀態(state)和行為(behavior)組成,對象在成員變量中存儲狀態,通過成員方法公開其行為

研究一個對象,我們要去關注它處於什麼狀態?具有哪些行為?

對象的三個主要特性:

  • 對象的行為——可以對對象施加哪些操作(方法)?
  • 對象的狀態——當施加那些方法時,對象如何響應?
  • 對象標識——如何辨別具有相同狀態與行為的不同對象?

3. 三大特性

3.1. 封裝

封裝(encapsulation)是Java面向對象的三大特行之一。

一個對象具有屬性和行為,封裝把其屬性和行為組合在了一起,但是為什麼需要封裝?

上文已經介紹了,一個對象由其狀態和行為組成。我們回看程序2-1,雖然這段代碼錶示出了Dog類,但是有一個很大的問題:創建出來的Dog對象的狀態很容易被改變

比如我們可以直接修改程序2-2中的對象的狀態:

dog.name = "哮地犬";
dog.legs = 3;

你的狗的名字被不懷好意的人給改了,腿也少了一條!這種事情是危險、可怕的

我們希望別人能夠“知道”自己的狗叫什麼名字、有幾條腿等信息,但是又要防止不懷好意的人隨便“傷害”自己的狗,怎麼辦呢?答案是封裝

將對象的狀態和行為封裝起來,使用該對象的用戶只能通過對象本身提供的方法來訪問該對象的狀態。前面也說過,對象的當前狀態可能會改變,但是這種改變不是對象自發的,必須通過調用對象本身提供的方法來改變。如果不通過調用方法就能改變對象狀態,只能說明封裝性被破壞了。

換句話說,我們將對象的屬性(狀態)對外隱藏起來,這些狀態能否被訪問或修改,由對象自己來決定,決定的方式就是「給對象的使用者提供可調用的方法,用戶通過這些方法來進行訪問和修改」。

程序2-1可以改進為:

程序3-1
/**
 * 封裝后的狗類
 * @author Xing Xiaoguan
 */

public class Dog {
    private String name;
    private int age;
    private int legs;

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    public int getLegs() {
        return legs;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setAge(int age) {
        this.age = age;
    }

    //行為——吠叫
    public void say() {
        System.out.println("我是" + name + "汪汪汪");
    }

    //行為——看門
    public void watchDoor() {
        System.out.println("趕走陌生人");
    }

}

乍一看多了許多代碼,其實就多了兩個部分:

  1. 使用private修飾符來修飾成員變量,將其私有化,確保只能在本類內被訪問到,實現隱藏。
  2. 給成員變量提供了對應的訪問方法(getter方法)和修改方法(setter方法)。

其中我們給用戶能夠訪問或修改的成員變量都編寫上對應的setter或getter方法,如name。用戶不能訪問或修改的成員變量不寫setter或getter方法即可。

/**
 * 實例化一隻小狗,並和它玩
 */
public class Play {
    public static void main(String[] args) {
        Dog dog = new Dog();
        dog.setName("哮天犬");;
        dog.setAge(2);
        dog.say();
        dog.watchDoor();
    }
}

現在,用戶不能直接訪問或修改對象的狀態,必須通過提供的方法。對象並沒有給legs變量提供setter方法,這樣用戶就只能訪問狗有幾條腿,但是不能修改。狗腿被人“偷”了的事情也不會再發生了。

而且,當我們使用setter方法修改成員變量時,可以進行其他的操作,如錯誤檢查。比如設置age變量時:

//狗的平均壽命為10~15年,太大了不合理
public void setName(String name) {
    if (name > 0 && name < 30)
    	this.name = name;
}

現在,Dog類就比較安全了。

由上面的代碼可以看出,如果要訪問或修改成員變量,需要:

  • 成員變量是私有的
  • 一個公有的getter方法
  • 一個公有的setter方法

封裝還有一個優點就是:對外隱藏了具體實現,這樣的好處就是:我們可以修改內部實現,除了修改了該類的方法外,不會影響其他代碼。

封裝使對象對外變成了一個“黑箱”,用戶只會使用,但不清楚內部情況。

前面買水的例子也體現了封裝思想:行小觀買水的方式有很多,走路去、騎車去、甚至找比人幫忙,但是他改變買水的方式並不會對我造成影響。

3.2. 繼承

生活中除了狗,還有許多其他動物,比如貓、兔子……

程序3-2
/**
 * 貓類
 * @author Xing Xiaoguan
 */

public class Cat {
    private String name;
    private int age;
    private int legs;
    private String owner;//主人

    //getters and setters……

    //行為——叫
    public void say() {
        System.out.println("我是" + name + "喵喵喵");
    }

    //行為——捉老鼠
    public void catchMouse() {
        System.out.println("捉到一隻老鼠");
    }
}
程序2-5
/**
 * 兔子類
 * @author Xing Xiaoguan
 */

public class Rabbit {
    private String name;
    private int age;
    private int legs;
    private String home;//住址

	//getters and setters……

    //行為——叫
    public void say() {
        System.out.println("我是" + name + "咕咕咕");
    }

    //行為——搗葯
    public void makeMedicine() {
        System.out.println("在" + home + "搗葯");
    }
}

寫完這兩個類,發現有許多屬性和方法是重複的,如果需要再寫100個動物的類,那得

這些動物形態各異,但是它們都被統稱為“動物”,也就是說,我們仍能在它們身上找出相同的特點,比如它們都有名字、年齡、腿、能發出聲音……

前面介紹類的時候已經說了,類是對一類具有共同特徵的事物的抽象,所以此時我們還能從狗、貓、兔子這些類中再抽象出一個類——動物類。

程序3-3
/**
 * 動物類
 * @author Xing Xiaoguan
 */

public class Animal {
    private String name;
    private Integer age;
    private Integer legs;

    public void say() {
        System.out.println("我是"+name+"發出聲響");
    }
    //setters and getters……
}

這個更抽象的類就是父類,而狗、貓、兔子類是子類。子類可以使用extends關鍵字繼承父類的屬性和方法,這意味着相同的代碼只需要寫一遍。

程序3-4
/**
 * 狗類繼承父類
 * @author Xing Xiaoguan
 */

public class Dog extends Animal{

    //行為——吠叫
    public void say() {
        System.out.println("我是" + getName() + "汪汪汪");
    }

    //行為——看門
    public void watchDoor() {
        System.out.println("趕走陌生人");
    }
}
程序3-5
/**
 * 貓類繼承父類
 * @author Xing Xiaoguan
 */

public class Cat extends Animal {
    private String owner;

    public String getOwner() {
        return owner;
    }

    public void setOwner(String owner) {
        this.owner = owner;
    }

    //行為——喵喵叫
    public void say() {
        System.out.println("我是" + getName() + "喵喵喵");
    }

    //行為——捉老鼠
    public void catchMouse() {
        System.out.println("捉到一隻老鼠");
    }
}
程序3-6
/**
 * 兔子類繼承父類
 * @author Xing Xiaoguan
 */

public class Rabbit extends Animal {

    private String home;

    public String getHome() {
        return home;
    }

    public void setHome(String home) {
        this.home = home;
    }

    //行為——叫
    public void say() {
        System.out.println("我是" + getName() + "咕咕咕");
    }

    //行為——搗葯
    public void makeMedicine() {
        System.out.println("在" + home + "搗葯");
    }
}

觀察上面的子類和父類,可以發現:

  1. 子類不用再重複父類中已有的屬性和方法,能通過繼承獲取到。
  2. 子類比父類的功能更加豐富,子類可以擁有自己的屬性和方法
  3. 子類如果感覺繼承自父類的方法不合適,可以重寫父類的方法的實現過程,注意返回值和形參不能改變。

使用繼承的好處:

  • 提高代碼的復用性,不用再寫那麼多重複代碼了。
  • 使代碼便於維護,當我們需要修改某個公用方法時,不需要一個個類去修改,只需修改父類的該方法即可。

3.3. 多態

看下面一段代碼,我們來直觀體驗什麼是多態。

程序3-7
public static void main(String[] args) {
    Animal dog = new Dog();
    dog.setName("哮天犬");
    dog.say(dog.getName());
    
    Animal cat = new Cat();
    cat.setName("加菲貓");
    cat.say();

    Animal rabbit = new Rabbit();
    rabbit.setName("玉兔");
    rabbit.say();
}

運行,輸出:
我是哮天犬汪汪汪
我是加菲貓喵喵喵
我是玉兔咕咕咕
  1. DogCatRabbit都是繼承Animal父類。
  2. DogCatRabbit重寫Animalsay(String name)方法。
  3. 在創建實例對象時,我們使用父類引用指向子類的對象

使用多態應當注意一下幾點:Animal dog = new Dog()

  • 在多態中,子類對象只能調用父類中定義的方法,不能調用子類中獨有的方法。

    比如dog不能調用watchDoor()方法。

  • 在多態中,子類可以調用父類的所有方法。

  • 在多態中,子類如果重寫了父類的方法,那麼子類調用該方法時,調用的是子類重寫的方法。

在上面的代碼中,狗、貓、兔子對象都運行了say方法,但是輸出不同。

由此看出,不同的對象的同一行為具有不同的表現形式,這就是多態。

在實際的本例中,我們可以理解為:動物Animal,他們都會叫出聲。如果是狗,則叫的是汪汪汪;如果是貓,則叫的是喵喵喵;如果是兔子,則叫的是咕咕咕。

4. 總結

面向對象思想使我們在編程更加貼近現實世界,類是現實世界的抽象,對象則是一個個具體的事物。

封裝使一個個具體的事物更加獨立,繼承則使一些類似的事物之間具有聯繫,而多態則使事物的行為更加靈活多樣。

面向對象編程提高了軟件的重用性、靈活性、擴展性。

如有錯誤,還請指正

參考資料:

  • The Java Tutorials

  • 維基百科

  • 百度百科

  • Java核心技術 卷1

文章首發於公眾號「行人觀學」

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

※評比南投搬家公司費用收費行情懶人包大公開

※幫你省時又省力,新北清潔一流服務好口碑

您可能也會喜歡…