面向對象的正確打開方式

引言

面向對象是什麼?怎麼去理解面向對象?我記得一開始接觸Java的時候,老師對面向對象的解釋舉例都是阿貓阿狗,講完之後,我們都知道了什麼叫對象,但什麼叫面向對象呢?其實並沒有說清楚。學Java這麼久了,關於面向對象還是有一點自己的理解的。這篇文章,將幫助你去理解什麼叫面向對象。

對象

前面一直在說面向對象,那什麼是對象呢?對象是系統中描述客觀事物的一個實體,它是構成系統的一個基本單位。這麼講好理解嗎?不好理解?那我們再簡單一點,對象是一個類別中的某一個個體,學了數學的我們應該知道集合,對象就是我們集合中的元素,他是獨一無二的。我們這裏講的對象也是獨一無二的,爲什麼這麼說,後面在作解釋。那這樣,我們能理解對象嗎?地球上的人,是一個大的類別,我們每個人就是其中的一個個體,所以說,我們就是一個對象。

好,對象的理解完,我們再來看。我們每個人是一個對象,那怎麼去分析這個對象呢?之前也講了,對象是獨一無二的存在。那我們來看看,爲什麼這麼說?我們人這個對象實體,有名字,身高,性別,年齡等基本的描述信息。看個程序(以下程序都是Java版):

public class Person {
    String name ;
    int height ;
    String sex ;
    int age ;
}

這是不是就是對人的一個基本描述(我們不考慮太多的描述)?這裏的 name , height , sex , age
稱爲成員變量,是Person類的屬性。剛剛說了,對象是由一個類實例化出來的一個實體。那是不是這樣?

Person person = new Person();

那這樣,我們是不是就可以說 person 是一個對象了?是的,person 就是一個對象。好,我們的對象是獨一無二的,那是不是要給對象加上它的基本信息,他叫什麼?多高?年齡多大之類的信息?

成員變量的賦值

成員變量怎麼賦值呢?一般的我們針對成員變量的賦值會有三種:

在定義的時候直接賦

public class Person {
    String name = "Alan";
    String height ;
    String sex ;
    int age ;
}

通過構造方法賦值

所謂構造方法,也就是我們常喜歡說的構造器,是指,我們根據這個類,去創建對象的一個途徑。

public class Person {
    String name = "Alan";
    String height ;
    String sex ;
    int age ;

    public Person(int height) {
        this.height = height;
    }
}

通過構造器,我們是在創建這個對象的時候就已經確定賦值了。

Person person = new Person("180cm");

通過方法賦值

這裏,我們使用方法去進行賦值,一般也是我們常說的 setter/getter 方法,進行 賦值/取值 操作。看這個例子:

public class Person {
    String name ;
    String height ;
    String sex ;
    int age ;

    public String getName() {
        return name;
    }

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

    public String getHeight() {
        return height;
    }

    public void setHeight(String height) {
        this.height = height;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public int getAge() {
        return age;
    }

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

面對這個方法,我們怎麼去用?

    public static void main(String[] args) {
        Person tom = new Person();
        tom.setName("Tom");
        tom.setAge(18);
        tom.setHeight("180cm");
        tom.setSex("male");

        System.out.println(tom);
    }

結果是什麼?

Person{name='Tom', height='180cm', sex='male', age=18}

我們一般會把這種在一個類中將方法包裝起來,並對去提供一個接口的方式,叫做 封裝 。那現在,我們對象相關點講完了,開始講面向對象了。

面向對象

首先,面向對象是一種程序設計的方法,是從現實世界中客觀存在的事物(即對象)出發來構造軟件系統,並且在系統構造中儘可能運用人類的自然思維方式。這句話怎麼理解呢?舉個例子:

在我們學習 C 語言的時候,會寫很多的方法去做某一件事,比如說,開門這個動作,C 中就會直接執行開門這個動作,那麼問題來了,誰來執行這個開門動作呢?C 語言強調的是怎麼去開門,誰來開門,他不關心。那在Java裏面呢?同樣是開門,但是他的開門會有人去開門,而不是讓門自己打開。所以說,面嚮對象語言更符合我們人類自然的思維方式,

基本特性

封裝

這裏我們前面的例子大家也看到了,封裝的含義就是隱藏對象內部的細節,不對外暴露。前面的 tom 對象的內部屬性 , 比如說 name 還是對外暴露的。封裝的原則是使對象以外的部分不能隨意的訪問和操作對象的內部屬性,從而避免了外界對對象內部屬性的破壞。可以通過對類的成員設置一定的訪問權限,實現類中成員的信息隱藏。我不要它對外暴露,怎麼做呢?

範圍 private default protected public
同一個類中 OK OK OK OK
同一個包中 NO OK OK OK
子類 NO NO OK OK
全局範圍 NO NO NO OK

繼承

繼承關係,應該是這裏面比較複雜的特性之一。首先,我們先理解什麼叫繼承,顧名思義繼承就是從上一代手中去獲取到他的一切並作爲自己的東西。很好理解吧?類的繼承也是這樣。子類要繼承父類的屬性,繼承你的方法,繼承你的一切。比如說,人裏面有好人,有壞人。怎麼說,那我給好人壞人打個標籤:

public class BadPerson extends Person {
    private String tag = "我是個壞人" ;

    @Override
    public String toString() {
        return "BadPerson{" +
                "tag='" + tag + '\'' +
                '}';
    }
}
public class GoodPerson {
    private String tag = "我是個好人" ;

    @Override
    public String toString() {
        return "GoodPerson{" +
                "tag='" + tag + '\'' +
                '}';
    }
}

看看我們得到的結果,

GoodPerson{tag='我是個好人', name='Tom', height='180cm', sex='male', age=18}
BadPerson{tag='我是個壞人', name='Alan', height='170cm', sex='female', age=24}

大家可以看到,我這裏並沒有去對GoodPerson做多餘的方法,只有一個添加的標籤。很顯然,這些其他的方法都是從父類繼承下來的。這裏,我們就需要注意幾點問題了:

  • Java中父類可以擁有多個子類,但是子類只能繼承一個父類,稱爲單繼承。
  • 繼承實現了代碼的複用。
  • Java中所有的類都是通過直接或間接地繼承java.lang.Object類得到的。
  • 子類不能繼承父類中訪問權限爲private的成員變量和方法。
  • 子類可以重寫父類的方法,即命名與父類同名的成員變量。

Java中通過super來實現對父類成員的訪問,super用來引用當前對象的父類。super 的使用有三種情況:

  • 訪問父類被隱藏的成員變量,如:super.variable;
  • 調用父類中被重寫的方法,如:super.Method([paramlist]),super()調用父類構造方法;
  • 調用父類的構造函數,如:super([paramlist]);

上面的幾個問題,大傢伙就自己去驗證吧。

多態

所謂多態就是指程序中定義的引用變量所指向的具體類型和通過該引用變量發出的方法調用在編程時並不確定,而是在程序運行期間才確定,即一個引用變量倒底會指向哪個類的實例對象,該引用變量發出的方法調用到底是哪個類中實現的方法,必須在由程序運行期間才能決定。因爲在程序運行時才確定具體的類,這樣,不用修改源程序代碼,就可以讓引用變量綁定到各種不同的類實現上,從而導致該引用調用的具體方法隨之改變,即不修改程序代碼就可以改變程序運行時所綁定的具體代碼,讓程序可以選擇多個運行狀態,這就是多態性。

怎麼理解這句話?舉個例子。好人 Tom 在路上抓到了一個小偷 Alan , 現在兩個人進警察局,那現在有一個警察,要來分辨誰是好人誰是壞人了。警察在他們進來的時候不知道誰是好人誰是壞人,必須要進來之後才能確定是吧。但是問題來了,第一個進來的可能是好人,也可能是壞人,那我這裏怎麼分析呢?看程序:

    public String analysis(GoodPerson goodPerson){
        Class<? extends GoodPerson> aClass = goodPerson.getClass();
        if (aClass.equals(GoodPerson.class)){
            return "是個好人" ;
        }
        return "不知道";
    }

這裏的解決方式有很多,我這裏解決了面向對象的一個基本問題就是:**我是誰 ?**也有其它的寫法。

如果我進來的是一個壞人,那我是不是還要一個方法來分析好人壞人?那這樣是不是就造成了人力財力?顯然不合適。我們可以看到一點,無論好人壞人,都有一個共同點,就是 你都繼承成了Person .現在來看:

    public String analysis(Person person){
        if (person instanceof GoodPerson){
            return "是個好人" ;
        }else if (person instanceof BadPerson){
            return "是個壞人" ;
        }else if (person instanceof Police){
            return "我是警察" ;
        }
        return "不知道";
    }

這樣來看,不管你是好人壞人還是警察,只要你敢來,我就敢分辨你是誰。這樣,是不是就方便了很多。這就是多態。

多態存在的幾個必要條件:

  • 繼承
  • 重寫
  • 父類引用子指向類的對象

多態的優勢在於:

1、可替換性。

2、可擴充性。多態對代碼具有可擴充性。

3、接口性。多態是超類通過方法簽名,向子類提供了一個共同接口,由子類來完善或者覆蓋它而實現的。

4、靈活性。它在應用中體現了靈活多樣的操作,提高了使用效率。

5、簡化性。多態簡化對應用軟件的代碼編寫和修改過程,尤其在處理大量對象的運算和操作時,這個特點尤爲突出和重要。

抽象

這裏我將抽象作爲面向對象的第四個特性,支持面向對象“四大特性”說法。抽象是程序員的核心素質之 ,體現出程序員對業務的建模能力,以及對架構的宏觀掌控力。雖然面向過程也需要進行 定的抽象能力,但是相對來說,面向對象思維,以對象模型爲核心富模型的內涵,擴展模型的外延,通過模型的行爲組合去共同解決某一類問題,抽象能力顯得尤爲重要 封裝是一種對象功能內聚的表現形式 使模塊之間輯合度變低,更具有維護性,繼承使子類能夠繼承父類,獲得父類的部分屬性和行爲,使模塊更有複用性 多態使模塊在複用性基礎上更加有擴展性,使運行期更有想象空間。

抽象是面向對象思想最基礎的能力之一,正確而嚴謹的業務抽象和建模分析能力是後續的封裝、繼承、多態的基礎 是軟件大廈的基石。在面向對象的思維中,抽象分爲歸納和演繹。前者是從具體到本質,從個性到共性,將一類對象的共同特徵進行歸一化的邏輯思維過程 後者則是從本質到具體,從共性到個性,逐步形象化的過程。在歸納的過程中,需要抽象出對象的屬性和行爲的共性,難度大於演繹。演繹是在已有問題解決方案的基礎上,正確地找到合適的使用場景。演繹錯誤在使用集合時比較常見,比如針對查多改少的業務場景 使用鏈表是非常不合理的,底層框架技術選型時如果有錯誤,則有可能導致技術架構完全不適應業務的快速發展。

現在我們來看我們的 Person 類, 我們可以把把他抽象出來,看結果:

public abstract class Person {
    String name ;
    String height ;
    String sex ;
    int age ;

    public String getName() {
        return name;
    }

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

    public String getHeight() {
        return height;
    }

    public void setHeight(String height) {
        this.height = height;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public int getAge() {
        return age;
    }

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

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", height='" + height + '\'' +
                ", sex='" + sex + '\'' +
                ", age=" + age +
                '}';
    }

    public String analysis(Person person){
        return "分析" ;
    }
}

這裏需要注意幾個問題:

  • 抽象類不能被實例化,由其子類進行實例化操作;
  • 類中有抽象方法,此類一定是抽象類;
  • 抽象類中的抽象方法只是聲明,不包含方法體,就是不給出方法的具體實現也就是方法的具體功能。
  • 構造方法,類方法(用 static 修飾的方法)不能聲明爲抽象方法。
  • 父類是有抽象方法的抽象類時,子類必須實現抽象方法或者聲明爲抽象。

在抽象類中,還有一個概念,我們稱爲 接口 。在接口中,一般是供別人調用的方法,他是對對象行爲的抽象。接口是要實現的。比如說 Person 類中的 analysis(Person person) , 可以實現一個接口的形式:

public interface Behavior {
    String analysis(Person person) ;
}

現在用Person 去實現我們的接口:

public abstract class Person implements Behavior{}

由於我們這裏的Person是一個抽象類,所以呢,我們這裏的Behavior裏面的接口可以不實現。但是,Person的子類是必須要實現這個接口的。如果說,這個接口可以選擇性實現,那我們的接口就要這麼寫:

    default void say() {
        System.out.println("Hello");
    }

接口裏面的 say() 就是一個可以不實現的方法。

接口與抽象之間的關係

語法不同

  • 抽象類可以提供成員方法的實現細節,而接口中只能存在public abstract 方法;

  • 抽象類中的成員變量可以是各種類型的,而接口中的成員變量只能是public static final類型的;

  • 接口中不能含有靜態代碼塊以及靜態方法,而抽象類可以有靜態代碼塊和靜態方法;

  • 一個類只能繼承一個抽象類,而一個類卻可以實現多個接口。

設計層面不同

  • 抽象類時對類的抽象,接口是對行爲的抽象;

  • 抽象類更傾向於一種模板,子類均按照這個模板來設計;接口更是一種行爲上的規範或者說是一種約束。

總結

正所謂,“一樹一菩提,一花一世界。”一切皆對象,萬物有三問:我是誰?我從哪來?我要到哪去?

面向對象編程(Object-Oriented Programming,OOP)的封裝,繼承,多態,抽象的理念推動了軟件大規模化的可能,也實踐了軟件工程的三個目標:可維護性、可重用性和可擴展性。Java中,有一位所有類的先祖 —— Object。在Object類中,我們能找到萬物三問的答案:

  • 我是誰? getClass() 回答了你,你是誰。 toString() 是你對外的公開名片;
  • 我從哪裏來? Object() 構造方法是生產對象的必要 , clone() 是繁殖對象的另一種方式;
  • 我到哪裏去? finalize() 是在對對象銷燬時觸發的方法。

另外,Object還映射了社會科學領域的一些問題:

  • 世界是否因你而不同? hashCode() 和 equals() 判斷是你和世界不同還是世界和你不同。
  • 與他人的協調合作? wait() 和 notify() 是對象建通信的一種方式?畢竟世界這麼大,你我距離這麼遠,想你了,怎麼辦呢?是吧。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章