2010-10-22 97 views
4

使用案例:我正在使用數據模板將視圖與ViewModel進行匹配。數據模板通過檢查所提供的具體類型的最派生類型來工作,並且它們不查看它提供的接口,所以我必須在沒有接口的情況下執行此操作。這是違反Liskov替代原則嗎?如果是這樣,我該怎麼辦?

我在這裏簡化了這個例子,而忽略了NotifyPropertyChanged等,但在現實世界中,一個View將綁定到Text屬性。爲了簡單起見,假設帶有TextBlock的視圖將綁定到ReadOnlyText,並且帶有TextBox的View將綁定到WritableText。

class ReadOnlyText 
{ 
    private string text = string.Empty; 

    public string Text 
    { 
     get { return text; } 
     set 
     { 
      OnTextSet(value); 
     } 
    } 

    protected virtual void OnTextSet(string value) 
    { 
     throw new InvalidOperationException("Text is readonly."); 
    } 

    protected void SetText(string value) 
    { 
     text = value; 
     // in reality we'd NotifyPropertyChanged in here 
    } 
} 

class WritableText : ReadOnlyText 
{ 
    protected override void OnTextSet(string value) 
    { 
     // call out to business logic here, validation, etc. 
     SetText(value); 
    } 
} 

通過重寫OnTextSet並改變其行爲,我是不是違反了LSP?如果是這樣,有什麼更好的方法來做到這一點?

+0

http://en.wikipedia.org/wiki/Liskov_substitution_principle(對於那些還沒有喝過咖啡的人) – 2010-10-22 12:41:20

+0

@SomeMiscGuy:對不起,添加鏈接:) – 2010-10-22 12:42:49

+0

順便說一下,有可能解決一個基於數據模板在實現接口的類上使用DataTemplateSelector。這對我來說效果很好:http://complexdatatemplates.codeplex.com/ – 2010-10-22 13:11:36

回答

9

LSP指出一個子類應該是它的超類的子類(參見stackoverflow問題here)。問自己的問題是,「可寫文本是一種只讀文本?」答案顯然是「不」,實際上這些是相互排斥的。所以,是的,這段代碼違反了LSP。但是,可寫文本是一種可讀文本(不是隻讀文本)?答案是「是」。所以,我認爲答案是看它是什麼你想在每一種情況下做的,並可能改變抽象有點如下:

class ReadableText 
{ 
    private string text = string.Empty; 
    public ReadableText(string value) 
    { 
     text = value; 
    } 

    public string Text 
    { 
     get { return text; } 
    } 
}   

class WriteableText : ReadableText 
{ 
    public WriteableText(string value):base(value) 
    { 

    } 

    public new string Text 
    { 
     set 
     { 
      OnTextSet(value); 
     } 
     get 
     { 
      return base.Text; 
     } 
    } 
    public void SetText(string value) 
    { 
     Text = value; 
     // in reality we'd NotifyPropertyChanged in here  
    } 
    public void OnTextSet(string value) 
    { 
     // call out to business logic here, validation, etc.  
     SetText(value); 
    } 
}  

只是要清楚,我們隱藏的文本使用Writeable類中的Text屬性上的new關鍵字從Readable類獲取屬性。
http://msdn.microsoft.com/en-us/library/ms173152(VS.80).aspx: 使用new關鍵字時,會調用新的類成員,而不是已被替換的基類成員。這些基類成員被稱爲隱藏成員。如果將派生類的實例轉換爲基類的實例,則仍然可以調用隱藏類成員。

+0

這不完全編譯,但可以修改工作。我不知道你可以在派生類中爲基類中的getter屬性定義屬性設置器。我學到了一些新東西。這將工作。謝謝! – 2010-10-22 13:05:18

+0

有趣的是,我需要將派生類中的Text屬性聲明爲「new」以避免編譯器警告,但在我看來,它並不是真正新的,因爲我不重寫getter。 – 2010-10-22 13:18:33

+0

我不相信你想讓SetText和OnTextSet公開 – 2010-10-22 13:19:29

8

只有ReadOnlyText.OnTextSet()的規格承諾會拋出。

想像這樣

public void F(ReadOnlyText t, string value) 
{ 
    t.OnTextSet(value); 
} 

代碼是否有意義給你,如果這個沒扔?如果不是,則WritableText不可替代。

它看起來像WritableText應該從文本繼承。如果有ReadOnlyTextWritableText之間的一些共享代碼,把它放在文本或其它類,它們都繼承(從Text繼承)

+0

根據我的具體問題你是正確的,但布蘭登指出了我對屬性設置者和獲取者的誤解,這讓我更加優雅地解決了這個問題。謝謝(你的)信息。 – 2010-10-22 13:12:09

2

我想說取決於合同。

如果ReadOnlyText的合同顯示「任何設置文本的嘗試都會拋出異常」,那麼肯定是違反了LSP。

如果不是,您的代碼仍然有一個尷尬:一個只讀文本的setter。

這是在給定情況下可接受的「反規範化」。我還沒有找到一種不依賴大量代碼的更好方法。在大多數情況下,Clean界面將是:

IThingieReader 
{ 
    string Text { get; } 
    string Subtext { get; } 
    // ... 
} 

IThingieWriter 
{ 
    string Text { get; set; } 
    string Subtext { get; set; } 
    // ... 
} 

...並僅在適當的時候實現接口。然而,如果你必須處理例如Text是可寫的,並且Subtext不是,對許多對象/屬性來說這是一件很痛苦的事情。

+0

正如我所說,這將是理想的,但我不能使用接口,因爲數據模板不會關閉接口,他們需要一個具體的類型。 – 2010-10-22 12:59:32

0

是的,它不會,如果受保護的覆蓋無效的OnTextSet(字符串值)也拋出了一個類型爲「InvalidOperationException」或從它繼承的異常。

你應該有一個基類Text和ReadOnlyText和WritableText都從它繼承。

相關問題