1. Antes de comenzar
Requisitos previos
- Estar familiarizado con el uso del Playground de Kotlin para editar programas de Kotlin
- Conocer los conceptos básicos de programación en Kotlin que se explican en la unidad 1 de este curso (en particular, el programa
main()
tiene funciones con argumentos que muestran valores, variables, tipos de datos y operaciones, así como sentenciasif/else
) - Ser capaz de definir una clase en Kotlin, crear una instancia de objeto desde ella y acceder a sus propiedades y métodos
Qué aprenderás
- Cómo crear un programa de Kotlin que use la herencia a fin de implementar una jerarquía de clases
- Cómo extender una clase, anular su funcionalidad existente y agregar una funcionalidad nueva
- Cómo elegir el modificador de visibilidad correcto para las variables
Qué compilarás
- Un programa de Kotlin con diferentes tipos de viviendas que se implementan como una jerarquía de clases
Requisitos
- Una computadora con conexión a Internet a fin de acceder al Playground de Kotlin
2. ¿Qué es una jerarquía de clases?
Es natural que los seres humanos clasifiquen elementos que tienen propiedades y comportamientos similares en grupos, y que incluso establezcan algún tipo de jerarquía entre ellos. Por ejemplo, puedes tener una categoría amplia, como vegetales, y en ella puedes tener un tipo más específico, como legumbres. Dentro de las legumbres, puedes tener tipos aún más específicos, como guisantes, frijoles, lentejas, garbanzos y porotos, por ejemplo.
Esto se puede representar como una jerarquía porque las legumbres contienen o heredan todas las propiedades de los vegetales (p. ej., son plantas y se pueden consumir). Del mismo modo, los guisantes, los frijoles y las lentejas tienen las propiedades de las legumbres y sus propias propiedades únicas.
Veamos cómo representarías esta relación en términos de programación. Si haces que Vegetable
sea una clase en Kotlin, puedes crear Legume
como elemento secundario o subclase de la clase Vegetable
. Eso significa que la clase Legume
hereda todas las propiedades y los métodos de la clase Vegetable
(lo que significa que también están disponibles en ella).
Puedes representar esto en un diagrama de jerarquía de clases, como se muestra a continuación. Puedes hacer referencia a Vegetable
como el elemento superior o la superclase de la clase Legume
.
Podrías continuar y expandir la jerarquía de clases creando subclases de Legume
como Lentil
y Chickpea
. Esto hace que Legume
sea tanto un elemento secundario o subclase de Vegetable
como un elemento principal o superclase de Lentil
y Chickpea
. Vegetable
es la clase raíz o de nivel superior (o base) de esta jerarquía.
Herencia en clases de Android
Si bien puedes escribir código Kotlin sin usar clases (que es lo que hiciste en codelabs anteriores), muchas partes de Android se te proporcionan en forma de clases, incluidas las actividades, las vistas y los grupos de vistas. Por lo tanto, comprender las jerarquías de clases resulta fundamental para el desarrollo de apps para Android y te permitirá aprovechar las funciones que ofrece el framework de Android.
Por ejemplo, hay una clase View
en Android que representa un área rectangular en la pantalla y es responsable de dibujar y manejar eventos. La clase TextView
es una subclase de la clase View
, lo cual significa que TextView
hereda todas las propiedades y funciones de la clase View
, y agrega lógica específica para mostrarle texto al usuario.
Si vamos un paso más allá, vemos que las clases EditText
y Button
son elementos secundarios de la clase TextView
. Ambas heredan todas las propiedades y los métodos de las clases TextView
y View
, y también agregan su propia lógica específica. Por ejemplo, EditText
agrega su propia funcionalidad en relación con la capacidad de editar texto en la pantalla.
En lugar de tener que copiar y pegar toda la lógica de las clases View
y TextView
en la clase EditText
, alcanza con que EditText
sea una subclase de la clase TextView
, que, a su vez, es la subclase de la clase View
. Luego, el código de la clase EditText
puede enfocarse específicamente en lo que hace que este componente de IU sea diferente de otras vistas.
En la parte superior de la página de la documentación de una clase de Android en el sitio web developer.android.com, puedes ver el diagrama de jerarquía de clases. Si ves kotlin.Any
en la parte superior de la jerarquía, se debe a que, en Kotlin, todas las clases tienen una superclase Any en común. Obtén más información aquí.
Como puedes ver, aprender a aprovechar la herencia entre clases puede hacer que tu código sea más fácil de escribir, reutilizar, leer y probar.
3. Crea una clase base
Jerarquía de clases de viviendas
En este codelab, compilarás un programa de Kotlin que demuestra cómo funcionan las jerarquías de clases usando viviendas (alojamientos donde viven las personas) con superficies, pisos y habitantes a modo de ejemplo.
A continuación, se muestra un diagrama de la jerarquía de clases que compilarás. En la raíz, tienes una Dwelling
que especifica las propiedades y funcionalidades que son verdaderas para todas las viviendas, similar a un plano técnico. Luego, tienes clases para una cabaña cuadrada (SquareCabin
); una caseta redonda (RoundHut
); y una torre redonda (RoundTower
), que es una RoundHut
con varios pisos.
Las clases que implementarás son las siguientes:
Dwelling
: Una clase base que representa un alojamiento no específico que contiene información común para todas las viviendasSquareCabin
: Una cabaña cuadrada de madera con una superficie de planta cuadradaRoundHut
: Una caseta redonda hecha de paja con una superficie de planta circular; el elemento superior deRoundTower
RoundTower
: Una torre redonda hecha de piedra con una superficie de planta circular y varios pisos
Crea una clase Dwelling abstracta
Cualquier clase puede ser la clase base de una jerarquía de clases o un elemento superior de otras clases.
Una clase "abstracta" es una clase de la que no se puede crear una instancia porque no se implementó por completo. Podemos pensar que es como un esbozo. Un esbozo incorpora las ideas y los planos para un elemento, pero en general no tiene información suficiente para compilarlo. Usarás un esbozo (clase abstracta) a fin de crear un plano (clase) a partir del cual crear la instancia real del objeto.
Un beneficio común de crear una superclase es que esta contiene propiedades y funciones comunes a todas sus subclases. Si no se conocen los valores de las propiedades y las implementaciones de funciones, haz que la clase sea abstracta. Por ejemplo, Vegetables
tiene muchas propiedades comunes a todos los vegetales, pero no puedes crear una instancia de un vegetal inespecífico, ya que no sabes, por ejemplo, su forma o color. Por lo tanto, Vegetable
es una clase abstracta que deja que las subclases determinen los detalles específicos de cada vegetal.
La declaración de una clase abstracta comienza con la palabra clave abstract
.
Dwelling
será una clase abstracta como Vegetable
. Tendrá propiedades y funciones comunes a muchos tipos de viviendas, pero no se conocen los valores exactos de las propiedades ni los detalles de la implementación de las funciones.
- Accede al Playground de Kotlin en https://developer.android.com/training/kotlinplayground.
- En el editor, borra
println("Hello, world!")
dentro de la funciónmain()
. - Luego, agrega este código, debajo de la función
main()
, a fin de crear una claseabstract
llamadaDwelling
.
abstract class Dwelling(){
}
Agrega una propiedad para materiales de construcción
En esta clase Dwelling
, defines cosas que son verdaderas para todas las viviendas, incluso aunque puedan ser diferentes para distintas viviendas. Todas las viviendas están hechas de materiales de construcción.
- Dentro de
Dwelling
, crea una variablebuildingMaterial
de tipoString
para representar el material de construcción. Dado que este material no cambiará, usaval
para convertirlo en una variable inmutable.
val buildingMaterial: String
- Ejecuta tu programa. Se mostrará el siguiente error:
Property must be initialized or be abstract
La propiedad buildingMaterial
no tiene un valor. De hecho, NO PUEDES asignarle un valor, porque un edificio inespecífico no está hecho de nada en particular. Por lo tanto, como se indica en el mensaje de error, puedes agregar un prefijo a la declaración de buildingMaterial
con la palabra clave abstract
a fin de indicar que esta no se definirá aquí.
- Agrega la palabra clave
abstract
a la definición de la variable.
abstract val buildingMaterial: String
- Ejecuta tu código. Aunque no hace nada, ahora se compila sin errores.
- Crea una instancia de
Dwelling
en la funciónmain()
y ejecuta tu código.
val dwelling = Dwelling()
- Se mostrará un error porque no puedes crear una instancia de la clase abstracta
Dwelling
.
Cannot create an instance of an abstract class
- Borra este código incorrecto.
Hasta ahora, el código terminado es el siguiente:
abstract class Dwelling(){
abstract val buildingMaterial: String
}
Agrega una propiedad para la capacidad
Otra propiedad de una vivienda es la capacidad, es decir, cuántas personas pueden vivir en ella.
Todas las viviendas tienen una capacidad que no cambia. Sin embargo, la capacidad no se puede establecer dentro de la superclase Dwelling
. Debe definirse en subclases para tipos específicos de viviendas.
- En
Dwelling
, agrega un número enteroval
abstract
llamadocapacity
.
abstract val capacity: Int
Agrega una propiedad privada para la cantidad de habitantes
Todas las viviendas tendrán una cantidad de residents
que residan en la vivienda (que puede ser menor o igual que la capacity
). Por lo tanto, define la propiedad residents
en la superclase Dwelling
para que todas las subclases hereden de ella y la usen.
- Puedes hacer que
residents
sea un parámetro que se pase al constructor de la claseDwelling
. La propiedadresidents
es unavar
, ya que la cantidad de habitantes puede cambiar después de creada la instancia.
abstract class Dwelling(private var residents: Int) {
Ten en cuenta que la propiedad residents
está marcada con la palabra clave private
. Private es un modificador de visibilidad en Kotlin, lo que significa que la propiedad residents
solo es visible en esta clase (y se puede usar dentro de ella). No se puede acceder a ella desde ningún otro lugar del programa. Puedes marcar las propiedades o los métodos con la palabra clave private. De lo contrario, cuando no se especifica un modificador de visibilidad, las propiedades y los métodos son public
de forma predeterminada y se puede acceder a ellos desde otras partes del programa. Dado que la cantidad de personas que viven en una vivienda suele ser información privada (en comparación con la información del material de construcción o la capacidad del edificio), esta es una decisión razonable.
Con la capacity
de la vivienda y la cantidad actual de residents
definida, puedes crear una función hasRoom()
a fin de determinar si hay espacio para otro habitante en la vivienda. Puedes definir e implementar la función hasRoom()
en la clase Dwelling
porque la fórmula para calcular si hay espacio es igual para todas las viviendas. Habrá espacio en una Dwelling
si la cantidad de residents
es menor a la capacity
, y la función debe mostrar true
o false
según esta comparación.
- Agrega la función
hasRoom()
a la claseDwelling
.
fun hasRoom(): Boolean {
return residents < capacity
}
- Puedes ejecutar este código. No debería haber errores. Todavía no hace nada visible.
El código terminado debería verse de la siguiente manera:
abstract class Dwelling(private var residents: Int) {
abstract val buildingMaterial: String
abstract val capacity: Int
fun hasRoom(): Boolean {
return residents < capacity
}
}
4. Crea subclases
Crea una subclase SquareCabin
- Debajo de la clase
Dwelling
, crea una llamadaSquareCabin
.
class SquareCabin
- A continuación, debes indicar que
SquareCabin
está relacionada conDwelling
. En tu código, indica queSquareCabin
se extiende deDwelling
(o es una subclase deDwelling)
, porqueSquareCabin
proporcionará una implementación de las partes abstractas deDwelling
.
Indica esta relación de herencia agregando dos puntos (:
) después del nombre de la clase SquareCabin
, seguidos de una llamada a fin de inicializar la clase superior Dwelling
. No olvides agregar paréntesis después del nombre de clase Dwelling
.
class SquareCabin : Dwelling()
- Cuando extiendes desde una superclase, debes pasar los parámetros obligatorios que requiere la superclase.
Dwelling
requiere la cantidad deresidents
como entrada. Puedes pasar una cantidad fija de habitantes como3
.
class SquareCabin : Dwelling(3)
Sin embargo, quieres que tu programa sea más flexible y permita una cantidad variable de habitantes en SquareCabins
. Por lo tanto, haz que residents
sea un parámetro en la definición de la clase SquareCabin
. No declares residents
como val,
porque volverías a usar una propiedad ya declarada en la clase superior Dwelling
.
class SquareCabin(residents: Int) : Dwelling(residents)
- Ejecuta tu código.
- Esto generará errores. Echemos un vistazo:
Class 'SquareCabin' is not abstract and does not implement abstract base class member public abstract val buildingMaterial: String defined in Dwelling
Cuando declaras funciones y variables abstractas, lo que haces es como una promesa de que les darás valores e implementaciones más adelante. Para una variable, significa que cualquier subclase de esa clase abstracta debe darle un valor. Para una función, significa que cualquier subclase necesita implementar el cuerpo de la función.
En la clase Dwelling
, definiste una variable abstract
buildingMaterial
. SquareCabin
es una subclase de Dwelling
, por lo que debe proporcionar un valor para buildingMaterial
. Usa la palabra clave override
a fin de indicar que esta propiedad se definió en una clase superior y que está por anularse en esta clase.
- En la clase
SquareCabin
,override
la propiedadbuildingMaterial
y asígnale el valor"Wood"
. - Haz lo mismo para la
capacity
e indica que 6 habitantes pueden vivir en unaSquareCabin
.
class SquareCabin(residents: Int) : Dwelling(residents) {
override val buildingMaterial = "Wood"
override val capacity = 6
}
El código finalizado debería verse de la siguiente manera:
abstract class Dwelling(private var residents: Int) {
abstract val buildingMaterial: String
abstract val capacity: Int
fun hasRoom(): Boolean {
return residents < capacity
}
}
class SquareCabin(residents: Int) : Dwelling(residents) {
override val buildingMaterial = "Wood"
override val capacity = 6
}
Para probar tu código, crea una instancia de SquareCabin
en tu programa.
Usa SquareCabin
- Inserta una función
main()
vacía antes de las definiciones de las clasesDwelling
ySquareCabin
.
fun main() {
}
abstract class Dwelling(private var residents: Int) {
...
}
class SquareCabin(residents: Int) : Dwelling(residents) {
...
}
- Dentro de la función
main()
, crea una instancia deSquareCabin
llamadasquareCabin
con 6 habitantes. Agrega sentencias de impresión para el material de construcción, la capacidad y la funciónhasRoom()
.
fun main() {
val squareCabin = SquareCabin(6)
println("\nSquare Cabin\n============")
println("Capacity: ${squareCabin.capacity}")
println("Material: ${squareCabin.buildingMaterial}")
println("Has room? ${squareCabin.hasRoom()}")
}
Observa que la función hasRoom()
no se definió en la clase SquareCabin
, pero sí se definió en la clase Dwelling
. Debido a que SquareCabin
es una subclase de la clase Dwelling
, la función hasRoom()
se heredó de forma gratuita. Ahora, se puede llamar a la función hasRoom()
en todas las instancias de SquareCabin
. Puedes verlo en el fragmento de código como squareCabin.hasRoom()
.
- Ejecuta el código. Se debería imprimir lo siguiente:
Square Cabin ============ Capacity: 6 Material: Wood Has room? false
Creaste squareCabin
con 6
habitantes, lo que equivale a la capacity
, por lo que hasRoom()
muestra false
. Puedes experimentar con la inicialización de SquareCabin
con una cantidad menor de residents
y, cuando vuelvas a ejecutar tu programa, hasRoom()
debería mostrar true
.
Usa with para simplificar tu código
En las sentencias println()
, cada vez que hagas referencia a una propiedad o función de squareCabin
, observa que debes repetir squareCabin.
. Esto se vuelve repetitivo y puede ser una fuente de errores cuando copias y pegas sentencias de impresión.
Cuando trabajas con una instancia específica de una clase y necesitas acceder a varias propiedades y funciones de esa instancia, puedes decir "haz todas las siguientes operaciones con este objeto de instancia" usando una sentencia with
. Comienza con la palabra clave with
, seguida del nombre de la instancia entre paréntesis, seguido de llaves que contienen las operaciones que deseas realizar.
with (instanceName) {
// all operations to do with instanceName
}
- En la función
main()
, cambia las instrucciones de impresión para usarwith
. - Borra
squareCabin.
de las sentencias de impresión.
with(squareCabin) {
println("\nSquare Cabin\n============")
println("Capacity: ${capacity}")
println("Material: ${buildingMaterial}")
println("Has room? ${hasRoom()}")
}
- Vuelve a ejecutar el código para asegurarte de que se ejecute sin errores y muestre el mismo resultado.
Square Cabin ============ Capacity: 6 Material: Wood Has room? false
Este es el código completado:
fun main() {
val squareCabin = SquareCabin(6)
with(squareCabin) {
println("\nSquare Cabin\n============")
println("Capacity: ${capacity}")
println("Material: ${buildingMaterial}")
println("Has room? ${hasRoom()}")
}
}
abstract class Dwelling(private var residents: Int) {
abstract val buildingMaterial: String
abstract val capacity: Int
fun hasRoom(): Boolean {
return residents < capacity
}
}
class SquareCabin(residents: Int) : Dwelling(residents) {
override val buildingMaterial = "Wood"
override val capacity = 6
}
Crea una subclase RoundHut
- De la misma manera que con
SquareCabin
, agrega otra subclase,RoundHut
, aDwelling
. - Anula
buildingMaterial
y asígnale un valor de"Straw"
. - Anula
capacity
y establécelo en 4.
class RoundHut(residents: Int) : Dwelling(residents) {
override val buildingMaterial = "Straw"
override val capacity = 4
}
- En
main()
, crea una instancia deRoundHut
con 3 habitantes.
val roundHut = RoundHut(3)
- Agrega el código que se indica a continuación a fin de imprimir información sobre
roundHut
.
with(roundHut) {
println("\nRound Hut\n=========")
println("Material: ${buildingMaterial}")
println("Capacity: ${capacity}")
println("Has room? ${hasRoom()}")
}
- Ejecuta el código. El resultado para todo el programa debería ser el siguiente:
Square Cabin ============ Capacity: 6 Material: Wood Has room? false Round Hut ========= Material: Straw Capacity: 4 Has room? true
Ahora, tienes una jerarquía de clases similar a esta, con Dwelling
como la clase raíz y SquareCabin
y RoundHut
como las subclases de Dwelling
.
Crea una subclase RoundTower
La última clase de esta jerarquía de clases es una torre redonda. Puedes pensar en una torre redonda como una caseta redonda de varios pisos y hecha de piedra. Por lo tanto, puedes hacer que RoundTower
sea una subclase de RoundHut
.
- Crea una clase
RoundTower
que sea una subclase deRoundHut
. Agrega el parámetroresidents
al constructor deRoundTower
y, luego, pasa ese parámetro al constructor de la superclaseRoundHut
. - Anula el valor
buildingMaterial
para que sea"Stone"
. - Establece la
capacity
en4
.
class RoundTower(residents: Int) : RoundHut(residents) {
override val buildingMaterial = "Stone"
override val capacity = 4
}
- Ejecuta este código. Se mostrará un error.
This type is final, so it cannot be inherited from
Este error significa que no se puede crear (ni heredar) una subclase de la clase RoundHut
. De manera predeterminada, en Kotlin, las clases son definitivas y no pueden crearse en subclases de ellas. Solo podrás heredar de las clases abstract
o clases marcadas con la palabra clave open
. Por lo tanto, debes marcar la clase RoundHut
con la palabra clave open
a fin de permitir que se pueda heredar de ella.
- Agrega la palabra clave
open
al principio de la declaraciónRoundHut
.
open class RoundHut(residents: Int) : Dwelling(residents) {
override val buildingMaterial = "Straw"
override val capacity = 4
}
- En
main()
, crea una instancia deroundTower
y, luego, imprime información sobre ella.
val roundTower = RoundTower(4)
with(roundTower) {
println("\nRound Tower\n==========")
println("Material: ${buildingMaterial}")
println("Capacity: ${capacity}")
println("Has room? ${hasRoom()}")
}
Este es el código completo.
fun main() {
val squareCabin = SquareCabin(6)
val roundHut = RoundHut(3)
val roundTower = RoundTower(4)
with(squareCabin) {
println("\nSquare Cabin\n============")
println("Capacity: ${capacity}")
println("Material: ${buildingMaterial}")
println("Has room? ${hasRoom()}")
}
with(roundHut) {
println("\nRound Hut\n=========")
println("Material: ${buildingMaterial}")
println("Capacity: ${capacity}")
println("Has room? ${hasRoom()}")
}
with(roundTower) {
println("\nRound Tower\n==========")
println("Material: ${buildingMaterial}")
println("Capacity: ${capacity}")
println("Has room? ${hasRoom()}")
}
}
abstract class Dwelling(private var residents: Int) {
abstract val buildingMaterial: String
abstract val capacity: Int
fun hasRoom(): Boolean {
return residents < capacity
}
}
class SquareCabin(residents: Int) : Dwelling(residents) {
override val buildingMaterial = "Wood"
override val capacity = 6
}
open class RoundHut(residents: Int) : Dwelling(residents) {
override val buildingMaterial = "Straw"
override val capacity = 4
}
class RoundTower(residents: Int) : RoundHut(residents) {
override val buildingMaterial = "Stone"
override val capacity = 4
}
- Ejecuta tu código. Ahora debería funcionar sin errores y producir el siguiente resultado:
Square Cabin ============ Capacity: 6 Material: Wood Has room? false Round Hut ========= Material: Straw Capacity: 4 Has room? true Round Tower ========== Material: Stone Capacity: 4 Has room? false
Agrega varios pisos a RoundTower
RoundHut
, por inferencia, es un edificio de un solo piso. Las torres suelen tener varios pisos (plantas).
Si se tiene en cuenta la capacidad, mientras más pisos tenga una torre, más capacidad tendrá.
Puedes modificar RoundTower
a fin de que tenga varios pisos y ajustar la capacidad según su cantidad.
- Actualiza el constructor de
RoundTower
de modo que tome un parámetro adicional de número enteroval floors
para la cantidad de pisos. Colócalo después de losresidents
. Observa que no necesitas pasar esto al constructorRoundHut
superior, ya quefloors
se define aquí enRoundTower
yRoundHut
no tienefloors
.
class RoundTower(
residents: Int,
val floors: Int) : RoundHut(residents) {
...
}
- Ejecuta tu código. Se produce un error cuando se crea
roundTower
en el métodomain()
, ya que no proporcionaste un número para el argumento defloors
. Puedes agregar el argumento que falta.
Como alternativa, en la definición de clase de RoundTower
, puedes agregar un valor predeterminado de floors
como se muestra a continuación. Luego, cuando no se pasa ningún valor de floors
al constructor, se puede usar el valor predeterminado a fin de crear la instancia del objeto.
- En tu código, agrega
= 2
después de la declaración defloors
a efectos de asignarle un valor predeterminado de 2.
class RoundTower(
residents: Int,
val floors: Int = 2) : RoundHut(residents) {
...
}
- Ejecuta tu código. Debería compilarse, ya que
RoundTower(4)
ahora crea una instancia de objetoRoundTower
con el valor predeterminado de 2 pisos. - En la clase
RoundTower
, actualiza lacapacity
y multiplícala por la cantidad de pisos.
override val capacity = 4 * floors
- Ejecuta tu código y observa que la capacidad de
RoundTower
ahora es de 8 para 2 pisos.
Este es tu código terminado.
fun main() {
val squareCabin = SquareCabin(6)
val roundHut = RoundHut(3)
val roundTower = RoundTower(4)
with(squareCabin) {
println("\nSquare Cabin\n============")
println("Capacity: ${capacity}")
println("Material: ${buildingMaterial}")
println("Has room? ${hasRoom()}")
}
with(roundHut) {
println("\nRound Hut\n=========")
println("Material: ${buildingMaterial}")
println("Capacity: ${capacity}")
println("Has room? ${hasRoom()}")
}
with(roundTower) {
println("\nRound Tower\n==========")
println("Material: ${buildingMaterial}")
println("Capacity: ${capacity}")
println("Has room? ${hasRoom()}")
}
}
abstract class Dwelling(private var residents: Int) {
abstract val buildingMaterial: String
abstract val capacity: Int
fun hasRoom(): Boolean {
return residents < capacity
}
}
class SquareCabin(residents: Int) : Dwelling(residents) {
override val buildingMaterial = "Wood"
override val capacity = 6
}
open class RoundHut(residents: Int) : Dwelling(residents) {
override val buildingMaterial = "Straw"
override val capacity = 4
}
class RoundTower(
residents: Int,
val floors: Int = 2) : RoundHut(residents) {
override val buildingMaterial = "Stone"
override val capacity = 4 * floors
}
5. Modifica las clases de la jerarquía
Calcula la superficie de la planta
En este ejercicio, aprenderás a declarar una función abstracta en una clase abstracta y, luego, a implementar su funcionalidad en las subclases.
Todas las viviendas tienen una superficie de planta, pero, según la forma del edificio, esta se calcula de manera diferente.
Define floorArea() en la clase Dwelling
- Primero, agrega una función
abstract
floorArea()
a la claseDwelling
. Muestra unDouble
. Double es un tipo de datos, comoString
yInt
. Se utiliza para números de punto flotante, es decir, números que tienen un punto seguido de una parte decimal, como 5.8793.
abstract fun floorArea(): Double
Todos los métodos abstractos definidos en una clase abstracta deben implementarse en cualquiera de sus subclases. Para poder ejecutar tu código, debes implementar floorArea()
en las subclases.
Implementa floorArea() para SquareCabin
Como sucede con buildingMaterial
y capacity
, ya que estás implementando una función abstract
que se define en la clase superior, debes usar la palabra clave override
.
- En la clase
SquareCabin
, comienza con la palabra claveoverride
seguida de la implementación real de la funciónfloorArea()
, como se muestra a continuación.
override fun floorArea(): Double {
}
- Muestra la superficie calculada de la planta. La superficie de un rectángulo o cuadrado es la longitud de su lado multiplicada por la longitud de su otro lado. El cuerpo de la función será
return length * length
.
override fun floorArea(): Double {
return length * length
}
La longitud no es una variable en la clase y es diferente para cada instancia, por lo que puedes agregarla como un parámetro constructor para la clase SquareCabin
.
- Cambia la definición de clase de
SquareCabin
para agregar un parámetrolength
de tipoDouble
. Declara la propiedad como unval
, ya que la longitud de un edificio no cambia.
class SquareCabin(residents: Int, val length: Double) : Dwelling(residents) {
Dwelling
y, por lo tanto, todas sus subclases tienen residents
como un argumento del constructor. Dado que es el primer argumento del constructor de Dwelling
, es una práctica recomendada que también lo conviertas en el primer argumento en todos los constructores de las subclases y que coloques los argumentos en el mismo orden en todas las definiciones de clase. Por lo tanto, inserta el nuevo parámetro length
después del parámetro residents
.
- En
main()
, actualiza la creación de la instancia desquareCabin
. Pasa50.0
al constructorSquareCabin
como lalength
.
val squareCabin = SquareCabin(6, 50.0)
- Dentro de la sentencia
with
parasquareCabin
, agrega una sentencia de impresión para la superficie de la planta.
println("Floor area: ${floorArea()}")
No se ejecutará tu código, ya que también deberás implementar floorArea()
en RoundHut
.
Implementa floorArea() para RoundHut
De la misma manera, implementa la superficie de la planta para RoundHut
. RoundHut
también es una subclase directa de Dwelling
, por lo que debes usar la palabra clave override
.
La superficie de la planta de una vivienda circular es igual a PI * radio * radio.
PI
es un valor matemático. Se define en una biblioteca de matemática. Una biblioteca es una colección predefinida de funciones y valores definidos fuera de un programa que este puede usar. Para usar una función o un valor de una biblioteca, deberás indicarle al compilador que lo vas a usar. Para ello, importa la función o el valor a tu programa. Para usar PI
en tu programa, debes importar kotlin.math.PI
.
- Importa
PI
desde la biblioteca de matemática de Kotlin. Coloca esto en la parte superior del archivo, antes demain()
.
import kotlin.math.PI
- Implementa la función
floorArea()
paraRoundHut
.
override fun floorArea(): Double {
return PI * radius * radius
}
Advertencia: Si no importas kotlin.math.PI, verás un error, por lo que debes importar esta biblioteca antes de usarla. Como alternativa, puedes escribir la versión completamente calificada de PI, como en kotlin.math.PI * radio * radio, y, luego, no se necesitará la sentencia de importación.
- Actualiza el constructor de
RoundHut
a fin de pasar elradius
.
open class RoundHut(
residents: Int,
val radius: Double) : Dwelling(residents) {
- En
main()
, actualiza la inicialización deroundHut
pasando unradius
de10.0
al constructor deRoundHut
.
val roundHut = RoundHut(3, 10.0)
- Agrega una sentencia de impresión dentro de la sentencia
with
deroundHut
.
println("Floor area: ${floorArea()}")
Implementa floorArea() para RoundTower
Tu código aún no se ejecuta correctamente y genera este error:
Error: No value passed for parameter 'radius'
En RoundTower
, a fin de que tu programa se compile, no necesitas implementar floorArea()
, ya que se hereda de RoundHut
, pero debes actualizar la definición de la clase RoundTower
para que también tenga el mismo argumento de radius
que su superior RoundHut
.
- Cambia el constructor de RoundTower de modo que también tome el
radius
. Coloca elradius
después de losresidents
y antes de losfloors
. Te recomendamos que las variables con valores predeterminados se enumeren al final. Recuerda pasar elradius
al constructor de la clase superior.
class RoundTower(
residents: Int,
radius: Double,
val floors: Int = 2) : RoundHut(residents, radius) {
- Actualiza la inicialización de
roundTower
enmain()
.
val roundTower = RoundTower(4, 15.5)
- Además, agrega una sentencia de impresión que llame a
floorArea()
.
println("Floor area: ${floorArea()}")
- Ahora puedes ejecutar tu código.
- Observa que el cálculo de
RoundTower
no es correcto porque se hereda deRoundHut
y no tiene en cuenta la cantidad defloors
. - En
RoundTower
,override floorArea()
de modo que puedas darle una implementación diferente que multiplique la superficie por la cantidad de pisos. Observa cómo puedes definir una función en una clase abstracta (Dwelling
), implementarla en una subclase (RoundHut
) y luego anularla de nuevo en una subclase de la subclase (RoundTower
). Es lo mejor de ambos mundos: heredas la funcionalidad que deseas y puedes anular aquella que no quieras.
override fun floorArea(): Double {
return PI * radius * radius * floors
}
Este código funciona, pero hay una forma de evitar repetir código que ya se encuentra en la clase superior RoundHut
. Puedes llamar a la función floorArea()
desde la clase superior RoundHut
, que muestra PI * radius * radius
. Luego, multiplica ese resultado por la cantidad de floors
.
- En
RoundTower
, actualizafloorArea()
a fin de usar la implementación de la superclase defloorArea()
. Usa la palabra clavesuper
para llamar a la función definida en el elemento superior.
override fun floorArea(): Double {
return super.floorArea() * floors
}
- Vuelve a ejecutar el código.
RoundTower
muestra el espacio correcto de todos los pisos.
Este es tu código terminado:
import kotlin.math.PI
fun main() {
val squareCabin = SquareCabin(6, 50.0)
val roundHut = RoundHut(3, 10.0)
val roundTower = RoundTower(4, 15.5)
with(squareCabin) {
println("\nSquare Cabin\n============")
println("Capacity: ${capacity}")
println("Material: ${buildingMaterial}")
println("Has room? ${hasRoom()}")
println("Floor area: ${floorArea()}")
}
with(roundHut) {
println("\nRound Hut\n=========")
println("Material: ${buildingMaterial}")
println("Capacity: ${capacity}")
println("Has room? ${hasRoom()}")
println("Floor area: ${floorArea()}")
}
with(roundTower) {
println("\nRound Tower\n==========")
println("Material: ${buildingMaterial}")
println("Capacity: ${capacity}")
println("Has room? ${hasRoom()}")
println("Floor area: ${floorArea()}")
}
}
abstract class Dwelling(private var residents: Int) {
abstract val buildingMaterial: String
abstract val capacity: Int
fun hasRoom(): Boolean {
return residents < capacity
}
abstract fun floorArea(): Double
}
class SquareCabin(residents: Int,
val length: Double) : Dwelling(residents) {
override val buildingMaterial = "Wood"
override val capacity = 6
override fun floorArea(): Double {
return length * length
}
}
open class RoundHut(residents: Int,
val radius: Double) : Dwelling(residents) {
override val buildingMaterial = "Straw"
override val capacity = 4
override fun floorArea(): Double {
return PI * radius * radius
}
}
class RoundTower(residents: Int, radius: Double,
val floors: Int = 2) : RoundHut(residents, radius) {
override val buildingMaterial = "Stone"
override val capacity = 4 * floors
override fun floorArea(): Double {
return super.floorArea() * floors
}
}
El resultado debería ser el siguiente:
Square Cabin ============ Capacity: 6 Material: Wood Has room? false Floor area: 2500.0 Round Hut ========= Material: Straw Capacity: 4 Has room? true Floor area: 314.1592653589793 Round Tower ========== Material: Stone Capacity: 8 Has room? true Floor area: 1509.5352700498956
Permite que otra persona obtenga una habitación
Agrega la capacidad para que otra persona obtenga una habitación mediante una función getRoom()
que aumente la cantidad de habitantes de uno en uno. Dado que esta lógica es la misma para todas las viviendas, puedes implementar la función en Dwelling
y hacer que esté disponible para todas las subclases y sus elementos secundarios. ¡Bravo!
Notas:
- Usa una sentencia
if
que solo agregue un habitante si hay capacidad restante. - Imprime un mensaje con el resultado.
- Puedes usar
residents++
como versión abreviada deresidents = residents + 1
a fin de incrementar en 1 la variableresidents
.
- Implementa la función
getRoom()
en la claseDwelling
.
fun getRoom() {
if (capacity > residents) {
residents++
println("You got a room!")
} else {
println("Sorry, at capacity and no rooms left.")
}
}
- Agrega algunas sentencias de impresión al bloque de sentencias
with
de modo queroundHut
detecte lo que sucede congetRoom()
yhasRoom()
usadas en conjunto.
println("Has room? ${hasRoom()}")
getRoom()
println("Has room? ${hasRoom()}")
getRoom()
Resultado de estas sentencias de impresión:
Has room? true You got a room! Has room? false Sorry, at capacity and no rooms left.
Consulta el código de la solución para obtener más información.
Adapta una alfombra en una vivienda redonda
Supongamos que necesitas saber la longitud de un lado de la alfombra para colocarla en RoundHut
o RoundTower
. Coloca la función en RoundHut
de modo que esté disponible para todas las viviendas redondas.
- Primero, importa la función
sqrt()
desde la bibliotecakotlin.math
.
import kotlin.math.sqrt
- Implementa la función
calculateMaxCarpetLength()
en la claseRoundHut
. La fórmula para calcular la longitud de la alfombra cuadrada que puede caber en un círculo essqrt(2) * radius
. Esto se explica en el diagrama anterior.
fun calculateMaxCarpetLength(): Double {
return sqrt(2.0) * radius
}
Pasa un valor Double
, 2.0
a la función matemática sqrt(2.0)
, ya que el tipo de datos que se muestra de la función es Double
y no Integer
.
- Ahora, se puede llamar al método
calculateMaxCarpetLength()
en instancias deRoundHut
yRoundTower
. Agrega sentencias de impresión aroundHut
yroundTower
en la funciónmain()
.
println("Carpet Length: ${calculateMaxCarpetLength()}")
Consulta el código de la solución para obtener más información.
¡Felicitaciones! Ya creaste una jerarquía de clases completa con propiedades y funciones, y aprendiste todo lo que necesitas para crear clases más útiles.
6. Código de solución
Este es el código de solución completo para este codelab, incluidos los comentarios.
/**
* Program that implements classes for different kinds of dwellings.
* Shows how to:
* Create class hierarchy, variables and functions with inheritance,
* abstract class, overriding, and private vs. public variables.
*/
import kotlin.math.PI
import kotlin.math.sqrt
fun main() {
val squareCabin = SquareCabin(6, 50.0)
val roundHut = RoundHut(3, 10.0)
val roundTower = RoundTower(4, 15.5)
with(squareCabin) {
println("\nSquare Cabin\n============")
println("Capacity: ${capacity}")
println("Material: ${buildingMaterial}")
println("Floor area: ${floorArea()}")
}
with(roundHut) {
println("\nRound Hut\n=========")
println("Material: ${buildingMaterial}")
println("Capacity: ${capacity}")
println("Floor area: ${floorArea()}")
println("Has room? ${hasRoom()}")
getRoom()
println("Has room? ${hasRoom()}")
getRoom()
println("Carpet size: ${calculateMaxCarpetLength()}")
}
with(roundTower) {
println("\nRound Tower\n==========")
println("Material: ${buildingMaterial}")
println("Capacity: ${capacity}")
println("Floor area: ${floorArea()}")
println("Carpet Length: ${calculateMaxCarpetLength()}")
}
}
/**
* Defines properties common to all dwellings.
* All dwellings have floorspace,
* but its calculation is specific to the subclass.
* Checking and getting a room are implemented here
* because they are the same for all Dwelling subclasses.
*
* @param residents Current number of residents
*/
abstract class Dwelling(private var residents: Int) {
abstract val buildingMaterial: String
abstract val capacity: Int
/**
* Calculates the floor area of the dwelling.
* Implemented by subclasses where shape is determined.
*
* @return floor area
*/
abstract fun floorArea(): Double
/**
* Checks whether there is room for another resident.
*
* @return true if room available, false otherwise
*/
fun hasRoom(): Boolean {
return residents < capacity
}
/**
* Compares the capacity to the number of residents and
* if capacity is larger than number of residents,
* add resident by increasing the number of residents.
* Print the result.
*/
fun getRoom() {
if (capacity > residents) {
residents++
println("You got a room!")
} else {
println("Sorry, at capacity and no rooms left.")
}
}
}
/**
* A square cabin dwelling.
*
* @param residents Current number of residents
* @param length Length
*/
class SquareCabin(residents: Int, val length: Double) : Dwelling(residents) {
override val buildingMaterial = "Wood"
override val capacity = 6
/**
* Calculates floor area for a square dwelling.
*
* @return floor area
*/
override fun floorArea(): Double {
return length * length
}
}
/**
* Dwelling with a circular floorspace
*
* @param residents Current number of residents
* @param radius Radius
*/
open class RoundHut(
residents: Int, val radius: Double) : Dwelling(residents) {
override val buildingMaterial = "Straw"
override val capacity = 4
/**
* Calculates floor area for a round dwelling.
*
* @return floor area
*/
override fun floorArea(): Double {
return PI * radius * radius
}
/**
* Calculates the max length for a square carpet
* that fits the circular floor.
*
* @return length of square carpet
*/
fun calculateMaxCarpetLength(): Double {
return sqrt(2.0) * radius
}
}
/**
* Round tower with multiple stories.
*
* @param residents Current number of residents
* @param radius Radius
* @param floors Number of stories
*/
class RoundTower(
residents: Int,
radius: Double,
val floors: Int = 2) : RoundHut(residents, radius) {
override val buildingMaterial = "Stone"
// Capacity depends on the number of floors.
override val capacity = floors * 4
/**
* Calculates the total floor area for a tower dwelling
* with multiple stories.
*
* @return floor area
*/
override fun floorArea(): Double {
return super.floorArea() * floors
}
}
7. Resumen
En este codelab aprendiste a hacer lo siguiente:
- Crear una jerarquía de clases, que es un árbol de clases en las que los elementos secundarios heredan la funcionalidad de las clases superiores (las propiedades y funciones se heredan mediante subclases)
- Crear una clase
abstract
en la que las subclases implementen algunas de sus funciones (por lo tanto, no se puede crear una instancia de una claseabstract
) - Crear subclases de una clase
abstract
- Usar la palabra clave
override
a fin de anular las propiedades y funciones en las subclases - Usar la palabra clave
super
para hacer referencia a funciones y propiedades en la clase superior - Crear una clase
open
que pueda ser una subclase - Crear una propiedad
private
de modo que solo se pueda usar dentro de la clase - Usar la construcción
with
a efectos de realizar varias llamadas en la misma instancia de objeto - Importar funciones desde la biblioteca
kotlin.math