Java interfaces para crear abstracción

Una de las cosas sugeridas por el grupo de Android Chile en slack fue que se creara un tutorial para explicar, y demostrar que son las interfaces en Java.
Voy a comenzar este tutorial con una explicación formal de que son las interfaces, y luego lo voy a explicar con ejemplos.

Segun wikipedia, esta es la definición de una interfaz:

“Una interfaz es una colección de métodos abstractos y propiedades.
En las interfaces se especifica qué se debe hacer pero no su implementación. Serán las clases que implementen estas interfaces las que describan la lógica del comportamiento de los métodos.”

Es probable de que esa definición no te ayude mucho, pero acuérdate de esto: “métodos abstractos”, y “implementar lógica del comportamiento de los métodos”.
Voy a explicar un poco mejor esto en un ejemplo.

Ejemplo, sin interfaz

Digamos que tengo una aplicación que te pregunta por tu nombre, y tiene un botón para guardar el nombre que escribas. Talvez llegue a ser tan compleja y hermosa como esto:

Desde esto, podemos asumir que el código para guardar la imagen comienza así:

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        EditText nombre = (EditText) findViewById(R.id.mi_nombre);
        Button boton = (Button) findViewById(R.id.mi_boton);
        boton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                // aca vamos a guardar la informacion
            }
        });
    }
}

Esta parte debería ser simple, pero obviamente la parte que nos importa es esta:

boton.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        // aca vamos a guardar la informacion
    }
});

Ahora tenemos que ver como guardamos la información.

Digamos que vamos a guardar la información en una database local, y después de guardar la información la vamos a mandar a un servidor.
Sin interfaces tenemos un par de opciones, pero la mas obvia es que en nuestra misma actividad guardemos la información directamente. Para esto, la recomendación es que hagamos métodos específicos para esto.
Nuestro código seria algo como esto:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    final EditText nombre = (EditText) findViewById(R.id.mi_nombre);
    Button boton = (Button) findViewById(R.id.mi_boton);
    boton.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            // aca vamos a guardar la informacion
            guardarEnDatabase(nombre.getText().toString());
            mandarAlServidor(nombre.getText().toString());
        }
    });
    }

    private void guardarEnDatabase(String nombre) {
        // guardar a db
    }

    private void mandarAlServidor(String nombre) {
        // enviar nombre al servidor
    }
}

Obviamente, esto funciona. Pero nos presenta 2 problemas:

  1. Que tipo de base de datos vamos a usar, y como la vamos a usar?
  2. Como vamos a mandar información al servidor? Vamos a usar una librería o framework, y cual?

Cual sea nuestra decision, es una decision que vamos a tener que tomar ahora. Esto se transforma en un problema mucho mayor cuando tu aplicación crece, y tienes este tipo de código en 30 actividades y fragmentos.

Cual es el problema real de este tipo de código?

Digamos que decidimos hoy que para nuestra base de datos vamos a usar SQLite con una librería que nos ayude, como OrmLite.
Digamos que decidimos hoy también que vamos a usar Asynctask para mandar la información al servidor.

Hasta ahora, todo bien. Pero en 6 meses tu aplicación crece y ahora necesitas una base de datos mejor que SQLite, o tu jefe/cliente te obliga a usar Realm.
Tambien, como tu aplicación creció, necesitas cambiar de Asynctask a algo mas nuevo como Volley o Retrofit.

Como tienes este tipo de código en 30 actividades y fragmentos, cambiar la base de datos (o la librería que te ayuda a usar la db), y/o cambiar como mandas información al servidor te va significar cambiar 30 actividades. No solo eso, sino que es también fácil agregar lógica adentro de cada método, por lo que cambiar la db o como mandar información al servidor realmente significa crear 80% de la aplicación de nuevo.

Comúnmente no se ve este problema como algo serio en una aplicación pequeña, o con pocos desarrolladores, pero sí se transforma en un problema serio una vez que tu aplicación crece.

Ahora que tenemos el problema, veamos como lo resolvemos.
Existen 2 formas generales para resolver este tipo de problema:

  1. Usamos Interfaces
  2. Ponemos el código de db y network en otra clase, y los llamamos desde métodos estáticos.

Usar metodos estáticos nos pueden servir para este problema, pero si usamos interfaces podemos también ganar la habilidad de ser notificados cuando algo lento termine de hacer lo que esta haciendo. Esto es obviamente un gran beneficio cuando haces cosas con bases de datos, y con cosas a travez de internet.

Convertir el código a Interfaces

Como puedes ves, usar interfaces no es obligatorio. Si realmente no lo quieres hacer, tienes otras alternativas, pero se va a hacer difícil crear abstracción en tu código.

Nuestro primer paso para convertir nuestro código a interfaz, es crear una clase que sea una interfaz, y sigues la definición de interfaz:
“Una interfaz es una colección de métodos abstractos y propiedades.
En las interfaces se especifica qué se debe hacer pero no su implementación”

Crea un archivo nuevo, y llámalo MiInterfaz.java. Aca vamos a crear 2 métodos: uno para guardar a la base de datos, y otro para mandar información a internet.

Al final, el archivo completo de nuestra interfaz va a ser así:

public interface MiInterfaz {

    void guardarLocalmente(String nombre);

    void mandarAlServidor(String nombre);
}

Ahora tenemos que volver a nuestra actividad. Vamos a crear una variable para la interfaz que creamos, y ahora cuando alguien apriete el botón de guardar, vamos a llamar a los 2 métodos que tenemos en la interfaz.

La actividad ahora va a ser así:

public class MainActivity extends AppCompatActivity {

    MiInterfaz miInterfaz;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        final EditText nombre = (EditText) findViewById(R.id.mi_nombre);
        Button boton = (Button) findViewById(R.id.mi_boton);
        boton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                // aca vamos a guardar la informacion
                miInterfaz.guardarLocalmente(nombre.getText().toString());
                miInterfaz.mandarAlServidor(nombre.getText().toString());
            }
        });
    }
}

Si te das cuenta, una de la cosas que también cambiaron en la actividad es que ahora no tenemos los 2 métodos que guardaban y enviaban información adentro de la actividad.

Ahora la actividad simplemente dice “guarda esta información” y “manda esta información”.
Como se hace, es ahora algo que ya no le importa a la actividad.
Al usar una interfaz hemos dejado que nuestra actividad solo se preocupe de mostrar cosas, y como interactúan con el usuario, y ahora podemos poner la lógica de la aplicación en otro lugar.

Pero ahora viene la pregunta. Que, o quien, guarda la información a la db y manda la información a internet?

Para esto, vamos a crear una clase nueva y la vamos a llamar MiClaseImplementadora.java, y en el nombre de la clase le vamos a agregar que implementa a MiInterfaz.

La clase MiClaseImplementadora, va a ser así:

public class MiClaseImplementadora implements MiInterfaz {
    @Override
    public void guardarLocalmente(String nombre) {
        // guarda la informacion a tu db
    }

    @Override
    public void mandarAlServidor(String nombre) {
        // manda la informacion a internet
    }
}

Ya que la clase MiClaseImplementadora implementa MiInterfaz, esta obligada a implementar los 2 métodos que tenemos en la interfaz MiInterfaz. Es por eso que los 2 métodos tienen la anotación de @Override.

Si corres tu aplicación…..se va a caer, con un NullPointerException tirado por la variable miInterfaz.
Por que? Nos falta una parte: declarar quien va a implementar tu interfaz.
Si bien la actividad importa y declara una variable de tipo MiInterfaz, aun no tenemos la definición de que es esta variable.

Interfaces pueden proveer abstracción, pero aun nos falta un voluntario de quien va a implementar los métodos de la interfaz, y nuestra actividad necesita saber de eso.
En otras palabras, podemos crear tareas como guardarLocalmente de forma abstractas, pero si las queremos usar vamos a necesitar de alguien que se comprometa a implementarlas.
En nuestro caso, ese “voluntario” es MiClaseImplementadora.

Para eso, vamos a hacer que nuestra variable de miInterfaz, sea de tipo MiClaseImplementadora:

MiInterfaz miInterfaz = new MiClaseImplementadora ();

Con esto, tu código de tu actividad completa, seria así:

public class MainActivity extends AppCompatActivity {

    MiInterfaz miInterfaz;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        miInterfaz = new MiClaseImplementadora();
        final EditText nombre = (EditText) findViewById(R.id.mi_nombre);
        Button boton = (Button) findViewById(R.id.mi_boton);
        boton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                // aca vamos a guardar la informacion
                miInterfaz.guardarLocalmente(nombre.getText().toString());
                miInterfaz.mandarAlServidor(nombre.getText().toString());
            }
        });
    }
}

Si corres tu aplicación ahora, todo debería funcionar.

Proximos pasos

Si nuestro ejemplo fuera una aplicación real, crearíamos por lo menos una interfaz mas, y haríamos un par de cambios:

  1. La actividad solo llamaría a 1 método general en la primera interfaz. Seria algo como onSave(String nombre). La idea es que la actividad sea lo mas simple y “tonta” posible. Así podemos intercambiar las actividades, o convertir la actividad a fragmento sin tener que preocuparnos de lógica o implementaciones de db o internet
  2. La primera interfaz (la que ya tenemos) cambiaría a tener solo un método, como onSave(String nombre).
  3. La segunda clase, la actual MiClaseImplementadora.java, continuaría implementando nuestra primera interfaz, y la clase solo tendría lógica. Talvez tendría un método para verificar de que la información recibida es valida, que la db existe y de que tenemos conexión a internet.
  4. Crearíamos ahora 2 interfaces mas. Una para base de datos, y una para conexiones a internet. Nuestra segunda clase, MiClaseImplementadora.java, llamaría a ambas interfaces nuevas.
  5. Creariamos 2 clases mas, y ambas implementarían a su nueva interfaz correspondiente. Una que sea solo para interactuar con la base de datos, y otra solo para interactuar con internet.

Al hacer estos cambios hemos creado capas de código en donde cada capa es independiente, y donde cada capa puede ser creada por otro desarrollador. Al usar interfaces no nos tenemos que preocupar de como otro desarrollador implementa la próxima capa, y solo llamamos a que algo funcione.

Y eso es todo.

Puedes usar la misma teoría y aplicación para ser notificado cuando algo termina de ocurrir, que es el segundo uso mas común de una interfaz.

Espero que esto te ayude.

Eduardo Flores.

Comenta este post