Les classes avec une seule méthode (publique) posent-elles un problème?


51

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 compressméthode qui prend une vidéo d'entrée de type RawVideoet renvoie une vidéo de sortie de type CompressedVideo).
  • VideoSplitter (avec une splitméthode qui prend une vidéo d'entrée de type RawVideoet renvoie un vecteur de 2 vidéos de sortie, chacune de type RawVideo).
  • VideoIndexer (avec une indexméthode qui prend une vidéo d'entrée de type RawVideoet renvoie un index vidéo de type VideoIndex).

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?


14
Cela dépend de la langue. Dans un langage multi-paradigme tel que C ++ ou Python, ces classes n'ont aucune activité commerciale existante: leurs "méthodes" doivent être des fonctions libres.

3
@delnan: même en C ++, vous utilisez généralement des classes pour créer des modules, même si vous n'avez pas besoin des «capacités OO» complètes. En effet, il existe une alternative consistant à utiliser des espaces de noms pour regrouper des "fonctions libres" dans un module, mais je ne vois aucun avantage à cela.
Doc Brown

5
@ DocBrown: C ++ a des espaces de noms pour cela. En fait, dans le C ++ moderne, vous utilisez souvent des fonctions libres pour les méthodes, car elles peuvent être surchargées (de manière statique) pour tous les arguments, tandis que les méthodes ne peuvent être surchargées que pour l'appelant.
Jan Hudec le

2
@DocBrown: C'est un coeur même de la question. Les modules utilisant des espaces de noms n'ayant qu'une méthode "externe" conviennent parfaitement lorsque la fonction est suffisamment complexe. Parce que les espaces de noms ne prétendent rien représenter et ne prétendent pas être orientés objet. Les classes le font, mais les classes comme celle-ci ne sont en réalité que des espaces de noms. Bien sûr, en Java, vous ne pouvez pas avoir de fonction, c'est donc le résultat. Honte à Java.
Jan Hudec

2
Connexes (presque un double): programmers.stackexchange.com/q/175070/33843
Heinzi

Réponses:


87

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!


6
C'est la bonne réponse, et je l'ai votée, mais vous pourriez l'améliorer en expliquant certains des avantages. Je pense que ce que l'instinct de l'OP lui dit est un problème, c'est autre chose ... c'est-à-dire que VideoCompressor est vraiment une interface. Mp4VideoCompressor est une classe et doit être interchangeable avec un autre VideoCompressor sans copier le code d'un VideoSplitter dans une nouvelle classe.
pdr

1
Désolé mais tu te trompes. Une classe avec une seule méthode ne devrait être qu'une fonction autonome.
Miles Rout

3
@MilesRout: votre commentaire indique que vous avez mal compris la question - le PO a écrit sur une classe avec une méthode publique, pas avec une méthode (et il parlait en fait d'une classe avec plusieurs méthodes privées, comme il nous l'a dit ici dans un commentaire).
Doc Brown

2
"Une classe utilisant une seule méthode ne devrait être qu'une fonction autonome." Hypothèse grossière sur la nature de cette méthode. J'ai une classe avec une méthode publique. BEHIND IT sont plusieurs méthodes de cette classe, plus cinq autres classes qui n'ont absolument aucune idée du client. Une excellente application de SRP produira une interface simple et nette de la structure de classe en couches, avec cette simplicité qui se manifeste jusqu'au sommet.
radarbob

2
@Andy: la question était "est-ce un problème" et non "cela est-il conforme à une définition spécifique de OO"? De plus, il n’ya absolument aucun signe dans la question que OP signifie des classes sans état.
Doc Brown

26

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::functionou 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? ).


12
"Pourquoi prétendre que c'est un cours alors qu'il ne l'est pas?" Avoir un objet au lieu d'une fonction libre permet à l'état et au sous-typage. Cela pourrait s'avérer précieux dans un monde où les exigences changent. Tôt ou tard, il est nécessaire de jouer avec les paramètres de compression vidéo ou de fournir des algorithmes de compression alternatifs. Avant cela, le mot "classe" nous indique simplement que cette fonction appartient à un module logiciel facilement extensible et interchangeable avec une responsabilité claire. N’est-ce pas ce que vise vraiment OO?
VENEZ DU

1
Eh bien, si elle n’a qu’une méthode publique (et ne comporte donc aucune méthode protégée), elle ne peut pas vraiment être étendue. Bien entendu, le compactage des paramètres de compression peut avoir un sens, auquel cas il devient un objet fonction (certaines langues ont un support séparé pour celles-là, d’autres pas).
Jan Hudec

3
Il s'agit d'un lien fondamental entre les objets et les fonctions: une fonction est isomorphe à un objet avec une seule méthode et aucun champ. Une fermeture est isomorphe à un objet avec une seule méthode et certains champs. Une fonction de sélecteur renvoyant une parmi plusieurs fonctions qui se ferment toutes sur le même ensemble de variables est isomorphe à un objet. (En fait, c'est comme cela que les objets sont codés en JavaScript, sauf que vous utilisez une structure de données de dictionnaire au lieu d'une fonction de sélecteur.)
Jörg W Mittag Le

4
"Ce n'est plus orienté objet. Parce que ces classes ne représentent rien, ce ne sont que des vaisseaux pour les fonctions." - C'est faux. Je dirais que cette approche est PLUS orientée objet. OO ne signifie pas qu'il représente un objet du monde réel. Une grande partie de la programmation traite de concepts abstraits, mais cela signifie-t-il que vous ne pouvez pas appliquer une approche orientée objet pour le résoudre? Définitivement pas. C'est plus une question de modularité et d'abstraction. Chaque objet a une responsabilité unique. Cela facilite la compréhension du programme et les modifications. Pas assez d’espace pour énumérer les nombreux avantages de la programmation orientée objet.
Despertar

2
@Phoshi: Oui, je comprends. Je n'ai jamais prétendu qu'une approche fonctionnelle ne fonctionnerait pas aussi bien. Cependant, ce n'est clairement pas le sujet. Un compresseur vidéo ou un transmogrifier vidéo ou autre est toujours un candidat parfaitement valable pour un objet.
VENEZ DU

13

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 VideoExporterqui, à terme, devrait pouvoir compresser une vidéo. Une façon propre serait d'avoir une interface:

interface IVideoCompressor
{
    Stream compress(Video video);
}

qui serait implémenté comme ceci:

class MpegVideoCompressor : IVideoCompressor
{
    // ...
}

class FlashVideoCompressor : IVideoCompressor
{
    // ...
}

et utilisé comme ceci:

class VideoExporter
{
    // ...
    void export(Destination destination, IVideoCompressor compressor)
    {
        // ...
        destination = compressor(this.source);
        // ...
    }
    // ...
}

Une mauvaise alternative serait d'avoir une VideoExportermé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.


2
Votre réponse ne fait pas la distinction entre les méthodes publiques et privées. Cela pourrait être compris comme une recommandation pour mettre tout le code d’une classe d’une seule méthode, mais je suppose que ce n’est pas ce que vous vouliez dire?
Doc Brown le

2
@ DocBrown: les méthodes privées ne sont pas pertinentes pour cette question. Ils peuvent être placés dans une classe d'assistance interne ou autre.
Jan Hudec

2
@JanHudec: actuellement, le texte est "Une mauvaise alternative serait d'avoir un VideoExporter qui a beaucoup de méthodes" - mais cela devrait être "Une mauvaise alternative serait d'avoir un VideoExporter qui a beaucoup de méthodes publiques ".
Doc Brown

@DocBrown: d'accord ici.
Jan Hudec

2
@DocBrown: merci pour vos remarques. J'ai édité ma réponse. À l'origine, je pensais que l'on supposait que la question (et donc ma réponse) concernait uniquement les méthodes publiques. Il semble que ce ne soit pas aussi évident.
Arseni Mourzenko

6

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.


1
Depuis Java 8, vous avez des lambdas, vous pouvez donc passer des fonctions.
Silviu Burcea

Bon point, mais cela ne sera pas officiellement publié avant un peu plus longtemps, et certains lieux de travail tardent à passer à des versions plus récentes.
Doval

La date de sortie est en mars. En outre, les versions EAP étaient très populaires pour JDK 8 :)
Silviu Burcea

Bien que les lambdas Java 8 ne soient qu'un moyen abrégé de définir des objets avec une seule méthode publique, mis à part la sauvegarde d'un peu de code passe-partout, ils ne font ici aucune différence.
Jules

5

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 VideoCompressoret avoir plusieurs implémentations alternatives, par exemple, class H264Compressor implements VideoCompressoret class 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.


1
public interface IVideoProcessor
{
   void Split();

   void Compress();

   void Index();
}

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.


Comme la raison pour laquelle chacune de ces fonctions doit être modifiée est quelque peu différente, je dirais que les regrouper comme ceci constitue une violation du PRS.
Jules

0

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

string mystring("Hello"); 
mystring.concat("World");

mais le PO veut que cela fonctionne comme ceci:

string mystring();
string result = mystring.concat("Hello", "World");

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.


9
-1, je suis totalement en désaccord. La conception OO pour débutants ne fonctionne qu'avec des objets de données comme un Videoet 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 un VideoCompressor(et laisse une Videoclasse juste une classe de données ou une façade pour le VideoCompressor).
Doc Brown le

5
@ DocBrown: À ce stade, il ne s'agit toutefois plus d'une conception orientée objet, car VideoCompressorelle 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.
Jan Hudec le

3
ah mais les débutants OO où 1 fonction est transformée en classe n'est pas vraiment OO du tout, et encourage seulement le découplage massif qui aboutit à un millier de fichiers de classes que personne ne peut maintenir. Je pense qu'il est préférable de considérer une classe comme un "encapsuleur de données" et non comme un "encapsuleur de fonctionnalités", ce qui donnera aux débutants une meilleure compréhension de la façon de penser aux programmes OO.
gbjbaanb

4
@DocBrown a en fait mis en évidence avec précision mon inquiétude; J'ai effectivement une Videoclasse, mais la méthode de compression n'est en aucun cas triviale, alors je la décomposerais compressen 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 verbes
yjwong

2
Je pense qu'il pourrait être autorisé à avoir une Videoclasse, mais il serait composée d'un VideoCompressor, un VideoSplitter, et d' autres classes connexes, ce qui devrait, en bonne forme OO, être des classes individuelles très cohérentes.
Eric King

-1

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

Je me trouve instancier chaque classe

, ces classes semblent être des services (et donc des apatrides). Avez-vous pensé à en faire des singletons?



3
Pour le reste, alors que le principe de séparation des interfaces est un bon modèle, cette question concerne les implémentations, pas les interfaces, donc je ne suis pas sûr que cela soit pertinent.
Jan Hudec

3
Je ne vais pas entrer pour discuter si le singleton est un motif ou un anti-modèle. J'ai suggéré une solution possible (il a même un nom) à son problème, c'est à lui de décider s'il convient ou non.
diegomtassis

Quant à savoir si le FAI est pertinent ou non, eh bien, le sujet de la question est "Les classes avec une seule méthode posent-elles problème?" client en dépend directement ... exactement l'exemple de xerox de Robert Martin qu'il lui a fallu pour formuler le FAI.
diegomtassis

-1

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.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.