Question

Au cours des derniers mois, j'ai demandé aux gens ici sur SE et sur d'autres sites m'offrent des critiques constructives concernant mon code. Il y a une chose qui a continué à sortir presque à chaque fois et je ne suis toujours pas d'accord avec cette recommandation; : P Je voudrais en discuter ici et peut-être que les choses deviendront plus claires pour moi.

Il s'agit du principe de responsabilité unique (SRP). Fondamentalement, j'ai une classe de données, Font, cela contient non seulement des fonctions pour manipuler les données, mais aussi pour les charger. On me dit que les deux devraient être séparés, que les fonctions de chargement doivent être placées à l'intérieur d'une classe d'usine; Je pense que c'est une mauvaise interprétation du SRP ...

Un fragment de ma classe de police

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();
};

Design suggéré

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


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

Le design suggéré suit censément le SRP, mais je ne suis pas d'accord - je pense qu'il va trop loin. La Font La classe n'est pas plus autosuffisante (elle est inutile sans l'usine), et FontFactory Doit connaître des détails sur la mise en œuvre de la ressource, qui se fait probablement par l'amitié ou les getters publics, qui exposent davantage la mise en œuvre de Font. Je pense que c'est plutôt un cas de responsabilité fragmentée.

Voici pourquoi je pense que mon approche est meilleure:

  • Font est autosuffisant - étant autosuffisant, il est plus facile de comprendre et de maintenir. En outre, vous pouvez utiliser la classe sans avoir à inclure autre chose. Si, cependant, vous constatez que vous avez besoin d'une gestion plus complexe des ressources (une usine), vous pouvez également le faire (plus tard je parlerai de ma propre usine, ResourceManager<Font>).

  • Suit la bibliothèque standard - Je pense que les types définis par l'utilisateur devraient essayer autant que possible pour copier le comportement des types standard dans ce langage respectif. La std::fstream est autosuffisant et il offre des fonctions comme open et close. Suivre la bibliothèque standard signifie qu'il n'est pas nécessaire de passer des efforts à apprendre une autre façon de faire les choses. En outre, d'une manière générale, le comité standard C ++ en sait probablement plus sur le design que quiconque ici, donc en cas de doute, copiez ce qu'ils font.

  • Testabilité - Quelque chose ne va pas, où pourrait être le problème? - est-ce le chemin Font gère ses données ou le chemin FontFactory chargé les données? Vous ne savez pas vraiment. Avoir les classes autosuffisantes réduit ce problème: vous pouvez tester Font en isolement. Si vous devez alors tester l'usine et vous savez Font Fonctionne bien, vous saurez également que chaque fois qu'un problème se produit, il doit être à l'intérieur de l'usine.

  • C'est le contexte agnostique - (cela se croit un peu avec mon premier point.) Font fait son truc et ne fait aucune hypothèse sur la façon dont vous l'utiliserez: vous pouvez l'utiliser comme vous le souhaitez. Forcer l'utilisateur à utiliser une usine augmente le couplage entre les classes.

Moi aussi, j'ai une usine

(Parce que la conception de Font me permet de le faire.)

Ou plutôt plus un manager, pas seulement une usine ... Font est autosuffisant pour que le manager n'ait pas besoin de savoir comment en construire un; Au lieu de cela, le gestionnaire s'assure que le même fichier ou tampon n'est pas chargé dans la mémoire plus d'une fois. On pourrait dire qu'une usine peut faire de même, mais cela ne casserait-il pas le SRP? L'usine devrait alors non seulement construire des objets, mais aussi les gérer.

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

Voici une démonstration de la façon dont le gestionnaire pourrait être utilisé. Notez qu'il est utilisé exactement comme une usine.

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...
}

Conclusion ...

(Il aimerait mettre un tl; dr ici, mais je ne peux pas en penser.: )
Eh bien, là, vous l'avez, j'ai fait de mon mieux que possible. Veuillez publier tous les contre-arguments que vous avez et également tous les avantages que vous pensez que la conception suggérée a sur ma propre conception. Fondamentalement, essayez de me montrer que je me trompe. :)

Pas de solution correcte

Licencié sous: CC-BY-SA avec attribution
scroll top