Classes e herança no Kotlin

1. Antes de começar

Pré-requisitos

  • Ter familiaridade com o uso do Playground Kotlin para editar programas.
  • Conhecer conceitos básicos de programação em Kotlin, conforme discutido na Unidade 1 deste curso. Em especial, o programa main(), funções com argumentos que retornam valores, variáveis, tipos de dados e operações e instruções if/else.
  • Saber definir uma classe no Kotlin, criar uma instância de objeto e acessar os métodos e propriedades dela.

O que você vai aprender

  • Criar um programa Kotlin que usa a herança para implementar uma hierarquia de classes.
  • Estender uma classe, adicionar novas funcionalidades a ela ou modificar suas funcionalidades existentes.
  • Escolher o modificador de visibilidade correto para as variáveis.

O que você vai criar

  • Um programa Kotlin com diferentes tipos de residências que são implementadas como uma hierarquia de classes.

O que é necessário

2. O que é uma hierarquia de classes?

É natural para as pessoas classificar itens com propriedades e comportamentos semelhantes em grupos e até formar algum tipo de hierarquia entre eles. Por exemplo, você pode ter uma categoria ampla, como vegetais, e dentro dela pode ter um tipo mais específico, como legumes (link em inglês). Na categoria dos legumes, é possível ter até tipos mais específicos, como ervilhas, feijões, lentilhas, grão-de-bico e soja.

Isso pode ser representado como uma hierarquia, porque os legumes contêm ou herdam todas as propriedades dos vegetais (por exemplo, são plantas e comestíveis). Da mesma forma, ervilhas, feijão e lentilhas têm as propriedades dos legumes, além de propriedades próprias.

Vamos ver como representar essa relação em termos de programação. Se você transformar Vegetable em uma classe no Kotlin, poderá criar Legume como uma filha ou subclasse da classe Vegetable. Isso significa que todos os métodos e propriedades da classe Vegetable serão herdados pela classe Legume (ou seja, também estarão disponíveis nela).

É possível representar isso em um diagrama de hierarquia de classes, como mostrado abaixo. Você pode se referir a Vegetable como a classe mãe ou a superclasse da Legume.

87e0a5eb0f85042d.png

Você pode expandir a hierarquia de classes criando subclasses de Legume, como Lentil e Chickpea. Isso faz com que a classe Legume seja uma filha ou subclasse de Vegetable, além de uma classe mãe ou uma superclasse de Lentil e Chickpea. A Vegetable é a classe raiz ou de nível superior (ou base) dessa hierarquia.

638655b960530d9.png

Herança em classes Android

Embora você possa escrever código Kotlin sem usar classes, como fez em codelabs anteriores, muitas partes do Android são fornecidas na forma de classes, incluindo atividades, visualizações e visualizações em grupo. Compreender as hierarquias de classes é fundamental para o desenvolvimento de apps Android e permite que você aproveite os recursos do framework do Android.

Por exemplo, há uma classe View no Android que representa uma área retangular na tela e é responsável por desenhar e processar eventos. A classe TextView é uma subclasse da View, o que significa que TextView herda todas as propriedades e funcionalidades da classe View, além de adicionar lógica específica para mostrar texto ao usuário.

c39a8aaa5b013de8.png

Além disso, as classes EditText e Button são filhas da TextView. Elas herdam todos os métodos e propriedades das classes TextView e View, além de adicionar a própria lógica específica. Por exemplo, a EditText adiciona uma funcionalidade própria para editar texto na tela.

Em vez de precisar copiar e colar toda a lógica das classes View e TextView na EditText, a EditText pode simplesmente ser uma subclasse da TextView, que por sua vez é uma subclasse da View. Assim, o código na classe EditText pode se concentrar especificamente no que torna esse componente de IU diferente de outras visualizações.

Na parte superior de uma página de documentação de uma classe Android no site developer.android.com, é possível ver o diagrama da hierarquia de classes. kotlin.Any aparece no topo da hierarquia porque, no Kotlin, todas as classes têm uma superclasse Any comum. Saiba mais neste link (em inglês).

1ce2b1646b8064ab.png

Como você pode notar, aprender a usar a herança entre as classes pode facilitar o desenvolvimento, a reutilização, a leitura e os testes do código.

3. Criar uma classe base

Hierarquia de classes de residências

Neste codelab, você criará um programa Kotlin que demonstra como as hierarquias de classes funcionam usando residências (locais em que as pessoas vivem) como exemplo, com área útil, andares e moradores.

Veja abaixo um diagrama da hierarquia de classes que você vai criar. Na raiz, há uma Dwelling que especifica propriedades e funcionalidades verdadeiras para todas as residências, semelhante a um esquema. Depois, há classes para um chalé quadrado (SquareCabin), uma cabana redonda (RoundHut) e uma torre redonda (RoundTower), que é uma RoundHut com vários andares.

de1387ca7fc26c81.png

Estas são as classes que você vai implementar:

  • Dwelling: uma classe base que representa um abrigo não específico que contém informações comuns a todas as residências.
  • SquareCabin: um chalé feito de madeira com uma planta quadrada.
  • RoundHut: uma cabana redonda feita de palha com uma planta circular, que é a classe pai da RoundTower.
  • RoundTower: uma torre redonda feita de pedra com uma planta circular e vários andares.

Criar uma classe Dwelling abstrata

Qualquer classe pode ser a base em uma hierarquia de classes ou mãe de outras classes.

Uma classe "abstrata" é uma que não pode ser instanciada porque não foi totalmente implementada. Pense nela como um esboço. Um esboço incorpora as ideias e os planos para algo, mas não costuma ter informações suficientes para a construção. Use um esboço (classe abstrata) para criar um esquema (classe) do qual você criará a instância real do objeto.

Uma vantagem comum de criar uma superclasse é que ela contém propriedades e funções comuns a todas as subclasses. Se os valores das propriedades e as implementações das funções não forem conhecidos, torne a classe abstrata. Por exemplo, a classe Vegetables tem muitas propriedades comuns a todos os vegetais, mas não é possível criar uma instância de um vegetal não específico, porque você não sabe, por exemplo, a forma ou a cor dele. Portanto, a Vegetable é uma classe abstrata que deixa que as subclasses determinem os detalhes específicos de cada vegetal.

A declaração de uma classe abstrata começa com a palavra-chave abstract.

A Dwelling será uma classe abstrata como Vegetable. Ela terá propriedades e funções comuns a muitos tipos de residências, mas os valores exatos das propriedades e os detalhes da implementação das funções não são conhecidos.

  1. Acesse o Playground Kotlin em https://developer.android.com/training/kotlinplayground.
  2. No editor, exclua println("Hello, world!") na função main().
  3. Em seguida, adicione esse código abaixo da função main() para criar uma classe abstract chamada Dwelling.
abstract class Dwelling(){
}

Adicionar uma propriedade para o material de construção

Nesta classe Dwelling, você define os elementos verdadeiros para todas as residências, mesmo que sejam diferentes entre elas. Todas as residências são feitas de algum material de construção.

  1. Dentro da Dwelling, crie uma variável buildingMaterial do tipo String para representar o material da construção. Como o material de construção não mudará, use val para torná-lo uma variável imutável.
val buildingMaterial: String
  1. Execute o programa e você verá esse erro.
Property must be initialized or be abstract

A propriedade buildingMaterial não tem um valor. Na verdade, NÃO é possível atribuir um valor a ela, já que uma construção não específica não é feita de nenhum material específico. Dessa forma, como a mensagem de erro indica, você pode prefixar a declaração da buildingMaterial com a palavra-chave abstract para indicar que ela não será definida aqui.

  1. Adicione a palavra-chave abstract à definição da variável.
abstract val buildingMaterial: String
  1. Execute o código e ele será compilado sem erros mesmo não fazendo nada por enquanto.
  2. Crie uma instância da Dwelling na função main() e execute o código.
val dwelling = Dwelling()
  1. Você verá um erro porque não é possível criar uma instância da classe abstrata Dwelling.
Cannot create an instance of an abstract class
  1. Exclua esse código incorreto.

Seu código finalizado até agora ficará assim:

abstract class Dwelling(){
    abstract val buildingMaterial: String
}

Adicionar uma propriedade para a capacidade

Outra propriedade de uma residência é a capacidade, ou seja, quantas pessoas podem morar nela.

Todas as residências têm uma capacidade que não muda. No entanto, a capacidade não pode ser configurada na superclasse Dwelling. Ela precisa ser definida em subclasses de tipos específicos de residência.

  1. Na Dwelling, adicione um inteiro val abstract chamado capacity.
abstract val capacity: Int

Adicionar uma propriedade particular para o número de moradores

Todas as residências terão um número de residents que moram nelas (que pode ser menor ou igual à capacity). Portanto, defina a propriedade residents na superclasse Dwelling para que todas as subclasses possam herdá-la e usá-la.

  1. É possível transformar residents em um parâmetro que é transmitido para o construtor da classe Dwelling. A propriedade residents é uma var, porque o número de residentes pode mudar depois que a instância é criada.
abstract class Dwelling(private var residents: Int) {

A propriedade residents é marcada com a palavra-chave private. Essa palavra-chave é um modificador de visibilidade (link em inglês) do Kotlin, o que significa que a propriedade residents só é visível e pode ser usada dentro dessa classe. Ela não pode ser acessada de outro lugar do programa. Você pode marcar propriedades ou métodos com a palavra-chave "private". Quando nenhum modificador de visibilidade for especificado, as propriedades e os métodos serão public por padrão e poderão ser acessados em outras partes do programa. Como o número de pessoas que vivem em uma residência geralmente é uma informação particular (em relação às informações sobre o material de construção ou a capacidade da residência), essa decisão faz sentido.

Com a capacity da residência e o número de residents atuais definidos, você pode criar uma função hasRoom() para determinar se há espaço para mais moradores na residência. É possível definir e implementar a função hasRoom() na classe Dwelling, porque a fórmula para calcular se há espaço é a mesma para todas as residências. Uma Dwelling terá espaço se o número de residents for menor que a capacity. A função precisa retornar true ou false com base nessa comparação.

  1. Adicione a função hasRoom() à classe Dwelling.
fun hasRoom(): Boolean {
    return residents < capacity
}
  1. Execute esse código, e ele não terá erros. Ele ainda não faz nada que possa ser observado ainda.

O código final ficará assim:

abstract class Dwelling(private var residents: Int) {

   abstract val buildingMaterial: String
   abstract val capacity: Int

   fun hasRoom(): Boolean {
       return residents < capacity
   }
}

4. Criar subclasses

Criar uma subclasse SquareCabin

  1. Abaixo da classe Dwelling, crie outra com o nome SquareCabin.
class SquareCabin
  1. Em seguida, é necessário indicar que a SquareCabin está relacionada à Dwelling. No código, você quer indicar que a SquareCabin se estende da Dwelling (ou é uma subclasse da Dwelling), porque a SquareCabin fornecerá uma implementação para as partes abstratas da Dwelling.

Indique essa relação de herança adicionando dois pontos (:) após o nome da classe SquareCabin, seguido de uma chamada para inicializar a classe pai Dwelling. Adicione parênteses depois do nome da classe Dwelling.

class SquareCabin : Dwelling()
  1. Ao estender uma superclasse, você precisa transmitir os parâmetros obrigatórios esperados por ela. A Dwelling exige o número de residents com entrada. Você pode informar um número fixo de residentes, como 3.
class SquareCabin : Dwelling(3)

No entanto, você quer que o programa seja mais flexível e permita um número variável de moradores para a SquareCabins. Portanto, torne residents um parâmetro na definição da classe SquareCabin. Não declare residents como uma val,, porque você está reutilizando uma propriedade já declarada na classe pai Dwelling.

class SquareCabin(residents: Int) : Dwelling(residents)
  1. Execute o código.
  2. Isso causará erros. Dê uma olhada:
Class 'SquareCabin' is not abstract and does not implement abstract base class member public abstract val buildingMaterial: String defined in Dwelling

Quando você declara funções e variáveis abstratas, isso age como uma promessa de que você fornecerá valores e implementações mais tarde. Para uma variável, isso significa que qualquer subclasse da classe abstrata precisa atribuir um valor a ela. Para uma função, isso significa que qualquer subclasse precisa implementar o corpo da função.

Na classe Dwelling, você definiu uma variável abstract chamada buildingMaterial. A SquareCabin é uma subclasse da Dwelling, por isso precisa fornecer um valor para a buildingMaterial. Use a palavra-chave override para indicar que essa propriedade foi definida em uma classe pai e que será modificada nessa classe.

  1. Dentro da classe SquareCabin, marque a propriedade buildingMaterial com a palavra-chave override e atribua a ela o valor "Wood".
  2. Faça o mesmo para a capacity, indicando que seis moradores podem viver em uma SquareCabin.
class SquareCabin(residents: Int) : Dwelling(residents) {
    override val buildingMaterial = "Wood"
    override val capacity = 6
}

O código finalizado ficará da seguinte forma:

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 testar o código, crie uma instância da SquareCabin no programa.

Usar uma SquareCabin

  1. Insira uma função main() vazia antes da definição das classe Dwelling e SquareCabin.
fun main() {

}

abstract class Dwelling(private var residents: Int) {
    ...
}

class SquareCabin(residents: Int) : Dwelling(residents) {
    ...
}
  1. Na função main(), crie uma instância da SquareCabin chamada squareCabin com seis moradores. Adicione instruções de exibição para o material de construção, a capacidade e a função hasRoom().
fun main() {
    val squareCabin = SquareCabin(6)

    println("\nSquare Cabin\n============")
    println("Capacity: ${squareCabin.capacity}")
    println("Material: ${squareCabin.buildingMaterial}")
    println("Has room? ${squareCabin.hasRoom()}")
}

A função hasRoom() não foi definida na classe SquareCabin, mas foi definida na classe Dwelling. Como a SquareCabin é uma subclasse da Dwelling, a função hasRoom() foi herdada automaticamente. A função hasRoom() agora pode ser chamada em todas as instâncias da SquareCabin, o que pode ser visto no snippet de código como squareCabin.hasRoom().

  1. Execute o código e ele vai mostrar o seguinte:
Square Cabin
============
Capacity: 6
Material: Wood
Has room? false

Você criou uma squareCabin com 6 moradores, que é igual à capacity, então a função hasRoom() retorna false. Você pode tentar inicializar a SquareCabin com um número menor de residents e, quando executar o programa novamente, a hasRoom() retornará true.

Usar a função "with" para simplificar o código

Nas instruções println(), sempre que você referenciar uma propriedade ou função da squareCabin, perceberá que é preciso repetir a squareCabin.. Isso se torna repetitivo e pode ser uma fonte de erros ao copiar e colar instruções de exibição.

Quando estiver trabalhando com uma instância específica de uma classe e precisar acessar várias propriedades e funções dela, você poderá usar a instrução with para pedir: "faça todas as seguintes operações usando este objeto da instância". Use a palavra-chave with antes da instrução, seguida pelo nome da instância entre parênteses. Em seguida, coloque as operações que você quer realizar entre chaves.

with (instanceName) {
    // all operations to do with instanceName
}
  1. Na função main(), altere as instruções de exibição para que usem with.
  2. Exclua a squareCabin. das declarações de exibição.
with(squareCabin) {
    println("\nSquare Cabin\n============")
    println("Capacity: ${capacity}")
    println("Material: ${buildingMaterial}")
    println("Has room? ${hasRoom()}")
}
  1. Execute o código novamente para garantir que ele será executado sem erros e mostrará a mesma saída.
Square Cabin
============
Capacity: 6
Material: Wood
Has room? false

Este é o código concluído:

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
}

Criar uma subclasse RoundHut

  1. Da mesma forma que na SquareCabin, adicione outra subclasse, RoundHut, à Dwelling.
  2. Modifique a propriedade buildingMaterial atribuindo a ela o valor "Straw".
  3. Modifique capacity e defina-a como 4.
class RoundHut(residents: Int) : Dwelling(residents) {
    override val buildingMaterial = "Straw"
    override val capacity = 4
}
  1. Na main(), crie uma instância da RoundHut com três moradores.
val roundHut = RoundHut(3)
  1. Adicione o código abaixo para exibir informações sobre a roundHut.
with(roundHut) {
    println("\nRound Hut\n=========")
    println("Material: ${buildingMaterial}")
    println("Capacity: ${capacity}")
    println("Has room? ${hasRoom()}")
}
  1. Execute seu código. A saída do programa será:
Square Cabin
============
Capacity: 6
Material: Wood
Has room? false

Round Hut
=========
Material: Straw
Capacity: 4
Has room? true

Agora você tem uma hierarquia de classes com essa aparência, usando a Dwelling como a classe raiz e a SquareCabin e a RoundHut como subclasses da Dwelling.

c19084f4a83193a0.png

Criar uma subclasse RoundTower

A última classe dessa hierarquia será uma torre redonda. Imagine uma torre redonda com uma cabana redonda feita de pedra e com vários andares. Dessa forma, você pode transformar a RoundTower em uma subclasse da RoundHut.

  1. Crie uma classe RoundTower que seja uma subclasse da RoundHut. Adicione o parâmetro residents ao construtor da RoundTower e transmita-o ao construtor da superclasse RoundHut.
  2. Modifique o buildingMaterial para que seja "Stone".
  3. Defina a capacity como 4.
class RoundTower(residents: Int) : RoundHut(residents) {
    override val buildingMaterial = "Stone"
    override val capacity = 4
}
  1. Execute este código e um erro será exibido.
This type is final, so it cannot be inherited from

Esse erro significa que a classe RoundHut não pode ser uma subclasse (ou ser herdada). Por padrão, no Kotlin, as classes são finais (link em inglês) e não podem ser transformadas em subclasses. Você só pode herdar classes abstract ou que sejam marcadas com a palavra-chave open. Portanto, é necessário marcar a classe RoundHut com a palavra-chave open para permitir que ela seja herdada.

  1. Adicione a palavra-chave open no início da declaração RoundHut.
open class RoundHut(residents: Int) : Dwelling(residents) {
   override val buildingMaterial = "Straw"
   override val capacity = 4
}
  1. No main(), crie uma instância da roundTower e exiba as informações dela.
 val roundTower = RoundTower(4)
with(roundTower) {
    println("\nRound Tower\n==========")
    println("Material: ${buildingMaterial}")
    println("Capacity: ${capacity}")
    println("Has room? ${hasRoom()}")
}

Veja o 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
}
  1. Execute o código. Agora ele funcionará sem erros e produzirá a seguinte saída.
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

Adicionar vários andares à RoundTower

Por implicação, a RoundHut é uma construção térrea. As torres costumam ter vários andares.

Pensando na capacidade, quanto mais andares uma torre tiver, maior será a capacidade dela.

Você pode modificar uma RoundTower para que tenha vários andares e ajustar a capacidade dela com base nesse número.

  1. Atualize o construtor RoundTower para usar outro parâmetro inteiro val floors para o número de andares. Coloque-o depois de residents. Você não precisa transmiti-lo para o construtor pai RoundHut, porque floors está definido na RoundTower, e a RoundHut não tem floors.
class RoundTower(
    residents: Int,
    val floors: Int) : RoundHut(residents) {

    ...
}
  1. Execute o código. Você verá um erro ao criar a roundTower no método main(), porque não está fornecendo um número para o argumento floors. Você pode adicionar o argumento ausente.

Como alternativa, na definição da classe RoundTower, é possível adicionar um valor padrão para floors, conforme mostrado abaixo. Depois, quando nenhum valor de floors for transmitido ao construtor, o valor padrão poderá ser usado para criar a instância do objeto.

  1. No seu código, adicione = 2 depois da declaração de floors para atribuir a ele um valor padrão de 2.
class RoundTower(
    residents: Int,
    val floors: Int = 2) : RoundHut(residents) {

    ...
}
  1. Execute o código. Ele será compilado, porque a RoundTower(4) agora cria uma instância de objeto RoundTower com o valor padrão de dois andares.
  2. Na classe RoundTower, atualize a capacity para ser multiplicada pelo número de andares.
override val capacity = 4 * floors
  1. Execute o código e observe que a capacidade da RoundTower agora é de oito moradores para dois andares.

Este é seu código finalizado.

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. Modificar classes na hierarquia

Calcular a área útil

Neste exercício, você aprenderá a declarar uma função abstrata em uma classe abstrata e a implementar a funcionalidade dela nas subclasses.

No entanto, todas as residências têm uma área útil e, dependendo da forma da residência, ela é calculada de modo diferente.

Definir floorArea() na classe Dwelling

  1. Primeiro, adicione uma função floorArea() abstract à classe Dwelling. Retorne um Double. Double é um tipo de dados, como String e Int, usado para números de pontos flutuantes, ou seja, números que têm um ponto decimal seguido por uma parte fracionária, como 5,8793.
abstract fun floorArea(): Double

Todos os métodos abstratos definidos em uma classe abstrata precisam ser implementados em todas as subclasses dela. Antes de executar seu código, é necessário implementar a floorArea() nas subclasses.

Implementar floorArea() para SquareCabin

Como acontece com o buildingMaterial e a capacity, já que você está implementando uma função abstract definida na classe pai, precisa usar a palavra-chave override.

  1. Na classe SquareCabin, use a palavra-chave override no início, seguida pela implementação da função floorArea(), como mostrado abaixo.
override fun floorArea(): Double {

}
  1. Retorne a área útil do andar. A área de um retângulo ou quadrado é o comprimento de um lado multiplicado pelo comprimento do outro. O corpo da função será return length * length.
override fun floorArea(): Double {
    return length * length
}

O comprimento não é uma variável na classe e é diferente para cada instância, então é possível adicioná-lo como um parâmetro construtor para a classe SquareCabin.

  1. Mude a definição da classe SquareCabin para adicionar um parâmetro length do tipo Double. Declare a propriedade como uma val, porque o comprimento de uma construção não muda.
class SquareCabin(residents: Int, val length: Double) : Dwelling(residents) {

A Dwelling e todas as subclasses dela têm residents como um argumento de construtor. Por ser o primeiro argumento no construtor da Dwelling, uma prática recomendada é torná-lo o primeiro argumento em todos os construtores das subclasses e colocar os argumentos na mesma ordem em todas as definições de classe. Então, insira o novo parâmetro length depois do residents.

  1. No main(), atualize a criação da instância squareCabin. Transmita 50.0 para o construtor da SquareCabin como o length.
val squareCabin = SquareCabin(6, 50.0)
  1. Na instrução with da squareCabin, adicione uma declaração de exibição para a área útil.
println("Floor area: ${floorArea()}")

Seu código não será executado porque você também precisa implementar floorArea() para RoundHut.

Implementar floorArea() para RoundHut

Da mesma forma, implemente a área útil para a RoundHut. A RoundHut também é uma subclasse direta da Dwelling, então você precisa usar a palavra-chave override.

A área útil de uma residência circular é PI * raio * raio.

PI é um valor matemático. Ele é definido em uma biblioteca de matemática. Uma biblioteca é um conjunto predefinido de funções e valores definidos fora de um programa que podem ser usados por ele. Para usar uma função ou um valor de uma biblioteca, é necessário informar ao compilador que você o usará. Faça isso importando a função ou o valor para seu programa. Para usar PI no seu programa, você precisa importar kotlin.math.PI.

  1. Importe PI da biblioteca de matemática do Kotlin. Coloque o seguinte na parte superior do arquivo, antes de main().
import kotlin.math.PI
  1. Implemente a função floorArea() para a RoundHut.
override fun floorArea(): Double {
    return PI * radius * radius
}

Aviso: se não importar kotlin.math.PI (link em inglês), você vai encontrar um erro. Importe essa biblioteca antes de a usar. Você também pode escrever a versão totalmente qualificada de PI como kotlin.math.PI * raio * raio para que a instrução de importação não seja necessária.

  1. Atualize o construtor da RoundHut para transmitir o radius.
open class RoundHut(
   residents: Int,
   val radius: Double) : Dwelling(residents) {
  1. No main(), atualize a inicialização da roundHut transmitindo um radius de 10.0 para o construtor da RoundHut.
val roundHut = RoundHut(3, 10.0)
  1. Adicione uma instrução de exibição na instrução with da roundHut.
println("Floor area: ${floorArea()}")

Implementar floorArea() para RoundTower

Seu código ainda não vai ser executado e falhará com este erro:

Error: No value passed for parameter 'radius'

Em uma RoundTower, para que seu programa seja compilado, você não precisa implementar floorArea(), porque essa função é herdada da RoundHut. Mas é necessário atualizar a definição da classe RoundTower para que tenha o mesmo argumento radius que a classe mãe RoundHut.

  1. Mude o construtor da RoundTower para também usar o radius. Coloque o radius depois de residents e antes de floors. Recomendamos que as variáveis com valores padrão sejam listadas no final. Transmita o radius para o construtor da classe pai.
class RoundTower(
    residents: Int,
    radius: Double,
    val floors: Int = 2) : RoundHut(residents, radius) {
  1. Atualize a inicialização da roundTower na função main().
val roundTower = RoundTower(4, 15.5)
  1. E adicione uma declaração de exibição que chame floorArea().
println("Floor area: ${floorArea()}")
  1. Agora você pode executar seu código.
  2. O cálculo para a RoundTower não está correto porque é herdado da RoundHut e não considera o número de floors.
  3. Na RoundTower, override floorArea() para usar uma implementação diferente que multiplique a área pelo número de andares. É possível definir uma função em uma classe abstrata (Dwelling), implementá-la em uma subclasse (RoundHut) e modificá-la novamente em uma subclasse da subclasse (RoundTower). O melhor dos dois mundos: você herda o recursos que quer usar e modifica aquele que não quer.
override fun floorArea(): Double {
    return PI * radius * radius * floors
}

Esse código funciona, mas há uma maneira de evitar a repetição do código que já está na classe mãe RoundHut. Você pode chamar a função floorArea() da classe mãe RoundHut, que retorna PI * radius * radius. Em seguida, multiplique esse resultado pelo número de floors.

  1. Na RoundTower, atualize a floorArea() para usar a implementação da superclasse de floorArea(). Use a palavra-chave super para chamar a função definida na classe pai.
override fun floorArea(): Double {
    return super.floorArea() * floors
}
  1. Execute o código novamente para que a RoundTower calcule a área correta para vários andares.

Este é o código finalizado:

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
    }
}

A saída vai ser:

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

Permitir que um novo morador tenha um quarto

Use uma função getRoom(), que aumenta o número de moradores em um, para adicionar a possibilidade de um novo morador ter um quarto. Como essa lógica é a mesma para todas as residências, você pode implementar a função na Dwelling, e isso a disponibilizará para todas as subclasses e classes filhas delas. Tudo pronto.

Observações:

  • Use uma instrução if que adiciona um morador apenas se houver capacidade restante.
  • Exiba uma mensagem com o resultado.
  • Você pode usar residents++ como uma forma abreviada de residents = residents + 1 para adicionar um à variável residents.
  1. Implemente a função getRoom() na classe Dwelling.
fun getRoom() {
    if (capacity > residents) {
        residents++
        println("You got a room!")
    } else {
        println("Sorry, at capacity and no rooms left.")
    }
}
  1. Adicione algumas instruções de exibição ao bloco de instruções with para a roundHut saber o que acontece quando getRoom() e hasRoom() são usadas juntas.
println("Has room? ${hasRoom()}")
getRoom()
println("Has room? ${hasRoom()}")
getRoom()

Saída dessas declarações de exibição:

Has room? true
You got a room!
Has room? false
Sorry, at capacity and no rooms left.

Veja o código da solução para ter mais detalhes.

Colocar carpete em uma residência redonda

Digamos que você tenha que saber o comprimento de um lado do carpete que precisa comprar para sua RoundHut ou RoundTower. Coloque a função na classe RoundHut para a disponibilizar a todas as residências redondas.

2e328a198a82c793.png

  1. Primeiro, importe a função sqrt() da biblioteca kotlin.math.
import kotlin.math.sqrt
  1. Implemente a função calculateMaxCarpetLength() na classe RoundHut. A fórmula para calcular o comprimento do carpete quadrado que pode ser encaixado em um círculo é sqrt(2) * radius. Ela é explicada no diagrama acima.
fun calculateMaxCarpetLength(): Double {

    return sqrt(2.0) * radius
}

Transmita um valor Double, 2.0 para a função matemática sqrt(2.0), porque o tipo de retorno da função é Double, e não Integer.

  1. O método calculateMaxCarpetLength() agora pode ser chamado nas instâncias da RoundHut e da RoundTower. Adicione instruções de exibição para a roundHut e roundTower na função main().
println("Carpet Length: ${calculateMaxCarpetLength()}")

Veja o código da solução para ter mais detalhes.

Parabéns! Você criou uma hierarquia de classes completa com propriedades e funções, aprendendo todo o necessário para criar classes mais úteis.

6. Código da solução

Este é o código completo da solução deste codelab, incluindo comentários.

/**
* 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. Resumo

Neste codelab, você aprendeu a:

  • criar uma hierarquia de classes, que é uma árvore de classes em que as classes filhas herdam a funcionalidade das classes pai. Propriedades e funções são herdadas por subclasses;
  • criar uma classe abstract em que algumas funcionalidades podem ser implementados pelas subclasses. Portanto, uma classe abstract não pode ser instanciada;
  • criar subclasses de uma classe abstract;
  • usar a palavra-chave override para modificar propriedades e funções em subclasses;
  • usar a palavra-chave super para referenciar funções e propriedades na classe pai;
  • criar uma classe open para que ela possa ser transformada em subclasse;
  • criar uma propriedade private, para que só possa ser usada dentro da classe;
  • usar a construção with para fazer várias chamadas na mesma instância do objeto;
  • importar a funcionalidade da biblioteca kotlin.math.

8. Saiba mais