2011-10-18 1 views
5

C++에서는 종종 RAII 스타일의 객체를 사용하여 코드의 안정성을 높이고 스택에 할당하여 코드를 더 효율적으로 만들었습니다 (bad_alloc을 방지했습니다).스택에 RAII 객체와 DI 원칙이 할당 됨

그러나 스택에 콘크리트 클래스의 객체를 생성하면 종속성 반전 (DI) 원칙을 위반하고이 객체를 조롱하는 것을 방지합니다.

다음 코드 고려 : 나는 IBar::process을 테스트 할 수 있습니다

struct IInputStream 
{ 
    virtual vector<BYTE> read(size_t n) = 0; 
}; 

class Connection : public IInputStream 
{ 
public: 
    Connection(string address); 
    virtual vector<BYTE> read(size_t n) override; 
}; 

struct IBar 
{ 
    virtual void process(IInputStream& stream) = 0; 
}; 

void Some::foo(string address, IBar& bar) 
{ 
    onBeforeConnectionCreated(); 
    { 
     Connection conn(address); 
     onConnectionCreated(); 
     bar.process(conn); 
    } 
    onConnectionClosed(); 
} 

, 그러나 나는 또한 실제 연결 객체를 생성하지 않고, Some::foo을 테스트 할 수 있습니다.

확실히 팩토리를 사용할 수 있지만 코드가 복잡해지고 힙 할당이 도입됩니다.
또한, Connection::open 메서드를 추가하고 싶지 않습니다. 완전히 초기화되고 완전한 기능을 갖는 개체를 만드는 것이 좋습니다.

나는 Some에 대한 Connection 타입 템플릿 매개 변수를 만들 (또는 foo을위한 경우에하는 무료 기능으로 추출),하지만 난 그게 올바른 방법 (템플릿을 많은 사람들에게 마술처럼, 그래서 있다는 모르겠어요 것 동적 다형성 사용을 선호)

+2

템플릿이 더 많거나 적을 유능한 C++ 프로그래머에게는 검은 마법이되어서는 안됩니다. 나는 그것을 피할 이유가 없습니다.또한 나는 힙 할당이 비싸다는 것을 생각하지 않는다. (스마트 포인터와 함께 사용되는 경우) 이것을 피할 이유가 없다. –

+4

@Alex B : "블랙 마술"이 아니기 때문에 일종의 피할 수있는 이유가 있습니다. 모든 것이 템플릿 매개 변수를 통해 주입되면 작성한 모든 내용이 템플릿이고 라이브러리가 헤더 전용이며 컴파일 또는 배포 측면에서 상당히 복잡 할 수 있기 때문입니다. 주의 할 것은 헤더 전용 라이브러리를 단위 테스트 할 수 있다고 가정하고 응용 프로그램에 필요한 인스턴스화 만 포함하는 TU를 작성하십시오. –

+1

RAII와 DI가 함께 작동하므로 제목이 잘못 표시됩니다. 문제는 Stack Allocation 대 ​​DI입니다. –

답변

5

당신이 지금하고있는 일은 RAII 클래스와 서비스 공급자 클래스 ("테스트 결합력"을 원한다면 실제로 인터페이스 여야 함)를 "강제 결합"하는 것입니다.

  • 는 예를 들어 그
  • 위에 RAII를 제공하는 별도의 ScopedConnection 클래스가 IConnectionConnection을 추상화

    1. :에 의해이 문제를 해결함으로써

      void Some::foo(string address, IBar& bar) 
      { 
          onBeforeConnectionCreated(); 
          { 
           ScopedConnection conn(this->pFactory->getConnection()); 
           onConnectionCreated(); 
           bar.process(conn); 
          } 
          onConnectionClosed(); 
      } 
      
    +2

    그리고'ScopedConnection'을 조롱 할 필요가 없다는 것을 받아 들여,'Some :: foo'를 격리시킬 테스트 에서조차 실제 버전을 사용하는 것이 "안전합니다". 아니면 그건 받아 들일 수없는 경우, 당신의 치아를 모래와 템플릿 매개 변수로 주입, 또는 RAII를 제공하기 위해'scoped_ptr'을 사용하십시오. 표준 클래스 (또는 C++ 03을 사용하는 경우 제 3 자)는 허용되는 하드입니다 의존. –

    +0

    그건 내가 공장에 대해 쓴거야. 당신의 대답을 따르기 위해서, 저는 Connection만을위한 팩토리를 만들거나 (당신이 제안한 것처럼) 많은 비 관련 클래스를위한 팩토리를 생성해야합니다. 이 팩토리를 많은 레이어를 통해'Some'에 가져 오십시오 (또는 글로벌하게 만듭니다). – Abyx

    +0

    @Abyx : 공장은 DI를위한 주요 후보가 될 것이고, 수동으로 통과하거나 글로벌을 갖는 것이 바람직 할 것입니다. 그러나 당신은 그것을 추상화해야합니다. – Jon

    1

    를 "나는 사용할 수 있습니다 공장이지만 코드를 복잡하게 만들고 힙 할당을 도입 할 것이다 "나는 다음 단계를 의미했다 :

    unique_ptr<AConnection> conn(createConnection(address)); 
    
    1

    가 실제 사이에 선택하려면 스마트 포인터가 스택 할당 connecton를 교체 추상 클래스를 만들고

    struct AConnection : IInputStream 
    { 
        virtual ~AConnection() {} 
    }; 
    

    추가 공장 방법 Some

    class Some 
    { 
    ..... 
    protected: 
        VIRTUAL_UNDER_TEST AConnection* createConnection(string address); 
    }; 
    

    에서 Connection을 유도 구현 및 조롱 하나, 당신은 실제 타이 주입해야 pe 당신이 어떤 유행에서 건설하고 싶은. 권장하는 방법은 형식을 선택적 템플릿 매개 변수로 주입하는 것입니다. 예전처럼 Some::foo을 눈에 띄지 않게 사용할 수 있지만 테스트시 생성 된 연결을 바꿀 수 있습니다.

    template<typename ConnectionT=Connection> // models InputStream 
    void Some::foo(string address, IBar& bar) 
    { 
        onBeforeConnectionCreated(); 
        { 
         ConnectionT conn(address); 
         onConnectionCreated(); 
         bar.process(conn); 
        } 
        onConnectionClosed(); 
    } 
    

    컴파일시 실제 유형을 알고있는 경우 팩토리 및 런타임 다형성의 오버 헤드를 생성하지 않습니다.