将策略和工厂模式与依赖注入一起使用 [英] Using a Strategy and Factory Pattern with Dependency Injection

查看:113
本文介绍了将策略和工厂模式与依赖注入一起使用的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在做一个辅助项目,以更好地理解控制反转和依赖注入以及不同的设计模式。

I am working on a side project to better understand Inversion of Control and Dependency Injection and different design patterns.

我想知道是否有最佳实践将DI与工厂和策略模式结合使用 ??

I am wondering if there are best practices to using DI with the factory and strategy patterns?

我的挑战来自于策略(从工厂构建)每个策略都需要不同的参数可能的构造函数和实现。结果,我发现自己在服务入口点声明了所有可能的接口,并将它们向下传递给应用程序。结果,对于新的和各种策略类实现,必须更改入口点。

My challenge comes about when a strategy (built from a factory) requires different parameters for each possible constructor and implementation. As a result I find myself declaring all possible interfaces in the service entry point, and passing them down through the application. As a result, the entry point must be changed for new and various strategy class implementations.

为便于说明,我整理了一个配对示例。这个项目的堆栈是.NET 4.5 / C#和IoC / DI的Unity。

I have put together a paired down example for illustration purposes below. My stack for this project is .NET 4.5/C# and Unity for IoC/DI.

在此示例应用程序中,我添加了一个默认的Program类,该类负责接受虚拟订单,并根据订单属性和所选的运送提供商,计算运送成本。 UPS,DHL和Fedex的计算方法不同,每种实现可能依赖也可能不依赖其他服务(以访问数据库,api等)。

In this example application, I have added a default Program class that is responsible for accepting a fictitious order, and depending on the order properties and the shipping provider selected, calculating the shipping cost. There are different calculations for UPS, DHL, and Fedex, and each implemnentation may or may not rely on additional services (to hit a database, api, etc).

public class Order
{
    public string ShippingMethod { get; set; }
    public int OrderTotal { get; set; }
    public int OrderWeight { get; set; }
    public int OrderZipCode { get; set; }
}

计算运费的虚拟程序或服务

public class Program
{
    // register the interfaces with DI container in a separate config class (Unity in this case)
    private readonly IShippingStrategyFactory _shippingStrategyFactory;

    public Program(IShippingStrategyFactory shippingStrategyFactory)
    {
        _shippingStrategyFactory = shippingStrategyFactory;
    }

    public int DoTheWork(Order order)
    {
        // assign properties just as an example
        order.ShippingMethod = "Fedex";
        order.OrderTotal = 90;
        order.OrderWeight = 12;
        order.OrderZipCode = 98109;

        IShippingStrategy shippingStrategy = _shippingStrategyFactory.GetShippingStrategy(order);
        int shippingCost = shippingStrategy.CalculateShippingCost(order);

        return shippingCost;
    }
}

// Unity DI Setup
public class UnityConfig
{
    var container = new UnityContainer();
    container.RegisterType<IShippingStrategyFactory, ShippingStrategyFactory>();
    // also register  IWeightMappingService and IZipCodePriceCalculator with implementations
}

public interface IShippingStrategyFactory
{
    IShippingStrategy GetShippingStrategy(Order order);
}

public class ShippingStrategyFactory : IShippingStrategyFactory
{
    public IShippingStrategy GetShippingStrategy(Order order)
    {
        switch (order.ShippingMethod)
        {
            case "UPS":
                return new UPSShippingStrategy();

            // The issue is that some strategies require additional parameters for the constructor
            // SHould the be resolved at the entry point (the Program class) and passed down?
            case "DHL":
                return new DHLShippingStrategy();

            case "Fedex":
                return new FedexShippingStrategy();

            default:
                throw new NotImplementedException(); 
        }
    }
}

现在策略接口和实现。 UPS是一个简单的计算,而DHL和Fedex可能需要不同的服务(以及不同的构造函数参数)。

Now for the Strategy interface and implementations. UPS is an easy calculation, while DHL and Fedex may require different services (and different constructor parameters).

public interface IShippingStrategy
{
    int CalculateShippingCost(Order order);
}

public class UPSShippingStrategy : IShippingStrategy()
{
    public int CalculateShippingCost(Order order)
    {
        if (order.OrderWeight < 5)
            return 10; // flat rate of $10 for packages under 5 lbs
        else
            return 20; // flat rate of $20
    }
}

public class DHLShippingStrategy : IShippingStrategy()
{
    private readonly IWeightMappingService _weightMappingService;

    public DHLShippingStrategy(IWeightMappingService weightMappingService)
    {
        _weightMappingService = weightMappingService;
    }

    public int CalculateShippingCost(Order order)
    {
        // some sort of database call needed to lookup pricing table and weight mappings
        return _weightMappingService.DeterminePrice(order);
    }
}

public class FedexShippingStrategy : IShippingStrategy()
{
    private readonly IZipCodePriceCalculator _zipCodePriceCalculator;

    public FedexShippingStrategy(IZipCodePriceCalculator zipCodePriceCalculator)
    {
        _zipCodePriceCalculator = zipCodePriceCalculator;
    }

    public int CalculateShippingCost(Order order)
    {
        // some sort of dynamic pricing based on zipcode
        // api call to a Fedex service to return dynamic price
        return _zipCodePriceService.CacluateShippingCost(order.OrderZipCode);
    }
}

上述问题是每个策略都需要附加和不同的服务来执行 CalculateShippingCost方法。这些接口/实现是否需要在入口点(Program类)中注册并通过构造函数传递?

The issue with the above is that each strategy requires additional and different services to perform the 'CalculateShippingCost' method. Do these interfaces/implementations need to be registered with the entry point (the Program class) and passed down through the constructors?

还有其他更合适的模式吗?完成上述方案?也许Unity可以专门处理某些事情( https:// msdn.microsoft.com/zh-CN/library/dn178463(v=pandp.30).aspx )?

Are there other patterns that would be a better fit to accomplish the above scenario? Maybe something that Unity could handle specifically (https://msdn.microsoft.com/en-us/library/dn178463(v=pandp.30).aspx)?

我非常感谢您的帮助或

谢谢,
安迪

推荐答案

有几种方法可以做到这一点,但是我更喜欢的方法是向工厂注入可用策略列表,然后过滤它们以返回您感兴趣的策略。

There are a few ways of doing this, but the way I prefer is to inject a list of available strategies into your factory, and then filtering them to return the one(s) you're interested in.

使用您的示例,我将修改 IShippingStrategy 以添加新属性:

Working with your example, I'd modify IShippingStrategy to add a new property:

public interface IShippingStrategy
{
    int CalculateShippingCost(Order order);
    string SupportedShippingMethod { get; }
}

然后我像这样实现工厂:

Then I'd implement the factory like so:

public class ShippingStrategyFactory : IShippingStrategyFactory
{
    private readonly IEnumerable<IShippingStrategy> availableStrategies;

    public ShippingStrategyFactory(IEnumerable<IShippingStrategy> availableStrategies)
    {
        this.availableStrategies = availableStrategies;
    }

    public IShippingStrategy GetShippingStrategy(Order order)
    {
        var supportedStrategy = availableStrategies
                .FirstOrDefault(x => x.SupportedShippingMethod == order.ShippingMethod);
        if (supportedStrategy == null)
        {
            throw new InvalidOperationException($"No supported strategy found for shipping method '{order.ShippingMethod}'.");
        }

        return supportedStrategy;
    }
}

我喜欢以此方式使用的主要原因是我再也不必回来修改工厂了。如果我必须实施新策略,则无需更改工厂。如果您正在对容器使用自动注册,那么您甚至都不需要注册新策略,因此这只是允许您花费更多时间编写新代码的情况。

The main reason I like using it this way is that I never have to come back and modify the factory. If ever I have to implement a new strategy, the factory doesn't have to be changed. If you're using auto-registration with your container, you don't even have to register the new strategy either, so it's simply a case of allowing you to spend more time writing new code.

这篇关于将策略和工厂模式与依赖注入一起使用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆