问题
I have implemented a factory in my project and it was recently suggested that I use attributes on my classes so the factory can determine which class to instantiate and pass back. I am new to the world of development and trying to rigidly follow the loosely-coupled rule, I wondering if relying on "hooks" (being the attributes) goes against this?
回答1:
I don't think that using attributes would be increasing the coupling between the factory and the class it creates, in fact, it would decrease the coupling here because the factory would be discovering the information at runtime via the attributes. If anything you're simply trading the coupling between the class being created for the coupling to the attribute. That said, I'm not sure exactly what that buys you. The point of the factory is that you localize the creational logic in a single place. By putting it into attributes you've spread it all over your code again, partially defeating the purpose of the factory: now you have to look at both the factory and the attribute to understand how the object is being created.
Of course, I may be misunderstanding your question. You may mean that a class uses attributes on it's properties to indicate which of the properties need to be instantiated by the factory. In this case, you're replacing some configuration-driven mechanism for doing dependency injection. I can certainly see where that might be useful; having the factory discover an object's dependencies and automatically create them as well at runtime. In this case, you'd be slightly increasing the overall coupling of your code, since there is now a dependency between the attribute and the factory that didn't exist before. Overall, though you might decrease code complexity since you'd be able to do without specific code for each class to supply its particular dependencies or discover them from a configuration file.
If you're asking if using attributes a good idea, I think we probably need more information, but since you seem to be asking only if you're going to violate an OO principle, I don't think so. I don't see it increasing the coupling between the factory and the class being created and only slightly increasing the overall coupling of the code. Factories, by their nature, need more coupling than other classes anyway. Remember, it's loosely-coupled, not uncoupled. Uncoupled code doesn't do anything. You need relationships between classes to make anything happen.
回答2:
Decorate product classes of a factory can make development much easier and it is something I sometime do. This is especially useful when products must be created based on a unique identifier stored in a database for instance. There must be a mapping between that unique id and the product class and using an attribute makes this very clear and reabable. Besides this, it allows you to add product classes, without changing the factory.
For instance, you can decorate your class like this:
[ProductAttribute(1)]
public class MyFirstProduct : IProduct
{
}
[ProductAttribute(2)]
public class MySecondProduct : IProduct
{
}
And you can implement your factory like this:
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 doing this you can use that factory for creating products like this:
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've used this concept in the past for instance to create invoice calculations based on a database identifier. The database contained a list of calculations per type of invoice. The actual calculations were defined in C# classes.
回答3:
Here is a factory implementation that i used to create concrete instances based on a attribute value. It also instantiates 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()
{
}
}
回答4:
In case anyone needs a version that uses 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>));
}
}
来源:https://stackoverflow.com/questions/4387573/can-i-use-attributes-so-my-factory-knows-what-it-can-should-instantiate-without