특정 순서로 함수를 호출해야하는 인터페이스 디자인


24

이 작업은 일부 입력 사양에 따라 장치 내에서 하드웨어를 구성하는 것입니다. 이것은 다음과 같이 달성되어야합니다.

1) 구성 정보를 수집하십시오. 이것은 다른 시간과 장소에서 발생할 수 있습니다. 예를 들어, 모듈 A와 모듈 B는 모두 내 모듈에서 일부 리소스를 (다른 시간에) 요청할 수 있습니다. 이러한 '자원'은 실제로 구성입니다.

2) 더 이상 요청이 실현되지 않는 것이 확인되면 요청 된 자원의 요약을 제공하는 시작 명령을 하드웨어로 보내야합니다.

3) 그 후에 만 ​​상기 자원의 상세한 구성을 수행 할 수있다 (필수).

4) 또한, 2) 이후에, 선택된 자원을 선언 된 발신자에게 라우팅 할 수 있으며, 반드시해야한다.


나에게조차도 글을 쓴 버그의 일반적인 원인은이 명령을 착각하는 것입니다. 코드를 처음 본 사람이 인터페이스를 사용할 수 있도록하기 위해 어떤 명명 규칙, 디자인 또는 메커니즘을 사용할 수 있습니까?


1 단계 더이라고 discoveryhandshake?
rwong

1
시간적 결합 은 반 패턴이며 피해야합니다.

1
질문의 제목은 단계 작성기 패턴에 관심이 있다고 생각합니다 .
Joshua Taylor

답변:


45

다시 디자인되었지만 많은 API의 오용을 막을 수 있지만 호출해서는 안되는 메소드를 사용할 수는 없습니다.

예를 들어 first you init, then you start, then you stop

생성자 init는 시작할 수있는 객체이고 start중지 할 수있는 세션을 만듭니다.

물론 한 번에 한 세션으로 제한되는 경우 누군가 이미 활성화 된 세션을 만들려고하는 경우를 처리해야합니다.

이제이 기술을 자신의 사례에 적용하십시오.


zlibjpeglib초기화에이 패턴에 따라 두 가지 예입니다. 그래도 개발자에게 개념을 가르치려면 많은 문서가 필요합니다.
rwong

5
이것은 정답입니다. 순서가 중요한 경우 각 함수는 다음 단계를 수행하기 위해 호출 할 수있는 결과를 반환합니다. 컴파일러 자체는 디자인 제약 조건을 적용 할 수 있습니다.

2
이것은 단계 빌더 패턴 과 유사합니다 . 주어진 단계에서 적합한 인터페이스 만 제시하십시오.
Joshua Taylor

@JoshuaTaylor 내 대답은 :) 패턴 구현 빌더 단계입니다
실비 Burcea

@SilviuBurcea 귀하의 답변 은 단계 작성 도구 구현이 아니지만 여기보다는 의견을 보내 드리겠습니다.
Joshua Taylor

19

시작 메소드가 필수 매개 변수 인 오브젝트를 구성에 리턴하도록 할 수 있습니다.

리소스 * MyModule :: GetResource ();
MySession * MyModule :: Startup ();
void Resource :: Configure (MySession * 세션);

MySession빈 구조체 일지라도 Configure()시작하기 전에 메서드를 호출 할 수 없도록 형식 안전성을 적용 합니다.


누군가가 무엇을하지 못하게 module->GetResource()->Configure(nullptr)합니까?
svick

@ svick : 아무것도 없지만 명시 적으로해야합니다. 이 접근법은 기대하는 것을 알려주고 expecation이 의식적인 결정이라는 것을 우회합니다. 대부분의 프로그래밍 언어와 마찬가지로 아무도 자신을 발로 쏠 수 없습니다. 그러나 API를 통해 항상 그렇게하고 있음을 분명히 나타내는 것이 좋습니다.)
Michael Klement

+1은 훌륭하고 단순 해 보입니다. 그러나 문제가 있습니다. 내가 객체가있는 경우 a, b, c, d, 그때 시작할 수 있습니다 a그것의 사용 및 MySession사용하려고하는 b현실이 아니지만, 이미 시작 개체로.
Vorac

8

Cashcow의 답을 바탕으로-새로운 인터페이스를 제시 할 수 있는데 왜 발신자에게 새로운 객체를 제시해야합니까? 브랜드 변경 패턴 :

class IStartable     { public: virtual IRunnable      start()     = 0; };
class IRunnable      { public: virtual ITerminateable run()       = 0; };
class ITerminateable { public: virtual void           terminate() = 0; };

세션을 여러 번 실행할 수있는 경우 ITerminateable이 IRunnable을 구현하도록 할 수도 있습니다.

당신의 대상 :

class Service : IStartable, IRunnable, ITerminateable
{
  public:
    IRunnable      start()     { ...; return this; }
    ITerminateable run()       { ...; return this; }
    void           terminate() { ...; }
}

// And use it like this:
IStartable myService = Service();

// Now you can only call start() via the interface
IRunnable configuredService = myService.start();

// Now you can also call run(), because it is wrapped in the new interface...

이런 식으로 처음에는 IStartable-Interface 만 있고 start ()를 호출 한 경우에만 run () 메소드에 액세스 할 수 있으므로 올바른 메소드 만 호출 할 수 있습니다. 외부에서는 여러 클래스와 객체가있는 패턴처럼 보이지만 기본 클래스는 항상 참조되는 하나의 클래스로 유지됩니다.


1
여러 클래스 대신 하나의 기본 클래스를 사용하면 어떤 이점이 있습니까? 이것이 내가 제안한 솔루션과의 유일한 차이점 이므로이 특정 점에 관심이 있습니다.
Michael Le Barbier Grünewald

1
@ MichaelGrünewald 하나의 클래스로 모든 인터페이스를 구현할 필요는 없지만 구성 유형 객체의 경우 인터페이스의 인스턴스간에 데이터를 공유하는 가장 간단한 구현 기술 일 수 있습니다 (즉, 동일한 이유로 인해 공유되기 때문에) 목적).
Joshua Taylor

1
이것은 본질적으로 단계 작성기 패턴 입니다.
Joshua Taylor

@JoshuaTaylor 인터페이스 인스턴스간에 데이터를 공유하는 것은 두 가지입니다. 구현하기가 쉽지만 "정의되지 않은 상태"(예 : 연결되지 않은 서버의 클라이언트 주소에 액세스)에 액세스하지 않도록주의해야합니다. OP가 인터페이스 사용성에 중점을두기 때문에 두 가지 접근 방식이 동일하다고 판단 할 수 있습니다. “단계 작성기 패턴”BTW를 인용 해 주셔서 감사합니다.
Michael Le Barbier Grünewald

1
@ MichaelGrünewald 주어진 지점에서 지정된 특정 인터페이스를 통해서만 객체와 상호 작용하는 경우 해당 상태에 액세스 할 수있는 방법이 없어야합니다 (캐스팅 등).
Joshua Taylor

2

문제를 해결하기위한 많은 유효한 접근 방법이 있습니다. Basile Starynkevitch는“제로 관료주의”접근법을 제안하여 간단한 인터페이스를 제공하고 적절한 인터페이스를 사용하여 프로그래머에게 의존합니다. 이 접근 방식이 마음에 들지만 좀 더 eineineering이 있지만 컴파일러가 오류를 잡을 수있는 또 다른 방법을 제시합니다.

  1. 장치가 같이에서 할 수있는 다양한 상태를 파악 Uninitialised, Started, Configured등등. 목록은 유한해야합니다 .¹

  2. 각 상태에 대해 struct해당 상태와 관련된 필요한 추가 정보 (예 : DeviceUninitialised등) DeviceStarted를 보유하십시오.

  3. DeviceStrategy메소드가 입력 및 출력으로 정의 된 구조를 사용하는 하나의 오브젝트에 모든 처리를 압축하십시오. 따라서 DeviceStarted DeviceStrategy::start (DeviceUninitalised dev)방법 이있을 수 있습니다 (또는 프로젝트 규칙에 따라 동등한 방법이있을 수 있음).

이 접근법을 사용하면 유효한 프로그램은 메소드 프로토 타입에 의해 시행되는 순서로 일부 메소드를 호출해야합니다.

다양한 상태는 관련이없는 객체이며, 이는 대체 원칙 때문입니다. 이러한 구조가 공통 조상을 공유하도록하는 것이 유용한 경우 방문자 패턴을 사용하여 추상 클래스 인스턴스의 구체적인 유형을 복구 할 수 있습니다.

3. 고유 DeviceStrategy클래스에 대해 설명했지만 제공하는 기능을 여러 클래스로 분할하려는 경우가 있습니다.

그것들을 요약하기 위해 내가 설명한 디자인의 핵심 사항은 다음과 같습니다.

  1. 대체 원칙으로 인해 장치 상태를 나타내는 객체는 고유해야하며 특별한 상속 관계가 없어야합니다.

  2. 장치 처리를 장치 자체를 나타내는 개체가 아닌 시작 개체로 묶어 각 장치 또는 장치 상태가 자신 만보고 전략이 모든 장치를보고 그들 사이의 가능한 전환을 표현하도록합니다.

텔넷 클라이언트 구현에 대한 설명을 한 번 보았지만 다시는 찾을 수 없다고 맹세합니다. 매우 유용한 참고 자료였습니다!

¹ :이를 위해 직관을 따르거나 "method₁ ~ method₂ iff"관계에 대한 실제 구현에서 메소드의 등가 클래스를 찾으십시오. 동일한 객체에 사용하는 것이 유효합니다.”— 기기의 모든 처리를 캡슐화하는 큰 객체가 있다고 가정합니다. 상태를 나열하는 두 가지 방법 모두 환상적인 결과를 제공합니다.


1
별도의 구조체를 정의하는 대신 각 단계에서 객체가 제공해야하는 필수 인터페이스를 정의하는 것으로 충분할 수 있습니다. 그런 다음 단계 작성기 패턴 입니다.
Joshua Taylor

2

빌더 패턴을 사용하십시오.

위에서 언급 한 모든 작업에 대한 방법이있는 객체를 준비하십시오. 그러나 이러한 작업은 즉시 수행하지 않습니다. 나중에 각 작업을 기억합니다. 작업이 즉시 실행되지 않기 때문에 빌더에 작업을 전달하는 순서는 중요하지 않습니다.

빌더에서 모든 조작을 정의한 후 execute-method 를 호출 하십시오. 이 메소드를 호출하면 위에 저장 한 조작에 따라 위에 나열된 모든 단계가 올바른 순서로 수행됩니다. 이 방법은 하드웨어에 쓰기 작업을 수행하기 전에 일부 작업 확장 상태 검사 (예 : 아직 설정되지 않은 리소스 구성 시도)를 수행하기에 좋은 방법입니다. 이는 하드웨어가 무의미한 구성으로 인해 하드웨어가 손상되는 것을 방지 할 수 있습니다 (하드웨어에 취약한 경우).


1

인터페이스 사용 방법을 올바르게 문서화하고 자습서 예제를 제공하면됩니다.

런타임 검사를 수행하는 디버깅 라이브러리 변형이있을 수도 있습니다.

아마도 정의하고 올바르게 일부 명명 규칙을 문서화 (예를 들어 preconfigure*, startup*, postconfigure*, run*....)

BTW, 많은 기존 인터페이스는 유사한 패턴을 따릅니다 (예 : X11 툴킷).


정보를 전달 하기 위해 Android 애플리케이션 활동 라이프 사이클 과 유사한 상태 전이 다이어그램 이 필요할 수 있습니다.
rwong

1

클라이언트 프로그램이 "문법적으로"정확해야하는 동안 컴파일러는 구문 조건 만 적용 할 수 있기 때문에 이것은 일반적이고 교활한 오류입니다.

불행히도 명명 규칙은 이러한 종류의 오류에 대해 거의 전적으로 비효율적입니다. 당신이 정말로 문법에 맞지 않는 일을하지 않는 사람들을 격려하려는 경우, 당신은 그들이 그렇게 것을 전제 조건에 대한 값으로 초기화해야 어떤 종류의 명령 개체를 전달해야 할 수없는 순서가 단계를 수행합니다.


같은 것을 의미 합니까?
Vorac

1
public class Executor {

private Executor() {} // helper class

  public void execute(MyStepsRunnable r) {
    r.step1();
    r.step2();
    r.step3();
  }
}

interface MyStepsRunnable {

  void step1();
  void step2();
  void step3();
}

이 패턴을 사용하면 모든 구현자가이 순서대로 실행됩니다. 한 단계 더 나아가 ExecutorFactory를 만들어 커스텀 실행 경로로 Executor를 빌드 할 수 있습니다.


다른 의견 에서는 이것을 단계 작성기 구현이라고 불렀지 만 그렇지 않습니다. MyStepsRunnable 인스턴스가있는 경우 step1 전에 step3을 호출 할 수 있습니다. 단계 작성기 구현은 ideone.com/UDECgY 라인을 따라 진행 됩니다. 아이디어는 step1을 실행하여 step2로 무언가를 얻는 것입니다. 따라서 올바른 순서로 메소드를 호출해야합니다. 예를 들어 stackoverflow.com/q/17256627/1281433을 참조하십시오 .
Joshua Taylor

보호 된 메소드 (또는 기본값)를 사용하여 추상 클래스로 변환하여 사용 방법을 제한 할 수 있습니다. executor를 사용해야하지만 현재 구현에 결함이있을 수 있습니다.
Silviu Burcea

그래도 여전히 단계 빌더가 아닙니다. 코드에서 사용자가 다른 단계 사이 에서 코드를 실행하기 위해 수행 할 수있는 작업은 없습니다 . 아이디어는 단순히 코드를 시퀀싱하는 것이 아닙니다 (공개 또는 개인 또는 캡슐화 여부에 관계없이). 코드에서 알 수 있듯이 간단하게 할 수 있습니다 step1(); step2(); step3();. 단계 작성기의 핵심은 일부 단계를 노출하는 API를 제공하고 호출 순서를 적용하는 것입니다. 프로그래머가 단계 사이에서 다른 일을하는 것을 방해해서는 안됩니다.
Joshua Taylor
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.