Disons que nous faisons un analyseur. Une mise en œuvre pourrait être:
public sealed class Parser1
{
public string Parse(string text)
{
...
}
}
Ou nous pourrions passer le texte au constructeur à la place:
public sealed class Parser2
{
public Parser2(string text)
{
this.text = text;
}
public string Parse()
{
...
}
}
L'utilisation est simple dans les deux cas, mais qu'est-ce que cela signifie d'activer la saisie des paramètres Parser1
par rapport à l'autre? Quel message ai-je envoyé à un collègue programmeur, quand il regarde l'API? Existe-t-il également des avantages / inconvénients techniques dans certains cas?
Une autre question se pose lorsque je me rends compte qu'une interface n'aurait aucun sens dans la deuxième implémentation:
public interface IParser
{
string Parse();
}
... où une interface sur la première pourrait au moins servir à quelque chose. Cela signifie-t-il quelque chose en particulier, qu'une classe est "interfaçable" ou non?
object-oriented
interfaces
methods
construction
ciscoheat
la source
la source
Réponses:
Sémantiquement, dans la POO, vous ne devez transmettre au constructeur qu'un ensemble de paramètres nécessaires à la construction de la classe - de même, lorsque vous appelez une méthode, vous ne devez lui transmettre que les paramètres dont il a besoin pour exécuter sa logique métier.
Les paramètres que vous devez passer dans le constructeur sont des paramètres qui n'ont pas de valeur par défaut sensible et si votre classe est immuable (ou bien a
struct
), alors les propriétés toutes non par défaut doivent être passées.Concernant vos deux exemples:
text
au constructeur, cela indique que laParser2
classe sera spécifiquement construite pour analyser cette instance de texte ultérieurement. Ce sera un analyseur spécifique. C'est généralement le cas lorsque la construction de la classe est très coûteuse ou subtile, un RegEx peut être compilé dans le constructeur, donc une fois que vous détenez une instance, vous pouvez la réutiliser sans avoir à payer le coût de la compilation; un autre exemple est l'initialisation du PRNG - il vaut mieux que cela soit fait rarement.text
à la méthode, elle signale qu'elleParser1
peut être réutilisée pour analyser différents textes par des appels.la source
Eh bien, rappelons ce que signifie passer une variable comme paramètre constructeur: vous initialisez un objet afin d'utiliser ses variables d'instance dans les méthodes de l'objet. Le fait est que vous voulez probablement l'utiliser dans plusieurs méthodes, car vous voulez avoir une cohésion élevée dans votre classe.
Passer un paramètre directement à une méthode signifie en quelque sorte envoyer un message à un objet et probablement recevoir une réponse. Par cela, le client veut que l'objet lui fournisse un service.
Donc, en conclusion, ce sont deux moyens très différents de transmettre des paramètres et vous devez choisir si votre objet doit fournir un service ou fournir des fonctionnalités de manière inhérente tout en gérant certaines informations en interne.
la source
C'est un changement de conception fondamental. Et le design doit transmettre l'intention et le sens. Avez-vous besoin d'avoir des objets séparés pour chaque chaîne que vous souhaitez analyser? En d'autres termes, pourquoi avons-nous besoin d'une instance d'analyseur avec stringX et d'une autre instance avec stringY? Qu'en est-il de l'analyse et de la chaîne donnée que les deux doivent vivre et mourir ensemble? En supposant que «l'implémentation [d'analyse] sous-jacente» (comme le dit Robert Harvey) ne change pas, cela ne semble d'aucune utilité. Et même alors son IMHO discutable.
Les paramètres du constructeur me disent que ces choses sont requises pour un objet. Un bon état n'est pas garanti sans eux. De plus, je sais comment / pourquoi un analyseur est fondamentalement différent d'un autre.
Les paramètres du constructeur m'empêchent d'avoir à en savoir trop sur la façon d'utiliser la classe. Si, à la place, je suis censé définir certaines propriétés - comment savoir cela? Toute une boîte de vers s'ouvre. Quelles propriétés? Dans quel ordre? Avant d'utiliser quelles méthodes? etc.
Une interface, comme dans l'API, est les méthodes et propriétés exposées au code client. Ne vous enveloppez pas
public interface { ... }
exclusivement. Ainsi, la signification de l'interface est dans le dilemme du paramètre soit-ou constructeur vs méthode, PASpublic interface Iparser
vspublic sealed class Parser
La
sealed
classe est bizarre. Si je pense à différentes implémentations d'analyseur - vous avez mentionné "Iparser" - alors l'héritage est ma première pensée. C'est juste une extension conceptuelle naturelle de ma pensée. IE tous lesParserX
s sont fondamentalementParser
s. Comment le dire autrement? ... Un berger allemand est un chien (héritage), mais je peux entraîner mon perroquet à aboyer (agir comme un chien - "interface"); mais Polly n'est pas un chien, prétendant simplement, ayant appris un sous-ensemble de dogness. Les classes, abstraites ou non, servent parfaitement d' interfaces .la source
La deuxième version de la classe peut être rendue immuable.
L'interface peut toujours être utilisée pour permettre d'échanger l'implémentation sous-jacente.
la source
Parser1
Construire avec un constructeur par défaut et passer du texte d'entrée dans une méthode implique que Parser1 est réutilisable.
Parser2
Passer le texte d'entrée dans le constructeur implique qu'un nouveau Parser2 doit être créé pour chaque chaîne d'entrée.
la source