web-dev-qa-db-ja.com

1対多の関係のプログラミング

だから私はグーグルとstackoverflowで検索を行ってもそれ以上の結果が返されないことに驚いています。

OOプログラミング(私はJavaを使用しています)では、1対多の関係をどのように正しく実装しますか?

クラスCustomerとクラスJobがあります。私のアプリケーションは、顧客の仕事を完了する架空の会社向けです。私の現在の実装では、JobクラスはCustomerクラスとは何の関係もなく、それへの参照はまったくありません。 Customerクラスは、コレクションとメソッドを使用して、顧客によって割り当てられた、または顧客のために完了したジョブに関する情報を保持、取得、および変更します。

問題は、特定のJobがどの顧客に対して行われたかを知りたい場合はどうすればよいかということです。関連するこの記事のみを見つけました: http://www.ibm.com/developerworks/webservices/library/ws-tip-objrel3/index.html

作成者の実装によると、JobコンストラクターにCustomerパラメーターを取得させ、それを格納して取得できるようにします。ただし、このモデルが一貫性であるという保証はまったくありません。ある仕事に関連する顧客をその仕事ではない顧客として設定し、他の誰かのために行われた顧客に仕事を追加するという制限はありません。これに関する助けをいただければ幸いです。

11
MarioDS

整合性を維持するための100%確実な方法はありません。

通常採用されるアプローチは、1つの方法を使用して関係を構築し、同じ方法で他の方向を構築することです。しかし、あなたが言うように、これは誰もがそれをいじるのを妨げるものではありません。

次のステップは、いくつかのメソッドをパッケージアクセス可能にして、少なくともあなたのコードとは関係のないコードがそれを壊さないようにすることです。

class Parent {

  private Collection<Child> children;

  //note the default accessibility modifiers
  void addChild(Child) {
    children.add(child);
  }

  void removeChild(Child) {
    children.remove(child);
  }
}

class Child {

   private Parent parent;
   public void setParent(Parent parent){
     if (this.parent != null)
       this.parent.removeChild(this);
     this.parent = parent;
     this.parent.addChild(this);
   }
}

実際には、クラスでこの関係をモデル化することはあまりありません。代わりに、ある種のリポジトリで親のすべての子を検索します。

9
Joeri Hendrickx

複雑な(そしてゼロコードの)答えを期待していなかったかもしれませんが、防爆APIを意図したとおりに構築するソリューションはありません。そして、それはパラダイム(OO)やプラットフォーム(Java)によるものではなく、間違った分析を行ったためです。トランザクションの世界(実際の問​​題とその時間の経過に伴う進化をモデル化するすべてのシステムトランザクション)このコード壊れることはありませんある時点で:

// create
Job j1 = ...
Job j2 = ...
...
// modify
j1.doThis();
...

// access
j2.setSomeProperty(j1.someProperty);

なぜならその時j1.somePropertyにアクセスし、j1およびj2存在すらできなかった:)

TL; DR

これに対する長い答えは不変性であり、ライフサイクルおよびトランザクションの概念も紹介します。他のすべての答えはあなたにそれを行う方法を教えます、代わりに私はなぜを概説したいと思います。 1対多の関係には2つの側面があります

  1. 多くを持っています
  2. 属する

顧客AにジョブBがあり、ジョブBが顧客Aに属している限り、システムは一貫しています。これはさまざまな方法で実装できますが、これはトランザクションで発生する必要があります。つまり、単純なアクションで構成される複雑なアクションであり、システムはトランザクションの実行が終了するまで使用できません。これは抽象的すぎてあなたの質問とは無関係に見えますか?いいえ、そうではありません:)トランザクションシステムは、これらすべてのオブジェクトが有効な状態にある場合にのみ、したがってシステム全体が一貫している場合にのみ、クライアントがシステムのオブジェクトにアクセスできることを保証します。他の回答から、問題を解決するために必要な処理の量がわかりますsomeので、保証にはコストがかかります:パフォーマンス。これは、Java(およびその他の汎用OO言語))が問題をすぐに解決できない理由の簡単な説明です。

もちろん、OO言語は、トランザクションワールドのモデル化とアクセスの両方に使用できますが、特別な注意が必要です。いくつかの制約が必要です。クライアント開発者には特別なプログラミングスタイルが必要です。通常、トランザクションシステムは2つのコマンドを提供します:検索(別名クエリ)とロック。クエリの結果は- 不変:それは撮影された特定の瞬間のシステムの写真(つまりコピー)であり、写真を変更しても現実の世界には明らかに影響しません。システムを変更するにはどうすればよいですか?通常は

  1. 必要に応じて、システム(またはその一部)をロックします
  2. オブジェクトを見つける:ローカルで読み書きできる実際のオブジェクトのコピー(写真)を返します
  3. ローカルコピーを変更する
  4. 変更されたオブジェクトをコミットします。つまり、提供された入力に基づいてシステムに状態を​​更新させます。
  5. (現在は役に立たない)ローカルオブジェクトへの参照を破棄します。システムが変更されたため、ローカルコピーが最新ではありません。

(ところで、ライフサイクルの概念がローカルオブジェクトとリモートオブジェクトにどのように適用されているかわかりますか?)

Sets、final修飾子などを使用できますが、トランザクションと不変性を導入するまで、設計に欠陥があります。通常、Javaアプリケーションは、トランザクション機能を提供するデータベースによってサポートされており、多くの場合、DBはORM(Hibernateなど)と結合されてオブジェクト指向コードを記述します。

4
Raffaele

他のデータ構造を使用する代わりに、HashSetのようなSet実装を使用することで、重複がないことを確認できます。そして、Jobを顧客に追加する代わりに、プライベートコンストラクターを持つJobクラスに最終的な内部クラスを作成します。これにより、ラッパーの内部クラスはジョブオブジェクトによってのみ作成できるようになります。 JobコンストラクターにjobIDとcustomerをパラメーターとして取り入れさせます。一貫性を維持するために-顧客がNullの場合、ダミージョブは作成されるべきではないため、例外をスローします。

Customerのaddメソッドで、JobUnitでラップされたJobが自身のIDと同じ顧客IDを持っているかどうかを確認します(スローされない場合)例外。

Jobクラスの顧客を置き換える場合は、Customerクラスで提供されるメソッドを使用してJobUnitを削除し、それ自体を新しい顧客に追加して、顧客参照を新しく渡された顧客に変更します。そうすれば、コードをより適切に推論できます。

顧客クラスは次のようになります。

public class Customer {

    Set<JobUnit> jobs=new HashSet<JobUnit>();    
    private Long id;
    public Customer(Long id){        
        this.id = id;
    }

    public boolean add(JobUnit unit) throws Exception{
       if(!unit.get().getCustomer().id.equals(id))
           throw new Exception(" cannot assign job to this customer");
        return jobs.add(unit);
    }

     public boolean remove(JobUnit unit){
        return jobs.remove(unit);
    }

    public Long getId() {
        return id;
    }

}

そしてジョブクラス:

public class Job {
Customer customer;
private Long id;

最終的なJobUnitユニット。

public Job(Long id,Customer customer) throws Exception{
    if(customer==null)
        throw new Exception("Customer cannot be null");
    this.customer = customer; 
   unit= new JobUnit(this);       
    this.customer.add(unit);
}

public void replace(Customer c) throws Exception{      
    this.customer.remove(unit);
    c.add(unit);
    this.customer=c;
}

public Customer getCustomer(){
    return customer;
}

/**
 * @return the id
 */
public Long getId() {
    return id;
}

public final class JobUnit{
    private final Job j;


    private JobUnit(Job j){
        this.j = j;

    }
    public Job get(){
        return j;
    }
 }
}

しかし、私が興味を持っていることの1つは、なぜ顧客オブジェクトにジョブを追加する必要があるのか​​ということです。どの顧客がどのジョブに割り当てられているかを確認するだけの場合は、ジョブを調べるだけでその情報が得られます。一般的に、やむを得ない場合を除いて、循環参照を作成しないようにしています。また、作成後にジョブから顧客を置き換える必要がない場合は、JobクラスのcustomerフィールドFinalを作成し、メソッドをsetまたはに削除します。 replaceそれ。

顧客をジョブに割り当てるための制限はデータベースで維持する必要があり、データベースエントリをチェックポイントとして使用する必要があります。他の人のために行われた顧客へのジョブの追加については、ジョブ内の顧客参照をチェックして、ジョブが追加されている顧客が保持している顧客と同じか、それ以上であることを確認できます-単にJobの顧客の参照を削除すると、作業が簡素化されます

2
anzaan

Customerオブジェクトがリレーションシップを所有している場合は、次のようにすることができます。

Job job = new Job();
job.setStuff(...);
customer.addJob(Job job) {
    this.jobs.add(job);
    job.setCustomer(this); //set/overwrite the customer for this job
}

//in the job class
public void setCustomer(Customer c) {
    if (this.customer==null) {
        this.customer = c;
    } // for the else{} you could throw a runtime exception
}

...所有権が逆の場合は、顧客を仕事に置き換えてください。

アイデアは、関係の所有者に一貫性を維持させることです。双方向の関係は、一般に、一貫性管理が両方のエンティティにあることを意味します。

1
Ryan Fernandes

一貫性を維持する適切なセッター関数を作成します。たとえば、ジョブを作成するときはいつでも、コンストラクターで顧客を提供します。次に、ジョブコンストラクターは、顧客のジョブのリストに自分自身を追加します。または、顧客にジョブを追加するたびに、追加機能は、ジョブの顧客が追加先の顧客であることを確認する必要があります。または、これとあなたのニーズに合ったものと同様のもののいくつかの組み合わせ。

0
Matsemann