명령 패턴 디자인


11

이 이전의 명령 패턴 구현이 있습니다. 그것은 모든 DIOperation 구현을 통해 Context를 전달하는 것이지만, 학습과 학습 과정에서 멈추지 않는 나중에 실현되지 않는 것을 나중에 깨달았습니다. 또한 여기에있는 "방문"은 실제로 적합하지 않으며 혼동된다고 생각합니다.

나는 실제로 내 코드를 리팩토링하려고 생각하고 있습니다. 명령은 다른 것에 대해 아무것도 알지 못하고 현재 모두 동일한 키-값 쌍을 공유하기 때문입니다. 어떤 클래스가 어떤 키-값을 소유 하는지를 유지하는 것은 실제로 어렵고 때로는 변수를 복제합니다.

사용 사례의 예 : CommandBCommandA에 의해 설정된 UserName 이 필요 하다고 가정 해 봅시다 . CommandA가 UserNameForCommandB = John 키를 설정해야합니까 ? 아니면 공통 UserName = John 키-값 을 공유해야 합니까? 세 번째 명령에서 UserName을 사용하면 어떻게됩니까?

이 디자인을 어떻게 개선 할 수 있습니까? 감사!

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가 Parcel에서하는 것과 비슷 함)? CommandA가 CommandB가 허용하는 키-값 쌍으로 이벤트를 작성해야한다는 점에서 동일합니까?
Andrea Richiardi

답변:


2

명령 매개 변수의 변경 가능성이 약간 걱정됩니다. 지속적으로 변경되는 매개 변수로 명령을 작성해야합니까?

접근 방식의 문제점 :

다른 스레드 / 명령 perform이 진행되는 동안 매개 변수를 변경 하도록 하시겠습니까?

당신이 원하는 마 visitBeforevisitAfter같은의 Command개체를 다른 호출 할 DIParameter객체?

명령에 대해 모르는 사람이 명령에 매개 변수를 제공하도록 하시겠습니까?

현재 디자인에서이 중 어느 것도 금지되지 않습니다. 일반 키-값 매개 변수 개념에는 때때로 장점이 있지만, 일반 명령 클래스와 관련하여 마음에 들지는 않습니다.

결과의 예 :

Command같은 클래스 의 구체적인 실현을 고려하십시오 CreateUserCommand. 이제 새 사용자를 만들도록 요청하면 명령에 해당 사용자의 이름이 필요합니다. 클래스 CreateUserCommandDIParameters클래스를 알고 있다면 어떤 매개 변수를 설정해야합니까?

userName매개 변수를 설정할 수 있습니다 username. 또는 매개 변수를 대소 문자를 구분하지 않습니까? 난 정말 몰랐어 .. 잠깐만 .. 어쩌면 그냥 name?

보시다시피 일반적인 키-값 매핑에서 얻을 수있는 자유는 클래스를 구현하지 않은 사람으로 클래스를 사용하는 것이 부당하게 어렵다는 것을 의미합니다. 최소한 다른 사람이 해당 명령에서 지원하는 키를 알 수 있도록 명령에 상수를 제공해야합니다.

가능한 다른 디자인 접근법 :

  • 변경할 수없는 매개 변수 : Parameter인스턴스를 변경할 수 없도록 설정하면 여러 명령에서 인스턴스를 자유롭게 재사용 할 수 있습니다.
  • 특정 매개 변수 클래스 : UserParameter사용자와 관련된 명령에 필요한 매개 변수를 정확하게 포함 하는 클래스가 주어지면 이 API를 사용하는 것이 훨씬 간단합니다. 여전히 매개 변수를 상속받을 수는 있지만 명령 클래스가 임의의 매개 변수를 취하는 것은 더 이상 의미가 없습니다. 물론 이것은 API 사용자가 어떤 매개 변수가 정확히 필요한지를 알고 있음을 의미합니다 .
  • 컨텍스트 당 하나의 명령 인스턴스 : visitBeforeand와 같은 것을 사용해야하는 명령이 필요 visitAfter하고 다른 매개 변수와 함께 재사용하는 경우 다른 매개 변수로 호출되는 문제에 노출 될 수 있습니다. 여러 메소드 호출에서 매개 변수가 동일해야하는 경우, 호출 사이의 다른 매개 변수에 대해 전환 할 수 없도록 명령에 매개 변수를 캡슐화해야합니다.

예, visitBefore와 visitAfter를 제거했습니다. 기본적으로 perform 메소드에서 DIParameter 인터페이스를 전달합니다. 인터페이스를 전달하는 유연성을 갖도록 선택했기 때문에 원치 않는 DIParamters 인스턴스의 문제는 항상 존재합니다. DIParameters 하위 항목이 채워지면 서브 클래스를 만들고 변경할 수 없다는 아이디어가 정말 좋습니다. 그러나 "중앙 권한"은 여전히 ​​올바른 DIParameter를 명령에 전달해야합니다. 이것이 아마도 방문자 패턴을 구현하기 시작한 이유 일 것입니다. 어떤 식 으로든 제어의 반전을 원했습니다 ...
Andrea Richiardi

0

디자인 원칙의 장점은 조만간 서로 충돌한다는 것입니다.

설명 된 상황에서 각 명령이 정보를 가져 와서 정보를 넣을 수있는 컨텍스트 (특히 키 값 쌍 인 경우)를 선호한다고 생각합니다. 이것은 트레이드 오프를 기반으로합니다 : 나는 서로에 대한 일종의 입력이기 때문에 별도의 명령을 결합하고 싶지 않습니다. CommandB 내에서 UserName이 어떻게 설정되어 있는지 상관하지 않습니다. CommandA의 동일한 내용 : 정보를 설정했습니다. 다른 사람이 정보를 사용하고 있는지 알고 싶지 않습니다.

이것은 일종의 통과 컨텍스트를 의미하며, 이는 당신이 나쁘게 찾을 수 있습니다. 저에게있어 대안은 더 나쁩니다. 특히이 간단한 키-값 컨텍스트 (게터와 세터가있는 간단한 빈이 될 수 있고 "자유 형식"요소를 조금 제한하기 위해)가 솔루션을 간단하고 테스트 할 수있게한다면 각각 고유 한 비즈니스 로직이있는 분리 된 명령.


1
여기에서 어떤 원칙이 상충 되는가?
Jimmy Hoffa

명확히하기 위해 내 문제는 컨텍스트 또는 방문자 패턴 중에서 선택하지 않습니다. 나는 기본적으로 방문자라는 컨텍스트 패턴을 사용하고 있습니다 :)
Andrea 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& oCommandB에 대한 참조로 설정 하십시오. 이제 CommandA가 변경 될 때마다 주제 이름 CommandB가 올바른 정보로 실행될 수 있습니다. 더 많은 명령에서 이름을 사용하는 경우std::list<NameObserver>


답변 해주셔서 감사합니다. 이 디자인의 문제점은 모든 매개 변수마다 Setter + NameObserver가 필요하다는 것입니다. DIParameters (컨텍스트) 인스턴스를 전달하고 알릴 수는 있지만 CommandA와 CommandB를 여전히 결합하고 있다는 사실을 해결하지 못할 것입니다. 내가 시도한 것은 외부 명령 (ParameterHandler)을 가지고 있었는데, 이는 DICommands 인스턴스에서 어떤 Command가 어떤 매개 변수를 필요로하는지 설정하고 얻는다는 것을 아는 유일한 사람입니다.
Andrea Richiardi

@Kap "이 디자인 imho의 문제는 모든 매개 변수마다 Setter + NameObserver가 필요하다는 것입니다."-이 컨텍스트의 매개 변수는 약간 혼란 스럽습니다. 필드를 의미한다고 생각합니다. 이 경우 변경되는 각 필드에 대한 세터가 이미 있어야합니다. 귀하의 예에서 ComamndA는 주제의 이름을 변경하는 것으로 보입니다. setter를 통해 필드를 변경해야합니다. 참고 : 필드 당 관찰자가 필요하지 않으며 getter 만 가지고 객체를 모든 관찰자에게 전달하십시오.
ahenderson

0

이것이 프로그래머 에게이 문제를 처리하는 올바른 방법인지는 모르겠지만 (이 경우 사과드립니다) 모든 답변을 확인한 후 (@ Frank 's 특히). 이런 식으로 코드를 리팩터링했습니다.

  • DI 매개 변수가 삭제되었습니다. DIOperation의 입력 (불변)으로 개별 (일반) 객체를 갖게됩니다. 예:
RelatedObjectTriplet {클래스
은밀한:
    std :: string const m_sPrimaryObjectId;
    std :: string const m_sSecondaryObjectId;
    std :: string const m_sRelationObjectId;

    RelatedObjectTriplet & operator = (RelatedObjectTriplet 기타);

공공의:
    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 ();
};
  • 다음과 같이 정의 된 새 DIOperation 클래스 (예)
템플릿 <class T = void> 
DIOperation 클래스
공공의:
    virtual int perform () = 0;

    가상 T getResult () = 0;

    가상 ~ DIOperation () = 0;
};

CreateRelation 클래스 : public DIOperation <RelatedObjectTriplet> {
은밀한:
    정적 std :: string const TYPE;

    // Params (불변)
    RelatedObjectTriplet const m_sParams;

    // 숨겨진
    CreateRelation & operator = (CreateRelation const & 소스);
    CreateRelation (CreateRelation const & source);

    // 내부의
    std :: string m_sNewRelationId;

공공의:
    CreateRelation (RelatedObjectTriplet const & params);

    int perform ();

    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.