As software development has grown over the years, patterns in the way developers write code began to emerge. Often there was a better way to approach many specific problems, and these approaches caught on. Many of them have been formalized into design patterns that can be used to address very specific coding challenges. This new column will present a variety of useful patterns applicable to a wide range of projects that will help you find easy solutions to your development problems.
|
esign patterns are a powerful tool for designing flexible soft-ware. They provide well-tested solutions to recurring software design problems. One such widely used pattern is the Concrete Factory. The Factory pattern decouples objects from knowledge about the creation of other objects or even the underlying type of those objects. Too often, however, the Factory pattern is not applied optimally, creating subtle coupling and low cohesion issues between various participants of the pattern. In this column, I'll use reflection in .NET to create factory classes for C#, removing dependencies common in some implementations of this pattern. Two of the sacred tenets of sound software design are keeping elements loosely coupled, yet highly cohesive. Coupling determines how strongly or closely related software components are, while cohesion relates to how well focused a software unit is, whether it's a method, class, or class library.
The Concrete Factory Design Pattern The Concrete Factory is one of the most widely used patterns. In order for an object A to send a message to object B, A must have a reference to B, which means that class B must be instantiated and a reference to object B must be available to A. If A instantiates B directly, A has a direct reference to B, but now they are tightly coupled, since A must know how to create B. In order to reduce tight coupling between these two objects, it is best to give the responsibility of creating B to a factory class, C, so that in addition to reducing the strength of coupling between A and B, other potential consumers of B can request an instance of B from C as well. This is what the Concrete Factory design pattern provides. Note that coupling is not eliminated between objects A and B; rather, it is weakened because A no longer needs to know how to create B. A further weakening of this coupling would be achieved if factory class C returned an interface to object B (let's call it I), rather than the concrete object itself. This would ensure that no matter how many implementations of I exist, object A would only know of I. The reason an interface renders coupling even weaker is that object A is further abstracted from new implementations of I. In the Factory pattern there is a client, a factory, and a product. The client is any object that needs an object from the factory. The product is the object returned to the client by the factory.
Common Implementations There are quite a few variations of the Factory pattern. I'll look at the pros and cons of two of the most common approaches, then I'll introduce a new approach that uses reflection. The first implementation I'll examine uses abstract and concrete factories. This implementation should not be confused with the Abstract Factory pattern, which handles the creation and return of families of objects. Suppose you have a computer parts store inventory application in which InventoryMgr is the Client class, PartsFactory, MonitorInvFactory, and KeyboardInvFactory make up the factory, and IPartsInventory is the product returned by the factory. The Unified Modeling Language (UML) diagram in Figure 1 illustrates this implementation. Here, MainClass is a class that sends a request to InventoryMgr.
Figure 1 Abstract and Concrete Factories
In Figure 1, MainClass simply represents an object that passes on the user's request to InventoryMgr. If MainClass needed to replenish the inventory of monitors, for example, it would send a message to the InventoryMgr (see Figure 2). Take a look at the code in the abstract factory PartsFactory and the concrete factories MonitorInvFactory and KeyboardInvFactory in Figure 3. Although this is a reasonable approach to implementing the Factory pattern, it has some shortcomings. Consider how coupling manifests itself and to what degree it is present in this pattern. The InventoryMgr class looks good (coupling is low) since it's shielded from knowing about any of the concrete factories. The addition of new concrete factories in the future will not affect it. InventoryMgr is also decoupled from the different implementations of IPartsInventory by having a reference to the interface instead of a concrete object. So, what is the problem? One of the axioms in object-oriented design is "don't talk to strangers," meaning that objects should not have a reference to objects they do not absolutely need in order to function properly. If you look at the code fragment for MainClass in Figure 2, this rule is violated because MainClass creates concrete factories. MainClass does not truly need to know about concrete factories since it does not send a message to them. It simply creates them and passes them on to InventoryMgr. This results in an unnecessary coupling between MainClass, MonitorInvFactory, and KeyboardInvFactory. As a general rule, objects should only create other objects if they plan to send a message or messages to those objects, unless their main responsibility is the creation and return of those objects. Factories, for instance, are such classes. An adverse effect of unnecessary coupling is lower cohesion. MainClass has lowered its cohesion as well since, given its role as a message forwarding agent, it should not concern itself with what concrete factories are needed to process the request. Its focus should simply be taking a request, possibly repackaging it, and sending it to InventoryMgr for further processing. As you'll see later on, reflection helps remove unnecessary coupling and improve cohesion in all classes that participate in the Factory pattern. Let's examine another popular implementation of the Factory pattern. This approach does not make use of abstract factories. It only uses a concrete factory to create objects; therefore it is considerably less complex. Let's take a look at what the code looks like in MainClass, InventoryMgr, and PartsFactory. IPartsInventory has not changed (see Figure 2). MonitorInvFactory and KeyboardInvFactory (see Figure 3) are no longer needed, since a single concrete factory class in now being used. The most striking change is the addition of an enumerator, enmInvParts. Figure 4 shows that the code in MainClass is significantly cleaner, less coupled, and more cohesive than the previous implementation in Figure 2. MainClass is no longer "talking to strangers." Instead of being coupled to factories, it is now coupled to the enumeration enmInvParts, which simply provides a repackaging mechanism of the command-line request. Repackaging the request comes naturally to MainClass since it fits well with its responsibility of simply passing on the request to InventoryMgr. Although InventoryMgr is coupled to PartsFactory, coupling has not truly changed, since InventoryMgr was previously coupled to the abstract version of PartsFactory by means of parameter passing (see Figure 2). At first glance, it looks like the coupling and cohesion issues have disappeared, so I can pop the champagne cork and declare victory. Well, not yet. If you take a closer look, you see that the coupling has moved from MainClass to PartsFactory. Although PartsFactory is now coupled to IPartsInventory and its implementor objects, coupling does not affect it nearly as adversely as MainClass was affected in the implementation of the Factory pattern in Figure 2 because class cohesion has not deteriorated. In other words, PartsFactory remains focused. If you could remove this hardcoded coupling from the factory, you would achieve the lowest possible coupling and highest cohesion in all the classes that make up the implementation of the Factory pattern. Stay tuned; next I'll describe how reflection can get you pretty close to that lofty goal.
A Dynamic Factory Using Reflection Reflection is simply a mechanism that allows components or classes to interrogate each other at run time and discover information that goes beyond the information gleaned from the publicly available interfaces the objects expose. In essence, reflection enables objects to provide information about themselves (metadata). The .NET Framework provides objects the ability to describe themselves through the use of attributes. An attribute is declared as a class that inherits from System.Attribute. Once an attribute is defined, it can be attached to types such as interfaces, classes, or an assembly. The code in Figure 5 defines two attributes. The first attribute is InventoryPartAttribute, which will be attached to the classes that implement the IPartsInventory interface in order to specify which type of parts they handle. The following code illustrates how this is achieved: [InventoryPartAttribute(enmInvParts.Monitors)]
class MonitorInventory : IPartsInventory {
public void Restock() {
Console.WriteLine("monitor inventory restocked");
}
}
[InventoryPartAttribute(enmInvParts.Keyboards)]
class KeyboardInventory : IPartsInventory {
public void Restock() {
Console.WriteLine("keyboard inventory restocked");
}
}
The second attribute defined in Figure 5 is attached to the IPartsInventory interface. This allows for the interface to be interrogated about which types implement it, like so: [ImplAttr(new Type[]{typeof(MonitorInventory),typeof(KeyboardInventory)})]
interface IPartsInventory() {
public void Restock();
}
So far, I have created two attributes and attached them to both the IPartsInventory interface and the classes that implement it (MonitorInventory and KeyboardInventory). The class with the largest amount of code changes is PartsFactory. This should certainly be no surprise, since it is the class whose hardcoded switch statement I'm trying to replace with code that's more dynamic through the use of attributes. Let's examine each line of code of the newly modified PartsFactory class in Figure 6. The first line retrieves the ImplAttr attribute of the IPartsInventory as shown here: Attr = Attribute.GetCustomAttribute(typeof(IPartsInventory),
typeof(ImplAttr));
Next, I cast the Attr attribute to my custom ImplAttr attribute and the array of types that implement IPartsInventory is retrieved by reading the attribute's ImplementorList property: IntrfaceImpl = ((ImplAttr)Attr).ImplementorList;
I determine the number of classes that implement the interface by obtaining the length of the IntrfaceImpl array: ImplementorCount = IntrfaceImpl.GetLength(0);
Next, I loop through the IntrfaceImpl array. The first thing I do within the loop is retrieve the InventoryPartAttribute attribute of each interface implementor class: Attr = Attribute.GetCustomAttribute(IntrfaceImpl[i],
typeof(InventoryPartAttribute));
Then, I cast the Attr attribute to my custom InventoryPartAttribute attribute and extract its value into the enmInventoryPart variable: InvPartAttr = (InventoryPartAttribute)Attr;
enmInventoryPart = InvPartAttr.InventoryPartSupported;
If the value of the extracted enumerator matches the client's enumerator, the class supports the right type of inventory part, so I instantiate it and break out of the loop: if((int) enmInventoryPart == (int)vInvPart) {
Obj = Activator.CreateInstance(IntrfaceImpl[i])
InvPart = (IPartsInventory)Obj;
break;
}
Finally, the factory returns the IPartsInventory object: return InvPart;
Let's look at what happens when the need arises to add another object that derives from the IPartsInventory interface. I'll name this object MousePadInventory. After updating the enmInvParts enumerator to accommodate the new inventory part, I define the new class and attach the InventoryPartAttribute attribute to it: public enum enmInvParts : int {Monitors = 1, Keyboards, MousePads};
[InventoryPartAttribute(enmInvParts.MousePads)]
class MousePadInventory : IPartsInventory {
public void Restock() {
Console.WriteLine("The mouse pad inventory has been restocked");
}
}
Next, the ImplAttr attribute of the IPartsInventory interface needs to reflect that a new class is implementing the interface: [ImplAttr(new Type[]{typeof(MonitorInventory),typeof(KeyboardInventory),
typeof(MousePadInventory)})]
interface IPartsInventory() {
public void Restock();
}
Then, I update MainClass to handle the request to replenish the newly added mouse pad inventory. The only change is to add another case for "MousePads" to the MainClass class shown in Figure 4. Add the following code right after the "Keyboards" case: case "MousePads":
InvMgr.ReplenishInventory(enmInvParts.Keyboards);
break;
Now I'm finished. There is no need to change PartsFactory or InventoryMgr. This shows that I have successfully removed all remaining coupling issues caused by the switch statement inside PartsFactory and that adding a new class requires minimal effort.
Conclusion The Concrete Factory pattern is one of the simplest yet most powerful design patterns. Because of its simplicity, the subtle coupling and cohesion implications that accompany the implementation you choose when applying the pattern are sometimes overlooked. Indeed, it is not always possible or necessary to severely reduce coupling or completely maximize cohesion; however, you need to be mindful of their presence and their implications in whatever context you happen to apply this design pattern.
|
相关推荐
4. **设计模式实现**:在实现工厂模式、策略模式等设计模式时,反射常被用来动态选择和创建具体实现。 #### 使用反射的注意事项 虽然反射提供了巨大的灵活性,但使用时也需谨慎: 1. **性能影响**:反射操作通常...
本文将深入探讨如何在C#中利用反射和简单工厂模式来实现对不同数据库的动态访问。 首先,我们需要理解反射的概念。反射允许程序在运行时获取关于自身类型的信息,并能创建和操作这些类型的实例。在C#中,我们可以...
例如,我们可以使用抽象工厂来创建SQL Server或Oracle数据库的连接,或者在测试环境中使用内存数据库。 综上所述,"ASP.NET 抽象工厂分层框架"是一个结合了抽象工厂模式和分层架构的设计,旨在提供一个可扩展、易于...
4. **动态加载组件**:在WinForm中,反射还可以用来动态加载和使用用户界面组件,特别是当组件数量众多或组件类型需要根据用户需求改变时。 通过这样的方式,我们可以构建一个灵活的WinForm应用程序,它可以根据...
1. **工厂模式**:在.NET中实现工厂模式时经常会用到反射技术。 2. **性能影响**:使用反射可能会导致性能上的损失,因为它涉及到更多的运行时开销。 3. **元数据限制**:有一些元数据信息是无法通过反射获取的。 4....
总结一下,ASP.NET中的工厂模式结合反射,可以实现动态对象创建,提高代码的灵活性和可扩展性。对于入门级开发者来说,理解并熟练运用这两种技术是提升开发能力的关键步骤。通过工厂模式,可以更好地管理对象的生命...
在.NET框架中,我们可以利用反射机制来进一步增强抽象工厂模式的灵活性。 反射是.NET框架中的一种强大功能,它允许运行时动态地获取类型信息以及创建和操作对象。通过反射,我们可以获取类型、方法、属性等元数据,...
7. **动态类型创建和方法调用**:反射允许在运行时创建未知类型的实例,调用其方法,甚至修改字段值,这在某些动态编程场景中非常有用。 8. **设计模式揭示**:通过查看代码的实现,开发者可以识别出设计模式,如...
.NET反射是.NET Framework提供的一种强大的元数据访问机制,它允许运行中的代码动态地获取类型信息并使用这些类型。在C#编程中,反射是通过System.Reflection命名空间中的类来实现的,它允许程序在运行时检查自身的...
1. 反射类型使用:反射是.NET Framework中的一个重要特性,它允许程序在运行时动态地获取类型信息并操作类型。通过`Type`类,我们可以获取任何对象的类型信息,创建实例,调用方法,访问字段和属性,以及检查接口和...
在实现中,根据传入的产品名,使用反射动态创建对应的产品实例。 ```csharp public interface IProductFactory { IProduct CreateProduct(string productName); } public class ReflectionProductFactory : ...
《北大青鸟ACCP5.0 S2 .NET C#三层架构详解》 ...通过学习和实践这些实例,开发者可以更深入地理解三层架构的原理和实现方式,掌握如何在C#项目中有效地应用简单工厂、接口和反射,提高软件的可维护性和可扩展性。
在这个特定的场景中,我们看到“C#抽象工厂实现的简单三层”指的是在一个三层架构(表现层、业务逻辑层、数据访问层)的应用中,通过抽象工厂模式来实现各层之间的解耦。 1. **三层架构**: - **表现层...
标题中的“抽象工厂访问不同的数据库(反射+缓存)”是指在软件设计模式中使用抽象工厂模式来创建和管理数据库连接。抽象工厂模式是一种创建型设计模式,它提供了一种创建对象族的方法,而无需指定具体类。在这个...
在.NET中,反射和委托经常与多种设计模式结合使用。例如: 1. 工厂模式:反射可以用于在运行时根据配置或输入动态创建对象的工厂。 2. 单例模式:反射可以用来在不直接引用类名的情况下获取单例实例。 3. 动态代理...
.NET框架中的反射、委托技术和设计模式是开发过程中不可或缺的工具,尤其在动态操作类型、创建对象和实现灵活的代码设计方面。本文将深入探讨这些概念,以帮助开发者更好地理解和运用。 首先,反射(Reflection)是...
2. 反射:.NET反射工厂模式利用反射机制动态创建对象,提供了在运行时获取类型信息和实例化对象的能力,常用于框架或插件设计,提高了代码的灵活性。 3. 简单工厂模式:用于创建对象的工厂方法,将对象的创建过程...
反射在.NET中主要通过System.Reflection命名空间的类实现,如Type、MethodInfo、ConstructorInfo等。它可以动态地获取类的信息,如类名、属性、方法、构造函数等,并在运行时创建对象、调用方法。反射的应用场景包括...
例如,在ASP.NET开发中,可以使用简单工厂来创建不同类型的数据库连接,如SQL Server、Oracle或MySQL,使得应用程序能够根据配置动态选择合适的数据库连接。 其次,序列化是将对象的状态转换为字节流的过程,以便...
在.NET编程环境中,C#是一种常用的面向对象的语言,它提供了丰富的特性和工具来帮助开发者创建高效、可维护的代码。本篇文章将详细讲解C#中的“反射”与“抽象工厂”设计模式,以及它们如何结合使用。 “反射”是C#...