本文章是由機器翻譯。
實用模式
內部定義域專屬語言
Jeremy Miller
網域特定的語言 (DSL) 在過去幾年已受歡迎的主題,並將可能會變來到年的重要性。您可能已經遵循 「 奧斯陸 」 專案 (現在稱為 SQL Server 模型) 或實驗 ANTLR 精心製作 「 外部 」 DSL 等工具。更立即平易近人的替代方法是建立會寫入現有的程式設計語言,如 C# 內的 「 內部 」 DSL。
內部 DSL 可能不為可以讀取和英文,一樣的外部 DSL 相當表達和非開發人員可讀取是但機制的建立內部的 DSL 是比較簡單,因為您不您的程式碼採用編譯器或外部的剖析器。
請注意我不建議本文中的 DSL 都適合由商業專家的檢閱。這個文件我將著重僅於如何模式的內部 DSL 容易我們身為開發人員工作由 crafting 較容易讀取和寫入的 API。
我正在提取出我管理及開發,以 C# 撰寫的兩個開放原始碼專案的範例很多。第一個是 StructureMap 其中一項 Microsoft.NET Framework 的反轉控制項 (IoC) 容器工具。第二個是 StoryTeller 接受度測試的工具。您可以下載 Subversion 透過在 https://structuremap.svn.sourceforge.net/svnroot/structuremap/trunk 或 storyteller.tigris.org/svn/storyteller/trunk (註冊所需) 這兩個專案的完整原始程式碼。我也可以為範例的另一個來源建議 Fluent NHibernate 專案 ( fluentnhibernate.org )。
常值的擴充程式
我想要在本文中的更重要事項是有許多小技巧,您可以如何使您可以清楚地讀取,並能更表達的程式碼。將這些小技巧可以真的值新增至您的程式碼撰寫努力藉容易撰寫程式碼,它會變得更宣告式且更意圖透露正確。
越來越多經常我使用延伸方法上基本物件 (例如字串和數字以減少在核心.NET Framework API repetitiveness 並增加可讀性。這種模式的擴充值的物件稱為 「 常值延伸 」。
let’s 開頭簡化的範例。我目前的專案牽涉到 reoccurring 及已排程的事件的可設定規則。我們一開始嘗試建立小型內部 DSL 來設定這些事件 (我們是而移動到外部 DSL 處理方法的程序中)。這些規則經常取決於事件應該發生的頻率、 應該啟動時和過期的時機的 TimeSpan 值。看起來會像這個程式碼片段:
x.Schedule(schedule =>
{
// These two properties are TimeSpan objects
schedule.RepeatEvery = new TimeSpan(2, 0, 0);
schedule.ExpiresIn = new TimeSpan(100, 0, 0, 0);
});
在特別注意,「 新 TimeSpan(2, 0, 0) 」 及 「 新 TimeSpan(100, 0, 0, 0)。 」 身經驗豐富的.NET Framework 開發人員可能會剖析程式碼來表示 「 2 小時 」 和 「 100 天 」 的那些兩個部份,但是您必須考慮它,didn’t 您吗? 因此,let’s 請 TimeSpan 定義更容易閱讀:
x.Schedule(schedule =>
{
// These two properties are TimeSpan objects
schedule.RepeatEvery = 2.Hours();
schedule.ExpiresIn = 100.Days();
});
我在上述範例中做的只是傳回 TimeSpan 物件的整數物件上使用某些延伸方法:
public static class DateTimeExtensions
{
public static TimeSpan Days(this int number)
{
return new TimeSpan(number, 0, 0, 0);
}
public static TimeSpan Seconds(this int number)
{
return new TimeSpan(0, 0, number);
}
}
在實作,方面從 「 新 TimeSpan(2, 0, 0, 0) 」 到 「 2.Days() 」 切換 isn’t 大的變更但哪一個都可以輕易地讀取嗎? 我知道當我將商務規則轉譯成程式碼,我而是說兩天比 「 一個包含兩天、 零小時和 0 分鐘時間範圍 」。更容易閱讀版本的程式碼容易掃描的正確性,且的 ’s 足夠的理由我使用常值運算式版本。
語意模型
當我建立新 DSL 我需要解決兩個問題。 先,我的小組和我啟動藉由詢問自己我們要如何用 [DSL 表示一邏輯又自我描述的方法,將會使它易於使用。 我試著執行這項操作而不管將要如何結構化或建置實際功能約可能。
比方說 StructureMap 反轉的控制 (IoC) 容器工具可以讓使用者設定容器明確內 StructureMap ’s 「 登錄 DSL 」 就像這樣:
var container = new Container(x =>
{
x.For<ISendEmailService>().HttpContextScoped()
.Use<SendEmailService>();
});
如果您已經 aren’t 熟悉 IoC 容器的使用方式,所有程式碼正在執行的動作指出當您在執行階段容器尋求 ISendEmailService 之型別的物件時,您將取得的具象型別 SendEmailService 執行個體。對 HttpContextScoped 呼叫指引 StructureMap 到 「 範圍 」,這表示如果執行 ASP.NET 內部的程式碼都會有唯一的單一執行個體 ISendEmailService 針對每個個別的 HTTP 要求不是單一 HttpRequest ISendEmailService 物件重要要求內單一的 HTTP 要求 ISendEmailService 多少次。
一旦有您想要的語法了解我離開重要提出問題的確實我要如何連接 DSL 語法會實作實際行為的程式碼。您可以直接將 DSL 程式碼放置行為的程式碼,使得直接在運算式建立幫手] 物件中會發生執行階段的動作,但我強烈建議針對這在任何除了最簡易的情況。運算式建立幫手] 類別可以是有點難以單元測試,而且偵錯的方式逐步執行 fluent 介面不是 conducive 產能,或您的例行性。您真的想要將您自己放在要能夠單元測試 (最好是)、 偵錯和疑難排解您的 DSL 的執行階段行為項目,而不需要的位置來逐步執行典型 fluent 介面中的所有程式碼間接取值。
我需要建置執行階段行為,我需要精心製作盡可能一樣可以清楚地表達 DSL 使用者 ’s 意圖的 DSL。在我的經驗中已被非常有幫助分開執行階段行為到 「 語意模型,」 由 Martin Fowler 定義為 「 DSL 填入的網域模型 」 ( martinfowler.com/dslwip/SemanticModel.html )。
有關先前的程式碼片段的重點是它 doesn’t 做任何真實的工作。所有的小位元的 DSL 的程式碼不會設定 IoC 容器的語意模型。您 可能 略過 fluent 上述的介面,並自行建置語意模型物件,就像這樣:
var graph = new PluginGraph();
PluginFamily family = graph.FindFamily(typeof(ISendEmailService));
family.SetScopeTo(new HttpContextLifecycle());
Instance defaultInstance = new SmartInstance<SendEmailService>();
family.AddInstance(defaultInstance);
family.DefaultInstanceKey = defaultInstance.Name;
var container = new Container(graph);
登錄 DSL 程式碼和直接上述程式碼是在執行階段行為相同。 DSL 確實的就是建立物件 Graph 的 PluginGraph]、 [PluginFamily]、 [執行個體] 和 [HttpContextLifecycle 物件。 問題是為什麼擔心有兩種不同的模型嗎?
先以使用者,因為 ’s 少很多的程式碼撰寫,我肯定會想 DSL 版本的兩個先前程式碼範例的所有多俐落地表達我的用意,doesn’t 要求使用者在所知非常多 StructureMap 內部。 StructureMap 實作器,我需要一個簡單的方式來建置和測試功能,以小單位,並可 ’s 相對較難處理 fluent 介面本身。
語意模型的方法我能夠建置,單元測試很容易行為的類別。 DSL 程式碼本身會變得非常簡單,因為它會全部是設定語意模型。
這個分隔 DSL 運算式和語意模型已開啟經過一段時間是很有幫助。 您經常需要重複稍微與您的 DSL 語法,以達到更容易閱讀,並可寫入運算式根據使用方式的意見反應。 該反覆項目將會更加順暢如果 don’t 必須擔心相當這麼有關破壞執行階段功能在同一時間變更語法。
在另一方面,透過 DSL 為正式的 API 為 StructureMap,我在許多情況下被能夠擴充或重建內部的語意模型而不會破壞 DSL 語法。 這是只有一個更多範例 「 分隔的考量 」 原則中軟體設計的優點。
fluent 介面] 和 [運算式建立幫手
fluent 介面是 API 的使用方法鏈結建立簡要、 可讀取的語法的樣式。 我相信最知名的範例可能是越來越普遍 jQuery 程式庫的 JavaScript 開發。 jQuery 使用者可快速地辨識程式碼 (例如下列:
var link = $(‘<a></a>’).attr("href", "#").appendTo(binDomElement);
$(‘<span></span>’).html(binName).appendTo(link);
fluent 介面可讓我的程式碼 「 densify 」 到較小視窗的潛在讓程式碼易於閱讀的文字。 而且,它通常有助於我引導使用者我的 API,以選取適當的選擇。 最簡單與或許是最常見的技巧,在製作 fluent 介面是只要進行即可從 (這是主要 jQuery 的運作方式) 的方法呼叫傳回其自身的物件。
我有一個簡單的類別使用中 StoryTeller 來產生 HTML 稱為 「 HtmlTag 」。我可以建置 HtmlTag 物件快速方法鏈結像這樣:
var tag = new HtmlTag("div").Text("my text").AddClass("collapsible");
內部,HtmlTag 物件只本身從傳回文字和 AddClass 的呼叫:
public HtmlTag AddClass(string className)
{
if (!_cssClasses.Contains(className))
{
_cssClasses.Add(className);
}
return this;
}
public HtmlTag Text(string text)
{
_innerText = text;
return this;
}
在更複雜的案例中您可能會分隔 fluent 介面分成兩個部分提供執行階段行為 (這種模式稍後詳細) 和 「 運算式建立幫手 」 類別來實作 DSL 文法的一系列的語意模型。
我可以用來定義鍵盤快速鍵和動態功能表 StoryTeller 使用者介面中使用這個圖樣的範例。 我想要在使用者介面中定義巨集指令的鍵盤快速鍵以程式設計方式快速的方法。 而且,因為大多數的我們 can’t 請記住,我們使用每個應用程式每個鍵盤快速鍵,我想在公開所有可用的快速鍵和鍵盤按鍵組合來執行他們的使用者介面 (UI) 中建立單一的功能表。 而且,如畫面啟動 StoryTeller UI 的主要] 索引標籤區域中,我想是作用中的螢幕的特定的 UI 中新增動態功能表區域按鈕。
我當然可以有只編碼這 idiomatic 的 Windows Presentation Foundation (WPF) 方法,但會有也就是說編輯的鍵盤筆勢、 命令、 確定這些已全部正確連結在一起的每個螢幕及功能表 items–and 之功能表區域物件的 XAML 標記的一些不同區域。 我想要讓此登錄新的捷徑和功能表項目的宣告式的越好,並我想要減少程式碼的單一點。 我當然做 fluent 的介面,在幕後設定我的所有不同的 WPF 物件。
我可以在使用量中, 指定下列的程式碼以開啟 「 執行佇列 」 畫面的全域快顯:
// Open the "Execution Queue" screen with the
// CTRL - Q shortcut
Action("Open the Test Queue")
.Bind(ModifierKeys.Control, Key.Q)
.ToScreen<QueuePresenter>();
在針對個別的螢幕畫面啟動程式碼,我可以定義暫存的鍵盤快速鍵和主應用程式殼層,像這樣的程式碼中動態功能表選項:
screenObjects.Action("Run").Bind(ModifierKeys.Control, Key.D1)
.To(_presenter.RunCommand).Icon = Icon.Run;
screenObjects.Action("Cancel").Bind(ModifierKeys.Control, Key.D2)
.To(_presenter.CancelCommand).Icon = Icon.Stop;
screenObjects.Action("Save").Bind(ModifierKeys.Control, Key.S)
.To(_presenter.SaveCommand).Icon = Icon.Save;
現在,let’s 看一下這個 fluent 介面的實作。 它的基礎是呼叫建置所有組成的 WPF 物件的實際運作的 ScreenAction 語意模型類別。 該類別看起來會像這樣:
public interface IScreenAction
{
bool IsPermanent { get; set; }
InputBinding Binding { get; set; }
string Name { get; set; }
Icon Icon { get; set; }
ICommand Command { get; }
bool ShortcutOnly { get; set; }
void BuildButton(ICommandBar bar);
}
這是很重要的詳細資料。 我可以建置和測試 ScreenAction 物件無關的 fluent 介面,現在 fluent 介面只是 ScreenAction 物件設定。 實際的 DSL 被實作類別,稱為 ScreenObjectRegistry 追蹤的作用中 (請參閱 的 圖 1) 的 ScreenAction 物件清單上。
圖 1 DSL 已實作上 [ScreenActionClass
public class ScreenObjectRegistry : IScreenObjectRegistry
{
private readonly List<ScreenAction> _actions =
new List<ScreenAction>();
private readonly IContainer _container;
private readonly ArrayList _explorerObjects = new ArrayList();
private readonly IApplicationShell _shell;
private readonly Window _window;
public IEnumerable<ScreenAction> Actions {
get { return _actions; } }
public IActionExpression Action(string name)
{
return new BindingExpression(name, this);
}
// Lots of other methods that are not shown here
}
註冊新的螢幕動作到上述 Action(name) 方法呼叫開始,並傳回做為一個運算式建立幫手] 來設定部分 的 圖 2 所示之新 ScreenAction 物件 BindingExpression 類別的新執行個體。
圖 2 的 BindingExpression 類別做為運算式建立幫手]
public class BindingExpression : IBindingExpression, IActionExpression
{
private readonly ScreenObjectRegistry _registry;
private readonly ScreenAction _screenAction = new ScreenAction();
private KeyGesture _gesture;
public BindingExpression(string name, ScreenObjectRegistry registry)
{
_screenAction.Name = name;
_registry = registry;
}
public IBindingExpression Bind(Key key)
{
_gesture = new KeyGesture(key);
return this;
}
public IBindingExpression Bind(ModifierKeys modifiers, Key key)
{
_gesture = new KeyGesture(key, modifiers);
return this;
}
// registers an ICommand that will launch the dialog T
public ScreenAction ToDialog<T>()
{
return buildAction(() => _registry.CommandForDialog<T>());
}
// registers an ICommand that would open the screen T in the
// main tab area of the UI
public ScreenAction ToScreen<T>() where T : IScreen
{
return buildAction(() => _registry.CommandForScreen<T>());
}
public ScreenAction To(ICommand command)
{
return buildAction(() => command);
}
// Merely configures the underlying ScreenAction
private ScreenAction buildAction(Func<ICommand> value)
{
ICommand command = value();
_screenAction.Binding = new KeyBinding(command, _gesture);
_registry.register(_screenAction);
return _screenAction;
}
public BindingExpression Icon(Icon icon)
{
_screenAction.Icon = icon;
return this;
}
}
其中一個重要的因素,在許多 fluent 介面正在嘗試引導 API 的使用者進入執行特定順序的事情。 在大小寫的 的 圖 2,我使用介面上 BindingExpression 嚴格來控制使用者選項中 IntelliSense,即使我正在總是傳回相同 BindingExpression 物件在整個。 思考這。 使用者的這個 fluent 介面應該只指定動作名稱及鍵盤快捷鍵一次。 之後,使用者 shouldn’t 有看到 IntelliSense 那些方法。 DSL 運算式的開頭會擷取捷徑,會出現在功能表中,並傳回新的 BindingExpression 物件,為這個介面的描述性名稱的 ScreenObjectRegistry.Action(name) 呼叫:
public interface IActionExpression
{
IBindingExpression Bind(Key key);
IBindingExpression Bind(ModifierKeys modifiers, Key key);
}
藉由轉型 BindingExpression IActionExpression,使用者擁有唯一的選擇是指定為會傳回相同的 BindingExpression 物件中但轉換 IBindingExpression 介面,只可讓使用者指定單一巨集指令的捷徑按鍵的組合:
// The last step that captures the actual
// "action" of the ScreenAction
public interface IBindingExpression
{
ScreenAction ToDialog<T>();
ScreenAction ToScreen<T>() where T : IScreen;
ScreenAction PublishEvent<T>() where T : new();
ScreenAction To(Action action);
ScreenAction To(ICommand command);
}
物件初始設定式
既然我們引入方法為在 C# 中的內部 DSL 開發 mainstay 鏈結,let’s 開始查看通常導致較簡單的機制,DSL 開發人員的替代模式。 第一個替代方案是只是使用 Microsoft.NET Framework 3.5 所引進的物件初始設定式功能。
我仍然可以記得我第一 foray 成 fluent 介面。 我曾發生作用為法律公司以電子方式送出法律發票和客戶之間的訊息保險經紀人的系統。 其中一個常見的使用情形,對我們而言是將訊息傳送給客戶的法律公司。 傳送郵件我們叫用介面就像這樣:
public interface IMessageSender
{
void SendMessage(string text, string sender, string receiver);
}
’s 非常簡單的 API ; 在三個字串引數和它的只是傳遞 ’s OK。 在用法問題是哪一個引數進入的位置。 是,ReSharper 等工具可以顯示您指定在任一時間,但只讀取程式碼時,如何呢掃描 SendMessage 對呼叫的參數? 查看下列的程式碼範例的使用方式及瞭解完全我的意思有關從調換字串引數順序錯誤:
// Snippet from a class that uses IMessageSender
public void SendMessage(IMessageSender sender)
{
// Is this right?
sender.SendMessage("the message body", "PARTNER001", "PARTNER002");
// or this?
sender.SendMessage("PARTNER001", "the message body", "PARTNER002");
// or this?
sender.SendMessage("PARTNER001", "PARTNER002", "the message body");
}
在時間,我移動至一 fluent 介面方法,以更清楚地指出哪一個引數是其中解決 API 的使用性問題:
public void SendMessageFluently(FluentMessageSender sender)
{
sender
.SendText("the message body")
.From("PARTNER001").To("PARTNER002");
}
我 genuinely 相信這針對一個更可用、 較不容易出錯的 API 進行,不過 let’s 看 的 圖 3 在運算式建立幫手基礎的實作如下。
圖 3 的 運算式產生器實作
public class FluentMessageSender
{
private readonly IMessageSender _messageSender;
public FluentMessageSender(IMessageSender sender)
{
_messageSender = sender;
}
public SendExpression SendText(string text)
{
return new SendExpression(text, _messageSender);
}
public class SendExpression : ToExpression
{
private readonly string _text;
private readonly IMessageSender _messageSender;
private string _sender;
public SendExpression(string text, IMessageSender messageSender)
{
_text = text;
_messageSender = messageSender;
}
public ToExpression From(string sender)
{
_sender = sender;
return this;
}
void ToExpression.To(string receiver)
{
_messageSender.SendMessage(_text, _sender, receiver);
}
}
public interface ToExpression
{
void To(string receiver);
}
}
’s 更多的程式碼來建立 API,比原先所需。 幸運的是,現在我們有另一個替代方案以物件初始設定式 (或使用具名參數在.NET Framework 4 或 VB.NET)。 做為它的參數,在單一物件中 let’s 進行郵件寄件者可接受另一個的版本:
public class SendMessageRequest
{
public string Text { get; set; }
public string Sender { get; set; }
public string Receiver { get; set; }
}
public class ParameterObjectMessageSender
{
public void Send(SendMessageRequest request)
{
// send the message
}
}
現在,API 使用方式以物件初始設定式是:
public void SendMessageAsParameter(ParameterObjectMessageSender sender)
{
sender.Send(new SendMessageRequest()
{
Text = "the message body",
Receiver = "PARTNER001",
Sender = "PARTNER002"
});
}
可說,API 的此第三個 incarnation 減少比 fluent 介面版本更簡單機制的使用中的錯誤。
這裡的重點是 fluent 介面不是唯一在.NET Framework 中建立更容易閱讀的 API 的圖樣。 這個 approach 是在其中您可以使用 JavaScript 物件表示法 (JSON) 來在一行程式碼,和注音標示 idiomatic 使用名稱/值雜湊做為方法的引數是完全指定物件的 JavaScript 更常見的。
巢狀的結案
我認為許多人假設 fluent 介面和方法鏈結是建置 DSL 在 C# 內的唯一的可能性。 我使用太,相信,但是我因為找到其他技術及經常更容易實作比方法鏈結的圖樣。 越來越普遍的模式是巢狀的終止模式:
藉由將它們放入引數中終止表達函式呼叫的陳述式子項目。
具有模型檢視控制程式模式完成越來越多的.NET Web 開發專案。 這個移位所產生,是副作用的更多需要輸入項目的程式碼中產生的 HTML 程式碼片段。 直線接字串操作來產生 HTML 可以取得醜陋快速。 所得到的重複很多的 「 安全性過濾 」 HTML,以避免資料隱碼攻擊的呼叫,而在許多情況下我們可能想要允許多個類別或方法中最後的 HTML 表示有某些說。 我想要表達 HTML 建立只需說出 「 我想使用這個文字和這個類別的 div 標記 」。若要減輕這個 HTML 層代,模型 HTML 和看起來像這樣在用法的 「 HtmlTag 」 物件
var tag = new HtmlTag("div").Text("my text").AddClass("collapsible");
Debug.WriteLine(tag.ToString());
這會產生下列的 HTML:
<div class="collapsible">my text</div>
此 HTML 產生模型的核心是 HtmlTag 物件具有以程式設計的方式建置像這樣的 HTML 項目結構的方法:
public interface IHtmlTag
{
HtmlTag Attr(string key, object value);
HtmlTag Add(string tag);
HtmlTag AddStyle(string style);
HtmlTag Text(string text);
HtmlTag SetStyle(string className);
HtmlTag Add(string tag, Action<HtmlTag> action);
}
這個模型也可讓我們加入巢狀的 HTML 標記,就像這樣:
[Test]
public void render_multiple_levels_of_nesting()
{
var tag = new HtmlTag("table");
tag.Add("tbody/tr/td").Text("some text");
tag.ToCompacted().ShouldEqual(
"<table><tbody><tr><td>some text</td></tr></tbody></table>"
);
}
在真實使用量我經常發現自己想要加入一個完全設定的子標記,以一個步驟。 我有提到我有一個稱為 「 我的小組使用來表達接受度測試的 StoryTeller 的開放原始碼專案。 StoryTeller 功能的一部份是在我們持續整合組建中執行所有的接受度測試並產生測試結果的報告。 測試結果摘要是以簡單的表格具有三個資料行來表示。 摘要資料表 HTML 看起來會像這樣:
<table>
<thead>
<tr>
<th>Test</th>
<th>Lifecycle</th>
<th>Result</th>
</tr>
</thead>
<tbody>
<!-- rows for each individual test -->
</tbody>
</table>
我使用上面所述的 HtmlTag 模型,產生結果的資料表,這段程式碼的標頭結構:
// _table is an HtmlTag object
// The Add() method accepts a nested closure argument
_table.Add("thead/tr", x =>
{
x.Add("th").Text("Test");
x.Add("th").Text("Lifecycle");
x.Add("th").Text("Result");
});
在 _table.Add 我傳遞 Lambda 函式中呼叫該 completely 會指定如何產生第一個標頭資料列。 使用巢狀的終止模式可讓我規格中的 [傳送],而不必建立 「 tr 」 標記的另一個變數第一個。 您不可能第一眼喜歡這種語法,但是它會使程式碼 terser。 在內部,使用巢狀的終止 Add 方法是只是:
public HtmlTag Add(string tag, Action<HtmlTag> action)
{
// Creates and adds the new HtmlTag with
// the supplied tagName
var element = Add(tag);
// Uses the nested closure passed into this
// method to configure the new child HtmlTag
action(element);
// returns that child
return element;
}
對於另一個範例會將主要 StructureMap 容器類別初始化透過傳入巢狀的終止,表示所有所需的組態容器就像這樣的:
IContainer container = new Container(r =>
{
r.For<Processor>().Use<Processor>()
.WithCtorArg("name").EqualTo("Jeremy")
.TheArrayOf<IHandler>().Contains(x =>
{
x.OfConcreteType<Handler1>();
x.OfConcreteType<Handler2>();
x.OfConcreteType<Handler3>();
});
});
簽章和主體的這個建構函式的函式是:
public Container(Action<ConfigurationExpression> action)
{
var expression = new ConfigurationExpression();
action(expression);
// As explained later in the article,
// PluginGraph is part of the Semantic Model
// of StructureMap
PluginGraph graph = expression.BuildGraph();
// Take the PluginGraph object graph and
// dynamically emit classes to build the
// configured objects
construct(graph);
}
我用於巢狀的終止模式在這種情況下幾個原因。第一個是 StructureMap 容器運作方式是以一個步驟進行完整的設定,然後使用 Reflection.Emit 動態產生 「 產生器 」 物件,才可使用容器。採取組態,透過巢狀的終止,可讓我擷取整個組態一次,並無訊息模式執行 [發出右容器進行可供使用之前。其他理由是要隔離與容器註冊型別方法,您會在執行階段用來擷取的服務 (這是介面隔離霍夫,在 「 我 」 S.O.L.I.D.中的範例) 遠離組態次方法。
我有本文包含巢狀的終止模式,因為它變得相當普遍 in.NET Framework 開放原始碼專案,例如犀牛 Mocks、 Fluent NHibernate 和許多 IoC 工具。而且,我經常發現巢狀的終止模式,以大幅容易實作比使用唯一的方法鏈結。缺點是許多開發人員仍然喜歡使用 Lambda 運算式。此外,這項技術是幾乎沒有可用 VB.NET 中,因為 VB.NET doesn’t 支援多行 Lambda 運算式。
IronRuby 和 Boo
對於主流訴求範圍撰寫 C# 中所有我在本文的範例,但如果 ’re 興趣進行 DSL 開發可能會想要查看使用其他 CLR 語言。在特別 IronRuby 是因為它有彈性且無相當雜亂的語法的建立內部 DSL 的例外 (選擇性的括號、 沒有分號和非常簡要)。逐步執行得更遠 afield,Boo 語言也是熱門 DSL 開發 CLR 中。
設計模式名稱和定義是取自 Martin Fowler ’s 眼前的書籍網域特定語言在 martinfowler.com/dslwip/index.html 的線上的草稿。
Jeremy Miller對於 C# 的 Microsoft MVP 是也與.NET 和眼前 StoryTeller ( storyteller.tigris.org ) 工具進行 supercharged FIT 測試在.NET 中的相依性插入開啟來源 StructureMap ( structuremap.sourceforge.net ) 工具的作者。請造訪他網誌,[的網底樹狀結構開發在 codebetter.com/blogs/jeremy.miller ,CodeBetter 站台的一部份。
多虧給來檢閱這份文件的技術專家下列:Glenn Block