使用带有依赖注入的策略和工厂模式 [英] Using a Strategy and Factory Pattern with Dependency Injection

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

问题描述

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

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

我想知道是否有在工厂和策略模式中使用 DI 的最佳实践?

当策略(从工厂构建)需要为每个可能的构造函数和实现提供不同参数时,我的挑战就出现了.结果,我发现自己在服务入口点中声明了所有可能的接口,并将它们传递给应用程序.因此,必须为新的和各种策略类实现更改入口点.

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# 和 Unity for IoC/DI.

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/en-us/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)?

我非常感谢任何帮助或朝正确方向的推动.

I greatly appreciate any help or a nudge in the right direction.

谢谢,安迪

推荐答案

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

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天全站免登陆