2015-11-19 70 views
5

更換對象我有一個包含兩個原始和自定義屬性的類:在繼承

public class Version_1 
{ 
    public string Name { get; set; } 
    public int Age { get; set; } 
    public WeirdDad Weird { get; set; } 
    public MysteriousDad Mysterious { get; set; } 
} 

在未來,我希望有一個int屬性加我的自定義對象之一的定製擴展類,在這樣:

public class Version_2 : Version_1 
{ 
    public string IdentityCode { get; set; } 
    public WeirdChild Weird { get; set; } 
} 

在Version_2類中,WeirdDad對象的外觀已被其子WeirdChild替換,所以我想替換它。 在這個例子中,我將在Version_2類中同時使用WeirdDad和WeirdChild。

你將如何實現這個例子?

+1

爲什麼你不抽象奇怪和神祕的爸爸屬性返回版本1可能的最抽象的基礎?您打算使用v2進行的更改會破壞客戶端代碼。孩子和爸爸都是「人」,因此,讓它返回人,使其虛擬,然後你可以在沒有制動客戶的情況下重寫你的怪異吸氣劑,因爲怪異也是一個人 –

+1

你正在尋找的東西叫做「隱藏」,你當你編譯上面的代碼時,可能會得到編譯器的警告,但是你通過聲明屬性「new」來明確隱藏,即:'public new WeirdChild Weird {get;組; }'但是請注意,當使用聲明爲'Version_1'的'Version_2'對象時,這可能會引起混淆,如'Version_1 child = new Version_2();','child.Wierd'將在基類上操作,而不是派生一。 –

+0

我很抱歉,但您的問題並不清楚。您不希望從'Version_2'對象的實例的基類中看到屬性'Weird',並且想隱藏它? –

回答

2

想繼承+多態性

下面是一些代碼,我會照顧做出點.. 。

public abstract class WeirdPerson() { 
    public virtual void DoThis() { 
     // base/default implementation as desired 
    } 

    public virtual void DoThat() { // ditto } 
    public abstract void GoWildAndCrazy; 
} 

public class WeirdChild : WeirdPerson { 
    public override void DoThis() { // weird child behavior} 
    // etc. 
} 

public class WeirdDad : WeirdPerson { 
    // override and add methods as needed. 
} 

public class Version_1 
{ 
    public string Name { get; protected set; } 
    public int Age { get; protected set; } 
    public WeirdPerson WeirdFamilyMember { get; protected set; } 
    public MysteriousDad Mysterious { get; protected set; } 

    public Version_1 (WeirdChild child, string name, int age, MysteriousDad Dad) { 

    } 
} 

public class Version_2 : Version_1 { 
    public Version_2 (WeirdDad dad, string name, int age, MysteriousDad grandfather) : base (dad, name, age, grandfather) {} 
} 

  • WeirdPerson - 爲所有子類型定義基本內容的更一般概念。
    • 子類型將得到「固定」之類的東西Name - 繼承
    • 子類型可以override默認行爲(方法) - 多態性
    • 現在
    • 我們沒有滑稽的東西: public class WeirdDad : WeirdChild
  • 構造
    • Version_1,Version_2構造函數強制客戶端提供正確的WeirdPerson子類型。
    • 客戶必須給我們所有必需的東西。
    • 我們可以驗證/交叉驗證傳入參數。
  • 我們不需要任何討厭的interface
    • abstract類允許我們有默認的行爲(方法)和默認狀態(性質)的任何類的
    • public方法和屬性是一般意義上的接口。我們不需要(C#關鍵字)interface爲了遵循原理代碼接口不執行
    • abstract方法強制子類實現。只是。喜歡。一個。interface
  • new
    • 警告。方法隱藏在當時斷開了繼承鏈。如果我們從這個類繼承,我們不會繼承原始基類的方法實現。
  • 里氏是幸福的。

附加重構

充分利用Version層次結構平行的WeirdPerson層次結構

假設這適合你的設計。我想,一年之後,你會很高興你做到了。

public abstract class Version_X { 
    // all the original properties here. 

    // virtual and abstract methods as needed 

    // LOOK! standard-issue constructor! 
    protected Version_X (WeirdPerson person, ...) { // common validation, etc. } 
} 

public class Version_1 : Version_X { 
    public Version_1(WeirdChild child, ...) : base (child, ...) {} 
} 

public class Version_2 : Version_X { 
    public Version_2 (WeirdDad dad, ...) {} 
} 

編輯 - 通過comment discussion with DVK

最少知識原理的啓發說,客戶不應該知道內部細節使用類。需要知道如何撰寫正確的VersionWeird是一種違規行爲,可以爭辯。

讓我們假設一個默認的構造函數,爲Visitor比方說,有必要在整體設計別處。允許這意味着客戶可以控制Version/Weird組合。

抽象 - 鬆散耦合 - 在abstract類。具體類必須正確組成,所以「強耦合」在創建具體對象(顯式類型構造函數參數)中是固有的,但底層鬆耦合允許具有所需的靈活性。

public enum WeirdFamily { Child, Dad, Mother, TheThing } 

public static class AdamsFamilyFactory() { 
    public static Version_X Create (WeirdFamily familyMember) { 
     switch (familyMember) { 
      case Dad: 
       return BuildAdamsDad(); 
     // . . . 
     } 
    } 
} 

public static class MunstersFactory() { // Munsters implementation } 

// client code 

List<Version_X> AdamsFamily = new List<Version_X>(); 
Version_X Pugsly = AdamsFamilyFactory.Create(WeirdFamily.Child); 
AdamsFamily.Add(Pugsly); 

List<Version_X> Munsters= new List<Version_X>(); 
Version_X Eddie= MunstersFactory.Create(WeirdFamily.Child); 
Munsters.Add(Eddie); 

DoTheMonsterMash(Munsters); 
DoTheMonsterMash(AdamsFamily); 

public void DoTheMonsterMash(List<Version_X> someWeirdFamily { 
    foreach (var member in someWeirdFamily) 
     member.GoWildAndCrazy(); 
} 
3

我相信你想要做的就是所謂的方法隱藏。你VERSION_2類應該是這樣的:

public class Version_2 : Version_1 
{ 
    public string IdentityCode { get; set; } 
    public new WeirdChild Weird { get; set; } 
} 

當您要訪問從VERSION_1的「古怪」屬性,你將不得不作出一個呼叫base.Weird

請記然而,這是不推薦是一種代碼味道。

+1

不應該有一個'新'在那裏的地方? – CompuChip

+0

@CompuChip糟糕。我已經添加了它xD – CodingMadeEasy

+1

很好的答案。這有點像使用彈出式座椅。是的,它是可用的,但只有在你完全沒有其他選擇的情況下才能使用。 – DVK

2

恕我直言,VERSION_2不應該繼承VERSION_1,因爲它不能完全表現像VERSION_1:

Version_2 version_2Instance = new Version_2(); 
version_2Instance.Weird = new WeirdDad(); 

不要忘記繼承假設您正在擴展的基類,而不是對矯正它。

如何使用模板類和抽象基類:

public abstract class VersionBase<T> 
{ 
    public string Name { get; set; } 
    public int Age { get; set; } 
    public abstract T Weird { get; set; } 
    public MysteriousDad Mysterious { get; set; } 
} 

然後:

public class Version_1 : VersionBase<WeirdDad> 
{ 
    public override WeirdDad Weird { get; set; } 
} 

public class Version_2 : VersionBase<WeirdChild> 
{ 
    public string IdentityCode { get; set; } 
    public override WeirdChild Weird { get; set; } 
} 
+0

如果只有一個T對象(怪異),這應該很好。但我有幾個這些自定義對象。 –

1

你需要重新考慮你正在嘗試做的一點點事情,因爲你所提議打破Liskov替換校長。

來源:https://en.wikipedia.org/wiki/Liskov_substitution_principle

可替代性是面向對象編程的一個原則。它指出,在一個計算機程序中,如果S是T的一個子類型,那麼類型T的對象可以用類型S的對象代替(即,類型S的對象可以替換類型T的對象),而不會改變任何合意的該程序的屬性(正確性,執行的任務等)。

如果只想VERSION_2包含一個WeirdChild,而不是任何類型實現WeirdDad類的,這是可以做到的,但你需要備份的水平,並開始在思維泛型,接口,和/或抽象類。

例如:

public interface IWeirdFamilyMember 
{ 
    // put some properties here 
} 

public interface IMyClass<T> where T: IWeirdFamilyMember 
{ 
    string Name { get; set; } 
    int Age { get; set; } 
    T Weird { get; set; } 
} 

最後,你可以定義你的階級作爲階級:

public class Version_1 : IMyClass<WeirdDad> 
{ 
    string Name { get; set; } 
    int Age { get; set; } 
    WeirdDad Weird { get; set; } 
} 

public class Version_2 : IMyClass<WeirdChild> 
{ 
    string Name { get; set; } 
    int Age { get; set; } 
    WeirdChild Weird { get; set; } 
} 

與Ksv3n的例子的問題是,你是假設開發商分配WeirdDad的WeirdChild子到奇怪的財產,實際上,它可以是任何類型的WeirdDad,或者一個WeirdDad本身。這是運行時異常的祕訣。

+0

*假設開發人員正在分配WeirdChild * - 如果存在需要所需子類類型的'Version_2'構造函數,我們可以假設這一點。這種情況對於封裝來說是一個很好的課堂教訓:不要讓客戶端直接設置屬性(通常狀態)。 – radarbob

+0

@radarbob是的,你可以將默認構造函數定義爲private,使Weird屬性成爲只讀屬性,並創建一個構造函數,該構造函數採用正確的派生對象類型,然後引入其他複雜性並可能破壞依賴於無參數構造函數。總的來說,你最終緊密耦合了大量的代碼。 – DVK

+0

** 1。**給客戶一個默認的構造函數意味着我們必須允許客戶設置屬性 - 這就是問題所在。 ** 2。**如果失去耦合意味着可以組成錯誤的(子)類型,那麼當然需要更緊密的耦合。 ** 3。**現在,更緊密的耦合已經存在 - 這是隱含的,因爲錯誤的'Weird'對象會破壞代碼。因此,迫使客戶做正確的事情*每個設計*不會使耦合更緊密,它使代碼少bug4 **。**,它堅持最不瞭解的原則。* – radarbob

0

我同意@CodingMadeEasy,使用新是一個嗅覺代碼。

因此,我建議使用顯式接口實現。

interface IVersion_1 
{ 
    WeirdDad Weird { get; set; } 
} 


interface IVersion_2 
{ 
    WeirdChild Weird { get; set; } 
} 

class Version_1 : IVersion_1 
{ 
    public string Name { get; set; } 
    public int Age { get; set; } 
    public WeirdDad Weird { get; set; } 
    public MysteriousDad Mysterious { get; set; } 
} 

class Version_2 : Version_1, IVersion_2 
{ 
    public string IdentityCode { get; set; } 
    WeirdChild IVersion_2.Weird { get; set; } 
} 

所以在客戶端,這取決於接口的類型,正確的屬性將被稱爲