10

我一直在嘗試在asp.net MVC5應用程序中實現鬆散耦合的應用程序。我有一個控制器:如何在C#中防止構造函數被濫用class

public class HeaderController : Controller 
    { 
     private IMenuService _menuService; 

     public HeaderController(IMenuService menuService) 
     { 
      this._menuService = menuService; 
     } 

     // 
     // GET: /Header/ 
     public ActionResult Index() 
     { 

      return View(); 
     } 


     public ActionResult GetMenu() 
     { 

      MenuItem menu = this._menuService.GetMenu(); 
      return View("Menu", menu); 

     } 

    } 

,服務於該控制器使用的是:

public class MenuService : IMenuService 
{ 
    private IMenuRespository _menuRepository; 

    public MenuService(IMenuRespository menuRepository) 
    { 
     this._menuRepository = menuRepository; 
    } 

    public MenuItem GetMenu() 
    { 
     return this._menuRepository.GetMenu(); 
    } 
} 

和存儲庫在服務類中使用的是:用於

public class MenuRepository : IMenuRespository 
    { 
     public MenuItem GetMenu() 
     { 
      //return the menu items 
     } 
    } 

的接口該服務和存儲庫是這樣的:

public interface IMenuService 
    { 
     MenuItem GetMenu(); 
    } 

public interface IMenuRespository 
    { 
     MenuItem GetMenu(); 
    } 

HeaderController的構造函數使用構造函數注入獲取MenuService,並且我已將ninject作爲處理此操作的DI容器。

這一切的偉大工程 - 除了在我的控制,我仍然可以做到這一點:

MenuItem menu = new MenuService(new MenuRepository()); 

...它打破了架構。我怎樣才能防止以這種方式使用'新'?

+2

乾脆不要做那?不知道爲什麼這會比命名控制器'HeaderCantroller'更麻煩「,這會破壞體系結構」...是否有特別的理由讓你尋找它(比如特殊的模式不能通過良好的命名/代碼審查來停止)? –

+0

你不能驗證構造函數內的參數,並且如果使用不正確的話會拋出異常? –

+1

@AlexeiLevenkov:使用構造函數直接違反了「關注點分離」,並將HeaderController緊密地耦合到MenuService和MenuRepository。 –

回答

7

這樣做的一種方法是將接口和實現移動到單獨的Visual Studio項目/程序集中,並只引用實際需要它的項目中的實現項目 - 其他所有項都可以引用您的接口項目IMenuService - 此時代碼可以使用該接口,但實際上本身並不是新增的任何實現。

然後,您可以在您的依賴關係中的任何位置引用實施項目。

Web應用程序解決方案:

Web應用程序PROJ(控制器等) - >服務接口PROJ

服務默認地將Impl項目 - >服務接口PROJ

即使如此,這是一個好的方法,一切都不是傻瓜證明 - 另一個組件是教育和代碼審查,以提出適用於您的團隊的最佳實踐,如可測試性和依賴注入。

+0

嗯 - 但不是這樣的代碼:MenuItem menu = this._menuService.GetMenu();需要參考MenuService的實際實現? –

+0

肯定 - 但你不知道任何關於正在使用的實例 - 除了它實現「IMenuService」 – BrokenGlass

+1

但如果我有參考,我仍然可以只調用新的MenuService(新的MenuRepository()),不是? –

4

我認爲手動實例化對象的部分問題可能伴隨着一個大團隊的工作,因此一些成員正在以錯誤的方式使用構造函數注入技術。如果是這樣的話,我通過教育他們解決大部分問題的框架,發現了很多。有時候,你會發現有人這樣做是錯誤的,但並不經常。另一種方法是在控制器構造函數中添加[EditorBrowsable(EditorBrowsableState.Never)]屬性。構造函數將從intellisense中消失;好吧,它似乎會消失。但它仍然可以使用。

您可能會將實現分解爲另一個不直接引用(隱式引用)的MVC項目,因此,由於沒有直接引用,因此不能直接使用這些類型。通過一個項目中的接口(每個項目引用)以及具有間接引用的實現的項目,只有這些接口將被包含在內。如果你正在進行單元測試,我建議在單元測試項目中包含一個直接參考,以提高測試覆蓋率。

+0

告訴人們不要使用它: - )?這個答案是什麼?如果有人不遵循這些指導方針,您會如何以編程方式限制他們?問題是如果人們可以直接訪問實際的實現,爲什麼我們應該實現接口? –

+3

@JitendraPancholi:這實際上是一個很好的答案。這是我在建築師看來的一個普遍問題,他們試圖阻止人們做錯事,這往往會導致非常複雜的無用代碼結構,開發人員仍然會發現大量的事情要做錯事以及工作方式圍繞建築師的限制。相反,開發人員應該受到教育,團隊應該有代碼審查,因爲這些更有效。 – Steven

+0

@Steven:而不是通過指導方針的這種限制,使用BrokenGlass&AlexeiLevenkov在上述答案中提到的方法來創建一個良好的架構來減少人爲錯誤。 –

2

作爲一般設計原則,接口(合同)應該在一個組件中,而實現應該在另一個組件中。合約彙編應該在MVC項目中引用,實現的彙編應該複製到「bin」文件夾中。比使用「Dynamic Module Loading」加載類型。通過這種方式,您將避免上述問題,這是更廣泛的解決方案。因爲您可以在不構建UI和聯繫人組件的情況下替換實現。

+5

哪個設計原理是這樣的? – Steven

3

夫婦的潛在選項(這是我從來沒有嘗試過,但可能有一些腿):

  • 你也許可以寫一個錯誤,如果在構造函數中的代碼使用的FxCop規則。

  • 您可以將構造函數標記爲過時,並且如果您在代碼中使用過時的方法,那麼構建服務器將失敗。

如果DI容器使用它通過反射這都應該是確定(儘管在FxCop的情況下,你很可能不扔,如果它是在NInject命名空間的方法)

+0

更好地顯式實現接口方法,所以如果任何人試圖實例化類並訪問方法,所以它甚至不會因訪問級別而出現。 –

+1

@JitendraPancholi好的,但這些選項都包含在其他答案中,我試圖給出一些可能的替代方案。我沒有意識到只有一種方式來剝皮這隻貓。 –