效力

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

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());
  }

追溯變更並非總是需要,但通常是需要的 - 畢竟,人們以犯錯而聞名。進行追溯變更通常需要這種比加法變更更原始的介面,因此有一個論點是,您不需要加法變更的單獨介面。然而,我比較喜歡包含它,因為它可以更容易地進行最常見的加法變更。畢竟,我們不是在尋找最小的介面,而是最容易使用的介面。小有助於易用性 (因為要學習的東西較少),但這只是一個因素,而不是主導因素。