面向對象的思想,談面試的過程?

0一、

好久沒有思考過什麼是面向對象這個問題了,就好像好久沒有吃過烤紅薯同樣,那股香味到底是什麼,已經很難準確地形容出來了。腦海中只浮現出這樣一幅動圖:java

在這裏插入圖片描述

前兩天,讀者秋秋問我:面試

二哥,究竟什麼是面向對象呢?還有,什麼是面向過程。今天去面試的時候,面試官讓我用面向對象的思想談一談此次面試的過程。編程

看到這個問題後,我思考了好一下子,總以爲面試官的問法有點問題:爲何要用面向對象的思想談一談面試的「過程」?設計模式

有點矛盾,有沒有?先無論這麼多了,且來看看什麼是面向對象吧。安全

一開始的時候,並無面向對象,只有面向過程的概念。咱們回到秋秋面試的話題上,把面試前(能夠下降需求的複雜性)的過程簡單地拆解一下。併發

  • 秋秋投遞簡歷
  • 面試官收到秋秋的簡歷
  • 面試官通知秋秋面試

爲了實現這 3 個步驟,咱們定義 3 個方法,並依次調用:this

  • qiuqiuDeliverResume();
  • interviewerReceiveResume();
  • interviewerNotifyQiuqiu();

可是,假如參加面試的不是秋秋,這 3 個方法就要從新定義了(莫擡槓),儘管步驟並無變。面向對象從另外一個角度來解決這個問題,它把對象(對事物的一種抽象描述)做爲程序的基本單元。spa

回到秋秋面試的例子,用面向對象的思想來實現,就須要先定義 2 個類(類是構建對象的藍圖,裏面包含若干的數據和操做這些數據的方法),分別是應聘者和麪試官。設計

應聘者能夠投遞簡歷;面試官能夠接收應聘者的簡歷和通知應聘者前來面試。而後再經過類建立兩個對象,分別是秋秋和他的面試官;對象建立成功後,就能夠依次調用對應的方法完成上述的 3 個步驟。code

面向對象(英語:Object Oriented,縮寫:OO)思想是一種試圖下降代碼間的依賴,應對複雜性,從而解決代碼重用的軟件設計思想——剛好解決了面向過程帶來的問題。

面向對象有不少重要的特性,好比說封裝、繼承和多態。這些概念又該怎麼理解呢?所謂一圖勝千言,我給你來一張有趣的、形象的。

瞭解了面向對象的思想後,咱們來經過具體的代碼完成秋秋面試前的 3 個步驟。並對類和對象的相關知識點進行概括和總結。

0二、

先來細緻地看一下應聘者類——Candidate.java。

package com.cmower

class Candidate {
    private String name;

    public Candidate(String name) {
        this.name = name;
    }

    public void deliverResume() {
        System.out.println(getName() + "發簡歷");
    }

    public String getName() {
        return name;
    }

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

}

Candidate 包含了類的 4 個重要概念:

  • 成員變量(有時叫作域,有時叫作字段,有時叫作屬性)name
  • 成員變量訪問器(有時叫作 getter/settergetName()setName()
  • 構造方法(有時叫作構造器)Candidate()
  • 普通方法 deliverResume()

Candidate 類雖然簡單,但卻大有學問。

1)爲了保證包名的絕對惟一,Sun 公司建議將域名(絕對是獨一無二的)以逆序的形式做爲包名——這也是爲何包名常常以 orgcom 開頭的緣由(是否是有一種豁然開朗的感受)。我曾申請過一個域名,叫 cmower.com,因此我我的編寫的絕大多數代碼都是在 com.cmower包下。

2)類的方法定義順序依次是:構造方法 > 公有(public)方法或保護(protected)方法 > 私有(private)方法 > getter/setter 方法。

構造方法是建立對象的必經之路,放在首位是必須的。若是隻有系統默認的無參構造方法,可忽略。

公有方法是類的調用者和維護者最關心的方法,應該在比較靠前的位置展現;保護方法雖然只有子類關心,也多是「模板設計模式」下的核心方法,因此也要靠前;私有方法只對本類可見,通常不須要特別關心,因此日後放;getter/setter 方法承載的信息價值較低,因此放在類的最後面。

3)setter 方法中,參數名稱與成員變量名稱保持一致,採用 this.成員名 = 參數名 的形式。

4)成員變量不要用 public 修飾,儘可能用 private 修飾;若是須要被子類繼承,能夠用 protected 修飾。

在初學 Java 編程的時候,我常常產生一個疑惑:爲何不使用 public 修飾成員變量呢?這樣作不是比 getter/setter 更方便嗎?

我最早想到的答案是這樣的:

解釋:若是隻有 private String name 而沒有 getter/setter 的話,Eclipse 會提示 The value of the field Candidate.name is not used 的警告。

固然了,這樣的答案過於牽強。那能不能來個靠譜點的答案呢?

能,爲了體現封裝的思想:將數據與行爲進行分離。封裝有什麼好處呢?

  • 隱藏類的實現細節;
  • 讓使用者只能經過事先定製好的方法(getter/setter)來訪問數據,能夠方便地加入控制方法,限制對成員變量的不合理操做;
  • 便於修改,加強代碼的維護性和健壯性;
  • 提升代碼的安全性和規範性;
  • 使程序更加具有穩定性和可拓展性。

不過,我對這些嚴肅的詞彙和科學用語實在是提不起半點興致。那就再換一個答案吧。

套用《Java 開發實戰經典》中舉過的一個例子,咱們增長一個應聘者年齡的共有成員變量 age。

class Candidate {
    public int age;
}

而後在建立應聘者對象的時候,直接經過類成員變量賦值:new Candidate().age = -99; 這樣賦值是沒有任何問題的,但沒有實際的意義,年齡是不可能爲負數的。爲了防止出現這樣的錯誤,能夠對它進行封裝,也就是私有化,而後在 setter 方法中對年齡進行判斷,代碼以下:

class Candidate {
    private int age;

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

這個答案你以爲滿意嗎?我最開始看到這個答案的時候以爲很滿意。但看了《阿里巴巴 Java 開發手冊》後(詳情截圖以下),就以爲不滿意了。

第一,類成員變量使用基本類型很容易形成NullPointException的錯誤;第二,在 getter/setter 增長業務邏輯的確很容易把實際的問題隱藏起來。

那,好的答案到底是什麼呢?

若是設置成員變量爲 public,那麼每一個調用者均可以讀寫它,但若是以 private 配合 getter/setter 的形式訪問時,就能夠達到「不許訪問」、「只讀訪問」、「讀寫訪問」以及「只寫訪問」的目的。由於不是每一個成員變量都須要 getter/setter

5)每一個類都至少會有一個構造方法。初學者可能會很是疑惑:個人那個類真的沒有構造方法啊!

若是在編寫一個類的時候沒有編寫構造方法,那麼系統就會提供一個無參的構造方法,就好像是這樣:

class Candidate {
    private String name;

    public Candidate() {
    }

}

當執行 new Candidate() 的時候,成員變量 name 就會被初始化爲 null。通常狀況下,咱們會爲類設置它必須的構造方法,而後在建立對象的時候對成員變量進行賦值。

0三、

再來粗略地看一下面試官類——Interviewer.java。

class Interviewer {
    private Candidate candidate;

    public Interviewer (Candidate candidate) {
        this.candidate = candidate;
    }

    public void receviveResume() {
        System.out.println("收到" + getCandidate().getName() + "簡歷");
    }

    public void notifyInterview() {
        System.out.println("通知" + getCandidate().getName() + "面試");
    }

    public Candidate getCandidate() {
        return candidate;
    }

    public void setCandidate(Candidate candidate) {
        this.candidate = candidate;
    }

}

Interviewer 有一個成員變量 Candidate,一個構造方法,兩個共有方法,以及成員變量對應的 getter/setter

(這段代碼存在一個嚴重的問題,你注意到了嗎?)

0四、

而後,咱們讓應聘者發送簡歷,讓面試官接收簡歷併發送通知。

Candidate qiuqiu = new Candidate("秋秋");
// 發送簡歷
qiuqiu.deliverResume();

Interviewer interviewer = new Interviewer(qiuqiu);
// 面試官接收到簡歷
interviewer.receviveResume();
// 面試官通知應聘者來面試
interviewer.notifyInterview();

在初學 Java 的很長一段時間裏,我老是搞不清楚什麼是「對象」,什麼是「引用」,差點所以放棄個人程序生涯。後來,在網上認識了一個大佬,人稱老王,是他挽救了個人程序生涯。

他解釋說。

Candidate qiuqiu = new Candidate("秋秋");能夠拆分爲兩行代碼:

Candidate qiuqiu;
qiuqiu = new Candidate("秋秋");

第一行代碼 Candidate qiuqiu; 中的 qiuqiu 這時候能夠稱做是對象變量,它暫時尚未引用任何對象,嚴格意義上,它也不能稱爲 null

第二行代碼 qiuqiu = new Candidate("秋秋"); 能夠拆分爲兩個部分,= 號左側和 = 號右側。

右側的表達式 new Candidate("秋秋") 先執行,執行完後,會在堆上建立了一個 name 爲「秋秋」的對象,類型爲 Candidate,表達式 new Candidate("秋秋") 的值是新建立對象的引用。

而後再把這個引用經過 = 操做符賦值給左側的對象變量 qiuqiu,賦值後,qiuqiu就再也不是對象變量了,應該稱爲對象引用。

看完老王的解釋,你會不會不由自主地「哦,原來如此啊!」反正我當時頓悟的時候是這樣的。

前面提到,Interviewer 類的設計存在一個嚴重的問題,是什麼呢?

Candidate qiuqiu = new Candidate("秋秋");
Interviewer interviewer = new Interviewer(qiuqiu);

interviewer.getCandidate().setName("夏夏");
System.out.println(qiuqiu.getName());

這段代碼執行完後,你會發現秋秋變成了夏夏,應聘者的私有成員變量 name 居然被改變了!問題的緣由也很簡單,qiuqiu 和 interviewer.getCandidate() 引用了同一個對象。

那怎麼解決呢?當 getter 須要返回一個可變對象的引用時,應該先進行克隆(clone)。如下展現了一個很是簡單的克隆方案。

class Interviewer {
    private Candidate candidate;

    public Interviewer (Candidate candidate) {
        this.candidate = candidate;
    }

    public Candidate getCandidate() {
        Candidate candidate = new Candidate(this.candidate.getName());
        return candidate;
    }

}

0五、

這篇文章花了 5 個多小時才寫完,此刻個人感受只有一個字——餓,我要出去吃飯了。吃飯以前,我決定先買個烤紅薯吃,重溫一下那種久違的香。

相關文章
相關標籤/搜索