5 – Archivos value

Como ya vimos en la carpeta values tenemos una serie de archivos donde podemos guardar distintos valores de nuestra aplicación. Vamos a ver los archivos que tenemos de base, para que sirven y como se usan.

Según creamos la aplicación tenemos 3 archivos, colors.xml, string.xml y styles.xml. Lo primero que vemos es que son archivos xml como el AndroidManifest, por lo que la sintaxis será muy parecida.

Empecemos con el archivo colors.xml:

colors.xml

Como vemos es un archivo donde guardamos los colores que usaremos en la aplicación. como vemos para incluir un nuevo color incluiremos entre las etiquetas resources una nueva etiqueta color. Dentro de esta etiqueta incluiremos el color en formato hexadecimal y el nombre del color en un atributo name dentro de la etiqueta color.

<color name="colorPersonalizado">#FF5722</color>

Para usar este color dentro de los archivos de kotlin tendremos que llamar a la variable R.color.colorPersonalizado y si lo llamamos en un archivo xml (los veremos en los layouts) se llamaría como @color/colorPersonalizado.

El siguiente de los archivos es el string.xml.

Este archivo sirve para guardar los textos de nuestra aplicación. Para ello incluiremos dentro de la etiqueta resources una etiqueta string con un atributo name con el nombre del texto y el texto dentro de la etiquetas string.

<string name="nick">Escribe tu nick</string>

Para usar este texto dentro de los archivos de kotlin llamaremos a la variable R.string.nick y aparecerá el texto «Escribe tu nick» si lo llamamos en un archivo xml se llamaría como @string/nick.

Por último tenemos el archivo styles.xml:

Este archivo nos sirve para definir el estilo general de la aplicación. En este caso tenemos 3 items definidos, el color primario, el primario oscuro y el color enfatizado (con los nombres colorPrimary, colorPrimaryDark y colorAccent). Como vemos en este archivo lo que estamos usando para definir estos colores son referencias al archivo color.xml. En este archivo podremos definir otro tipo de estilos que sirvan de manera global a la aplicación como tipografías o tamaños de letras, pero eso lo veremos en un artículo más adelante.

2 – Archivos de la App

En el anterior artículo nos quedamos aquí:

Como vemos, hay dos zonas muy bien diferenciadas. Una a izquierda que sería el árbol de archivos y a la derecha tenemos el editor de código.

Si vemos el árbol de archivos en la parte de arriba tenemos un desplegable en la que se puede elegir distintas vistas del árbol de archivos. De momento vamos a quedarnos con la que se llama Android y vamos a ver los archivos en más detalle.

Árbol de archivos de una aplicación

Lo primero que podemos ver es que el proyecto se separa en dos carpetas: app y Gradle Scrits.

Vamos a empezar hablando un poco de Gradle. Gradle es una herramienta de automatización. Sirve para automatizar la generación del apk una vez acabada la app y la gestión de dependencias.

¿Apk?¿Gestión de dependencias?¿Eso se come?

Es verdad, a lo mejor he ido un poco rápido. A ver, ahora mismo lo que tenemos es una carpeta de proyecto llena de subcarpetas y archivos. Pero claro, eso no es lo que se pueda subir a Google play. Cuando subamos nuestra aplicación a la store de Google, subiremos un solo archivo de terminación .apk. La generación de este archivo es lo que controla gradle.

Ahora las hablemos de las dependencias. En Internet, principalmente en github, podemos encontrar códigos reutilizables hechos para facilitarnos la vida. A estos códigos los llamamos librerías. Por ejemplo, existe una librería muy utilizada para facilitarnos el uso de imágenes en nuestra app, llamada picasa. La adición de estas librerías a nuestro código es lo que llamamos dependencias.

Todo esto lo gestionamos desde el archivo build.gradle (Module: app) dentro de la carpeta Gradle Scripts, el cual veremos en profundidad en un artículo más adelante. El resto de archivos de la carpeta son propios de gradle y no los veremos.

Ahora hablaremos de la carpeta app. En esta carpeta encontraremos tres subcarpetas llamadas manifest, java y res.

En la carpeta manifest, tenemos un solo archivo el AndroidManifest.xml. Este archivo está escrito en lenguaje xml y sirve para controlar distintas partes de la configuración de la app como el nombre, el SDK mínimo o los permisos que pedirá la app para su uso. Este archivo también tendrá su propio artículo en el futuro.

La siguiente carpeta es la carpeta java, donde tendremos todos los archivos en kotlin. dentro hay 3 subcarpetas. La primera es donde tendremos los archivos de la aplicación y las otras dos, las que tienen (androidTest) y (Test) sirven para programas las pruebas para nuestra app.

Por último tenemos la carpeta res. En esta carpeta se clasificarán los distintos recursos de la app. enn la subcarpeta drawable tendremos las imágenes de la app. En layout los archivos que las pantallas de la app en formato xml. En mipmap, tendremos la imagen del icono de la app. Y por último en values, tendremos distintos archivos xml donde guardaremos distintos valores de la app. Tendremos un archivo para los colores, uno para definir el estilo de la app, uno para los textos… Esta carpeta la veremos también en profundidad más adelante.

4 – Gradle

Si abrimos el archivo build.gradle (module:app) nos encontramos con el siguiente archivo:

Como hicimos con el Android Manifest, en este artículo vamos a ir desgranando el archivo.

El archivo se escribe en un DSL (Domain Specific Language), por lo que no es ningún lenguaje específico como el archivo AndroidManifest que estaba escrito en XML. Aunque es verdad que recuerda mucho a JSON (JavaScript Object Notation).

Vamos a ver el primer bloque del archivo, el Bloque android:

android {
    compileSdkVersion 29

    defaultConfig {
        applicationId "com.example.myapplication"
        minSdkVersion 23
        targetSdkVersion 29
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
}

En este apartado vemos 3 apartados muy parecidos y a la vez confusos. El primero es compileSdkVersion que indica en que versión del Sdk de android se ha creado la app. El targetSdkVersion es a la versión a la que está destinada la app (Debería ser del mismo nivel que el compileSdkVersion o mayor) y por último el minSdkVersion, es el Sdk mínimo que puede usar la app. Un movil con un Sdk menor no podrá instalar la app.

En applicationId va el package del AndroidManifest.

Ahora veamos las versiones de la app. En primer lugar tenemos versionCode. Este apartado sirve para controlar que versión hay en la play Store. Si hacemos cambios en la app y queremos actualizarla, hay que cambiar ese número porque si no la play store no reconocerá que hay una actualización. Por otro lado versionName es el número de la versión que se mostrará al usuario.

Por último tenemos minifyEnabled, que está marcado como false. Mientras está en false en el momento de compilarse se compilará de manera normal, pero si lo ponemos en true en el momento de compilar lo hará minificando (Eliminando todo lo innecesario) y ofuscando (haciendo el código compilado ilegible) el código compilado.

Vamos a ver el siguiente bloque de código, el bloque dependencies:

dependencies {
    implementation fileTree(dir: "libs", include: ["*.jar"])
    implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
    implementation 'androidx.core:core-ktx:1.1.0'
    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test.ext:junit:1.1.1'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'

}

En este bloque se añaden las dependencias para nuestro código. Si queremos incluir alguna librería externa tendremos que buscar en su documentación como incluirla y la añadiremos en esta sección. Por ejemplo para incluir la librería de picasa que vimos hace algunos artículos tendremos que incluir las siguiente líneas:

implementation 'com.android.support:recyclerview-v7:27.1.1'
implementation 'com.squareup.picasso:picasso:2.71828'

Una vez hecho esto el compilador se encargará de descargar y enlazar la librería para que podamos usarla en nuestro proyecto.

3 – Android Manifest

Si abrimos el archivo AndroidManifest.xml nos encontramos con el siguiente archivo:

Vamos a ir desgranando el archivo poco a poco.

Lo primero que vemos en este archivo es que no está escrito en kotlin, ni java y ni siquiera se parece a un lenguaje de programación. ¿Verdad? Eso es porque no está escrito en ningún lenguaje de programación, si no en XML que es un lenguaje de marcado.

Para los que hayáis usado HTML os resultará ligeramente familiar, eso es porque el HTML es un subconjunto de XML. XML realmente es un metalenguaje o lo que es lo mismo, un lenguaje para definir lenguajes de marcado. Por eso todo documento XML tiene que ir acompañado de otro archivo llamado esquema XML que define las reglas del documento. De esta manera con XML se genera el HTML o sirve para escribir el Android Manifest.

La primera línea:

<?xml version="1.0" encoding="utf-8"?>

Sirve para indicarnos que estamos ante un documento xml y que se escribe usando utf-8.

Luego tenemos

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.myapplication">

</manifest>

Estas etiquetas van a encerrar dentro todo el documento. Lo más importante de esta etiqueta es que tiene dos atributos: xmlns:android en el cual se escribe la ruta del archivo del esquema que define el android manifest. El otro atributo es el package donde se pone el paquete de la aplicación que definimos al generar la app.

Después, entre la etiqueta de apertura de manifest y la de cierre, tenemos la etiqueta:

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">

    </application>

Esta etiqueta application, incluye 6 atributos. El primero es android:allowBackup el cual puede ser «true» (por defecto) o «false». Estando en true permite que la aplicación sea incluida al hacer un Backup del sistema. El segundo es android:icon y nos indica donde está el icono de la aplicación. Según está escrito en nuestro caso está en el archivo mipmap (veremos este archivo cuando hablemos de los archivos value) y corresponde al valor ic_launcher. El tercero android:label define el nombre de la aplicación. Al igual que nos pasó con el atributo anterior, no tenemos escrito el valor del atributo, si no que tenemos una referencia al lugar donde encontramos este valor. En este caso es en el archivo String el valor app_name.

El cuarto atributo android:roundIcon indica la dirección donde encontrar un icono adaptativo. El quinto atributo es android:supportsRtl. Es otro atributo de true o false y sirve para indicar si nuestra aplicación aceptará diseños de derecha a izquierda. Por último tenemos android:theme desde donde indicamos donde están los valores (de los colores) del tema de nuestra aplicación.

Dentro de la etiqueta application tenemos la etiqueta <activity>. En este caso solo tenemos una pero pueden haber más de una. Y no solo eso, tambien pueden estar las etiquetas <activity-alias>, <meta-data>, <service>, <reciver>, <provider> y <uses-library>. En este artículo no describiremos estas etiquetas, podéis consultarlas en la página de la documentación de Android, aunque en un futuro seguramente les dediquemos artículos en el blog. De momento vamos a describir la etiqueta <activity> que tenemos en nuestro android manifest.

        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

Las etiquetas activity sirven para declarar las activitys de nuestra aplicación. Las activity se puede decir que son las pantallas de nuestra app. Esta etiqueta tiene el atribuuto android;name, el cual indica el nombre de la activity.

Dentro de esta etiqueta tenemos la etiqueta <intent-filter>, esta etiqueta sirve para indicar los intens a los que puede responder una activity. Los intents son objetos de mensajería que sirven para reclamar acciones. Pueden ser llamar a otra activity, llamar al navegador para mostrar una web, o llamar a la cámara del movil.

Cuando hablemos de los Intents desarrollaremos también este apartado.

1 – Creando una app

Lo primero para desarrollar una app es tener instalado el Android Studio. Este no es el único IDE que podemos usar. En el pasado se utilizaba eclipse con un plugin de android, pero hace mucho que ese plugin no está soportado. Una opción más viable sería usar el Visual Studio Code. Este editor de código es uno de los más usados últimamente y tiene una gran cantidad de extensiones con la que convertirlo en un entorno de desarrollo android completo.

Aun así siendo sincero creo que la mejor opción es Android Studio. Está construido sobre el IDE Intellij idea, el cual es específico para Java y Kotlin (De echo es de la empresa creadora de Kotlin) y está soportado directamente por Google, así que siempre tendrás soporte para las últimas novedades del framework.

Para instalarlo podemos seguir los pasos que nos indican en la propia página de Android Studio.

Una vez instalado, lo iniciamos y nos encontraremos con la siguiente pantalla:

Página de inicio de Android Studio

Para iniciar una App nueva le daremos a Start a new Android Studio project.

Hecho esto, nos aparecerá un menu para elegir la plantilla sobre la que queremos empezar el proyecto:

Plantillas para apps

Para empezar elegiremos Empty Activity o Activity vacía (Más adelante veremos lo que es una Activity).

La siguiente pantalla es la pantalla de configuración de la App:

En esta pantalla lo primero que tenemos (Name) es el nombre de la App. Después tenemos el Package name. Este campo se completa solo y no hace faltas que lo toquemos. En save location tenemos la localización de la carpeta donde se guardará la App. Language es el lenguaje de programación que usaremos para programar. En nuestro caso pondremos Kotlin.

Lo último es una parte bastante importante. El SDK de android es la versión del sistema operativo Android. Cada vez que se actualiza Android aparece un SDK más nuevo, con nuevas características. Lógicamente no vamos a trabajar para un solo SDK, sería hacer Apps para la gente que tiene una versión de Android. Lo que se hace es elegir la versión mínima que queremos soportar en nuestra App. Esto hace que se pueda instalar nuestra App en dispositivos que tengan ese SDK en adelante.Esto lo elegimos en Minium SDK. Una ayuda antes de decidir que SDK, lo mejor es dar a Help me choose. Al darle aparecerá la siguiente pantalla:

Help me choose

Aquí vemos que porcentaje de móviles con Android podrían instalar nuestra App eliguiendo ese SDK. Si escogemos por ejemplo la API 16 llegaremos al 99.8% de los moviles. Es verdad que eso nos hace llegar a casi todos los móviles, pero solo podremos usar las características de Android hasta la versión 4.1.

Lo mejor es llegar a menor población pero tener características más nuevas. Por ejemplo la API 22 o la API 23 llegaremos a un porcentaje de móviles muy altos (entre el 92.3 y el 84.9%) con características más modernas.

Esto claro también depende del proyecto. Si queremos hacer una App con características de la versión oreo 8.0 no vamos a tener más remedio que usar la API 26 o superior.

Una vez decidido con esta información que API escoger, le damos a ok, elegimos en Minium SDK la API escogida, y pulsamos Finish.

Una vez echo esto esperamos unos minutos mientras se generan todos los archivos, y gradle construye la App hasta que obtengamos esta pantalla:

Nuestra primera App

Y ya tenemos nuestra primera App creada y lista para ser modificada.

11 – Métodos constructores

El método constructor es un método especial que tienen todas las clases aunque no lo sepamos. Este método es el primero que se ejecuta cuando instanciamos una clase, se hace de manera automática y tiene como objetivo inicializar las variables del objeto.

Imaginemos que queremos hacer un objeto de tipo Persona. Este objeto tendrá como variables un nombre, unos apellidos y una edad y queremos que esas variables sean distintos entre los distintos objetos que creemos.

Hasta ahora habíamos visto que se crean los objetos inicializando las variables con un valor predefinido y después les cambiamos el valor con los métodos setter porque las variables serán privadas para respetar el encapsulamiento. Pero también habíamos dicho que somos unos vagos, y claro eso con un objeto de 3 variables no es tanto, pero… ¿Y si queremos crear 10 objetos con 8 variables cada una? Pues nos morimos del asco y para eso está el método constructor, gracias al cual podemos pasar los valores de las variables desde el momento de la instanciación facilitándonos mucho la creación de objetos.

Como hacemos muchas veces vamos a ver un ejemplo y después comentamos los detalles.

class Persona(private var nombre: String, private  var apellidos: String, private var edad: Int){

    fun presentarse(){
        println("Hola, me llamo $nombre $apellidos y tengo $edad años.")
    }

}

fun main() {

    val pepito: Persona = Persona("Pepe", "Perez Garcia", 35)

    pepito.presentarse()

}

El constructor sería este fragmento de código: (private var nombre: String, private var apellidos: String, private var edad: Int). Como vemos, se hace añadiendo la declaración de las variables entre paréntesis al lado del nombre de la clase. Así de fácil y sin tener que añadir otro método.

¿Y cómo lo usamos? Pues en la instanciación añadimos los valores que queremos darle entre paréntesis así («Pepe», «Perez Garcia», 35).

Bloque init

En ocasiones podemos querer que existan condiciones a la hora de inicializar un objeto. Igual que pasaba en los métodos setters podemos escribir una lógica en un bloque llamado init, que haga las comprobaciones que quieras. Este bloque se ejecutará automáticamente después del método constructor. Veamos un ejemplo de uso:

class Persona(private var nombre: String, private  var apellidos: String, private var edad: Int){

    init{
        if(edad < 0){
            edad = 0
        }
    }

    fun presentarse(){
        println("Hola, me llamo $nombre $apellidos y tengo $edad años.")
    }

}

fun main() {

    val pepito: Persona = Persona("Pepe", "Perez Garcia", -35)

    pepito.presentarse()

}

Como vemos en el bloque init hemos introducido un condicional que compruebe que la edad no es negativa, ya que una persona no puede tener edad negativa. Con esto lo que hacemos es que se compruebe la edad y si es menor la pone a 0.

Valores predeterminados en el constructor

Bueno pues vamos a jugar un poco con el constructor. ¿Qué pasa si damos un valor en la declaración de la variable? Pues que si llamamos al constructor sin dar valor a esa variable, el objeto cogerá el valor que le damos en el constructor.

Mira como está hecho en el siguiente ejemplo y como al llamar al constructor no le pasamos el valor de la edad. Entonces el objeto tomará como valor 18 que es el valor que se le da en el constructor.

class Persona(private var nombre: String, private  var apellidos: String = "González Díaz", private var edad: Int = 18){

    init{
        if(edad < 0){
            edad = 0
        }
    }

    fun presentarse(){
        println("Hola, me llamo $nombre $apellidos y tengo $edad años.")
    }

}

fun main() {

    val pepito: Persona = Persona("Pepe", "Perez Garcia")

    pepito.presentarse()

}

Varios métodos constructores

Antes de ver esto quiero que probéis el siguiente código:

class Persona(private var nombre: String, private  var apellidos: String, private var edad: Int){

    init{
        if(edad < 0){
            edad = 0
        }
    }

    fun presentarse(){
        println("Hola, me llamo $nombre $apellidos y tengo $edad años.")
    }

}

fun main() {

    val pepito: Persona = Persona("Perez Garcia", 35)

    pepito.presentarse()

}

Nos da error. ¿Verdad? Nos dice el compilador que el segundo parámetro de Persona debe de ser un String y es que el método de poner valores por defecto es muy limitada, siempre al poner parámetros tomará el primer parámetro como el valor de la primera variable, el segundo como la segunda variable, etc. Por lo que en el ejemplo anterior toma como «Perez Garcia» el valor del nombre y el 35 como el valor de apellidos pero y si queremos solo dar el apellido y la edad? Necesitaremos un segundo constructor:

class Persona(private var nombre: String, private  var apellidos: String, private var edad: Int){

    constructor(apellidos: String, edad: Int): this( "pepe", apellidos, edad)

    init{
        if(edad < 0){
            edad = 0
        }
    }

    fun presentarse(){
        println("Hola, me llamo $nombre $apellidos y tengo $edad años.")
    }

}

fun main() {

    val pepito: Persona = Persona("Perez Garcia", -35)

    pepito.presentarse()

}

Como vemos el segundo constructor se escribe así constructor(apellidos: String, edad: Int): this( «pepe», apellidos, edad).

Primero escribes la palabra reservada constructor, después ponemos las variables que pasaremos entre paréntesis (Ojo que aquí no las estamos declarando, por lo que no hay que pones val o var). Despues añadimos : this (lo que estamos haciendo aquí es pasarle el constructor principal) y al final los valores de los parámetros. Donde queramos poner un valor predeterminado se lo damos, y donde queramos que sea el valor de una variable ponemos la variable.

Variables con nombre

Pero esas no son las únicas alternativas, aunque podemos crear varios métodos constructores, también tenemos la opción de poner la variable que queremos nombrar a la hora de instanciar un objeto de las siguiente manera:

class Persona(private var nombre: String = "Juan", private  var apellidos: String = "González Díaz",
              private var edad: Int = 18){
    init{
        if(edad < 0){
            edad = 0
        }
    }
    fun presentarse(){
        println("Hola, me llamo $nombre $apellidos y tengo $edad años.")
    }
}
fun main() {

    val persona1: Persona = Persona(nombre = "Pepe", edad = 35)
    persona1.presentarse()

    val persona2: Persona = Persona(apellidos = "Martinez Reverte")
    persona2.presentarse()

    val persona3: Persona = Persona(edad = 53)
    persona3.presentarse()

    val persona4: Persona = Persona(apellidos = "Jimenez Luengo", edad = 27)
    persona4.presentarse()

    val persona5: Persona = Persona("Maria", "Luján Patiño", 31)
    persona5.presentarse()
}

Como vemos con un solo constructor al que damos valores por defectos podemos instanciar 5 objetos al que damos distintas combinaciones de variables sin necesidad de métodos constructores adicionales.

10 – Polimorfismo

El polimorfismo es la capacidad de varios objetos de distintas familias para responder a lo mismo, pudiendo ser «intercambiables» en determinadas circunstancias.

Para ello, se usan la interfaces. Las interfaces son como pequeños trozos de clases que se usan para poder reutilizar código. Es muy parecido a la herencia, pero sin formar familias por un lado y mientras que una clase solo puede tener una herencia, puede recibir código de varias interfaces.

Para crear una interfaz , se haría igual que una clase, pero en lugar de escribir la palabra reservada class, usamos la palabra reservada interface. Por otro lado para que una clase use una interfaz, solo hay que hacerlo como con la herencia pero sin poner los paréntesis después del nombre de la interfaz.

interface Interfaz1{

    fun metodo1(){
        println("metodo 1")
    }

}

interface Interfaz2{

    fun metodo2(){
        println("metodo 2")
    }

}

open class ClasePadre{
    fun metodo3(){
        println("metodo 3")
    }
}

class ClaseHeredera: ClasePadre(), Interfaz1, Interfaz2{
    fun metodo4(){
        println("metodo 4")
    }
}

fun main() {

    val objetoHeredero: ClaseHeredera = ClaseHeredera()
    objetoHeredero.metodo1()
    objetoHeredero.metodo2()
    objetoHeredero.metodo3()
    objetoHeredero.metodo4()

}

9 – Herencia

Imaginemos que estamos haciendo otra vez un videojuego. Esta vez es un videojuego en el que nos enfrentamos a enemigos elementales de distintos elementos. Para crear estos elementales necesitaremos una clase de cada tipo de elemental para generar los objetos que serán los enemigos.

Bueno en principio eso es fácil y ya sabemos hacerlo ¿Verdad? Pero tenemos un pequeño inconveniente y es que estos elementales tendrán tanto variables como funciones en común y claro escribir lo mismo tantas veces es muy ineficiente y aquí somos muy vagos como para perder el tiempo en esas ineficiencias.

Para solucionar esto tan molesto existe la herencia. Lo que nos permite la herencia es generar una jerarquía de clases, en la que tendremos una clase padre con las variables y funciones comunes, y una serie de clases hijas que heredarán estas variables y funciones e implementarán las variables que son propias.

open class Elemental{

    // Variables de la clase elemental

    var vida: Int = 500
    var armadura: Int = 125
    var ataque: Int = 25

    //Métodos de la clase elemental

    fun ataqueBasico(){
        println("Realizo un ataque de $ataque")
    }

    fun defensa(){
        println("Me defiendo con mi armadura de $armadura")
    }

    fun curarse(){
        vida = 500
        println("Estoy curado")
    }

}

class ElementalFuego: Elemental() {

    // Variables de la clase elemental de fuego

    var fuego: Int = 500

    //Métodos de la clase elemental de fuego

    fun ataqueFuego(){
        println("Realizo un ataque de fuego de $fuego.")
        println("Ahora estás quemado.")
    }

}

class ElementalHielo: Elemental() {

    // Variables de la clase elemental de hielo

    var hielo: Int = 400

    //Métodos de la clase enemigo

    fun ataqueHielo(){
        println("Realizo un ataque de fuego de $hielo.")
        println("Ahora estás congelado.")
    }

}

class ElementalTierra: Elemental() {

    // Variables de la clase elemental de tierra

    var tierra: Int = 535

    //Métodos de la clase enemigo

    fun ataqueTiera(){
        println("Realizo un ataque de fuego de $tierra.")
        println("Ahora estas tumbado por el temblor.")
    }

}

fun main() {

    // Instanciamos varios enemigos

    val elementalDeFuego: ElementalFuego = ElementalFuego()
    elementalDeFuego.ataqueBasico()
    elementalDeFuego.ataqueFuego()

    val elementalDeHielo: ElementalHielo = ElementalHielo()
    elementalDeHielo.ataqueBasico()
    elementalDeHielo.ataqueHielo()

    val elementalDeTierra: ElementalTierra = ElementalTierra()
    elementalDeTierra.ataqueBasico()
    elementalDeTierra.ataqueTiera()

}

Como vemos en el ejemplo, tenemos una clase padre llamada Elemental y de ella heredan 3 clases hijas llamadas ElementalFuego, ElementalHielo y ElementalTierra.

En la clase padre tenemos tres variables y tres métodos que estarán también en las clases hijas, y luego tenemos a las tres clases hijas. Cada una de ellas tendrá una variable propia y un método propio.

Como vemos para que una clase pueda ser una clase padre, hay que añadir la palabra reservada open antes de class y para que una clase herede, hay que añadir dos puntos y el nombre de la clase de padre, seguido de un paréntesis (: Elemental() en el ejemplo).

8 – Abstracción

La abstracción es una característica que aparece de la programación orientada a objetos más que algo que se implemente programando.

La abstracción es la opacidad que tienen el resto de elementos para saber como funcionan los objetos. Los objetos funcionan como una caja negra y aunque cambie su implementación (el algoritmo que tenga dentro de sus métodos) estos no tienen manera de saber que ha cambiado algo.

Vamos a ver un ejemplo de como el cambio de implementación no afecta a como se usa un ejemplo:

class Password{

    // Variables de la clase enemigo

    private var password: String = "1234"

    //Métodos de la clase enemigo

    fun generarPassword(): String {

        for(num in 1..10) {

            val randomVal: Int = (0..27).random()

            if(num.equals(1)){
                password = randomVal.toString()
            }else{
                password = password+randomVal.toString()
            }
        }

        return password
    }


}

fun main() {

    // Instanciamos 1 enemigo

    val password: Password = Password()


    println(password.generarPassword())

}
class Password{

    // Variables de la clase enemigo

    private var password: String = "1234"

    //Métodos de la clase enemigo

    fun generarPassword(): String {

        var letras = arrayOf("a","b","c","d","e","f","g","h","i","j","k","l","m","n",
            "o","p","q","r","s","t","u","v","w","x","y","z")

        for(num in 1..10) {

            val randomVal: Int = (0..25).random()
            val randomNumOChar: Int = (0..1).random()

            if (randomNumOChar.equals(0)){
                if(num.equals(1)){
                    password = randomVal.toString()
                }else{
                    password = password+randomVal.toString()
                }
            }else{
                if(num.equals(1)){
                    password = letras[randomVal]
                }else{
                    password = password + letras[randomVal]
                }
            }

        }

        return password
    }


}

fun main() {

    // Instanciamos 1 enemigo

    val password: Password = Password()


    println(password.generarPassword())

}

Como vemos, en el primer ejemplo tenemos un generador de claves que genera un String compuesto de números aleatorios. En el segundo genera un String compuesto de números y letras aleatorios.

Son dos implementaciones distintas, pero al final su uso es exactamente igual. Primero se instancia un objeto Password, y después se llama al método generarPassword() y en los dos casos se devuelve un String. Por lo tanto usemos donde usemos el generador será completamente ajeno a como funciona por dentro el objeto Password.

7 – Encapsulación

La encapsulación es la manera que tienen los objetos de proteger sus variables de que sean modificadas por cualquiera. La información y el estado tienen que estar protegidas para que solo el objeto pueda cambiarlas.

Veamos que quiero decir con eso, con el enemigo del artículo anterior:

class Enemigo{

    // Variables de la clase enemigo

    var vida: Int = 500
    var armadura: Int = 125
    var ataque: Int = 25

    //Métodos de la clase enemigo

    fun ataque(){
        println("Realizo un ataque de $ataque")
    }

    fun defensa(){
        println("Me defiendo con mi armadura de $armadura")
    }

    fun curarse(){
        vida = 500
        println("Estoy curado")
    }

}

fun main() {

    // Instanciamos 1 enemigo

    val chasqueador: Enemigo = Enemigo()


    chasqueador.ataque()

    // Modificamos su valor de ataque

    chasqueador.ataque = 2

    chasqueador.ataque()

}

Como vemos en el ejemplo anterior el pobre chasqueador a pasado de tener un ataque de 25 a uno de 2, por lo que a Ellie y a Joel solo les va a hacer cosquillas. Esto es porque las variables están desprotegidas.

Para impedir este problema, tenemos lo que se llaman modificadores de acceso. Existen dos: public y private.

El modificador public, es el que tienen por defecto las variables y métodos si no ponemos nada y permiten el acceso a las ellos desde todos lados.

Por otro lado si ponemos el modificador private antes de la variable o métodos, solo tendrán acceso a su modificación desde dentro del objeto.

class Enemigo{

    // Variables de la clase enemigo

    private var vida: Int = 500
    private var armadura: Int = 125
    private var ataque: Int = 25

    //Métodos de la clase enemigo

    fun ataque(){
        println("Realizo un ataque de $ataque")
    }

    fun defensa(){
        println("Me defiendo con mi armadura de $armadura")
    }

    fun curarse(){
        vida = 500
        println("Estoy curado")
    }

}

fun main() {

    // Instanciamos 1 enemigo

    val chasqueador: Enemigo = Enemigo()


    chasqueador.ataque()

    // Modificamos su valor de ataque

    chasqueador.ataque = 2

    chasqueador.ataque()

}

Si usamos el modificador en las variables ahora no nos dejará compilar lo escrito ya que nos dará un error. ¿Cuál? Pues sí esta línea. chasqueador.ataque = 2. En ella pedimos modificar la variable ataque del objeto chasqueador cuando la hemos hecho privada, por lo que si no se modifica desde dentro de la clase no se podrá modificar. ¿Pero como hacemos esto? Pues con métodos setter y getter para modificarlas.

class Enemigo{

    // Variables de la clase enemigo

    private var vida: Int = 500
    private var armadura: Int = 125
    private var ataque: Int = 25

    //Métodos de la clase enemigo

    fun setAtaque(ataque: Int) {
        this.ataque = ataque
    }

    fun getAtaque() {
        return ataque
    }

    fun ataque(){
        println("Realizo un ataque de $ataque")
    }

    fun defensa(){
        println("Me defiendo con mi armadura de $armadura")
    }

    fun curarse(){
        vida = 500
        println("Estoy curado")
    }

}

fun main() {

    // Instanciamos 1 enemigo

    val chasqueador: Enemigo = Enemigo()


    chasqueador.ataque()

    // Modificamos su valor de ataque

    chasqueador.setAtaque(2)

    chasqueador.ataque()

}

Ahora si que podemos cambiar el valor de la variable ya que lo hacemos desde un método que está dentro de la clase. Puesto así parece que no hay ninguna diferencia y claro así no la hay, pero podemos añadir la lógica que queramos en el setter y en el getter, haciendo que puedas controlar lo que se hace con la variable.

class Enemigo{

    // Variables de la clase enemigo

    private var vida: Int = 500
    private var armadura: Int = 125
    private var ataque: Int = 25

    //Métodos de la clase enemigo

    fun setAtaque(ataque: Int) {
        if (ataque > 15 && ataque < 35){
            this.ataque = ataque
        }else{
            println("Valor de ataque incorrecto, se queda como estaba!")
        }
    }

    fun ataque(){
        println("Realizo un ataque de $ataque")
    }

    fun defensa(){
        println("Me defiendo con mi armadura de $armadura")
    }

    fun curarse(){
        vida = 500
        println("Estoy curado")
    }

}

fun main() {

    // Instanciamos 1 enemigo

    val chasqueador: Enemigo = Enemigo()


    chasqueador.ataque()

    // Modificamos su valor de ataque

    chasqueador.setAtaque(2)

    chasqueador.ataque()

    chasqueador.setAtaque(20)

    chasqueador.ataque()

}

Como vemos en este ejemplo, solo se aceptan valores de ataque comprendidos entre 15 y 35. El resto de valores serán rechazados gracias a la lógica del setter. Esto es lo que permite la encapsulación.