summaryrefslogtreecommitdiff
path: root/src/05_oop.md
diff options
context:
space:
mode:
Diffstat (limited to 'src/05_oop.md')
-rw-r--r--src/05_oop.md318
1 files changed, 318 insertions, 0 deletions
diff --git a/src/05_oop.md b/src/05_oop.md
index fbe51e3..7d0eacd 100644
--- a/src/05_oop.md
+++ b/src/05_oop.md
@@ -48,3 +48,321 @@ perspectiva de que python no tiene ni idea de lo que es un perro y tú tienes
que explicárselo. Una vez lo haces, declarando tu clase, puedes crear
diferentes perros y ponerlos a jugar. Lo bonito de programar es que tu programa
es tu mundo y tú decides lo que es para ti (o para tu programa) un perro.
+
+A nivel práctico, los objetos son grupos de datos (el *estado*) y funciones (la
+*funcionalidad*). Estas funciones son capaces de alterar los datos del propio
+objeto y no de otro (se intuye el concepto de *identidad*). Analizándolo desde
+el conocimiento que ya tienes, es lógico pensar que un objeto es, por tanto,
+una combinación de valores y funciones accesible a modo de elemento único.
+Exactamente de eso se trata.
+
+Existe una terminología técnica, eso sí, para referirse a esos valores y a esas
+funciones. Normalmente los valores se conocen como *propiedades* del objeto y
+las funciones se conocen como *métodos*. Así que siempre que hagamos referencia
+a cualquiera de estas dos palabras clave debes recordar que hacen referencia a
+la programación orientada a objetos.
+
+### Fundamento teórico
+
+La programación basada en clases se basa en tres conceptos fundamentales que
+repasaremos aquí de forma rápida para razonar el interés de la programación
+orientada a objetos sobre otros paradigmas.
+
+La **encapsulación**[^encapsulation] trata de crear datos con sus métodos
+propios para alterarlos de modo que restrinjan el acceso directo al contenido
+de estos datos con el fin de asegurar una coherencia o robustez interna. Puedes
+entender esto como una forma de esconder información o como mi profesor de
+programación II en la universidad solía decir: «Las patatas se pelan en la
+cocina del restaurante, no en el comedor». La utilidad de la encapsulación es
+la de aislar secciones del programa para tener total control sobre su
+contenido gracias a tener total control de la vía de acceso a estos datos. A
+nivel práctico este concepto puede usarse para, por ejemplo, obligar a que un
+objeto sólo pueda ser alterado en incrementos controlados en lugar de poder
+pisarse con un valor arbitrario.
+
+La **herencia**[^inheritance] es un truco para reutilizar código de forma
+agresiva que, casualmente, sirve como una buena forma de razonar. Aporta la
+posibilidad de crear nuevas *clases* a partir de clases ya existentes.
+Volviendo a la simplificación anterior, si una clase es una definición
+enciclopédica de un concepto, como un perro, puede estar basada en otra
+descripción para evitar contar todo lo relacionado con ella. En el caso del
+perro, el perro es un animal. Animal podría ser otra clase definida previamente
+de la que el perro heredara y recibiera gran parte de su descripción genérica
+para sólo cubrir puntos que necesite especificar como el tamaño, la forma, el
+tipo de animal, el comportamiento concreto, etc. Existe la posibilidad de hacer
+herencias múltiples también ya que algunos conceptos pueden describirse en dos
+superclases distintas: un perro es un animal (vive, muere, se alimenta, se
+reproduce) y también es terrestre (camina sobre una superficie, etc). Ambos
+conceptos son independientes: los coches también son terrestres pero no son
+animales y los peces también son animales pero no terrestres.
+
+Y, finalmente, el **polimorfismo**[^polymorphism]. La propia etimología de la
+palabra define con bastante precisión el concepto, pero aplicarlo a la
+programación orientada a objetos no es tan evidente. Existen varios tipos de
+polimorfismo pero el más sencillo es entender el *subtyping*[^subtyping]. Una
+vez lo comprendas el resto será evidente. Si volvemos al ejemplo del perro,
+para ciertos comportamientos, nos da igual que tratemos de perros, de peces o
+de pájaros, todos son animales y todos los animales se comportan de la misma
+forma. Es decir, todas las subclases señaladas comparten el comportamiento de
+la superclase animal. Si esto es cierto, puede suponerse que en cualquier caso
+en el que se espere un objeto de la clase animal es seguro usar una subclase de
+ésta.
+
+Visto desde otra perspectiva, las subclases comparten comportamiento porque
+reutilizan las funciones de la clase principal o las redefinen (*herencia*),
+pero podemos asegurar que todas las subclases tienen un conjunto de funciones
+con la misma estructura, independientemente de lo que hagan, que aseguran que
+siempre van a ser compatibles. El nombre de esta cualidad viene a que un perro
+puede tomar la forma de un animal.
+
+Los otros tipos de polimorfismo explotan el mismo comportamiento de diferentes
+maneras, mientras que recuerdes que es posible programar de modo que el tipo de
+los datos que trates sea indiferente o pueda variar es suficiente. Otro ejemplo
+de esto son los operadores matemáticos, que son capaces de funcionar en
+cualquier tipo de número (integer, float, complex, etc.) de la misma manera, ya
+que todos son números, al fin y al cabo.
+
+Entender estos conceptos a nivel intuitivo, sin necesidad de entrar en los
+detalles específicos de cada uno, es interesante para cualquier programador y
+facilita de forma radical la comprensión de muchas de las decisiones de diseño
+tomadas en python y en proyectos relacionados aunque también, por supuesto, de
+otros lenguajes y herramientas.
+
+[^encapsulation]: https://en.wikipedia.org/wiki/Encapsulation_(computer_programming)
+[^inheritance]: https://en.wikipedia.org/wiki/Inheritance_(object-oriented_programming)
+[^polymorphism]: https://en.wikipedia.org/wiki/Polymorphism_(computer_science)
+[^subtyping]: https://en.wikipedia.org/wiki/Subtyping
+
+
+## Sintaxis
+
+En el siguiente ejemplo se muestra la sintaxis básica a la hora de crear una
+clase y después instanciar dos nuevos objetos `bobby` y `beltza`. Los puntos
+(`.`) se utilizan para indicar a quién pertenece el método o propiedad al que
+se hace referencia (*identidad*). De este modo, no ocurrirá lo mismo cuando el
+perro (`Dog`) `bobby` ladre (`bark`) que cuando lo haga el perro `beltza`.
+
+Los métodos describen la *funcionalidad* asociada a los perros en general, pero
+además, la función `bark` los describe en particular, haciendo que cada perro
+tome su nombre (`name`), una propiedad o dicho de otro modo, su *estado*.
+
+``` python
+class Dog:
+ type = "canine"
+ def __init__(self, name):
+ self.name = name
+ def bark(self):
+ print("Woof! My name is " + self.name)
+
+bobby = Dog("Bobby")
+beltza = Dog("Beltza")
+
+bobby.name # Bobby
+beltza.name # Beltza
+
+bobby.type # canine
+beltza.type # canine
+
+bobby.bark() # Prints "Woof! My name is Bobby"
+beltza.bark() # Prints "Woof! My name is Beltza"
+```
+
+Queda por aclarar, sin embargo, qué es la función `__init__` y por qué tiene un
+nombre tan extraño y qué es `type = canine`, que lo trataremos en próximos
+apartados de este capítulo.
+
+Antes de entrar en esos dos puntos, que tratan conceptos algo más avanzados, es
+interesante ver cómo definir clases mediante la herencia. Basta con introducir
+una lista de clases de las que heredar en la definición de la clase, entre
+paréntesis, como si de argumentos de entrada de una función se tratara, tal y
+como se muestra en la clase `Dog` del siguiente ejemplo ejecutado en la REPL:
+
+``` python
+>>> class Animal:
+... def live(self):
+... print("I'm living")
+...
+>>> class Terrestrial:
+... def move(self):
+... print("I'm moving on the surface")
+...
+>>> class Dog(Animal, Terrestrial):
+... def bark(self):
+... print("woof!")
+... def move(self):
+... print("I'm walking on the surface")
+...
+>>> bobby = Dog()
+>>> bobby.bark()
+woof!
+>>> bobby.live()
+"I'm living"
+>>> bobby.move()
+"I'm walking on the surface"
+```
+
+El ejemplo muestra un claro uso de la herencia. La clase `Dog` hereda
+automáticamente las funciones asociadas a las superclases, pero es capaz de
+definir las propias e incluso redefinir algunas. Independientemente de la
+redefinición del método `move`, cualquier perro (`Dog`) va a ser capaz de
+moverse por la superficie, porque la superclase `Terrestrial` ya le da los
+métodos necesarios para hacerlo. Lo que ocurre es que cualquier subclase de
+`Terrestrial` tiene la ocasión moverse (`move`) a su manera: en el caso del
+perro, caminando.
+
+> NOTA: La herencia es interesante, pero tampoco debe caerse en la psicosis de
+> añadir demasiadas superclases. En ocasiones las superclases son necesarias,
+> sobre todo cuando aprovechar el polimorfismo facilita el trabajo, pero
+> usarlas de forma agresiva genera código extremadamente complejo sin razón.
+
+
+### Métodos de objeto o funciones de clase: `self`
+
+Te habrás fijado que los métodos reciben un parámetro de entrada llamado `self`
+que no se utiliza a la hora de llamarlos: al hacer `bobby.bark()` no se
+introduce ningún argumento de entrada a la función `bark`.
+
+Sin embargo, si no se añade el argumento de entrada a la definición del método
+`bark` y se llama a `bobby.bark()` pasa lo siguiente:
+
+``` python
+>>> class Dog:
+... def bark():
+... pass
+...
+>>> bobby = Dog()
+>>> bobby.bark()
+Traceback (most recent call last):
+ File "<stdin>", line 1, in <module>
+TypeError: bark() takes 0 positional arguments but 1 was given
+```
+
+Python dice que `bark` espera `0` argumentos posicionales pero se le ha
+entregado `1`, que nosotros no hemos metido en la llamada, claro está. Así que
+ha debido de ser él.
+
+Efectivamente, python introduce un argumento de entrada en los métodos, el
+argumento de entrada que por convención se suele llamar `self`. Este parámetro
+es el propio `bobby` en este caso.
+
+> NOTA: Por convención se le denomina `self`. Tú le puedes llamar como te
+> apetezca pero, si pretendes que otros programadores te entiendan, mejor
+> `self`.
+
+Para explicar por qué ocurre esto es necesario diferenciar bien entre clase y
+objeto. Tal y como hemos hecho antes con las definiciones enciclopédicas
+(*clase*) y los conceptos del mundo real que encajan en la definición
+(*objeto*). Los objetos también se conocen como instancias, son piezas de
+información independiente que han sido creadas a partir de la definición que la
+clase aportaba.
+
+En python las clases tienen la posibilidad de tener funciones, que definen el
+comportamiento de la clase y no el de los objetos que se crean desde ellas.
+Ten en cuenta que las clases también deben procesarse y ocupan un espacio en la
+memoria, igual que te ocurre a ti, puedes conocer un concepto y su
+comportamiento y luego muchos casos que cumplan ese concepto y ambas cosas
+son independientes. Esta posibilidad aporta mucha flexibilidad y permite
+definir clases complejas.
+
+Ahora bien, para python las funciones de clase y los métodos (de los objetos,
+si no no se llamarían métodos), se implementan de la misma manera. Para la
+clase ambas cosas son lo mismo. Sin embargo, el comportamiento del operador
+punto (`.`), que dice a quién pertenece la función o método, es diferente si el
+valor de la izquierda es una clase o un objeto. Introduciendo en el segundo
+caso el propio objeto como primer parámetro de entrada, el `self` del que
+hablamos, para que la clase sepa qué objeto tiene que alterar. Este es el
+mecanismo de la *identidad* del que antes hablamos y no llegamos a definir en
+detalle. Cada objeto es único, y a través del `self` se accede a él.
+
+Es un truco interesante para no almacenar las funciones en cada uno de los
+objetos como método. En lugar de eso, se mantienen en la definición de la clase
+y cuando se llama al método, se busca de qué clase es el objeto y se llama a la
+función de la clase con el objeto como argumento de entrada.
+
+Dicho de otra forma, `bobby.bark()` es equivalente a `Dog.bark( bobby )`.
+
+Ilustrado en un ejemplo más agresivo, puedes comprobar que en función de a
+través de qué elemento se acceda a la función `bark` python la interpreta de
+forma distinta. A veces como función (*function*) y otras veces como método
+(*method*), en función de si se accede desde la clase o desde el objeto:
+
+``` python
+>>> class Dog:
+... def bark(self):
+... pass
+...
+>>> type ( Dog.bark)
+<class 'function'>
+>>> type ( bobby.bark )
+<class 'method'>
+```
+
+> NOTA: También te habrás fijado, y si no lo has hecho es momento de hacerlo,
+> que los nombres de las clases empiezan por mayúscula en los ejemplos (`Dog`)
+> mientras que los objetos comienzan en minúscula (`bobby`). Se trata de otra
+> convención ampliamente utilizada para saber diferenciar entre uno y otro de
+> forma sencilla. Es evidente cuál es la clase y el objeto con los nombres que
+> hemos tratado en los ejemplos, pero en otros casos puede no serlo y con este
+> sencillo truco facilitas la lectura de tu código. Hay muchas ocasiones en las
+> que esta convención se ignora, así que cuidado.
+> Prueba a hacer `type(int)` en la terminal.
+
+### Variables de clase
+
+
+
+### Encapsulación explícita
+
+Es posible que te encuentres en alguna ocasión con métodos o propiedades,
+*campos* en general, cuyo nombre comience por `_` o por `__`. Se trata de casos
+en los que esas propiedades o métodos quieren ocultarse del exterior.
+
+El uso de `_` al inicio del nombre de un campo es una convención que avisa de
+que este campo no debe accederse desde el exterior de la clase y su objetivo es
+usarlo desde el interior de ésta.
+
+Esta convención se llevó al extremo en algún momento y se decidió crear un caso
+en el que esta convención inicial tuviera cierta funcionalidad añadida para las
+dobles barras bajas (`__`) que impidiera un acceso accidental a esos campos
+conocido como *name mangling*.
+
+#### Campos privados: *name mangling*
+
+El *name mangling* es un truco que hace python para asegurarse de que no se
+entra por accidente a las secciones que empiezan por `__`. Añade
+`_nombredeclase` al inicio de los campos, transformando su nombre final y
+dificultando el acceso por accidente.
+
+Ese acceso accidental no sólo es para que el programador no acceda, ya que, si
+se esfuerza la suficiente, va a poder hacerlo de igual modo, si no para que el
+propio python no acceda al campo que no corresponde. El hecho de añadir el
+nombre de la clase al campo crea una brecha en la herencia, haciendo que los
+campos no se hereden de la forma esperada.
+
+En una subclase en la que los campos de la clase madre han sido marcados con
+`__`, la herencia hace que estos campos se hereden con el nombre cambiado que
+contiene el nombre de la superclase. De este modo, es difícil para la subclase
+pisar estos campos ya que tendría que definirlos manualmente con el nombre
+cambiado. Crear nuevos campos con `__` no funcionaría, ya que, al haber
+cambiado de clase, el nombre generado será distinto.
+
+Este mecanismo es un truco para crear *campos privados*, concepto bastante
+común en otros lenguajes como Java o C++, que en python es inexistente.
+
+El concepto de los *campos privados* es interesante en la programación
+orientada a objetos. Pensando en la *encapsulación*, es lógico que a veces las
+clases definan métodos o propiedades que sólo los objetos creados a partir de
+ellas conozcan y que los objetos creados de clases heredadas no. Este es el
+método que python tiene para aportar esta funcionalidad.
+
+Es interesante añadir, por otro lado, que python es un lenguaje de programación
+muy dinámico por lo que la propia definición de las clases, y muchas cosas más,
+puede alterarse una vez creadas. Esto significa que el hecho de ocultar campos
+no es más que un acuerdo tácito entre programadores porque, si quisieran,
+podrían definir todo de nuevo. Trucos como este sirven para que el programador
+sea consciente de que está haciendo cosas que se supone que no debería hacer.
+Cuando programes en python, tómate esto como pistas que te indican cómo se
+supone que deberías estar usando las clases.
+
+### Acceso a la superclase
+