如何通過父類引用“呼叫”子類所獨有的方法
最近看書,看到向上引用的情況:派生類引用或指標轉換為基類引用或指標被稱為向上強制轉換。
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;
}