【问题标题】:Class design, polymorphic load method, dependency injection类设计、多态加载方法、依赖注入
【发布时间】:2018-06-26 17:25:04
【问题描述】:

我是一名初级程序员,但遇到了一些类设计问题。我想在游戏类中使用Load() 方法,该方法非常通用且具有依赖注入的多态性。示例如下:

假设我有一门课:

public class MyGame {
   public IGameData _data;
   public ISolver _solver;
   public ILoader _loader;


   public MyGame(ILoader loader, ISolver solver) {
      _loader = loader; 
      _solver = solver;
   }

   public Load() {
      /*
      The loader might need a param or not, 
      how can I make the method Dependency Injection friendly
      */
       _data = _loader.Load();        
   }
}

问题是我想拥有FileLoader()NetworkLoader() 等课程。每一个都需要不同的参数。我得到的一种解决方案是在构建具体 Loader 时传递参数:

var fileLoader = new FileLoader('somegamestate.txt');
var someSolver = new SomeSolver();
var game = new MyGame(fileLoader, someSolver);

但这样的设计似乎不适用于 C# Unity 依赖注入框架,因为我在整个系统中将文件名绑定到 FileLoader 在逻辑上是错误的。

我虽然在使用工厂,但这并没有满足我的灵魂:)

你对设计有什么想法吗?应该怎么做?

工厂示例:

public class MyGameFactory {
   public ISolver _solver;
   public MyGameFactory(ISolver solver){
       _solver = solver;
   }

   public MyGame MakeGame(ILoader loader) {
       return new MyGame(loader, _solver);
   }
}

附:我正在用 C# 编码。

【问题讨论】:

  • 我会避免在构造函数中使用像文本文件这样的参数。如果您在某些时候需要具体文件,请将其作为参数传递给需要它的方法。
  • 你能指出我应该把它(文件名)放在哪里吗?

标签: c# class dependency-injection unity-container class-design


【解决方案1】:

关注this

你可以使用

container.RegisterType<ILoader , FileLoader>(
                new InjectionConstructor("somegamestate.txt") //Old way to pass value to constructor - not flexible. 
                );

container.Resolve<MyGame>(new DependencyOverride<ILoader >(new FileLoader("somegamestate.txt")));

【讨论】:

  • 如我所见,DependencyOverride 会覆盖每个对象图节点中的依赖关系,这可能会导致意外行为。 RegisterType 也有同样的问题。我想唯一的方法是做属性覆盖。
【解决方案2】:

肯定的一个选择是将Load 方法放在一个接口中,然后在其他类中实现其余的方法。这将允许您始终从 Game 类调用它,但同时让每个实例都提供自己的实现。

界面就这么简单:

public interface ILoader {
    void Load();
}

在这里你注入你的加载器可能需要的两个字符串。

public class Loader1 : ILoader {
    public string prop1 { get; set; }
    public string prop2 { get; set; }

    public Loader1(string param1, string param2) {
        prop1 = param1;
        prop2 = param2;
    }

    public void Load() {
        // Use prop1 and prop2
    }
}

在这种情况下,你注入了一些其他的对象。

public class Loader2 : ILoader {
    public SomeObject prop1 { get; set; }

    public Loader2(SomeObject param1) {
        prop1 = param1;
    }

    public void Load() {
        // Use prop1
    }
}

它的美妙之处在于每个加载器都会通过构造函数获取自己的参数,但游戏总是会以相同的方式调用Load 方法。如果你需要一个工厂,那就去做吧,但是你总是可以使用一个通用的依赖注入器,比如统一,就像你提到的那样。

对于大多数依赖注入容器来说,向构造函数注入参数是可行的。就像@Thierry 在他的回答中评论的那样,您可以使用 Unity 来做到这一点。另一方面,如果您更喜欢切换到另一个稍微容易一些的框架(基于意见),您可以尝试 Ninject,例如。在这种情况下,您只需执行以下操作:

Bind<ILoader>().To<FileLoader>().WithConstructorArgument("file.txt");

无论如何,我认为设计非常好。

【讨论】:

  • 看来您是在转述我的问题文本 :)
  • @user9992715 是的,我没有注意到 rofl。 TBH 那么,设计非常好。我对 Unity 容器并不十分熟悉,但您想要做的不仅仅是使用大多数其他注入框架,如 ninject。我会用一个例子来编辑我的答案:)
  • 与其他答案相同的问题,如果我需要在代码中构建多个游戏对象怎么办?我再做一次绑定?看起来像穷人解决方案......
  • @user9992715 好吧,我不能 100% 确定您在寻找什么。一方面,如果只是为了不将FileLoader绑定到一个文件名,在FileLoader的构造函数中使用ConfigurationManager。如果您有可能的游戏组合到加载器受限,则使用容器内的选项(上下文绑定)。但是如果你想实现加载器与游戏的任意组合,那么也许我不会直接使用容器,而是为任何场景手动注入它们。无论如何,如果您确实找到了您正在寻找的答案,请务必分享。
【解决方案3】:

想想你想做什么。您想要加载游戏,因此您需要一些有关要加载的游戏的信息,并且在加载时需要这些信息。因此Load() 需要一个携带此信息的参数,而不是ILoader 实现的构造函数。假设信息是保存槽的编号,那么您的课程将如下所示

public class MyGame {
   public MyGame(ILoader loader, ...) {
      _loader = loader;
   }

   public Load(int saveSlotNumber) {
       _data = _loader.Load(saveSlotNumber);        
   }
}

现在ILoader 实现可以将 saveSlotNumber 转换为任何合适的值。如果你有 FileLoader,那么它可以从 {Application-Path}\savegames\{saveSlotNumber}.sav 加载,或者如果你有 NetworkLoader,它可以从 \\game-server\savegames\{saveSlotNumber}.sav 加载。

【讨论】:

  • 当您可以控制软件结构时,您提出的解决方案很好,但事实并非如此:/
猜你喜欢
  • 2015-06-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-05-08
  • 1970-01-01
  • 1970-01-01
  • 2015-05-27
相关资源
最近更新 更多