Chapter 16
\selectlanguage{spanish
\chapter{Herencia}
\section{Herencia}
\index{herencia}
\index{programación orientada a objetos}
\index{clase padre}
\index{clase hija}
\index{subclase}
La característica de un lenguaje que más se asocia con la programación
orientada a objetos es la {\bf 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 {\bf padre}. La nueva clase puede
denominarse clase {\bf hija}, o también «subclase».
\index{diseño orientado a objetos}
La herencia es una característica poderosa. Ciertos programas serían
complicados sin herencia, pero 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 estructura del problema, lo cual
hace que el programa sea más fácil de comprender.
Por otro lado, la herencia pude hacer que los porgramas sean más
difíciles de leer. Cuando se llama a un método, a veces no es claro
dónde debe uno encontrar su definición. El código relevante puede
estar diseminado entre varios módulos. Además, muchas de las cosas
que se hacen mediante el uso de la herencia, se pueden lograr
(más) elegantemente 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 al «Old Maid»\revisar{old maid: que
es?}. Una de nuestras metas será que el código que escribamos se
pueda reusar para implementar otros juegos de naipes.
\section{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. De acuerdo al
juego que se esté jugando, podemos querer realizar ciertas operaciones
sobre una mano que no tienen sentido sobre un mazo. Por ejemplo, en
el \eningles{poker} queremos clasificar una mano (\eningles{straight},
\eningles{flush}, etc.) y compararla con otra. En \eningles{bridge}
necesitaremos calcular el puntaje para la mano para así poder hacer
una apuesta.
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 nombde 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
en 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, las elipsis 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 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(cartas)
for i in range(nCartas):
if self.estaVacio(): break # fin si se terminan las cartas
carta = self.darCarta() # da la carta superior
mano = manos[i % nManos] # ¿quién tiene el siguiente turno?
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 en el 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}, el cual quita y retorna 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
en ronda (una carta a la 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 tomar ventaja de la
existencia de los métodos {\tt muestraMazo} y {\tt \_\_str\_\_} que se
heredan desde {\tt Mazo}. Por ejemplo:
\revisar{salto de página}
%\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 un
\revisar{straight flush: poker???}
\index{straight flush}\revisar{straight flush: poker???}
Aunque es conveniente usar la herencia de los métodos existentes,
existe información adicional en una mano que desearíamos mostrar.
Para ello, podemos proporcionar un método {\tt \_\_str\_\_} en la
clase {\tt Mano} 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
%
Inicialmente, {\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 de no ser vacía, el programa agrega la palabra {\tt contiene}
y la representación como cadena de caracteres del {\tt Mazo}, que se
obtiene al llamar al método {\tt \_\_str\_\_} en la clase {\tt Mazo}
sobre la instancia {\tt self}.\revisar{en el original no dice
instancia, pero queda más claro}
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 Deck},
hasta que nos damos cuenta que una {\tt Mano} es una clase de {\tt
Mazo}. Los objetos {\tt Mano} pueden hacer cualquier cosa que puede
hacer un objeto {\tt Mazo}, y por ello es legal que pasemos un
argumento de clase {\tt Mano} a un método {\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} toma responsabilidad sobre algunas
\revisar{chores: en castellano?} básicos 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, fuera de la
inicialización de atributos.
Para implementar juegos específicos, debemos heredar desde {\tt
JuegoDeCartas} y agregar las características para el nuevo juego.
Como ejemplo, escribiremos una simulación para el \revisar{Old
Maid:????}.
La meta del \revisar{Old Maid} es desembarazarse de las cartas que uno
tiene en la mano. Uno se saca las cartas de encima apareándolas por
valor y color. Por ejemplo, el 4 de Tréboles aparea con el 4 de Picas
porque ambos palos son negros. La Sota de Corazones aparea 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 quien aparear. Las
cincuenta y un cartas restantes se reparten entre los jugadores, en
ronda. Luego del reparto, todos los jugadores aparean y descartan
tantas cartas como sea posible.
Cuando no se pueden realiazr más concordancias, el juego comienza.
Por turnos, cada jugador levanta una carta (sin mirarla) desde el
vecino más cercano de la izquierda que aún tiene cartas. Si la carta
levantada concuerda con una en la mano del jugador, se elimina dicho
par. Sino, la carta se agrega a la mano del jugador. Eventualmente
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 computarizada del juego, la computadora juega
todas las manos. Desafortunadamente, algunas de las \revisar{nuances
???} del juego real se pierden. En una partida real, el jugador
que tiene a la \revisar{Old Maid} 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 ManoOldMaid}}
\index{clase!ManoOldMaid}
Una mano para jugar al \revisar{Old Maid} requiere ciertas capacidades
que están más allá de las que posee una {\tt Mano}. Definiremos una
nueva clase {\tt ManoOldMaid}, que hereda desde {\tt Mano} y
proporciona un método adicional denominado {\tt eliminaCoincidencias}:
\beforeverb
\begin{verbatim}
class ManoOldMaid(Mano):
def eliminaCoincidencias(self):
cant = 0
cartasOriginales = self.cartas[:]
for carta in cartasOriginales:
aparea = Carta(3 - carta.palo, carta.valor)
if aparea in self.cartas:
self.cartas.remove(carta)
self.cartas.remove(aparea)
print "Mano %s: %s concuerda con %s" % (self.nombre,carta,aparea)
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}
\revisar{adjustpage??}
%\adjustpage{1}
Para cada carta en 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). Usted debe verificar 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 eliminaConcordancias}:
\beforeverb
\begin{verbatim}
>>> juego = JuegoDeCartas()
>>> mano = ManoOldMaid("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
Jack de Diamantes
10 de Diamantes
10 de Corazones
>>> mano.eliminaConcordancias()
Mano hugo: 7 de Picas concuerda con 7 de Tréboles
Mano hugo: 8 de Picas concuerda con 8 de Tréboles
Hand frank: 10 de Diamantes concuerda con 10 de Corazones
\end{verbatim}
\afterverb
%
Debe usted notar que no existe un método {\tt \_\_init\_\_} para la
clase {\tt ManoOldMaid}. Lo heredamos desde {\tt Mano}.
\section {La clase {\tt JuegoOldMaid}}
\index{clase!JuegoOldMaid}
Ahora podemos poner nuestra atención en el juego en sí mismo. {\tt
JuegoOldMaid} es una subclase de {\tt JuegoDeCartas} con nuevo
método denominado {\tt jugar} que toma una lista de jugadores como
parámetro.
Como el método {\tt \_\_init\_\_} se hereda desde {\tt JuegoDeCartas},
el nuevo objeto {\tt JuegoOldMaid} contiene un mazo recientemtente
mezclado:
\revisar{adjustpage??}
%\adjustpage{-1}
\beforeverb
\begin{verbatim}
class JuegoOldMaid(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(ManoOldMaid(nombre))
# repartimos los naipes
self.mazo.repartir(self.manos)
print "----- Se han repartido las cartas."
self.muestraManos()
# eliminamos las concordancias iniciales
matches = self.eliminaTodasLasConcordancias()
print "----- Se eliminaron las concordancias, el juego comienza."
self.muestraManos()
# se juega hasta que se han descartado las 50 cartas
turno = 0
cantManos = len(self.manos)
while apareadas < 25:
apareadas = apareadas + self.jugarUnTurno(turno)
turno = (turno + 1) % cantManos
print "----- El juego terminó."
self.muestraManos()
\end{verbatim}
\afterverb
%
Algunos de los pasos del juego se han puesto en métodos separados.
{\tt eliminaTodasLasConcordancias} recorre la lista de manos y llama a
{\tt eliminaConcordancias} para cada una de ellas:
\beforeverb
\begin{verbatim}
class JuegoOldMaid(JuegoDeCartas):
...
def eliminaTodasLasConcordancias(self):
cant = 0
for mano in self.manos:
cant = cant + mano.eliminaConcordancias()
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 devuelev el total.
\index{acumulador}
Cuando la cantidad total de concordancias alcanza a las veinticinco,
eso 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:
\revisar{adjustpage, pagebreak}
%\adjustpage{-2}
%\pagebreak
\beforeverb
\begin{verbatim}
class JuegoOldMaid(JuegoDeCartas):
...
def jugarUnTurno(self, i):
if self.manos[i].estaVacio():
return 0
vecino = self.encuentraVecino(i)
cartaLevantada = self.manos[vecino].darCarta()
self.manos[i].agregaCarta(cartaLevantada)
print "Mano", self.manos[i].nombre, "levantó", cartaLevantada
cant = self.manos[i].eliminaConcordancias()
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 ella o él no hace nada y retorna 0.
Sino, un turno consta de encontrar el primer jugador a la izquierda
que aún tiene cartas, tomar una carta de las que posee, y controlar
para ver si hay concordancias. Antes de retornar, las cartas en la
mano se mezclan, de tal manera que la selección del siguiente jugador
es al azar.
El método {\tt encuentraVecino} comienza con el jugador que está
inmediatamante a la izquierda y continúa hasta alrededor del círculo
hasta que encuentra un jugador que aún tiene cartas.
\beforeverb
\begin{verbatim}
class JuegoOldMaid(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, retornarí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}. Usted puede escribir el
suyo propio.
La siguiente salida proviene de una forma reducida del juego, en la
cual solamente se reparten las quince cartas más altas (desde los diez
hacia arriba) a los tres jugadores. Con este mazo más pequeño, el
juego termina luego de siete concordancias, en lugar de veinticinco.
\beforeverb
\begin{verbatim}
>>> import cartas
>>> juego = cartas.JuegoOldMaid()
>>> 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 concuerda con Reina de Diamantes
Mano Chris: 10 de Picas concuerda con 10 de Tréboles
----- Se eliminaron las concordancias, 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: levantó Rey de Diamantes
Mano Allen: Rey de Corazones concuerda con Rey de Diamantes
Mano Jeff: levantó 10 de Corazones
Mano Chris: levantó Sota de Tréboles
Mano Allen: levantó Sota de Corazones
Mano Jeff: levantó Sota de Diamantes
Mano Chris: levantó Reina de Picas
Mano Allen: levantó Sota de Diamantes
Mano Allen: Sota de Corazones concuerda con Sota de Diamantes
Mano Jeff: levantó Rey de Tréboles
Mano Chris: levantó Rey de Picas
Mano Allen: levantó 10 de Corazones
Mano Allen: 10 de Diamantes concuerda con 10 de Corazones
Mano Jeff: levantó Reina de Picas
Mano Chris: levantó Sota de Picas
Mano Chris: Sota de Tréboles concuerda con Sota de Picas
Mano Jeff: levantó Rey de Picas
Mano Jeff: Rey de Tréboles concuerda con Rey de Picas
----- El juego terminó.
Mano Allen está vacía.
Mano Jeff contiene
Reina de Picas
Mano 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 desde la cual la clase hija hereda.
\item[clase hija:] Una nueva clase creada heredando desde 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: