使用依赖注入在应用程序中保存字典的位置

发布于 2025-01-02 09:42:18 字数 335 浏览 1 评论 0原文

我有一个遗留代码,并且在重构它时遇到问题。

在我的应用程序开始时,我从 WCF 加载到 App(这是 SL 应用程序)用户列表的属性。

然后每个控件(用于发送电子邮件、查看日历和分配任务)都使用此属性,

(App.Current as App).Users

现在,我正在尝试为使用此列表的控件之一创建单元测试,但我陷入了困境。

我应该使用 App 作为参数进行构造函数注入(我正在使用 Unity)吗?或者也许引入一些类来保存这个列表?

I have a legacy code, and I have a problem with reconstructor it.

At start of my application I load from WCF to property on App (this is SL application) list of users.

Then every control (for sending emails, view calendar and assigning tasks) use this property as

(App.Current as App).Users

Now, I'm trying to create Unit Test for one of controls that use this lists, and I'm stuck.

Should I make a Constructor Injection(I'm using Unity) with App as parameter? Or maybe introduce some class to hold this list?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

扛起拖把扫天下 2025-01-09 09:42:18

更新了 OP 的实现,因为伪代码不完整。

我建议为所有应用程序服务创建一个接口,

将 IApplicationService 注入到您的模块中。

您可以将此接口用于应用程序提供的所有服务(可能您需要更多)。模拟单元测试的接口

OP的实现

 public interface IApplicationService
    {
        List<User> Users{get;set;}
    }

    public class ApplicationService : IApplicationService
    {
        public List<User> Users
        {
            get { return (App.Current as App).Users; }
            set { (App.Current as App).Users = value; }
        }
    }

    public partial class MainWindow : UserControl
    {
        readonly IApplicationService _applicationService
        public MainWindow(IApplicationService applicationService)
        {
            _applicationService=applicationService;
        }
    }

Updated with OP's implementation as the pseudocode was incomplete.

I propose create an interface for all your application services

Inject IApplicationService to your modules.

You can use this interface for all the services the application provides(probably you will need more). Mock the interface for the unit tests

OP's implemantation

 public interface IApplicationService
    {
        List<User> Users{get;set;}
    }

    public class ApplicationService : IApplicationService
    {
        public List<User> Users
        {
            get { return (App.Current as App).Users; }
            set { (App.Current as App).Users = value; }
        }
    }

    public partial class MainWindow : UserControl
    {
        readonly IApplicationService _applicationService
        public MainWindow(IApplicationService applicationService)
        {
            _applicationService=applicationService;
        }
    }
天气好吗我好吗 2025-01-09 09:42:18

我将创建一个包装类来公开用户列表。在生产代码中,此类只是 App.Current 属性的包装器,并且可以通过 Unity 将其注入到构造函数中。

在单元测试中,您可以轻松模拟 App 参数并在构建新的 SUT 时传递它。

像这样的东西:

public interface IUserList
{
   List<User> Users { get; }
}

public class SUT
{
   private IUserList UserList { get; set; }

   public SUT(IUserList userList)
   {
     this.UserList = userList;
   }
}

public class AppUserList : IUserList
{
   public List<User> Users
   {
      get
      {
         return ((App)App.Current).Users;
      }
   }
}

I would create a wrapper class that will expose the list of users. In production code this class will just be a wrapper around your App.Current property and it can be injected in the constructor trough Unity.

In your Unit Tests you can easily mock the App parameter and pass it when constructing a new SUT.

Something like:

public interface IUserList
{
   List<User> Users { get; }
}

public class SUT
{
   private IUserList UserList { get; set; }

   public SUT(IUserList userList)
   {
     this.UserList = userList;
   }
}

public class AppUserList : IUserList
{
   public List<User> Users
   {
      get
      {
         return ((App)App.Current).Users;
      }
   }
}
早乙女 2025-01-09 09:42:18

对于 Silverlight,有一个名为 应用程序扩展服务 的扩展模型

出于基础设施目的,这可能是比向应用程序类添加属性并来回转换 App.Current 更好的替代方案。

该模型的缺点是创建一个单例,您必须为单元测试进行初始化。它还会隐藏您的消费类中对 Users 的依赖。

您的用户似乎只是数据。使该数据成为可以在应用程序中的任何位置访问和编辑的环境上下文会令您烦恼。您不知道谁对这些数据做了什么以及何时做。这就像会话状态。

因此,明确对数据的依赖将是能够跟踪该数据滥用的第一步。

如果您认为创建一个具有Users属性的“数据持有者对象”有意义,或者直接将该数据注入到您的消费者中,则由您决定。如果数据多于用户,那么很容易将所有数据放入同一个中央数据存储对象中,即使您的特定消费者不需要它们。

For Silverlight there is an extension model called Application Extension Services.

For infrastructure purposes that might be a better alternative than adding properties to your app class and casting App.Currentback and forth.

Downside of that model is the creation of a singleton you would have to initialize for your unit tests. It would also hide the dependency on Users in your consuming classes.

Your users seem to be just data. Making that data an ambient context which can be accessed and edited everywhere in your application will bite you. You don't know who does what with that data and when he does it. This is like a session state.

So making the dependency on your data explicit would be a first step to be able to track abuse of that data.

If it makes sense to you to create a "data holder object" that has a property for Users or directly inject that data into your consumers is up to you. If there is more data than just Usersit is tempting to put all of them into the same central data store object, even if your specific consumers don't need them.

宛菡 2025-01-09 09:42:18

吉米的回答很好,但可以提供很多内容,并且修复了一些错误。代码/说明下方解释了差异:


创建公共接口:IUserService

public interface IUserService
{       
    // Implemented functionality as methods where possible for better
    // extendability (like IoC)
    IEnumerable<User> Users();

    // Add any other user service stuff as you see fit.
    void AddUser(User user);
}

编写实现 UserService IUserService

public class UserService : IUserService
{
    // If you need DI for this service, follow the same pattern of using 
    // fields and controller injection. I left examples in comment below.

    // private readonly IRepository _repository;

    // Constructor is unnecessary if you do not need DI example.
    public UserService(/* IRepository repository */) 
    {
        // _repository = repository;
    }

    // Methods
    public IEnumerable<User> Users()
    {
        return ((App)App.Current).Users;
    }
    public void AddUser(User user)
    {
        ((App)App.Current).Users.Add(user);
    }

}

通过构造函数将 IUserService 注入到类中

在本例中,以您的 MainWindow 为例:

public partial class MainWindow : UserControl
{
    private readonly IUserService _userService;

    public MainWindow(IUserService userService)
    {
        _userService = userService;
    }

    // Example method consuming the service
    public IEnumerable<User> GetUsers()
    {
        return _userService.Users();
    }
}

区别:

  1. 将您的用户服务与中央应用程序服务

    更好的模块化。此外,我使用 IApplicationService 来获取更集中/全局的数据,例如 Api 密钥、超时、清理、数据库准备等。

  2. 返回 IEnumerable 而不是 <代码>列表

    这只是保持事物干燥并且不对您的消费类强加硬实例化的黄金经验法则。重构更容易/更安全,并且您的代码更具可扩展性。

  3. 使用方法而不是属性

    这是偏好,但我认为在服务层中尽可能使用方法是明智的,以便您可以引入过滤器和重载或继续使用依赖项注入 - 例如,您可以添加 GetUsers(string lastName)GetUsers(string lastName, string firstName) 并为您的消费类维护一个干净的接口。

  4. 在不使用 as 关键字的情况下投射 App.Current

    这是一个很好的做法,因为使用 as 关键字意味着当转换失败时它将返回 null,而不是抛出异常。我更喜欢例外,因为 99% 的情况下,如果你的转换失败,你的下一个操作也会失败。 :)

享受!

Jimmy's answer is great, but can be provide quite a bit, and some errors fixed. Differences are explained at the bottom below the code/instructions:


Create a public interface: IUserService

public interface IUserService
{       
    // Implemented functionality as methods where possible for better
    // extendability (like IoC)
    IEnumerable<User> Users();

    // Add any other user service stuff as you see fit.
    void AddUser(User user);
}

Write a UserService that implements IUserService

public class UserService : IUserService
{
    // If you need DI for this service, follow the same pattern of using 
    // fields and controller injection. I left examples in comment below.

    // private readonly IRepository _repository;

    // Constructor is unnecessary if you do not need DI example.
    public UserService(/* IRepository repository */) 
    {
        // _repository = repository;
    }

    // Methods
    public IEnumerable<User> Users()
    {
        return ((App)App.Current).Users;
    }
    public void AddUser(User user)
    {
        ((App)App.Current).Users.Add(user);
    }

}

Inject IUserService into classes via their Constructor

In this case your MainWindow as an example:

public partial class MainWindow : UserControl
{
    private readonly IUserService _userService;

    public MainWindow(IUserService userService)
    {
        _userService = userService;
    }

    // Example method consuming the service
    public IEnumerable<User> GetUsers()
    {
        return _userService.Users();
    }
}

Differences:

  1. Separate your User Services from a central Application Service

    Better modularity. In addition I use an IApplicationService for more central/global data like Api Keys, Timeouts, cleanup, DB prepping, etc.

  2. Return IEnumerable<T> instead of List<T>

    This is just a golden rule of thumb for keeping things dry and not imposing hard instantiations on your consuming classes. Refactoring is easier/safer, and your code more extensible.

  3. Use methods instead of properties

    This is preference, but I think it smart in a service layer to use methods where possible so that you can introduce filters and overloads or continue to use dependency injection - for example, you could add GetUsers(string lastName), GetUsers(string lastName, string firstName) and maintain a clean interface for your consuming classes.

  4. Cast App.Current without the as keyword

    This is a good practice because using the as keyword means when the cast fails it will return null, rather than throw an exception. I prefer the exception because 99% of the time, if your cast fails, your next operations will too. :)

Enjoy!

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文