การออกแบบรูปแบบคำสั่ง


11

ฉันมีการใช้รูปแบบคำสั่งเก่านี้ เป็นการผ่านบริบทผ่านการใช้งานDIOperationทั้งหมดแต่ฉันรู้ในภายหลังในกระบวนการเรียนรู้และการเรียนรู้ (ที่ไม่เคยหยุดนิ่ง) นั่นไม่เหมาะสม ฉันยังคิดว่า "การเยี่ยมชม" ที่นี่ไม่เหมาะและสับสนจริงๆ

จริง ๆ แล้วฉันกำลังคิดที่จะเปลี่ยนรหัสของฉันอีกครั้งเนื่องจากคำสั่งไม่ควรรู้อะไรเกี่ยวกับผู้อื่นและในขณะนี้พวกเขาทั้งหมดใช้คู่ค่าคีย์เดียวกัน มันยากที่จะรักษาคลาสที่เป็นเจ้าของคีย์ - ค่าซึ่งบางครั้งนำไปสู่ตัวแปรที่ซ้ำกัน

ตัวอย่างกรณีการใช้งานสมมติว่าCommandBต้องการชื่อผู้ใช้ที่CommandAกำหนดไว้ CommandA ควรตั้งค่าUserNameForCommandB = Johnหรือไม่ หรือพวกเขาควรแบ่งปันชื่อผู้ใช้ทั่วไป= John key-value หรือไม่ จะเกิดอะไรขึ้นหากชื่อผู้ใช้ถูกใช้โดยคำสั่งที่สาม

ฉันจะปรับปรุงการออกแบบนี้ได้อย่างไร ขอบคุณ!

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;
};

3
ฉันไม่เคยมีโชคโดยใช้คำสั่งเพื่อตั้งค่าคุณสมบัติ (เช่นชื่อ) มันเริ่มเป็นที่พึ่งมาก หากคุณสมบัติการตั้งค่าของคุณลองใช้สถาปัตยกรรมเหตุการณ์หรือรูปแบบผู้สังเกตการณ์
ahenderson

1
1. ทำไมต้องผ่านพารามิเตอร์ผ่านผู้เข้าชมแยกต่างหาก เกิดอะไรขึ้นกับการส่งบริบทเพื่อเป็นข้อโต้แย้งในการแสดง? 2. บริบทสำหรับส่วน 'ทั่วไป' ของคำสั่ง (เช่นเซสชัน / เอกสารปัจจุบัน) พารามิเตอร์เฉพาะการดำเนินการทั้งหมดจะถูกส่งผ่านตัวสร้างของการดำเนินการได้ดีขึ้น
Kris Van Bael

@KrisVanBael เป็นส่วนที่สับสนที่ฉันพยายามเปลี่ยน ฉันผ่านมันเป็นผู้มาเยือนในขณะที่จริง ๆ แล้วมันเป็นบริบท ...
Andrea Richiardi

@ahenderson คุณหมายถึงเหตุการณ์ระหว่างคำสั่งของฉันใช่ไหม คุณจะใส่ค่าคีย์ของคุณที่นั่น (คล้ายกับ Android ที่ทำกับพัสดุ) ในกรณีที่ CommandA ควรสร้างเหตุการณ์ด้วยคู่คีย์ - ค่าที่ CommandB ยอมรับหรือไม่
Andrea Richiardi

คำตอบ:


2

ฉันกังวลเกี่ยวกับความผันแปรของพารามิเตอร์คำสั่งของคุณ จำเป็นหรือไม่ที่จะต้องสร้างคำสั่งด้วยพารามิเตอร์ที่เปลี่ยนแปลงตลอดเวลา?

ปัญหาเกี่ยวกับวิธีการของคุณ:

คุณต้องการให้เธรด / คำสั่งอื่นเปลี่ยนพารามิเตอร์ของคุณในขณะที่performเกิดขึ้นหรือไม่?

คุณต้องการvisitBeforeและเรียกวัตถุvisitAfterเดียวกันCommandด้วยDIParameterวัตถุที่แตกต่างกันหรือไม่?

คุณต้องการให้ใครสักคนป้อนพารามิเตอร์ให้กับคำสั่งของคุณซึ่งคำสั่งนั้นไม่มีความคิดหรือไม่?

สิ่งเหล่านี้ไม่ได้รับอนุญาตจากการออกแบบปัจจุบันของคุณ ในขณะที่แนวคิดพารามิเตอร์คีย์ - ค่าทั่วไปมีข้อดีอยู่หลายครั้งฉันไม่ชอบมันเกี่ยวกับคลาสคำสั่งทั่วไป

ตัวอย่างของผลที่ตามมา:

พิจารณาการทำให้Commandคลาสของคุณเป็นCreateUserCommandจริง ตอนนี้เห็นได้ชัดว่าเมื่อคุณร้องขอให้สร้างผู้ใช้ใหม่คำสั่งจะต้องมีชื่อสำหรับผู้ใช้นั้น ระบุว่าฉันรู้CreateUserCommandและDIParametersคลาสฉันควรตั้งค่าพารามิเตอร์ใด

ฉันสามารถตั้งค่าuserNameพารามิเตอร์หรือusername.. คุณปฏิบัติกับกรณีพารามิเตอร์อย่างไม่รู้สึกตัวหรือไม่? ฉันไม่รู้จริง ๆ เลย .. โอ้เดี๋ยวก่อน .. อาจจะเป็นเพียงแค่name?

ในขณะที่คุณสามารถเห็นอิสระที่คุณได้รับจากการจับคู่คีย์ - ค่าทั่วไปหมายความว่าการใช้คลาสของคุณเป็นคนที่ไม่ได้ใช้มันเป็นเรื่องยาก อย่างน้อยคุณจะต้องให้ค่าคงที่บางคำสั่งของคุณเพื่อให้ผู้อื่นทราบว่าคีย์ใดบ้างที่ได้รับการสนับสนุนโดยคำสั่งนั้น

วิธีการออกแบบที่เป็นไปได้ที่แตกต่างกัน:

  • พารามิเตอร์ที่ไม่เปลี่ยนรูป: เมื่อเปลี่ยนParameterอินสแตนซ์ของคุณที่ไม่เปลี่ยนรูปคุณสามารถนำกลับมาใช้ใหม่ได้อย่างอิสระในคำสั่งต่าง ๆ
  • คลาสพารามิเตอร์เฉพาะ: เมื่อกำหนดUserParameterคลาสที่มีพารามิเตอร์ที่แน่นอนที่ฉันต้องการสำหรับคำสั่งที่เกี่ยวข้องกับผู้ใช้มันจะง่ายกว่ามากในการทำงานกับ API นี้ คุณยังอาจมีมรดกพารามิเตอร์ แต่มันจะไม่ทำให้รู้สึกใด ๆ อีกต่อไปสำหรับการเรียนคำสั่งที่จะใช้พารามิเตอร์พล - ที่ด้านโปรของหลักสูตรนี้หมายถึงว่าผู้ใช้ API รู้ซึ่งพารามิเตอร์จะต้องตรง
  • อินสแตนซ์คำสั่งหนึ่งคำสั่งต่อบริบท: หากคุณต้องการให้คำสั่งของคุณมีสิ่งที่ชอบvisitBeforeและvisitAfterในขณะที่การนำกลับมาใช้ใหม่ด้วยพารามิเตอร์ที่แตกต่างกันคุณจะเปิดปัญหาการเรียกด้วยพารามิเตอร์ที่แตกต่าง หากพารามิเตอร์ควรเหมือนกันในการเรียกใช้เมธอดหลายวิธีคุณต้องใส่แค็ปซูลลงในคำสั่งเพื่อไม่สามารถเปลี่ยนพารามิเตอร์อื่น ๆ ระหว่างการโทรได้

ใช่ฉันได้ไปก่อนหน้าแล้วไปเยี่ยมชมหลังจาก โดยทั่วไปฉันจะส่งผ่านส่วนต่อประสานพารามิเตอร์ของฉันในวิธีดำเนินการ ปัญหาเกี่ยวกับอินสแตนซ์ DIParamters ที่ไม่พึงประสงค์มักจะอยู่ที่นั่นเพราะฉันเลือกที่จะมีความยืดหยุ่นในการผ่านส่วนต่อประสาน ฉันชอบความคิดที่จะสามารถซับคลาสและทำให้เด็ก ๆ พารามิเตอร์ไม่เปลี่ยนรูปเมื่อพวกเขาเติมเต็ม อย่างไรก็ตาม "ผู้มีอำนาจส่วนกลาง" ยังคงต้องการผ่าน DIParameter ที่ถูกต้องไปยังคำสั่ง นี่อาจจะเป็นเหตุผลที่ผมเริ่มต้นการดำเนินการของผู้เข้าชม pattern..I ต้องการที่จะมีการผกผันของการควบคุมในบางวิธี ...
อันเดรีย Richiardi

0

สิ่งที่ดีเกี่ยวกับหลักการออกแบบคือไม่ช้าก็เร็วพวกเขาขัดแย้งกัน

ในสถานการณ์ที่อธิบายไว้ฉันคิดว่าฉันต้องการไปกับบริบทบางอย่างที่แต่ละคำสั่งสามารถนำข้อมูลจากและนำข้อมูลไปใช้ (โดยเฉพาะถ้าเป็นคู่ของคีย์ - ค่า) สิ่งนี้ขึ้นอยู่กับการแลกเปลี่ยน: ฉันไม่ต้องการให้คำสั่งแยกจากกันเป็นเพียงเพราะพวกเขาเป็นชนิดของการป้อนข้อมูลซึ่งกันและกัน ภายใน CommandB ฉันไม่สนใจว่า UserName ได้รับการตั้งค่าอย่างไรเพียงแค่มีไว้ให้ฉันใช้ สิ่งเดียวกันใน CommandA: ฉันตั้งค่าข้อมูลไว้ฉันไม่ต้องการรู้ว่าคนอื่นทำอะไรกับมัน - ไม่ว่าพวกเขาจะเป็นใคร

นี่หมายถึงบริบทการผ่านซึ่งคุณสามารถค้นหาได้ไม่ดี สำหรับฉันทางเลือกนั้นแย่ลงโดยเฉพาะอย่างยิ่งถ้าบริบทคีย์ - ค่าง่าย ๆ (อาจเป็นถั่วธรรมดาที่มี getters และ setters เพื่อ จำกัด บิตของ "free form" factor) สามารถทำให้โซลูชันนั้นง่ายและทดสอบได้ดี คำสั่งที่แยกจากกันแต่ละตัวมีตรรกะทางธุรกิจของตัวเอง


1
หลักการใดที่คุณพบว่าขัดแย้งกันที่นี่
Jimmy Hoffa

เพียงชี้แจงปัญหาของฉันไม่ได้เลือกระหว่างรูปแบบบริบทหรือผู้เข้าชม ฉันกำลังโดยทั่วไปใช้รูปแบบที่เรียกว่าบริบทของผู้เข้าชม :)
อันเดรีย Richiardi

ตกลงฉันอาจเข้าใจผิดคำถามหรือปัญหาที่แน่นอนของคุณ
Martin

0

สมมติว่าคุณมีอินเตอร์เฟสคำสั่ง:

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

และเรื่อง:

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

สิ่งที่คุณต้องการคือ:

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

ตั้งNameObserver& oเป็นการอ้างอิงไปยัง CommandB ตอนนี้เมื่อใดก็ตามที่ CommandA เปลี่ยนชื่อวิชา CommandB สามารถดำเนินการด้วยข้อมูลที่ถูกต้อง หากชื่อถูกใช้โดยคำสั่งเพิ่มเติมให้ใช้std::list<NameObserver>


ขอบคุณสำหรับคำตอบ. ปัญหาของการออกแบบนี้คือเราต้องการ Setter + NameObserver ต่อพารามิเตอร์ทุกตัว ฉันสามารถผ่านอินสแตนซ์ DIParameters (บริบท) และแจ้งเตือน แต่อีกครั้งฉันอาจจะไม่แก้ความจริงที่ว่าฉันยังคงเชื่อมต่อ CommandA กับ CommandB หมายความว่า CommandA ต้องใส่คีย์ - ค่าที่ CommandB เท่านั้นควรรู้ ... สิ่งที่ฉันพยายามก็คือการมีหน่วยงานภายนอก (ParameterHandler) ซึ่งเป็นคนเดียวที่รู้ว่าคำสั่งใดที่ต้องการพารามิเตอร์และชุด / รับตามในอินสแตนซ์ DIParameters
Andrea Richiardi

@Kap "ปัญหาของการออกแบบ imho นี้คือเราต้องการ Setter + NameObserver ต่อทุกพารามิเตอร์" - พารามิเตอร์ในบริบทนี้สับสนเล็กน้อยสำหรับฉันฉันคิดว่าคุณหมายถึงฟิลด์ ในกรณีนี้คุณควรมี setter สำหรับแต่ละฟิลด์ที่เปลี่ยนแปลง จากตัวอย่างของคุณดูเหมือนว่า ComamndA จะเปลี่ยนชื่อของหัวเรื่อง ควรเปลี่ยนฟิลด์ผ่านตัวตั้งค่า หมายเหตุ: คุณไม่จำเป็นต้องมีผู้สังเกตการณ์ต่อฟิลด์เพียงแค่มีทะเยอทะยานและส่งวัตถุไปยังผู้สังเกตการณ์ทั้งหมด
ahenderson

0

ฉันไม่รู้ว่านี่เป็นวิธีที่ถูกต้องในการจัดการกับโปรแกรมเมอร์ (ซึ่งในกรณีนี้ฉันขอโทษ) แต่หลังจากตรวจสอบคำตอบทั้งหมดที่นี่ (โดยเฉพาะ @ @ Frank) ฉัน refactored รหัสของฉันด้วยวิธีนี้:

  • Diparameters ที่ลดลง ฉันจะมีวัตถุ (ทั่วไป) แต่ละรายการเป็นอินพุตของ DIOperation (ไม่เปลี่ยนรูป) ตัวอย่าง:
คลาส RelatedObjectTriplet {
เอกชน:
    std :: string const m_sPrimaryObjectId;
    std :: string const m_sSecondaryObjectId;
    std :: string const m_sRelationObjectId;

    RelatedObjectTriplet & operator = (RelatedObjectTriplet อื่น ๆ );

ส่วนกลาง:
    RelatedObjectTriplet (std :: สตริง const & sPrimaryObjectId,
                         std :: สตริง const & sSecondaryObjectId,
                         std :: string const & sRelationObjectId);

    RelatedObjectTriplet (ที่เกี่ยวข้อง ObjectTriplet const & อื่น ๆ );


    std :: string const & getPrimaryObjectId () const;
    std :: สตริง const & getSecondaryObjectId () const;
    std :: สตริง const & getRelationObjectId () const;

    ~ RelatedObjectTriplet ();
};
  • คลาส DIOperation ใหม่ (พร้อมตัวอย่าง) กำหนดเป็น:
แม่แบบ <class T = void> 
ระดับ DIOperation {
ส่วนกลาง:
    virtual int perform () = 0;

    virtual T getResult () = 0;

    virtual ~ DIOperation () = 0;
};

ระดับ CreateRelation: สาธารณะ DIOperation <RelatedObjectTriplet> {
เอกชน:
    คงที่มาตรฐาน :: สตริง const TYPE;

    // Params (ไม่เปลี่ยนรูป)
    RelatedObjectTriplet const m_sParams;

    // ซ่อนเร้น
    CreateRelation & operator = (CreateRelation const & แหล่งที่มา);
    CreateRelation (CreateRelation const & แหล่งที่มา);

    // ภายใน
    std :: string m_sNewRelationId;

ส่วนกลาง:
    CreateRelation (ที่เกี่ยวข้อง ObjectTriplet const & params);

    int ดำเนินการ ();

    RelatedObjectTriplet getResult ();

    ~ CreateRelation ();
};
  • มันสามารถใช้เช่นนี้:
RelatedObjectTriplet triplet ("33333", "55555", "77777");
CreateRelation createRel (triplet);
createRel.perform ();
const RelatedObjectTriplet res = createRel.getResult ();

ขอบคุณสำหรับความช่วยเหลือและฉันหวังว่าฉันไม่ได้ทำผิดพลาดที่นี่ :)

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.