我应该使用构造函数注入还是 IoC.Resolve?

Posted

技术标签:

【中文标题】我应该使用构造函数注入还是 IoC.Resolve?【英文标题】:Should I use Constructor Injection or IoC.Resolve? 【发布时间】:2016-02-20 13:26:12 【问题描述】:

我正在构建非常简单的棋盘游戏(只是为了学习一些新东西)。它将是跨平台的(使用 Xamarin 编写)。我已经编写了游戏的核心,但我不确定(仍然)应该使用 构造函数注入 还是 IoC 解析。现在我使用 IoC.Resolve 然后传递参数。

这是我的代码。我有 Game 有 2 个依赖项和类型。我有工厂来创建游戏和播放器:

Abc.MyGame.Domain

接口

public interface IGame

    GameType GameType  get; 
    IGameBoard GameBoard  get; 
    List<IPlayer> Players  get; 

    // Other stuff...


public interface IGameBoard

    //board stuff...


public interface IPlayer

    int PlayerId  get; set; 
    PlayerType PlayerType  get; set; 

工厂接口

public interface IGameFactory

    IGame CreateGame(GameType type, IGameBoard board);


public interface IPlayerFactory

    IPlayer CreatePlayer(PlayerType type, int id);
  

工厂

public class GameFactory : IGameFactory

    public IGame CreateGame(GameType type, IGameBoard board)
    
        switch (type)
        
            case GameType.SinglePlayer:
                return new MyGame(type, board,
                    new List<IPlayer>  CreateHuman(1), CreateBot(2) );
            case GameType.TwoPlayers:
                return new MyGame(type, board,
                    new List<IPlayer>  CreateHuman(1), CreateHuman(2) );
            case GameType.Online:
                return new MyGame(type, board,
                    new List<IPlayer>  CreateHuman(1), CreateOnlinePlayer(2) );

        
        return null;
    

然后,有一个 API。由 UI 使用:

Abc.MyGame.API

public class GameAPI

    public IGame CurrentGame  get; set; 

    public IGame CreateGame(IGameFactory gameFactory, GameType type, IBoard board)
    
        CurrentGame = gameFactory.CreateGame(type, board);
        return CurrentGame;
    

    // Other stuff, like make move etc...

...和我的用户界面:

Abc.MyGame.UI.WinForms

public partial class Form1 : Form

    private GameAPI api = new GameAPI();
    IKernel kernel = new StandardKernel();

    public Form1()
    
        InitializeComponent();

        // Load all dependencies
        var modules = new List<INinjectModule>  new GameModule() ;
        kernel.Load(modules);
    

    private void buttonStartGame(object sender, EventArgs e)
    
        IGameBoard board = kernel.Get<IGameBoard>(
            new ConstructorArgument("width", 7), 
            new ConstructorArgument("height", 6)
        );

        api.CreateGame(kernel.Get<IGameFactory>(), GameType.TwoPlayers, board);
    

我需要在我的IGame 中添加IGameBoard。只有那里。我需要将棋盘和玩家注入IGame

这是我的问题/疑问: 我真的需要在程序的最“前端”解析IGameBoard 吗?当有人单击“开始游戏”按钮时,我在UI 中解决了它。然后我通过API 传递这个板,然后我将它传递给GameFactory,然后我将它传递给Game 构造函数(终于!)。在GameFactory 中创建(或解决)IPlayerFactoryIGameBoard 是一种不好的做法吗? API.CreateGame 在这种情况下将只有 type 参数?我的意思是对于特定的 GameFactory 只有一个板(每次都一样),所以我不确定我是否需要在一开始就创建板......

编辑: MyGame 构造函数:

public class MyGame : IGame

    public IBoard Board  get; 
    public GameType Type  get; 
    public List<IPlayer> Players  get; 

    public MyGame(GameType type, IBoard board, List<IPlayer> players)
    
        Type = type;
        Board = board;
        Players = players;
    

    //...

【问题讨论】:

您是否应该使用构造函数注入与属性注入或注入与从对象内解析? @DStanley 构造函数注入与从对象内解析。我是否应该在 UI 项目中解析 IGameBoard 并通过构造函数通过 GameAPIGameFactory 将解析的实例注入到 Game 或......在创建 GameFactory 的对象中解析它 MyGame 简短的回答是你应该支持构造函数注入而不是其他模式;特别是Service Locator, which is an anti-pattern。不过,尚不清楚为什么您必须通过层传递board。为什么GameAPI.CreateGameIGameBoard 作为参数?为什么IGameFactory.CreateGame? (为什么GameAPI.GameFactory 是一个可设置的属性?) @MarkSeemann 我知道 Service Locator anit-pattern。这就是我问这个问题的原因。 为什么你必须通过层传递板? - 因为,当我打电话给api.CreateGame 时,我知道它取决于板(它需要板)。我可以跳过它,通过GameFactory.CreateGame 中的new 创建板-但我认为这很糟糕。 为什么 GameAPI.CreateGame 将 IGameBoard 作为参数? - 我需要一个棋盘来创建游戏。不是构造函数注入吗? @MarkSeemann 为什么 GameAPI.GameFactory 是可设置的属性? - 抱歉,这是个错误。我修好了它。 CurrentGame 稍后在课程中用于:CurrentGame.MakeMove 等。 【参考方案1】:

我真的需要在程序的最“前端”解析 IGameBoard 吗?

是的,需要在应用程序的入口点和生命周期范围(如果有)的开始处解决依赖关系。通常,这是抽象的(就像在 ASP.NET MVC 中一样),因此您永远不必自己调用 kernel.Get&lt;&gt;(或任何其他 container.Resolve&lt;&gt;)。在 WinForms 中没有这样的机制,所以你必须自己解决依赖关系,最好只有这样的根依赖:

public static class Program

    private static void Main()
    
        var kernel = new StandardKernel();
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.Run(kernel.Get<Form1>());
    

通过在您的主窗体上放置正确的依赖项(可能在您的游戏案例中包括一个工厂),您的应用程序中应该不再需要kernel.Get&lt;&gt;

【讨论】:

以上是关于我应该使用构造函数注入还是 IoC.Resolve?的主要内容,如果未能解决你的问题,请参考以下文章

为啥在 CDI 中使用构造函数而不是 setter 注入?

为啥使用 NGRX 而不是构造函数注入服务?

注入服务的构造函数何时运行?

我应该将变量放在类还是构造函数中? PHP

装配SpringBean--依赖注入

通过将 cacheManager 迁移到构造函数注入,类不存在