Je travaille actuellement sur un projet logiciel qui effectue la compression et l'indexation sur des images de vidéosurveillance. La compression consiste à scinder les objets d’arrière-plan et d’avant-plan, puis à enregistrer l’arrière-plan en tant qu’image statique et l’avant-plan en tant qu’image-objet.
Récemment, j'ai entrepris de revoir certaines des classes que j'ai conçues pour le projet.
J'ai remarqué qu'il y a beaucoup de classes qui n'ont qu'une seule méthode publique. Certaines de ces classes sont:
- VideoCompressor (avec une
compress
méthode qui prend une vidéo d'entrée de typeRawVideo
et renvoie une vidéo de sortie de typeCompressedVideo
). - VideoSplitter (avec une
split
méthode qui prend une vidéo d'entrée de typeRawVideo
et renvoie un vecteur de 2 vidéos de sortie, chacune de typeRawVideo
). - VideoIndexer (avec une
index
méthode qui prend une vidéo d'entrée de typeRawVideo
et renvoie un index vidéo de typeVideoIndex
).
Je me trouve instancier chaque classe juste pour faire des appels comme VideoCompressor.compress(...)
, VideoSplitter.split(...)
, VideoIndexer.index(...)
.
En apparence, j'estime que les noms de classe décrivent suffisamment la fonction à laquelle ils sont destinés et qu'il s'agit en réalité de noms. De manière correspondante, leurs méthodes sont également des verbes.
Est-ce réellement un problème?
Réponses:
Non, ce n'est pas un problème, bien au contraire. C'est un signe de modularité et de responsabilité claire de la classe. L’interface allégée est facile à comprendre du point de vue d’un utilisateur de cette classe et elle encouragera un couplage lâche. Cela présente de nombreux avantages mais presque aucun inconvénient. Je souhaite que plus de composants soient conçus de cette façon!
la source
Ce n'est plus orienté objet. Parce que ces classes ne représentent rien, ce ne sont que des vaisseaux pour les fonctions.
Cela ne veut pas dire que c'est faux. Si la fonctionnalité est suffisamment complexe ou générique (c'est-à-dire que les arguments sont des interfaces et non des types finaux concrets), il est logique de placer cette fonctionnalité dans un module séparé .
A partir de là, cela dépend de votre langue. Si la langue a des fonctions libres, il devrait s'agir de fonctions d'exportation de modules. Pourquoi prétendre que c'est un cours alors que ça ne l'est pas? Si le langage n'a pas de fonctions libres comme Java, par exemple, vous créez des classes avec une seule méthode publique. Cela montre bien les limites de la conception orientée objet. Parfois, fonctionnel est tout simplement mieux correspondre.
Il existe un cas où vous pourriez avoir besoin d'une classe avec une méthode publique unique car elle doit implémenter une interface avec une méthode publique unique. Que ce soit pour un modèle d'observateur ou une injection de dépendance ou autre. Ici encore, cela dépend de la langue. Dans les langages dotés de foncteurs de première classe (C ++ (
std::function
ou paramètre de modèle), C # (délégué), Python, Perl, Ruby (proc
), Lisp, Haskell, ...), ces modèles utilisent des types de fonction et ne nécessitent pas de classes. Java n'a pas (encore, sera dans la version 8) de types de fonction, vous utilisez donc des interfaces de méthode uniques et les classes de méthodes uniques correspondantes.Bien sûr, je ne préconise pas l'écriture d'une seule fonction énorme. Il doit avoir des sous-routines privées, mais elles peuvent être privées pour le fichier d'implémentation (espace de noms statique ou anonyme au niveau fichier en C ++) ou dans une classe d'assistance privée instanciée uniquement dans la fonction publique ( stocker des données ou non? ).
la source
Il peut y avoir des raisons d'extraire une méthode donnée dans une classe dédiée. L'une de ces raisons est d'autoriser l'injection de dépendance.
Imaginez que vous ayez une classe appelée
VideoExporter
qui, à terme, devrait pouvoir compresser une vidéo. Une façon propre serait d'avoir une interface:qui serait implémenté comme ceci:
et utilisé comme ceci:
Une mauvaise alternative serait d'avoir une
VideoExporter
méthode qui a beaucoup de méthodes publiques et fait tout le travail, y compris la compression. Cela deviendrait rapidement un cauchemar de maintenance, rendant difficile l'ajout de la prise en charge d'autres formats vidéo.la source
C'est un signe que vous souhaitez transmettre des fonctions en tant qu'arguments à d'autres fonctions. Je suppose que votre langage (Java?) Ne le supporte pas; Si tel est le cas, ce n'est pas tant un défaut de votre conception que c'est un défaut de la langue de votre choix. C'est l'un des plus gros problèmes avec les langues qui insistent sur le fait que tout doit être un cours.
Si vous ne transmettez pas réellement ces fausses fonctions, vous voulez juste une fonction libre / statique.
la source
Je sais que je suis en retard à la fête mais comme tout le monde semble avoir manqué de le dire:
C'est un modèle de conception bien connu appelé: Modèle de stratégie .
Le modèle de stratégie est utilisé lorsqu'il existe plusieurs stratégies possibles pour résoudre un sous-problème. En règle générale, vous définissez une interface qui applique un contrat sur toutes les mises en œuvre, puis vous utilisez une forme d' injection de dépendance pour vous fournir une stratégie concrète.
Par exemple, dans ce cas, vous pouvez avoir
interface VideoCompressor
et avoir plusieurs implémentations alternatives, par exemple,class H264Compressor implements VideoCompressor
etclass XVidCompressor implements VideoCompressor
. OP n'indique pas clairement qu'il existe une interface, même si ce n'est pas le cas, il se peut simplement que l'auteur original ait laissé la porte ouverte pour mettre en œuvre un modèle de stratégie si nécessaire. Ce qui en soi est un bon design aussi.Le problème qui se pose constamment à OP d'instancier les classes pour appeler une méthode est qu'elle ne utilise pas correctement l'injection de dépendance et le modèle de stratégie. Au lieu de l'instancier où vous en avez besoin, la classe contenante devrait avoir un membre avec l'objet de stratégie. Et ce membre doit être injecté, par exemple dans le constructeur.
Dans de nombreux cas, le modèle de stratégie aboutit à des classes d'interface (comme vous le montrez) avec une seule
doStuff(...)
méthode.la source
Ce que vous avez est modulaire et c'est bien, mais si vous regroupiez ces responsabilités dans IVideoProcessor, cela aurait probablement plus de sens du point de vue de DDD.
D'un autre côté, si le fractionnement, la compression et l'indexation n'étaient aucunement liés, je les conserverais en tant que composants distincts.
la source
C'est un problème - vous travaillez à partir de l'aspect fonctionnel de la conception, plutôt que des données. Ce que vous avez réellement sont 3 fonctions autonomes qui ont été OO-ified.
Par exemple, vous avez une classe VideoCompressor. Pourquoi travaillez-vous avec une classe conçue pour compresser la vidéo - pourquoi ne disposez-vous pas d'une classe Video avec des méthodes permettant de compresser les données (vidéo) que chaque objet de ce type contient?
Lors de la conception de systèmes OO, il est préférable de créer des classes représentant des objets plutôt que des classes représentant des activités que vous pouvez appliquer. A l'époque, les classes étaient appelées types - OO était un moyen d'étendre un langage avec la prise en charge de nouveaux types de données. Si vous pensez à OO comme ceci, vous obtenez un meilleur moyen de concevoir vos classes.
MODIFIER:
laissez-moi essayer de m'expliquer un peu mieux, imaginez une classe de chaînes qui a une méthode concat. Vous pouvez implémenter une telle chose où chaque objet instancié à partir de la classe contient les données de chaîne, vous pouvez donc dire
mais le PO veut que cela fonctionne comme ceci:
Il existe maintenant des endroits où une classe peut être utilisée pour contenir une collection de fonctions associées, mais ce n'est pas OO, c'est un moyen pratique d'utiliser les fonctionnalités OO d'un langage pour aider à mieux gérer votre base de code, mais ce n'est en aucun cas une sorte de "OO Design". L'objet dans de tels cas est totalement artificiel, simplement utilisé comme ceci car le langage n'offre rien de mieux pour gérer ce genre de problème. par exemple. Dans des langages tels que C #, vous utiliseriez une classe statique pour fournir cette fonctionnalité. Il réutilise le mécanisme de classe, mais vous n'avez plus besoin d'instancier un objet pour simplement appeler les méthodes. Vous vous retrouvez avec des méthodes comme celles
string.IsNullOrEmpty(mystring)
que je trouve médiocres par rapport àmystring.isNullOrEmpty()
.Donc, si quelqu'un demande "comment puis-je concevoir mes classes", je vous recommande de penser aux données que la classe contiendra plutôt qu'aux fonctions qu'elle contient. Si vous optez pour le "une classe est un tas de méthodes", alors vous finissez par écrire du code de style "meilleur C". (ce qui n’est pas nécessairement une mauvaise chose si vous améliorez le code C) mais cela ne vous donnera pas le meilleur programme conçu par OO.
la source
Video
et tend à surcharger de telles classes avec des fonctionnalités, ce qui aboutit souvent à un code compliqué avec> 10K LOC par classe. La conception OO avancée décompose la fonctionnalité en unités plus petites comme unVideoCompressor
(et laisse uneVideo
classe juste une classe de données ou une façade pour leVideoCompressor
).VideoCompressor
elle ne représente pas un objet . Il n'y a rien de mal à cela, cela montre juste la limite de la conception orientée objet.Video
classe, mais la méthode de compression n'est en aucun cas triviale, alors je la décomposeraiscompress
en plusieurs autres méthodes privées. Cela fait partie de la raison de ma question, après avoir lu Ne créez pas de classes de verbesVideo
classe, mais il serait composée d'unVideoCompressor
, unVideoSplitter
, et d' autres classes connexes, ce qui devrait, en bonne forme OO, être des classes individuelles très cohérentes.Le principe de séparation des interfaces ( ISP ) indique qu'aucun client ne doit être forcé de dépendre de méthodes qu'il n'utilise pas. Les avantages sont multiples et clairs. Votre approche respecte totalement le fournisseur de services Internet, et c'est bien.
Une approche différente, respectant également le fournisseur de services Internet, consiste par exemple à créer une interface pour chaque méthode (ou un ensemble de méthodes à forte cohésion), puis à avoir une classe unique implémentant toutes ces interfaces. Que ce soit ou non une meilleure solution dépend du scénario. L'avantage de cela est que, lorsque vous utilisez l'injection de dépendance, vous pouvez avoir un client avec différents collaborateurs (un pour chaque interface), mais à la fin, tous les collaborateurs pointeront vers la même instance d'objet.
Au fait, vous avez dit
, ces classes semblent être des services (et donc des apatrides). Avez-vous pensé à en faire des singletons?
la source
Oui, il y a un problème. Mais pas grave. Je veux dire que vous pouvez structurer votre code comme ceci et que rien de grave ne se produira, il est maintenable. Mais ce type de structuration présente certaines faiblesses. Par exemple, si la représentation de votre vidéo (dans votre cas, elle est regroupée dans la classe RawVideo) change, vous devrez mettre à jour toutes vos classes d'opérations. Ou considérez que vous pouvez avoir plusieurs représentations de la vidéo dont l'exécution varie. Ensuite, vous devrez faire correspondre la représentation à une classe "d'opération" particulière. Sur le plan subjectif, il est également agaçant de faire glisser une dépendance pour chaque opération que vous souhaitez effectuer. et pour mettre à jour la liste des dépendances transmises chaque fois que vous décidez que l'opération n'est plus nécessaire ou que vous avez besoin d'une nouvelle opération.
En outre, c'est en réalité une violation du PÉR. Certaines personnes considèrent simplement le PRS comme un guide pour la division des responsabilités (et vont trop loin en considérant chaque opération comme une responsabilité distincte), mais elles oublient que le PRS sert également de guide pour le regroupement des responsabilités. Et, selon le PRS, les responsabilités qui changent pour la même raison doivent être regroupées afin que, si des changements se produisent, ils soient localisés dans un nombre de classes / modules aussi réduit que possible. En ce qui concerne les grandes classes, le fait d’avoir plusieurs algorithmes dans la même classe n’est pas un problème tant que ces algorithmes sont liés (c’est-à-dire qu’ils partagent certaines connaissances qui ne devraient pas être connues en dehors de cette classe). Le problème est que les grandes classes ont des algorithmes qui ne sont aucunement liés et changent / varient pour différentes raisons.
la source