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çõesif/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
- Um computador com conexão de Internet para acessar o Playground Kotlin.
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
.
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.
Herança em classes Android
Embora você possa criar código Kotlin sem usar classes, como fez em codelabs anteriores, muitas partes do Android são fornecidas na forma de classes, incluindo atividades e visualizações em grupo ou individuais. 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.
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).
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ê vai 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.
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 mãe daRoundTower
.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.
- Acesse o Playground Kotlin em https://developer.android.com/training/kotlinplayground.
- No editor, exclua
println("Hello, world!")
na funçãomain()
. - Em seguida, adicione esse código abaixo da função
main()
para criar uma classeabstract
chamadaDwelling
.
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.
- Dentro da
Dwelling
, crie uma variávelbuildingMaterial
do tipoString
para representar o material da construção. Como o material de construção não mudará, useval
para torná-lo uma variável imutável.
val buildingMaterial: String
- 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.
- Adicione a palavra-chave
abstract
à definição da variável.
abstract val buildingMaterial: String
- Execute o código e ele será compilado sem erros mesmo não fazendo nada por enquanto.
- Crie uma instância da
Dwelling
na funçãomain()
e execute o código.
val dwelling = Dwelling()
- 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
- 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.
- Na
Dwelling
, adicione um inteiroval
abstract
chamadocapacity
.
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.
- É possível transformar
residents
em um parâmetro que é transmitido para o construtor da classeDwelling
. A propriedaderesidents
é umavar
, 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.
- Adicione a função
hasRoom()
à classeDwelling
.
fun hasRoom(): Boolean {
return residents < capacity
}
- 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
- Abaixo da classe
Dwelling
, crie outra com o nomeSquareCabin
.
class SquareCabin
- Em seguida, é necessário indicar que a
SquareCabin
está relacionada àDwelling
. No código, você quer indicar que aSquareCabin
se estende daDwelling
(ou é uma subclasse daDwelling)
, porque aSquareCabin
fornecerá uma implementação para as partes abstratas daDwelling
.
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()
- Ao estender uma superclasse, você precisa transmitir os parâmetros obrigatórios esperados por ela. A
Dwelling
exige o número deresidents
com entrada. Você pode informar um número fixo de residentes, como3
.
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)
- Execute o código.
- 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.
- Dentro da classe
SquareCabin
, marque a propriedadebuildingMaterial
com a palavra-chaveoverride
e atribua a ela o valor"Wood"
. - Faça o mesmo para a
capacity
, indicando que seis moradores podem viver em umaSquareCabin
.
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
- Insira uma função
main()
vazia antes da definição das classeDwelling
eSquareCabin
.
fun main() {
}
abstract class Dwelling(private var residents: Int) {
...
}
class SquareCabin(residents: Int) : Dwelling(residents) {
...
}
- Na função
main()
, crie uma instância daSquareCabin
chamadasquareCabin
com seis moradores. Adicione instruções de exibição para o material de construção, a capacidade e a funçãohasRoom()
.
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()
.
- 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
}
- Na função
main()
, altere as instruções de exibição para que usemwith
. - 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()}")
}
- 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
- Da mesma forma que na
SquareCabin
, adicione outra subclasse,RoundHut
, àDwelling
. - Modifique a propriedade
buildingMaterial
atribuindo a ela o valor"Straw"
. - Modifique
capacity
e defina-a como 4.
class RoundHut(residents: Int) : Dwelling(residents) {
override val buildingMaterial = "Straw"
override val capacity = 4
}
- Na
main()
, crie uma instância daRoundHut
com três moradores.
val roundHut = RoundHut(3)
- Adicione o código abaixo para mostrar informações sobre a
roundHut
.
with(roundHut) {
println("\nRound Hut\n=========")
println("Material: ${buildingMaterial}")
println("Capacity: ${capacity}")
println("Has room? ${hasRoom()}")
}
- 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
.
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
.
- Crie uma classe
RoundTower
que seja uma subclasse daRoundHut
. Adicione o parâmetroresidents
ao construtor daRoundTower
e transmita-o ao construtor da superclasseRoundHut
. - Modifique o
buildingMaterial
para que seja"Stone"
. - Defina a
capacity
como4
.
class RoundTower(residents: Int) : RoundHut(residents) {
override val buildingMaterial = "Stone"
override val capacity = 4
}
- 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.
- Adicione a palavra-chave
open
no início da declaraçãoRoundHut
.
open class RoundHut(residents: Int) : Dwelling(residents) {
override val buildingMaterial = "Straw"
override val capacity = 4
}
- No
main()
, crie uma instância daroundTower
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
}
- 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.
- Atualize o construtor
RoundTower
para usar outro parâmetro inteiroval floors
para o número de andares. Coloque-o depois deresidents
. Você não precisa transmiti-lo para o construtor paiRoundHut
, porquefloors
está definido naRoundTower
, e aRoundHut
não temfloors
.
class RoundTower(
residents: Int,
val floors: Int) : RoundHut(residents) {
...
}
- Execute o código. Você verá um erro ao criar a
roundTower
no métodomain()
, porque não está fornecendo um número para o argumentofloors
. 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.
- No seu código, adicione
= 2
depois da declaração defloors
para atribuir a ele um valor padrão de 2.
class RoundTower(
residents: Int,
val floors: Int = 2) : RoundHut(residents) {
...
}
- Execute o código. Ele será compilado, porque a
RoundTower(4)
agora cria uma instância de objetoRoundTower
com o valor padrão de dois andares. - Na classe
RoundTower
, atualize acapacity
para ser multiplicada pelo número de andares.
override val capacity = 4 * floors
- 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
- Primeiro, adicione uma função
floorArea()
abstract
à classeDwelling
. Retorne umDouble
. Double é um tipo de dados, comoString
eInt
, 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
.
- Na classe
SquareCabin
, use a palavra-chaveoverride
no início, seguida pela implementação da funçãofloorArea()
, como mostrado abaixo.
override fun floorArea(): Double {
}
- 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
.
- Mude a definição da classe
SquareCabin
para adicionar um parâmetrolength
do tipoDouble
. Declare a propriedade como umaval
, 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
.
- No
main()
, atualize a criação da instânciasquareCabin
. Transmita50.0
para o construtor daSquareCabin
como olength
.
val squareCabin = SquareCabin(6, 50.0)
- Na instrução
with
dasquareCabin
, 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
.
- Importe
PI
da biblioteca de matemática do Kotlin. Coloque o código mostrado abaixo na parte de cima do arquivo, antes demain()
:
import kotlin.math.PI
- Implemente a função
floorArea()
para aRoundHut
.
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.
- Atualize o construtor da
RoundHut
para transmitir oradius
.
open class RoundHut(
residents: Int,
val radius: Double) : Dwelling(residents) {
- No
main()
, atualize a inicialização daroundHut
transmitindo umradius
de10.0
para o construtor daRoundHut
.
val roundHut = RoundHut(3, 10.0)
- Adicione uma instrução de exibição na instrução
with
daroundHut
.
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
.
- Mude o construtor da RoundTower para também usar o
radius
. Coloque oradius
depois deresidents
e antes defloors
. Recomendamos que as variáveis com valores padrão sejam listadas no final. Transmita oradius
para o construtor da classe pai.
class RoundTower(
residents: Int,
radius: Double,
val floors: Int = 2) : RoundHut(residents, radius) {
- Atualize a inicialização da
roundTower
na funçãomain()
.
val roundTower = RoundTower(4, 15.5)
- E adicione uma declaração de exibição que chame
floorArea()
.
println("Floor area: ${floorArea()}")
- Agora você pode executar seu código.
- O cálculo para a
RoundTower
não está correto porque é herdado daRoundHut
e não considera o número defloors
. - 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 os recursos que quer usar e substitui aqueles 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
.
- Na
RoundTower
, atualize afloorArea()
para usar a implementação da superclasse defloorArea()
. Use a palavra-chavesuper
para chamar a função definida na classe pai.
override fun floorArea(): Double {
return super.floorArea() * floors
}
- 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 deresidents = residents + 1
para adicionar um à variávelresidents
.
- Implemente a função
getRoom()
na classeDwelling
.
fun getRoom() {
if (capacity > residents) {
residents++
println("You got a room!")
} else {
println("Sorry, at capacity and no rooms left.")
}
}
- Adicione algumas instruções de exibição ao bloco de instruções
with
para aroundHut
saber o que acontece quandogetRoom()
ehasRoom()
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.
- Primeiro, importe a função
sqrt()
da bibliotecakotlin.math
.
import kotlin.math.sqrt
- Implemente a função
calculateMaxCarpetLength()
na classeRoundHut
. 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
.
- O método
calculateMaxCarpetLength()
agora pode ser chamado nas instâncias daRoundHut
e daRoundTower
. Adicione instruções de exibição para aroundHut
eroundTower
na funçãomain()
.
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 mãe. Propriedades e funções são herdadas por subclasses;
- criar uma classe
abstract
em que algumas funcionalidades podem ser implementados pelas subclasses. Portanto, uma classeabstract
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
- Herança (link em inglês)
- Classes e herança (link em inglês)
- Construtores (link em inglês)
- Herança (classes abstratas, abertas, substitutas e particulares) (link em inglês)
- Definição formal de
with
(link em inglês)