단일 책임 및 사용자 정의 데이터 유형


10

지난 몇 달 동안 여기 SE 및 다른 사이트의 사람들에게 내 코드에 대해 건설적인 비판을 제공했습니다. 거의 매번 튀어 나온 한 가지가 있으며 여전히 그 권장 사항에 동의하지 않습니다. : P 여기에서 논의하고 싶습니다. 아마도 상황이 더 명확해질 것입니다.

단일 책임 원칙 (SRP)에 관한 것입니다. 기본적으로 데이터 조작 클래스 Font는 데이터 조작뿐만 아니라로드하는 함수를 보유합니다. 로드 함수는 팩토리 클래스 안에 있어야한다고 두 개는 분리되어야한다고 들었습니다. 나는 이것이 SRP의 잘못된 해석이라고 생각합니다 ...

내 서체 클래스의 조각

class Font
{
  public:
    bool isLoaded() const;
    void loadFromFile(const std::string& file);
    void loadFromMemory(const void* buffer, std::size_t size);
    void free();

    void some();
    void another();
};

제안 된 디자인

class Font
{
  public:
    void some();
    void another();
};


class FontFactory
{
  public:
    virtual std::unique_ptr<Font> createFromFile(...) = 0;
    virtual std::unique_ptr<Font> createFromMemory(...) = 0;
};

제안 된 디자인은 아마도 SRP를 따른다고 생각하지만 동의하지 않습니다. 너무 멀다고 생각합니다. 이 Font클래스는 더 이상 자급 자족하지 않고 (공장 없이는 쓸모가 없으며) FontFactory자원의 구현에 대한 세부 사항을 알아야합니다.이 구현은 우정이나 공개 게터를 통해 수행 될 수 Font있습니다. 나는 이것이 오히려 분열 된 책임 의 경우라고 생각한다 .

내 접근 방식이 더 낫다고 생각하는 이유는 다음과 같습니다.

  • Font자급 자족 — 자급 자족하기 때문에 이해하고 유지하기가 더 쉽습니다. 또한 다른 것을 포함하지 않고도 클래스를 사용할 수 있습니다. 그러나보다 복잡한 자원 관리 (공장)가 필요한 경우 쉽게 수행 할 수 있습니다 (나중에 공장에 대해 이야기하겠습니다 ResourceManager<Font>).

  • 표준 라이브러리를 따릅니다. 사용자 정의 형식은 표준 언어의 동작을 해당 언어로 복사하기 위해 가능한 한 많이 시도해야한다고 생각합니다. 는 std::fstream자급 자족이며 같은 기능을 제공 open하고 close. 표준 라이브러리를 따르면 또 다른 방법으로 학습하는 데 노력을 기울일 필요가 없습니다. 일반적으로 C ++ 표준위원회는 아마도 여기에있는 사람보다 디자인에 대해 더 많이 알고있을 것이므로 의심이가는 경우 그 일을 복사하십시오.

  • 테스트 가능성 — 문제가 발생했습니다. 문제는 어디에있을 수 있습니까? — Font데이터를 처리하는 방식 FontFactory입니까, 아니면 데이터를로드하는 방식 입니까? 당신은 정말로 모른다. 클래스를 자급 자족하게하면이 문제가 줄어 듭니다 Font. 별도로 테스트 할 수 있습니다 . 그런 다음 공장을 테스트 Font해야하지만 제대로 작동한다는 것을 알고 있다면 문제가 발생할 때마다 공장 내부에 있어야한다는 것을 알게됩니다.

  • 문맥에 구애받지 Font않습니다 . (이것은 내 첫 번째 요점과 약간 교차합니다.) 그 일을하고 그것을 어떻게 사용할 지에 대한 가정을하지 않습니다. 원하는대로 사용할 수 있습니다. 사용자가 팩토리를 사용하게하면 클래스 간의 커플 링이 증가합니다.

나도 공장을 가지고

(의 디자인 때문에 Font가능합니다.)

또는 단순히 공장 Font이 아니라 관리자의 수가 많기 때문에 자급 자족하기 때문에 관리자는이 를 구축 하는 방법 을 알 필요가 없습니다 . 대신 관리자는 동일한 파일 또는 버퍼가 메모리에 두 번 이상로드되지 않도록합니다. 당신은 공장이 똑같이 할 수 있다고 말할 수 있지만 SRP를 깨뜨리지 않을 것입니까? 그러면 팩토리는 오브젝트를 빌드 할뿐만 아니라 관리해야합니다.

template<class T>
class ResourceManager
{
  public:
    ResourcePtr<T> acquire(const std::string& file);
    ResourcePtr<T> acquire(const void* buffer, std::size_t size);
};

다음은 관리자 사용 방법에 대한 데모입니다. 기본적으로 팩토리와 동일하게 사용됩니다.

void test(ResourceManager<Font>* rm)
{
    // The same file isn't loaded twice into memory.
    // I can still have as many Fonts using that file as I want, though.
    ResourcePtr<Font> font1 = rm->acquire("fonts/arial.ttf");
    ResourcePtr<Font> font2 = rm->acquire("fonts/arial.ttf");

    // Print something with the two fonts...
}

결론 ...

(여기에는 tl; dr을 넣고 싶지만 하나는 생각할 수 없습니다. : \)
글쎄, 거기에 최선을 다해 최선을 다했습니다. 당신이 가진 반론과 제안 된 디자인이 내 자신의 디자인보다 유리하다고 생각하는 장점을 게시하십시오. 기본적으로 내가 틀렸다는 것을 보여주십시오. :)


2
Martin Fowler의 ActiveRecord vs DataMapper를 상기 시킵니다.
사용자

가장 바깥 쪽의 사용자 인터페이스에서 편리함 (현재 디자인)을 제공하십시오. 향후 구현 변경을 쉽게하기 위해 SRP를 내부적으로 사용하십시오. 기울임 꼴과 굵은 체를 건너 뛰는 Font loader 꾸밈을 생각할 수 있습니다. 유니 코드 BMP 등 만로드합니다.
rwong


@rwong 나는 그 프리젠 테이션에 대해 알고 있고, 그 북마크를 가지고 있었다 ( video ). :) 그러나 나는 당신이 다른 의견에서 당신이 말하는 것을 이해하지 못합니다 ...
Paul

1
@rwong 그것은 이미 하나의 라이너 아닌가요? Font를 직접로드하든 ResourceManager를 통해로드하든 관계없이 한 줄만 있으면됩니다. 사용자가 불만을 제기하면 RM을 다시 구현하지 못하게하는 요소는 무엇입니까?
Paul

답변:


7

내 의견으로는 그 코드에는 아무런 문제가 없으며, 현명하고 합리적으로 유지하기 쉬운 방법으로 필요한 것을 수행합니다.

그러나이 코드의 문제점은 다른 작업 을하려면 모든 코드를 변경해야한다는 것 입니다.

SRP의 요점은 알고리즘 A ()를 수행하는 단일 컴포넌트 'CompA'가 있고 알고리즘 A ()를 변경해야하는 경우 'CompB'도 변경할 필요가 없다는 것입니다.

내 C ++ 기술은 글꼴 관리 솔루션을 변경 해야하는 적절한 시나리오를 제안하기에는 너무 녹슬지 만 일반적인 경우는 캐싱 레이어에서 슬라이딩하는 아이디어입니다. 이상적으로는 물건을로드하는 물건이 어디에서 왔는지, 물건이 어디에서 왔는지 신경 쓰지 않기를 원합니다. 변경하는 것이 더 간단하기 때문입니다. 그것은 유지 관리에 관한 것입니다.

예를 들어, 세 번째 소스 (예 : 문자 스프라이트 이미지)에서 글꼴을로드 할 수 있습니다. 이를 위해서는 로더 (첫 번째 두 개가 실패하면 세 번째 메소드 호출)와 세 번째 호출을 구현하도록 Font 클래스 자체를 변경해야합니다. 이상적으로는 다른 팩토리 (SpriteFontFactory 또는 기타)를 작성하고 동일한 loadFont (...) 메소드를 구현 한 다음 글꼴을로드하는 데 사용할 수있는 팩토리 목록에 고정시키는 것이 좋습니다.


1
아, 알다시피 : 글꼴을로드하는 방법을 하나 더 추가하면 관리자에 하나 이상의 획득 기능을 추가하고 리소스에 하나 이상의로드 기능을 추가해야합니다. 실제로, 그것은 한 가지 단점입니다. 그러나이 새로운 소스가 무엇인지에 따라 데이터를 다르게 처리해야 할 것입니다 (TTF는 하나이고 글꼴 스프라이트는 다른 것입니다). 따라서 특정 디자인의 유연성을 실제로 예측할 수는 없습니다. 그래도 난 당신의 요점을 참조하십시오.
Paul

예, 내가 말했듯이 C ++ 기술은 꽤 녹슨 것이므로 문제에 대한 실용적인 데모를 제시하기 위해 고심했습니다. 유연성에 동의합니다. 내가 말했듯이 코드와 함께하는 일에 실제로 달려 있습니다. 원래 코드는 문제에 대한 완벽한 해결책이라고 생각합니다.
Ed James

좋은 질문과 대답은 여러 개발자가 배울 수 있다는 것입니다. 이것이 내가 여기서 놀고있는 것을 좋아하는 이유입니다 :). 아, 그래서 내 의견이 완전히 중복되지는 않습니다 .SRP는 '무엇을 해야하는지'스스로에게 물어봐야하기 때문에 조금 까다로울 수 있습니다. '조기 최적화는 모든 악의 근원'또는 야니의 철학. 흑백 답은 없습니다!
Martijn Verburg

0

수업에 대해 나를 괴롭히는 한 가지는 당신이 가지고 loadFromMemory있고 loadFromFile메소드라는 것입니다. 이상적으로는 loadFromMemory방법 만 있어야합니다 . 글꼴은 메모리의 데이터 상태를 신경 쓰지 않아야합니다. 또 다른 것은로드 및 free메소드 대신 생성자 / 소멸자를 사용해야한다는 것입니다 . 따라서, loadFromMemory될 것입니다 Font(const void *buf, int len)free()될 것입니다 ~Font().


로드 함수는 두 생성자에서 액세스 할 수 있으며 소멸자에서 free가 호출됩니다. 여기서는이를 표시하지 않았습니다. 먼저 파일을 열고 데이터를 버퍼에 쓴 다음 Font로 전달하는 대신 파일에서 직접 글꼴을로드하는 것이 편리하다는 것을 알았습니다. 때로는 버퍼에서로드해야하기 때문에 두 가지 방법이 모두 있습니다.
Paul
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.