(zhuan)Castle项目简介--第一部分(译)
Introducing Castle - Part I(原文)
Introduction
The construction of a well-designed system is challenging, whatever its size, and the main challenges could be summarized as follows:
- Objects' responsibilities
- Extensibility
- Reutilization
By objects' responsibilities I mean the ability of designing classes that have a clear, distinct responsibility. In other words, a class should have only one concern and the concern should be very explicit through its name and operations. It might sound trivial but any developer knows it's not. Unfortunately, this only comes with experience, and there is no shortcut.
不論項目大小,設計一個優秀的系統構架都是一個挑戰。概括的來說,主要包括以下幾方的挑戰:
- Objects' responsibilities
- 可擴展性
- 可重用性
在Objects' responsibilities方面我認為設計的類都必須有清晰可區分的功能。換句話說:一個類僅僅只能一個關注點并且這個關注點可以通個它的名字與操作(方法)清楚的表現。這看起很簡單,但每一個有開發經驗的人都知道不是這樣的。而且不幸的是,要做到這一點都需要經驗而沒有其它任何捷徑。
Extensibility, however, is a polemic topic as XP enthusiasts, like myself, advocates that the extensibility exposed by an application should match the requirements brought by the client. Nothing more, nothing less.
Reuse is tricky. The more your classes depend on each other, the bigger is your headache to reuse them in different applications or different contexts. Usually, we talk in levels of linkage: is your system tight coupled or loosely coupled?
可擴展性。這個主題對于熱衷于采用XP方式的開發者來說有點異議,比如我,他們提倡只需要達到客戶需求的可擴展性即可,不要多也不要少。
可重性性。類之間的信賴程度越高,那么在不同的應用或環境中重用這些類將越困難。通常也就行語說的:系統是高耦合還低耦合的!(不好譯,請高手指點怎樣譯更好)
This is the first part of three articles intended to show how inversion of control might result in less code, simpler design and testable software. In this part, I'll mainly focus on what is a decoupled design and how an inversion of control container might help you. I'll also describe Castle's Windsor Container, how it works, and how to augment it.
為了向你展示采用IOC你將能用更少的代碼,更簡單的設計得到有可測試的軟件我寫下這三篇文章,這里是第一部分:我將主要介紹什么是a decoupled design 和IOC容器。我也會介紹Castle's Windsor Container,以及怎樣使用它。
Inversion of Control
What's inversion of control, anyway? Inversion of control is a simple principle that dictates that an external entity should send messages to programmer's objects in order to perform some specialized action or to allow the programmer to override some logic. This is the main difference between an API and a framework. On the first case, your object uses the API in an active fashion; in the second case, the framework uses your objects, so your objects are now in a passive mode.
Putting off all the hype about Inversion of Control and the wrong meaning some legends of software development try to impose to it, inversion of control is a nice design principle to construct a loosely coupled application. It requires that you shift your paradigm, though. So, before you embrace the principle, do some - or several - experimentations. It might be good to browse the code of some projects that use inversion of control too, like Cocoon, James and Turbine.
什么是IOC?簡單的來說就是要求一個外部實體要完成某個方法調用時應是發送消息給其它對象來完成或允許開發者重寫處理邏輯。這與API或框架有重要的區別:首先,對象調用API是主動,第二,框架使用你的對象時你的對象則被動的。
脫去IOC廣告外衣與一些理想軟件開發者強加于它的錯誤概念,IOC應該是構架松耦合應用的絕佳設計。但是它要求改變你的慣例。所以,要你接受這個原則之前,需要動動手來體驗它。看看采用IOC的項目如Cocoon,James和Turbine就是一個好的注意。
So, one of the uses of inversion of control that solves the loosely coupled problem (or one aspect of it) is by inverting how your class obtains external object references or configuration. Let's work on a not-so-distant-from-the-real-world example:+
所以,改變對象引用或配置方式可以解決或部分解決這個問題。不扯遠了,我們就從一個真實的示例開始:
using System;
using System.Configuration;
public class MyAwfulEmailClass
{
public MyAwfulEmailClass()
? {
? }
public void SendEmail( String from, String to, String message, String templateName )
? {
??? String host = ConfigurationSettings.AppSettings["smtphost"];
int port = Convert.ToInt( ConfigurationSettings.AppSettings["smtpport"] );
??? NVelocityTemplateEngine engine = new NVelocityTemplateEngine();
??? String newMessage = engine.Process( message, templateName );
// Finally send message...
? }
}
This class has a few lines `of code, but has screaming problems:
- It obtains configuration from the ConfigurationSettings. What if the configuration is not there? What if you'd like to use Yaml instead of XML to hold configuration?
- It instantiates a template engine. In fact, there are two problems here: one is that this class has more responsibility than the name implies. It sends an email, that's for sure, but it also uses a template engine to process the message contents. The second problem is that the class has deep knowledge about the template engine it uses. If you'd like to change the engine later, you're going to dig code.
這個類僅僅幾行代碼,但卻有令人捧腹大笑的問題:(怎樣譯,這樣的問題不會令人捧股吧)
- 首先它從 ConfigurationSettings. 獲取配置項。那么如果配置項不存在會發生什么?如果你喜歡使用Yaml替代XML來存儲配置項會發生什么呢?
- 其次它使用了模板引擎。這有兩個問題:一是類實現的功能比它名稱所能表示更多:它不但發送Email,而且使用模板引擎生成了Email的內容;二是它必須深入了解怎樣使用模板引擎。如果引擎以后需要修改,你需要重新研究這段代碼還是否可行。
So this simple class has two strong dependencies: the means to obtain the configuration and the template engine. Suppose you'd like to use this very class in another project. In order to do that, you'll have to:
- Remember about the configuration keys.
- Bring the NVelocityTemplateEngine class and all the dependencies it might have, with you.
And this is just a small class! It could be worse, and I'm positive you have seen a similar situation. Usually, the fastest solution is to copy the code and change a few things. But c'mon, there must be another way. Fortunately, there is.
所以這個類有兩個嚴重的依賴點:一是配置的獲取二是模板引擎。假如你想在另一個項目中重用它,你將不得不:
- 記住配置對應的鍵值對。
- 附加帶上NVelocityTemplateEngine和其它一切依賴的類。
Component and Services
There were people using an interesting buzzword: COP (component oriented programming). I think we already have enough of buzzwords nowadays and we should save space on our brains for more important things. Nevertheless, I'd like to depict the concepts of components.
A component is a small unit of reusable code. It should implement and expose just one service, and do it well. In practical terms, a component is a class that implements a service (interface). The interface is the contract of the service, which creates an abstraction layer so you can replace the service implementation without effort.
To define our email service, we could use the following interface:
許多人正在使用一個有趣的專業術語:COP(面向組件編程)。我認為現在有太多的專業術語,我們不應該花大多腦精在它們上面,而應節省精力去做更多重要的事件。不過,我仍然要介始一下什么是組件。
簡單的說組件就是一個可重用的單元。它應當并僅僅實現良好一個服務。實際應用中,組件應是一個實現某個接口服務的類。接口約定了服務:這個抽象層使隨意替換服務的實現而對使用接口服務的代碼而任何影響。
To define our email service, we could use the following interface:
定義一個Email服務我們可以這樣做:
// The contract
public interface IEmailSender
{
void Send(String from, String to, String message)
}
Please note that this contract is valid for any implementation of e-mail sender, for example, using SMTP, IMAP, SendMail and so on.
Now, we can work on one of those implementations, a simple SMTP e-mail sender:
不管你使用SMTP,IMAP等等來實現該接口時都必須遵守這個約定。
我們來看一下用SMTP怎樣來實現它:
// The implementation
public class SmtpEmailSender : IEmailSender
{
private String _host;
private int _port;
public SmtpEmailSender(String host, int port)
? {
??? _host = host;
??? _port = port;
? }
public void Send(String from, String to, String message)
? {
// Configures the Smtp class and sends the e-mail
? }
}
Better, huh? But where's the inversion of control? Before going further, allow me to make things more complex. You have probably noticed that now the email sender is only responsible for sending the email, no template processing. So let's define the contract for a template processor engine:
哈哈,是不是蠻好的?但是不是要講IOC嗎,它在哪呢?在繼續之前,允許我將它們變得再復雜點。你可能已經注意到發送郵件的類僅僅負責郵件發送,而沒有處理模板。所以我們得繼續定義一個模板處理引擎的接口:
public interface ITemplateEngine
{
? String Process(String templateName)
}
This is a fairly naive example. Any decent template engine will use a context or something similar. Now we can have a different component which processes the templates and dispatches the emails:
它非常簡單。對于任何的模板引擎實現都可能有許多類似的實現。現在就讓我們來看看一個不太一樣的組件是怎樣實現模板處理與分派郵件的:
public interface INewsletterService
{
void Dispatch(String from, String[] targets, String messageTypeName)
}
Let's speculate about the implementation of INewsletterService. It certainly needs to use the IEmailSender service and the ITemplateEngine service without even caring about their implementation. Fair enough, let's code:
讓我思考一下怎樣實現INewsletterService:它當然必須使用IEmailSender與ItemplateEngine,但不必關心它們是怎樣實現的。就如這樣:
public class SimpleNewsletterService : INewsletterService
{
private IEmailSender _sender;
private ITemplateEngine _templateEngine;
public SimpleNewsletterService(IEmailSender sender,
????????????????????? ITemplateEngine templateEngine)
? {
??? _sender = sender;
??? _templateEngine = templateEngine;
? }
public void Dispatch(String from, String[] targets, String messageTypeName)
? {
??? String message = _templateEngine.Process(messageTypeName);
foreach(String target in targets)
??? {
????? _sender.Send(from, target, message);
??? }
? }
}
As you can see, with small and well defined responsibilities, it's easier to cope with software development. But the problem now is that we have to assemble the application, in other words, we need to connect everything, instantiating and configuring the IEmailSender and the ITemplateEngine, pass them to INewsletterService and God knows what else. You can do it by hand, but believe me, things can get really complicated with a big system.
如你所見,足夠小但功能明確的組件對軟件開發來說是容易實現的。但現在卻有一個問題,那就是必須在應用中綁定它,換句話說,我們需要關心任何一件事,如配置IEmailSender與ITemplateEngine,并將它們傳遞給INewsletterService,也許還有其它更多的事需做。你可能隨手可做好它,但可以確認,在大的系統中這將變得很復雜。
Castle Windsor
Castle Windsor is an inversion of control container. It's built on top of a MicroKernel responsible for inspecting the classes and trying to understand what they need in order to work and then provide the requirements or fail fast if something is wrong.
????? Castle Windsor是一個IOC容器。它構建于MicroKernel之上,能檢測類并了解使用這些類時需要什么參數,然后提供這些參數并且當某些內容有錯誤時將來及時報告失敗。
For our small example, the following code will take care of everything:
下面的代碼就關注這些:
IWindsorContainer container = new WindsorContainer();
container.AddComponent( "newsletter", typeof(INewsletterService),
typeof(SimpleNewsletterService) );
container.AddComponent( "smtpemailsender", typeof(IEmailSender),
typeof(SmtpEmailSender) );
container.AddComponent( "templateengine", typeof(ITemplateEngine),
typeof(NVelocityTemplateEngine) );
// Ok, start the show
INewsletterService service = (INewsletterService) container["newsletter"];
service.Dispatch("hammett at gmail dot com", friendsList, "merryxmas");
First, let me explain what happened in this code snippet:
首先,讓我來解釋一下這個代碼片斷:
1. 首先在容器中注冊了INewsletterService服務并告知容器SimpleNewsletterService實現了這個服務。其中這一個參數是一個Key值,你能使用它在以來后請求這個服務。當然你也能用Type來請求服務。
2. Windsor檢測實現的服務并發現需要其它兩個服務才可工作。它將會自我檢測并發現不存在這樣的注冊服務,那么些時請求生成這樣的組件實例將會拋出一個異常。
3. 當在容器中注冊IEmailSender時,Windsor將會發現類的構造函數需要兩個參數(host與port)。我們已經對采用外部配置文件的解決方式事感到不滿意,所以我們等會兒來糾正它。
4. Windsor注冊ITemplateEngine成功,將通知其它處于WaitingDependency(即依賴其它服務但卻在容器還沒有發現該服務的)狀態的組件告訴它們一個組件注冊了。然后INewsletterService將會發現需要依賴一個組件都OK了,但是它仍然需要等待。
The fact that a class has a non default constructor has a specific meaning for the container. The constructor says to it: "Look, I really need these in order to work". If our implementation were different, the container would have used a different approach. For example:
如果類沒有缺省的構造函數,那么對于容器就有特殊的意義:我需要其它東西才能工作。實現方式不一樣,容器也就會有不同的處理方式。例如:
public class SmtpEmailSender : IEmailSender
{
private String _host;
private int _port;
public SmtpEmailSender()
? {
??? _host = "mydefaulthost";
??? _port = 110; // default port
? }
public String Host
? {
get { return _host; }
set { _host = value; }
? }
public int Port
? {
get { return _port; }
set { _port = value; }
? }
? ...
}
In this case, you may or may not specify the host and port in an external configuration and the container will be able to use it. Another approach is to expose more than one constructor. The container will try to use the best constructor - meaning the one it can satisfy more arguments.
在這里無論在外部配置中提供host和port也或不提供,這個容器都將能使用它。另一個方法為類提供多個構造函數。而容器將使用最恰當的構造函數。
"But wait! What about the configuration?"
讓我們稍停一下,講講配置是怎么一回事。
By default, Windsor will try to obtain the component configuration from the XML file associated with the AppDomain. However, you can override it by implementing the interface IConfigurationStore. There's another implementation of IConfigurationStore that reads the configuration from standard XML files, which is good when you have a configuration for testing environment, another for production, and so on.
缺省的情況下,Windsor將嘗試從與AppDomain關聯的XML文件中獲取組件的配置信息。然后你也可以通過實現IConfigurationStore來取代它。當你需要配置測試環境或在其它產品等上使用其它區別于標準的XML配置讀取方式可能更好。
Here is the configuration file you must use to run the example:
要運行這個示例,這是必須使用的配置文件:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
??? <configSections>
?????? <section name="castle"
type="Castle.Windsor.Configuration.AppDomain.CastleSectionHandler,
Castle.Windsor" />
??? </configSections>
??? <castle>
??????? <components>
?????????? <component id="smtpemailsender">
?????????????? <parameters>
?????????????????? <host>localhost</host>
?????????????????? <port>110</port>
?????????????? </parameters>
?????????? </component>
?????? </components>
??? </castle>
</configuration>
From experience, I know that sometimes it's dumb to have interfaces for all your components. There are classes that are unlikely to have a different implementation, like Data Access Objects. In this case, you can register classes into the container as follows:
現實中,在所有組件中某些是不必定義接口的。它們看起來有不同的實現方式,比如數據訪問對象。事實上,你也能在容器中像下面一樣地注冊并使用它們:
public class MyComponent
{
private IEmailSender _sender;
public MyComponent()
? {
? }
public IEmailSender EmailSender
? {
get { return _sender; }
set { _sender = value; }
? }
}
IWindsorContainer container = new WindsorContainer();
container.AddComponent( "mycomponent", typeof(MyComponent) );
You might want to request a component by the service:
你可以這樣使用組件服務:
IEmailSender emailSender = container[ typeof(IEmailSender) ] as IEmailSender;
But please note that you can register more than one implementation for a given service. The default behavior is to return the first implementation registered for the specified service. If you want a specific implementation, then you need to use the key.
值得注意的是可以注冊多個服務的實現到容器中。但上面這個方法會缺省的返回你注冊的第一個。如果你想使用某個特殊的實現,你需要使用前面提到的Key。
As a matter of fact, the last paragraph leads us to an interesting situation. Suppose you have implemented several IEmailSender but you want that the INewsletterService implementation uses the SMTP version and nothing else. The better way to do it is to use the configuration to specify the exact implementation you want:
事實上,到了最后有一個有趣的情形。假如你已經實現了許多IEmailSender服務,而你僅僅需要使用SMTP版本的服務。那么這時最好的方式就是使用配置來達到你的目的。
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
??? <configSections>
?????? <section name="castle"
type="Castle.Windsor.Configuration.AppDomain.CastleSectionHandler,
Castle.Windsor" />
??? </configSections>
??? <castle>
??????? <components>
?????????? <component id="newsletter">
?????????????? <parameters>
?????????????????? <sender>#{smtpEmailSender}</sender>
?????????????? </parameters>
?????????? </component>
?????? </components>
??? </castle>
</configuration>
The nodes inside 'parameters' must use the name of a property exposed by the implementation or the name of the argument the implementation's constructor uses. Just to refresh your memory:
????? ’parameters’節點內部節點名稱必須與實現的公共屬性名稱或者構造函數中的參數名稱相同。
public class SimpleNewsletterService : INewsletterService
{
private IEmailSender _sender;
private ITemplateEngine _templateEngine;
public SimpleNewsletterService(IEmailSender sender, ITemplateEngine templateEngine)
? {
??? _sender = sender;
??? _templateEngine = templateEngine;
? }
? ...
Lifecycle and Lifestyle
Lifecycle and lifestyle are useful strategies that Apache Avalon have been using for quite some time (since 1999 to be exact). As a former member of Avalon, I like these ideas and decided to provide it too.
????? Apache Avalon使用Lifecycel and lifestyle作為一個很有用的策略模式已經很長一段時間(準確來說始自1999年)。作為Avalon的成員,我喜歡它并愿愿意提供它。
The lifestyle of a component is related to its instance. It might be singleton (which is the default lifestyle) that means that only one instance will be created for the whole life of the container. Other supported lifestyles are:
????? Lifestyle與一個組件的實例相關。它可以是:Singleton(缺省的),這意味著容器中總只存在唯一組件實現。其它可能還有:
- Transient: a new instance is created per request.
- PerThread: just one instance exists per thread.
- Custom: you provide the implementation of ILifestyleManager to implement your own semantics, like poolable objects.
- Transient: 每次請求都會產生新實例。
- PerThread: 每個線程一個實例。
- Custom: 通過實現ILifestyleManager使你的實例具有特殊的Lifestyle,比如對象池。
There are two ways to specify the lifestyle of a component: by using attributes or by using the external configuration. In fact, the configuration overrides whatever is defined on the component. The following have the same result:
有兩種方來指定組件的Lifestyle:一是使用特性,另一個則是使用外部配置。任何時候如果兩者都存在的時候,配置總是會取代直接在組件上定義的特性。下面兩個方式不同但結果卻一樣:
using Castle.Model;
[PerThread]
public class SimpleNewsletterService : INewsletterService
{
private IEmailSender _sender;
private ITemplateEngine _templateEngine;
? ...
<castle>
<components>
?????? <component id="newsletter" lifestyle="perthread" />
??? </components>
</castle>
Lifecycle means the sequence of invocation that happens during the creation of your component (before making it available to the external world) and the sequence of invocation during the destruction. Out of box, Windsor only supports the IInitialize and the standard IDisposable interfaces.
Lifecycle是指創建對象與析構對象時發生的一系列的調用。除此之外,Windsor僅僅支持IInitialize與標準的IDisposable接口。
More about Windsor Container
When you add a component to the container, a few things happen. First, the container creates a ComponentModel which holds a lot of meta information about the component. Then it delegates the inspection of the component to the contributors. Each contributor cares about one specific task. For example, there is a contributor that gathers all public constructors and adds to the ComponentModel, another one checks if the implementation implements specific lifecycle interfaces, another one looks for specific attributes of lifestyle, and so on.
往容器中加入一個組件時,首先容器會新建一個ComponentModel對象,它包含該組件的元數據信息。然后它會作為許多Contributor的代理的偵察員。每一個Contributor都有一個特別的任務。如有一個Contributor專門收所有的公共構造函數并將它加入ComponentModel中,還有專門檢查是否實現出某種特定的Lifecycle接口的Contributor,還有尋找Lifestyle特性的Contributor等等。
The real fun begins when you start to use this process by adding your own contributor that checks for _anything_ and gives the component a different semantic or role when found that _anything_.
真正有趣的是你可以添加你自已的Contributor來做你想做的任何事情。
There's also an important entity called Handler. The Handler is the keeper and orchestrator of the component. If the minimal set of dependencies for the component is not satisfied, the handler will not allow the component to be used (by your code or by any other component).
這里有一重要的實體調用處理器。這個處理器是組件的守護者與協調員。如果組件的最小依賴關系沒有滿足,該處理器將不允許使用該組件(通過你的代碼或者其它組件)。
Now, you're ready for the most interesting section: extending the container.
好了,我們進入更有趣的部分,那就是對這個容器進行擴展。
Extending the Container
I'm going to present you a simple and a not so simple sample applications. The first one will introduce a different semantic to components that implement the IStartable interface; the other depicts a simple yet functional transaction infrastructure for your Data Access Objects and two ways of using it.
接下來我將展示給你一個簡單和一個略微復雜一點的示例應用。簡單的示例將通過實現IStartable接口來說明怎樣為組件實現特殊的功能;而復雜點的則仍然會講講關于DAO對象的事務問題,在這里用了兩種不同的方法來達到這個目的。
Basically, to extend the container, you can use a few extension points as you like. For example, you can add an inspector to look for an attribute, or an entry on the component configuration, or whatever you like. You can also subscribe to the events the MicroKernel exposes. You can even replace some parts of the MicroKernel and implement different semantics for your container (for example, to allow null values be passed to components' constructors).
要擴展這個IOC容器,你可以使用示例提到的增加特性的探測或者增加獲取組件配置信息的入口,只要你喜歡,你都可以這個擴展點來達到你的目的。你還可以發送事件給MicroKernel。甚至你還可以重定義MicroKernel某些部分,使你的容器具有某些其它的作用。(比如,允許傳送一個空值到組件的構造函數)。
When extending the container, you can give the component instance more information, register interceptors (proxies), change the lifestyle, add more lifecycles and so on. At first, let's talk about implementing a new lifecycle step.
通過擴展這個IOC容器,能夠給予組件實例更多的信息,并注入透明動態代理,改變它的Lifestyle和增加更多的Lifecycles等等。首先,就讓我們來看看怎樣實現新的Lifecycel吧。
Startable Lifecycle
Suppose you'd like that every component that implements an IStartable interface be started as soon as possible. By started, I mean:
- Create an instance of it by requesting it.
- Invoke the Start method defined by IStartable, so the component may start its work.
假如每個組件只有有可能都實現了具有Started的IStartable接口。關于Started,我認為包含兩個意義:
- 請求葉創建一個組件實例。
- 調用IStartable的Started方法來組件可就可以開始運行。
For a world usage example, imagine a WinForms application so you can register Forms subclasses as components. One component may run the Application.Run( your main form ). Thus, this component is likely to benefit from the new lifecycle. We'll use the components developed previously just to pretend we have some real action going on.
實際使用時,你可能會想到做WinForm應用程序時可以注冊將Forms的子類為組件:它會調用Application.Run(mainform)。你應該想到新添加的Lifecycle有用了吧。我們將繼續使用前面的示例,假當它真的能完成那些功能。
When you decide to implement extensions like that, you can do it ad-hoc, or you can create a Facility. Facility is an extension unit so you can create a library of facilities and apply them to other applications, thus reusing your work.
如果決定做類似這樣的擴展,你可以做一個Facility或者做Ad-hoc、Facility可以幫助你將這些擴展應用到其它的項目中,從而達以重用的目的。
To implement our requirements, we need to:
So, let's work! The first step: create a new Facility:
為了實現這些需求,我們需要:
1.當某個組件實現了IStartable接口并注冊到容器能夠檢測得到,
2.如果組件實現了IStarted就應用自動添加一個新的Lifecycle用來執行Start方法。
????? Ok,讓我開始吧。首先,創建一個新的Facility。
public class StartableFacility : IFacility
{
private IKernel _kernel;
public void Init(IKernel kernel, IConfiguration facilityConfig)
??? {
??????? _kernel = kernel;
??? }
public void Terminate()
??? {
// Nothing to do
??? }
}
A Facility can have its own configuration which can be useful for settings (like the NHibernate facility that I'll talk about in the next article).
Now, let's add a hook to the registration process:
????? Facility可以使用自已配置信息是很有用。(就像我下一篇文章中將提到的NHibernate的Facility)。讓我們看看怎樣添加一個鉤子到組件注冊的過程中吧:
public class StartableFacility : IFacility
{
private IKernel _kernel;
public void Init(IKernel kernel, IConfiguration facilityConfig)
??? {
??????? _kernel = kernel;
kernel.ComponentModelBuilder.AddContributor( new StartableInspector() );
??? }
public void Terminate()
??? {
// Nothing to do
??? }
private class StartableInspector : IContributeComponentModelConstruction
??? {
public void ProcessModel(IKernel kernel, ComponentModel model)
??????? {
bool startable =
typeof(IStartable).IsAssignableFrom(model.Implementation);
??????????? model.ExtendedProperties["startable"] = startable;
if (startable)
??????????? {
??????????????? model.LifecycleSteps.Add(
??????????????????? LifecycleStepType.Commission, new StartableConcern() );
??????????? }
??????? }
private class StartableConcern : ILifecycleConcern
??????? {
public void Apply(ComponentModel model, object component)
??????????? {
??????????????? (component as IStartable).Start();
??????????? }
??????? }
??? }
}
So we added a contributor that looks for the IStartable interface. If found, we add a new lifecycle step which implements the ILifecycle interface. Simple, huh? But we still need to implement the logic to start the components as soon as possible. Here we go:
這樣我就添加了一個可以查詢組件是否實現了IStartable接口的Contributor、如果注冊到容器的組件實現了它,組件將會添加一個實現了ILifecycle接口的Lifecyle。但是我們仍然需要實現只要可能就啟動組件的處理邏輯,讓我們繼續吧:
public class StartableFacility : IFacility
{
private ArrayList _waitList = new ArrayList();
private IKernel _kernel;
public void Init(IKernel kernel, IConfiguration facilityConfig)
??? {
??????? _kernel = kernel;
??????? kernel.ComponentModelBuilder.AddContributor( new StartableInspector() );
kernel.ComponentRegistered +=
new ComponentDataDelegate(OnComponentRegistered);
??? }
??? ...
private void OnComponentRegistered(String key, IHandler handler)
??? {
bool startable = (bool)
??????????? handler.ComponentModel.ExtendedProperties["startable"];
if (startable)
??????? {
if (handler.CurrentState == HandlerState.WaitingDependency)
??????????? {
??????????????? _waitList.Add( handler );
??????????? }
else
??????????? {
??????????????? Start( key );
??????????? }
??????? }
??????? CheckWaitingList();
??? }
/// For each new component registered,
/// some components in the WaitingDependency
/// state may have became valid, so we check them
private void CheckWaitingList()
??? {
??????? IHandler[] handlers = (IHandler[])
??????????? _waitList.ToArray( typeof(IHandler) );
foreach(IHandler handler in handlers)
??????? {
if (handler.CurrentState == HandlerState.Valid)
??????????? {
??????????????? Start( handler.ComponentModel.Name );
??????????????? _waitList.Remove(handler);
??????????? }
??????? }
??? }
/// Request the component instance
private void Start(String key)
??? {
object instance = _kernel[key];
??? }
}
Now, if the component that was just registered is startable, we try to start it by requesting it. Note however that sometimes the components are not ready to be started, so we have to keep them on a list and check if they are OK to be started later.
現在當一個Startable組件注冊完畢后,容器就會通過請求開始運行該組件。值得注意的是并不是所有組件隨時都可以這樣,所以需要維護一個列表在以后檢查它們運行條件是否已經具備,如果是則開始運行。
If you'd like to see the application running, check the sample code.
如果你想看看這個運行的應用程序,你可以下載示例代碼。
A Transaction Framework for database access
In this example, the goal is to simplify the development of Data Access Objects by automatic handling of the connection and transactions. We purse two possible usages:
在接下來的示例中,將提供兩種方法使DAO對象自動處理連接與事件,從而使開發更容易。
Through attributes:
一種是通過特性:
[Transactional]
public class BlogDao
{
private IConnectionFactory _connFactory;
public BlogDao(IConnectionFactory connFactory)
??? {
??????? _connFactory = connFactory;
??? }
[RequiresTransaction]
public virtual Blog Create(Blog blog)
??? {
using(IDbConnection conn = _connFactory.CreateConnection())
??????? {
??????????? IDbCommand command = conn.CreateCommand();
// Not the best way, but the simplest
??????????? command.CommandText =
??????????????? String.Format("INSERT INTO blog (name, blog.desc) " +
"values ('{0}', '{1}');select @@identity",
??????????????? blog.Name, blog.Description);
object result = command.ExecuteScalar();
??????????? blog.Id = Convert.ToInt32(result);
??????? }
return blog;
??? }
[RequiresTransaction]
public virtual void Delete(String name)
??? {
// We pretend to delete the blog here
??? }
??? ...
}
And through external configuration:
另外一種則是使用外部配置:
<component id="postDao" transactional="true">
<transaction>
??? <method>Create</method>
??? <method>Update</method>
</transaction>
</component>
Might sound complex, but it's not. We need to implement an IConnectionFactory which is aware of transactions. We also need to implement an interceptor that begins/commits or rolls-back transactions.
聽起來也許很復雜,其實不然。我們只需要實現IConnectionFactory,它知道事務怎樣做。同時我們也需要實現一個具有開始提交或回滾事務的攔截器。
It all starts with a Facility again:
我們仍然從創建一個Facility開始:
public class TransactionFacility : IFacility
{
??? TransactionConfigHolder _transactionConfigHolder;
public void Init(IKernel kernel, IConfiguration facilityConfig)
??? {
??????? kernel.AddComponent( "transactionmanager",
typeof(ITransactionManager), typeof(DefaultTransactionManager) );
??????? kernel.AddComponent( "transaction.interceptor",
typeof(TransactionInterceptor) );
??????? kernel.AddComponent( "transaction.configholder",
typeof(TransactionConfigHolder) );
??????? _transactionConfigHolder =
??????????? kernel[ typeof(TransactionConfigHolder) ]
as TransactionConfigHolder;
??????? kernel.ComponentModelCreated += new
?????????????? ComponentModelDelegate(OnModelCreated);
??? }
public void Terminate()
??? {
??? }
private void OnModelCreated(ComponentModel model)
??? {
if (IsTransactional(model))
??????? {
??????????? TransactionConfig config = CreateTransactionConfig(model);
??????????? _transactionConfigHolder.Register(model.Implementation, config);
??????????? model.Interceptors.Add(
new InterceptorReference(typeof(TransactionInterceptor)) );
??????? }
??? }
??? ...
}
This Facility registers the components it needs. If the component is transactional, we associate an interceptor with it. Please note that only virtual methods can be intercepted (in future releases, we'd be able to proxy everything through interfaces).
這個Facility添加注冊了所有需要的組件。如果組件需要處理事務,它將關聯一個攔截器給它。不過請注意目前僅能攔截虛函數的調用(將業發布的版本也許可以通過接口代理一切)。
The interceptor code follows:
這個攔截器的代碼是這樣的:
public class TransactionInterceptor : IMethodInterceptor
{
private ITransactionManager _transactionManager;
private TransactionConfigHolder _transactionConfHolder;
public TransactionInterceptor(ITransactionManager transactionManager,
??????? TransactionConfigHolder transactionConfHolder)
??? {
??????? _transactionManager = transactionManager;
??????? _transactionConfHolder = transactionConfHolder;
??? }
public object Intercept(IMethodInvocation invocation, params object[] args)
??? {
if (_transactionManager.CurrentTransaction != null)
??????? {
// No support for nested transactions
// is necessary
return invocation.Proceed(args);
??????? }
??????? TransactionConfig config =
??????????? _transactionConfHolder.GetConfig(
??????????????? invocation.Method.DeclaringType );
if (config != null && config.IsMethodTransactional( invocation.Method ))
??????? {
??????????? ITransaction transaction =
??????????????? _transactionManager.CreateTransaction();
object value = null;
try
??????????? {
??????????????? value = invocation.Proceed(args);
??????????????? transaction.Commit();
??????????? }
catch(Exception ex)
??????????? {
??????????????? transaction.Rollback();
throw ex;
??????????? }
finally
??????????? {
??????????????? _transactionManager.Release(transaction);
??????????? }
return value;
??????? }
else
??????? {
return invocation.Proceed(args);
??????? }
??? }
}
The rest of the implementation is pretty ADO.NET specific, and I encourage you to see the sample attached to this article. Follows some screenshots of the sample implementation:
剩下的事情就是使用Ado.net來完成,我建議你看看附件中代碼。下面是這個示例運行時的快照:
Stepping back: Castle
Castle project's goal is to offer a set of tools and an inversion of control container. The container is the rendezvous of all the tools, but this is a topic for the next article.
Castle項目的目標就是提供一個IOC容器,并提供相關的一系列工具。容器是所有工具的集合點,但這是下一篇文章的主題。
A few values have driven the development of the Windsor (and MicroKernel) container:
使用Windsor(與Microkernel)將使開發獲益:
- Extensibility: the container should not stop the programmer from implementing his ideas, so it must be very easy to extend it.
- Orthogonality: extending the container should not impact on other extensions (as you saw).
- Reversibility: the container should be able to run code that does not rely on the container API, so you can reuse your components even on other applications without using an inversion of control container.
- 可擴展性:這個容器沒有阻斷程序員的思想,你可以非常容易的去擴展它;
- 獨立性:擴展容器不會影響到其它的擴展(正如你所見到的);
- Reversibility: 組件運行應該不依賴容器的API,這樣在不使用IOC容器的應用時你也能重用你的組件。
We also don't want to be XML or configuration driven as the component holds enough information to assemble itself.
我們也不需要使用XML或者其它形式的配置來保存能夠驅動組件的足夠信息。
Conclusion
In the next article, I'll talk about the NHibernate and the Prevalence facilities. I'll also explain the integration with Web and the Castle on Rails MVC framework inspired by Ruby on Rails.
下一篇文章中我將談到NHibernate與Prevalence的Facility,同時也將介紹怎樣集成Web與從Ruby on Rails得到靈感而開發的Castle on Rail這個MVC框架進行Web開發。
The Castle project still is on the alpha stage, but the public API is very unlikely to change. There is a lot of work to do, so if you enjoyed the reading, consider joining the team and help us develop tools, facilities and extensions so your next enterprise project can be done in half the time, hopefully!
????? Castle項目仍然在alpha階段,但是公開的API很少改變。這兒有大量工作需要去做,如果你喜歡請考慮加入我們的隊伍,來與你們一道開發Tools,Facilities與Extensions。這樣你的下一個項目也許能節省一半的時間。
Please visit Castle project site for more information.
如果你想了解得更多,請訪問Castle project站點。
History
- 26-Dec-2004 - Initial version.
- 29-Dec-2004 - Minor corrections on the text and on the sample.
About Hamilton Verissimo
Hamilton Verissimo has been working with software development for 9 years. He's involved with a lot of open source projects to fill his spare time
轉載于:https://www.cnblogs.com/pursue/archive/2009/09/09/1562974.html
總結
以上是生活随笔為你收集整理的(zhuan)Castle项目简介--第一部分(译)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: boost
- 下一篇: string:值类型?引用类型?[转]