你的評論有兩個誤解。
首先,派生類中的構造函數不會在基類中覆蓋覆蓋構造函數。相反,他們給他們打電話。 覆蓋一種方法意味着它被稱爲而不是一個基類的等價方法,不如。
其次,總是有一個叫基礎構造函數。任何沒有明確調用基礎構造函數的構造函數都會隱式調用無參數基礎構造函數。
您可以通過刪除A()
構造函數或將其設爲私有來演示此操作。不調用基類構造函數的B()
構造函數現在將成爲編譯器錯誤,因爲沒有可用於隱式調用的無參數構造函數。
由於B
從A
衍生B
是一個A
。因此B
不能成功構建沒有A
正在構建(如建造一輛跑車沒有建立一輛車)。
施工發生在基地優先。 A
的構造函數負責確保A
的實例完全構造並處於有效狀態。這是B
的構造函數將B
的實例置於有效狀態的起點。
至於的想法:
或者我應該有一個OnInitialize()中鍵入方法A是其在乙重寫並從受保護的,無參數的構造或者叫做抽象或虛擬一個?
絕對不是。在調用A
的構造函數時,B
的其餘部分尚未構造。然而,初始化者已經運行。這導致真正討厭的情況,如:
public abstract class A
{
private int _theVitalValue;
public A()
{
_theVitalValue = TheValueDecider();
}
protected abstract int TheValueDecider();
public int TheImportantValue
{
get { return _theVitalValue; }
}
}
public class B : A
{
private readonly int _theValueMemoiser;
public B(int val)
{
_theValueMemoiser = val;
}
protected override int TheValueDecider()
{
return _theValueMemoiser;
}
}
void Main()
{
B b = new B(93);
Console.WriteLine(b.TheImportantValue); // Writes "0"!
}
在當A
調用B
覆蓋虛擬方法時,B
的initialisers已經運行,但不是它的構造函數的其餘部分。因此虛擬方法正在B
上運行,該狀態不是有效的狀態。
確實有可能使這種方法有效,但它也很容易導致很多痛苦。
相反:
定義你的類,使他們每個總是留下它們的構造在一個完全初始化和有效狀態。正是在這一點上,你爲這個類設置了不變量(一個不變量是一組必須始終保持真實的條件,在任何時候都可以從外部觀察一個類。例如,對於內部持有月日的日期類來說是可以的在單獨的字段中的值暫時處於2月31日的狀態,但是它不能在構建結束時或者在方法的開始或結束時處於這種狀態,因爲調用代碼會期望它是日期)。
抽象類可以依賴派生類來完成它的rôle,但不是它的初始狀態。如果一個設計正在朝着這個方向發展,那麼將這個狀態的責任進一步降低到惡作劇。例如,考慮:
public abstract class User
{
private bool _hasFullAccess;
protected User()
{
_hasFullAccess = CanSeeOtherUsersItems && CanEdit;
}
public bool HasFullAccess
{
get { return _hasFullAccess; }
}
protected abstract bool CanSeeOtherUsersItems {get;}
protected abstract bool CanEdit {get;}
}
public class Admin : User
{
protected override bool CanSeeOtherUsersItems
{
get { return true; }
}
protected override bool CanEdit
{
get { return true; }
}
}
public class Auditor : User
{
protected override bool CanSeeOtherUsersItems
{
get { return true; }
}
protected override bool CanEdit
{
get { return false; }
}
}
這裏,我們根據信息在派生類設置基類的狀態,並在此狀態下完成的任務。這將在這裏起作用,但如果屬性依賴於派生類中的狀態(這對於創建新的派生類來承擔某些事情被允許是完全合理的),則不會這樣。取而代之的是,我們做的功能,但不是國家依賴於派生類通過改變User
到:
public abstract class User
{
public bool HasFullAccess
{
get { return CanSeeOtherUsersItems && CanEdit; }
}
protected abstract bool CanSeeOtherUsersItems {get;}
protected abstract bool CanEdit {get;}
}
(如果我們預計此類電話要貴有時,我們仍然可以在第一次通話memoise的結果,因爲這隻能在完全構造的User
中發生,並且在回憶錄之前和之後的狀態都是有效且準確的狀態,以便User
處於)。
定義了一個基類以使其構造函數保持有效狀態後,我們對派生類也做同樣的事情。它可以依賴於它的基類在它的構造函數中的狀態,因爲它的基類是完全構造的,並且它正在被構建的第1步。它在初始階段不能依賴於這個,儘管它不會像人們想要嘗試的東西那樣出現。
要返回的預期輸出:
A()
B()
A(1)
B(1)
這意味着你想把東西放在一個有效的起始狀態,然後進入不同的有效起始狀態。這沒有任何意義。
現在,我們當然可以使用this()
表單從構造函數中調用構造函數,但應該將其純粹視爲便於保存輸入。它的意思是「這個構造函數完成所有被調用的功能,然後是一些」,除此之外別無其他。對外界來說,只有一個構造函數曾經被調用過。在沒有這種便利的語言,我們要麼重複碼或做:
private void repeatedSetupCode(int someVal, string someOtherVal)
{
someMember = someVal;
someOtherMember = someOtherVal;
}
public A(int someVal, string someOtherVal)
{
repeatedSetupCode(someVal, someOtherVal);
}
public A(int someVal)
{
repeatedSetupCode(someVal, null);
}
public A()
{
repeatedSetupCode(0, null);
}
這是很好的,我們有this()
。它不會節省太多的輸入,但它確實表明所討論的內容屬於對象生命週期的構建階段。但如果我們真的需要,我們可以使用上面的表格,甚至可以保護repeatedSetupCode
。
雖然我會非常謹慎。以任何方式處理對方構造的對象,除了根據基類的不變性調用將基類放入有效狀態所需的單個基構造函數之外,是一種難聞的氣味,表明完全不同的方法可能會更好。
你不會調用'B()',那麼你爲什麼期望在輸出中看到? –
我希望或者希望有一種方法可以將'B()'標記爲'A()'的重寫。我不能從'B(int)'調用'B()',因爲'A'中的構造函數都不會被調用。 – Hugo
這是不正確的。在這種情況下,A的默認構造函數將運行。 –