Skip to content

Latest commit

 

History

History
400 lines (384 loc) · 18.8 KB

POO.md

File metadata and controls

400 lines (384 loc) · 18.8 KB


Índice:

  1. Introducción a la Programación Orientada a Objetos
  • Historia y evolución de la POO.
  • Beneficios y propósito de la POO.
  1. Conceptos Básicos de la POO
  • Clases y Objetos.

Las clases son abstracciones que usamos para convertir elementos que queremos que formen parte de nuestro programa dl cual encapsulamos mediante dos conceptos: atributos y métodos. Habría que añadir los eventos a este fin, ya que serían los triggers que desencadenarían paquetes de código mediante la interacción del usuario con una interfaz grafica, por ejemplo.

Los objetos, por otro lado, son la materialización de dicha clase en el codigo, lo que tambien se conoce como instsancia. La clase sería algo así como el concepto, la idea de lo que queremos hacer, concebida al detalle, mientras que el objeto es la materialización de la dea la cual podremos usar.

  • Atributos y Métodos.

    • Atributos: Son los datos que contendrá la clase (nombre, contraseña, contador…)

    • Métodos: Son las funcionalidades con las que contara la clase las cuales podrá usar el propio objeto u otros objetos que hereden de dicha clase. Pueden tener opcionalmente parámetros de entrada o parámetros de salida. Si el método retorna un valor se le suele llamar función si no procedimiento

  • Encapsulación y el principio de ocultamiento.
    La encapsulación es el principio mediante el cual en el código se ocultan distintas secciones que no necesitan conocer o modificar elementos fuera de dicha clase. Esto ofrece dos ventajas:

    • Lo que hace el usuario puede ser controlado internamente (incluido los errores)

    • Al mantener la mayor parte del codigo oculto, se pueden modificar o mejorar el codigo sin que afecte al modo en el que los usuarios acceden a dicho codigo

  1. Herencia
  • Concepto y beneficios.

La herencia es la capacidad que tiene una clase de heredar los atributos y métodos de otra. Lo cual favorece a reutilización de código

  • Clases base y clases derivadas.

    • Clase base: La clase base es un tipo específico de clase de la cual podrán heredar las distintas clases derivadas, normalmente atienden a dos objetivos:
      • Diversos tipos tienen algo en común, por ejemplo en el juego del ajedrez, donde todas las piezas forman parte del juego y compartirían desplazamientos en los mismos ejes
      • Se precisa ampliar la funcionalidad de un programa sin tener que modificar el código existente. Un alfil se mueve de una manera distinta al peón pero comparten dichos estructuras de movimiento.
  • Clase derivada: La clase derivada es aquella que hereda de una clase base con el fin de reutilizar código para ampliar funcionalidades, testear…

  • Sobrescritura y sobrecarga de métodos.

    • Sobrecarga: La sobrecarga de métodos, permite usar el mismo nombre del método pero solo si se tiene diferente firma. Cuando hablamos de la firma de un método, nos referimos a sus parámetros…
      Un ejemplo: Supongamos que yo quiero hacer sumas, pero mi programa debe sumar por una parte 2 números enteros o 2 números doubles… para esto tengo 2 opciones:
    1. crear 2 métodos, uno llamado sumaEnteros(int a, int b) y otro sumaDoubles(double a, double b)

    2. Aplicar el concepto de sobrecarga, donde aunque también vamos a crear 2 metodos, los vamos a llamar con el mismo nombre pero con diferentes parametros…sumar(int a, int b) y sumar(double a, double b)

    • Sobreescriura: Se da cuando en un método heredado se está sobreescribiendo la lógica. El resto de la clase se puede mantener sin sobreescribir, por ejemplo:

    // Definimos la clase padre class ClasePadre {
    public String metodo1() {
    return “Método 1 de ClasePadre”;
    }

     public String metodo2() {
         return "Método 2 de ClasePadre";
     } }
    

    // Definimos la clase hija que hereda de la clase padre class
    ClaseHija extends ClasePadre {

     // Sobreescribimos el metodo1 de la ClasePadre
     @Override
     public String metodo1() {
         return "Método 1 de ClaseHija";
     } }
    

    // Clase principal para probar el comportamiento public class Main {
    public static void main(String[] args) {
    ClaseHija obj = new ClaseHija();

         // Llamamos a los métodos
         System.out.println(obj.metodo1());  // Salida: Método 1 de ClaseHija
         System.out.println(obj.metodo2());  // Salida: Método 2 de ClasePadre
     } }
    

Ahora, si queremos utilizar el método original de la clase padre dentro del método sobrescrito en la clase hija super(), lo hacemos de la siguiente manera:

 class ClaseHija extends ClasePadre {
 // Sobreescribimos el metodo1 de la ClasePadre
 @Override
 public String metodo1() {
     String original = super.metodo1();
     return original + " pero modificado por ClaseHija";
 } }

  1. Polimorfismo
  • Definición y utilidad.
    El polimorfismo es la capacidad de una entidad (como una variable o función) de tomar varias formas (como hemos visto antes cuando hablabamos de la sobreescritura y la sobrecarga de métodos). Más concretamente, se refiere a la capacidad de diferentes clases de ser tratadas como instancias de la misma clase, principalmente a través de la herencia. El término “polimorfismo” proviene del griego y significa “muchas formas”.

El polimorfismo es una herramienta poderosa y fundamental en POO por las siguientes razones:

  1. Flexibilidad y Reusabilidad: Permite que el código se escriba de manera más genérica y reutilizable. Por ejemplo, puedes tener una función que procese objetos de una clase padre y, gracias al polimorfismo, esta función puede procesar cualquier objeto de las clases derivadas sin necesidad de reescribir o adaptar la función.

  2. Extensibilidad: El polimorfismo facilita la adición de nuevas clases derivadas sin modificar el código existente. Imagina que tienes un sistema que dibuja diferentes formas geométricas. Si luego deseas agregar una nueva forma, simplemente creas una nueva clase para esa forma y, gracias al polimorfismo, el sistema podrá dibujarla sin cambios adicionales.

  3. Encapsulación de Comportamiento: Cada clase derivada puede tener su propia implementación de un método (sobrescritura), permitiendo que el objeto se comporte de manera adecuada según su tipo real, aunque sea tratado como un objeto de la clase padre.

  4. Desacoplamiento: Reduce las dependencias entre componentes del software, ya que permite que las operaciones se realicen basándose en contratos (como interfaces o clases base) en lugar de implementaciones específicas.

  5. Claridad y Organización del Código: Facilita la lectura y el mantenimiento del código, ya que los desarrolladores pueden esperar comportamientos consistentes entre objetos relacionados, incluso si esos objetos pertenecen a diferentes clases derivadas.

  • Polimorfismo de sobrecarga y polimorfismo de sobreescritura (o dinámico).

Polimorfismo de Sobrecarga (Overloading)

Definición: Se refiere a la capacidad de una función o método de ser “sobrecargado” con múltiples versiones, cada una aceptando diferentes números o tipos de argumentos.

Características:

  • Una misma función o método se define múltiples veces con diferentes listas de parámetros.
  • La decisión sobre qué versión del método llamar se toma en tiempo de compilación.

Ejemplo:

public int sumar(int a, int b) {
    return a + b;
}

public double sumar(double a, double b) { return a + b; }

Ventajas:

  • Incrementa la legibilidad al permitir que diferentes funciones o métodos compartan un mismo nombre.
  • Facilita la implementación de funciones o métodos que realizan tareas similares pero con diferentes tipos o números de argumentos.

Inconvenientes:

  • Puede aumentar la complejidad si se abusa de la sobrecarga con muchas versiones del mismo método.

Polimorfismo de Sobreescritura (o Dinámico, Overriding)

Definición: Se refiere a la capacidad de una clase derivada para proporcionar una implementación específica de un método que ya está definido en su clase base o interfaz.

Características:

  • La firma del método en la clase derivada debe ser idéntica a la de la clase base.
  • La decisión sobre qué versión del método llamar se toma en tiempo de ejecución basándose en el tipo real del objeto.
  • En muchos lenguajes, como Java, la anotación @Override se utiliza para indicar que un método está siendo sobreescrito.

Ejemplo:

class Animal {
    void sonido() {
        System.out.println("El animal hace un sonido");
    }
}

class Perro extends Animal { @Override void sonido() { System.out.println("El perro ladra"); } }

Ventajas:

  • Facilita la extensibilidad al permitir que clases derivadas modifiquen o extiendan comportamientos definidos en clases base.
  • Refuerza la abstracción al permitir que objetos de diferentes clases se utilicen de manera uniforme, confiando en que cada objeto se comportará de manera adecuada según su tipo real.

Inconvenientes:

  • Si no se utiliza adecuadamente, puede llevar a comportamientos no deseados en la clase derivada.
  1. Abstracción

La abstracción es uno de los cuatro pilares fundamentales de la Programación Orientada a Objetos (junto con la encapsulación, la herencia y el polimorfismo). Permite a los programadores ocultar los detalles complejos y mostrar solo la información esencial, facilitando el manejo de conceptos complejos al representarlos a un alto nivel.


Clases Abstractas y Métodos Abstractos:

Clases Abstractas:

  • Una clase abstracta es una que no puede ser instanciada directamente. Su propósito principal es ser extendida (heredada) por otras clases.
  • Puede tener variables de instancia, métodos (que tienen cuerpo) y métodos abstractos (sin cuerpo).

Métodos Abstractos:

  • Son métodos declarados en una clase abstracta sin implementación. No tienen cuerpo.
  • Las clases que heredan una clase abstracta deben proporcionar una implementación concreta de todos los métodos abstractos, a menos que ellas mismas sean también clases abstractas.

Ejemplo:

abstract class Animal {
    abstract void sonido();
}

class Perro extends Animal { @Override void sonido() { System.out.println("El perro ladra"); } }

Diferencia entre Interfaces y Clases Abstractas:

Interfaces:

  • Es una estructura completamente abstracta que puede incluir métodos abstractos y constantes, pero no implementaciones concretas ni variables de instancia.
  • Permite la “herencia múltiple”, es decir, una clase puede implementar múltiples interfaces.

Clases Abstractas:

  • Pueden tener tanto métodos concretos (con cuerpo) como abstractos (sin cuerpo).
  • Pueden tener variables de instancia.
  • Una clase puede extender solo una clase abstracta, siguiendo el principio de “herencia simple” en la mayoría de los lenguajes de programación orientados a objetos.

Comparativa:

  • Propósito: Las clases abstractas suelen ser una base para clases con características y comportamientos similares. Las interfaces se utilizan para definir contratos que otras clases deben implementar, independientemente de su jerarquía en la herencia.

  • Flexibilidad: Las interfaces son más flexibles ya que una clase puede implementar múltiples interfaces, mientras que solo puede heredar de una única clase abstracta.

  • Estado: Las clases abstractas pueden mantener el estado a través de variables de instancia. Las interfaces no pueden.

Ejemplo:

interface Corredor {
    void correr();
}

abstract class Animal { abstract void sonido(); }

class Perro extends Animal implements Corredor { @Override void sonido() { System.out.println("El perro ladra"); }

@Override
public void correr() {
    System.out.println("El perro corre");
}

}

  1. Encapsulación

Definición: La encapsulación se refiere al proceso de ocultar detalles internos o complejos de un objeto, exponiendo solo lo que es necesario o seguro. En otras palabras, es una técnica que envuelve el código y los datos juntos como una única unidad, protegiendo el estado del objeto de accesos no autorizados y modificaciones.


Propósito:

  1. Proteger la Integridad de los Datos: Mediante la encapsulación, podemos asegurarnos de que el estado interno de un objeto no sea alterado inapropiadamente.

  2. Flexibilidad y Mantenibilidad: Al ocultar los detalles internos, es más fácil hacer cambios en la implementación de una clase sin afectar a las partes del código que la utilizan.

  3. Aumentar la Modularidad: La encapsulación fomenta la modularidad al mantener el comportamiento y el estado del objeto en un solo lugar.


Cómo se Implementa:

  1. Variables/Miembros Privados: Los atributos (o variables de instancia) de una clase se hacen privados para que no puedan ser accesibles o modificables directamente desde fuera de la clase.

  2. Métodos Públicos Accesores y Mutadores (Getters y Setters): Para acceder o modificar los atributos privados, se proporcionan métodos públicos. Los métodos “getters” (obtener) permiten leer valores de atributos, y los métodos “setters” (establecer) permiten cambiarlos. Estos métodos actúan como una puerta controlada al estado interno del objeto.

Ejemplo:

public class Persona {
    private String nombre; // Atributo privado
    private int edad;      // Atributo privado
// Constructor
public Persona(String nombre, int edad) {
    this.nombre = nombre;
    this.edad = edad;
}

// Método "getter" para el nombre
public String getNombre() {
    return nombre;
}

// Método "setter" para el nombre
public void setNombre(String nombre) {
    this.nombre = nombre;
}

// Método "getter" para la edad
public int getEdad() {
    return edad;
}

// Método "setter" para la edad
public void setEdad(int edad) {
    if (edad > 0) { // Validación simple
        this.edad = edad;
    }
}

}


Beneficios:

  1. Control: La encapsulación brinda un control completo sobre los datos de un objeto, permitiendo validaciones o transformaciones cuando se accede o modifica.

  2. Reducción de Errores: Al restringir el acceso directo, se evita que otras partes del código modifiquen el estado del objeto de maneras inesperadas o erróneas.

  3. Simplicidad para el Usuario: Los usuarios o desarrolladores que utilicen la clase no necesitan conocer o preocuparse por los detalles internos de implementación.


  1. Composición y Agregación
  • Relaciones entre clases.
  • Cuándo usar herencia vs. composición.
  1. Principios SOLID

  2. Patrones de Diseño

  • Concepto y propósito.
  • Patrones comunes: Singleton, Factory, Observer, Strategy, entre otros.

Recursos recomendados:

  1. Libros:
  • “Object-Oriented Thought Process” de Matt Weisfeld: Es un excelente libro para principiantes que desean comprender el pensamiento detrás de la POO.
  • “Design Patterns: Elements of Reusable Object-Oriented Software” de Erich Gamma, Richard Helm, Ralph Johnson y John Vlissides: Es un libro clásico sobre patrones de diseño en POO.
  1. Ejercicios y práctica:
  • Exercism.io: Te permite practicar con ejercicios de programación y recibir retroalimentación. Tienen ejercicios específicos para diferentes lenguajes que te ayudarán a practicar POO.

Dado que la Programación Orientada a Objetos es un concepto que se aplica en muchos lenguajes de programación (Java, C#, C++, Python, Ruby, etc.)