La respuesta de Doc Brown muestra una implementación clásica de la Ley de Deméter en los libros de texto, y la molestia / hinchazón de código desorganizado de agregar docenas de métodos de esa manera es probablemente la razón por la cual los programadores, incluido yo mismo, a menudo no nos molestamos en hacerlo, incluso si deberían hacerlo.
Hay una forma alternativa de desacoplar la jerarquía de objetos:
Exponga interfacetipos, en lugar de classtipos, a través de sus métodos y propiedades.
En el caso del Cartel original (OP), encoder->WaitEncoderFrame()devolvería un en IEncoderFramelugar de un Frame, y definiría qué operaciones son permisibles.
SOLUCION 1
En el caso más fácil, Framey las Encoderclases están bajo su control, IEncoderFramees un subconjunto de métodos que Frame ya expone públicamente, y a la Encoderclase en realidad no le importa lo que haga con ese objeto. Entonces, la implementación es trivial ( código 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; } }
...
}
SOLUCIÓN 2
En un caso intermedio, donde la Framedefinición no está bajo su control, o no sería apropiado agregar IEncoderFramelos métodos Frame, entonces una buena solución es un Adaptador . Eso es lo que discute la respuesta de CandiedOrange , como new FrameHandler( frame ). IMPORTANTE: si hace esto, es más flexible si lo expone como una interfaz , no como una clase . Encodertiene que saber class FrameHandler, pero los clientes solo necesitan saberlo interface IFrameHandler. O como lo llamé, interface IEncoderFramepara indicar que es específicamente Frame como se ve desde 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 ); } }
...
}
COSTO: Asignación y GC de un nuevo objeto, EncoderFrameWrapper, cada vez que encoder.TheFramese llama. (Puede almacenar en caché ese contenedor, pero eso agrega más código. Y solo es fácil de codificar de manera confiable si el campo de marco del codificador no se puede reemplazar con un nuevo marco).
SOLUCIÓN 3
En el caso más difícil, el nuevo contenedor necesitaría saber sobre ambos Encodery Frame. Ese objeto en sí mismo violaría LoD: está manipulando una relación entre Encoder y Frame que debería ser responsabilidad de Encoder, y probablemente sea un dolor hacerlo bien. Esto es lo que puede suceder si comienzas por ese camino:
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;
...
}
Eso se puso feo. Hay una implementación menos complicada, cuando el contenedor necesita tocar los detalles de su creador / propietario (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 ...
}
...
}
De acuerdo, si supiera que terminaría aquí, no podría hacer esto. Podría simplemente escribir los métodos LoD y terminar con eso. No es necesario definir una interfaz. Por otro lado, me gusta que la interfaz envuelva métodos relacionados juntos. Me gusta cómo se siente hacer las "operaciones tipo marco" a lo que se siente como un marco.
COMENTARIOS FINALES
Considere esto: si el implementador Encoderconsidera que la exposición Frame frameera apropiada para su arquitectura general, o era "mucho más fácil que implementar LoD", entonces habría sido mucho más seguro si en su lugar hicieran el primer fragmento que muestro: exponga un subconjunto limitado de Marco, como una interfaz. En mi experiencia, esa es a menudo una solución completamente viable. Simplemente agregue métodos a la interfaz según sea necesario. (Estoy hablando de un escenario en el que "sabemos" que Frame ya tiene los métodos necesarios, o sería fácil y no controversial agregarlos. El trabajo de "implementación" para cada método es agregar una línea a la definición de la interfaz). Sabemos que incluso en el peor escenario futuro, es posible mantener esa API funcionando: aquí,IEncoderFrameFrameEncoder.
También tenga en cuenta que si usted no tiene permiso para añadir IEncoderFramea Frame, o los métodos necesarios no se ajustan bien a lo general Framede clase, y la solución # 2 no le conviene, quizás debido a objeto adicional creación y destrucción, La solución n. ° 3 puede verse simplemente como una forma de organizar los métodos Encoderpara lograr LoD. No solo pases por docenas de métodos. Envuélvalos en una interfaz y use "implementación de interfaz explícita" (si está en c #), de modo que solo se pueda acceder a ellos cuando el objeto se vea a través de esa interfaz.
Otro punto que quiero enfatizar es que la decisión de exponer la funcionalidad como una interfaz , manejó las 3 situaciones descritas anteriormente. En el primero, IEncoderFramees simplemente un subconjunto de Framela funcionalidad de. En el segundo, IEncoderFramees un adaptador. En el tercero, IEncoderFramehay una partición en Encoderla funcionalidad de s. No importa si sus necesidades cambian entre estas tres situaciones: la API permanece igual.