Evitar problemas orientados a objetos, migrar desde C, ¿qué funcionó para usted?

12

He estado programando en lenguajes de procedimiento desde hace bastante tiempo, y mi primera reacción a un problema es comenzar a dividirlo en tareas para realizar en lugar de considerar las diferentes entidades (objetos) que existen y sus relaciones.

He tenido un curso universitario en OOP y entiendo los fundamentos de la encapsulación, la abstracción de datos, el polimorfismo, la modularidad y la herencia.

Leí /programming/2688910/learning-to-think-in-the-object-oriented-way y /programming/1157847/learning-object-oriented-thinking , y miraré algunos de los libros señalados en esas respuestas.

Creo que varios de mis proyectos de tamaño medio a grande se beneficiarán del uso efectivo de OOP, pero como novato me gustaría evitar errores comunes que requieren mucho tiempo.

Según sus experiencias, ¿cuáles son estos obstáculos y cuáles son las formas razonables de evitarlos? Si pudiera explicar por qué son trampas y cómo su sugerencia es efectiva para abordar el problema, sería apreciado.

Estoy pensando en algo como "¿Es común tener una buena cantidad de métodos de observación y modificación y usar variables privadas o existen técnicas para consolidarlas / reducirlas?"

No me preocupa usar C ++ como un lenguaje OO puro, si hay buenas razones para mezclar métodos. (Recuerda las razones para usar GOTO, aunque con moderación).

¡Gracias!

Stephen
fuente
2
no es digno de una respuesta completa, pero es una importante que me llevó mucho tiempo aceptar (es decir, leí mucho sobre eso, pero lo traté como una charla fanboy): prefiero las funciones gratuitas a las funciones de los miembros. Esto mantiene sus clases mínimas, lo cual es algo bueno
2011
@stijn Esencialmente estás diciendo que si no necesita estar en la clase, no lo pongas allí. Por ejemplo, veo muchas funciones de miembros de utilidad que fácilmente podrían ser funciones libres en el código que he leído hasta ahora.
Stephen
si eso es. Si busca 'prefiera no amigo no miembro', encontrará mucha información al respecto. Al final, todo se reduce a adherirse al Principio de Responsabilidad Única
2011

Respuestas:

10

Una gran cosa que he aprendido es diseñar clases desde afuera hacia adentro. Diseñe la interfaz incluso antes de comenzar a pensar en la implementación. Esto hará que la clase sea mucho, mucho más intuitiva para sus usuarios (aquellos que usan la clase) que escribir los algoritmos subyacentes y desarrollar la clase, y escribir nuevas funciones de miembros públicos según sea necesario.

Dominic Gurto
fuente
77
Además de esto, podemos 'programar por intención', es decir, escribir un código de muestra que usaría la nueva clase, ver qué tipos de métodos y políticas hacen que sea cómodo de usar. Luego use esa información como línea de base para las implementaciones.
2
Genial en teoría: diseñe interfaces y básicamente habrá terminado. Pero no funciona tan simple en la práctica. El diseño y la implementación de la interfaz a menudo van de la mano en iteraciones consecutivas hasta que la interfaz final se cristaliza. En ese momento, es probable que también tenga la implementación final.
Gene Bushuyev
9

Bueno, el primero es el peligro de exponer demasiada información. El valor predeterminado debería ser private, no public.

Después de eso viene demasiados captadores / setters. Digamos que tengo un miembro de datos. ¿ Realmente necesito estos datos en otra clase? Ok, haz un captador. ¿ Realmente, realmente necesito cambiar estos datos durante la vida útil del objeto? Luego haz un setter.

La mayoría de los programadores novatos tienen el valor predeterminado para hacer un captador / configurador para cada miembro de datos. Esto satura las interfaces y a menudo son malas elecciones de diseño.

orlp
fuente
2
Sí, es bastante popular entre aquellos que entendieron la encapsulación superficialmente para hacer que los datos sean privados y luego encapsularlos con getters y setters.
Gene Bushuyev
Java es el único lenguaje popular que todavía necesita métodos get / set. Todos los demás idiomas admiten propiedades, por lo que no existe una diferencia sintáctica entre un miembro de datos públicos y una propiedad. Incluso en Java, no es pecado tener miembros de datos públicos si también controlas a todos los usuarios de la clase.
Kevin Cline
Los miembros de datos públicos son más difíciles de mantener. Con getters / setters (o propiedades), puede mantener la interfaz mientras cambia la representación de datos internos, sin cambiar ningún código externo. En los lenguajes donde las propiedades son sintácticamente idénticas a las variables miembro desde el punto de vista del consumidor, este punto no se cumple.
tdammers
Hasta ahora creo que estoy mirando a los miembros privados "como" variables locales en una función ... el resto del mundo no necesita saber nada sobre ellos para que el fn funcione ... y si el fn cambios, solo la interfaz debe ser coherente. No suelo utilizar el captador / configurador de spam, por lo que podría estar en el camino correcto, de hecho, mirando una clase de buffer que escribí, no hay miembros de datos públicos en absoluto. ¡Gracias!
Stephen
2

Cuando crucé ese abismo me decidí por el siguiente enfoque:

0) Comencé lento, con aplicaciones de procedimiento pequeñas / medianas y sin cosas de misión crítica en el trabajo.

1) un mapeo simple de primer paso de cómo escribiría el programa desde cero en el estilo OO, lo más importante para mí en ese momento, y esto ES subjetivo, fue descubrir todas las clases base. Mi objetivo era encapsular lo más posible en las clases base. Métodos virtuales puros para todo lo posible en las clases base.

2) Luego, el siguiente paso fue crear las derivaciones.

3) el paso final fue: en el código de procedimiento original, separe las estructuras de datos del código del observador / modificador. Luego, use la ocultación de datos y asigne todos los datos comunes a las clases base, y en las subclases fueron los datos que no eran comunes en todo el programa. Y el mismo tratamiento para el código de observador / modificador de procedimiento: toda la lógica 'utilizada en todas partes' entró en las clases base. Y la lógica de visualización / modificación que solo actuaba en un subconjunto de datos entró en las clases derivadas.

Esto es subjetivo pero es RÁPIDO, si conoce bien el código de procedimiento y las estructuras de datos. Un FALLO en una revisión de código es cuando un bit de datos o lógica no aparece en las clases base pero se usa en todas partes.


fuente
2

Eche un vistazo a otros proyectos exitosos que usan OOP para tener una sensación de buen estilo. Recomiendo mirar Qt , un proyecto que siempre admiro cuando tomo mis propias decisiones de diseño.

rcv
fuente
¡Si! Antes de que una persona se convierta en un maestro de algo, aprende a imitar a los grandes maestros que trabajaron antes que él. Estudiar buenos códigos implementados por otros es una buena manera de aprender. Recomendaría mirar el impulso, comenzando con diseños simples y profundizando a medida que mejora la comprensión.
Gene Bushuyev
1

Dependiendo de con quién hable, todos los datos en OOP deben ser privados o protegidos, y solo estar disponibles a través de accesores y mutadores. En general, creo que esta es una buena práctica, pero hay ocasiones en las que me desvío de esta norma. Por ejemplo, si tiene una clase (en Java, digamos) cuyo único propósito es agrupar algunos datos en una unidad lógica, tiene sentido dejar los campos públicos. Si se trata de una cápsula inmutable, simplemente márquelas como finales e inicialícelas en el constructor. Esto reduce la clase (en este caso) a poco más que una estructura (de hecho, usando C ++, debería llamar a esto una estructura. Funciona igual que una clase, pero la visibilidad predeterminada es pública y su intención es más clara), pero Creo que encontrarás que usarlo es mucho más cómodo en estos casos.

Una cosa que definitivamente no desea hacer es tener un campo que debe verificarse para determinar la coherencia en el mutador, y dejarlo público.

jpm
fuente
3
También sugeriría que si tiene esas clases que solo contienen datos públicos, debe declararlas como structs: no hay diferencia semántica, pero aclara la intención y hace que su uso parezca más natural, especialmente para los programadores de C.
1
Sí, estoy de acuerdo aquí si estás en C ++ (que la pregunta mencionó), pero hago la mayor parte de mi trabajo en Java, y desafortunadamente no tenemos ninguna estructura.
necesita distinguir claramente entre clases agregadas (caso infrecuente) y clases con funcionalidad (caso general). Este último debe practicar la encapsulación y acceder a sus miembros solo a través de la interfaz pública, no se deben exponer datos públicos.
Gene Bushuyev
1

El libro de Kent Beck, Implementation Patterns, es una excelente base sobre cómo usar, no usar mal, los mecanismos orientados a objetos.

Steven A. Lowe
fuente
1

No me preocupa usar C ++ como un lenguaje OO puro, si hay buenas razones para mezclar métodos. (Recuerda las razones para usar GOTO, aunque con moderación).

Realmente no pensé que tenía mucho que ofrecer la conversación hasta que vi esto. Tengo que estar en desacuerdo con el sentimiento. OOP es solo uno de los paradigmas que pueden y deben usarse en C ++. Francamente, en mi opinión, no es una de sus características más fuertes.

Desde el punto de vista de OO, creo que C ++ en realidad se queda un poco corto. La idea de tener funciones no virtuales, por ejemplo, es una marca en su contra a este respecto. He tenido discusiones con aquellos que no están de acuerdo conmigo, pero los miembros no virtuales simplemente no se ajustan al paradigma en lo que a mí respecta. El polimorfismo es un componente clave para OO y las clases con funciones no virtuales no son polimórficas en el sentido OO. Entonces, como lenguaje OO, creo que C ++ es bastante débil en comparación con lenguajes como Java u Objective-C.

Programación genérica por otro lado, C ++ tiene esta bastante buena. He oído decir que también hay mejores lenguajes para esto, pero la combinación de objetos y funciones genéricas es algo muy potente y expresivo. Además, puede ser muy rápido tanto en tiempo de programación como en tiempo de procesamiento. Es realmente en esta área que creo que C ++ brilla, aunque es cierto que podría ser mejor (soporte de lenguaje para conceptos, por ejemplo). Alguien que piensa que debería apegarse al paradigma OO y tratar a los demás en el orden de la declaración goto en niveles de inmoralidad realmente se está perdiendo al no mirar este paradigma.

La capacidad de metaprogramación de las plantillas también es bastante impresionante. Echa un vistazo a la biblioteca Boost.Units, por ejemplo. Esta biblioteca proporciona soporte de tipo para cantidades dimensionales. He utilizado ampliamente esta biblioteca en la empresa de ingeniería para la que trabajo actualmente. Simplemente proporciona una respuesta mucho más inmediata para un aspecto del posible programador, o incluso un error de especificación. Es imposible compilar un programa que use una fórmula en la que ambos lados del operador '=' no sean dimensionalmente equivalentes sin una conversión explícita. Personalmente no tengo experiencia con ningún otro lenguaje en el que esto sea posible, y ciertamente no con uno que también tenga el poder y la velocidad de C ++.

La metaprogramación es un paradigma puramente funcional.

Entonces, realmente, creo que ya estás entrando en C ++ con algunos conceptos erróneos desafortunados. Los otros paradigmas además de OO no deben ser evitados, deben ser APROVECHADOS. Use el paradigma que es natural para el aspecto del problema en el que está trabajando. No fuerce objetos sobre lo que esencialmente no es un problema propenso a objetos. En lo que a mí respecta, OO no es ni la mitad de la historia de C ++.

Edward extraño
fuente
¿La palabra clave virtual no proporciona funcionalidad de clase virtual en C ++? En cuanto al comentario de ir a, lo que estaba conduciendo era que no me preocupaba romper las reglas empíricas, siempre y cuando comprenda el razonamiento. Sin embargo, he estado buscando evitar el imperativo / procedimiento a favor de OO de lo que podría haber sido necesario. Gracias.
Stephen
Los métodos virtuales no son un requisito previo para el polimorfismo, es solo una forma común de hacerlo. de hecho, todo lo demás es igual, entonces, hacer que un método sea virtual en realidad debilita la encapsulación, porque estás aumentando el tamaño de la API de la clase, y haces más difícil asegurarte de seguir a Liskov, etc. haga algo virtual solo si necesita una costura, algún lugar para inyectar un nuevo comportamiento a través de la herencia (aunque la herencia también es algo de lo que debe tener cuidado en la POO). virtual por el bien de virtual no hace una clase "más OOP"
sara
1

Quería aceptar una respuesta a esta pregunta, pero no podía decidir una respuesta para otorgarle la marca de verificación. Como tal, he votado a los autores originales y he creado esto como una respuesta resumida. Gracias a todos los que se tomaron unos minutos, descubrí que la información que me brindaron me dio una buena dirección y un poco de tranquilidad de que no me había salido de los rieles.

@nightcracker

Bueno, el primero es el peligro de exponer demasiada información. El valor predeterminado debe ser privado, no público. Después de eso viene demasiados captadores / setters.

Sentí que había observado este problema en acción en el pasado. Sus comentarios también me hicieron recordar que al ocultar las variables subyacentes y su implementación, soy libre de cambiar su implementación sin destruir nada que dependa de ellas.

Dominic Gurto

Diseñe la interfaz incluso antes de comenzar a pensar en la implementación. El diseño y la implementación de la interfaz de Gene Bushuyev a menudo van de la mano en iteraciones consecutivas hasta que la interfaz final se cristalice.

Pensé que el comentario de Dominic era un gran ideal al que aspirar, pero creo que el comentario de Gene realmente golpea la realidad de la situación. Hasta ahora he visto esto en acción ... y me siento un poco mejor de que no sea raro. Creo que a medida que madure como programador, me inclinaré hacia diseños más completos, pero en este momento todavía sufro por saltar y obtener algunos códigos escritos.

wantTheBest

Comencé lento, con aplicaciones de procedimiento pequeñas / medianas y sin cosas de misión crítica en el trabajo. en el código de procedimiento original, separe las estructuras de datos del código del observador / modificador

Esto tiene mucho sentido ... Me gustó la idea de mantener las cosas funcionando en el trabajo, pero refactorizar algunas de las cosas no críticas con las clases.

jpm

Una cosa que definitivamente no desea hacer es tener un campo que debe verificarse para determinar la coherencia en el mutador, y dejarlo público

Hace tiempo que sé que este es uno de los puntos fuertes de encapsular los datos ... poder imponer la coherencia y, en realidad, condiciones / rangos / etc.

Eddie loco

Alguien que piensa que debería apegarse al paradigma OO y tratar a los demás en el orden de la declaración goto en niveles de inmoralidad realmente se está perdiendo al no mirar este paradigma. La capacidad de metaprogramación de las plantillas también es bastante impresionante.

Originalmente me perdí mucho en la respuesta de Crazy Eddie, creo que porque no había leído sobre algunos de los temas mencionados ... como la metaprogramación. Creo que el gran mensaje en la publicación de CE fue que C ++ es una mezcla de capacidades y estilos que cada uno debe usar para su mejor potencial ... incluido el imperativo si eso es lo que tiene sentido.

Así que de nuevo, ¡gracias a todos los que respondieron!

Stephen
fuente
0

El mayor escollo es la creencia de que OOP es una bala de plata, o el "paradigma perfecto".

alternativa
fuente