A resposta de Doc Brown mostra uma implementação clássica de livros didáticos da Lei de Demeter - e o incômodo / desorganização do código de adicionar dezenas de métodos dessa maneira é provavelmente o motivo pelo qual os programadores, inclusive eu, geralmente não se incomodam em fazê-lo, mesmo que devessem.
Existe uma maneira alternativa de dissociar a hierarquia de objetos:
Exponha interfacetipos, em vez de classtipos, através de seus métodos e propriedades.
No caso de Pôster original (OP), encoder->WaitEncoderFrame()retornaria um em IEncoderFramevez de a Framee definiria quais operações são permitidas.
SOLUÇÃO 1
No caso mais fácil, Framee as Encoderclasses estão sob seu controle, IEncoderFrameé um subconjunto de métodos que o Frame já expõe publicamente e a Encoderclasse não se importa com o que você faz com esse objeto. Em seguida, a implementação é trivial ( código em 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; } }
...
}
SOLUÇÃO 2
Em um caso intermediário, em que a Framedefinição não está sob seu controle ou não seria apropriado adicionar IEncoderFrameos métodos de Frame, então uma boa solução é um Adaptador . É isso que a resposta de CandiedOrange discute, como new FrameHandler( frame ). IMPORTANTE: Se você fizer isso, será mais flexível se você o expuser como uma interface , não como uma classe . Encoderprecisa conhecer class FrameHandler, mas os clientes precisam apenas saber interface IFrameHandler. Ou como eu o nomeei, interface IEncoderFrame- para indicar que é especificamente Frame, como visto no POV do codificador :
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 ); } }
...
}
CUSTO: Alocação e GC de um novo objeto, o EncoderFrameWrapper, toda vez que encoder.TheFrameé chamado. (Você pode armazenar em cache esse invólucro, mas isso adiciona mais código. E só é fácil codificar com segurança se o campo de quadro do codificador não puder ser substituído por um novo quadro.)
SOLUÇÃO 3
No caso mais difícil, o novo wrapper precisaria saber sobre ambos Encodere Frame. Esse objeto violaria o LoD - ele está manipulando um relacionamento entre o Codificador e o Quadro que deveria ser de responsabilidade do Codificador - e provavelmente seria um problema para acertar. Aqui está o que pode acontecer se você começar por esse caminho:
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;
...
}
Isso ficou feio. Há uma implementação menos complicada, quando o wrapper precisa tocar nos detalhes de seu criador / proprietário (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 ...
}
...
}
É verdade que, se soubesse que terminaria aqui, talvez não o fizesse. Poderia simplesmente escrever os métodos LoD e terminar com ele. Não há necessidade de definir uma interface. Por outro lado, gosto que a interface agrupe métodos relacionados. Eu gosto de como é fazer as "operações tipo quadro" com o que parece um quadro.
COMENTÁRIOS FINAIS
Considere o seguinte: se o implementador Encoderconsiderou que a exposição Frame frameera apropriada à sua arquitetura geral ou "era muito mais fácil do que implementar o LoD", seria muito mais seguro se eles fizessem o primeiro trecho que eu mostro - exponha um subconjunto limitado de Quadro, como uma interface. Na minha experiência, isso geralmente é uma solução completamente viável. Basta adicionar métodos à interface, conforme necessário. (Estou falando de um cenário em que "sabemos" que o Frame já possui os métodos necessários, ou eles seriam fáceis e não controversos de serem adicionados. O trabalho de "implementação" para cada método está adicionando uma linha à definição da interface.) E sabemos que, mesmo no pior cenário futuro, é possível manter a API funcionando - aqui,IEncoderFrameFrameEncoder.
Observe também que se você não tem permissão para adicionar IEncoderFramea Frame, ou os métodos necessários não se encaixam bem com o general Frameclasse e solução # 2 não combina com você, talvez por causa do objeto extra de criação e destruição, A solução 3 pode ser vista simplesmente como uma maneira de organizar os métodos de Encoderrealizar LoD. Não basta passar por dezenas de métodos. Envolva-os em uma Interface e use "implementação explícita da interface" (se você estiver em c #), para que eles só possam ser acessados quando o objeto for visualizado através dessa interface.
Outro ponto que quero enfatizar é que a decisão de expor a funcionalidade como uma interface lidou com todas as três situações descritas acima. No primeiro, IEncoderFrameé simplesmente um subconjunto da Framefuncionalidade. No segundo, IEncoderFrameé um adaptador. No terceiro, IEncoderFrameé uma partição para Encodera funcionalidade s. Não importa se suas necessidades mudam entre essas três situações: a API permanece a mesma.