`
ihuashao
  • 浏览: 4562346 次
  • 性别: Icon_minigender_1
  • 来自: 济南
社区版块
存档分类
最新评论

使用反射在.NET中实现动态工厂

阅读更多
Romi Kovacs
Download the code for this article: DesignPatterns0303.exe (79KB)

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.

D
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
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.
分享到:
评论

相关推荐

    .Net中利用反射通过简单工厂模式实现对不同数据库的访问

    在.Net环境中利用C#语言编写的反射Demo,通过配置文件中的DB字符串,从而修改程序中反射出的不同类型,从而实现对不同数据库访问类的实例化。利用了简单的工厂模型,可以学习一下。

    asp.net知识库

    在ASP.NET中使用WINDOWS验证方式连接SQL SERVER数据库 改进ADO.Net数据库访问方式 ASP.NET 2.0 绑定高级技巧 简单实用的DataSet更新数据库的类+总结 [ADO.NET]由数据库触发器引发的问题 为ASP.NET封装的SQL数据库...

    抽象工厂模式实现代码(利用了反射)

    抽象工厂的目的是要提供一个创建一系列相关或相互依赖对象的接口,而不需要指定它们具体的类。将抽象工厂类中的条件判断语句,用.NET中发射机制代替。抽象工厂模式实现代码(利用了反射)

    Spring.net框架

    (4)使用Spring.net实现Ioc; (5)Romoting; (6)利用Ioc在不动一行代码的情 况下实现Remoting。为了更好的理解文中的内容,最好顺序阅读。 作为一个应用系统,代码复用至关重要。如果在你的设计中,类与类存在...

    亮剑.NET深入体验与实战精要2

    读者可以在欣赏一个个有趣例子的过程中,不知不觉具备开发真正商业项目的能力。 本书集实用性、思想性、趣味性于一体,内容共分为技术基础总结、系统架构设计思想及项目实战解析三部分,随书所附光盘收录大量实例...

    史上最好传智播客就业班.net培训教程60G 不下会后悔

    再比如ASP.Net内置的AJAX解决方案UpdatePanel只在部分要求不高的内网项目中才被使用,因此我们在讲解UpdatePanel的使用和原理之外,把更多的时间放在讲解企业中用的最多的JQuery AJAX解决方案上。 6、B/S系统项目(7...

    .NET代码生成插件源码

    动软.Net代码生成器Codematic 是一款为C#数据库程序员设计的自动代码生成器,Codematic 生成的代码基于面向对象的思想和三层架构设计,结合了Petshop中经典的思想和设计模式,融入了工厂模式,反射机制等等一些思想...

    亮剑.NET深入体验与实战精要3

    读者可以在欣赏一个个有趣例子的过程中,不知不觉具备开发真正商业项目的能力。 本书集实用性、思想性、趣味性于一体,内容共分为技术基础总结、系统架构设计思想及项目实战解析三部分,随书所附光盘收录大量实例...

    抽象工厂访问不同的数据库(反射+缓存)

    NULL 博文链接:https://walleyekneel.iteye.com/blog/1113114

    .NET代码生成器 最新版

    动软.Net代码生成器Codematic 是一款为C#数据库程序员设计的自动代码生成器,Codematic 生成的代码基于面向对象的思想和三层架构设计,结合了Petshop中经典的思想和设计模式,融入了工厂模式,反射机制等等一些思想...

    .NET之美:.NET关键技术深入分析

    11.5.4使用工厂方法实现分离 11.6 Remoting中的方法回调 11.6.1远程回调方式说明 11.6.2客户端类型和服务端类型 …… 第12章 在.NET中操作XML 第13章 .NET应用程序配置 第14章 基于角色的安全性 第15章 .NET...

    反射机制与抽象工厂结合多数据库模型源码

    1、本程序为.NET的反射机制与抽象工厂结合开发的多数据库系统模型! 2、在本系统中,集合了SQL Server、Mysql、Oracle、DB2、Access数据库的不同连接方式。实现了切换数据库的最简方式! 3、本系统中附带一个新闻管理...

    动软.Net代码自动生成器2.0

    动软.Net代码自动生成器Codematic 是一款为C#数据库程序员设计的自动代码生成器,生成的代码基于基于面向对象的思想和三层架构设计,结合了Petshop中经典的思想和设计模式,融入了工厂模式,反射机制等等一些思想。...

    Net程序员的开发利器:动软.Net代码生成器

    动软.Net代码生成器 是一款为C#数据库程序员设计的自动代码生成器,Codematic 生成的代码基于面向对象的思想和三层架构设计,结合了Petshop中经典的思想和设计模式,融入了工厂模式,反射机制等等一些思想。主要实现...

    Codematic.Net代码自动生成器

    Codematic 是一款为C#数据库程序员设计的自动代码生成器,Codematic 生成的代码基于基于面向对象的思想和三层架构设计,结合了Petshop中经典的思想和设计模式,融入了工厂模式,反射机制等等一些思想。采用 Model + ...

    动软.Net代码生成器2.41

    动软.Net代码生成器Codematic 是一款为C#数据库程序员设计的自动代码生成器,Codematic 生成的代码基于面向对象的思想和三层架构设计,结合了Petshop中经典的思想和设计模式,融入了工厂模式,反射机制等等一些思想...

    动软.Net代码自动生成器

    Codematic 是一款为C#数据库程序员设计的自动代码生成器,Codematic 生成的代码基于基于面向对象的思想和三层架构设计,结合了Petshop中经典的思想和设计模式,融入了工厂模式,反射机制等等一些思想。采用 Model + ...

    .Net代码生成器Codematic 是一款为C#数据库程序员设计的自动代码生成器

    .Net代码生成器Codematic 是一款为C#数据库程序员设计的自动代码生成器,Codematic 生成的代码基于面向对象的思想和三层架构设计,结合了Petshop中经典的思想和设计模式,融入了工厂模式,反射机制等等一些思想。...

    动软.Net代码生成器2.41版

    动软.Net代码生成器 是一款为C#数据库程序员设计的自动代码生成器,Codematic 生成的代码基于面向对象的思想和三层架构设计,结合了Petshop中经典的思想和设计模式,融入了工厂模式,反射机制等等一些思想。主要实现...

    动软.Net代码生成器 v2.77 build 20121212

    动软.Net代码生成器Codematic是一款为C#数据库程序员设计的自动代码生成器,Codematic生成的代码基于面向对象的思想和三层架构设计,结合了Petshop中经典的思想和设计模式,融入了工厂模式,反射机制等等一些思想。...

Global site tag (gtag.js) - Google Analytics