web-dev-qa-db-ja.com

OOPオブジェクト、ネストされたオブジェクト、およびDAO

ここに私が最善の解決策を見つけるのに苦労しているものがあります。 PHPおよびJavaで作業しているときにこの問題が発生したため、OOP問題の基本的な理解です。例は次のとおりです。 PHPで。

ここにいくつかのオブジェクトがあるとします。曲、アーティスト、ArtistProfile、ユーザー。

したがって、アーティストを呼び出すときにArtistProfileとユーザーオブジェクト(サブスクライバー)の配列が必要な場合があります(アーティストのプロファイルページなど)。曲のページを表示するときなど、アーティスト情報のみが必要な場合もあります。

あるオブジェクトを別のオブジェクトの一部としてネストする必要があるのか​​、それとも、用途に応じてより具体的なオブジェクトを作成する必要があるのか​​。

オプション1:ネスト

Class Song {
   private $songId;
   private $songName;
   private $year;
   private $Artist; //Artist object
}

Class Artist {
   private $artistId;
   private $name;
   private $age;
   private $subscriberArr; //Array of User objects which then have more nested objects such as a Role object, Profile object
   private $profile; //Profile object which could also have more nested objects
}

Class User {
   private $userId;
   private $name;
   private $age;
   private $role; //Role object
   private $profile; //UserProfile object
}

オプション2:オブジェクトをさらに作成する

Class Song {
   private $songId;
   private $songName;
   private $year;
   private $artistId;
}

Class Artist {
   private $artistId;
   private $age;
   private $name;
}

Class User {
   private $userId;
   private $name;
   private $age;
   private $roleId;
}

Class SongWithArtist {
   private $song; //Basic Song object
   private $artist; //Basic Artist object
}

Class ArtistWithProfile {
   private $artist; //Basic artist object
   private $profile; //Profile object
   private $subscriberArr; //UserDisplay object containing basic User object
}

 Class UserWithProfile {}

オプション1は、そのページに必要ではないが管理が容易な情報を取得するために多くの時間とリソースを浪費することを意味します。オプション2は面倒で、どのオブジェクトが何であるかを追跡する必要がありますが、db呼び出しは高速ではるかに少なくなります。 「正しい」オプションおよび/または3番目の正しいオプションはどれですか?

4
user103555

あるオブジェクトを別のオブジェクトの一部としてネストする必要があるのか​​、それとも、用途に応じてより具体的なオブジェクトを作成する必要があるのか​​。

  1. OO原則は、最初に「より具体的なオブジェクト」を意味します。そうすることで、クラスがデータベーススキーマと「一致」する可能性がありますが、DBスキーマがうまく機能しない場合がありますOOデザイン。

  2. 単一の責任の原則 は、どのクラスを構築するかをガイドするのに役立ちます。たとえば、SRPはSongが曲であることを意味します。これはアーティストではなく、サブスクライバーのリストでもないため、アーティストやサブスクライバーの要素を含めることはできません。曲だけ。

  3. 上記は、多くの小さく、独立した、完全に機能的なもの-クラスがあることを意味します。 「完全に機能する」とは、Songnamedate、およびidである場合、それがその中にあることを意味します。限目。あるアーティストがその歌を歌うという事実は、本質的にSongを定義するものではありません。これは、たとえば「アーティストの曲のレパートリー」などの関係をモデル化する他のクラスを意味します。

  4. 小さな関数クラスは、ユーザーインターフェイスに優れたDAOと柔軟性をもたらします。

オプション1は、そのページに必要ではないが管理が容易な情報を取得するために多くの時間とリソースを浪費することを意味します。オプション2は面倒で、どのオブジェクトが何であるかを追跡する必要がありますが、db呼び出しは高速ではるかに少ないです

  1. 時期尚早の最適化の犠牲になっています。オプション2の「DB呼び出しが少ない」ことをどのようにして前もって知ることができますか?とにかくそれはどういう意味ですか?

  2. これは、ドメインクラス/モデルについて考えるのに間違った方法です。これが、DBスキーマを複製することになる理由です。

Class SongWithArtist {
  private $song; //Basic Song object
  private $artist; //Basic Artist object
}

あなたが持っているべきものは現実の世界を説明するものであるとき:

Class ArtistPortfolio {
    private Artist $theArtist;
    private List<Song> $portfolio;  // list of songs (s)he sings 
}

Class ArtistSubscribers {
    private Artist $theArtist;
    private List<User> $subscribers;  // list of people who like this artist
}

// And it would probably make sense to combine the above 2 classes:

Class ArtistProfile {
    // an Artist object, not just an id. We're doing OBJECT oriented programming.
    private Artist $theArtist;

    private List<Song> $portfolio;  // list of Song objects 
    private List<User> $subscribers; // list of User objects
}

// and if you need a list of profiles...
Class ArtistProfiles {
    private List<ArtistProfile> $profiles; // a list of type ArtistProfile

    public ArtistProfile GetProfileByArtist (Artist thisArtist){}
    public ArtistProfile GetProfileByName (string name) {}
    public ArtistProfile GetProfileById (string id) {}
}

// I'd say a ArtistProfile could be part of an Artist..

Class Artist {
    private $id;
    private $name;
    private ArtistProfile $profile; // this is composition.
}


//In lieu of the above, an DAO oriented Artist composition ...
// Just go with the refactoring flow!
Class Profile {
    private $id;
    private $name;
    private $birthdate
}

 Class Repertoire {
    // a Profile object, not just an id. We're doing OBJECT oriented programming.
    private Profile $theArtist;

    private List<Song> $portfolio;  // list of Song objects 
    private List<User> $subscribers; // list of User objects
}

Class Artist {
    private Profile $me;
    private Repertoire $myStuff; 
}

DB呼び出しが多すぎます!

ない。 $myStuffにデータを入力せずにArtistオブジェクトをインスタンス化し、$subscribers/portfolioリストの入力を必要になるまで延期できます。これは遅延読み込みと呼ばれます

3
radarbob

プログラミング全般でやりたいことは、データの重複を避けることだと思います。

リレーショナルデータベースを設計する場合、4種類の関係があります。

  • 一対一
  • 1対多
  • 多対一
  • 多対多

多対多の関係の場合、データベースに「リンクテーブル」があり、通常、相互にリンクされている2つのレコードの2つのIDしかありません。

2つのリンクされたレコードIDに加えて、リンクテーブルには、多対多のリンクテーブルを持つ2つのデータベーステーブルの間に他のデータを関連付けることができます。

例えば、

class DBRecord {
    private $recordID; // a BIGINT, auto-incrementing field, the primary key
}

class Product extends DBRecord {
    private $productID; // an ID that the user can set
    private $productName;
    private $suggestedRetail;
    ...
}

class Vendor extends DBRecord {
   private $vendorID; // an ID that the user can set
   private $vendorName;
   private $contactID;
   ...
}

class ProductVendors extends DBRecord {
   private $productRecordID; // this ID never changes
   private $vendorRecordID;  // this ID never changes
   private $wholesaleCost;   // the cost of a product can be different by vendor
}

この例では、クラスのオブジェクトであるProduct、Vendor、およびProductVendersは、データベース内のレコードによく似ています。データベーステーブル全体を配列に読み込むことができます。

$products = readAll();
$vendors = readAll();
$productVendors = readAll();

レコード数のデータセットのサイズによっては、それらをすべてRAMに読み込まない方がよい場合があります。

レコードが多すぎてすべてをRAMに読み込むことができない場合は、オブジェクト間の関係を設定して、リレーショナルデータベース内のデータベーステーブル間の関係をミラーリングできます。

// database code is encapsulated in this class
class DB {
   ...
}

// this class implements functionality common to all subclasses
abstract class DBTable {
   private $db;
   private $internalName; // the SQL name for this database table
   private $displayName;  // the name that the user sees for this db table
   public lookup($productID) {
       ...
   }
}

class Products extends DBTable {
   ...
}

class Vendors extends DBTable {
    ...
}

class ProductVendors extends DBTable {
   ...
}

これが私が多対多の関係を作成した方法であり、100,000の製品レコードを持つような非常に大規模なデータセットでも機能します。

  • 1対多と多対1は、お互いの裏側にすぎません

    class PurchaseOrderLineItem extends DBRecord {
    }
    
    class PurchaseOrder extends DBRecord {
        private $lineItems;
        public loadLineItems() {
           ...
        }
        public saveLineItems() {
           ...
        }
    }
    

注文書の1つの品目は1つの注文書ドキュメントにのみ含めることができますが、1つの注文書ドキュメントには多数の行アイテムを含めることができます。これは通常、膨大な数のラインアイテムではないので、それらをすべてRAMに配列にロードします。

  • 1対1の関係

    class Product extends DBRecord {
        private $itemRecordID;
        private $inventoryOnHand;
        ...
    }
    
    class InventoryCount extends DBRecord {
        private $itemRecordID;
        private $inventoryCounted;
    }
    

これらの2つのレコードは1対1の関係です。ストア内のすべての在庫をカウントする場合、各製品には1つのカウントのみがあり、各カウントには1つの製品しかありません。

つまり、これらは4つのタイプの関係です。最も一般的な解決策はリレーショナルデータベースをミラーリングすることだと思いますが、ラインアイテムを「含む」注文書の場合など、常にそうするわけではありません。

マルチユーザーの問題に注意する 有名な失われた更新の問題。

データを複製しないでください 「通常の形式」を参照してください。

生年月日を保存する-年齢ではない 一般的に、年齢ではなく生年月日を保存し、年齢を計算して返す「getAge()」と呼ばれるアクセサを用意するのが最善です。

1
Kaydell