问题
This pattern uses an abstract factory, and then an implementation of the factory.
I am sure there is a standard naming convention for these two classes, but I don't know what it is.
For example:
public abstract class ChocolateFactory { };
public class MyChocolateFactory { } : ChocolateFactory
What's the standard convention here?
I'm thinking either ChocolateFactoryBase, or ConcreteChocolateFactory, but perhaps there is something else (much like Enums tend to be suffixed with Enum, e.g. PetTypeEnum
so that you can do PetTypeEnum PetType;
Hopefully this isn't subjective.
回答1:
The question and the answer
OK, this question started with a naming question of Abstract factories. As a rule of thumb, always use the 'formal' name what you're doing (e.g. Factory, Decorator, etc) and a description of the concrete implementation (e.g. Snickers, Mars, MotifWidget, etc, etc).
So basically, you create a MSSQLConnection
which is the concrete thing you're describing, and Factory
which means it follows the characteristics of the factory pattern.
OK, so far for naming and the original question. Now for the cool stuff. The discussion goes to the best way to implement abstract factories in C#, which is a different topic. I did quite some work on implementing all design patterns in C#, for which I'll share some details on Factories here. Here goes:
Abstract factories and factories
Abstract factories are basically a combination of a base class or interface, and a concrete implementation. A base class is what you want if you share a lot of code, an interface if you don't.
I usually make a distinction between 'factories' and 'abstract factories'. A factory is a thing that creates objects (of a certain type), an 'abstract factory' is a thing that creates objects of arbitrary types. As such, it follows that an implementation of an abstract factory is a factory. This is relevant for the next piece of info.
Factory pattern
Languages that support RTTI are able to implement the factory pattern. A factory pattern is a thing that creates objects. The most trivial implementation is a class that only contains methods that create objects, e.g.:
// ...
public void CreateConnection()
{
return new SqlConnection();
}
// ...
You usually use this to abstract things away. For example, the thing in a HTML parser that generates XML nodes creates a node of a certain type based on the HTML tag.
Factories often make decisions based on runtime information. It is therefore possible to generalize the Factory pattern to implement something like:
public T Create(string name)
{
// lookup constructor, invoke.
}
It's pretty easy to create a generalized factory pattern using RTTI that stores a Type
for each name. Lookup the name, create the object using reflection. Done.
Oh and as a bonus you have to write way less code than making all the factories by hand. Because all implementations are the same, you might as well just put it in a base class and fill the Dictionary in a static constructor.
Generalizing abstract factories
Abstract factories are basically collections of factories that create objects the same way as a Factory pattern. The only thing that's shared is the interface (e.g. Create or you can use inheritance to create an abstraction).
The implementation is quite trivial, so I'll just leave it at that.
Decoupling factories and types
Let's go back to the GoF example. They talk about a MotifFactory
and a PMFactory
. In the future we will encounter yet another UI thingie and we'll need an ASPNETFactory
or a SilverlightFactory
. However, the future is unknown and we'd rather not ship our old DLL's if we don't need to - after all, that's not flexible.
A second problem arrises if we want to add a new method to our factory. It follows that doing this will involve changing all factories. As you might have guessed, I don't want to change this in multiple places.
Fortunately we can solve both these issues. The interface is the same (and it can even be generalized) so we can simply add new features to our factory at runtime.
Instead of telling the factory what objects to create, we can use an attribute to tell a class that it should be materialized by a certain factory. We can also scan all types during the load of an assembly, so if an assembly is loaded, we can simply build new factories on-the-fly.
What I sacrifice for this is compile-time checks, but because the Factory pattern usually uses runtime information, that's not necessarily a problem.
Wrapping it all up, here's the code of my Factory:
/// <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<string, IControl>
/// {
/// 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:
I don't know about any convention here, but I think it highly depends on the situation. I am using AbstractFactory only in Dependency injection secenarios where I want to create types at runtime and I have to provide an abstraction.
- The "abstract" part could be an interface -> Convention would be IName
- Your base class should describe a "common" thing about it's implementations, the Information lies in the context: If in your context a ChocolateFactory could be an "abstract" concept the implementations (that should describe concrete things (MyChocolateFactory is not a good name)) should show their (is) relationship but also the concrete usecase.
- Regarding to your comment: Do not use the abstract factory if there is no need for any other factory implementations just for possible future usecases ;)
回答3:
You can use something like this :
// 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
It's just and idea but what implementation of Abstract Factory pattern to use depends entirely on the specific task you have.
来源:https://stackoverflow.com/questions/31263041/naming-convention-for-gof-factory