Comenzando con oop

Introducción a la programación orientada a objetos

Introducción

La Programación Orientada a Objetos (principalmente conocida como OOP) es un paradigma de programación para resolver problemas.
La belleza de un programa OO (orientado a objetos) es que pensamos en el programa como un grupo de objetos que se comunican entre sí, en lugar de un script secuencial que sigue órdenes específicas.

Hay muchos lenguajes de programación que soportan programación orientada a objetos, algunos de los más populares son:

-Java -C++

  • C#

También se sabe que Python es compatible con OOP, pero carece de algunas propiedades.


Terminología de programación orientada a objetos

El término más básico en programación orientada a objetos es una clase.
Una clase es básicamente un objeto, que tiene un estado y funciona de acuerdo a su estado.

Otro término importante es instancia.
Piense en una clase como una plantilla utilizada para crear instancias de sí misma. La clase es una plantilla y la(s) instancia(s) son los objetos concretos.

Una instancia creada a partir de la clase A generalmente se conoce como del ’tipo A’, exactamente como el tipo de 5 es int y el tipo de “abcd” es una cadena.

Un ejemplo de creación de una instancia llamada insance1 de tipo (clase) ClassA:

###Java

ClaseA instancia1 = nueva ClaseA();

C++

ClaseA instancia1; o

ClaseA * instancia1 = nueva ClaseA (); # En el montón

pitón

instancia1 = ClaseA()

Como puede ver en el ejemplo anterior, en todos los casos se mencionaba el nombre de la clase y después había paréntesis vacíos (excepto en C++ donde, si están vacíos, se pueden quitar los paréntesis). En estos paréntesis podemos pasar argumentos al constructor de nuestra clase.

Un constructor es un método de una clase que se llama cada vez que se crea una instancia. Puede tomar argumentos o no. Si el programador no especifica ningún constructor para una clase que construye, se creará un constructor vacío (un constructor que no hace nada).
En la mayoría de los lenguajes el constructor se define como un método sin definir su tipo de retorno y con el mismo nombre de la clase (ejemplo en algunas secciones).

Un ejemplo de creación de una instancia denominada b1 de tipo (clase) ClassB. El constructor de ClassB toma un argumento de tipo int:

###Java

ClaseA instancia1 = nueva ClaseA(5); o

int i = 5; ClaseA instancia1 = nueva ClaseA(i);

C++

ClaseA instancia1(5);

pitón

instancia1 = ClaseA(5) Como puede ver, el proceso de creación de una instancia es muy similar al proceso de llamar a una función.


Funciones vs Métodos

Tanto las funciones como los métodos son muy similares, pero en el diseño orientado a objetos (OOD) cada uno tiene su propio significado.
Un método es una operación realizada en una instancia de una clase. El método en sí suele utilizar el estado de la instancia para operar.
Mientras tanto, una función pertenece a una clase y no a una instancia específica. Esto significa que no utiliza el estado de la clase ni ningún dato almacenado en una instancia.

De ahora en adelante mostraremos nuestros ejemplos solo en Java ya que la programación orientada a objetos es muy clara en este lenguaje, pero los mismos principios funcionan en cualquier otro lenguaje de programación orientada a objetos.

En Java, una función tiene la palabra static en su definición, así:

// File's name is ClassA
public static int add(int a, int b) {
    return a + b;
}

Esto significa que puede llamarlo desde cualquier parte del script.

// From the same file
System.out.println(add(3, 5));

// From another file in the same package (or after imported)
System.out.println(ClassA.add(3, 5));

Cuando llamamos a la función desde otro archivo usamos el nombre de la clase (en Java también es el nombre del archivo) a la que pertenece, esto da la intuición de que la función pertenece a la clase y no a ninguna de sus instancias.

Por el contrario, podemos definir un método en ClassA así:

// File's name is ClassA
public int subtract(int a, int b){
    return a - b;
}

Después de esta declaración podemos llamar a este método así:

ClassA a = new ClassA();
System.out.println(a.subtract(3, 5));

Aquí necesitábamos crear una instancia de ClassA para llamar a su método restar. Tenga en cuenta que NO PODEMOS hacer lo siguiente:

System.out.println(ClassA.subtract(3, 5));

Esta línea producirá un error de compilación quejándose de que llamamos a este método no estático sin una instancia.


Usando el estado de una clase

Supongamos que queremos implementar nuestro método restar nuevamente, pero esta vez siempre queremos restar el mismo número (para cada instancia). Podemos crear la siguiente clase:

class ClassB {

    private int sub_amount;

    public ClassB(int sub_amount) {
        this.sub_amount = sub_amount;
    }

    public int subtract(int a) {
        return a - sub_amount;
    }

    public static void main(String[] args) {
        ClassB b = new ClassB(5);
        System.out.println(b.subtract(3)); // Ouput is -2
    }
}

Cuando ejecutamos este código, se crea una nueva instancia llamada b de la clase ClassB y su constructor se alimenta con el valor 5.
El constructor ahora toma la sub_cantidad dada y la almacena como su propio campo privado, también llamado sub_cantidad (esta convención es muy conocida en Java, para nombrar los argumentos de la misma manera que los campos).
Después de eso, imprimimos en la consola el resultado de llamar al método restar en b con el valor de 3.

Tenga en cuenta que en la implementación de subtract no usamos this. como en el constructor.
En Java, this solo necesita escribirse cuando hay otra variable con el mismo nombre definida en ese ámbito. Lo mismo funciona con self de Python.
Entonces, cuando usamos sub_amount en restar, hacemos referencia al campo privado que es diferente para cada clase.

Otro ejemplo a destacar.
Simplemente cambiemos la función principal en el código anterior a lo siguiente:

ClassB b1 = new ClassB(1);
ClassB b2 = new ClassB(2);

System.out.println(b1.subtract(10)); // Output is 9
System.out.println(b2.subtract(10)); // Output is 8

Como podemos ver, b1 y b2 son independientes y cada uno tiene su propio estado.


Interfaces y Herencia

Una interfaz es un contrato, define qué métodos tendrá una clase y por lo tanto sus capacidades. Una interfaz no tiene una implementación, solo define lo que debe hacerse.
Un ejemplo en Java sería:

interface Printalbe {
    public void print();
}

La interfaz Printalbe define un método llamado print pero no da su implementación (bastante extraño para Java). Cada clase que se declara a sí misma como “implementadora” de esta interfaz debe proporcionar una implementación para el método de dibujo. Por ejemplo:

class Person implements Printalbe {

    private String name;

    public Person(String name) {
        this.name = name;
    }

    public void print() {
        System.out.println(name);
    }
}

Si Person se declarara implementando Drawable pero no proporcionara una implementación para print, habría un error de compilación y el programa no compilaría.

La herencia es un término que apunta a una clase que extiende otra clase. Por ejemplo, digamos que ahora tenemos una persona que tiene una edad. Una forma de implementar una persona como esa sería copiar la clase Persona y escribir una nueva clase llamada PersonaEnvejecida que tiene los mismos campos y métodos pero tiene otra propiedad: edad.
Esto sería horrible ya que duplicamos nuestro código completo solo para agregar una característica simple a nuestra clase.
Podemos usar la herencia para heredar de Persona y así obtener todas sus funciones, luego mejorarlas con nuestra nueva función, así:

class AgedPerson extends Person {

    private int age;

    public AgedPerson(String name, int age) {
        super(name);
        this.age = age;
    }

    public void print() {
        System.out.println("Name: " + name + ", age:" + age);
    }
}

Están pasando algunas cosas nuevas:

  • Usamos la palabra guardada extends para indicar que heredamos de Person (y también su implementación a Imprimible, por lo que no necesitamos declarar implementando Imprimible nuevamente).
  • Usamos la palabra guardada super para llamar al constructor de Person.
  • Reemplazamos el método print de Person con uno nuevo.

Esto se está volviendo bastante técnico en Java, así que no profundizaré más en este tema. Pero mencionaré que hay muchos casos extremos que deben aprenderse sobre herencia e interfaces antes de comenzar a usarlos. Por ejemplo, ¿qué métodos y funciones se heredan? ¿Qué sucede con los campos privados/públicos/protegidos cuando se heredan de una clase? y así.

Clase abstracta

Una clase abstracta es un término bastante avanzado en programación orientada a objetos que describe una combinación de interfaces y herencia. Le permite escribir una clase que tiene métodos/funciones implementados y no implementados. En Java, esto se hace usando la palabra clave abstract y no lo explicaré más que con un ejemplo rápido:

abstract class AbstractIntStack {

    abstract public void push(int element);

    abstract public void pop();

    abstract public int top();

    final public void replaceTop(int element) {
        pop();
        push(element);
    }
}

Nota: la palabra clave final indica que no puede anular este método cuando hereda de esta clase. Si una clase se declara final, entonces ninguna clase puede heredar nada de ella.

Introducción

OOP: la programación orientada a objetos es un paradigma de programación muy utilizado en estos días. En OOP, modelamos problemas del mundo real usando Objetos y sus comportamientos, para resolverlos mediante programación.

Hay cuatro conceptos principales de OOP

  1. Herencia
  2. Polimorfismo
  3. Abstracción
  4. Encapsulación

Estos cuatro conceptos juntos se utilizan para desarrollar programas en programación orientada a objetos.

Hay varios lenguajes que soportan la Programación Orientada a Objetos. Los idiomas más populares son

  • C++
  • Java
  • C#
  • Python (Python no está completamente orientado a objetos, pero tiene la mayoría de las características de programación orientada a objetos)