有効性

オブジェクトに期間を追加して、それが有効な時期を示します。

これは、2000年代半ばに執筆していた「エンタープライズアプリケーションアーキテクチャ開発のさらなる展開」(Further Enterprise Application Architecture development)の一部です。残念ながら、それ以来、他の多くのことが私の注意を引いてしまい、それらをさらに進める時間がありませんでしたし、近い将来にも時間が見込めそうにありません。そのため、この資料は非常に草稿の状態であり、再び作業する時間を見つけるまで、修正や更新は行いません。

仕組み

多くの事実は、特定の期間だけ真実です。したがって、これらの事実を記述する明白な方法は、それらに期間をマークすることです。多くの場合、その期間は日付のペアですが、ここでは範囲(Range)を使用して、その日付範囲をオブジェクトにすることができます。

有効期間が定義されると、クエリで使用されて、特定の日付に有効な適切なオブジェクトが返されます。

有効期間はしばしば直接更新されますが、通常はクラスのニーズにより適したインターフェースを提供するのが理にかなっています。作成メソッドは有効期間の開始日を取得し、次にオープンエンドの範囲(Range)を使用して終了日がないことを示すことができます。これは、特定の日付に作成されるように設定され、追って通知があるまで有効な場合に適しています。その追って通知が来た場合は、オブジェクトが有効ではなくなったことと、それが発生した日付を示すメソッドを使用できます。

スケッチで提案されている雇用の例を使用すると、ウェリントンという人物がいます。1999年12月12日、彼はIndia Inc.での雇用を開始し、これは雇用オブジェクトの作成によって表されます(図1)。時間が経つにつれて、彼は4月1日にPeninsula Inc.で新しい雇用を開始し、5月1日にIndia Inc.での雇用を終了します。図2は、これらのイベント後のオブジェクトの状態を示しています。これは、4月中は彼が両方に雇用されていたことを意味することに注意してください。

図1:単一の雇用

図2:1つの雇用が終了し、別の雇用が開始された

この更新メカニズムは、追加的な更新、つまりタイムライン上で正しい順序で発生する更新を処理します。多くの場合、追加的な更新だけで十分です。しかし、タイムラインの間違いを効果的に修正する、遡及的な更新が必要になることもあります。ウェリントンが実際には5月中にDublin Inc.で働いており、6月1日までPeninsula Inc.で働き始めていなかったことが後で判明したとします。Peninsulaの雇用の有効性を変更し、Dublinの新しい雇用を追加して、図3に示されている状態にする必要があります。

図3:Dublin Inc.の雇用を追加した後

これには通常、雇用の有効期間を直接変更できる、よりプリミティブなインターフェースが必要です。

遡及的な変更をサポートすることは、人が間違いを犯すため、しばしば重要です。有効期間を直接変更できることで、それを追加できます。

バイテンポラルサポートを追加することは、ある意味では非常に簡単です。別の日付範囲を追加するだけです。もちろん、複雑さは、クエリと更新ですべての日付を常に使用する必要があるクラスのユーザーに渡されます。

いつ使うか

有効日付は、モデリングにおける時間性を示す最も一般的な方法です。シンプルで使い方が分かりやすいです。主な欠点は、クライアントがこれらの時間的側面を認識し、処理中に考慮に入れる必要があることです。したがって、現在の情報を見たいクエリは、有効期間をテストするための句をロジックに追加する必要があります。これはそれほど難しい要件ではありませんが、特に時間的責任がドメインから明らかでない場合、物事をより複雑にします。

この責任の多くを取り除き、時間の問題をより透過的にする構造を構築することが可能です。つまり、特に必要な場合にのみ、それらについて心配する必要があります。一度に1つのプロパティに対してこれを行うには、時間的プロパティ(Temporal Property)を使用できます。オブジェクト全体に対してこれを行うには、時間的オブジェクト(Temporal Object)を使用できます。これらのより洗練されたパターンの両方は、時間ロジックの多くを処理し、これらのオブジェクトのクライアントの負担を軽減します。

したがって、時間的動作の状況が単純で、それらのオブジェクトが時間的であることがドメインにおいて理にかなっている場合は、有効性を使用します。オブジェクトの実装では、日付のペアを使用するよりもはるかに簡単なので、有効期間に範囲(Range)を実際に使用していることを確認する必要があります。

例:雇用(Java)

これらのコード例は、仕組みのセクションで説明したケースに対応しています。まず、人物と会社のための単純な名前付きオブジェクトクラスから始めます。

class NamedObject...

  protected String _name = "no name";
  public NamedObject ()  {}
  public NamedObject (String name)  {_name = name;}
  public String name ()  {return _name;}
  public String toString() {return _name;}

class Company...

  class Company extends NamedObject{
    Company (String name) {
      super(name);
    }

class 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]);
    }

雇用クラスは、有効期間を持つクラスです。基本データは非常にシンプルです。

class Employment...

  private DateRange effective;
  private Company company;
  Company company() {return company;}
  boolean isEffectiveOn(MfDate arg){
    return effective.includes(arg);
  }

それでは、加算的な動作を追加してみましょう。開始日を使用して新しい雇用を作成する、人物のメソッドによって新しい雇用を追加します。

class Person...

  void addEmployment(Company company, MfDate startDate) {
    employments.add(new Employment(company, startDate));
  }

class Employment...

  Employment (Company company, MfDate startDate) {
    this.company = company;
    effective = DateRange.startingOn(startDate);
  }

雇用クラスのメソッドを使用して雇用を終了します。

class Employment...

  void end (MfDate endDate) {
    effective = new DateRange(effective.start(), endDate);
  }

これで、人物に雇用を追加し、特定の日に適切な雇用を見つけるためにクエリを実行できます。

class 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ループを実際にPersonクラスのメソッドに移動する必要があるかどうか疑問に思うかもしれません。確かにそうすべきであり、それが時間的プロパティ(Temporal Property)パターンの推進力です。そこでそのような移動の結果を確認するので、このパターンでは引き続きforループを使用します。

それでは、遡及的な変更を見てみましょう。これらには、雇用と人物に関するよりプリミティブな動作が必要です。

class Person...

  void addEmployment(Employment arg) {
   employments.add(arg);
 }

class Employment...

  void setEffectivity(DateRange arg) {
    effective = arg;
  }
  Employment (Company company, DateRange effective) {
    this.company = company;
    this.effective = effective;
  }

その後、次のように遡及的な変更を行うことができます。

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

遡及的な変更は必ずしも必要ではありませんが、通常は必要です。結局のところ、人は間違いを犯すことで知られています。遡及的な変更を行うには、通常、加算的な変更よりもこの種のよりプリミティブなインターフェースが必要になるため、加算的な変更に別個のインターフェースは必要ないという議論があります。しかし、最も一般的な加算的な変更を簡単に行えるため、私はそれを含めることを好みます。結局のところ、私たちは最小のインターフェースではなく、最も使いやすいインターフェースを探しているのです。小さいことは使いやすさ(学ぶことが少ないため)に役立ちますが、それは1つの要素に過ぎず、支配的な要素ではありません。