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 interface
types, plutôt que les class
types, via vos méthodes et propriétés.
Dans le cas de Poster Original (OP), encoder->WaitEncoderFrame()
renverrait un IEncoderFrame
au lieu de Frame
et définirait les opérations autorisées.
SOLUTION 1
Dans le cas le plus simple, Frame
et les Encoder
classes sont à la fois sous votre contrôle, IEncoderFrame
est un sous - ensemble de méthodes Cadre déjà publiquement expose, et la Encoder
classe 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 Frame
définition n'est pas sous votre contrôle, ou s'il ne serait pas approprié d'ajouter IEncoderFrame
des 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 . Encoder
doit 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.TheFrame
appel. (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 Encoder
et 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 Encoder
estimait 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,IEncoderFrame
Frame
Encoder
.
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 Frame
classe, 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 Encoder
ré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, IEncoderFrame
est simplement un sous-ensemble de Frame
la fonctionnalité de. Dans le second, IEncoderFrame
est un adaptateur. Dans le troisième, IEncoderFrame
est une partition en Encoder
fonctionnalité s. Peu importe si vos besoins changent entre ces trois situations: l'API reste la même.