本文章是由機器翻譯。

C# 最佳作法

在 C# 中違反 SOLID 原則的風險

Brannon King

隨著進程的寫作軟體從理論領土發展演變成為一個真正的工程學科,已經出現的一些原則。 當我說的原則,我所指的電腦代碼,有助於維護該代碼的值的一個特點。 模式是指一種常見的代碼情況,或好或壞。

例如,您可能價值工程安全地在多執行緒環境中的電腦代碼。 你可能價值不會崩潰當您修改代碼,在另一位置的電腦代碼。 事實上,你可能價值許多樂於助人的品質在您的電腦代碼,但會遇到在日常基礎上的對面。

已捕獲下堅實的首字母縮寫詞的一些神奇軟體發展原則 — — 單的責任,對擴展開放和封閉的改性、 Liskov 替代、 介面隔離和依賴關係注入。 你應該有一些熟悉這些原則,正如我將演示 C# 的各種-違反這些原則的具體模式。 如果你熟悉的固體的原則,可能要審查這些快速之前。 我也會承擔一些熟悉模型和 ViewModel 的建築用語。

固體的首字母縮寫詞和囊括的原則不是和我一起起源。 謝謝你,羅伯特 C. 馬丁、Michael羽毛、 貝特朗 · 邁耶、JamesCoplien 和其他人與其他人分享你的智慧。 許多其他書籍和博客文章探討和完善這些原則。 我希望能説明擴大應用這些原則。

有與合作和培訓了很多初級軟體工程師,我發現第一專業編碼的努力和可持續代碼之間有很大的差距。 在本文中,我會嘗試以輕鬆的方式這一差距。 這些示例並與的目標是説明您識別您可以對所有形式的軟體應用的堅實的原則有點傻。

專業發展環境為有抱負的軟體工程師帶來的許多挑戰。 你的學業已經教會你想從自上而下的角度看問題。 你會帶自上而下方法去世界豐盛、 中小型企業軟體的初始分配給您的。 你會很快發現你頂級函數已經增長到一個笨重的大小。 要使最小的更改需要的整個系統,完整的工作知識和有一點把它放在檢查。 指導軟體原則 (其中只有部分集這裡有提及) 將説明保持結構從超出它的基礎。

單一責任原則

單一責任原則通常定義為:一個物件應該只有一個原因要更改 ; 時間越長的檔或類,更難它將是實現這一目標。 銘記這一定義,看看這段代碼:

public IList<IList<Nerd>> ComputeNerdClusters(
  List<Nerd> nerds,
  IPlotter plotter = null) {
  ...
foreach (var nerd in nerds) {
    ...
if (plotter != null)
      plotter.Draw(nerd.Location, 
      Brushes.PeachPuff, radius: 10);
    ...
}
  ...
}

此代碼有什麼毛病? 是軟體正在寫入或調試嗎? 它可能是此特定的繪圖代碼僅用於調試目的。 它是在已知只能通過介面,服務,但它不屬於不錯。 畫筆是一個很好的線索。 美麗、 廣泛如朵朵桃花的可能是,它是特定于平臺。 它是在此計算模型的類型層次結構以外。 有許多方法來隔離的計算和關聯的調試實用程式。 至少,在你可以公開通過繼承或事件所需的資料。 將測試和測試意見分開。

這裡是另一個錯誤示例:

class Nerd {
  public int IQ { get; protected set; }
  public double SuspenderTension { get; set; }
  public double Radius { get; protected set; }
  /// <summary>Get books for growing IQ</summary>
  public event Func<Nerd, IBook> InTheMoodForBook;
  /// <summary>Get recommendations for growing Radius</summary>
  public event Func<Nerd, ISweet> InTheMoodForTwink;
  public IList<Nerd> FitNerdsIntoPaddedRoom(
    IList<Nerd> nerds, IList<Point> boundary)
  {
    ...
}
}

此代碼有什麼毛病? 它混合了所謂的"學校科目"。還記得你是如何瞭解不同的主題在不同的班級在學校嗎? 它是重要的是保持在代碼中的分離 — — 不是因為他們是完全無關,但作為組織的努力。 一般情況下,不要把這些專案的任何兩個放在相同的類:數學、 模式、 語法、 意見、 體育或平臺配接器、 客戶特定的代碼,等等。

你可以看到一般的比喻對事情您構建學校與雕塑、 木材和金屬。 他們需要測量、 分析、 指導,等等。 前面的示例中混合了數學和模型 — — FitNerdsIntoPaddedRoom 不屬於。 這種方法可以輕鬆地移動到一個實用程式類,甚至是一種靜態。 你不應該來具現化您的數學測試常式中的模型。

這裡的另一個職責的多個示例:

class AvatarBotPath
{
  public IReadOnlyList<ISegment> Segments { get; private set; }
  public double TargetVelocity { get; set; }
  public bool IsReverse { get { return TargetVelocity < 0; } }
  ...
}
public interface ISegment // Elsewhere
{
  Point Start { get; }
  Point End { get; }
  ...
}

在這裡有什麼問題? 很明顯有兩個不同的抽象介面由一個單一的物件表示。 其中之一是關乎遍歷一個形狀,其他代表的幾何形狀本身。 這是常見的代碼。 你有一種表示形式,該表示形式和一起去的單獨使用的特定參數。

繼承是你的朋友在這裡。 你可以移動到繼承人的 TargetVelocity 和 IsReverse 的屬性和捕獲它們在一個簡明的 IHasTravelInfo 介面。 或者,您可以向形狀添加一般的功能的集合。 那些需要速度將查詢功能集合以查看它是否定義特定的形狀上。 你也可以使用其他一些集合機制對申述帶旅行參數。

Open Closed Principle

這引出了下一個原則:可以擴展,對修改關閉。 它是怎麼做的呢? 最好是不是這樣:

void DrawNerd(Nerd nerd) {
  if (nerd.IsSelected)
    DrawEllipseAroundNerd(nerd.Position, nerd.Radius);
  if (nerd.Image != null)
    DrawImageOfNerd(nerd.Image, nerd.Position, nerd.Heading);
  if (nerd is IHasBelt) // a rare occurrence
    DrawBelt(((IHasBelt)nerd).Belt);
  // Etc.
}

在這裡有什麼問題? 好,你要修改此方法每次客戶需要顯示的新事物 — — 他們總是需要顯示的新事物。 幾乎每個新的軟體功能,需要某種形式的使用者介面元素。 畢竟,它是缺乏促使新功能要求的現有介面中的某些東西。 顯示在此方法中的圖案是一個好的線索,但您可以移動那些如果入方法聲明他們守衛和它不會使問題消失。

您需要更好的計畫,但是如何? 它看起來會像? 好,你有一些代碼,知道如何繪製某些東西。 好的。 你只需要匹配的那些東西用代碼來繪製它們的一般過程。 它基本上會下來像這樣一種模式:

readonly IList<IRenderer> _renderers = new List<IRenderer>();
void Draw(Nerd nerd)
{
  foreach (var renderer in _renderers)
    renderer.DrawIfPossible(_context, nerd);
}

有其他方法將添加到清單的渲染。 點的代碼,但是,是編寫繪圖類 (或關於繪圖類的類),實現了一個知名的介面。 渲染器必須有 smarts 能夠確定是否它可以或應該繪製任何基於其輸入。 例如,帶繪圖代碼可以將移動到其自身"帶呈現"器檢查的介面和收益如果必要。

您可能需要 CanDraw 分開 Draw 方法,但是,不會違反開放封閉原則或 OCP。 使用渲染的代碼不應該更改如果您添加新的渲染器。 就是這麼簡單。 你應該也能夠添加新的渲染器按正確的順序。 雖然我使用呈現作為一個例子,這也適用于處理輸入、 資料處理和存儲資料。 這項原則有許多應用程式通過所有類型的軟體。 模式是更難效仿在Windows Presentation Foundation(WPF),但它是可能。 請參閱圖 1 的一個可能的選擇。

圖 1 的合併到單個源的Windows Presentation Foundation呈現程式示例

public abstract class RenderDefinition : ViewModelBase
{
  public abstract DataTemplate Template { get; }
  public abstract Style TemplateStyle { get; }
  public abstract bool SourceContains(object o); // For selectors
  public abstract IEnumerable Source { get; }
}
public void LoadItemsControlFromRenderers(
    ItemsControl control,
    IEnumerable<RenderDefinition> defs) {
  control.ItemTemplateSelector = new DefTemplateSelector(defs);
  control.ItemContainerStyleSelector = new DefStyleSelector(defs);
  var compositeCollection = new CompositeCollection();
  foreach (var renderDefinition in defs)
  {
    var container = new CollectionContainer
    {
      Collection = renderDefinition.Source
    };
    compositeCollection.Add(container);
  }
  control.ItemsSource = compositeCollection;
}

這裡是另一個犯規示例:

class Nerd
{
  public void WriteName(string name)
  {
    var pocketProtector = new PocketProtector();
    WriteNameOnPaper(pocketProtector.Pen, name);
  }
  private void WriteNameOnPaper(Pen pen, string text)
  {
    ...
}
}

在這裡有什麼問題? 與此代碼的問題是巨大和雜物。 我想指出的主要問題是有沒有辦法重寫創建 PocketProtector 實例。 這樣的代碼,它很難寫繼承者。 你有處理這種情況下的幾個選項。 您可以更改的代碼:

  • 使該 WriteName 方法虛擬。 這也需要你讓 WriteNameOnPaper 保護,滿足具現化改性的口袋保護的目標。
  • 公開的 WriteNameOnPaper 方法,但是這將保持你繼承者中破碎的 WriteName 方法。 這不是一個不錯的選擇,除非你擺脫的 WriteName,在其中案例選擇交給入將 PocketProtector 的實例傳遞到該方法。
  • 添加其他受保護的虛擬方法其唯一的目的就是要構建 PocketProtector。
  • 給類泛型型別 T,是一種類型的 PocketProtector,並構造它與某種物件工廠。 然後你就會有相同需要注入物件工廠。
  • 將 PocketProtector 的實例傳遞到此類的建構函式中或通過一個公共的屬性,而不是構建它的類中。

所列的最後一個選項通常是最好的計畫,假設您可以重複使用 PocketProtector。 虛擬創建方法也是一個很好和很容易的選擇。

您應該考慮哪些方法使虛擬以容納 OCP。 這一決定是經常離開直到最後一分鐘:"我會做方法虛擬時我需要給他們打電話從我目前沒有傳人"其他人可能會選擇,使每個方法虛擬的希望這將使擴展器周圍的初始代碼在任何監督工作的能力。

兩種方法都是錯誤的。 他們體現了無法提交到開放的介面。 有太多的虛擬方法限制您以後更改代碼的能力。 您可以重寫的方法缺乏限制了可擴充性和代碼的再使用性。 這限制了其實用性和使用壽命。

這裡是 OCP 侵犯行為的另一個常見的例子:

class Nerd
{
  public void DanceTheDisco()
  {
    if (this is ChildOfNerd)
            throw new CoordinationException("Can't");
    ...
}
}
class ChildOfNerd : Nerd { ...
}

在這裡有什麼問題? 書呆子有對它的子類型的硬引用。 這是痛苦到見和初級開發人員不幸的是常見的錯誤。 你可以看到它違反了 OCP。 您必須修改多個類,以提高或重構 ChildOfNerd。

基類應從不直接引用其繼承者。 然後傳人功能不再是繼承者之間保持一致。 避免這種衝突的好方法是把一個類的繼承者放在單獨的專案。 這種專案的引用樹的結構將不允許這種不幸的情況。

這個問題不是局限于父-子關係。 它具有對等類,以及存在。 假設你有這樣的事情:

class NerdsInAnArc
{
  public bool Intersects(NerdsInAnLine line)
  {
    ...
}
  ...
}

弧形和線通常是同行中的物件層次結構。 他們不應該知道任何非繼承的私密細節彼此,那些細節,往往需要對最優交集演算法。 讓自己自由修改而無需更改另一個。 這也會帶來單一責任違反。 你是存儲弧形或分析他們嗎? 分析操作放入他們自己的實用程式類。

如果您需要這種特定的跨同行能力,你就需要引入適當的介面。 遵循此規則以避免跨實體混淆:您應使用"是"關鍵字而不是具體的類的抽象。 雖然您可能仍會推遲到一些其他交集的公用程式類分析資料暴露在介面上,可能可以定制的示例中,IIntersectable 或 INerdsInAPattern 的介面。

替換原則

替換原則定義用於維護傳人替代一些準則。 傳遞代替基類物件的繼承人不應該中斷任何現有的功能調用的方法中。 你應該能夠替換給定的介面與對方的所有實現。

C# 不允許修改的返回類型或參數類型的重寫方法 (即使返回類型是繼承的基類中的返回類型)。 因此,它不會苦苦掙扎的最常見的替代侵犯:逆變的方法 (overriders 必須有的父級方法相同或基類型) 的參數和返回類型 (在重寫的方法的返回類型必須是相同的或繼承的基類中的返回類型) 的共變數。 但是,它是共同來嘗試解決此限制:

class Nerd : Mammal {
  public double Diopter { get; protected set; }
  public Nerd(int vertebrae, double diopter)
    : base(vertebrae) { Diopter = diopter; }
  protected Nerd(Nerd toBeCloned)
    : base (toBeCloned) { Diopter = toBeCloned.Diopter; }
  // Would prefer to return Nerd instead:
  // public override Mammal Clone() { return new Nerd(this); }
  public new Nerd Clone() { return new Nerd(this); }
}

在這裡有什麼問題? 行為的物件發生更改時調用使用一個抽象的參考。 克隆方法新不是虛擬的並因此不執行時使用的哺乳動物的引用。 新的方法聲明上下文中的關鍵字是假想的功能。 如果你不控制類的基類,不過,你怎能保證妥善執行?

C# 具有幾個可行的替代辦法,儘管他們仍然有點令人反感。 可以使用泛型介面 (有點像 IComparable < T >),在每個繼承人顯式實現。 但是,您仍然需要一個不會實際的克隆操作的虛方法。 你需要這個,所以你的克隆與派生的類型相匹配。 C# 還支援 Liskov 標準逆變的返回類型和方法參數的共變數時使用的事件,但那不會幫你更改通過類繼承的公開的介面。

從該代碼,您可能會認為 C# 中的類方法衝突解決程式使用的方法足跡包括返回類型。 這是不正確 — — 你不能與不同的返回類型,但相同的名稱和輸入的類型有多個覆蓋。 方法約束的方法解析也將被忽略。 圖 2 顯示的語法上正確的代碼不會編譯由於方法含糊不清的一個示例。

圖 2 含糊不清的方法足跡

interface INerd {
  public int Smartness { get; set; }
}
static class Program
{
  public static string RecallSomeDigitsOfPi<T>(
    this IList<T> nerdSmartnesses) where T : int
  {
    var smartest = nerdSmartnesses.Max();
    return Math.PI.ToString("F" + Math.Min(14, smartest));
  }
  public static string RecallSomeDigitsOfPi<T>(
    this IList<T> nerds) where T : INerd
  {
    var smartest = nerds.OrderByDescending(n => n.Smartness).First();
    return Math.PI.ToString("F" + Math.Min(14, smartest.Smartness));
  }
  static void Main(string[] args)
  {
    IList<int> list = new List<int> { 2, 3, 4 };
    var digits = list.RecallSomeDigitsOfPi();
    Console.WriteLine("Digits: " + digits);
  }
}

中的代碼圖 3 顯示如何替代的能力可能會被打破。 考慮您的繼承者。 其中之一可以隨意修改的 isMoonWalking 欄位。 如果這真的發生了,類的基類將運行缺少關鍵清理部分的風險。 IsMoonWalking 欄位應該是私人的。 如果繼承者需要知道,應該是受保護的 getter 提供屬性的訪問,但不是修改。

圖 3 示例如何替代的能力可能會被打破的

class GrooveControl: Control {
  protected bool isMoonWalking;
  protected override void OnMouseDown(MouseButtonEventArgs e) {
    isMoonWalking = CaptureMouse();
    base.OnMouseDown(e);
  }
  protected override void OnMouseUp(MouseButtonEventArgs e) {
    base.OnMouseUp(e);
    if (isMoonWalking) {
      ReleaseMouseCapture();
      isMoonWalking = false;
    }
  }
}

智者和偶爾地賣弄學問的程式師會以這進一步邁出的一步。 密封的滑鼠處理常式 (或任何其他方法,依賴于或修改私有狀態),讓使用事件或其他不是必須調用的方法的虛擬方法的繼承者。 要求基地調用的模式是可以受理,但不是理想。 我們都忘記有時調用基方法,預期。 Don不允許繼承者打破封裝的狀態。

Liskov 替代還需要繼承者,不會引發新的異常類型 (雖然已經在基類中引發的異常的繼承者都很好)。 C# 有沒有辦法強制這。

介面隔離原則

每個介面應該有特定的目的。 你不應該被迫實現一個介面,當您的物件並不共用這一目的。 由外推法,越大的介面,更有可能它包括並不是所有實現者可以實現的方法。 這就是介面隔離原則的精髓。 考慮老和通用的介面對從 Microsoft.NET 框架:

public interface ICollection<T> : IEnumerable<T> {
  void Add(T item);
  void Clear();
  bool Contains(T item);
  void CopyTo(T[] array, int arrayIndex);
  bool Remove(T item);
}
public interface IList<T> : ICollection<T> {
  T this[int index] { get; set; }
  int IndexOf(T item);
  void Insert(int index, T item);
  void RemoveAt(int index);
}

介面仍有些有用,但有一個隱式的假設,如果你使用這些介面,你想要修改集合。 很多時候,不管是誰將這些資料集合創建想要阻止任何人修改的資料。 它是將介面分成來源和消費者其實非常有用。

很多資料存儲要共用通用的、 可索引的非可寫介面。 考慮資料分析或資料搜索軟體。 他們通常讀取較大的日誌檔或資料庫表中的分析。 修改的資料永遠不是議程的一部分。

誠然,IEnumerable 介面被為了將最小的唯讀介面。 加上LINQ擴充方法,它已開始完成該使命。 微軟也認識到在可轉位集合介面的差距。 該公司已經解決了這個在 4.5 版本的.NET 框架加上的 IReadOnlyList < T >,現在實施的很多框架組合。

你會記得這些美女中舊的 ICollection 介面:

public interface ICollection : IEnumerable {
  ...
object SyncRoot { get; }
  bool IsSynchronized { get; }
  ...
}

換句話說,你可以逐一查看集合之前,必須先有可能鎖定其 SyncRoot 上。 執行了若干的繼承者甚至那些特定專案明確只是説明隱藏自己在不得不實施他們的羞辱。 在多執行緒方案中的期望成為您鎖定在集合上到處使用它 (而不是使用 SyncRoot)。

你們中的大多數想要封裝您的集合,以便他們可以以執行緒安全方式訪問。 而不是使用 foreach,你必須封裝的多執行緒的資料存儲區和僅公開一個 ForEach 方法,而是採用委託。 幸運的是,較新的集合類,如併發集合在.NET Framework 4 或.NET 框架 4.5 (通過 NuGet) 現在可用的不可變集合已消除了很多這種瘋狂。

.NET 流抽象共用同一故障的方式太大,包括可讀和可寫元素和同步標誌。 但是,它不會包含屬性,以確定可寫性:CanRead、 CanWrite、 CanSeek 等等。 如果進行比較 (流。CanWrite) 到如果 (流是 IWritableStream)。 對於那些你創造不是可寫的溪流,後者是當然讚賞。

現在,看看在代碼圖 4

圖 4 的不必要的初始化和清理代碼示例

// Up a level in the project hierarchy
public interface INerdService {
  Type[] Dependencies { get; }
  void Initialize(IEnumerable<INerdService> dependencies);
  void Cleanup();
}
public class SocialIntroductionsService: INerdService
{
  public Type[] Dependencies { get { return Type.EmptyTypes; } }
  public void Initialize(IEnumerable<INerdService> dependencies)
  { ...
}
  public void Cleanup() { ...
}
  ...
}

這裡的問題是什麼? 您的服務初始化和清理應該通過.NET 框架中,而不是正在重塑了控制項 (IoC) 容器通常可用神奇反演之一。 為示例的緣故,沒人在乎初始化和清理非服務管理員 /­容器/boostrapper — — 無論代碼載入了這些服務。 這是關心的代碼。 你不想讓別人過早地調用清理。 C# 具有一種稱為顯式實現的説明這機制。 您可以實現更乾淨像這樣的服務:

public class SocialIntroductionsService: INerdService
{
  Type[] INerdService.Dependencies { 
    get { return Type.EmptyTypes; } }
  void INerdService.Initialize(IEnumerable<INerdService> dependencies)
  { ...
}
  void INerdService.Cleanup() {       ...
}
  ...
}

一般情況下,您想要設計一些目的不純抽象的單個具體的類介面。 這給你組織和擴展的手段。 然而,有至少兩個顯著的例外。

第一,介面傾向通常少於其具體實現更改。 您可以使用這對你有好處。 將介面放在單獨的程式集。 讓消費者引用僅介面的程式集。 它可以説明編譯速度快。 它可以説明您避免放在不屬於 (因為不適當的屬性類型不可與恰當的專案層次結構) 的介面上的屬性。 如果相應的抽象介面和介面是相同的檔中,出了問題。 介面適合在專案層次結構中作為其實現的父母和服務 (或服務的抽象) 的使用他們的同行。

第二,根據定義,介面沒有任何依賴項。 因此,他們把自己借給易於單元測試通過物件在嘲弄/代理框架。 我想談談的下一個和最後的原則。

依賴倒置原則

依賴倒置意味著要依賴于抽象而不是具體的類型。 有很多的這項原則和其他人已經討論了之間的重疊。 許多人前面的示例,包括未能依賴于抽象。

在他的書,"域驅動設計"(艾迪生-衛斯理專業,2003年),Eric· 埃文斯概述了一些物件分類在討論依賴項反演中非常有用。 總之這本書,很有用,把您的物件劃分為這三個組之一:值、 實體或服務。

值引用的物件通常是短暫的和不可變無依賴性。 他們沒有通常被提取,您可以在將具現化他們。 然而,沒有什麼毛病提取他們,尤其是如果你能把抽象的所有好處。 隨著時間的推移,一些值可能會增長到實體。 實體是您的業務模型和 ViewModels。 他們是從數值型別和其他實體生成的。 很有用有抽象為這些專案,特別是如果你有一個 ViewModel 表示幾個不同的變形的一種模型,反之亦然。 服務是,包含、 組織、 服務和使用實體的類。

與這種分類在頭腦中,依賴倒置主要涉及服務和需要它們的物件。 始終應在介面中捕獲特定于服務的方法。 無論您需要訪問該服務時,您通過介面訪問它。 Don不使用具體的服務類型在任何地方都不在構建服務的代碼中。

服務通常依賴于其他服務。 一些 ViewModels 取決於服務、 特別是集裝箱和工廠類型服務。 因此,通常很難進行測試,因為你需要充分的服務樹具現化服務。 到一個介面的抽象其精華。 然後對服務的所有引用都應通過該介面使他們可以輕鬆地嘲笑了測試的目的。

您可以在任何級別的代碼中創建的抽象。 當你發現自己在想,"哇,這會痛苦的支援 B 的介面的 A 和 B 到 A 的支援的介面"時,那是引入一個新的抽象,在中間的絕佳時機。 使可用的介面和依賴它們。

在配接器和調解員模式可以説明您符合首選介面。 聽起來好像額外的抽象帶來額外的代碼,但一般不是這樣的。 考慮互通性的部分步驟可以説明您組織代碼,早就不存在 A 和 B 可以相互交談。

幾年前,我讀了開發人員應該"總是重用代碼。"在的時間似乎太簡單。 我不敢相信這種簡單的口號能擊穿的義大利面都我的螢幕。 隨著時間推移,雖然,我明白了。 看看這裡的代碼:

private readonly IRamenContainer _ramenContainer; // A dependency
public bool Recharge()
{
  if (_ramenContainer != null)
  {
    var toBeConsumed = _ramenContainer.Prepare();
    return Consume(toBeConsumed);
  }
  return false;
}

你看到任何重複的代碼嗎? 有在 _ramenContainer 上讀取的雙精度型。 技術上講,編譯器將消除這種優化稱為"公共子運算式消除"。有關的討論,假設您運行在多執行緒情況下,編譯器實際上重複類欄位讀取的方法中。 您將運行風險您的類變數更改為 null,它甚至在使用之前。

你做如何解決此問題? 介紹如果以上的本地引用的聲明。 此重排要求您將添加一個新的專案或高於外部範圍。 原則是在您的專案組織相同 ! 當您重用代碼或抽象時,你最終在您的專案層次結構中到達一個有用的範圍。 讓磁碟機專案間的引用層次結構中的依賴項。

現在,看看這段代碼:

public IList<Nerd> RestoreNerds(string filename)
{
  if (File.Exists(filename))
  {
    var serializer = new XmlSerializer(typeof(List<Nerd>));
    using (var reader = new XmlTextReader(filename))
      return (List<Nerd>)serializer.Deserialize(reader);
  }
  return null;
}

它是根據抽象嗎?

不,它不是。 它始于對檔案系統的靜態引用。 它使用硬編碼反序列化程式硬編碼類型引用。 它預期的異常處理發生在類的外部。 此代碼是不可能不隨附的存儲代碼進行測試。

通常情況下,您將進入這兩個抽象:一個用於存儲格式,另一個用於存儲介質。 存儲格式的一些示例包括 XML、 JSON 和 Protobuf 的二進位資料。 存儲介質包括直接在磁片上,資料庫檔案。 第三個抽象也是系統的典型的在這種類型:一些種類很少改變代表要存儲的物件,以作留念。

請考量以下範例:

class MonsterCardCollection
{
  private readonly IMsSqlDatabase _storage;
  public MonsterCardCollection(IMsSqlDatabase storage)
  {
    _storage = storage;
  }
  ...
}

你可以看到與這些依賴項錯了什麼嗎? 線索中的依賴項名稱。 它是特定于平臺。 該服務不是特定于平臺 (或至少它試圖通過使用外部儲存引擎避免平臺的依賴項)。 這是一種情況,你需要雇用的配接器模式。

特定于平臺的依賴關係時,家屬會有他們自己特定于平臺的代碼。 您可以避免這一附加層。 附加層將説明您組織中一種特定于平臺的實現存在於它自己的特別專案 (與所有其特定于平臺的引用) 的專案。 你只需要引用包含特定于平臺的所有代碼通過啟動應用程式專案的專案。 平臺的包裝往往大 ; 不重複他們比更加必要。

依賴倒置彙集整個的一套原則在這篇文章中討論。 它使用可以填充的乾淨、 有目的的抽象與具體實現的不打破底層的服務狀態。 這就是目標。

事實上,固體的原則在其對可持續電腦代碼的影響一般重疊。 (意思很容易編譯) 的中間代碼的廣袤世界是美妙在其揭示的充分程度,您可能會擴展的任何物件的能力。 隨著時間的推移淡一些.NET 庫專案。 那不是因為這個想法是錯誤的 ; 他們只是不能安全地將擴展到未來的意外和不同需要。 在您的代碼中感到自豪。 應用的堅實的原則,你會看到您的代碼的使用壽命增加。

Brannon B. King 作為全職軟體發展人員工作了 12 年,其中八個深在 C# 和.NET Framework 中花了。他最近的工作一直與自主解決方案的公司。 (ASI) 附近的猶他州洛根 (asirobots.com)。ASI 是獨特的能力,以促進的 C# ; 傳染性愛情 乘員組在 ASI 需要充分利用語言並推到了其極限的.NET 框架中的激情。聯繫到他在 countprimes@gmail.com

衷心感謝以下技術專家對本文的審閱:Max Barfuss (ASI) 和Brian佩潘 (Microsoft)
BrianPepin 一直作為一名軟體工程師微軟公司在 1994 年以來,主要集中于開發人員的 Api 和工具。 他曾在Visual Basic、JAVA、.NET 框架、 Windows 表單,WPF,Silverlight 和Visual Studio中的 Windows 8 XAML 設計器。 目前他在 Xbox 團隊專注于 Xbox 作業系統元件上工作並享有支出與他的妻子旦西雅圖地區的閒置時間和兒子科爾。
Max Barfuss 是致力於良好的編碼、 設計及通信習慣是很棒的軟體工程師區別開來,其餘的事的信念軟體工匠。 他有十六年的軟體發展經驗,包括在.NET 土地的十一年。