سؤال

I am in the process of reading and wrapping my head around DependencyInjection in .Net. I am working on a new project and refactoring a lot of code and for the most part it is very exciting and promising. I am now trying to work out how to use Factories (in Ninject - but any IOC container would work at this point).

In the code below, I was testing the waters with some other code and am happy with the concept. However, as I refactor the code, I am kind of stuck on a clean way to do this. As you can see, there is a lot of duplication. Also, I am not sure if I should tuck the factory away inside of an IOC container (and don't know how to do this) or if it is okay to have the creation occur outside of the CompositionRoot. The only real important thing to keep is the MeasurementTypes enum as other code is reliant on distinguishing types at this level.

Can someone point me in the right direction? I have looked online and am obviously not putting it all together well.

public enum MeasurementTypes
{
    Position,
    Distance,
    Altitude,
    Velocity,
    Clock
}

public enum VelocityTypes
{
    [Description("meters / second")]
    MetersPerSecond,
    [Description("knots")]
    Knots
}

public enum DistanceTypes
{
    [Description("meters")]
    Meters,
    [Description("Nautical Miles")]
    NauticalMiles,
    [Description("Statute Miles")]
    StatuteMiles
}

public class UnitOfMeasurementFactory
{
    public static UnitOfMeasurement CreateUnitOfMeasurement(Enum type, string name = "anonymous", double value = 0)
    {
        if (type is VelocityTypes)
        {
            return VelocityUnitOfMeasurementFactory.CreateVelocityUnitOfMeasurement((VelocityTypes)type, name, value);
        }
        else if (type is DistanceTypes)
        {
            return DistanceUnitOfMeasurementFactory.CreateDistanceUnitOfMeasurement((DistanceTypes) type, name,
                                                                                    value);
        }

        throw new NotImplementedException();
    }
}

public class VelocityUnitOfMeasurementFactory
{
    public static UnitOfMeasurement CreateVelocityUnitOfMeasurement(VelocityTypes velocityType, string name, double value)
    {
        UnitOfMeasurement uom;

        switch (velocityType)
        {
            case VelocityTypes.Knots:
                uom = new Knots(name, value);
                break;
            case VelocityTypes.MetersPerSecond:
                uom = new MetersPerSecond(name, value);
                break;
            default:
                uom = new MetersPerSecond(name, value);
                break;
        }

        return uom;
    }
}

public class DistanceUnitOfMeasurementFactory
{
    public static UnitOfMeasurement CreateDistanceUnitOfMeasurement(DistanceTypes distanceType, string name,
                                                                    double value)
    {
        UnitOfMeasurement uom;

        switch (distanceType)
        {
            case DistanceTypes.Meters:
                uom = new Meters(name, value);
                break;
            case DistanceTypes.NauticalMiles:
                uom = new NauticalMiles(name, value);
                break;
            case DistanceTypes.StatuteMiles:
                uom = new StatuteMiles(name, value);
                break;
            default:
                uom = new Meters(name, value);
                break;
        }

        return uom;
        }
}
public sealed class Knots : UnitOfMeasurement
{
    public Knots(string name, double value)
    {
        MeasurementType = MeasurementTypes.Velocity;
        RoundingDigits = 5;
        ConvertToBaselineFactor = .514444;
        ConvertFromBaselineFactor = 1.94384;
        Name = name;
        Value = value;
        Units = "knots";

    }
}

public sealed class Meters : UnitOfMeasurement
{
    public Meters(string name, double value)
    {
        MeasurementType = MeasurementTypes.Distance;
        RoundingDigits = 5;
        ConvertToBaselineFactor = 1.0;
        ConvertFromBaselineFactor = 1.0;
        Name = name;
        Value = value;
        Units = "m";
    }
}
هل كانت مفيدة؟

المحلول

I'd do something like this.

interface IUnitOfMeasurementFactory
{
    T Create<T>(string name, double value) where T: UnitOfMeasurement;
}

class Program
{
    static void Main(string[] args)
    {
        var kernel = new StandardKernel();
        kernel.Bind<IUnitOfMeasurementFactory>().ToFactory();

        var factory = kernel.Get<IUnitOfMeasurementFactory>();
        var meters = factory.Create<Meters>("myDistance", 123.12);
        var knots = factory.Create<Knots>("mySpeed", 345.21)
    }
}

It skips the 'MeasurementTypes' concept, but you can manage that by making Meters implement IDistanceUnitOfMeasurement and Knots implement IVelocityUnitOfMeasurement.

نصائح أخرى

I would go with @shamp00's answer, but here you have a working implementation the way you were doing things:

using System.ComponentModel;
using System.Globalization;
using System.Linq;
using FluentAssertions;
using Ninject;
using Ninject.Activation;
using Ninject.Parameters;
using Ninject.Syntax;
using Xunit;

public class NinjectFactoryTest
{
    [Fact]
    public void Test()
    {
        var kernel = new StandardKernel();
        kernel.Bind<IUnitOfMeasurementFactory>().To<UnitOfMeasurementFactory>();
        kernel.Bind<UnitOfMeasurement>().To<Knots>()
            .WhenClassifiedBy(VelocityUnitOfMeasurementFactory.BuildClassification(VelocityTypes.Knots));

        kernel.Bind<UnitOfMeasurement>().To<Meters>()
            .WhenClassifiedBy(DistanceUnitOfMeasurementFactory.BuildClassification(DistanceTypes.Meters));

        const string ExpectedName = "hello";
        const double ExpectedValue = 5.5;

        var actualUnitOfMeasurement = kernel.Get<VelocityUnitOfMeasurementFactory>()
            .CreateVelocityUnitOfMeasurement(VelocityTypes.Knots, ExpectedName, ExpectedValue);

        actualUnitOfMeasurement.Should().BeOfType<Knots>();
        actualUnitOfMeasurement.Name.Should().Be(ExpectedName);
        actualUnitOfMeasurement.Value.Should().Be(ExpectedValue);
    }
}

public class ClassifiedParameter : Parameter
{
    public ClassifiedParameter(string classification)
        : base("Classification", ctx => null, false)
    {
        this.Classification = classification;
    }

    public string Classification { get; set; }
}

public static class ClassifiedBindingExtensions
{
    public static IBindingInNamedWithOrOnSyntax<T> WhenClassifiedBy<T>(this IBindingWhenSyntax<T> syntax, string classification)
    {
        return syntax.When(request => request.IsValidForClassification(classification));
    }

    public static bool IsValidForClassification(this IRequest request, string classification)
    {
        ClassifiedParameter parameter = request
            .Parameters
            .OfType<ClassifiedParameter>()
            .SingleOrDefault();

        return parameter != null && classification == parameter.Classification;
    }
}

public enum MeasurementTypes
{
    Position,
    Distance,
    Altitude,
    Velocity,
    Clock
}

public enum VelocityTypes
{
    [Description("meters / second")]
    MetersPerSecond,

    [Description("knots")]
    Knots
}

public enum DistanceTypes
{
    Meters,
    NauticalMiles,
    StatuteMiles
}

public interface IUnitOfMeasurementFactory
{
    UnitOfMeasurement Create(string classification, string name, double value);
}

internal class UnitOfMeasurementFactory : IUnitOfMeasurementFactory
{
    public const string ClassificationTemplate = "{0}://{1}";

    private readonly IResolutionRoot resolutionRoot;

    public UnitOfMeasurementFactory(IResolutionRoot resolutionRoot)
    {
        this.resolutionRoot = resolutionRoot;
    }

    public UnitOfMeasurement Create(string classification, string name, double value)
    {
        return this.resolutionRoot.Get<UnitOfMeasurement>(
            new ClassifiedParameter(classification),
            new ConstructorArgument("name", name),
            new ConstructorArgument("value", value));
    }
}

public class DistanceUnitOfMeasurementFactory
{
    private readonly IUnitOfMeasurementFactory factory;

    public DistanceUnitOfMeasurementFactory(IUnitOfMeasurementFactory factory)
    {
        this.factory = factory;
    }

    public static string BuildClassification(DistanceTypes distanceType)
    {
        return string.Format(
            CultureInfo.InvariantCulture,
            UnitOfMeasurementFactory.ClassificationTemplate,
            MeasurementTypes.Distance.ToString(),
            distanceType.ToString());
    }

    public UnitOfMeasurement CreateDistanceUnitOfMeasurement(DistanceTypes distanceType, string name, double value)
    {
        string classification = BuildClassification(distanceType);
        return this.factory.Create(classification, name, value);
    }
}

public class VelocityUnitOfMeasurementFactory
{
    private readonly IUnitOfMeasurementFactory factory;

    public VelocityUnitOfMeasurementFactory(IUnitOfMeasurementFactory factory)
    {
        this.factory = factory;
    }

    public static string BuildClassification(VelocityTypes velocityType)
    {
        return string.Format(
            CultureInfo.InvariantCulture,
            UnitOfMeasurementFactory.ClassificationTemplate,
            MeasurementTypes.Velocity.ToString(),
            velocityType.ToString());
    }

    public UnitOfMeasurement CreateVelocityUnitOfMeasurement(VelocityTypes velocityType, string name, double value)
    {
        string classification = BuildClassification(velocityType);
        return this.factory.Create(classification, name, value);
    }
}

public abstract class UnitOfMeasurement
{
    public MeasurementTypes MeasurementType { get; set; }

    public int RoundingDigits { get; set; }

    public string Name { get; set; }

    public double Value { get; set; }

    public string Units { get; set; }
}

public sealed class Knots : UnitOfMeasurement
{
    public Knots(string name, double value)
    {
        MeasurementType = MeasurementTypes.Velocity;
        RoundingDigits = 5;
        Name = name;
        Value = value;
        Units = "knots";
    }
}

public sealed class Meters : UnitOfMeasurement
{
    public Meters(string name, double value)
    {
        MeasurementType = MeasurementTypes.Distance;
        RoundingDigits = 5;
        Name = name;
        Value = value;
        Units = "m";
    }
}

Hint: It uses xunit ([Fact] attribute) but you can easily replace that by a Main Method. And also there are FluentAssertions which, of course, you can remove.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top