使用特性(attributes)和激活机制来实现工厂模式【翻译】

Introduction

简介

The basic rule for a factory pattern is that the factory should create an instance of an object with a known ancestor, and an unknown implementation behind. Which object to be created is decided by the factory based on the criteria’s passed to the factory’s method that shall instantiate the object.

工厂模式的基本规则是工厂必须使用一个已知的祖先(类)来创建一个对象的实例,而后是(对这个对象)未知的实现。创建哪个对象由工厂自行判断,其判断准则是调用方所调用的用于对象实例化的方法。

I would also like to share some rules that I usually follow when I am writing a factory.

  1. If you need the factory to be able to create more classes than it was originally designed for, you should not have to modify the factory code.
  2. The factory should be equally unknowing as the caller about the implementation behind the classes that it can instantiate.
  3. The factory should provide a fixed mechanism how to decide which class to instantiate. The class itself should provide some information to the factory, which the factory should base it’s decision on.

我将跟大家分享一下我在创建工厂时所遵循的一些规则。

  1. 如果你希望工厂比它原先设计时能创建出更多的类,你就不应该(随意)修改(或改变)工厂的代码。
  2. 工厂应该跟调用方一样对其背后用于实例化对象的实现一无所知。
  3. 工厂必须提供一种固定的机制来决定哪个类被实例化。类自身应该向工厂提供一些信息,以便工厂能够根据这些信息来作出判断。

One way solve the issues presented in rules 1 and 2 is to let the factory have an internal list of types it could instantiate, and to provide a register mechanism so that it’s possible to tell the factory which classes it can instantiate.

一种解决规则1和规则2所阐述的问题的方法就是让工厂拥有一个内置的类型列表,该列表列举了它能实例化的类型。另外,你还要提供一种注册的机制,使得告诉工厂哪些类可以被实例化,成为可能。

To solve the third rule it would require the classes which the factory can instantiate to override a static abstract method from the ancestor class, since you need information from the class before it’s instantiated and you need to override it’s behaviour in descendent classes. This is not possible in .NET since there is no meaning by having an static abstract method in .NET, therefore we must solve this with a different approach: Attributes.

为了解决第三个规则的问题,就需要这样的类,工厂可以实例化这些类来覆盖一个祖先类的静态抽象方法 ,因为你需要在类实例化之前从类里得到信息,然后在它的派生类里覆盖他的行为。这在.NET里是不可能的,因为在.NET里并没有一个静态抽象方法的概念,于是我们就需要用另外一种途径来解决这个问题:特性。

In this article I will show you how to implement a factory, which uses attributes to decide which class to instantiate. To fully understand this article you need to have some basic knowledge about attributes.

在这篇文章里我会为你演示怎么实现一个工厂,这个工厂使用特性来决定哪个类将被实例化。为了完全理解这篇文章,你需要关于特性的一些基本知识。

Background

背景

Why do I think these three rules are so important?

为什么我认为这三个规则这么重要呢?

Let's say the factory is part of a plug in architecture and could not be modified because it’s compiled into a host application or a separate DLL. A third party should easily be able to write a plug in without modifying the factory code. Even if the current situation is not requiring this at the moment. If you use these three rules when developing your system this could easily be accomplished if the requirements changes later on

我们可以认为工厂是插件构架中的一部分,它是不能被修改的,因为它被编译进入宿主程序或者一个单独的DLL。第三方可以轻松地写一个插件而不需要修改工厂代码。即使目前的情况还不需要。如果你使用这三个规则开发你的系统,如果日后需求改变,也能轻松完成。

I also strongly believe that a class should be an expert on itself. Since a class obviously knows about it’s own implementation, why shouldn’t the class be a part of the decision making process that decides when to use it? If the class provide the factory with information to base it’s decision on, we could state that the class is an expert on itself since it knows what it does and when to be used.

我也非常相信类对于自己而言是一个专家。既然类是显然清楚自己的实现的,为何不让类加入决策过程进而决定何时使用自己呢?如果类向工厂提供一些能让工厂作出决定的信息,我们就可以认为这个类对它自己而言是一个专家了。

About the code

关于代码

To illustrate how to build such a factory, I have decided to use the following case:

为了举例说明怎么构造这样一个工厂,我决定使用以下的情形:

The mission is to create an ASP.NET page which says: "Hello World" when a client is requesting the page. The problem is that the page should be available to various types of clients, like HTML browsers, XML Spy and other XML based tools, mobile phones and other WML browsers. The page should also be able to be requested manually by a GET from a telnet client.

任务是创建一个ASP.NET页面,在客户端请求这个页面的时候说:“Hello World”。问题是这个页面必须对各种各样的客户端可用,像HTML浏览器,XML Spy和其他基于XML的工具,移动电话和其他WML浏览器。从一个telnet客户端手动地发出一个Get也能请求这个页面。

Depending on the User Agent, the page should be generated differently, either in HTML, XML, WML or in plain text for the telnet client.

根据用户代理的不同,这个页面应该以不同的方式产生,要么用HTML,XML,WML或者为一个telnet客户端使用纯文本。

To simplify the demonstration, the application will actually be an ordinary windows application with an input field where to enter the "User Agent", and a textbox to display the output.

为了简化这个示范,这个应用程序实际上将是一个普通的Windows应用程序,带有一个输入标签来输入“用户代理”和一个文本框来显示输出。

The basic idea how to solve this task is to have an abstract class

解决这个问题的基本想法是有一个抽象类

public   abstract   class  ResponseFormatter 

    
public abstract string FormatResponse(string Text); 
}
 

 

which is inherited by several classes and implemented in different ways. For example one class is implemented to format the text using HTML and another class is formatting the text using WML etc.  These classes are the classes that the factory is responsible for instantiating.

它能够被一些类继承,以不同的方式实现。比如说一个类被实现使用HTML来格式化文本,另一个类则使用WML来格式化文本,等等。这些类就是那些让工厂负责实例化的类。

The factory class looks like this:

工厂类是这样的:

 

public   sealed   class  ResponseFormatterFactory
{
    
private ArrayList RegisteredImplementations;
    
public ResponseFormatterFactory()
    
{
        RegisteredImplementations 
= new ArrayList();
    }

    
    
public void RegisterResponseFormatter(Type ResponseFormatterImpl)
    
{
        
if (!ResponseFormatterImpl.IsSubclassOf(typeof(ResponseFormatter)))
            
throw new ResponseFactoryError("Response Formatter 
                must inherit from class ResponseFormatter");

        RegisteredImplementations.Add(ResponseFormatterImpl);
    }


    
public ResponseFormatter CreateResponseFormatter(string UserAgent)
    
{
        
    }

}


 

To make the factory aware of the classes it can instantiate, it has an ArrayList that containins the System.Type for each ResponseFormatter implementation registered. You must therefor register all implementations with the factory using the method RegisterResponseFormatter in order to make it aware of them.  The method RegisterResponseFormatter checks that the ResponseFormatter parameter actually is a type subclassed from the abstract class ResponseFormatter.

为让工厂知道它能实例化的类,它有一个ArrayList来为每一个注册的ResponseFormatter实现保存System.Type。于是你必须使用RegisterResponseFormatter方法注册所有使用工厂的实现以使工厂知道它们(保存的类型)。RegisterResponseFormatter 方法检查ResponseFormatter参数是否真是一个由抽象类 ResponseFormatter继承来的子类。

Each class that the factory should manage should be "tagged" with one or more UserAgentAttribute which looks like this:

每一个工厂需要管理的类必须使用一个或者更多的UserAgentAttribute来“标签”,就像这样:

[AttributeUsage(AttributeTargets.Class, AllowMultiple = true )]
public   class  UserAgentAttribute : Attribute
{
    
private string UserAgentText;
    
private bool ExactMatch;

    
public UserAgentAttribute(string UserAgentText, bool ExactMatch)
    
{
        
this.UserAgentText = UserAgentText;
        
this.ExactMatch = ExactMatch;
    }


    
public virtual bool MatchesUserAgent(string UserAgent)
    
{
        
if (!ExactMatch)
        
{
            
return (UserAgent.ToLower().IndexOf(UserAgentText.ToLower()) != -1);
        }

        
else
            
return UserAgent.ToLower().Equals(UserAgentText.ToLower());
    }

}

 

The AttributeUsage attribute says that this attribute is applicable to classes only, and it's okay to use several of these attributes on the same class.

AttributeUsage特性说明这个特性只适用于类,在同一个类上多次使用本特性是没问题的。

When applying this attribute to a class you must supply two parameters:  UserAgentText and ExactMatch.  The first parameter is the string that is tested with the useragent  you supply when you invoke the factory's CreateResponseFormatter method.  The next parameter defines how the match should be performed: exact match (true) or partial match (false).

当给类使用这个特性的时候你必须提供两个参数:UserAgentText ExactMatch。第一个参数是一格字符串,当你调用工厂的CreateResponseFormatter 方法时,根据你提供的用户代理来被测试。第二个参数定义了将执行怎样的匹配:完全匹配(true)还是部分匹配(false)

The method MatchesUserAgent performes the actual test and based on those two parameters it decides whether this class could serve the the specified UserAgent.

MatchesUserAgent 方法执行实际测试,根据两个参数,它决定是否这个类能够适合指定的UserAgent

Let's have a look at a class that implements the abstract ResponseFormatter

让我们来看看一个实现ResponseFormatter抽象类的类。

 

[UserAgent( " Mozilla " false )]
public   class  HTMLResponseFormatter : ResponseFormatter
{
    
public HTMLResponseFormatter() {}
    
public override string FormatResponse(string Text)
    
{
        System.Text.StringBuilder Response 
= new System.Text.StringBuilder();

        Response.Append(
"<html></body>");
        Response.Append(Text);
        Response.Append(
"</body></html>");

        
return Response.ToString();
    }

}
        

 

First of all we have the attribute which says that this class should be used when the useragent contains (partial matching) the string "Mozilla".

首先我们有一个特性说明这个类在这个时候应该被使用:用户代理包含(部分匹配)字符串"Mozilla"

The method FormatResponse is overidden to return the string in the variable Text formatted as HTML.

FormatResponse 方法被覆盖来返回使用HTML格式化后的Text变量中的字符串。

Now let's check inside the method CreateResponseFormatter in the ResponseFormatterFactory class:

现在然我们来检查一下在ResponseFormatterFactory类里的CreateResponseFormatter 方法的内部:


public  ResponseFormatter CreateResponseFormatter( string  UserAgent)
{
    
// loop thru all registered implementations
    foreach (Type impl in RegisteredImplementations)
    
{
        
// get attributes for this type
        object[] attrlist = impl.GetCustomAttributes(true);

        
// loop thru all attributes for this class
        foreach (object attr in attrlist)
        
{
    
            
if (attr is UserAgentAttribute)
            
{
            
// okay, we found an useragent attribute
            
// lets check if it matches our useragent
                if (((UserAgentAttribute) attr).MatchesUserAgent(UserAgent))
                
{
                    
// okay, this ResponseFormatter could be 
                    
// used with the specified UserAgent
                    
// created instance and return
                    return (ResponseFormatter) 
                               System.Activator.CreateInstance(impl);
                }

            }

        }

    }


    
// if we got this far, no ResponseFormatter implementation 
    
// could be used with this UserAgent
    throw new ResponseFactoryError("Could not find a ResponseFormatter 
                                    implementation for this UserAgent");
}


 

This method consists of two nested loops: the first loop iterate through all the registered types.  The second loop iterates through all attributes for the current type.

这个方法由两个嵌套的循环组成:第一个循环遍历所有注册的类型。第二个循环遍历当前类型的所有特性。


If the attribute is a
UserAgentAttribute it calls the attributes MatchesUserAgent method to find out if this class could serve the specified UserAgent.  If the MatchesUserAgent returned true we simply instantiate the class behind the type using activation and returns it.  If the loop's have finished without finding a implementation to use, an exception is raised.

如果特性是一个UserAgentAttribute 就调用特性的MatchesUserAgent方法来找出是否这个了能够匹配指定的UserAgent。如果MatchesUserAgent返回true 我们就使用激活器简单地实例化这个类型背后的类,然后返回它。如果这个循环结束并没有找到一个可以使用的实现,就抛出一个异常。

Well let's put everything together and have a look where the factory is used.  First we'll create an instance of the factory and register our implementation with it.

好了,让我们把所有东西放在一起来看看工厂在哪里被使用了。首先我们创建了一个工厂的实例,并使用它注册我们的实现。

//  create factory 
factory  =   new  ResponseFormatterFactory();

//  register implementations
factory.RegisterResponseFormatter( typeof (HTMLResponseFormatter));
factory.RegisterResponseFormatter(
typeof (XMLResponseFormatter));
factory.RegisterResponseFormatter(
typeof (TextResponseFormatter));
factory.RegisterResponseFormatter(
typeof (WMLResponseFormatter));     

 

Next up is to finally use the factory:

接下来的,也是最后一步了,就是使用工厂了:

private   void  btnHello_Click( object  sender, System.EventArgs e)
{
    
try
    
{
        tbResponse.Text 
= 
          factory.CreateResponseFormatter(cobxUserAgent.Text).FormatResponse(
"Hello World!!");
    }

    
catch (ResponseFactoryError error)
    
{
        MessageBox.Show(error.Message, 
"Error"); 
    }

}
    
       
 

And that's it!

这就是它了!

Points of Interest

有趣的地方

Since I am an old Delphi coder I am used to the fact that it's possible to utilize static abstract methods, or as it's called in Delphi : an abstract class function.  When I found out that this wasn't possible in .NET i was very depressed for a couple of days, until I stumbled upon attributes! Attributes is a very powerful tool for the programmer and could in a way mimic the behaviour of abstract static methods since you can derive attributes as well.

因为我是一个长久使用Delphi的程序员,我习惯于有可能使用静态抽象方法(static abstract methods),在Delphi里它是这么叫的:一个抽象类函数(an abstract class function)。当我发现在.NET里是不可能的时候,我失落了好几天,直到我发现特性!特性对程序员来说是一个强有力的工具,可以使用一种方法模拟抽象静态方法的行为,因为你也可以继承特性。

原文链接:http://www.codeproject.com/csharp/factorywithattributes.asp
原文作者:John Gunnarsson

你可能感兴趣的:(attribute)