【问题标题】:Naming convention for GoF Factory?GoF 工厂的命名约定?
【发布时间】:2015-07-07 08:05:19
【问题描述】:

此模式使用抽象工厂,然后是工厂的实现。

我确信这两个类有一个标准的命名约定,但我不知道它是什么。

例如:

public abstract class ChocolateFactory { };

public class MyChocolateFactory { } : ChocolateFactory

这里的标准约定是什么?

我在考虑 ChocolateFactoryBase 或 ConcreteChocolateFactory,但也许还有其他东西(很像 Enums 往往以 Enum 为后缀,例如PetTypeEnum,这样你就可以做到PetTypeEnum PetType;

希望这不是主观的。

【问题讨论】:

  • 两个名字都没有说什么,所以他们都一样糟糕。通常最好使用描述性名称,例如“WhiteChocolateFactory”,它是一个具体的工厂。只是为了造厂而造厂是没有意义的;如果有不同的实现创建某个“类”的东西,你通常会做这些。使用该类的名称作为前缀是有意义的。
  • 根据GoF(而不是简单的工厂),而不是仅仅创建一个简单的工厂,例如CustomerFactory,你应该做一个抽象版本,然后是一个实现,这样将来有人可以把工厂换成他们自己的实现。
  • 是的,我知道模式; (从我的脑海中浮出水面...... :) 他们解释的上下文是 f.ex。如果你有一个“小部件”集,你可以换掉渲染引擎。如果您查看示例,您会发现它们使用渲染引擎的名称作为工厂名称。至于我的评论:请注意,渲染引擎很可能会随着时间而改变(因为每天都在发明新的 UI 工具包),所以在这里使用工厂是有意义的;如果工厂不改变,那么使用工厂是没有意义的。
  • 我猜DefaultChocolateFactory() 最有意义。
  • 如果你有一个 Abstract factory ,你应该总是提供一个默认的实现,但是因为问题是关于命名的:默认...糟透了! DefaultFactory 会做什么?为什么是默认的?那么所有其他实现都是特殊的吗?

标签: c# factory abstract factory-pattern


【解决方案1】:

问题和答案

好的,这个问题从抽象工厂的命名问题开始。根据经验,始终使用“正式”名称(例如 Factory、Decorator 等)和具体实现的描述(例如 Snickers、Mars、MotifWidget 等)。

所以基本上,你创建了一个MSSQLConnection,这是你描述的具体事物,Factory,这意味着它遵循工厂模式的特征。

好的,到目前为止命名和原始问题。现在是很酷的东西。讨论的是在 C# 中实现抽象工厂的最佳方式,这是一个不同的主题。我在用 C# 实现所有设计模式方面做了很多工作,为此我将在这里分享有关工厂的一些细节。如下:

抽象工厂和工厂

抽象工厂基本上是基类或接口与具体实现的组合。如果您共享大量代码,则需要一个基类;如果您不共享,则需要一个接口。

我通常会区分“工厂”和“抽象工厂”。工厂是创建对象(某种类型)的事物,“抽象工厂”是创建任意类型对象的事物。因此,抽象工厂的实现就是工厂。这与下一条信息有关。

工厂模式

支持 RTTI 的语言能够实现工厂模式。工厂模式是一种创建对象的东西。最简单的实现是一个只包含创建对象的方法的类,例如:

// ...

public void CreateConnection()
{
    return new SqlConnection();
}

// ...

您通常使用它来抽象事物。例如,HTML 解析器中生成 XML 节点的事物会根据 HTML 标记创建某种类型的节点。

工厂通常根据运行时信息做出决策。因此可以推广工厂模式来实现类似的东西:

public T Create(string name) 
{
    // lookup constructor, invoke.
}

使用 RTTI 创建一个通用工厂模式非常容易,它为每个名称存储一个Type。查找名称,使用反射创建对象。完成。

哦,作为奖励,您编写的代码比手工制作所有工厂要少得多。因为所有的实现都是一样的,你不妨把它放在一个基类中,然后在一个静态构造函数中填充 Dictionary。

概括抽象工厂

抽象工厂基本上是工厂的集合,它们以与工厂模式相同的方式创建对象。唯一共享的是接口(例如创建或者您可以使用继承来创建抽象)。

实现很简单,所以我就先放在那里。

工厂和类型解耦

让我们回到 GoF 示例。他们谈论MotifFactory 和PMFactory。将来我们会遇到另一个 UI 事物,我们需要 ASPNETFactory 或 SilverlightFactory。然而,未来是未知的,如果我们不需要,我们宁愿不发布我们的旧 DLL——毕竟,这不灵活。

如果我们想向工厂添加新方法,则会出现第二个问题。因此,这样做将涉及更改所有工厂。您可能已经猜到了,我不想在多个地方更改它。

幸运的是,我们可以解决这两个问题。接口是相同的(甚至可以泛化),所以我们可以在运行时简单地向我们的工厂添加新功能。

我们可以使用属性告诉类它应该由某个工厂实现,而不是告诉工厂要创建什么对象。我们还可以在加载程序集期间扫描所有类型,因此如果加载了程序集,我们可以简单地即时构建新工厂。

我为此牺牲的是编译时检查,但因为工厂模式通常使用运行时信息,所以这不一定是问题。

总结一下,这是我工厂的代码:

/// <summary>
/// This attribute is used to tag classes, enabling them to be constructed by a Factory class. See the <see cref="Factory{Key,Intf}"/> 
/// class for details.
/// </summary>
/// <remarks>
/// <para>
/// It is okay to mark classes with multiple FactoryClass attributes, even when using different keys or different factories.
/// </para>
/// </remarks>
/// <seealso cref="Factory{Key,Intf}"/>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)]
public class FactoryClassAttribute : Attribute
{
    /// <summary>
    /// This marks a class as eligible for construction by the specified factory type.
    /// </summary>
    /// <example>
    /// [FactoryClass("ScrollBar", typeof(MotifFactory))]
    /// public class MotifScrollBar : IControl { }
    /// </example>
    /// <param name="key">The key used to construct the object</param>
    /// <param name="factoryType">The type of the factory class</param>
    public FactoryClassAttribute(object key, Type factoryType)
    {
        if ((factoryType.IsGenericType &&
             factoryType.GetGenericTypeDefinition() == typeof(Factory<,>)) ||
            factoryType.IsAbstract || 
            factoryType.IsInterface)
        {
            throw new NotSupportedException("Incorrect factory type: you cannot use GenericFactory or an abstract type as factory.");
        }
        this.Key = key;
        this.FactoryType = factoryType;
    }

    /// <summary>
    /// The key used to construct the object when calling the <see cref="Factory{Key,Intf}.Create(Key)"/> method.
    /// </summary>
    public object Key { get; private set; }

    /// <summary>
    /// The type of the factory class
    /// </summary>
    public Type FactoryType { get; private set; }
}

/// <summary>
/// Provides an interface for creating related or dependent objects.
/// </summary>
/// <remarks>
/// <para>
/// This class is an implementation of the Factory pattern. Your factory class should inherit this Factory class and 
/// you should use the [<see cref="FactoryClassAttribute"/>] attribute on the objects that are created by the factory.
/// The implementation assumes all created objects share the same constructor signature (which is not checked by the Factory). 
/// All implementations also share the same <typeparamref name="Intf"/> type and are stored by key. During runtime, you can 
/// use the Factory class implementation to build objects of the correct type.
/// </para>
/// <para>
/// The Abstract Factory pattern can be implemented by adding a base Factory class with multiple factory classes that inherit from 
/// the base class and are used for registration. (See below for a complete code example).
/// </para>
/// <para>
/// Implementation of the Strategy pattern can be done by using the Factory pattern and making the <typeparamref name="Intf"/>
/// implementations algorithms. When using the Strategy pattern, you still need to have some logic that picks when to use which key.
/// In some cases it can be useful to use the Factory overload with the type conversion to map keys on other keys. When implementing 
/// the strategy pattern, it is possible to use this overload to determine which algorithm to use.
/// </para>
/// </remarks>
/// <typeparam name="Key">The type of the key to use for looking up the correct object type</typeparam>
/// <typeparam name="Intf">The base interface that all classes created by the Factory share</typeparam>
/// <remarks>
/// The factory class automatically hooks to all loaded assemblies by the current AppDomain. All classes tagged with the FactoryClass
/// are automatically registered.
/// </remarks>
/// <example>
/// <code lang="c#">
/// // Create the scrollbar and register it to the factory of the Motif system
/// [FactoryClass("ScrollBar", typeof(MotifFactory))]
/// public class MotifScrollBar : IControl { }
/// 
/// // [...] add other classes tagged with the FactoryClass attribute here...
///
/// public abstract class WidgetFactory : Factory&lt;string, IControl&gt;
/// {
///     public IControl CreateScrollBar() { return Create("ScrollBar") as IScrollBar; }
/// }
///
/// public class MotifFactory : WidgetFactory { }
/// public class PMFactory : WidgetFactory { }
///
/// // [...] use the factory to create a scrollbar
/// 
/// WidgetFactory widgetFactory = new MotifFactory();
/// var scrollbar = widgetFactory.CreateScrollBar(); // this is a MotifScrollbar intance
/// </code>
/// </example>
public abstract class Factory<Key, Intf> : IFactory<Key, Intf>
    where Intf : class
{
    /// <summary>
    /// Creates a factory by mapping the keys of the create method to the keys in the FactoryClass attributes.
    /// </summary>
    protected Factory() : this((a) => (a)) { }

    /// <summary>
    /// Creates a factory by using a custom mapping function that defines the mapping of keys from the Create 
    /// method, to the keys in the FactoryClass attributes.
    /// </summary>
    /// <param name="typeConversion">A function that maps keys passed to <see cref="Create(Key)"/> to keys used with [<see cref="FactoryClassAttribute"/>]</param>
    protected Factory(Func<Key, object> typeConversion)
    {
        this.typeConversion = typeConversion;
    }

    private Func<Key, object> typeConversion;
    private static object lockObject = new object();
    private static Dictionary<Type, Dictionary<object, Type>> dict = null;

    /// <summary>
    /// Creates an instance a class registered with the <see cref="FactoryClassAttribute"/> attribute by looking up the key.
    /// </summary>
    /// <param name="key">The key used to lookup the attribute. The key is first converted using the typeConversion function passed 
    /// to the constructor if this was defined.</param>
    /// <returns>An instance of the factory class</returns>
    public virtual Intf Create(Key key)
    {
        Dictionary<Type, Dictionary<object, Type>> dict = Init();
        Dictionary<object, Type> factoryDict;
        if (dict.TryGetValue(this.GetType(), out factoryDict))
        {
            Type t;
            return (factoryDict.TryGetValue(typeConversion(key), out t)) ? (Intf)Activator.CreateInstance(t) : null;
        }
        return null;
    }

    /// <summary>
    /// Creates an instance a class registered with the <see cref="FactoryClassAttribute"/> attribute by looking up the key.
    /// </summary>
    /// <param name="key">The key used to lookup the attribute. The key is first converted using the typeConversion function passed 
    /// to the constructor if this was defined.</param>
    /// <param name="constructorParameters">Additional parameters that have to be passed to the constructor</param>
    /// <returns>An instance of the factory class</returns>
    public virtual Intf Create(Key key, params object[] constructorParameters)
    {
        Dictionary<Type, Dictionary<object, Type>> dict = Init();
        Dictionary<object, Type> factoryDict;
        if (dict.TryGetValue(this.GetType(), out factoryDict))
        {
            Type t;
            return (factoryDict.TryGetValue(typeConversion(key), out t)) ? (Intf)Activator.CreateInstance(t, constructorParameters) : null;
        }
        return null;
    }

    /// <summary>
    /// Enumerates all registered attribute keys. No transformation is done here.
    /// </summary>
    /// <returns>All keys currently known to this factory</returns>
    public virtual IEnumerable<Key> EnumerateKeys()
    {
        Dictionary<Type, Dictionary<object, Type>> dict = Init();
        Dictionary<object, Type> factoryDict;
        if (dict.TryGetValue(this.GetType(), out factoryDict))
        {
            foreach (object key in factoryDict.Keys)
            {
                yield return (Key)key;
            }
        }
    }

    private void TryHook()
    {
        AppDomain.CurrentDomain.AssemblyLoad += new AssemblyLoadEventHandler(NewAssemblyLoaded);
    }

    private Dictionary<Type, Dictionary<object, Type>> Init()
    {
        Dictionary<Type, Dictionary<object, Type>> d = dict;
        if (d == null)
        {
            lock (lockObject)
            {
                if (dict == null)
                {
                    try
                    {
                        TryHook();
                    }
                    catch (Exception) { } // Not available in this security mode. You're probably using shared hosting

                    ScanTypes();
                }
                d = dict;
            }
        }
        return d;
    }

    private void ScanTypes()
    {
        Dictionary<Type, Dictionary<object, Type>> classDict = new Dictionary<Type, Dictionary<object, Type>>();
        foreach (Assembly ass in AppDomain.CurrentDomain.GetAssemblies())
        {
            AddAssemblyTypes(classDict, ass);
        }
        dict = classDict;
    }

    private void AddAssemblyTypes(Dictionary<Type, Dictionary<object, Type>> classDict, Assembly ass)
    {
        try
        {
            foreach (Type t in ass.GetTypes())
            {
                if (t.IsClass && !t.IsAbstract &&
                    typeof(Intf).IsAssignableFrom(t))
                {
                    object[] fca = t.GetCustomAttributes(typeof(FactoryClassAttribute), false);
                    foreach (FactoryClassAttribute f in fca)
                    {
                        if (!(f.Key is Key))
                        {
                            throw new InvalidCastException(string.Format("Cannot cast key of factory object {0} to {1}", t.FullName, typeof(Key).FullName));
                        }
                        Dictionary<object, Type> keyDict;
                        if (!classDict.TryGetValue(f.FactoryType, out keyDict))
                        {
                            keyDict = new Dictionary<object, Type>();
                            classDict.Add(f.FactoryType, keyDict);
                        }
                        keyDict.Add(f.Key, t);
                    }
                }
            }
        }
        catch (ReflectionTypeLoadException) { } // An assembly we cannot process. That also means we cannot use it.
    }

    private void NewAssemblyLoaded(object sender, AssemblyLoadEventArgs args)
    {
        lock (lockObject)
        {
            // Make sure new 'create' invokes wait till we're done updating the factory
            Dictionary<Type, Dictionary<object, Type>> classDict = new Dictionary<Type, Dictionary<object, Type>>(dict);
            dict = null;
            Thread.MemoryBarrier();

            AddAssemblyTypes(classDict, args.LoadedAssembly);
            dict = classDict;
        }
    }
}

【讨论】:

    【解决方案2】:

    我不知道这里有什么约定,但我认为这在很大程度上取决于具体情况。我只在依赖注入场景中使用 AbstractFactory,我想在运行时创建类型并且必须提供抽象。

    1. “抽象”部分可以是一个接口 -> 约定是 IName
    2. 您的基类应该描述关于它的实现的“共同”事物,信息位于上下文中:如果在您的上下文中 ChocolateFactory 可能是一个“抽象”概念,那么实现(应该描述具体事物(MyChocolateFactory 不是good name)) 应该显示它们的 (is) 关系以及具体的用例。
    3. 关于您的评论:如果未来可能的用例不需要任何其他工厂实现,请不要使用抽象工厂;)

    【讨论】:

      【解决方案3】:

      你可以使用这样的东西:

      // general interface for abstract factory (this is optional)
      public abstract class AbstractFactory { };
      
      // interface that uses a type of factory and produce abstract product
      public abstract class AbstractChocolateFactory : AbstractFactory  { };
      
      // family of concrete factories that produce concrete products
      public class NestleChocolateFactory { } : AbstractChocolateFactory 
      
      public class SwissChocolateFactory { } : AbstractChocolateFactory 
      

      这只是想法,但要使用抽象工厂模式的实现完全取决于您的具体任务。

      【讨论】:

      • AbstractFactory 基类的原因是什么?那里可以有哪些成员,您可以获得什么好处?
      • 但是使用 55 种方法,你有一个来自可怕类设计的(可怕的)类接口;)
      • 有时不是你设计了你所知道的所有东西 :) 也许这个界面是由其他人设计的,你只需要使用它。我只是不想指出接口很好,但有时使用抽象类也很酷。恕我直言,这都是特定于问题的
      • 根据 GoF 工厂,您将创建一个抽象 SnickersFactory() 然后实现它,因此如果将来有人想以不同的方式创建 snickers,他们可以在不更改代码的情况下这样做(open/封闭原则),如果它是SQLConnectionFactory() 并且他们想以不同的方式实现它,则更有意义。
      • @Fabjan 这就是为什么抽象类很差,如果你声明了抽象方法并且从不实现它们只是糟糕的设计。使用界面告诉开发人员这是你的合同,要么使用它,要么远离。
      猜你喜欢
      • 2011-03-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-12-27
      • 2010-11-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多