Puis-je utiliser des atsortingbuts pour que mon usine sache ce qu’elle peut / doit instancier sans enfreindre la règle du «couplage lâche»?

J’ai implémenté une usine dans mon projet et il a récemment été suggéré d’utiliser des atsortingbuts dans mes classes afin que l’usine puisse déterminer quelle classe instancier et retransmettre. Je suis nouveau dans le monde du développement et j’essaie de suivre de manière rigide la règle à couplage lâche. Je me demande si le fait de s’appuyer sur des “crochets” (étant les atsortingbuts) va à l’encontre de cela?

Je ne pense pas que l’utilisation d’atsortingbuts augmenterait le couplage entre l’usine et la classe qu’elle crée. En fait, cela diminuerait le couplage ici, car l’usine découvrirait les informations au moment de l’exécution via les atsortingbuts. Au mieux, vous négociez simplement le couplage entre la classe en cours de création pour le couplage à l’atsortingbut. Cela dit, je ne sais pas exactement ce qui vous achète. Le but de l’usine est que vous localisiez la logique de création en un seul endroit. En le plaçant dans des atsortingbuts, vous le répartissez à nouveau dans votre code, ce qui annule partiellement l’objective de l’usine: vous devez maintenant examiner à la fois l’usine et l’atsortingbut pour comprendre comment est créé l’object.

Bien sûr, je peux mal comprendre votre question. Vous voulez peut-être dire qu’une classe utilise des atsortingbuts sur ses propriétés pour indiquer laquelle des propriétés doit être instanciée par la fabrique. Dans ce cas, vous remplacez un mécanisme basé sur la configuration pour effectuer une dependency injection. Je peux certainement voir où cela pourrait être utile; demander à l’usine de découvrir les dépendances d’un object et de les créer automatiquement au moment de l’exécution. Dans ce cas, vous augmenteriez légèrement le couplage global de votre code, car il existe désormais une dépendance entre l’atsortingbut et la fabrique qui n’existait pas auparavant. Dans l’ensemble, vous pouvez toutefois réduire la complexité du code car vous pouvez vous passer d’un code spécifique pour chaque classe afin de fournir ses dépendances particulières ou de les découvrir à partir d’un fichier de configuration.

Si vous demandez si l’utilisation d’atsortingbuts est une bonne idée, je pense que nous avons probablement besoin de plus d’informations, mais puisque vous ne demandez que si vous allez enfreindre un principe d’utilisation en sortie, je ne le pense pas. Je ne vois pas que cela augmente le couplage entre l’usine et la classe en cours de création et n’augmente que légèrement le couplage global du code. Les usines, de par leur nature, nécessitent de toute façon plus de couplage que les autres classes. Rappelez-vous que c’est couplé de manière lâche , pas découplé . Le code non couplé ne fait rien. Vous avez besoin de relations entre les classes pour que quelque chose se passe.

Décorer les classes de produits d’une usine peut rendre le développement beaucoup plus facile et c’est quelque chose que je fais parfois. Ceci est particulièrement utile lorsque des produits doivent être créés sur la base d’un identifiant unique stocké dans une firebase database, par exemple. Il doit y avoir un mappage entre cet identifiant unique et la classe de produit et l’utilisation d’un atsortingbut rend cela très clair et réaliste. En plus de cela, il vous permet d’append des classes de produits sans changer l’usine.

Par exemple, vous pouvez décorer votre classe comme ceci:

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

Et vous pouvez implémenter votre usine comme ceci:

 public class ProductFactory : IProductFactory { private static Dictionary products = new Dictionary(); 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 atsortingbutes = type.GetCustomAtsortingbutes( typeof(ProductAtsortingbute), false) let atsortingbute = atsortingbutes[0] as ProductAtsortingbute select new { type, atsortingbute.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; } } 

Après cela, vous pouvez utiliser cette usine pour créer des produits comme celui-ci:

 private IProductFactory factory; public void SellProducts(IEnumerable productIds) { IEnumerable products = from productId in productIds select factory.CreateInstanceById(productId); foreach (var product in products) { product.Sell(); } } 

J’ai utilisé ce concept par exemple dans le passé pour créer des calculs de factures basés sur un identifiant de firebase database. La firebase database contenait une liste de calculs par type de facture. Les calculs réels ont été définis dans les classes C #.

Voici une implémentation d’usine que j’ai utilisée pour créer des instances concrètes basées sur une valeur d’atsortingbut. Il instancie également avec des parameters.

 class ViewFactory { public static IView GetViewType(ssortingng PropertyValue, SomeOtherObject parentControl){ Assembly assembly = Assembly.GetAssembly(typeof(ViewFactory)); var types = from type in assembly.GetTypes() where Atsortingbute.IsDefined(type,typeof(ViewTypeAtsortingbute)) select type; var objectType = types.Select(p => p). Where(t => t.GetCustomAtsortingbutes(typeof(ViewTypeAtsortingbute), false) .Any(att => ((ViewTypeAtsortingbute)att).name.Equals(PropertyValue))); IView myObject = (IView)Activator.CreateInstance(objectType.First(),parentControl); return myObject; } } [ViewTypeAtsortingbute("PropertyValue", "1.0")] class ListboxView : IView { public ListboxView(FilterDocumentChoseTypeFieldControll parentControl) { } public override void CreateChildrens() { } } 

Si quelqu’un a besoin d’une version utilisant 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(ssortingng[] 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 atsortingbutes of classes it can create? Reflection! // This works great and now all we have to do is add an atsortingbute 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.Create("A_ID").DoIt(); Factory.Create("B_ID").DoIt(); Console.ReadKey(); } } class TraditionalFactory { public static IDoSomething Create(ssortingng 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 ReturnableTypes; static ReflectionFactory() { ReturnableTypes = GetReturnableTypes(); } private static Dictionary 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 atsortingbute, 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 returnableTypes = new Dictionary(); Type outputType = typeof(IDoSomething); Type factoryLabelType = typeof(FactoryAtsortingbute); foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies()) { ssortingng assemblyName = assembly.GetName().Name; if (!assemblyName.StartsWith("System") && assemblyName != "mscorlib" && !assemblyName.StartsWith("Microsoft")) { foreach (Type type in assembly.GetTypes()) { if (type.GetCustomAtsortingbutes(factoryLabelType, false).Length > 0) { foreach (object label in ((FactoryAtsortingbute)type.GetCustomAtsortingbutes(factoryLabelType, true)[0]).Labels) { if (label != null && label.GetType() == typeof(ssortingng)) { if (outputType.IsAssignableFrom(type)) { returnableTypes.Add((ssortingng)label, type); } } } } } } } return returnableTypes; } public static IDoSomething Create(ssortingng 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(); } ///  /// Atsortingbute class for decorating classes to use with the generic Factory ///  public sealed class FactoryAtsortingbute : Atsortingbute { public IEnumerable Labels { get; private set; } public FactoryAtsortingbute(params object[] labels) { if (labels == null) { throw new ArgumentNullException("labels cannot be null"); } Labels = labels; } } ///  /// Custom exception class for factory creation errors ///  public class FactoryCreationException : Exception { public FactoryCreationException() : base("Factory failed to create object") { } } ///  /// Generic Factory class. Classes must have a parameterless constructor for use with this class. Decorate classes with /// FactoryAtsortingbute labels to match identifiers ///  /// Input identifier, matches FactoryAtsortingbute labels /// Output base class / interface public class Factory where TOutput : class { private static readonly Dictionary JumpTable; private static readonly Func Creator; static Factory() { JumpTable = new Dictionary(); Dictionary returnableTypes = GetReturnableTypes(); int index = 0; foreach (KeyValuePair kvp in returnableTypes) { JumpTable.Add(kvp.Key, index++); } Creator = CreateDelegate(returnableTypes); } ///  /// Creates a TOutput instance based on the label ///  /// Identifier label to create ///  public static TOutput Create(TInput label) { return Creator(label); } ///  /// Creates a TOutput instance based on the label ///  /// Identifier label to create /// default object to return if creation fails ///  public static TOutput Create(TInput label, TOutput defaultOutput) { try { return Create(label); } catch (FactoryCreationException) { return defaultOutput; } } private static Dictionary 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 atsortingbute, 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 returnableTypes = new Dictionary(); Type outputType = typeof(TOutput); Type factoryLabelType = typeof(FactoryAtsortingbute); foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies()) { ssortingng assemblyName = assembly.GetName().Name; if (!assemblyName.StartsWith("System") && assemblyName != "mscorlib" && !assemblyName.StartsWith("Microsoft")) { foreach (Type type in assembly.GetTypes()) { if (type.GetCustomAtsortingbutes(factoryLabelType, false).Length > 0) { foreach (object label in ((FactoryAtsortingbute)type.GetCustomAtsortingbutes(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 CreateDelegate(Dictionary returnableTypes) { // get FieldInfo reference to the jump table dictionary FieldInfo jumpTableFieldInfo = typeof(Factory).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), // 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).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 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)dynamicMethod.CreateDelegate(typeof(Func)); } }