Могу ли я использовать атрибуты, чтобы моя фабрика знала, что она может / должна создавать, не нарушая правило «слабосвязанной»? - PullRequest
3 голосов
/ 08 декабря 2010

Я реализовал фабрику в своем проекте, и недавно было предложено использовать атрибуты в моих классах, чтобы фабрика могла определить, какой класс создать и передать обратно.Я новичок в мире разработки и пытаюсь жестко следовать правилу слабосвязанной информации. Интересно, не противоречит ли использование «крючков» (являющихся атрибутами)?

Ответы [ 4 ]

6 голосов
/ 08 декабря 2010

Декорирование классов продукции фабрики может значительно облегчить разработку, и это то, чем я иногда занимаюсь.Это особенно полезно, когда продукты должны создаваться на основе уникального идентификатора, хранящегося, например, в базе данных.Должно быть сопоставление между этим уникальным идентификатором и классом продукта, и использование атрибута делает это очень понятным и пригодным для использования.Кроме того, он позволяет добавлять классы продуктов, не изменяя фабрику.

Например, вы можете украсить свой класс следующим образом:

[ProductAttribute(1)]
public class MyFirstProduct : IProduct
{
}

[ProductAttribute(2)]
public class MySecondProduct : IProduct
{
}

И вы можете реализовать свою фабрику следующим образом:

public class ProductFactory : IProductFactory
{
    private static Dictionary<int, Type> products =
        new Dictionary<int, Type>();

    static ProductFactory()
    {
        // Please note that this query is a bit simplistic. It doesn't
        // handle error reporting.
        var productsWithId =
          from type in 
              Assembly.GetExecutingAssembly().GetTypes()
          where typeof(IProduct).IsAssignableFrom(type)
          where !type.IsAbstract && !type.IsInterface
          let attributes = type.GetCustomAttributes(
            typeof(ProductAttribute), false)
          let attribute = attributes[0] as ProductAttribute
          select new { type, attribute.Id };

        products = productsWithId
            .ToDictionary(p => p.Id, p => p.type);
    }

    public IProduct CreateInstanceById(int id)
    {
        Type productType = products[id];

        return Activator.CreateInstance(productType) as IProduct;
    }
}

После этого вы можете использовать эту фабрику для создания таких продуктов:

private IProductFactory factory;

public void SellProducts(IEnumerable<int> productIds)
{
    IEnumerable<IProduct> products =
        from productId in productIds
        select factory.CreateInstanceById(productId);

    foreach (var product in products)
    {
        product.Sell();
    }
}

Я использовал эту концепцию в прошлом, например, для создания расчетов по счетам на основеидентификатор базы данных.База данных содержала список расчетов по типу счета.Фактические расчеты были определены в классах C #.

3 голосов
/ 08 декабря 2010

Я не думаю, что использование атрибутов увеличило бы связь между фабрикой и классом, который она создает, фактически, это уменьшило бы связь здесь, потому что фабрика обнаружила бы информацию во время выполнения через атрибуты. Во всяком случае, вы просто торгуете связью между классом, который создается для связи с атрибутом. Тем не менее, я не уверен, что именно это тебя покупает. Суть фабрики в том, что вы локализуете творческую логику в одном месте. Поместив его в атрибуты, вы снова распространили его по всему коду, частично нанося ущерб цели фабрики: теперь вы должны взглянуть на фабрику и атрибут, чтобы понять, как создается объект .

Конечно, я могу неправильно понять ваш вопрос. Вы можете иметь в виду, что класс использует атрибуты в своих свойствах, чтобы указать, какие из свойств должны быть созданы фабрикой. В этом случае вы заменяете некоторый механизм, управляемый конфигурацией, для внедрения зависимости. Я, конечно, вижу, где это может быть полезно; заставить фабрику обнаруживать зависимости объекта и автоматически создавать их во время выполнения. В этом случае вы бы немного увеличили общую связь вашего кода, поскольку теперь существует зависимость между атрибутом и фабрикой, которой раньше не было. В целом, хотя вы можете уменьшить сложность кода, так как вы сможете обойтись без конкретного кода для каждого класса, чтобы предоставить его конкретные зависимости или обнаружить их из файла конфигурации.

Если вы спрашиваете, является ли использование атрибутов хорошей идеей, я думаю, что нам, вероятно, нужна дополнительная информация, но, поскольку вы, похоже, спрашиваете только, собираетесь ли вы нарушать ОО-принцип, я так не думаю. Я не вижу, чтобы это увеличивало связь между фабрикой и создаваемым классом и лишь немного увеличивало общую связь кода. В любом случае, фабрики по своей природе нуждаются в большем количестве связей, чем другие классы. Помните, что слабо связаны , а не не связаны . Отцепленный код ничего не делает. Вам нужны отношения между классами, чтобы что-то произошло.

0 голосов
/ 13 ноября 2015

В случае, если кому-то нужна версия, которая использует System.Reflection.Emit ...

// just paste this into a Console App

using System;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;

class Program
{
    static void Main(string[] args)
    {
        // Here's the usage of a "traditional" factory, which returns objects that implement a common interface.
        // This is a great pattern for a lot of different scenarios.
        // The only downside is that you have to update your factory class whenever you add a new class.
        TraditionalFactory.Create("A_ID").DoIt();
        TraditionalFactory.Create("B_ID").DoIt();        
        Console.ReadKey();

        // But what if we make a class that uses reflection to find attributes of classes it can create? Reflection!
        // This works great and now all we have to do is add an attribute to new classes and this thing will just work.
        // (It could also be more generic in its input / output, but I simplified it for this example)
        ReflectionFactory.Create("A_ID").DoIt();
        ReflectionFactory.Create("B_ID").DoIt();
        // Wait, that's great and all, but everyone always says reflection is so slow, and this thing's going to reflect 
        // on every object creation...that's not good right?
        Console.ReadKey();

        // So I created this new factory class which gives the speed of the traditional factory combined with the flexibility 
        // of the reflection-based factory.
        // The reflection done here is only performed once. After that, it is as if the Create() method is using a switch statement        
        Factory<string, IDoSomething>.Create("A_ID").DoIt();
        Factory<string, IDoSomething>.Create("B_ID").DoIt();

        Console.ReadKey();
    }
}

class TraditionalFactory
{
    public static IDoSomething Create(string id)
    {
        switch (id)
        {
            case "A_ID":
                return new A();
            case "B_ID":
                return new B();
            default:
                throw new InvalidOperationException("Invalid factory identifier");
        }
    }
}

class ReflectionFactory
{
    private readonly static Dictionary<string, Type> ReturnableTypes;

    static ReflectionFactory()
    {
        ReturnableTypes = GetReturnableTypes();
    }

    private static Dictionary<string, Type> GetReturnableTypes()
    {
        // get a list of the types that the factory can return
        // criteria for matching types:
        // - must have a parameterless constructor
        // - must have correct factory attribute, with non-null, non-empty value
        // - must have correct BaseType (if OutputType is not generic)
        // - must have matching generic BaseType (if OutputType is generic)

        Dictionary<string, Type> returnableTypes = new Dictionary<string, Type>();

        Type outputType = typeof(IDoSomething);
        Type factoryLabelType = typeof(FactoryAttribute);
        foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
        {
            string assemblyName = assembly.GetName().Name;
            if (!assemblyName.StartsWith("System") &&
                assemblyName != "mscorlib" &&
                !assemblyName.StartsWith("Microsoft"))
            {
                foreach (Type type in assembly.GetTypes())
                {
                    if (type.GetCustomAttributes(factoryLabelType, false).Length > 0)
                    {
                        foreach (object label in ((FactoryAttribute)type.GetCustomAttributes(factoryLabelType, true)[0]).Labels)
                        {
                            if (label != null && label.GetType() == typeof(string))
                            {
                                if (outputType.IsAssignableFrom(type))
                                {
                                    returnableTypes.Add((string)label, type);
                                }
                            }
                        }
                    }
                }
            }
        }

        return returnableTypes;
    }

    public static IDoSomething Create(string id)
    {
        if (ReturnableTypes.ContainsKey(id))
        {
            return (IDoSomething)Activator.CreateInstance(ReturnableTypes[id]);
        }
        else
        {
            throw new Exception("Invalid factory identifier");
        }
    }
}

[Factory("A_ID")]
class A : IDoSomething
{
    public void DoIt()
    {
        Console.WriteLine("Letter A");
    }
}

[Factory("B_ID")]
class B : IDoSomething
{
    public void DoIt()
    {
        Console.WriteLine("Letter B");
    }
}

public interface IDoSomething
{
    void DoIt();
}

/// <summary>
/// Attribute class for decorating classes to use with the generic Factory
/// </summary>
public sealed class FactoryAttribute : Attribute
{
    public IEnumerable<object> Labels { get; private set; }
    public FactoryAttribute(params object[] labels)
    {
        if (labels == null)
        {
            throw new ArgumentNullException("labels cannot be null");
        }
        Labels = labels;
    }
}

/// <summary>
/// Custom exception class for factory creation errors
/// </summary>
public class FactoryCreationException : Exception
{
    public FactoryCreationException()
        : base("Factory failed to create object")
    {
    }
}

/// <summary>
/// Generic Factory class.  Classes must have a parameterless constructor for use with this class.  Decorate classes with 
/// <c>FactoryAttribute</c> labels to match identifiers
/// </summary>
/// <typeparam name="TInput">Input identifier, matches FactoryAttribute labels</typeparam>
/// <typeparam name="TOutput">Output base class / interface</typeparam>
public class Factory<TInput, TOutput>
    where TOutput : class
{
    private static readonly Dictionary<TInput, int> JumpTable;
    private static readonly Func<TInput, TOutput> Creator;

    static Factory()
    {
        JumpTable = new Dictionary<TInput, int>();
        Dictionary<TInput, Type> returnableTypes = GetReturnableTypes();
        int index = 0;
        foreach (KeyValuePair<TInput, Type> kvp in returnableTypes)
        {
            JumpTable.Add(kvp.Key, index++);
        }
        Creator = CreateDelegate(returnableTypes);
    }

    /// <summary>
    /// Creates a TOutput instance based on the label
    /// </summary>
    /// <param name="label">Identifier label to create</param>
    /// <returns></returns>
    public static TOutput Create(TInput label)
    {
        return Creator(label);
    }

    /// <summary>
    /// Creates a TOutput instance based on the label
    /// </summary>
    /// <param name="label">Identifier label to create</param>
    /// <param name="defaultOutput">default object to return if creation fails</param>
    /// <returns></returns>
    public static TOutput Create(TInput label, TOutput defaultOutput)
    {
        try
        {
            return Create(label);
        }
        catch (FactoryCreationException)
        {
            return defaultOutput;
        }
    }

    private static Dictionary<TInput, Type> GetReturnableTypes()
    {
        // get a list of the types that the factory can return
        // criteria for matching types:
        // - must have a parameterless constructor
        // - must have correct factory attribute, with non-null, non-empty value
        // - must have correct BaseType (if OutputType is not generic)
        // - must have matching generic BaseType (if OutputType is generic)

        Dictionary<TInput, Type> returnableTypes = new Dictionary<TInput, Type>();

        Type outputType = typeof(TOutput);
        Type factoryLabelType = typeof(FactoryAttribute);
        foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
        {
            string assemblyName = assembly.GetName().Name;
            if (!assemblyName.StartsWith("System") &&
                assemblyName != "mscorlib" &&
                !assemblyName.StartsWith("Microsoft"))
            {
                foreach (Type type in assembly.GetTypes())
                {
                    if (type.GetCustomAttributes(factoryLabelType, false).Length > 0)
                    {
                        foreach (object label in ((FactoryAttribute)type.GetCustomAttributes(factoryLabelType, true)[0]).Labels)
                        {
                            if (label != null && label.GetType() == typeof(TInput))
                            {
                                if (outputType.IsAssignableFrom(type))
                                {
                                    returnableTypes.Add((TInput)label, type);
                                }
                            }
                        }
                    }
                }
            }
        }

        return returnableTypes;
    }

    private static Func<TInput, TOutput> CreateDelegate(Dictionary<TInput, Type> returnableTypes)
    {
        // get FieldInfo reference to the jump table dictionary
        FieldInfo jumpTableFieldInfo = typeof(Factory<TInput, TOutput>).GetField("JumpTable", BindingFlags.Static | BindingFlags.NonPublic);
        if (jumpTableFieldInfo == null)
        {
            throw new InvalidOperationException("Unable to get jump table field");
        }

        // set up the IL Generator
        DynamicMethod dynamicMethod = new DynamicMethod(
            "Magic",                                        // name of dynamic method
            typeof(TOutput),                                // return type
            new[] { typeof(TInput) },                        // arguments
            typeof(Factory<TInput, TOutput>),                // owner class
            true);
        ILGenerator gen = dynamicMethod.GetILGenerator();

        // define labels (marked later as IL is emitted)
        Label creationFailedLabel = gen.DefineLabel();
        Label[] jumpTableLabels = new Label[JumpTable.Count];
        for (int i = 0; i < JumpTable.Count; i++)
        {
            jumpTableLabels[i] = gen.DefineLabel();
        }

        // declare local variables
        gen.DeclareLocal(typeof(TOutput));
        gen.DeclareLocal(typeof(TInput));
        LocalBuilder intLocalBuilder = gen.DeclareLocal(typeof(int));

        // emit MSIL instructions to the dynamic method
        gen.Emit(OpCodes.Ldarg_0);
        gen.Emit(OpCodes.Stloc_1);
        gen.Emit(OpCodes.Volatile);
        gen.Emit(OpCodes.Ldsfld, jumpTableFieldInfo);
        gen.Emit(OpCodes.Ldloc_1);
        gen.Emit(OpCodes.Ldloca_S, intLocalBuilder);
        gen.Emit(OpCodes.Call, typeof(Dictionary<TInput, int>).GetMethod("TryGetValue"));
        gen.Emit(OpCodes.Brfalse, creationFailedLabel);
        gen.Emit(OpCodes.Ldloc_2);
        // execute the MSIL switch statement
        gen.Emit(OpCodes.Switch, jumpTableLabels);

        // set up the jump table
        foreach (KeyValuePair<TInput, int> kvp in JumpTable)
        {
            gen.MarkLabel(jumpTableLabels[kvp.Value]);
            // create the type to return
            gen.Emit(OpCodes.Newobj, returnableTypes[kvp.Key].GetConstructor(Type.EmptyTypes));
            gen.Emit(OpCodes.Ret);
        }

        // CREATION FAILED label
        gen.MarkLabel(creationFailedLabel);
        gen.Emit(OpCodes.Newobj, typeof(FactoryCreationException).GetConstructor(Type.EmptyTypes));
        gen.Emit(OpCodes.Throw);

        // create a delegate so we can later invoke the dynamically created method
        return (Func<TInput, TOutput>)dynamicMethod.CreateDelegate(typeof(Func<TInput, TOutput>));
    }
}
0 голосов
/ 28 марта 2013

Вот фабричная реализация, которую я использовал для создания конкретных экземпляров на основе значения атрибута. Он также создается с параметрами.

class ViewFactory
{
    public static IView GetViewType(string PropertyValue, SomeOtherObject parentControl){
    Assembly assembly = Assembly.GetAssembly(typeof(ViewFactory));
    var types = from type in assembly.GetTypes()
                where Attribute.IsDefined(type,typeof(ViewTypeAttribute))
                select type;

    var objectType = types.Select(p => p).
        Where(t => t.GetCustomAttributes(typeof(ViewTypeAttribute), false)
            .Any(att => ((ViewTypeAttribute)att).name.Equals(PropertyValue)));

    IView myObject = (IView)Activator.CreateInstance(objectType.First(),parentControl);
    return myObject;
}
}

[ViewTypeAttribute("PropertyValue", "1.0")]
class ListboxView : IView
{


public ListboxView(FilterDocumentChoseTypeFieldControll parentControl)
{

}       
public override void CreateChildrens()
{

}

}
...