優れた実践における乾燥の原則?


11

私は、できる限り一生懸命プログラミングのDRY原則に従うようにしています。最近、私はOOPでデザインパターンを学んでいますが、結局、かなりの量を繰り返しています。

永続性を処理するために、FactoryパターンとGatewayパターンとともにRepositoryパターンを作成しました。私はアプリケーションでデータベースを使用していますが、ゲートウェイを交換して、必要に応じて別の種類の永続性に切り替えることができるはずなので、それは問題ではありません。

最終的に自分で作成した問題は、所有しているテーブルの数に対して同じオブジェクトを作成することです。たとえば、これらはテーブルを処理する必要があるオブジェクトになりますcomments

class Comment extends Model {

    protected $id;
    protected $author;
    protected $text;
    protected $date;
}

class CommentFactory implements iFactory {

    public function createFrom(array $data) {
        return new Comment($data);
    }
}

class CommentGateway implements iGateway {

    protected $db;

    public function __construct(\Database $db) {
        $this->db = $db;
    }

    public function persist($data) {

        if(isset($data['id'])) {
            $sql = 'UPDATE comments SET author = ?, text = ?, date = ? WHERE id = ?';
            $this->db->prepare($sql)->execute($data['author'], $data['text'], $data['date'], $data['id']);
        } else {
            $sql = 'INSERT INTO comments (author, text, date) VALUES (?, ?, ?)';
            $this->db->prepare($sql)->execute($data['author'], $data['text'], $data['date']);
        }
    }

    public function retrieve($id) {

        $sql = 'SELECT * FROM comments WHERE id = ?';
        return $this->db->prepare($sql)->execute($id)->fetch();
    }

    public function delete($id) {

        $sql = 'DELETE FROM comments WHERE id = ?';
        return $this->db->prepare($sql)->execute($id)->fetch();
    }
}

class CommentRepository {

    protected $gateway;
    protected $factory;

    public function __construct(iFactory $f, iGateway $g) {
        $this->gateway = $g;
        $this->factory = $f;
    }

    public function get($id) {

        $data = $this->gateway->retrieve($id);
        return $this->factory->createFrom($data);
    }

    public function add(Comment $comment) {

        $data = $comment->toArray();
        return $this->gateway->persist($data);
    }
}

それから私のコントローラーは

class Comment {

    public function view($id) {

        $gateway = new CommentGateway(Database::connection());
        $factory = new CommentFactory();
        $repo = new CommentRepository($factory, $gateway);

        return Response::view('comment/view', $repo->get($id));
    }
}

だから、私はデザインパターンを正しく使用し、良い慣行を守っていると思ったが、このことの問題は、新しいテーブルを追加するときに、他の名前だけで同じクラスを作成しなければならないことだ。これは、私が何か間違ったことをしているのではないかという疑念を引き起こします。

インターフェイスの代わりに、クラス名を使用して操作する必要があるテーブルを把握する抽象クラスがありますが、それは正しいことではないようです、ファイルストレージに切り替えるか、テーブルがないmemcache。

私はこれに正しく近づいていますか、または私が見なければならない別の視点がありますか?


新しいテーブルを作成するとき、常に同じ一連のSQLクエリ(または非常に類似したセット)を使用して対話しますか?また、ファクトリーは実際のプログラムに意味のあるロジックをカプセル化していますか?
Ixrec

@Ixrecは通常、結合のようなより複雑なsqlクエリを実行するゲートウェイとリポジトリにカスタムメソッドがあります。問題は、インターフェイスで定義された永続化、取得、削除機能がテーブル名を除いて常に同じであることです。しかし、主キー列とは考えにくいため、すべての実装でこれらを繰り返す必要があります。ファクトリーがロジックを保持することは非常にまれで、時にはスキップして、ゲートウェイがデータの代わりにオブジェクトを返すようにすることもありますが、適切な設計である必要があるため、この例のファクトリーを作成しましたか?
エミリオロドリゲス

私はおそらく適切な答えを出す資格がありませんが、1)FactoryクラスとRepositoryクラスは実際には何も役に立たないという印象を受けますので、それらを捨ててCommentとCommentGatewayだけで直接作業する方が良いでしょう2)一般的な永続化/取得/削除機能を、おそらく「デフォルト実装」の抽象クラス(Javaのコレクションが行うようなもの)のコピークラスではなく、単一の場所に置くことができるはずです
Ixrec

回答:


12

あなたが取り組んでいる問題は非常に基本的です。

数百のWebページと100万行以上のJavaコードで構成される大規模なJ2EEアプリケーションを作成した会社で働いていたときに、同じ問題を経験しました。このコードは永続化のためにORM(JPA)を使用しました。

この問題は、アーキテクチャのすべてのレイヤーでサードパーティのテクノロジーを使用している場合に悪化し、テクノロジーはすべて独自のデータ表現を必要とします。

使用しているプログラミング言語のレベルでは問題を解決できません。パターンを使用するのは良いことですが、ご覧のとおり、コードの繰り返しを引き起こします(より正確には、デザインの繰り返し)。

私の考えでは、考えられる解決策は3つしかありません。実際には、これらのソリューションは同じものになります。

解決策1:永続化する必要があるものだけを記述できる他の永続化フレームワークを使用します。おそらくそのようなフレームワークがあります。このアプローチの問題は、パターンがすべて永続性に関連するとは限らないため、かなり単純だということです。また、ユーザーインターフェイスコードのパターンを使用するため、選択した永続化フレームワークのデータ表現を再利用できるGUIフレームワークが必要になります。それらを再利用できない場合は、GUIフレームワークと永続化フレームワークのデータ表現を橋渡しするボイラープレートコードを記述する必要があります。これはDRYの原則に反しています。

解決策2:設計コードを再利用できるように、反復設計を表現できる構造を持つ、より強力な別のプログラミング言語を使用します。これはおそらくあなたのためのオプションではありませんが、しばらくの間はそうだと思います。再度、永続化レイヤーの上にユーザーインターフェイスの作成を開始すると、ボイラープレートコードを作成せずにGUIの作成をサポートするのに十分に強力な言語が再び必要になります。ほとんどの言語は、それぞれが独自のデータ表現を必要とするGUI構築のためにサードパーティのフレームワークに依存しているため、希望することを実行できるほど強力な言語があるとは考えられません。

解決策3:何らかの形式のコード生成を使用して、コードと設計の繰り返しを自動化します。繰り返しコード/デザインを手作業でコーディングするとDRYの原則に違反するため、パターンとデザインの繰り返しを手作業でコーディングする必要があります。現在、非常に強力なコード生成フレームワークが世に出ています。独自のプログラミング言語を作成し(経験のない半日)、その言語を使用してコード(PHP / Java / SQL-考えられるテキストファイル)を生成できる「言語ワークベンチ」もあります。私はXTextの経験がありますが、MetaEditとMPSも同様にうまくいくようです。これらの言語ワークベンチのいずれかを確認することを強くお勧めします。私にとって、それは私の職業人生で最も解放的な経験でした。

Xtextを使用すると、マシンに反復コードを生成させることができます。Xtextは、独自の言語仕様のコード補完機能を備えた構文強調エディターも生成します。その時点から、ゲートウェイとファクトリクラスを取得し、それらに穴を開けてコードテンプレートに変換します。それらをジェネレーター(Xtextによって完全に生成される言語のパーサーによって呼び出されます)にフィードすると、ジェネレーターがテンプレートの穴を埋めます。結果は生成されたコードです。その時点から、どこでもコードの繰り返しを取り出すことができます(GUIコード永続化コードなど)。


返信いただきありがとうございます。コード生成を真剣に検討しており、ソリューションの実装を始めています。これらは4つの定型クラスなので、PHP自体でそれを行うことができると思います。これはコードの繰り返しの問題を解決するものではありませんが、トレードオフはそれだけの価値があると思います-繰り返しコードであってもメンテナンス性が高く、簡単に変更できます。
エミリオロドリゲス

XTextを聞いたのはこれが初めてで、非常に強力に見えます。これを知ってくれてありがとう!
マシュージェームスブリッグス

8

直面している問題は古いものです。永続オブジェクトのコードは、各クラスでよく似ていることが多く、単なる定型コードです。それが、一部の賢い人々がオブジェクトリレーショナルマッパーを発明した理由です。彼らはまさにその問題を解決します。PHPのORMのリストについては、この以前のSO投稿を参照してください。

既存のORMがニーズに合わない場合は、別の方法もあります。独自のコードジェネレーターを作成して、オブジェクトのメタ記述を保持し、そこからコードの繰り返し部分を生成できます。これは実際にはそれほど難しくありません。過去にいくつかの異なるプログラミング言語でこれを行ったことがあります。そのようなことをPHPでも実装することもできると確信しています。


このような機能を作成しましたが、SRPに準拠していないデータ永続化タスクをデータオブジェクトに処理させるため、これからこれに切り替えました。たとえば、以前はModel::getByPKメソッドを持っていましたが、上記の例では実行できましたComment::getByPKが、データベースからデータを取得してオブジェクトを構築することはすべてデータオブジェクトクラスに含まれており、これが設計パターンを使用して解決しようとしている問題です。
エミリオロドリゲス

ORMは、永続化ロジックをモデルオブジェクトに配置する必要はありません。これはActive Recordパターンであり、一般的ですが、代替手段もあります。利用可能なORMを見てください。この問題がないものを見つける必要があります。
ジュール

@Julesは非常に良い点です、それは私に考えさせられ、私は疑問に思っていました-ActiveRecordとData Mapper実装の両方を私のアプリケーションで利用可能にすることの問題は何でしょうか。その後、必要なときにそれらのそれぞれを使用することができます-これにより、ActiveRecordパターンを使用して同じコードを書き換えるという問題が解決され、実際にデータマッパーが必要なときに必要なクラスを作成するのはそれほど苦労しません仕事で?
エミリオロドリゲス

1
私が今これで見ることができる唯一の問題は、クエリが2つのテーブルを結合する必要がある場合のエッジケースを解決することです。起きない 個人的には、マッパーを使用するだけです-最初からActive Recordが好きではありませんでしたが、それは私の意見であり、他の人は同意しないことを知っています。
ジュール
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.