WPF依赖注入

一、IOC 在 WPF 中的原理

控制反转(IOC)是一种设计原则,它将对象的创建和依赖关系的管理从对象本身转移到外部容器(IOC 容器)。在传统的编程方式中,一个对象如果需要使用另一个对象(即存在依赖关系),通常会在自身内部通过 new 关键字等方式直接创建被依赖的对象,这就导致了对象之间的紧密耦合。而在 IOC 模式下,由 IOC 容器负责创建对象,并在对象需要时将其依赖的对象注入进去。这样,对象只需要关注自身的业务逻辑,而不需要关心依赖对象的创建过程,从而实现了对象之间依赖关系的解耦。

 

在 WPF 应用中,这种解耦可以使视图、视图模型和其他组件之间的关系更加清晰和灵活,便于代码的维护和扩展。例如,视图模型依赖于数据访问服务来获取数据,通过 IOC,数据访问服务的具体实现可以由 IOC 容器注入到视图模型中,而不是视图模型自己去创建数据访问服务的实例。

二、IOC 在 WPF 中的作用

  1. 降低耦合度:使得组件之间的依赖关系更加松散,一个组件的变化不会轻易影响到其他组件。比如,当数据访问层的实现从数据库 A 切换到数据库 B 时,只需要在 IOC 容器中修改数据访问服务的注册配置,而使用该数据访问服务的业务逻辑组件无需进行修改。
  2. 提高可维护性:由于组件之间的耦合度降低,当需要修改或替换某个组件的依赖时,只需要在 IOC 容器中进行配置修改,而无需在大量的组件代码中查找和修改与依赖创建相关的代码。
  3. 增强可测试性:在进行单元测试时,可以方便地使用模拟对象或测试替身替换实际的依赖对象。例如,在测试视图模型时,可以注入一个模拟的数据访问服务,返回预设的数据,使单元测试更加独立和简单,不受实际依赖对象的影响。
  4. 实现依赖注入:可以在对象创建时将其依赖的对象自动注入,简化对象的初始化过程。对象在设计时只需要定义好依赖关系(通常通过构造函数、属性或方法参数来体现),IOC 容器会负责在合适的时机将依赖对象传递给它。

三、IOC 在 WPF 中的优劣势

  1. 优势
    1. 灵活性高:可以根据不同的需求或环境,动态地切换依赖对象的实现。例如,在开发环境中使用模拟的服务进行测试,而在生产环境中切换到实际的服务实现。
    2. 代码复用性好:由于依赖关系的解耦,组件可以更容易地在不同的项目或模块中复用。一个组件只依赖于抽象的接口或抽象类,只要提供符合接口定义的实现,就可以在不同的上下文中使用该组件。
    3. 易于扩展:添加新的功能或依赖时,对现有代码的影响较小,只需要在 IOC 容器中进行相应的配置。例如,添加一个新的日志服务,只需要在 IOC 容器中注册该日志服务的实现,并在需要使用日志服务的组件中注入它,而不需要对其他组件进行大规模的修改。
  2. 劣势
    1. 学习成本较高:理解和使用 IOC 需要一定的学习成本,尤其是对于初学者来说,需要掌握相关的概念(如依赖注入、控制反转)和技术(如如何配置 IOC 容器、如何进行依赖注入)。
    2. 配置复杂:在大型项目中,IOC 容器的配置可能会变得复杂,需要仔细管理和维护,否则可能会导致难以调试的问题。例如,当存在大量的依赖关系和不同的对象生命周期管理时,配置不当可能会导致对象创建错误或依赖关系混乱。

四、IOC 在 WPF 中的写法要求

  1. 定义接口或抽象类:首先要明确组件之间的依赖关系,定义好接口或抽象类,作为依赖的契约。接口或抽象类应该清晰地定义出所提供的功能和行为,例如定义一个 ICustomerService 接口,其中声明获取客户信息、保存客户信息等方法。
  2. 实现具体类:针对每个接口或抽象类,编写具体的实现类,实现接口中定义的方法或属性。例如,实现 ICustomerService 接口的 SqlCustomerService 类,在其中实现从数据库中获取和保存客户信息的具体逻辑。
  3. 创建 IOC 容器:编写一个简单的 IOC 容器类,用于管理对象的注册和解析。通常使用字典来存储接口和实现类的映射关系。容器类应该提供注册接口和实现类映射关系的方法(如 Register 方法),以及根据接口解析出对应实现类实例的方法(如 Resolve 方法)。
  4. 注册映射关系:在 IOC 容器中,将接口和对应的实现类进行注册,建立它们之间的映射关系。可以使用泛型方法来实现注册,例如 Register<TInterface, TImplementation>(),其中 TInterface 是接口类型,TImplementation 是实现类类型。
  5. 进行依赖注入:在需要使用依赖对象的组件中,通过构造函数、属性或方法注入依赖对象。在创建组件实例时,从 IOC 容器中获取相应的依赖对象并进行注入。例如,在视图模型的构造函数中接收依赖的服务对象,或者通过属性设置依赖对象。

五、IOC 在 WPF 中的应用场景

  1. 业务逻辑层与数据访问层的解耦:在 WPF 应用中,业务逻辑层通常依赖数据访问层来获取和存储数据。通过 IOC,可以将数据访问层的具体实现(如数据库访问类)注入到业务逻辑层中,使业务逻辑层与具体的数据访问技术(如 SQL Server、MySQL 等)解耦。这样,当需要更换数据库或数据访问方式时,只需要在 IOC 容器中修改数据访问服务的注册配置,而业务逻辑组件无需修改。
  2. 视图模型与服务的注入:视图模型(ViewModel)通常需要依赖一些服务(如网络服务、日志服务、消息推送服务等)来完成其功能。使用 IOC 可以将这些服务注入到视图模型中,提高视图模型的可测试性和可维护性。例如,在视图模型中注入日志服务,以便在处理业务逻辑时记录相关信息,而在测试视图模型时可以注入模拟的日志服务,避免实际的日志输出影响测试结果。
  3. 多语言支持:可以将不同语言的资源文件加载逻辑封装成服务,通过 IOC 注入到视图中,实现多语言的切换和支持。例如,创建一个 ILanguageService 接口,实现类负责根据用户设置加载相应语言的资源文件,并将其注入到视图中,从而实现视图的多语言显示。
  4. 插件式架构:当应用程序需要实现插件式架构时,IOC 可以帮助管理插件的依赖关系。插件可以通过接口来定义其功能,然后在 IOC 容器中注册插件的实现,使得主应用程序能够方便地加载和使用插件。主应用程序可以依赖于插件接口,而不需要关心插件的具体实现,从而实现插件的动态插拔和扩展。

六、5 个 IOC 容器示例代码及解析

示例 1:构造函数注入简单服务

 

csharp

using System;using System.Collections.Generic;using System.Windows;

// 定义IOC容器类class SimpleIocContainer{

    // 使用字典存储接口和对应的创建实例的委托

    private readonly Dictionary<Type, Func<object>> _registrations = new Dictionary<Type, Func<object>>();

 

    // 注册接口和实现类的映射关系

    public void Register<TInterface, TImplementation>() where TImplementation : TInterface

    {

        // 将创建TImplementation实例的委托存入字典,键为TInterface的Type对象

        _registrations[typeof(TInterface)] = () => Activator.CreateInstance<TImplementation>();

    }

 

    // 根据接口解析出对应的实现类实例

    public TInterface Resolve<TInterface>()

    {

        if (_registrations.TryGetValue(typeof(TInterface), out var factory))

        {

            // 如果字典中存在对应的创建委托,则调用委托创建实例并返回

            return (TInterface)factory();

        }

        throw new Exception($"Type {typeof(TInterface)} is not registered.");

    }}

// 定义服务接口interface IMessageService{

    string GetMessage();}

// 实现服务接口class ConsoleMessageService : IMessageService{

    public string GetMessage()

    {

        return "Hello from ConsoleMessageService!";

    }}

// 视图模型类,通过构造函数注入服务class MainViewModel{

    // 保存注入的IMessageService实例

    private readonly IMessageService _messageService;

 

    // 构造函数接收IMessageService实例作为参数,实现依赖注入

    public MainViewModel(IMessageService messageService)

    {

        _messageService = messageService;

    }

 

    // 视图模型的方法,调用注入的服务获取消息

    public string GetViewModelMessage()

    {

        return _messageService.GetMessage();

    }}

// 主窗口类public partial class MainWindow : Window{

    public MainWindow()

    {

        InitializeComponent();

 

        // 创建IOC容器实例

        var container = new SimpleIocContainer();

        // 注册IMessageService接口和ConsoleMessageService实现类的映射关系

        container.Register<IMessageService, ConsoleMessageService>();

 

        // 从IOC容器中解析出MainViewModel实例,此时会自动注入已注册的IMessageService实例

        var viewModel = container.Resolve<MainViewModel>();

        // 设置窗口的数据上下文为视图模型实例

        DataContext = viewModel;

    }}

 

解析:此示例展示了最基本的构造函数注入方式。SimpleIocContainer 类实现了简单的 IOC 容器功能,通过 Register 方法注册接口和实现类的映射,Resolve 方法根据接口解析实例。IMessageService 定义了服务接口,ConsoleMessageService 是其实现类。MainViewModel 通过构造函数接收 IMessageService 实例,实现依赖注入。在主窗口中,创建 IOC 容器,注册映射关系,解析出视图模型并设置为数据上下文,从而实现了从容器中获取依赖并注入到视图模型的过程。

 

示例 2:属性注入服务

 

csharp

using System;using System.Collections.Generic;using System.Windows;

class SimpleIocContainer{

    private readonly Dictionary<Type, Func<object>> _registrations = new Dictionary<Type, Func<object>>();

 

    public void Register<TInterface, TImplementation>() where TImplementation : TInterface

    {

        _registrations[typeof(TInterface)] = () => Activator.CreateInstance<TImplementation>();

    }

 

    public TInterface Resolve<TInterface>()

    {

        if (_registrations.TryGetValue(typeof(TInterface), out var factory))

        {

            return (TInterface)factory();

        }

        throw new Exception($"Type {typeof(TInterface)} is not registered.");

    }}

interface ILogger{

    void Log(string message);}

class FileLogger : ILogger{

    public void Log(string message)

    {

        Console.WriteLine($"Logging to file: {message}");

    }}

class OrderViewModel{

    // 定义可读写的属性来接收注入的ILogger实例

    public ILogger Logger { get; set; }

 

    public void PlaceOrder()

    {

        // 在方法中使用注入的Logger实例记录日志

        Logger.Log("Order placed successfully.");

    }}

public partial class MainWindow : Window{

    public MainWindow()

    {

        InitializeComponent();

 

        var container = new SimpleIocContainer();

        container.Register<ILogger, FileLogger>();

 

        var viewModel = container.Resolve<OrderViewModel>();

        // 从IOC容器中解析出ILogger实例,并赋值给视图模型的Logger属性,实现属性注入

        viewModel.Logger = container.Resolve<ILogger>();

 

        DataContext = viewModel;

    }}

 

解析:该示例使用属性注入方式。OrderViewModel 定义了一个 Logger 属性用于接收 ILogger 实例。在主窗口中,创建 IOC 容器并注册 ILogger 接口和 FileLogger 实现类的映射关系。解析出 OrderViewModel 实例后,再从容器中解析出 ILogger 实例并赋值给视图模型的 Logger 属性,从而实现了属性注入。视图模型的 PlaceOrder 方法中使用注入的 Logger 实例记录日志。

 

示例 3:方法注入服务

 

csharp

using System;using System.Collections.Generic;using System.Windows;

class SimpleIocContainer{

    private readonly Dictionary<Type, Func<object>> _registrations = new Dictionary<Type, Func<object>>();

 

    public void Register<TInterface, TImplementation>() where TImplementation : TInterface

    {

        _registrations[typeof(TInterface)] = () => Activator.CreateInstance<TImplementation>();

    }

 

    public TInterface Resolve<TInterface>()

    {

        if (_registrations.TryGetValue(typeof(TInterface), out var factory))

        {

            return (TInterface)factory();

        }

        throw new Exception($"Type {typeof(TInterface)} is not registered.");

    }}

interface IEmailSender{

    void SendEmail(string to, string subject, string body);}

class SmtpEmailSender : IEmailSender{

    public void SendEmail(string to, string subject, string body)

    {

        Console.WriteLine($"Sending email to {to}: {subject} - {body}");

    }}

class CustomerViewModel{

    private IEmailSender _emailSender;

 

    // 定义方法用于接收注入的IEmailSender实例

    public void SetEmailSender(IEmailSender emailSender)

    {

        _emailSender = emailSender;

    }

 

    public void SendWelcomeEmail(string customerEmail)

    {

        // 在方法中使用注入的IEmailSender实例发送邮件

        _emailSender.SendEmail(customerEmail, "Welcome", "Thank you for choosing us!");

    }}

public partial class MainWindow : Window{

    public MainWindow()

    {

        InitializeComponent();

 

        var container = new SimpleIocContainer();

        container.Register<IEmailSender, SmtpEmailSender>();

 

        var viewModel = container.Resolve<CustomerViewModel>();

        // 从IOC容器中解析出IEmailSender实例,并调用视图模型的SetEmailSender方法进行注入

        viewModel.SetEmailSender(container.Resolve<IEmailSender>());

 

        DataContext = viewModel;

    }}

 

解析:此示例采用方法注入。CustomerViewModel 定义了 SetEmailSender 方法用于接收 IEmailSender 实例。在主窗口中,创建 IOC 容器并注册 IEmailSender 接口和 SmtpEmailSender 实现类的映射关系。解析出 CustomerViewModel 实例后,从容器中解析出 IEmailSender 实例并调用视图模型的 SetEmailSender 方法,实现方法注入。视图模型的 SendWelcomeEmail 方法中使用注入的 IEmailSender 实例发送欢迎邮件。

 

 

案例4

csharp

using System;using System.Collections.Generic;using System.Windows;

class SimpleIocContainer{

    private readonly Dictionary<Type, Func<object>> _registrations = new Dictionary<Type, Func<object>>();

 

    public void Register<TInterface, TImplementation>() where TImplementation : TInterface

    {

        _registrations[typeof(TInterface)] = () => Activator.CreateInstance<TImplementation>();

    }

 

    public TInterface Resolve<TInterface>()

    {

        if (_registrations.TryGetValue(typeof(TInterface), out var factory))

        {

            return (TInterface)factory();

        }

        throw new Exception($"Type {typeof(TInterface)} is not registered.");

    }}

interface IDataAccess{

    string GetData();}

class SqlDataAccess : IDataAccess{

    public string GetData()

    {

        return "Data retrieved from SQL database.";

    }}

interface IBusinessLogic{

    string ProcessData();}

class BusinessLogic : IBusinessLogic{

    // 保存注入的IDataAccess实例

    private readonly IDataAccess _dataAccess;

 

    // 构造函数接收IDataAccess实例作为参数,实现第一层依赖注入

    public BusinessLogic(IDataAccess dataAccess)

    {

        _dataAccess = dataAccess;

    }

 

    // 业务逻辑方法,调用注入的IDataAccess实例获取数据并处理

    public string ProcessData()

    {

        return $"Processed: {_dataAccess.GetData()}";

    }}

class MainViewModel{

    // 保存注入的IBusinessLogic实例

    private readonly IBusinessLogic _businessLogic;

 

    // 构造函数接收IBusinessLogic实例作为参数,实现第二层依赖注入

    public MainViewModel(IBusinessLogic businessLogic)

    {

        _businessLogic = businessLogic;

    }

 

    // 视图模型的方法,调用注入的IBusinessLogic实例获取处理后的数据

    public string GetViewModelData()

    {

        return _businessLogic.ProcessData();

    }}

public partial class MainWindow : Window{

    public MainWindow()

    {

        InitializeComponent();

 

        var container = new SimpleIocContainer();

        // 注册IDataAccess接口和SqlDataAccess实现类的映射关系

        container.Register<IDataAccess, SqlDataAccess>();

        // 注册IBusinessLogic接口和BusinessLogic实现类的映射关系

        container.Register<IBusinessLogic, BusinessLogic>();

 

        // 从IOC容器中解析出MainViewModel实例,此时会自动按照依赖关系

        // 先解析出BusinessLogic实例,而BusinessLogic实例的创建又会依赖于

        // 已注册的SqlDataAccess实例,从而实现多层依赖注入

        var viewModel = container.Resolve<MainViewModel>();

        DataContext = viewModel;

    }}

 

解析:此示例展示了多层依赖注入的情况。BusinessLogic 类依赖于 IDataAccess 接口,通过构造函数注入 IDataAccess 实例来获取数据并进行处理。MainViewModel 类又依赖于 IBusinessLogic 接口,同样通过构造函数注入 IBusinessLogic 实例。在主窗口中,先在 IOC 容器中分别注册 IDataAccess 与 SqlDataAccess、IBusinessLogic 与 BusinessLogic 的映射关系。当解析 MainViewModel 实例时,IOC 容器会按照依赖关系,先创建 BusinessLogic 实例,而创建 BusinessLogic 实例时又会去解析并注入 SqlDataAccess 实例,从而实现了多层依赖的正确注入和使用。

 

示例 5:单例模式在 IOC 中的应用

 

csharp

using System;using System.Collections.Generic;using System.Windows;

class SimpleIocContainer{

    // 存储接口和对应的创建实例的委托

    private readonly Dictionary<Type, Func<object>> _registrations = new Dictionary<Type, Func<object>>();

    // 存储单例对象,键为接口的Type对象,值为单例实例

    private readonly Dictionary<Type, object> _singletons = new Dictionary<Type, object>();

 

    // 注册普通的接口和实现类的映射关系

    public void Register<TInterface, TImplementation>() where TImplementation : TInterface

    {

        _registrations[typeof(TInterface)] = () => Activator.CreateInstance<TImplementation>();

    }

 

    // 注册单例模式的接口和实现类的映射关系

    public void RegisterSingleton<TInterface, TImplementation>() where TImplementation : TInterface

    {

        _registrations[typeof(TInterface)] = () =>

        {

            // 如果单例字典中不存在该接口对应的实例,则创建一个新的实例并存储

            if (!_singletons.ContainsKey(typeof(TInterface)))

            {

                _singletons[typeof(TInterface)] = Activator.CreateInstance<TImplementation>();

            }

            // 返回已存储的单例实例

            return _singletons[typeof(TInterface)];

        };

    }

 

    // 根据接口解析出对应的实现类实例

    public TInterface Resolve<TInterface>()

    {

        if (_registrations.TryGetValue(typeof(TInterface), out var factory))

        {

            return (TInterface)factory();

        }

        throw new Exception($"Type {typeof(TInterface)} is not registered.");

    }}

interface IConfigurationService{

    string GetConfigurationValue(string key);}

class AppConfigurationService : IConfigurationService{

    public string GetConfigurationValue(string key)

    {

        return $"Value for {key} from configuration.";

    }}

class SettingsViewModel{

    // 保存注入的IConfigurationService实例

    private readonly IConfigurationService _configurationService;

 

    // 构造函数接收IConfigurationService实例作为参数,实现依赖注入

    public SettingsViewModel(IConfigurationService configurationService)

    {

        _configurationService = configurationService;

    }

 

    // 视图模型的方法,调用注入的IConfigurationService实例获取配置值

    public string GetSettingValue(string key)

    {

        return _configurationService.GetConfigurationValue(key);

    }}

public partial class MainWindow : Window{

    public MainWindow()

    {

        InitializeComponent();

 

        var container = new SimpleIocContainer();

        // 注册IConfigurationService接口和AppConfigurationService实现类为单例模式

        container.RegisterSingleton<IConfigurationService, AppConfigurationService>();

 

        // 从IOC容器中解析出SettingsViewModel实例,此时会注入已注册为单例的

        // IConfigurationService实例,保证整个应用中该服务只有一个实例

        var viewModel = container.Resolve<SettingsViewModel>();

        DataContext = viewModel;

    }}

解析:该示例演示了在 IOC 容器中实现单例模式。SimpleIocContainer 类增加了一个 _singletons 字典用于存储单例对象。RegisterSingleton 方法实现了单例注册逻辑,当解析接口对应的实例时,如果单例字典中不存在该实例,则创建一个新的实例并存储,以后每次请求该接口的实例时都返回已存储的单例实例。IConfigurationService 定义了配置服务接口,AppConfigurationService 是其实现类。SettingsViewModel 通过构造函数注入 IConfigurationService 实例。在主窗口中,注册 IConfigurationService 与 AppConfigurationService 的单例映射关系,解析 SettingsViewModel 实例时会注入单例的配置服务实例,确保在整个应用中该配置服务只有一个实例,避免了重复创建和资源浪费,同时也方便在不同组件中共享相同的配置信息。

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值