Desain pola perintah

11

Saya memiliki implementasi pola Command yang lama ini. Ini semacam mengeluarkan Konteks melalui semua implementasi DIOperasi , tetapi saya sadari kemudian, dalam proses belajar dan belajar (yang tidak pernah berhenti), itu tidak optimal. Saya juga berpikir bahwa "mengunjungi" di sini tidak benar-benar cocok dan hanya membingungkan.

Saya sebenarnya berpikir untuk mengembalikan kode saya, juga karena Perintah seharusnya tidak tahu apa-apa tentang yang lain dan saat ini mereka semua berbagi pasangan nilai kunci yang sama. Sangat sulit untuk mempertahankan kelas mana yang memiliki nilai kunci apa, kadang-kadang mengarah ke variabel duplikat.

Contoh use case: katakanlah CommandB membutuhkan UserName yang ditetapkan oleh CommandA . Haruskah CommandA mengatur kunci UserNameForCommandB = John ? Atau haruskah mereka berbagi UserName = John -nilai kunci yang umum? Bagaimana jika UserName digunakan oleh Perintah ketiga?

Bagaimana saya bisa meningkatkan desain ini? Terima kasih!

class DIParameters {
public:
   /**
    * Parameter setter.
    */
    virtual void setParameter(std::string key, std::string value) = 0;
    /**
    * Parameter getter.
    */
    virtual std::string getParameter(std::string key) const = 0;

    virtual ~DIParameters() = 0;
};

class DIOperation {
public:
    /**
     * Visit before performing execution.
     */
    virtual void visitBefore(DIParameters& visitee) = 0;
    /**
     * Perform.
     */
    virtual int perform() = 0;
    /**
     * Visit after performing execution.
     */
    virtual void visitAfter(DIParameters& visitee) = 0;

    virtual ~DIOperation() = 0;
};
Andrea Richiardi
sumber
3
Saya tidak pernah beruntung menggunakan perintah untuk mengatur properti (seperti nama). Itu mulai menjadi sangat tergantung. Jika properti pengaturan Anda coba gunakan arsitektur acara atau pola pengamat.
ahenderson
1
1. Mengapa melewati parameter melalui pengunjung terpisah? Apa yang salah dengan melewatkan konteks sebagai argumen kinerja? 2. Konteks adalah untuk bagian 'umum' dari perintah (mis. Sesi / dokumen saat ini). Semua parameter khusus operasi lebih baik melewati konstruktor operasi.
Kris Van Bael
@KrisVanBael itu bagian yang membingungkan yang saya coba ubah. Saya menyampaikannya sebagai Pengunjung sementara itu sebenarnya adalah Konteks ...
Andrea Richiardi
@ahenderson Apakah maksud Anda peristiwa di antara perintah saya benar? Apakah Anda akan memasukkan nilai kunci di sana (mirip dengan apa yang dilakukan Android dengan Parcel)? Apakah akan sama dalam arti bahwa CommandA harus membangun suatu Acara dengan pasangan nilai kunci yang diterima CommandB?
Andrea Richiardi

Jawaban:

2

Saya agak khawatir tentang berubahnya parameter perintah Anda. Apakah benar-benar perlu membuat perintah dengan parameter yang terus berubah?

Masalah dengan pendekatan Anda:

Apakah Anda ingin utas / perintah lain mengubah parameter Anda saat performsedang berlangsung?

Apakah Anda ingin visitBeforedan objek yang visitAftersama Commanddipanggil dengan DIParameterobjek yang berbeda ?

Apakah Anda ingin seseorang memberi makan parameter ke perintah Anda, yang tidak diketahui perintahnya?

Semua ini dilarang oleh desain Anda saat ini. Sementara konsep parameter kunci-nilai umum memiliki kelebihan di kali, saya tidak suka sehubungan dengan kelas perintah generik.

Contoh konsekuensi:

Pertimbangkan realisasi konkret Commandkelas Anda - sesuatu seperti CreateUserCommand. Sekarang jelas, ketika Anda meminta pengguna baru untuk dibuat, perintah akan membutuhkan nama untuk pengguna itu. Mengingat saya tahu kelas CreateUserCommand- DIParameterskelasnya, parameter mana yang harus saya atur?

Saya dapat mengatur userNameparameter, atau username.. apakah Anda memperlakukan parameter case dengan tidak sensitif? Saya tidak akan tahu benar .. oh, tunggu .. mungkin hanya saja name?

Seperti yang Anda lihat kebebasan yang Anda peroleh dari pemetaan nilai kunci generik menyiratkan bahwa menggunakan kelas Anda sebagai seseorang yang tidak menerapkannya tidak sulit tanpa alasan. Anda setidaknya perlu memberikan beberapa konstanta untuk perintah Anda agar orang lain tahu kunci mana yang didukung oleh perintah itu.

Kemungkinan pendekatan desain yang berbeda:

  • Parameter yang tidak dapat diubah: Dengan memutar Parameterinstance Anda yang tidak dapat diubah, Anda dapat dengan bebas menggunakannya kembali di antara berbagai perintah.
  • Kelas parameter khusus: Diberikan UserParameterkelas yang berisi persis parameter yang saya perlukan untuk perintah yang melibatkan pengguna, akan jauh lebih mudah untuk bekerja dengan API ini. Anda masih dapat memiliki pewarisan pada parameter, tetapi tidak masuk akal lagi bagi kelas perintah untuk mengambil parameter sewenang-wenang - di sisi pro tentu saja ini berarti bahwa pengguna API mengetahui parameter mana yang diperlukan secara tepat.
  • Satu contoh perintah per konteks: Jika Anda memerlukan perintah untuk memiliki hal-hal seperti visitBeforedan visitAfter, sementara juga menggunakannya kembali dengan parameter yang berbeda, Anda akan terbuka untuk masalah dipanggil dengan parameter yang berbeda. Jika parameternya harus sama pada beberapa pemanggilan metode, Anda perlu merangkumnya ke dalam perintah sedemikian rupa sehingga tidak dapat dialihkan untuk parameter lain di sela-sela panggilan.
jujur
sumber
Ya, saya menyingkirkan kunjunganSebelum dan kunjungiSetelah. Saya pada dasarnya melewati antarmuka DIParameter saya dalam metode perform. Masalah dengan instance DIParamters yang tidak diinginkan selalu akan ada di sana, karena saya memilih untuk memiliki fleksibilitas melewati antarmuka. Saya benar-benar menyukai gagasan untuk dapat membuat subkelas dan membuat anak-anak DIParameter tidak dapat diubah begitu mereka diisi. Namun, "otoritas pusat" masih harus meneruskan DIParameter yang benar ke Komando. Ini mungkin mengapa saya mulai menerapkan pola Pengunjung .. Saya ingin memiliki inversi kontrol dalam beberapa cara ...
Andrea Richiardi
0

Apa yang baik tentang prinsip-prinsip desain adalah bahwa cepat atau lambat, mereka bertentangan satu sama lain.

Dalam situasi yang dijelaskan, saya pikir saya lebih suka pergi dengan semacam konteks di mana setiap perintah dapat mengambil informasi dari dan memasukkan informasi ke (terutama jika mereka adalah pasangan kunci-nilai). Ini didasarkan pada trade off: Saya tidak ingin perintah terpisah digabungkan hanya karena mereka adalah semacam input satu sama lain. Di dalam CommandB, saya tidak peduli bagaimana UserName diset - hanya karena itu ada untuk saya gunakan. Hal yang sama di CommandA: Saya mengatur informasinya, saya tidak ingin tahu apa yang dilakukan orang lain dengannya - juga siapa mereka.

Ini berarti semacam konteks lewat, yang menurut Anda buruk. Bagi saya, alternatifnya lebih buruk, terutama jika konteks kunci-nilai sederhana ini (bisa menjadi kacang sederhana dengan getter dan setter, untuk membatasi sedikit faktor "bentuk bebas") dapat memungkinkan solusi menjadi sederhana dan dapat diuji, dengan baik perintah terpisah, masing-masing dengan logika bisnisnya sendiri.

Martin
sumber
1
Prinsip apa yang menurut Anda bertentangan di sini?
Jimmy Hoffa
Hanya untuk memperjelas, masalah saya bukan memilih antara Konteks atau pola Pengunjung. Saya pada dasarnya menggunakan pola Konteks yang disebut Pengunjung :)
Andrea Richiardi
Ok, saya mungkin salah paham tentang pertanyaan / masalah Anda.
Martin
0

Mari kita asumsikan Anda memiliki antarmuka perintah:

class Command {
public:
    void execute() = 0;
};

Dan subjek:

class Subject {
    std::string name;
public:
    void setName(const std::string& name) {this->name = name;}
}

Yang Anda butuhkan adalah:

class NameObserver {
public:
    void update(const std::string& name) = 0;
};

class Subject {
    NameObserver& o;
    std::string name;
private:
    void setName(const std::string& name) {
        this->name = name;
        o.update(name);
    }
};

class CommandB: public Command, public NameObserver {
    std::string name;
public:
    void execute();
    void update(const std::string& name) {
        this->name = name;
        execute();
    }
};

Tetapkan NameObserver& osebagai referensi ke CommandB. Sekarang setiap kali CommandA mengubah nama Subjek, CommandB dapat mengeksekusi dengan informasi yang benar. Jika nama digunakan oleh lebih banyak perintah, gunakan astd::list<NameObserver>

ahenderson
sumber
Terima kasih atas jawabannya. Masalah dengan imho desain ini adalah bahwa kita memerlukan Setter + NameObserver per setiap parameter. Saya dapat melewatkan instance DIParameters (konteks) dan memberi tahu, tetapi sekali lagi, saya mungkin tidak akan menyelesaikan fakta bahwa saya masih menggabungkan CommandA dengan CommandB, yang berarti CommandA harus meletakkan nilai kunci yang hanya perlu diketahui oleh CommandB ... apa yang saya coba adalah juga memiliki entitas eksternal (ParameterHandler) yang merupakan satu-satunya yang tahu Perintah mana yang memerlukan parameter dan set / dapatkan sesuai pada instance DIParameters.
Andrea Richiardi
@ Kap "Masalah dengan desain imho ini adalah bahwa kita memerlukan Setter + NameObserver per setiap parameter" - parameter dalam konteks ini agak membingungkan bagi saya, saya pikir Anda maksud bidang. Dalam hal ini Anda harus sudah memiliki setter untuk setiap bidang yang berubah. Dari contoh Anda, sepertinya ComamndA mengubah nama Subjek. Itu harus mengubah bidang melalui setter. Catatan: Anda tidak perlu pengamat per bidang, hanya memiliki pengambil dan meneruskan objek ke semua pengamat.
ahenderson
0

Saya tidak tahu apakah ini cara yang tepat untuk menangani ini pada Programer (dalam hal ini saya minta maaf), tetapi, setelah memeriksa semua jawaban di sini (@ Frank khususnya). Saya refactored kode saya dengan cara ini:

  • DIParameter jatuh. Saya akan memiliki objek individual (generik) sebagai input DIOperation (tidak berubah). Contoh:
class RelatedObjectTriplet {
pribadi:
    std :: string const m_sPrimaryObjectId;
    std :: string const m_sSecondaryObjectId;
    std :: string const m_sRelationObjectId;

    RelatedObjectTriplet & operator = (OtherObjectTriplet lainnya);

publik:
    RelatedObjectTriplet (std :: string const & sPrimaryObjectId,
                         std :: string const & sSecondaryObjectId,
                         std :: string const & sRelationObjectId);

    RelatedObjectTriplet (RelatedObjectTriplet const & other);


    std :: string const & getPrimaryObjectId () const;
    std :: string const & getSecondaryObjectId () const;
    std :: string const & getRelationObjectId () const;

    ~ RelatedObjectTriplet ();
};
  • Kelas DIOperasi baru (dengan contoh) didefinisikan sebagai:
template <class T = void> 
kelas DIOperasi {
publik:
    kinerja virtual int () = 0;

    virtual T getResult () = 0;

    virtual ~ DIOperation () = 0;
};

class CreateRelation: DIOperation publik <RelatedObjectTriplet> {
pribadi:
    static std :: string const TYPE;

    // Params (tidak berubah)
    RelatedObjectTriplet const m_sParams;

    // Tersembunyi
    CreateRelation & operator = (Const & sumber CreateRelation);
    CreateRelation (const & source CreateRelation);

    // Internal
    std :: string m_sNewRelationId;

publik:
    CreateRelation (RelatedObjectTriplet const & params);

    int perform ();

    TerkaitObjectTriplet getResult ();

    ~ CreateRelation ();
};
  • Dapat digunakan seperti ini:
Terkait triplet Triplet ("33333", "55555", "77777");
CreateRelation createRel (triplet);
createRel.perform ();
const RelatedObjectTriplet res = createRel.getResult ();

Terima kasih atas bantuannya dan saya harap saya tidak membuat kesalahan di sini :)

Andrea Richiardi
sumber