Next Up Previous Hi Index

Chapter 16

Herencia

16.1 Herencia

La caracter�stica de un lenguaje que m�s se asocia con la programaci�n orientada a objetos es la herencia. La herencia es la capacidad de definir una nueva clase que es una versi�n modificada de otra ya existente.

La principal ventaja de esta caracter�stica es que se pueden agregar nuevos m�todos a una clase sin modificar la clase existente. Se denomina "herencia" porque la nueva clase hereda todos los m�todos de la clase existente. Si extendemos esta mat�fora, a la clase existente a veces se la denomina clase padre. La nueva clase puede denominarse clase hija, o tambi�n "subclase".

La herencia es una caracter�stica poderosa. Ciertos programas que ser�an complicados sin herencia pueden escribirse de manera simple y concisa gracias a ella. Adem�s, la herencia puede facilitar la reutilizaci�n del c�digo, pues se puede adaptar el comportamiento de la clase padre sin tener que modificarla. En algunos casos, la estructura de la herencia refleja la propia estructura del problema, lo que hace que el programa sea m�s f�cil de comprender.

Por otro lado, la herencia pude hacer que los porgramas sean dif�ciles de leer. Cuando se llama a un m�todo, a veces no est� claro d�nde debe uno encontrar su definici�n. El c�digo relevante puede estar diseminado por varios m�dulos. Adem�s, muchas de las cosas que se hacen mediante el uso de la herencia, se pueden lograr de forma igualmente (incluso m�s) elegante sin ella. Si la estructura general del problema no nos gu�a hacia la herencia, dicho estilo de programaci�n puede hacer m�s mal que bien.

En este cap�tulo demostraremos el uso de la herencia como parte de un programa que juega a las cartas a la "Mona". Una de nuestras metas ser� que el c�digo que escribamos se pueda reutilizar para implementar otros juegos de naipes.

16.2 Una mano de cartas

Para casi cualquier juego de naipes, necesitamos representar una mano de cartas. Una mano es similar a un mazo, por supuesto. Ambos est�n compuestos de un conjunto de naipes, y ambos requieren de operaciones tales como agregar y eliminar una carta. Adem�s, necesitaremos la capacidad de mezclar tanto un mazo como una mano de cartas.

Una mano es diferente de un mazo en ciertos aspectos. Seg�n el juego al que se est� jugando, podemos querer realizar ciertas operaciones sobre una mano que no tienen sentido sobre un mazo. Por ejemplo, en el p�ker queremos clasificar una mano (\eningles{straight (consecutiva), \eningles{flush} (de un solo palo), etc.) y compararla con otra. En \eningles{bridge} necesitaremos calcular el puntaje para la mano para as� poder hacer la subasta. Esta situaci�n sugiere el uso de la herencia. Si {\tt Mano} es una subclase de {\tt Mazo}, entonces tendr� todos los m�todos de {\tt Mazo} y le podremos agregar otros m�todos nuevos. \index{clase padre} \index{clase!padre} En la definici�n de clase, el nombre de la clase padre aparece entre par�ntesis: \beforeverb \begin{verbatim} class Mano(Mazo): pass \end{verbatim} \afterverb % Esta sentencia indica que la nueva clase {\tt Mano} hereda de la clase existente {\tt Mazo}. El constructor de {\tt Mano} inicializa los atributos para la mano, que son {\tt nombre} y {\tt cartas}. La cadena de caracteres {\tt nombre} identifica a esta mano, probablemente mediante el nombre del jugador que la sostiene. El nombre es un par�metro opcional con un valor por omisi�n de cadena vac�a. {\tt cartas} es la lista de cartas de la mano, inicializada como lista vac�a. \beforeverb \begin{verbatim} class Mano(Mazo): def __init__(self, nombre=""): self.cartas = [] self.nombre = nombre \end{verbatim} \afterverb % Casi para cualquier juego de naipes, es necesario agregar y quitar cartas del mazo. La eliminaci�n de cartas ya ha sido resuelta, pues {\tt Mano} hereda {\tt eliminaCarta} de {\tt Mazo}. Pero deberemos escribir {\tt agregaCarta}: \beforeverb \begin{verbatim} class Mano(Mazo): ... def agregaCarta(self,carta) : self.cartas.append(carta) \end{verbatim} \afterverb % De nuevo, los puntos suspensivos indican que hemos omitido los otrs m�todos. El m�todo de lista {\tt append} agrega la nueva carta al final de la lista de cartas. \section{El reparto de los naipes} \index{reparto de naipes} Ahora que ya tenemos la clase {\tt Mano}, queremos repartir las cartas del {\tt Mazo} en manos. No es claramente obvio si este m�todo debe ir en la clase {\tt Mano} o en la clase {\tt Mazo}, pero como opera sobre un mazo �nico y (posiblemente) sobre varias manos, es m�s natural ponerlo en el {\tt Mazo}. {\tt repartir} debe ser bastante general, pues los diferentes juegos tienen distintos requerimentos. Puede que necesitemos repartir todo el mazo de una vez, o que agreguemos una carta a cada mano. {\tt repartir} toma dos par�metros, una lista (o tupla) de manos y la cantidad total de naipes a repartir. Si no hay suficientes cartas en el mazo, el m�todo reparte todas las cartas y se detiene: \beforeverb \begin{verbatim} class Mazo : ... def repartir(self, manos, nCartas=999): nManos = len(manos) for i in range(nCartas): if self.estaVacio(): break # fin si se acaban las cartas carta = self.darCarta() # da la carta superior mano = manos[i % nManos] # a qui�n le toca? mano.agregaCarta(carta) # agrega la carta a la mano \end{verbatim} \afterverb % El segundo par�metro, {\tt nCartas} es opcional; el valor por omisi�n es un n�mero muy grande, lo cual es lo mismo que decir que se repartir�n todos los naipes del mazo. \index{variable de bucle} \index{variable!bucle} La variable de bucle {\tt i} va desde 0 hasta {\tt nCartas-1}. A cada paso a trav�s del bucle, se elimina una carta del mazo mediante el m�todo de lista {\tt pop}, que quita y devuelve el �ltimo elemento de la lista. \index{operador m�dulo} \index{operador!m�dulo} El operador m�dulo ({\tt \%}) permite que podamos repartir las cartas de una en una (una carta cada vez para cada mano). Cuando {\tt i} es igual a la cantidad de manos en la lista, la expresi�n {\tt i \% nManos} salta hacia el comienzo de la lista (el �ndice es 0). \section {Mostremos la mano} \index{impresi�n!mano de cartas} Para mostrar el contenido de una mano, podemos sacar partido de la existencia de los m�todos {\tt muestraMazo} y {\tt \_\_str\_\_} que se heredan de {\tt Mazo}. Por ejemplo: %\adjustpage{-2} %\pagebreak \beforeverb \begin{verbatim} >>> mazo = Mazo() >>> mazo.mezclar() >>> mano = Mano("hugo") >>> mazo.repartir([mano], 5) >>> print mano La mano de hugo contiene 2 de Picas 3 de Picas 4 de Picas As de Corazones 9 de Tr�boles \end{verbatim} \afterverb % No es una gran mano, pero tiene lo necesario como para disponer de una escalera de color. \index{escalera de color} Aunque es conveniente usar la herencia de los m�todos existentes, existe informaci�n adicional en una {\tt Mano} que desear�amos mostrar al imprimirla. Para ello, podemos proporcionar a la clase {\tt Mano} un m�todo {\tt \_\_str\_\_} que reemplace al de la clase {\tt Mazo}: \beforeverb \begin{verbatim} class Mano(Mazo) ... def __str__(self): s = "La mano de " + self.nombre if self.estaVacio(): s = s + " est� vac�a\n" else: s = s + " contiene\n" return s + Mazo.__str__(self) \end{verbatim} \afterverb % Al principio {\tt s} es una cadena de caracteres que identifica a la mano. Si la mano est� vac�a, el programa agrega las palabras {\tt est� vac�a} y devuelve {\tt s}. En caso contrario, el programa agrega la palabra {\tt contiene} y la representaci�n como cadena de caracteres del {\tt Mazo}, que se obtiene llamando al m�todo {\tt \_\_str\_\_} de la clase {\tt Mazo} sobre la instancia {\tt self}. Puede parecer extra�o que enviemos a {\tt self}, que se refiere a la {\tt Mano} actual, como argumento de un m�todo de la clase {\tt Mazo}, hasta que nos damos cuenta de que una {\tt Mano} es un tipo de {\tt Mazo}. Los objetos {\tt Mano} pueden hacer cualquier cosa que pueda hacer un objeto {\tt Mazo}, y por ello es legal que pasemos una {\tt Mano} a un m�todo de {\tt Mazo}. \index{subclase} \index{clase padre} \index{clase!padre} En general, siempre es legal usar una instancia de una subclase en el lugar de una instancia de una clase padre. \section {La clase {\tt JuegoDeCartas}} La clase {\tt JuegoDeCartas} asume la responsabilidad sobre algunas obligaciones b�sicas comunes a todos los juegos, tales como la creaci�n del mazo y la mezcla de los naipes: \beforeverb \begin{verbatim} class JuegoDeCartas: def __init__(self): self.mazo = Mazo() self.mazo.mezclar() \end{verbatim} \afterverb % Esta es la primera vez que vemos que un m�todo de inicializaci�n realiza una actividad computacional significativa, m�s all� de la inicializaci�n de atributos. Para implementar juegos espec�ficos, debemos heredar de {\tt JuegoDeCartas} y agregar las caracter�sticas del nuevo juego. Como ejemplo, escribiremos una simulaci�n para La Mona. La meta de La Mona es desembarazarse de las cartas que uno tiene en la mano. Uno se saca las cartas de encima emparej�ndolas por valor y color. Por ejemplo, el 4 de Tr�boles se empareja con el 4 de Picas porque ambos palos son negros. La Sota de Corazones se empareja con la Sota de Diamantes porque ambos son rojos. Para iniciar el juego, se elimina la Reina de Tr�boles del mazo, de manera que la Reina de Picas no tiene con qui�n emparejarse. Las cincuenta y una cartas restantes se reparten entre los jugadores, de una en una. Luego del reparto, todos los jugadores emparejan y descartan tantas cartas como sea posible. Cuando no se pueden realizar m�s concordancias, el juego comienza. Por turnos, cada jugador toma una carta (sin mirarla) del vecino m�s cercano de la izquierda que a�n tiene cartas. Si la carta elegida concuerda con una de la mano del jugador, se elimina dicho par. Si no, la carta se agrega a la mano del jugador. Llega el momento en el que se realizan todas las concordancias posibles, con lo que queda s�lo la Reina de Picas en la mano del perdedor. En nuestra simulaci�n inform�tica del juego, la computadora juega todas las manos. Desafortunadamente, se pierden algunos de los matices del juego real. En una partida real, el jugador que tiene la Mona realiza ciertos esfuerzos para que su vecino la tome, por ejemplo mostr�ndola prominentemente o al contrario, errando al intentar mostrarla abiertamente, o incluso puede fallar al tratar de errar en su intento de mostrarla prominentemente. La computadora simplemente toma una carta al azar de su vecino. \section {La clase {\tt ManoDeLaMona}} \index{clase!ManoDeLaMona} Una mano para jugar a La Mona requiere ciertas capacidades que est�n m�s all� de las que posee una {\tt Mano}. Definiremos una nueva clase {\tt ManoDeLaMona}, que hereda de {\tt Mano} y nos proporciona un m�todo adicional denominado {\tt eliminaCoincidencias}: \beforeverb \begin{verbatim} class ManoDeLaMona(Mano): def eliminaCoincidencias(self): cant = 0 cartasOriginales = self.cartas[:] for carta in cartasOriginales: empareja = Carta(3 - carta.palo, carta.valor) if empareja in self.cartas: self.cartas.remove(carta) self.cartas.remove(empareja) print "Mano %s: %s con %s" % (self.nombre,carta,empareja) cant = cant + 1 return cant \end{verbatim} \afterverb % Comenzamos por hacer una copia de la lista de las cartas, de tal manera que podamos recorrer la copia mientras vamos quitando cartas de la lista original. Como {\tt self.cartas} se modifica en el bucle, no vamos a querer usarla para controlar el recorrido. �Python puede quedar realmente confundido si se recorre una lista que est� cambiando! \index{recorrido} %\adjustpage{1} Para cada carta de la mano, averiguamos cu�l es la carta que concordar� con ella y la buscamos. La carta que concuerda tiene el mismo valor y el otro palo del mismo color. La expresi�n {\tt 3 - carta.palo} transforma un Tr�bol (palo 0) en una Pica (palo 3) y un Diamante (palo 1) en un Coraz�n (palo 2). Verifique por su cuenta que las operaciones opuestas tambi�n funcionan. Si la carta que concuerda est� en la mano, ambas se eliminan. El siguiente ejemplo demuestra el uso de {\tt eliminaCoincidencias}: \beforeverb \begin{verbatim} >>> juego = JuegoDeCartas() >>> mano = ManoDeLaMona("hugo") >>> juego.mazo.repartir([mano], 13) >>> print mano La mano de hugo contiene As de Picas 2 de Diamantes 7 de Picas 8 de Tr�boles 6 de Corazones 8 de Picas 7 de Tr�boles Raina de Tr�boles 7 de Diamantes 5 de Tr�boles Sota de Diamantes 10 de Diamantes 10 de Corazones >>> mano.eliminaCoincidencias() Mano hugo: 7 de Picas con 7 de Tr�boles Mano hugo: 8 de Picas con 8 de Tr�boles Mano hugo: 10 de Diamantes con 10 de Corazones \end{verbatim} \afterverb % Debe usted notar que no existe un m�todo {\tt \_\_init\_\_} para la clase {\tt ManoDeLaMona}. Lo heredamos de {\tt Mano}. \section {La clase {\tt JuegoDeLaMona}} \index{clase!JuegoDeLaMona} Ahora podemos poner nuestra atenci�n en el juego en s� mismo. {\tt JuegoDeLaMona} es una subclase de {\tt JuegoDeCartas} con un m�todo nuevo denominado {\tt jugar} que toma una lista de jugadores como par�metro. Como el m�todo {\tt \_\_init\_\_} se hereda de {\tt JuegoDeCartas}, el nuevo objeto {\tt JuegoDeLaMona} contiene un mazo recientemtente mezclado: %\adjustpage{-1} \beforeverb \begin{verbatim} class JuegoDeLaMona(JuegoDeCartas): def jugar(self, nombres): # quitamos la Reina de Tr�boles self.mazo.eliminaCarta(Carta(0,12)) # construimos una mano para cada jugador self.manos = [] for nombre in nombres : self.manos.append(ManoDeLaMona(nombre)) # repartimos los naipes self.mazo.repartir(self.manos) print "----- Se han repartido las cartas." self.muestraManos() # eliminamos las coincidencias iniciales emparejadas = self.eliminaTodasLasCoincidencias() print "----- Coincidencias eliminadas, el juego comienza." self.muestraManos() # se juega hasta que se han descartado las 50 cartas turno = 0 cantManos = len(self.manos) while emparejadas < 25: emparejadas = emparejadas + self.jugarUnTurno(turno) turno = (turno + 1) % cantManos print "----- El juego termin�." self.muestraManos() \end{verbatim} \afterverb % Algunos de los pasos que componen el juego se han colocado en m�todos separados. {\tt eliminaTodasLasCoincidencias} recorre la lista de manos y llama a {\tt eliminaCoincidencias} para cada una de ellas: \beforeverb \begin{verbatim} class JuegoDeLaMona(JuegoDeCartas): ... def eliminaTodasLasCoincidencias(self): cant = 0 for mano in self.manos: cant = cant + mano.eliminaCoincidencias() return cant \end{verbatim} \afterverb % \begin{quote} {\em Como ejercicio, escriba {\tt muestraManos}, el cual recorre {\tt self.manos} y muestra cada mano.} \end{quote} {\tt cant} es un acumulador que va sumando la cantidad de concordancias en cada mano y devuelve el total. \index{acumulador} Cuando la cantidad total de coincidencias alcanza a las veinticinco significa que se han eliminado cincuenta cartas de las manos, lo que es lo mismo que decir que s�lo queda una carta y el juego ha terminado. La variable {\tt turno} recuerda el turno de cu�l jugador se est� jugando. Comienza en cero y se incrementa en uno cada vez; cuando alcanza el valor {\tt cantManos}, el operador de m�dulo lo hace volver a cero. El m�todo {\tt jugarUnTurno} toma un par�metro que indica de qui�n es el turno. El valor de retorno es la cantidad de concordancias que se han realizado durante ese turno: %\adjustpage{-2} %\pagebreak \beforeverb \begin{verbatim} class JuegoDeLaMona(JuegoDeCartas): ... def jugarUnTurno(self, i): if self.manos[i].estaVacio(): return 0 vecino = self.encuentraVecino(i) cartaElegida = self.manos[vecino].darCarta() self.manos[i].agregaCarta(cartaElegida) print "Mano", self.manos[i].nombre, "eligi�", cartaElegida cant = self.manos[i].eliminaCoincidencias() self.manos[i].mezclar() return cant \end{verbatim} \afterverb % Si la mano de un jugador est� vac�a, el jugador sali� del juego, as� que no hace nada y devuelve 0. Si no, un turno consiste en encontrar el primer jugador a la izquierda que a�n tiene cartas, tomar una carta de las que posee, y controlar si hay concordancias. Antes de volver se mezclan las cartas de la mano, de tal manera que la selecci�n del siguiente jugador sea al azar. El m�todo {\tt encuentraVecino} comienza con el jugador que est� inmediatamante a la izquierda y contin�a alrededor del c�rculo hasta que encuentra un jugador que a�n tiene cartas. \beforeverb \begin{verbatim} class JuegoDeLaMona(JuegoDeCartas): ... def encuentraVecino(self, i): cantManos = len(self.manos) for proximo in range(1,cantManos): vecino = (i + proximo) % cantManos if not self.manos[vecino].estaVacio(): return vecino \end{verbatim} \afterverb % Si por cualquier motivo {\tt encuentraVecino} llegara a dar la vuelta completa al c�rculo sin encontrar cartas, devolver�a {\tt None} y eso causar�a un error en alguna otra parte del programa. Afortunadamante podemos probar que eso no va a suceder nunca (siempre y cuando se detecte correctamente el final del juego). Hemos omitido el m�todo {\tt muestraManos}. �se puede escribirlo usted mismo. La siguiente salida proviene de una forma reducida del juego, en la cual solamente se reparten las quince cartas m�s altas (desde los dieces hacia arriba) a tres jugadores. Con este mazo m�s peque�o, el juego termina tras siete coincidencias, en lugar de veinticinco. \beforeverb \begin{verbatim} >>> import cartas >>> juego = cartas.JuegoDeLaMona() >>> juego.jugar(["Allen","Jeff","Chris"]) ----- Se han repartido las cartas. Mano Allen contiene Rey de Corazones Sota de Tr�boles Reina de Picas Rey de Picas 10 de Diamantes Mano Jeff contiene Reina de Corazones Sota de Picas Sota de Corazones Rey de Diamantes Reina de Diamantes Mano Chris contiene Sota de Diamantes Rey de Tr�boles 10 de Picas 10 de Corazones 10 de Tr�boles Mano Jeff: Reina de Corazones con Reina de Diamantes Mano Chris: 10 de Picas con 10 de Tr�boles ----- Se eliminaron las coincidencias, el juego comienza. Mano Allen contiene Rey de Corazones Sota de Tr�boles Reina de Picas Rey de Picas 10 de Diamantes Mano Jeff contiene Sota de Picas Sota de Corazones Rey de Diamantes Mano Chris contiene Sota de Diamantes Rey de Tr�boles 10 de Corazones Mano Allen: eligi� Rey de Diamantes Mano Allen: Rey de Corazones con Rey de Diamantes Mano Jeff: eligi� 10 de Corazones Mano Chris: eligi� Sota de Tr�boles Mano Allen: eligi� Sota de Corazones Mano Jeff: eligi� Sota de Diamantes Mano Chris: eligi� Reina de Picas Mano Allen: eligi� Sota de Diamantes Mano Allen: Sota de Corazones con Sota de Diamantes Mano Jeff: eligi� Rey de Tr�boles Mano Chris: eligi� Rey de Picas Mano Allen: eligi� 10 de Corazones Mano Allen: 10 de Diamantes con 10 de Corazones Mano Jeff: eligi� Reina de Picas Mano Chris: eligi� Sota de Picas Mano Chris: Sota de Tr�boles con Sota de Picas Mano Jeff: eligi� Rey de Picas Mano Jeff: Rey de Tr�boles con Rey de Picas ----- El juego termin�. La mano de Allen est� vac�a. La mano de Jeff contiene Reina de Picas La mano de Chris est� vac�a. \end{verbatim} \afterverb % As� que Jeff es quien perdi�. \section{Glosario} \begin{description} \item[herencia:] La capacidad de definir una nueva clase que es una versi�n modificada de una clase previamente definida. \item[clase padre:] Aquella clase de la cual la clase hija hereda. \item[clase hija:] Una nueva clase creada heredando de una clase existente; tambi�n se la llama ``subclase''. \index{herencia} \index{clase padre} \index{clase hija} \index{subclase} \end{description} %%% Local Variables: %%% mode: latex %%% TeX-master: "top" %%% End:


Next Up Previous Hi Index

" + str + "

Close window

Hosted by www.Geocities.ws

"); } //-->
Hosted by www.Geocities.ws

1