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


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.