Can I use attributes to let my Factory know what it can / should create without breaking the Looseely Coupled rule?

I implemented a factory in my project, and it has recently been suggested to use attributes for my classes so that the factory can determine which class should be created and passed back. I am new to the development world and try to strictly follow the rule with limited connection, I wonder if I rely on “hooks” (being attributes)?

+3
source share
4 answers

I don’t think that using attributes will increase the connection between the factory and the class it creates, in fact, it will reduce the connection here because the factory will detect information at runtime through the attributes. If anything, you are simply trading the link between the created class to link to the attribute. However, I'm not sure what exactly you are buying. The factory point is that you localize the creation logic in one place. Having placed it in the attributes, you again distributed it throughout your code, partially defeating the factory goal: now you need to look at the factory attribute and to understand how the object is created.

Of course, I may not understand your question. You can keep in mind that a class uses attributes in its properties to indicate which of the properties should be instantiated by the factory. In this case, you are replacing some configuration-driven mechanism for performing dependency injection. Of course, I can see where this can be useful; Using factory, they detect object dependencies and automatically create them at run time. In this case, you will slightly increase the overall relationship of your code, since now there is a dependency between the attribute and the factory that did not exist before. In general, although you can reduce the complexity of the code, since you can do without specific code for each class to provide your specific dependencies or discover them from the configuration file.

If you ask if using attributes is a good idea, I think we probably need more information, but since you seem to ask only if you violate the OO principle, I don’t think so. I don’t see that this increases the connection between the factory and the class being created and only slightly increases the overall code connection. Factories, by their nature, need more communication than other classes. Remember that it is loosely coupled, not disconnected. Unrelated code does nothing. You need relationships between classes for something to happen.

+3
source

Decorating product classes with a factory can greatly simplify development, and this is what I ever do. This is especially useful when products must be created based on a unique identifier stored in a database, for example. There must be a mapping between this unique identifier and the product class, and using the attribute makes it very clear and robust. In addition, it allows you to add product classes without changing the factory.

For example, you can decorate your class as follows:

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

And you can implement your factory as follows:

 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; } } 

After that, you can use this factory to create these products:

 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(); } } 

I used this concept in the past, for example, to create invoice calculations based on a database identifier. The database contained a list of settlements by invoice type. Actual calculations have been defined in C # classes.

+4
source

Here is the factory implementation that I used to create specific instances based on the attribute value. It also creates parameters with parameters.

 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() { } } 
0
source

In case someone needs a version using 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 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 great and all, but everyone always says reflection is so slow, and this thing going to reflect // on every object creation...that 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
source

Source: https://habr.com/ru/post/1332879/


All Articles