La réponse de Doc Brown montre une implémentation classique de Law of Demeter dans les manuels scolaires - et l'agacement / le désordre de code désorganisé consistant à ajouter des dizaines de méthodes de cette façon est probablement la raison pour laquelle les programmeurs, y compris moi-même, ne se donnent souvent pas la peine de le faire, même s'ils le devraient.
Il existe un autre moyen de découpler la hiérarchie des objets:
Exposez les interfacetypes, plutôt que les classtypes, via vos méthodes et propriétés.
Dans le cas de Poster Original (OP), encoder->WaitEncoderFrame()renverrait un IEncoderFrameau lieu de Frameet définirait les opérations autorisées.
SOLUTION 1
Dans le cas le plus simple, Frameet les Encoderclasses sont à la fois sous votre contrôle, IEncoderFrameest un sous - ensemble de méthodes Cadre déjà publiquement expose, et la Encoderclasse ne se soucie pas vraiment ce que vous faites à cet objet. Ensuite, la mise en œuvre est triviale ( code en c # ):
interface IEncoderFrame {
void DoOrGetSomething();
}
class Frame : IEncoderFrame {
// A method that already exists in Frame.
public void DoOrGetSomething() { ... }
}
class Encoder {
private Frame _frame;
public IEncoderFrame TheFrame { get { return _frame; } }
...
}
SOLUTION 2
Dans un cas intermédiaire, où la Framedéfinition n'est pas sous votre contrôle, ou s'il ne serait pas approprié d'ajouter IEncoderFramedes méthodes à Frame, une bonne solution est alors un adaptateur . C’est ce que la réponse de CandiedOrange explique, en tant que new FrameHandler( frame ). IMPORTANT: si vous faites cela, il est plus flexible si vous l'exposez en tant qu'interface et non en tant que classe . Encoderdoit savoir class FrameHandler, mais les clients doivent seulement savoir interface IFrameHandler. Ou comme je l’ai nommé, interface IEncoderFrame- pour indiquer qu’il s’agit spécifiquement de Frame vu de POV de Encoder :
interface IEncoderFrame {
void DoOrGetSomething();
}
// Adapter pattern. Appropriate if no access needed to Encoder.
class EncoderFrameWrapper : IEncoderFrame {
Frame _frame;
public EncoderFrameWrapper( Frame frame ) {
_frame = frame;
}
public void DoOrGetSomething() {
_frame....;
}
}
class Encoder {
private Frame _frame;
// Adapter pattern. Appropriate if no access needed to Encoder.
public IEncoderFrame TheFrame { get { return new EncoderFrameWrapper( _frame ); } }
...
}
COST: Allocation et validation d'un nouvel objet, EncoderFrameWrapper, à chaque encoder.TheFrameappel. (Vous pouvez mettre en cache ce wrapper, mais cela ajoute plus de code. Et ce n'est facile de coder de manière fiable que si le champ frame de l'encodeur ne peut pas être remplacé par un nouveau frame.)
SOLUTION 3
Dans le cas le plus difficile, le nouvel emballage doit connaître à la fois Encoderet Frame. Cet objet violerait lui-même le LoD - il manipule une relation entre Encoder et Frame qui devrait incomber à Encoder - et il serait probablement difficile de bien faire les choses. Voici ce qui peut arriver si vous vous engagez dans cette voie:
interface IEncoderFrame {
void DoOrGetSomething();
}
// *** You will end up regretting this. See next code snippet instead ***
class EncoderFrameWrapper : IEncoderFrame {
Encoder _owner;
Frame _frame;
public EncoderFrameWrapper( Encoder owner, Frame frame ) {
_owner = owner; _frame = frame;
}
public void DoOrGetSomething() {
_frame.DoOrGetSomething();
// Hmm, maybe this wrapper class should be nested inside Encoder...
_owner... some work inside owner; maybe should be owner-internal details ...
}
}
class Encoder {
private Frame _frame;
...
}
C'est devenu moche. Il existe une implémentation moins compliquée, lorsque le wrapper doit toucher les détails de son créateur / propriétaire (Encoder):
interface IEncoderFrame {
void DoOrGetSomething();
}
class Encoder : IEncoderFrame {
private Frame _frame;
// HA! Client gets to think of this as "the frame object",
// but its really me, intercepting it.
public IEncoderFrame TheFrame { get { return this; } }
// This is the method that the LoD approach suggests writing,
// except that we are exposing it only when the instance is accessed as an IEncoderFrame,
// to avoid extending Encoder's already large API surface.
public void IEncoderFrame.DoOrGetSomething() {
_frame.DoOrGetSomething();
... make some change within current Encoder instance ...
}
...
}
Certes, si je savais que je finirais ici, je ne ferais peut-être pas cela. Pourrait juste écrire les méthodes LoD, et en finir avec ça. Pas besoin de définir une interface. D'un autre côté, j'aime bien que l'interface encapsule les méthodes associées. J'aime ce que l'on ressent lorsque l'on fait les "opérations de type cadre" pour créer ce qui ressemble à un cadre.
COMMENTAIRES FINAUX
Considérez ceci: si le réalisateur de Encoderestimait que l'exposition Frame frameétait appropriée à leur architecture globale, ou était "tellement plus facile que de mettre en œuvre un LoD", il aurait été beaucoup plus sûr de se fier au premier extrait que je montre - exposer un sous-ensemble limité de Frame, en tant qu'interface. D'après mon expérience, c'est souvent une solution tout à fait réalisable. Ajoutez simplement des méthodes à l'interface selon vos besoins. (Je parle d'un scénario dans lequel nous "savons" que Frame dispose déjà des méthodes nécessaires, sinon elles seraient faciles et sans controverse. Le travail de "mise en œuvre" pour chaque méthode consiste à ajouter une ligne à la définition de l'interface.) Et sachez que même dans le pire des scénarios futurs, il est possible de faire fonctionner cette API - ici,IEncoderFrameFrameEncoder.
Notez également que si vous n'êtes pas autorisé à ajouter IEncoderFrameà Frame, ou les méthodes nécessaires ne correspondez bien à la générale Frameclasse, et la solution n ° 2 ne vous convient pas, peut - être à cause de la création et destruction d'objets supplémentaires, La solution n ° 3 peut être considérée simplement comme un moyen d’organiser les méthodes de Encoderréalisation du LoD. Ne faites pas que passer par des dizaines de méthodes. Enveloppez-les dans une interface et utilisez "implémentation d'interface explicite" (si vous êtes en c #), de sorte qu'ils ne soient accessibles que lorsque l'objet est visualisé via cette interface.
Un autre point que je tiens à souligner est que la décision d' exposer la fonctionnalité en tant qu'interface a traité les 3 situations décrites ci-dessus. Dans le premier, IEncoderFrameest simplement un sous-ensemble de Framela fonctionnalité de. Dans le second, IEncoderFrameest un adaptateur. Dans le troisième, IEncoderFrameest une partition en Encoderfonctionnalité s. Peu importe si vos besoins changent entre ces trois situations: l'API reste la même.