Thiết kế mẫu lệnh


11

Tôi có triển khai mẫu cũ này. Đó là loại thông qua một bối cảnh thông qua tất cả các triển khai DIOperation , nhưng tôi nhận ra sau này, trong quá trình học và học (điều đó không bao giờ dừng lại), điều đó không tối ưu. Tôi cũng nghĩ rằng việc "ghé thăm" ở đây không thực sự phù hợp và chỉ gây nhầm lẫn.

Tôi thực sự đang nghĩ đến việc tái cấu trúc mã của mình, cũng bởi vì một Lệnh không biết gì về những cái khác và tại thời điểm này, tất cả chúng đều có chung các cặp khóa-giá trị. Thật sự rất khó để duy trì lớp nào sở hữu khóa-giá trị nào, đôi khi dẫn đến các biến trùng lặp.

Một ví dụ về trường hợp sử dụng: giả sử CommandB yêu cầu Tên người dùng được đặt bởi CommandA . CommandA có nên đặt khóa UserNameForCommandB = John không? Hoặc họ nên chia sẻ một UserName = John key-value chung? Nếu Tên người dùng được sử dụng bởi Lệnh thứ ba thì sao?

Làm thế nào tôi có thể cải thiện thiết kế này? Cảm ơn!

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
Tôi chưa bao giờ gặp may mắn khi sử dụng lệnh để đặt thuộc tính (như tên). Nó bắt đầu trở nên rất phụ thuộc. Nếu thuộc tính cài đặt của bạn hãy thử sử dụng kiến ​​trúc sự kiện hoặc mẫu quan sát.
ahenderson

1
1. Tại sao vượt qua các tham số thông qua một khách truy cập riêng biệt? Có gì sai khi vượt qua một bối cảnh như là đối số của việc thực hiện? 2. Bối cảnh dành cho phần 'chung' của lệnh (ví dụ: phiên / tài liệu hiện tại). Tất cả các tham số dành riêng cho hoạt động được chuyển tốt hơn thông qua hàm tạo của thao tác.
Kris Van Bael

@KrisVanBael đó là phần khó hiểu mà tôi đang cố gắng thay đổi. Tôi sẽ chuyển nó với tư cách là Khách truy cập trong khi nó thực sự là một bối cảnh ...
Andrea Richiardi

@ahenderson Ý bạn là sự kiện giữa các lệnh của tôi phải không? Bạn có đặt các giá trị khóa của mình ở đó không (tương tự như những gì Android làm với Parcel)? Điều này có giống với ý nghĩa rằng CommandA sẽ xây dựng một Sự kiện với các cặp khóa-giá trị mà CommandB chấp nhận không?
Andrea Richiardi

Câu trả lời:


2

Tôi hơi lo lắng về tính biến đổi của các tham số lệnh của bạn. Có thực sự cần thiết để tạo một lệnh với các tham số thay đổi liên tục?

Vấn đề với cách tiếp cận của bạn:

Bạn có muốn các chủ đề / lệnh khác thay đổi các tham số của bạn trong khi performđang diễn ra không?

Bạn có muốn visitBeforevisitAftercủa cùng một Commandđối tượng được gọi với các DIParameterđối tượng khác nhau không?

Bạn có muốn ai đó cung cấp các tham số cho các lệnh của bạn, mà các lệnh không có ý tưởng về?

Không ai trong số này bị cấm bởi thiết kế hiện tại của bạn. Mặc dù khái niệm tham số khóa-giá trị chung đôi khi có giá trị của nó, tôi không thích nó đối với một lớp lệnh chung.

Ví dụ về hậu quả:

Xem xét một nhận thức cụ thể của Commandlớp học của bạn - một cái gì đó như CreateUserCommand. Bây giờ rõ ràng, khi bạn yêu cầu một người dùng mới được tạo, lệnh sẽ cần một tên cho người dùng đó. Cho rằng tôi biết CreateUserCommandvà các DIParameterslớp, tôi nên đặt tham số nào?

Tôi có thể đặt userNametham số, hoặc username.. bạn có xử lý trường hợp tham số không nhạy cảm không? Tôi sẽ không biết thực sự .. oh chờ đợi .. có lẽ chỉ là name?

Như bạn có thể thấy sự tự do mà bạn có được từ ánh xạ khóa-giá trị chung ngụ ý rằng việc sử dụng các lớp của bạn như một người không thực hiện chúng là khó khăn một cách vô lý. Ít nhất bạn sẽ cần cung cấp một số hằng cho các lệnh của mình để cho người khác biết khóa nào được lệnh đó hỗ trợ.

Phương pháp thiết kế khác nhau có thể:

  • Các tham số bất biến: Bằng cách biến các Parameterthể hiện của bạn thành bất biến, bạn có thể tự do sử dụng lại chúng trong số các lệnh khác nhau.
  • Các lớp tham số cụ thể: Đưa ra một UserParameterlớp chứa chính xác các tham số tôi cần cho các lệnh liên quan đến người dùng, việc làm với API này sẽ đơn giản hơn nhiều. Bạn vẫn có thể có thừa kế trên các thông số, nhưng nó sẽ không có ý nghĩa nữa cho các lớp lệnh để lấy các thông số tùy ý - ở phía ủng hộ tất nhiên phương tiện này mà người dùng API biết thông số được yêu cầu chính xác.
  • Một trường hợp lệnh cho mỗi ngữ cảnh: Nếu bạn cần các lệnh của mình để có những thứ như visitBeforevisitAfter, đồng thời sử dụng lại chúng với các tham số khác nhau, bạn sẽ mở ra vấn đề nhận được các lệnh với các tham số khác nhau. Nếu các tham số phải giống nhau trên nhiều cuộc gọi phương thức, bạn cần gói gọn chúng vào lệnh sao cho chúng không thể được tắt cho các tham số khác ở giữa các cuộc gọi.

Có, tôi đã thoát khỏi lượt truy cập Trước và sau khi truy cập. Về cơ bản, tôi đang chuyển giao diện DIParameter của mình trong phương thức thực hiện. Vấn đề với các phiên bản DIParters không mong muốn luôn luôn tồn tại, bởi vì tôi đã chọn để có sự linh hoạt khi chuyển giao diện. Tôi thực sự thích ý tưởng về việc có thể phân lớp và làm cho trẻ em BẠC bất biến một khi chúng được lấp đầy. Tuy nhiên, một "cơ quan trung ương" vẫn cần phải truyền tham số chính xác cho Lệnh. Đây có lẽ là lý do tại sao tôi bắt đầu triển khai mô hình Khách truy cập..Tôi muốn đảo ngược quyền kiểm soát theo một cách nào đó ...
Andrea Richiardi

0

Điều tốt đẹp về các nguyên tắc thiết kế là sớm hay muộn, chúng xung đột với nhau.

Trong tình huống được mô tả, tôi nghĩ rằng tôi muốn sử dụng một số loại bối cảnh mà mỗi lệnh có thể lấy thông tin từ đó và đưa thông tin vào (đặc biệt nếu đó là các cặp giá trị khóa). Điều này dựa trên sự đánh đổi: Tôi không muốn các lệnh riêng biệt được ghép nối chỉ vì chúng là một loại đầu vào cho nhau. Bên trong CommandB, tôi không quan tâm đến việc UserName được đặt như thế nào - chỉ là nó ở đó để tôi sử dụng. Điều tương tự trong CommandA: Tôi đặt thông tin vào, tôi không muốn biết người khác đang làm gì với thông tin đó - họ cũng không phải là ai.

Điều này có nghĩa là một loại bối cảnh đi qua, mà bạn có thể tìm thấy xấu. Đối với tôi, sự thay thế là tồi tệ hơn, đặc biệt là nếu bối cảnh khóa-giá trị đơn giản này (có thể là một bean đơn giản với getters và setters, để hạn chế một chút yếu tố "dạng tự do") có thể cho phép giải pháp đơn giản và có thể kiểm tra được các lệnh riêng biệt, mỗi lệnh có logic nghiệp vụ riêng.


1
Những nguyên tắc nào bạn thấy xung đột ở đây?
Jimmy Hoffa

Chỉ cần làm rõ, vấn đề của tôi là không chọn giữa mẫu Bối cảnh hoặc Khách truy cập. Về cơ bản, tôi đang sử dụng một mẫu Bối cảnh có tên là Người truy cập :)
Andrea Richiardi

Ok, tôi có thể hiểu nhầm câu hỏi / vấn đề chính xác của bạn.
Martin

0

Giả sử bạn có giao diện lệnh:

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

Và một chủ đề:

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

Những gì bạn cần là:

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

Đặt NameObserver& olàm tham chiếu đến CommandB. Bây giờ bất cứ khi nào CommandA thay đổi tên Đối tượng CommandB có thể thực thi với thông tin chính xác. Nếu tên được sử dụng bởi nhiều lệnh hơn, hãy sử dụngstd::list<NameObserver>


Cảm ơn câu trả lời. Vấn đề với imho thiết kế này là chúng ta cần một Setter + NameObserver cho mỗi tham số. Tôi có thể thông qua một ví dụ (bối cảnh) và thông báo, nhưng một lần nữa, tôi có thể sẽ không giải quyết được sự thật rằng tôi vẫn đang ghép CommandA với CommandB, nghĩa là CommandA phải đặt một giá trị khóa mà chỉ CommandB mới biết ... những gì tôi đã cố gắng cũng là để có một thực thể bên ngoài (ParameterHandler), đây là người duy nhất biết Lệnh nào cần tham số nào và đặt / lấy tương ứng trên đối tượng DIParameter.
Andrea Richiardi

@Kap "Vấn đề với imho thiết kế này là chúng tôi cần Setter + NameObserver cho mỗi tham số" - tham số trong ngữ cảnh này hơi khó hiểu đối với tôi, tôi nghĩ bạn có nghĩa là trường. Trong trường hợp đó, bạn nên có một setter cho từng trường thay đổi. Từ ví dụ của bạn, có vẻ như ComamndA không thay đổi tên của Chủ đề. Nó sẽ thay đổi trường thông qua một setter. Lưu ý: bạn không cần người quan sát trên mỗi trường, chỉ cần có một getter và truyền đối tượng cho tất cả người quan sát.
ahenderson

0

Tôi không biết liệu đây có phải là cách đúng để xử lý việc này đối với Lập trình viên (trong trường hợp tôi xin lỗi), nhưng, sau khi đã kiểm tra tất cả các câu trả lời ở đây (cụ thể là @ Frank). Tôi đã cấu trúc lại mã của mình theo cách này:

  • Rơi xuống. Tôi sẽ có các đối tượng riêng (chung) làm đầu vào của DIOperation (không thay đổi). Thí dụ:
lớp Liên quanObjectTriplet {
riêng tư:
    std :: chuỗi const m_sPrimaryObjectId;
    std :: chuỗi const m_sSecondaryObjectId;
    std :: chuỗi const m_sRelationObjectId;

    Liên quanObjectTriplet & toán tử = (Liên quanObjectTriplet khác);

công cộng:
    Liên quanObjectTriplet (std :: chuỗi const & sPrimaryObjectId,
                         std :: chuỗi const & sSecondaryObjectId,
                         std :: chuỗi const & sRelationObjectId);

    Liên quanObjectTriplet (Liên quanObjectTriplet const & khác);


    std :: chuỗi const & getPrimaryObjectId () const;
    std :: chuỗi const & getSecondaryObjectId () const;
    std :: chuỗi const & getRelationObjectId () const;

    ~ Liên quanObjectTriplet ();
};
  • Lớp DIOperation mới (có ví dụ) được định nghĩa là:
mẫu <class T = void> 
lớp DIOperation {
công cộng:
    ảo int thực hiện () = 0;

    ảo T getResult () = 0;

    ảo ~ DIOperation () = 0;
};

class CreatRelation: công khai DIOperation <RecentObjectTriplet> {
riêng tư:
    tĩnh std :: chuỗi const TYPE;

    // Params (bất biến)
    Liên quanObjectTriplet const m_sParams;

    // Ẩn
    CreatRelation & toán tử = (constRelation const & source);
    CreatRelation (constRelation const & source);

    // Nội bộ
    std :: chuỗi m_sNewRelationId;

công cộng:
    CreatRelation (constObjectTriplet const & params);

    int biểu diễn ();

    Liên quanObjectTriplet getResult ();

    ~ TạoRelation ();
};
  • Nó có thể được sử dụng như thế này:
Bộ ba liên quanObjectTriplet ("33333", "55555", "77777");
CreatRelation createdRel (bộ ba);
createdRel.perform ();
const Liên quanObjectTriplet res = createdRel.getResult ();

Cảm ơn sự giúp đỡ và tôi hy vọng tôi đã không phạm sai lầm ở đây :)

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.