Algún ejemplo práctico real para enseñar programación y conceptos orientados a objetos (en python)

Dec 31 2020

Les he enseñado a mis estudiantes de secundaria a escribir algunos scripts simples de Python, tomando algunas ideas de "Automatizar las cosas aburridas con Python" , por ejemplo, cambiar los nombres de los archivos en una carpeta a un patrón de nomenclatura específico.

Mi próximo objetivo es enseñarles conceptos orientados a objetos y, si es posible, darles algunos ejemplos "reales" para que los prueben ellos mismos. Pero la mayoría de los materiales que encuentro (la mayoría de los principales resultados de búsqueda de Google) sobre programación OO (no solo Python) están cargados de ejemplos deficientes, si se me permite decirlo. Realmente no me gusta usar coche / vehículo o animal / perro para enseñarles el concepto OO y OOP.

A. es aburrido (ya conoces a los chicos de secundaria)
B. no tiene ningún uso real.
C. como decía un comentario "tienen defectos fatales". (Pero si realmente le gusta usar el ejemplo animal, marque este "Diseño orientado a objetos" )

Planeo usar pathlib (por cierto, ya tienen una idea básica de la diferencia entre Windows y UNIX)

El desarrollo de GUI podría ser otro buen ejemplo para enseñar POO, pero todavía no quiero enseñarles GUI.

¿Alguna sugerencia para introducir OO con el ejemplo "real"?

Por cierto, no soy un firme defensor de la programación orientada a objetos. Pero la pathlib.Pathclase es una abstracción útil, especialmente en comparación con os.path, aunque es poco probable que los estudiantes de secundaria lo aprecien.

---- actualizar ----

Encuentro que el módulo de registro de Python es otro buen ejemplo, pero parece demasiado complicado para programadores sin experiencia.

Cuando usamos el módulo de registro, usamos principalmente 3 objetos, Logger, Handler, Formatter . Logger es la fachada , comohttps://docs.python.org/3/howto/logging.html#loggers dicho,

Los objetos del registrador tienen un triple trabajo. Primero, exponen varios métodos al código de la aplicación para que las aplicaciones puedan registrar mensajes en tiempo de ejecución. En segundo lugar, los objetos del registrador determinan sobre qué mensajes de registro actuar según la gravedad (la función de filtrado predeterminada) o los objetos de filtrado. En tercer lugar, los objetos del registrador transmiten mensajes de registro relevantes a todos los administradores de registros interesados.

Handler es un buen ejemplo para usar la herencia https://docs.python.org/3/howto/logging.html#useful-handlers y los controladores utilizan formateadores, que ocultan la complejidad de LogRecord, en el mensaje de registro de formato.

Además de estas clases, hay una serie de funciones a nivel de módulo para un uso conveniente.

En general, es un buen ejemplo del uso de composición y agregación.

Pero no soy un experto en el módulo de registro de Python, espero que alguien que esté familiarizado con el módulo de registro de Python pueda agregar una respuesta a mi pregunta.

--- actualización 2 ----

Encontré que Alan Kay respondió a la pregunta “¿Cuáles son las cinco características del paradigma orientado a objetos que considera importantes para la buena práctica de la ingeniería de software? ”Con estas palabras, en mi opinión, resuenan con la respuesta que dio Buffy. Pero cómo transmitir estas ideas a estudiantes de secundaria o programadores sin experiencia es otro desafío,

una construcción de "parte" que puede proteger su interior de su exterior, y viceversa

una construcción de "comunicaciones" que puede transmitir interacciones y lidiar con las dependencias

una construcción de "sistema" que es una combinación de partes y comunicaciones que pueden encajar en una parte de forma recursiva, y todo se hace de esta manera

los "mensajes" que se comunican son también en términos de los sistemas

el sistema que se está haciendo está hecho con el mismo tipo de sistema ...

Respuestas

3 Buffy Dec 31 2020 at 21:53

Espero actualizar esto varias veces y espero tener una respuesta larga eventualmente.

Pero permítanme comenzar explicando algunas cosas de las que es posible que ya estén al tanto, pero que otros lectores no.

En primer lugar, la programación orientada a objetos no se trata, fundamentalmente, de herencia y muchos libros y autores no lo entienden. Además, utilizan la herencia de una manera terrible que hace que el software sea difícil de entender y más difícil de mantener.

Por ejemplo, tenga en cuenta que la jerarquía de estilo Linneo del mundo biológico es casi en su totalidad una de "interfaces" y ni siquiera "clases abstractas", mucho menos clases concretas. No hay "mamíferos" instanciados, por ejemplo. Es sólo al margen de la jerarquía que las cosas existen realmente, además de como ideas. Hay cierta continuidad genética, por supuesto.

En segundo lugar, existen algunos principios que pueden guiarlo en la escritura y la enseñanza del diseño de OO, pero requieren disciplina. Y parte de esa disciplina es controlar las situaciones en las que "rompes" las reglas.

Mi primer consejo para cualquiera que quiera ser programador OO es que no piense en términos de herencia, sino de composición. Las cosas complejas (objetos) se componen de otras cosas (objetos) que son un poco más simples que el objeto que las contiene y le brindan algún servicio esencial. Si escribe clases donde todas (o incluso la mayoría) de las variables de su instancia son primitivas del lenguaje, entonces realmente no lo entiende. Y si esas variables de instancia (objetos o primitivas) tienen muchos captadores y definidores, entonces no estás haciendo Programación OO en absoluto.

De hecho, cometer los errores anteriores requiere que el programador lleve un registro de todos los detalles en todos los puntos del programa donde OO está diseñado para capturar decisiones de modo que no sea necesario "verificarlas" nuevamente. Configúrelo y olvídelo, por así decirlo.

Por lo tanto, si desea fabricar un automóvil, no lo considere una subclase de vehículo (lo que no le aporta nada: un dron personal y un acorazado son ambos vehículos). Piense en ello en lugar de estar compuesto de varias partes: motor, transmisión, controles, acomodaciones, etc. Y esas partes están, en sí mismas, compuestas de partes. Un motor tiene encendedores y pistones, escape, etc. Muchos de ellos también están compuestos por partes. Solo en el nivel más bajo y simple se construye con primitivas.

Dos de los principios que vale la pena tener en cuenta y seguir casi siempre son el principio de sustitución de Liskov y la ley de Deméter .

El primero sugiere que si amplía una clase con una subclase, no amplía también la interfaz pública de la subclase. Entonces, todos los objetos de la subclase son sustituibles, difieren en el comportamiento pero no en la interfaz. Demeter, por otro lado, te obliga a escribir un código más explícito que aclare las relaciones al lector. Por supuesto, también te obliga a introducir más nombres, y si son nombres que revelan la intención , tu código es más claro.

El principio de Liskov, por supuesto, es uno de los elementos de SOLID que también necesita incorporar a su pensamiento.

En mi propia programación soy muy fiel a Liskov, también definiendo interfaces para la mayoría de las cosas antes de escribir clases. Soy menos fiel a Demeter en el fragor de la batalla, mensajes en cascada. Pero a veces también necesito desentrañar ese tipo de cascadas abcd para descubrir lo que realmente quiero decir.

Un objetivo que tengo al escribir código OO es tratar de escribir solo métodos muy cortos con un mínimo de estructura. En otras palabras, trato de minimizar la Complejidad Ciclomática . Me empiezan a picar las palmas de las manos después de la cuarta frase de un método o si el nivel de complejidad llega a tres. No siempre puedo salirme con la mía, pero es un objetivo. La solución es refactorizar la complejidad sin piedad. Factoriza métodos, por supuesto, pero también "partes", creando nuevas clases para gestionar la complejidad. Incluso si muchas de esas clases son Singleton, el código generalmente se mejora y tener el objetivo en mente desde el principio significa que el paso de refactorización es menos necesario.

Los patrones de diseño son herramientas que necesita para ser un programador eficaz en la mayoría de los lenguajes OO. Los especialmente útiles son Estrategia, Decorador, Observador e Iterador. La mayoría de estos se utilizan en realidad para crear las distintas bibliotecas de Java.


Ahora, para llegar a la pregunta real que se hace aquí. Pero tenga en cuenta que se puede aprender mucho incluso si los estudiantes nunca llegan a completar el proyecto en el tiempo asignado. Un enfoque ágil (Programación extrema, digamos, con usted como "Cliente") para el desarrollo les deja algunas funcionalidades incluso si no se implementan todas las especificaciones.

Juego de mazmorras

Construye un juego de mazmorras basado en texto. Los objetos principales son personajes (personas), lugares y cosas. Los lugares están organizados en una especie de mapa, laberinto o cuadrícula. Suceden cosas cuando los personajes entran en lugares. Los personajes encuentran y cargan cosas. Las cosas tienen acciones, según el tipo de cosas. Un "hechizo" es una "cosa" cuya acción puede depender del contexto. Un objeto "transportador" puede funcionar de manera diferente en diferentes salas (objetos de estrategia).

Tenga en cuenta que el clásico juego de mesa Chutes (o Snakes) and Ladders es una versión simplificada de esto. Una versión basada en texto evita mucha complejidad.

Calculadora

Una calculadora tiene partes como teclas y la pantalla. Menos visible es la memoria interna, posiblemente una pila. Incluso las operaciones pueden ser objetos. El comportamiento de las claves cambia, dependiendo del estado del cálculo (patrón de estrategia). En realidad, es posible construir una calculadora simple sin una sola declaración IF.

Computadora abstracta con lenguaje ensamblador

Una simulación de procesador de computadora basada en pila es bastante sencilla. Puede haber acumuladores y demás, pero una sola pila en la que se realizan todas las operaciones es simple y completa. Las operaciones pueden ser objetos (partes). Una ventaja de este ejemplo es que la mayoría de los métodos necesarios pueden ser muy cortos. Un programa se puede leer con un objeto Java Scanner. Necesita al menos un contador de programa y posiblemente un puntero de trama si el lenguaje va a soportar subrutinas.

Notas adhesivas (no he probado esto)

Una aplicación que permite a los usuarios tomar notas, hacer referencias cruzadas y organizarlas. Algunas clases pueden ser Notas, Palabras clave, Conexiones, Listas.

Peligro

Un simulador del juego de riesgo de la televisión. Categorías, respuestas, preguntas, equipos, puntuaciones.

(Volver pronto, tal vez).

1 meuh Feb 05 2021 at 22:10

Encontré que una clase de Python es tan simple de escribir (para tareas "mundanas"), que pueden crecer naturalmente por el deseo de simplificar y refactorizar el código en desarrollo. Es un enfoque muy pragmático de abajo hacia arriba; esperabas hackear un código simple para un propósito particular, luego crece un poco y encuentras que tienes varias funciones que manejan "estructuras" que eran tuplas o matrices o incluso globales. De repente, ves la luz y creas una clase, y el tamaño del código se divide por 2 o más, y es mucho más simple .

Esta es una manera fácil de argumentar a favor de la programación orientada a objetos, tomando un código existente concreto "plano" y buscando la abstracción que podría hacerse, moviendo los datos fuera de los parámetros de la función, dentro de la clase y usando self.

Cosas como la herencia se pueden descubrir de manera similar de manera casi natural; usas una clase existente que no hace exactamente lo que quieres y necesitas cambiarla; en lugar de copiarlo y modificarlo, crea una subclase y cambia o agrega un método.

Para obtener ejemplos concretos, puede consultar el hardware informático en sí. En un nivel bajo, los registros a menudo se dividen en varias funciones dispares. Si desea establecer un bit de función en 1, debe desplazarlo a la izquierda 20 bits, leer el valor actual del registro, enmascarar los bits 0 a 5 ya que son "escribir 1 para borrar", y así sucesivamente. Intente emular un puerto serial 16550 uart; es bueno para el alma. Y, por supuesto, si usa MicroPython ejecutándose en un microprocesador real, probablemente podría incluso probar su código.

Flater Jun 02 2021 at 16:36

Mi ejemplo de referencia tanto para el modelado de datos como para la programación orientada a objetos (que en ambos casos gira en torno a una especie de normalización) es una tienda de alquiler de videos . Tal vez sea un ejemplo muy anticuado, siéntase libre de cambiarlo a una biblioteca o una tienda de alquiler por otras cosas, pero encuentro que el ejemplo de la tienda de videos resalta las complejidades de la POO y la normalización de datos, mientras que al mismo tiempo es muy simple contexto para comprender.

El objetivo principal es construir hasta un diagrama / clase tres tabla: Customer, Videoy Rental(lo que es la tabla de cruce entre los clientes y los videos).

El resto de esta respuesta son solo consejos sobre cómo relacionar los conceptos básicos específicos de OOP con el ejemplo en cuestión.


¿Por qué objetos?

Bueno, ¿cómo almacenaría sus datos de video, si desea rastrear tres cosas para un alquiler: el nombre del cliente, la dirección, el nombre del video y la fecha prevista de devolución?

Pida a los estudiantes que escriban un programa muy simple que les diga que Alice alquiló Antz, Bob alquiló Bee Movie y Charlie alquiló Cars. Permítales confiar en algún PrintRentalInfométodo reutilizable , pero permítales definir los parámetros del método como mejor les parezca.

Los estudiantes que aún no hayan visto la programación orientada a objetos utilizarán cuatro matrices distintas y confiarán en el hecho de que un video se encuentra en el mismo índice en las cuatro matrices. Explíqueles que en lugar de tener una "bolsa" de nombres de clientes, una "bolsa" de direcciones, una "bolsa" de nombres de video y una "bolsa" de fechas de devolución, no es realmente fácil trabajar con ellos. Sugiera la idea de que en lugar de hacer una "bolsa" por campo de datos, tendría más sentido si hiciéramos una "bolsa" por alquiler.

Construye la Rentalclase con las cuatro propiedades. Construye la misma aplicación que ellos, pero usando OOP. Esto les mostrará la inicialización del objeto, cómo diferentes objetos tienen la misma estructura pero contenido único individualmente, y cómo puede pasar un objeto (a diferencia de múltiples parámetros de métodos de tipos primitivos).

Realmente resalte lo fácil que es que puede mover esta "bolsa" de alquiler de un método a otro, manteniendo toda la información relevante junta.


¿Por qué más de una clase?

Aparece un cuarto cliente. Su nombre también es Alice. Te encuentras con un problema aquí, porque ahora no puedes saber qué Alice ha alquilado qué videos, y no quieres multar a la Alice equivocada.

Además, la Alice original nos ha llamado para informarnos que su dirección ha cambiado. Señale la dificultad de tener que pasar por todos los alquileres y darse cuenta de que no puede cambiar ciegamente "dirección_antigua" por "dirección_nueva", porque puede haber otros clientes viviendo en la misma dirección que no se mudaron con Alice. Además, tampoco puede confiar en el nombre, ya que la cuarta Alice ya ha realizado algunos alquileres.

Si los estudiantes siguen protestando que puedes hacerlo en base a la combinación de nombre y dirección, ¿qué pasa si esas dos Alice viven en la misma dirección y solo una de ellas se muda?

Sugiera a los estudiantes que tendría mucho sentido si tuviéramos una lista separada de todos nuestros clientes y sus direcciones, para poder distinguirlos por algo más que su nombre y cambiar fácilmente los detalles de una persona.

Objetivo: crear una Customerclase y cambiarla Rentalpara que incluya una Customerpropiedad en lugar de las propiedades de nombre / dirección.

Enfoque: resalte mucho cómo puede tener dos objetos de cliente diferentes , incluso cuando sus nombres y direcciones sean los mismos.

Extra: puede adoptar el mismo enfoque para crear la Videoclase, de modo que pueda realizar un seguimiento de los videos específicos que tiene. Quizás deje esto como un ejercicio para los estudiantes, ya que es muy parecido a antes.


A partir de este momento, puede ampliar la lógica empresarial en función de lo que desee mostrar.

  • Herencia : tal vez la tienda alquile Videoy Game, pero aún desea Rentalpoder vincular a cualquiera de ellos (usando la RentableObjectclase base )
  • Interfaz : puede usar el mismo ejemplo como herencia.
  • Normalización de datos : ¿cómo haríamos el seguimiento de las películas que tenemos disponibles y de los casetes físicos individuales (podríamos tener varias de la misma película)? ¿Cómo podría realizar un seguimiento de las multas en las que ha incurrido un cliente y cuáles de ellas ya ha pagado?
  • Transformación de datos : nuestro jefe quiere que imprimamos un informe semanal sobre todos los alquileres realizados, los alquileres devueltos, las multas incurridas y las multas anteriores que aún no se han pagado.
  • Referencia frente a valor : agregue Pricea tanto a Videocomo a Rental. Muestre cómo establece rental.Pricesegún video.Price, pero cuando video.Pricecambia más tarde, rental.Priceno se modifica . Ahora repita el mismo ejercicio con un objeto de referencia (por ejemplo, cambiando el nombre del cliente).

Encuentro que el contexto de este ejemplo es muy fácil de comprender y tiene muchas oportunidades de expansión. Esto podría convertirse en un proyecto a largo plazo que continúe expandiendo, lo que puede ser una lección valiosa si desea enseñar a sus estudiantes cómo manejar los requisitos cambiantes y los beneficios de la codificación limpia o el mantenimiento y el desarrollo heredado.