1. 程式人生 > >如何通過父類引用“呼叫”子類所獨有的方法

如何通過父類引用“呼叫”子類所獨有的方法

最近看書,看到向上引用的情況:派生類引用或指標轉換為基類引用或指標被稱為向上強制轉換。

BrassPlus dilly("Annie Dill",493222,2000);

Brass *pb = &dilly;

Brass &rb = dilly;

在這裡想到,經過向上轉換的基類物件能否呼叫子類中的獨有的成員函式呢? 

首先,我們能否用父類呼叫子類的方法呢?

多型。 如果說父類中有這個屬性跟方法,子類有重寫過,那麼呼叫的是子類中的屬性跟方法。 如果父類中沒有這個屬性跟方法,那麼子類呼叫就會出錯。 如果父類有這個屬性跟方法,而子類沒有,則呼叫的是父類的屬性跟方法。

疑問:Animal cat = new Cat(); //向上轉型。       

父類引用指向子類物件,該引用不能再訪問子類新增加的成員,那麼這樣和直接new一個父類例項(Animal a = new Animal())有什麼區別?
           1、當父類是抽象類或是介面時,不能例項化時,只能運用多型,向上轉型。
           2、普通類中,可以在子類中重寫父類中的方法,這樣就可以訪問子類中的重寫方法。或者:Cat c = (Cat)cat; 向下轉型,再訪問子類中新增加的成員。

所以,舉個為什麼不new一個父類的例項:

你寫了一個飛機遊戲,畫面裡出現什麼型別飛機是隨機決定的,你的程式碼裡也就不可能用一個具體飛機型別來操作。 
所以,往往是隨機生成各種型別飛機,他們有共同的父類,你的程式碼就可以用父類指標來控制行為。比如中彈後的能量損失多少之類,每種飛機可能不同。 

Eg:   List是介面,ArrayList是List的實現類。

至於為什麼是寫成List list = new ArrayList() 而不是 ArrayList arrayList = new ArrayList 有如下的原因:

1.介面有什麼好處,這種定義方式就有什麼好處
當然你可以用 ArrayList   list   =   new   ArrayList;

但是一般不這麼用

 2 .設計模式中有對依賴倒置原則。程式要儘量依賴於抽象,不依賴於具體。
從Java語法上,這種方式是使用介面引用指向具體實現。

比如,你若希望用LinkedList的實現來替代ArrayList的話,只需改動一行即可:
List   list   =   new   LinkedList();
而程式中的其它部分不需要改動,這樣比較靈活

這個如果你想把儲存結構該為LinkedList的時候,只要把List   list   =   new   ArrayList 改為list   =   new   LinkedList 而其他的所有的都不需要改動。這也是一種很好的設計模式.一個介面有多種實現,當你想換一種實現方式時,你需要做的改動很小.

假設你開始用 ArrayList alist = new ArrayList , 這下你有的改了,特別是如果你使用了 ArrayList特有的方法和屬性。如果沒有特別需求的話,最好使用List list = new LinkedList(); ,便於程式程式碼的重構. 這就是面向介面程式設計的好處

 3 面向介面程式設計

 4 提高程式寬展性,以後修改維護好些

  • ArrayList不是繼承List介面,是實現了List介面。
  • 你寫成ArrayList arrayList = new ArrayList();這樣不會有任何問題。
  • 和List list = new ArrayList();相比這2個寫是有區別的。arrayList是一個ArrayList物件,它可以使用ArrayList的所有方法。
  • List是介面,它是不可以被例項化的(介面是個抽象類),所以必須以它的實現類去例項化它。list物件雖然也是被例項化為ArrayList但是它實際是List物件,list只能使用ArrayList中已經實現了的List介面中的方法,ArrayList中那些自己的、沒有在List介面定義的方法是不可以被訪問到的。
  • 我們說,用介面去做是有它的好處的,如果你把型別定義成ArrayList(也就是一個具體的實現類)那麼你就只能接收這一種型別的資料了,如果你要是定義為List那麼你不僅可以接收ArrayList的物件還可以接收LinkedList的物件,這樣你的程式就靈活了。
     

 然後,我們看下如何強制實現父類呼叫子類的方法。

該做法的意義何在,姑且不論。今天我們主要關注該功能的實現,至少在實現的思路上是對面向物件思想的一次深入理解。

首先一點,父類引用是無法呼叫子類獨有的方法(不僅無法訪問,而且是不可見的),結論是顯然的,不然該方法就不作為子類所獨有了,不然子類就沒有任何的獨特之處了(隱私空間),也就喪失了子類存在的意義。

// C++
class Base
{

};

class Derived :public Base
{
public:
    void foo() {}
};

int main(int, char**)
{
    Base* pBase = new Derived;
    pBase->foo();
                        // class “Base” 沒有成員 “foo”
    return 0;
}


解決方案是,在父類中宣告一個虛擬函式用以向下型別轉換,在父類中給出其介面實現(否則會出現連結錯誤),在子類中自然給出其真正實現。

class Derived;  
                    // 前置宣告
class Base
{
public:
    virtual Derived& downcast() { return *(Derived* )NULL; }
    virtual const Derived& downcast() const { return *(Derived* )NULL; }
};

class Derived :public Base
{
public:
    Derived& downcast() { return *this; }
    const Derived& downcast() const { return *this;}
    void foo(){ }
};


注意,因為在父類Base要用到子類Derived類的宣告,我們需要在父類的定義之前,對子類進行前置宣告(forward declaration)。

int main(int, char**)
{
    Base* pBase = new Derived;
    pBase->downcast().foo();
                            // 通過
    return 0;
}

在論壇看到的其他的方法:

 1. ATL 裡的用法,有時間可以看一下 <深入解析 ATL> 附錄

#include <stdio.h>
 
template <typename T>
class A
{
public:
    A()
    {
        T *pT = (T *) this;
        pT->speek();
    }
};
 
class B    :    public A<B>
{
public:
    void speek()
    {
        printf("OK\n");
    }
};
 
int main()
{
    B b;
    return 0;
}

2.B “is a” A 同時 A “has a” B

#include <iostream>
using namespace std;
 
class B;
 
class A
{
private:
    B* b;
public:
    A(){};
    A(B* b);
    void fun();
};
 
class B : public A
{
public:
    B(){};
    void speek()
    {
        printf("OK\n");
    }
};
 
A::A(B* b)
{
    this->b = b;
}
 
void A::fun()
{
    b->speek();
}
 
int main()
{
    A a(new B);
    a.fun();
 
    return 0;
}

參考:https://bbs.csdn.net/topics/390171765