效力
將時間區間新增至物件,以顯示其生效時間。

2004 年 3 月 7 日
這是 進階企業應用程式架構開發 的一部分,是我在 2000 年代中期撰寫的。遺憾的是,自此之後,太多其他事情吸引了我的注意力,所以我沒有時間進一步研究它們,我也看不到未來會有太多時間。因此,這份文件仍處於草稿階段,在我找到時間再次研究它之前,我不會進行任何更正或更新。
運作方式
許多事實僅在特定時間段內為真。因此,描述這些事實的一個顯而易見的方法是標記它們的時間段。對於許多人來說,該時間段是一對日期,但 範圍 可用於在此處將該日期範圍設為物件。
定義後,效力範圍會用於查詢,以傳回在特定日期有效的適當物件。
效力範圍通常會直接更新,但通常提供更符合類別需求的介面是有意義的。建立方法可以使用效力期間的開始日期,然後使用開放式 範圍 來表示沒有結束日期:這適用於在特定日期設定為建立且有效,直到另行通知的情況。當另行通知到來時,您可以使用一個方法,表示物件不再有效,以及發生的日期
使用草圖中建議的雇用範例,我們有一個人物,惠靈頓。1999 年 12 月 12 日,他開始在印度公司就職,我們透過建立一個雇用物件來表示 (圖 1)。隨著時間推移,他在 4 月 1 日開始在半島公司擔任新工作,並在 5 月 1 日結束在印度公司的雇用。 圖 2 顯示了這些事件後物件的狀態。請注意,這表示他在 4 月期間受雇於兩家公司。

圖 1:單一受雇

圖 2:一份受雇結束,另一份開始
此更新機制處理加法更新,亦即在時間線上以正確順序發生的更新。在許多情況下,加法更新就是您所需要的。然而,有時您需要追溯更新,有效修正時間線上的錯誤。假設我們後來發現威靈頓實際上在五月期間為 Dublin Inc 工作,直到 6 月 1 日才開始在 Peninsula Inc 工作。我們必須變更 Peninsula 的受雇效期,並新增 Dublin 的受雇,以產生 圖 3 中所述的狀態。

圖 3:新增 Dublin Inc 受雇後
這通常需要一個更原始的介面,讓受雇可以直接變更其效期範圍。
支援追溯變更通常很重要,因為人們會犯錯。我們可以透過直接存取效期日期範圍來新增這項功能。
在某種意義上,新增雙時間支援相當簡單,您只需新增另一個日期範圍。當然,複雜性會轉嫁到類別使用者身上,他們現在需要在查詢和更新中始終使用兩個日期。
何時使用
效期標示是建模中表示時間性的最常見方式。它簡單易懂。其主要缺點是,它要求客戶了解這些時間面向,並在處理時將其考慮在內。因此,任何想要查看目前資訊的查詢都需要在其邏輯中新增一個子句,以測試效期範圍。雖然這不是一個非常繁重的要求,但它確實讓事情變得更加棘手,特別是在時間責任從網域中不明顯時。
可以建立移除許多這種責任的結構,從而讓時間問題更透明,讓您只在特定需要時才需要擔心它們。要一次針對一個屬性執行此操作,您可以使用 時間屬性。要針對整個物件執行此操作,您可以使用 時間物件。這兩種更精密的模式處理大部分的時間邏輯,減輕這些物件客戶的負擔。
因此,當您有時間行為的簡單情況時,請使用 效能,並且在那些物件應該具有時間性的網域中,這是有意義的。在物件實作中,您應該確保實際上使用 範圍 來作為效能範圍,因為這比使用日期對要容易得多。
範例:受僱 (Java)
這些程式碼範例與我在運作方式區段中所討論的案例平行。我將從人員和公司的簡單命名物件類別開始。
類別 NamedObject...
protected String _name = "no name"; public NamedObject () {} public NamedObject (String name) {_name = name;} public String name () {return _name;} public String toString() {return _name;}
類別 Company...
class Company extends NamedObject{ Company (String name) { super(name); }
類別 Person...
class Person extends NamedObject{ private List employments = new ArrayList(); public Person (String name) { super(name); } Employment[] employments() { return (Employment[]) employments.toArray(new Employment[0]); }
受僱類別是具有效能範圍的類別。其基本資料非常簡單
類別 Employment...
private DateRange effective; private Company company; Company company() {return company;} boolean isEffectiveOn(MfDate arg){ return effective.includes(arg); }
現在,讓我們新增加法行為。我透過人員上的一個方法新增新的受僱,該方法使用開始日期來建立新的受僱。
類別 Person...
void addEmployment(Company company, MfDate startDate) { employments.add(new Employment(company, startDate)); }
類別 Employment...
Employment (Company company, MfDate startDate) { this.company = company; effective = DateRange.startingOn(startDate); }
我使用受僱類別上的方法來結束受僱。
類別 Employment...
void end (MfDate endDate) { effective = new DateRange(effective.start(), endDate); }
有了這個,我們現在可以將受僱新增到人員,並查詢它們以找出特定日期的適當受僱。
類別 Tester...
public void setUp() { duke.addEmployment(india, new mf.MfDate(1999,12,1)); duke.addEmployment(peninsular, new MfDate(2000,4,1)); duke.employments()[0].end(new MfDate (2000,5,1)); } public void testAdditive() { assertEquals(2, duke.employments().length); Employment actual = null; for (int i = 0; i < duke.employments().length; i++) { if (duke.employments()[i].isEffectiveOn(new MfDate(2000,6,1))) { actual = duke.employments()[i]; break; } } assertNotNull(actual); assertEquals(peninsular, actual.company()); }
一位優秀的物件設計師可能會想知道那個 for 迴圈是否真的應該移到人員類別上的方法中。確實應該,而且那是 時間屬性 模式的驅動程式。我們將在那裡看到此類變動的後果,因此我將繼續在此模式的片段中使用 for 迴圈。
現在,讓我們看看追溯變更。對於這些變更,我們需要在受僱和人員上進行更多原始行為。
類別 Person...
void addEmployment(Employment arg) { employments.add(arg); }
類別 Employment...
void setEffectivity(DateRange arg) { effective = arg; } Employment (Company company, DateRange effective) { this.company = company; this.effective = effective; }
然後,我們可以進行像這樣的追溯變更
類別 Tester...
public void testRetro() { duke.employments()[1].setEffectivity(DateRange.startingOn(new MfDate(2000,6,1))); duke.addEmployment(new Employment(dublin, new DateRange(new MfDate(2000,5,1), new MfDate(2000,5,31)))); Employment april = null; for (int i = 0; i < duke.employments().length; i++) { if (duke.employments()[i].isEffectiveOn(new MfDate(2000,4,10))) { april = duke.employments()[i]; break; } } assertNotNull(april); assertEquals(india, april.company()); Employment may = null; for (int i = 0; i < duke.employments().length; i++) { if (duke.employments()[i].isEffectiveOn(new MfDate(2000,5,10))) { may = duke.employments()[i]; break; } } assertNotNull("null may", may); assertEquals(dublin, may.company()); }
追溯變更並非總是需要,但通常是需要的 - 畢竟,人們以犯錯而聞名。進行追溯變更通常需要這種比加法變更更原始的介面,因此有一個論點是,您不需要加法變更的單獨介面。然而,我比較喜歡包含它,因為它可以更容易地進行最常見的加法變更。畢竟,我們不是在尋找最小的介面,而是最容易使用的介面。小有助於易用性 (因為要學習的東西較少),但這只是一個因素,而不是主導因素。