我可以使用属性,以便我的工厂知道它可以/应该实例化而不破坏“松散耦合”规则吗?

我在我的项目中实现了一个工厂,最近建议我在我的类上使用属性,以便工厂可以确定要实例化和传回的类。 我是开发世界的新手,并试图严格遵循松散耦合的规则,我想知道依赖“钩子”(作为属性)是否违背了这一点?

我不认为使用属性会增加工厂和它创建的类之间的耦合,事实上,它会减少耦合,因为工厂将通过属性在运行时发现信息。 如果你只是简单地交换为了与属性耦合而创建的类之间的耦合。 那就是说,我不确定那到底是买什么的。 工厂的意义在于您将创建逻辑本地化在一个地方。 通过将其放入属性中,您再次将其遍布到代码中,部分地破坏了工厂的目的:现在您必须同时查看工厂属性以了解对象的创建方式。

当然,我可能会误解你的问题。 您可能意味着类使用其属性上的属性来指示工厂需要实例化哪些属性。 在这种情况下,您将替换一些配置驱动的机制来执行依赖项注入。 我当然可以看到哪些可能有用; 使工厂发现对象的依赖关系并在运行时自动创建它们。 在这种情况下,您将稍微增加代码的整体耦合,因为现在属性和工厂之间存在依赖关系。 总的来说,虽然您可能会降低代码复杂性,因为您可以在没有特定代码的情况下为每个类提供其特定的依赖项或从配置文件中发现它们。

如果你问是否使用属性是一个好主意,我想我们可能需要更多的信息,但由于你似乎只是在问你是否会违反OO原则,我不这么认为。 我没有看到它增加了工厂和正在创建的类之间的耦合,只是略微增加了代码的整体耦合。 工厂本质上需要比其他类更多的耦合。 请记住,它松散耦合 ,而不是解耦未耦合的代码不做任何事情。 您需要在类之间建立关系以使任何事情发生。

装饰工厂的产品类可以使开发更容易,这是我有时会做的事情。 当必须基于存储在数据库中的唯一标识符创建产品时,这尤其有用。 必须在该唯一ID和产品类之间建立映射,并且使用属性使其非常清晰且可重用。 除此之外,它还允许您添加产品类,而无需更改工厂。

例如,您可以像这样装饰您的类:

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

你可以这样实现你的工厂:

 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 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 productIds) { IEnumerable products = from productId in productIds select factory.CreateInstanceById(productId); foreach (var product in products) { product.Sell(); } } 

我过去曾使用过这个概念,例如根据数据库标识符创建发票计算。 该数据库包含每种发票类型的计算列表。 实际计算是在C#类中定义的。

这是一个工厂实现,我用它来创建基于属性值的具体实例。 它还使用参数进行实例化。

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

如果有人需要使用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.Create("A_ID").DoIt(); Factory.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 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 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 returnableTypes = new Dictionary(); 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(); } ///  /// Attribute class for decorating classes to use with the generic Factory ///  public sealed class FactoryAttribute : Attribute { public IEnumerable Labels { get; private set; } public FactoryAttribute(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 /// FactoryAttribute labels to match identifiers ///  /// Input identifier, matches FactoryAttribute 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 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 returnableTypes = new Dictionary(); 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 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)); } }