2017-08-03 140 views
1

我有接口的實現,我必須解決: 下面是一個例子:實現接口擴展屬性(?繼承)

interface ISettingsBase 
{ 
    string Name { get; set;} 
    DateTime TimeStamp { get; set; } 
} 

public class SettingsBase : ISettingsBase 
{ 
    public string Name { get; set; } 
    public DateTime TimeStamp { get; set; } 
} 

interface IWorkerBase 
{ 
    ISettingsBase Settings { get; set; } 
} 

public class WorkerBase: ISettingsBase 
{ 
    public ISettingsBase Settings { get; set; } 
} 

interface IExtendedSettings : ISettingsBase 
{ 
    string FilePath { get; set; } 
} 

interface IWorkerExtended : IWorkerBase 
{ 
    // This configuration property should respect those of 
    // the IWorkerBase and increase the features. 
    IExtendedSettings Settings { get; set; } 
} 

public class WorkerExtended : WorkerBase, IWorkerExtended 
{ 
    ... 
    ... 
    public IExtendedSettings Settings { get; set; } 
} 

的問題是,編譯器告訴我,有一個錯誤在WorkerExtended,並且我不尊重IWorkerBase.Settings接口的實現。問題是我需要新的改進類還支持具有更多屬性的配置。

+0

它只是感覺你有太多的接口和太多的實現。你的客戶如何知道它有一個'WorkerExtended'實例(而不僅僅是'IWorkerBase'的一個實例),調用它的'Settings'會返回一個'IExtendedSettings'實例? – user270576

+0

@ user270576對不起,我應該在這個例子中更詳細一些,我不能複製原始代碼,因爲它更復雜一些。實際上,WorkerBase應該是一個具有一些基本功能的抽象類。我認爲最初的想法是強制工人有一個基本的配置和通用的功能,但變化使他們不同,所以他們也有額外的配置。 – Cheva

+0

仍然不能回答我的問題:消費者類將如何知道它有'IWorkerExtended'實例而不是'IWorkerBase'實例?它如何知道調用'myWorker.Settings.FilePath'? – user270576

回答

4
interface IWorkerBase 
{ 
    ISettingsBase Settings { get; set; } 
} 

interface IWorkerExtended : IWorkerBase 
{ 
    IExtendedSettings Settings { get; set; } 
} 

這已經是有問題的,因爲IWorkerExtended.Settings隱藏Settings構件IWorkerBase需要。因此,IWorkerExtended的實施者仍然必須提供原始的Settings成員(類型ISettingsBase)以便可傳遞地執行IWorkerBase

編譯器會在這裏警告你,因爲這裏隱藏了這個成員。通常這樣做將是一個錯誤,所以你需要使用new鍵盤來表達你心甘情願要做到這一點:

interface IWorkerExtended : IWorkerBase 
{ 
    new IExtendedSettings Settings { get; set; } 
} 

注意,這不會對實施者雖然有影響。所以當實現這個接口時,你將不得不提供兩個Settings成員。你可以做到這一點的明確實施的基本接口:

public class Worker : IWorkerExtended 
{ 
    public IExtendedSettings Settings { get; set; } 

    ISettingsBase IWorkerBase.Settings { get; set; } 
} 

這樣做的原因很簡單:Liskov substitution principle說,當IWorkerExtendedIWorkerBase一個亞型,然後IWorkerBase類型的任何對象都可以通過一個對象所取代類型爲IWorkerExtended。現在考慮這個:

IWorkerBase baseWorker = GetExtendedWorker(); 
baseWorker.Settings = new SettingsBase(); // not extended 

此,如果你可以將只會工作一個SettingsBaseSettings這是什麼IWorkerBase類型擔保(因爲SettingsBaseISettingsBase亞型)。因此,爲了讓擴展工作人員可以分配給基礎工作人員類型,它需要保證您可以爲其設置一個ISettingsBase。但是,如果它只是實現IWorkerExtended,則可以將更具體的擴展設置分配給Settings

+0

正如我在之前對user270576的評論中所說的那樣。我認爲最初的想法是強制工人有一個基本的配置和通用的功能,但變化使他們不同,所以他們也有額外的配置。問題是,這種方法不好嗎? 「新」關鍵詞將作爲一種修正來應用?我的意思是有更正確的方法來解決問題嗎? – Cheva

+1

在沒有更多瞭解系統的情況下,設計這種方法很困難。但是你能*做的是分開擴展工作者和擴展設置之間的嚴格關係。每個工人類型只會暴露「ISettingsBase設置」。而這些設置也可以是更簡單的或更加擴展的設置。如果擴展工作人員本身也依賴於擴展設置,那麼使用不同名稱的單獨成員比隱藏其他成員更合適。 - 但是,這裏的「正確」是什麼很難說...... – poke

0

接口意味着隱藏實現細節,而不是告訴消費者「哦,是的,這傢伙有一些額外的領域」。

作爲一項規則,像if (worker is IWorkerExtended)這樣的鑄造或類型檢查是一個標誌,根本不需要接口。

我會說,在這種情況下,您最好是隻有一堆worker類實現一個空的IWorker接口。

喜歡的東西:

public interface IWorker 
{ 
} 

public class SettingsBase 
{ 
    public string Name { get; set; } 
    public DateTime TimeStamp { get; set; } 
} 

public class ExtendedSettingsA : SettingsBase 
{ 
    public string FilePath { get; set; } 
} 

public class ExtendedSettingsB : SettingsBase 
{ 
    public string SomeOtherProp { get; set; } 
} 

public class WorkerBase : IWorker 
{ 
    public SettingsBase Settings { get; set; } 
} 

public class WorkerExtendedA : IWorker 
{ 
    public ExtendedSettingsA Settings { get; set; } 
} 

public class WorkerExtendedB : IWorker 
{ 
    public ExtendedSettingsB Settings { get; set; } 
} 


public class XmlLoader 
{ 
    public IWorker Load(string xml) 
    { 
     return null; // instance of WorkerBase or WorkerExtendedA or WorkerExtendedB 
    } 
} 

public class Consumer 
{ 
    public void Process(IWorker w) 
    { 
     if (w is WorkerBase) 
     { 
      WorkerBase wbase = w as WorkerBase; 
      string name = wbase.Settings.Name; 
      DateTime t = wbase.Settings.TimeStamp; 
     } 
     if (w is WorkerExtendedA) 
     { 
      WorkerExtendedA wa = w as WorkerExtendedA; 
      string name = wa.Settings.Name; 
      DateTime t = wa.Settings.TimeStamp; 
      string f = wa.Settings.FilePath; 
     } 
     if (w is WorkerExtendedB) 
     { 
      WorkerExtendedB wb = w as WorkerExtendedB; 
      string name = wb.Settings.Name; 
      DateTime t = wb.Settings.TimeStamp; 
      string other = wb.Settings.SomeOtherProp; 
     } 
    } 
} 
+0

我也不認爲這是一個可行的解決方案,因爲實際上,除了必須序列化的設置之外,工作者已經建立了方法和屬性。這些常用方法和道具必須受合同約束。 – Cheva

+0

這就是爲什麼界面被定義,爲了能夠添加新的工人和更好的做事方式。即:一名工作人員可以執行一個需要計算的進程,另一個改進的工作人員可以做同樣的事情,但調用GPU,兩者的有效實施方式不同,所有這些都避免了硬編碼,我認爲可以使用可用工作人員列表反射或使用註冊帶有額外庫的工人的方法。 – Cheva

+0

@Cheva將相同的概念應用於'Settings',然後:在'IWorkerBase'中有'Settings'方法返回'ISettings',不要重載此方法並且不要重載'ISettings'。讓你的序列化對象直接實現'ISettings'。在你的消費者而不是'if(worker is IWorkerExtended)'do if(worker.Settings is ExtendedSettingsA)' – user270576