design patterns - first - ¿Hay alguna literatura sobre este tipo de programación?
head first design patterns epub (8)
Algunos aspectos de este estilo de programación se tratan en Programación orientada a datos (un estilo de desarrollo que se centra en el diseño y la transformación de los datos).
Mi principal problema con este estilo es que si hay suposiciones / restricciones implícitas para un tipo dado (por ejemplo, digamos que la baraja de cartas nunca debe tener 2 comodines seguidos después de un orden aleatorio), debe duplicar esas restricciones / verificaciones en todo el los tipos de administrador, ya que el tipo de datos en el que está operando es totalmente tonto: no puede cuidarse solo, es solo una bolsa de datos. Puede extraer la lógica duplicada en un método separado, pero generalmente es un poco más difícil escribir un código bueno y limpio con este método.
Compare esto con la implementación de un método Deck.Shuffle
que toma una estrategia IDeckShuffle
. En este escenario, puede realizar la orden aleatoria y luego agregar verificaciones invariables como un paso posterior para garantizar que no importa qué estrategia aleatoria se usó, el mazo nunca entrará en un estado no válido; El código que impone la integridad está en un solo lugar y es fácil de verificar y actualizar.
Además, dado que la llamada a IDeckShuffler.Shuffle (...) proviene del interior de la cubierta, la plataforma tiene acceso a todos los campos ocultos y al estado encapsulado. Como tal, puede exponer los detalles mínimos a la implementación del barajador de mazo en lugar de establecer como predeterminado pasar un Mazo. En su lugar, puede pasar IEnumerable<Card>
o algo aún menos específico, en lugar de pasar toda la bolsa de datos por defecto.
De todos modos, la forma de desarrollo que está investigando es básicamente la programación de procedimientos. Como tal, es más difícil ocultar información y encapsular cosas. En sistemas críticos para el rendimiento, esto puede ser un compromiso aceptable (agrupar todos los datos por tipo, luego iterarlos usando las funciones de ''proceso'' del administrador = buena coherencia de caché).
En el desarrollo general, me mantengo alejado de este estilo de programación, ya que dificulta gravemente mi capacidad para gestionar la complejidad. Ayende tuvo un buen post sobre esto hace un tiempo . Aunque está hablando de un objeto de almacenamiento de datos y las operaciones que actúan sobre él, el principio es exactamente el mismo: la separación de datos y las funciones que actúan sobre esos datos y los problemas en ellos.
En la universidad tomé una clase de física moderna, en la que aprendimos sobre relatividad especial. Me quedé completamente impresionado por la forma en que diferentes marcos de referencia podían observar que las propiedades físicas de un objeto eran diferentes y que tampoco eran incorrectas. Con el tiempo, este concepto ha ido cambiando lentamente la forma en que programo, hasta el punto en que ahora tiendo a dividir las clases en 2 categorías principales, objetos de datos y objetos de observación (solo de función).
Por el simple hecho de que no se convierta en una publicación complicada y larga para una pregunta tan simple, intentaré explicar lo que quiero decir a través de dos ejemplos.
Primero, tome por ejemplo este tipo de código, que solía escribir:
class Deck
{
private Card[] cards;
public void Shuffle()
{
// shuffle algorithm
}
// other deck related functions like draw, cut, etc...
}
Normalmente ahora escribo el mismo escenario que:
class Deck
{
// by intention, i just mean some kind of immutable collection of cards
private ReadonlyCollection<Card> _Cards;
public Card[] Cards { get { return this._Cards; } }
}
interface IDeckHandler
{
Deck Shuffle(Deck deck);
// other deck related functions like draw, cut, etc..
}
class Dealer : IDeckHandler
{
// IDeckHandler implementation
}
El Deck
ya no es responsable de implementar las funciones que pueden actuar en él. O para que se ajuste a la terminología, la cubierta es solo un conjunto de valores y la forma en que se observa es responsabilidad del observador. Naturalmente, puede haber muchos observadores que realizan las acciones de manera diferente.
Para el segundo ejemplo, usaré algo con lo que la gente con la que he tratado de explicar esto ha sido más fácil. Tome la situación en la que tenemos letras de colores en papel de colores que deletrean una palabra. Tenemos un agente cuya responsabilidad es leer la palabra en el papel. Ahora supongamos que el agente es algún tipo de color ciego. La imagen que se emite desde el papel es la misma, pero la percepción podría ser diferente. El observador no tiene un conocimiento íntimo del objeto y no puede modificarlo, solo responde con una interpretación del mismo.
Como he dicho, este concepto impulsa muchas de mis decisiones de desarrollo. Entonces, volviendo a la pregunta, ¿ es este un tipo de programación publicada y, si es así, me puede indicar algo de literatura al respecto ? Hay algunos escenarios comunes y poco comunes en los que me he topado, para los que es difícil tomar decisiones, y ciertamente algunas cosas en las que simplemente no he pensado o que no he encontrado, y que con suerte se habrían examinado en la literatura.
De lo que estás hablando es de uno de los grandes "a HA!" Momentos en que los programadores orientados a objetos corren. Es divertido cuando sucede. Comenzaré con la cuadrilla de cuatro libros de "Patrones de diseño" y partiré desde allí.
En el mundo Java, tenemos buenos contenedores, que contienen beans de sesión sin estado, que se pueden usar precisamente para separar el comportamiento de los datos, como lo formaliza la arquitectura DCI. Esto a veces se llama programación orientada al servicio.
La OOP restringe a los diseñadores al exigir que el comportamiento se coloque en clases en las que viven los datos, con el fin de aumentar la coherencia, en lugar de dejar que el diseñador junte los datos relacionados y los comportamientos relacionados, pero no necesariamente también el comportamiento en esas clases de datos.
En un buen diseño, a veces tenemos un comportamiento en las clases, y otras veces tenemos un comportamiento en las clases de orden superior.
Esta es la forma típica en que se presentan la mayoría de las aplicaciones. Considero que las clases forman una dicotomía: objetos de contenedor de datos y objetos de estrategia. Los objetos de contenedor de datos son portadores de información, mientras que las estrategias son encapsulaciones de varios tipos de algoritmos que se pueden aplicar en los contenedores de datos.
El patrón de comando está muy cerca del patrón de estrategia. Las estrategias también tienden a manifestarse como controladores, fachadas y similares.
Los objetos del contenedor de datos se manifiestan como entidades, objetos modelo, objetos de valor, objetos de transferencia de datos, etc.
El grupo de cuatro es un buen comienzo, pero es posible que desee examinar otros tratados de patrones de diseño clásicos para una mayor elaboración. Dependiendo de su predilección por el lenguaje de programación, es posible que también desee considerar libros de patrones más específicos. Amazon tiene un montón de listas en los patrones de diseño.
En este artículo he hablado de la dicotomía de clase.
Este patrón de diseño puede funcionar bien, particularmente si las operaciones no mutan los objetos de datos originales. En .NET, por ejemplo, LINQ y sus métodos de extensión asociados se pueden ver como operaciones generales para tratar las enumeraciones, por lo que las propias enumeraciones no necesitan saber cómo pueden usarse. Sin embargo, los métodos no mutan las colecciones que se enumeran, sino que simplemente proporcionan una nueva forma de interpretar, filtrar y agrupar la enumeración.
Sin embargo, si la funcionalidad está mutando los objetos originales, tiendo a preferir que la funcionalidad se encapsule como métodos en el objeto. De esa manera, el objeto es responsable de mantener sus propios invariantes y aísla más detalles de la implementación de los clientes del objeto. Cuantos más detalles de implementación deba filtrar para trabajar con el objeto, mayor será la posibilidad de que se utilice incorrectamente y cause errores.
Interesante. Bueno, creo que estás haciendo Modelo-> Controlador-> Vista (Patrón MVC) pero en este caso solo estás usando las partes Controlador y Modelo por separado.
Las ganancias aquí son claras si tiene múltiples escenarios en los que se utilizarán los objetos, es una forma típica de los administradores de POJO + de hacer las cosas. En este caso, los objetos en sí mismos son tontos y sin ninguna funcionalidad además de su propio estado.
Las ventajas son claras en cuanto a la separación de responsabilidades, aunque las desventajas son un poco más de manejo por parte del administrador.
Si lo piensas bien, si los objetos no hacen nada consigo mismos, básicamente estás eliminando un nivel de direccionamiento indirecto (el nivel base) y todo debe estar dirigido al uso. Esto significa más código para manejar situaciones inesperadas, objetos nulos y demás. Tal vez más código de pegamento del necesario.
¿Flexible? Sí. ¿Práctico? Sólo tú puedes contestar.
Lo que está haciendo es separar los datos del concepto de las operaciones que actúan sobre ese concepto. Lo que el sistema es de lo que hace el sistema. Esto abre la puerta a muchos escenarios diferentes, en los que cambia el comportamiento del sistema utilizando diferentes clases de comportamiento. Estas clases de comportamiento también pueden ser reutilizables para diferentes clases de datos. Muchos patrones abordan este problema, como Visitante, Comando, Estrategia u Observador.
Pero hay algo más profundo en juego aquí. Tal vez necesitemos otro concepto en nuestros lenguajes de programación (general) (o tal vez solo en nuestra mente), que nos permita separar y reutilizar estos comportamientos.
La arquitectura DCI aborda estos problemas y presenta roles o rasgos (pdf) como la unidad fundamental de comportamiento y reutilización del código.
Me parece que estás implementando la programación funcional de una manera OOP. Independientemente de la explicación de la Física sobre la "relatividad especial", la idea completa de la POO es básicamente la encapsulación: desea que los objetos se conozcan a sí mismos cómo debe hacerse todo. Básicamente, lo que dice aquí es "no, solo hay datos y funciones que funcionan con los datos". ¿Qué pasa si cambia el mazo? ¿Tienes un nuevo distribuidor? ¿Cómo sabrá qué distribuidor debe crearse para repartir el nuevo mazo?
Si pensó en una declaración de "cambio", entonces está incorporando los conceptos OOP en una plantilla de programación funcional.