模式:工程化实现及扩展——工厂模式

相比较传统的工厂模式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
}

/// <summary>
/// 充当 依赖注入 的角色
/// </summary>
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就是实现类。

posted @ 2019-07-11 23:35  又见阿郎  阅读(427)  评论(0编辑  收藏  举报