相比較傳統的工廠模式IFactory/Concrete Factory會反覆引用並編譯代碼 但是作為開發人員,我們更希望的是少修改代碼,儘量從配置著手也就是設計模式的根本原則之一:開放封閉原則。如果我要增加新的產品,那麼修改就比較大了,對於業務來講還是可以接受的。但是如果可以做到不修改代碼是最好的。 ...
相比較傳統的工廠模式IFactory/Concrete Factory會反覆引用並編譯代碼
但是作為開發人員,我們更希望的是少修改代碼,儘量從配置著手也就是設計模式的根本原則之一:開放封閉原則。如果我要增加新的產品,那麼修改就比較大了,對於業務來講還是可以接受的。但是如果可以做到不修改代碼是最好的。上一份工作中,我印象最深的一句話就是我上司對我說的"能不改代碼就別改,能寫進配置里的就寫到配置里"。因此我們將要增加的工廠類寫到配置裡面。如此,新的產品類型和工廠類型即便在系統上線後仍可以通過修改配置文件的方式不斷補充。但是,還有一個問題,我們仍然需要為每"類"抽象產品定製特定的工廠介面並實現之,也就是"多頭管理"問題。泛型可以用來解決這個問題,我們定義一個泛型工廠即可。代碼如下:
/// <summary>
/// 工廠介面定義
/// </summary>
/// <remarks>
/// TTarget: 抽象產品類型
/// TSource: 具體產品類型
/// </remarks>
public interface IFactory
{
#region config and register type mapping
/// <summary>
/// 如果需要同時載入配置文件中定義的映射關係,可以按照SRP的原則定義獨立的配置類型。
/// 由該配置類型調用這兩個介面為Factory載入配置信息
/// </summary>
IFactory RegisterType<TTarget, TSource>(); // 註入產品
IFactory RegisterType<TTarget, TSource>(string name); // 註入產品
#endregion
#region factory method
TTarget Create<TTarget>();
TTarget Create<TTarget>(string name);
#endregion
}
public sealed class TypeRegistry
{
/// <summary>
/// default name in type mappings
/// </summary>
readonly string DefaultName = Guid.NewGuid().ToString();
/// <summary>
/// Type : TTarget, 抽象產品類型
/// IDictionary<string ,Type>
/// string : name
/// Type : TSource, 具體產品類型
/// </summary>
IDictionary<Type, IDictionary<string, Type>> registry =
new Dictionary<Type, IDictionary<string, Type>>();
public void RegisterType(Type targetType, Type sourceType)
{
RegisterType(targetType, sourceType, DefaultName);
}
public void RegisterType(Type targetType, Type sourceType, string name)
{
if(targetType == null) throw new ArgumentNullException("targetType");
if(sourceType == null) throw new ArgumentNullException("sourceType");
if(string.IsNullOrEmpty(name)) throw new ArgumentNullException("name");
if (!registry.TryGetValue(targetType, out IDictionary<string, Type> subDictionary))
{
subDictionary = new Dictionary<string, Type>
{
{ name, sourceType }
};
registry.Add(targetType, subDictionary);
}
else
{
if (subDictionary.ContainsKey(name))
throw new Exception($"{name}重覆");
subDictionary.Add(name, sourceType);
}
}
public Type this[Type targetType, string name]
{
get
{
if (targetType == null) throw new ArgumentNullException("targetType");
if (string.IsNullOrEmpty(name)) throw new ArgumentNullException("name");
if (registry.Count() == 0)
return null;
return
(registry.Where(x => x.Key == targetType)).FirstOrDefault().Value
.Where(x => string.Equals(name, x.Key)).FirstOrDefault().Value;
}
}
public Type this[Type targetType]
{
get { return this[targetType, DefaultName]; }
}
}
public class Factory : IFactory
{
protected TypeRegistry registry = new TypeRegistry();
#region IFactory Members
public IFactory RegisterType<TTarget, TSource>()
{
registry.RegisterType(typeof(TTarget), typeof(TSource));
return this;
}
public IFactory RegisterType<TTarget, TSource>(string name)
{
registry.RegisterType(typeof(TTarget), typeof(TSource), name);
return this;
}
public TTarget Create<TTarget>()
{
return (TTarget)Activator.CreateInstance(registry[typeof(TTarget)]);
}
public TTarget Create<TTarget>(string name)
{
return (TTarget)Activator.CreateInstance(registry[typeof(TTarget), name]);
}
#endregion
}
上面的示例表明新的工廠類型不僅可以完成經典工廠方法模式所希望實現的各項要求,也滿足抽象工廠的要求,同時他可以作為整個項目一個獨立的而且是唯一的工廠入口,供項目中各子系統訪問和使用。原因在於它的底層將工廠介面與抽象產品類型的依賴關係變成基於CLR"萬能工廠"類型Activator基於參數Type的構造。
工廠管理的是其內的產品。我們的工廠介面IFactory有兩個功能,一個是往工廠中註入產品,一個是創建指定產品的實例。藉助RegisterType將配置文件中定義的類型映射方希載入到新的具體工廠類型中,也就是重載函數中的參數(name)。我們通過字典Dictionary來管理維護工廠內的產品,將抽象產品也就是介面或是抽象類作為key,要考慮到同一介面可以有多個不同的實現,因此我們再維護一個實現類的字典,使用一個唯一的標識作為key就行,value就是實現類。