[英]Can I use Attributes so my Factory knows what it can/should instantiate without breaking the “Loosely-Coupled” rule?
我在我的項目中實現了一個工廠,最近建議我在我的類上使用屬性,以便工廠可以確定要實例化和傳回的類。 我是開發世界的新手,並試圖嚴格遵循松散耦合的規則,我想知道依賴“鈎子”(作為屬性)是否違背了這一點?
裝飾工廠的產品類可以使開發更容易,這是我有時會做的事情。 當必須基於存儲在數據庫中的唯一標識符創建產品時,這尤其有用。 必須在該唯一ID和產品類之間建立映射,並且使用屬性使其非常清晰且可重用。 除此之外,它允許您添加產品類,而無需更改工廠。
例如,您可以像這樣裝飾您的類:
[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#類中定義的。
我不認為使用屬性會增加工廠和它創建的類之間的耦合,事實上,它會減少耦合,因為工廠將通過屬性在運行時發現信息。 如果你只是簡單地交換為了與屬性耦合而創建的類之間的耦合。 那就是說,我不確定是什么讓你買的。 工廠的意義在於您將創建邏輯本地化在一個地方。 通過將其放入屬性中,您再次將其遍布到代碼中,部分地破壞了工廠的目的:現在您必須同時查看工廠和屬性以了解對象的創建方式。
當然,我可能會誤解你的問題。 您可能意味着類使用其屬性上的屬性來指示工廠需要實例化哪些屬性。 在這種情況下,您將替換一些配置驅動的機制來執行依賴項注入。 我當然可以看到哪些可能有用; 使工廠發現對象的依賴關系,並在運行時自動創建它們。 在這種情況下,您將略微增加代碼的整體耦合,因為現在屬性和工廠之間存在以前不存在的依賴關系。 總的來說,雖然您可能會降低代碼復雜性,因為您可以在沒有特定代碼的情況下為每個類提供其特定的依賴關系或從配置文件中發現它們。
如果你問的是使用屬性是一個好主意,我想我們可能需要更多的信息,但由於你似乎只是在詢問你是否會違反OO原則,我不這么認為。 我沒有看到它增加了工廠和正在創建的類之間的耦合,只是略微增加了代碼的整體耦合。 無論如何,工廠本質上需要比其他類更多的耦合。 記住,它松散耦合 ,而不是解耦 。 未耦合的代碼不執行任何操作。 您需要在類之間建立關系以使任何事情發生。
這是一個工廠實現,我用它來創建基於屬性值的具體實例。 它還使用參數進行實例化。
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<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>));
}
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.