Entiendo el motivo detrás del principio de menor conocimiento , pero encuentro algunas desventajas si trato de aplicarlo en mi diseño.
Uno de los ejemplos de este principio (en realidad, cómo no usarlo), que encontré en el libro Head First Design Patterns especifica que es incorrecto llamar a un método en objetos que fueron devueltos de llamar a otros métodos, en términos de este principio .
Pero parece que a veces es muy necesario usar esa capacidad.
Por ejemplo: tengo varias clases: clase de captura de video, clase de codificador, clase de transmisor, y todas usan alguna otra clase básica, VideoFrame, y dado que interactúan entre sí, pueden hacer, por ejemplo, algo como esto:
streamer
código de clase
...
frame = encoder->WaitEncoderFrame()
frame->DoOrGetSomething();
....
Como puede ver, este principio no se aplica aquí. ¿Se puede aplicar este principio aquí, o es que este principio no siempre se puede aplicar en un diseño como este?
fuente
Respuestas:
El principio del que está hablando (mejor conocido como Ley de Demeter ) para las funciones se puede aplicar agregando otro método auxiliar a su clase streamer como
Ahora, cada función solo "habla con amigos", no con "amigos de amigos".
En mi humilde opinión, es una guía aproximada que puede ayudar a crear métodos que sigan más estrictamente el principio de responsabilidad única. En un caso simple como el anterior, probablemente sea muy testarudo si realmente vale la pena y si el código resultante es realmente "más limpio", o si simplemente expandirá su código formalmente sin ninguna ganancia notable.
fuente
El Principio de menor conocimiento o la Ley de Demeter es una advertencia contra el enredo de su clase con detalles de otras clases que atraviesan capa tras capa. Le dice que es mejor hablar solo con sus "amigos" y no con "amigos de amigos".
Imagine que le han pedido que suelde un escudo sobre una estatua de un caballero con una armadura de placas brillantes. Coloca cuidadosamente el escudo en el brazo izquierdo para que se vea natural. Notará que hay tres lugares pequeños en el antebrazo, el codo y la parte superior del brazo donde el escudo toca la armadura. Sueldes los tres lugares porque quieres asegurarte de que la conexión sea fuerte. Ahora imagine que su jefe está enojado porque no puede mover el codo de su armadura. Asumiste que la armadura nunca se movería y creaste una conexión inmóvil entre el antebrazo y la parte superior del brazo. El escudo solo debe conectarse a su amigo, antebrazo. No a los antebrazos amigos. Incluso si tiene que agregar un trozo de metal para que se toquen.
Las metáforas son buenas, pero ¿qué queremos decir con amigo? Cualquier cosa que un objeto sepa cómo crear o encontrar es un amigo. Alternativamente, un objeto puede pedir que le entreguen otros objetos , de los cuales solo conoce la interfaz. Estos no cuentan como amigos porque no se impone ninguna expectativa sobre cómo conseguirlos. Si el objeto no sabe de dónde vino porque algo más lo pasó / lo inyectó, entonces no es amigo de un amigo, ni siquiera es un amigo. Es algo que el objeto solo sabe usar. Eso es bueno.
Cuando se trata de aplicar principios como este, es importante comprender que nunca te están prohibiendo lograr algo. Son una advertencia de que puede estar descuidando hacer más trabajo para lograr un mejor diseño que logre lo mismo.
Nadie quiere trabajar sin razón alguna, por lo que es importante entender lo que se obtiene al seguir esto. En este caso, mantiene su código flexible. Puede hacer cambios y tener menos clases afectadas por los cambios de los que preocuparse. Eso suena bien, pero no te ayuda a decidir qué hacer a menos que lo tomes como una especie de doctrina religiosa.
En lugar de seguir ciegamente este principio, tome una versión simple de este problema. Escriba una solución que no siga el principio y una que sí lo haga. Ahora que tiene dos soluciones, puede comparar cuán receptivo es cada una a los cambios tratando de hacerlas en ambas.
Si NO PUEDES resolver un problema mientras sigues este principio, es probable que te falte otra habilidad.
Una solución a su problema particular es inyectarlo
frame
en algo (una clase o método) que sepa cómo hablar con marcos para que no tenga que difundir todos esos detalles de chat de marcos en su clase, que ahora solo sabe cómo y cuándo hacerlo. conseguir un marcoEso en realidad sigue otro principio: uso separado de la construcción.
Al usar este código, te has responsabilizado de alguna manera de adquirir un
Frame
. Aún no has asumido ninguna responsabilidad por hablar con aFrame
.Ahora debe saber cómo hablar con a
Frame
, pero reemplácelo con:Y ahora solo tienes que saber cómo hablar con tu amigo, FrameHandler.
Hay muchas maneras de lograr esto y esta quizás no sea la mejor, pero muestra cómo seguir el principio no resuelve el problema. Solo te exige más trabajo.
Toda buena regla tiene una excepción. Los mejores ejemplos que conozco son los lenguajes internos específicos del dominio . A DSL s cadena métodoparece abusar de la Ley de Deméter todo el tiempo porque constantemente estás llamando a métodos que devuelven diferentes tipos y usándolos directamente. ¿Por qué está bien esto? Porque en un DSL todo lo que se devuelve es un amigo cuidadosamente diseñado con el que debes hablar directamente. Por diseño, se le ha dado el derecho de esperar que la cadena de métodos de una DSL no cambie. No tienes ese derecho si simplemente profundizas en la base del código encadenando lo que encuentres. Los mejores DSL son representaciones o interfaces muy delgadas con otros objetos en los que probablemente tampoco debería profundizar. Solo menciono esto porque descubrí que entendía la ley del demeter mucho mejor una vez que aprendí por qué las DSL son un buen diseño. Algunos van tan lejos como para decir que las DSL ni siquiera violan la ley real de demeter.
Otra solución es dejar que algo más te inyecte
frame
. Siframe
vino de un instalador o, preferiblemente, de un constructor, entonces no asume ninguna responsabilidad por construir o adquirir un marco. Esto significa que su papel aquí es mucho más comoFrameHandlers
lo que iba a ser. En cambio, ahora eres el que hablaFrame
y hace que otra cosa descubra cómo obtener unFrame
En cierta forma, esta es la misma solución con un simple cambio de perspectiva.Los principios SÓLIDOS son los más importantes que trato de seguir. Aquí se respetan dos principios de responsabilidad única e inversión de dependencia. Es realmente difícil respetar a estos dos y aun así terminar violando la Ley de Deméter.
La mentalidad que implica violar a Demeter es como comer en un restaurante buffet donde simplemente agarras lo que quieras. Con un poco de trabajo por adelantado, puede proporcionarse un menú y un servidor que le brindará lo que quiera. Siéntese, relájese y dé una buena propina.
fuente
¿Es el diseño funcional mejor que el diseño orientado a objetos? Depende.
¿MVVM es mejor que MVC? Depende.
Amos y Andy o Martin y Lewis? Depende.
¿De qué depende? Las elecciones que haga dependerán de qué tan bien cada técnica o tecnología cumpla con los requisitos funcionales y no funcionales de su software, al tiempo que satisface adecuadamente sus objetivos de diseño, rendimiento y mantenibilidad.
Cuando lea esto en un libro o blog, evalúe el reclamo según su mérito; es decir, pregunta por qué. No existe una técnica correcta o incorrecta en el desarrollo de software, solo existe "¿cuán bien cumple esta técnica con mis objetivos? ¿Es efectiva o ineficaz? ¿Resuelve un problema pero crea uno nuevo? ¿Puede ser entendido bien por todo el equipo? equipo de desarrollo, o es demasiado oscuro? "
En este caso particular, el acto de invocar un método en un objeto devuelto por otro método, dado que existe un patrón de diseño real que codifica esta práctica (Factory), es difícil imaginar cómo se podría afirmar que es categórica incorrecto.
La razón por la que se llama el "Principio de menor conocimiento" es que el "Acoplamiento bajo" es una cualidad deseable de un sistema. Los objetos que no están estrechamente unidos entre sí funcionan de manera más independiente y, por lo tanto, son más fáciles de mantener y modificar individualmente. Pero como muestra su ejemplo, hay momentos en que el acoplamiento alto es más deseable, por lo que los objetos pueden coordinar sus esfuerzos de manera más efectiva.
fuente
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:
En el caso del Cartel original (OP),
encoder->WaitEncoderFrame()
devolvería un enIEncoderFrame
lugar de unFrame
, y definiría qué operaciones son permisibles.SOLUCION 1
En el caso más fácil,
Frame
y lasEncoder
clases están bajo su control,IEncoderFrame
es un subconjunto de métodos que Frame ya expone públicamente, y a laEncoder
clase en realidad no le importa lo que haga con ese objeto. Entonces, la implementación es trivial ( código en c # ):SOLUCIÓN 2
En un caso intermedio, donde la
Frame
definición no está bajo su control, o no sería apropiado agregarIEncoderFrame
los métodosFrame
, entonces una buena solución es un Adaptador . Eso es lo que discute la respuesta de CandiedOrange , comonew FrameHandler( frame )
. IMPORTANTE: si hace esto, es más flexible si lo expone como una interfaz , no como una clase .Encoder
tiene que saberclass FrameHandler
, pero los clientes solo necesitan saberlointerface IFrameHandler
. O como lo llamé,interface IEncoderFrame
para indicar que es específicamente Frame como se ve desde POV de Encoder :COSTO: Asignación y GC de un nuevo objeto, EncoderFrameWrapper, cada vez que
encoder.TheFrame
se 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
Encoder
yFrame
. 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:Eso se puso feo. Hay una implementación menos complicada, cuando el contenedor necesita tocar los detalles de su creador / propietario (Encoder):
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
Encoder
considera que la exposiciónFrame frame
era 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í,IEncoderFrame
Frame
Encoder
.También tenga en cuenta que si usted no tiene permiso para añadir
IEncoderFrame
aFrame
, o los métodos necesarios no se ajustan bien a lo generalFrame
de 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étodosEncoder
para 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,
IEncoderFrame
es simplemente un subconjunto deFrame
la funcionalidad de. En el segundo,IEncoderFrame
es un adaptador. En el tercero,IEncoderFrame
hay una partición enEncoder
la funcionalidad de s. No importa si sus necesidades cambian entre estas tres situaciones: la API permanece igual.fuente