1. Antes de começar
Este codelab ensina a usar classes e objetos no Kotlin.
As classes fornecem instruções para construir objetos. Um objeto é uma instância de uma classe que consiste em dados específicos desse objeto. Você pode usar objetos ou instâncias de classe como sinônimos.
Como analogia, imagine que você vai construir uma casa. Uma classe é semelhante ao plano de design de um arquiteto, também conhecido como planta. A planta não é a casa em si, mas sim instruções sobre como a construir. A casa é o objeto real que é construído com base na planta.
Assim como a planta da casa especifica várias salas, cada uma com o próprio design e finalidade, cada classe também tem um design e propósito próprios. Para aprender a projetar suas classes, você precisa se familiarizar com a programação orientada a objetos (OOP, na sigla em inglês), um framework que mostra como incluir dados, lógica e comportamento em objetos.
A OOP ajuda a simplificar problemas complexos em objetos menores. Você vai aprender sobre os quatro conceitos básicos de OOP mais adiante neste codelab:
- Encapsulamento. Une as propriedades e os métodos relacionados que executam ações nessas propriedades em uma classe. Por exemplo, pense no smartphone. Ele encapsula uma câmera, uma tela, cartões de memória e vários outros componentes de hardware e software. Você não precisa se preocupar com a forma como os componentes são conectados internamente.
- Abstração. Uma extensão do encapsulamento. A ideia é ocultar ao máximo a lógica de implementação interna. Por exemplo, para tirar uma foto com o smartphone, basta abrir o app da câmera, enquadrar a cena que você quer capturar e tocar em um botão. Você não precisa saber como o app foi criado ou como o hardware da câmera no smartphone realmente funciona. Em resumo, a mecânica interna do app e a forma como uma câmera móvel captura as fotos são abstraídas para você.
- Herança. Permite criar uma classe com base nas características e no comportamento de outras classes ao estabelecer uma relação pai-filho. Por exemplo, os fabricantes produzem vários dispositivos móveis com o Android, mas a interface deles é diferente. Em outras palavras, os fabricantes herdam o recurso do SO Android e criam personalizações com base nele.
- Polimorfismo. Essa palavra é uma adaptação da raiz grega poli, que significa "muitas", e morphos, que significa "formas". O polimorfismo é a capacidade de usar objetos diferentes de uma maneira única e comum. Por exemplo, quando você conecta um alto-falante Bluetooth a um smartphone, a única informação necessária é que há um dispositivo que pode tocar áudio por Bluetooth. No entanto, há várias opções de alto-falantes Bluetooth disponíveis, e o smartphone não precisa saber trabalhar com cada um deles especificamente.
Por fim, você vai aprender sobre delegados de propriedade que fornecem código reutilizável para gerenciar valores de propriedade com uma sintaxe concisa. Neste codelab, você vai aprender esses conceitos ao criar uma estrutura de classes para um app de casa inteligente.
Pré-requisitos
- Saber abrir, editar e executar código no Playground Kotlin.
- Conhecimentos básicos de programação em Kotlin, incluindo variáveis e funções, em especial
println()
emain()
.
O que você vai aprender
- Uma visão geral da OOP.
- O que são classes.
- Como definir uma classe com construtores, funções e propriedades.
- Como instanciar um objeto.
- O que é herança.
- A diferença entre as relações IS-A e HAS-A.
- Como substituir propriedades e funções.
- O que são modificadores de visibilidade.
- O que é um delegado e como usar o delegado
by
.
O que você vai criar
- Uma estrutura de classe de casa inteligente.
- Classes que representam dispositivos inteligentes, como uma smart TV e uma iluminação inteligente.
O que é necessário
- Um computador com acesso à Internet e um navegador da Web.
2. Definir uma classe
Na definição de uma classe, você especifica as propriedades e os métodos que todos os objetos dela precisam ter.
Esse processo começa com a palavra-chave class
, seguida de um nome e um conjunto de chaves. A parte da sintaxe antes da chave de abertura também é conhecida como cabeçalho da classe. Dentro das chaves, você pode especificar propriedades e funções para a classe. Você vai aprender sobre propriedades e funções em breve. Confira a sintaxe de uma definição de classe neste diagrama:
Estas são as convenções de nomenclatura recomendadas para uma classe:
- Você pode escolher qualquer nome de classe que quiser, mas não use palavras-chave (link em inglês) do Kotlin como nome de classe. Por exemplo, a palavra-chave
fun
. - Como o nome da classe é escrito em PascalCase, cada palavra começa com uma letra maiúscula e não há espaços entre elas. Por exemplo, em SmartDevice, a primeira letra de cada palavra é maiúscula e não há espaço entre elas.
Uma classe consiste em três partes principais:
- Propriedades: variáveis que especificam os atributos dos objetos da classe.
- Métodos: funções que contêm os comportamentos e as ações da classe.
- Construtores: funções de membro especiais que criam instâncias da classe em todo o programa em que são definidas.
Esta não é a primeira vez que você trabalha com classes. Nos codelabs anteriores, você aprendeu sobre os tipos de dados, como Int
, Float
, String
e Double
. Eles são definidos como classes no Kotlin. Na definição de uma variável, você cria um objeto da classe Int
que é instanciada com um valor 1
, como mostrado neste snippet de código:
val number: Int = 1
Defina uma classe SmartDevice
:
- No Playground Kotlin, substitua o conteúdo por uma função
main()
vazia:
fun main() {
}
- Na linha antes da função
main()
, defina uma classeSmartDevice
com corpo que inclua um comentário//
empty
body
:
class SmartDevice {
// empty body
}
fun main() {
}
3. Criar uma instância de uma classe
Como você aprendeu, uma classe é uma planta para construir um objeto. O ambiente de execução do Kotlin usa a classe, ou a planta, para criar um objeto desse tipo específico. Com a classe SmartDevice
, você tem uma planta de um dispositivo inteligente. Para usar um dispositivo inteligente real no programa, é necessário criar uma instância de objeto SmartDevice
. A sintaxe de instanciação começa com o nome da classe seguido de um conjunto de parênteses, como mostrado neste diagrama:
Para usar um objeto, crie-o e o atribua a uma variável, como você faria na definição de uma variável. Use a palavra-chave val
para criar uma variável imutável e a palavra-chave var
para uma variável mutável. A palavra-chave val
ou var
é seguida pelo nome da variável, depois por um operador de atribuição =
e pela instanciação do objeto de classe. Confira a sintaxe neste diagrama:
Instancie a classe SmartDevice
como um objeto:
- Na função
main()
, use a palavra-chaveval
para criar uma variável com o nomesmartTvDevice
e a inicialize como uma instância da classeSmartDevice
:
fun main() {
val smartTvDevice = SmartDevice()
}
4. Definir métodos de classe
Na Unidade 1, você aprendeu que:
- A definição de uma função usa a palavra-chave
fun
seguida de um conjunto de parênteses e um conjunto de chaves. As chaves contêm o código, que são as instruções necessárias para executar uma tarefa. - Chamar uma função faz com que todo o código contido nela seja executado.
As ações que a classe pode realizar são definidas como funções. Por exemplo, imagine que você tem um dispositivo inteligente, uma smart TV ou uma iluminação inteligente que pode ser ativada e desativada com o smartphone. O dispositivo inteligente é convertido para a classe SmartDevice
na programação, e a ação para ativar e desativar é representada pelas funções turnOn()
e turnOff()
.
A sintaxe para definir uma função em uma classe é idêntica à que você aprendeu antes. A única diferença é que a função é colocada no corpo da classe. Quando você define uma função no corpo da classe, ela é uma função de membro ou método e representa o comportamento da classe. No restante deste codelab, as funções são chamadas de métodos sempre que aparecem no corpo de uma classe.
Defina métodos turnOn()
e turnOff()
na classe SmartDevice
:
- No corpo da classe
SmartDevice
, defina um métodoturnOn()
com um corpo vazio:
class SmartDevice {
fun turnOn() {
}
}
- No corpo do método
turnOn()
, adicione uma instruçãoprintln()
e transmita uma string"Smart
device
is
turned
on."
a ela:
class SmartDevice {
fun turnOn() {
println("Smart device is turned on.")
}
}
- Depois do método
turnOn()
, adicione outro métodoturnOff()
que mostra uma string"Smart
device
is
turned
off."
:
class SmartDevice {
fun turnOn() {
println("Smart device is turned on.")
}
fun turnOff() {
println("Smart device is turned off.")
}
}
Chamar um método em um objeto
Até agora, você definiu uma classe que serve como uma planta para um dispositivo inteligente, criou uma instância da classe e atribuiu a instância a uma variável. Agora você vai usar os métodos da classe SmartDevice
para ligar e desligar o dispositivo.
A chamada de um método em uma classe é semelhante à chamada de funções na função main()
do codelab anterior. Por exemplo, se você precisar chamar o método turnOff()
no método turnOn()
, escreva algo semelhante a este snippet de código:
class SmartDevice {
fun turnOn() {
// A valid use case to call the turnOff() method could be to turn off the TV when available power doesn't meet the requirement.
turnOff()
...
}
...
}
Para chamar um método de classe fora da classe, comece com o objeto de classe seguido pelo operador .
, o nome da função e um conjunto de parênteses. Se aplicável, os parênteses podem conter argumentos exigidos pelo método. Confira a sintaxe neste diagrama:
Chame os métodos turnOn()
e turnOff()
no objeto:
- Na função
main()
na linha após a variávelsmartTvDevice
, chame o métodoturnOn()
:
fun main() {
val smartTvDevice = SmartDevice()
smartTvDevice.turnOn()
}
- Na linha após o método
turnOn()
, chame o métodoturnOff()
:
fun main() {
val smartTvDevice = SmartDevice()
smartTvDevice.turnOn()
smartTvDevice.turnOff()
}
- Execute o código.
A saída vai ser assim:
Smart device is turned on. Smart device is turned off.
5. Definir propriedades de classe
Na Unidade 1, você aprendeu sobre variáveis, que são contêineres para dados únicos. Você aprendeu a criar uma variável somente leitura com a palavra-chave val
e uma variável mutável com a palavra-chave var
.
Enquanto os métodos definem as ações que uma classe pode realizar, as propriedades definem as características ou os atributos de dados dessa classe. Por exemplo, um dispositivo inteligente tem estas propriedades:
- Nome: nome do dispositivo.
- Categoria: tipo de dispositivo inteligente, como de entretenimento, utilitário ou culinário
- Status do dispositivo: se o dispositivo está ligado, desligado, on-line ou off-line. O dispositivo é considerado on-line quando está conectado à Internet. Caso contrário, é considerado off-line.
Basicamente, as propriedades são variáveis definidas no corpo da classe em vez de no corpo da função. Isso significa que a sintaxe para definir propriedades e variáveis é idêntica. Defina uma propriedade imutável com a palavra-chave val
e uma propriedade mutável com a palavra-chave var
.
Implemente as características mencionadas acima como propriedades da classe SmartDevice
:
- Na linha anterior ao método
turnOn()
, defina a propriedadename
e a atribua a uma string"Android
TV"
:
class SmartDevice {
val name = "Android TV"
fun turnOn() {
println("Smart device is turned on.")
}
fun turnOff() {
println("Smart device is turned off.")
}
}
- Na linha após a propriedade
name
, defina a propriedadecategory
e a atribua uma string"Entertainment"
. Em seguida, defina uma propriedadedeviceStatus
e a atribua a uma string"online"
:
class SmartDevice {
val name = "Android TV"
val category = "Entertainment"
var deviceStatus = "online"
fun turnOn() {
println("Smart device is turned on.")
}
fun turnOff() {
println("Smart device is turned off.")
}
}
- Na linha após a variável
smartTvDevice
, chame a funçãoprintln()
e transmita uma string"Device
name
is:
${smartTvDevice.name}"
a ela:
fun main() {
val smartTvDevice = SmartDevice()
println("Device name is: ${smartTvDevice.name}")
smartTvDevice.turnOn()
smartTvDevice.turnOff()
}
- Execute o código.
A saída vai ser assim:
Device name is: Android TV Smart device is turned on. Smart device is turned off.
Funções getter e setter em propriedades
As propriedades podem fazer mais do que uma variável. Por exemplo, você criou uma estrutura de classes para representar uma smart TV. Uma das ações mais comuns é aumentar e diminuir o volume. Para representar essa ação na programação, você pode criar uma propriedade com o nome speakerVolume
que contém o nível de volume atual definido no alto-falante da TV, mas há um intervalo em que o valor do volume pode estar. O volume mínimo que uma pessoa pode definir é 0, e o máximo é 100. Para a propriedade speakerVolume
nunca exceder 100 ou ficar abaixo de 0, crie uma função setter. Ao atualizar o valor da propriedade, você precisa verificar se o valor está no intervalo de 0 a 100. Como outro exemplo, imagine que há uma exigência de letras maiúsculas para o nome. Implemente uma função getter para converter a propriedade name
em letras maiúsculas.
Antes de se aprofundar na implementação dessas propriedades, você precisa entender a sintaxe completa de declaração. A sintaxe completa para definir uma propriedade mutável começa com a definição da variável seguida pelas funções opcionais get()
e set()
. Confira a sintaxe neste diagrama:
Quando você não define as funções getter e setter para uma propriedade, o compilador do Kotlin cria as funções internamente. Por exemplo, se você usar a palavra-chave var
para definir uma propriedade speakerVolume
e atribuir a ela um valor 2
, o compilador vai gerar automaticamente as funções getter e setter, como você pode notar neste snippet de código:
var speakerVolume = 2
get() = field
set(value) {
field = value
}
Essas linhas não estão visíveis no código porque são adicionadas pelo compilador em segundo plano.
A sintaxe completa de uma propriedade imutável tem duas diferenças:
- Ela começa com a palavra-chave
val
. - As variáveis de tipo
val
são somente leitura, portanto, não têm funçõesset()
.
As propriedades do Kotlin usam um campo de apoio para armazenar um valor na memória. Em linhas gerais, um campo de apoio é uma variável de classe definida internamente nas propriedades. Um campo de apoio tem como escopo uma propriedade, o que significa que só pode ser acessado pelas funções de propriedade get()
ou set()
.
Para ler o valor da propriedade na função get()
ou atualizar o valor na função set()
, você precisa usar o campo de apoio da propriedade. Ele é gerado automaticamente pelo compilador Kotlin e referenciado com um identificador field
.
Por exemplo, quando você quiser atualizar o valor da propriedade na função set()
, use o parâmetro da função set()
, conhecido como parâmetro value
, e o atribua ao field
, como foi feito neste snippet de código:
var speakerVolume = 2
set(value) {
field = value
}
Para o valor atribuído à propriedade speakerVolume
ficar no intervalo de 0 a 100, implemente a função setter como neste snippet de código:
var speakerVolume = 2
set(value) {
if (value in 0..100) {
field = value
}
}
As funções set()
verificam se o valor Int
está em um intervalo de 0 a 100 usando a palavra-chave in
seguida pelo intervalo de valor. Se o valor estiver no intervalo esperado, o valor de field
é atualizado. Caso contrário, o valor da propriedade permanece inalterado.
Para não ter que adicionar a função setter ao código agora, inclua essa propriedade em uma classe na seção Implementar uma relação entre classes deste codelab.
6. Definir um construtor
O objetivo principal do construtor é especificar como os objetos da classe são criados. Em outras palavras, os construtores inicializam e preparam um objeto para uso. Você fez isso quando instanciou o objeto. O código dentro do construtor é executado quando o objeto da classe é instanciado. Você pode definir um construtor com ou sem parâmetros.
Construtor padrão
Um construtor padrão não tem parâmetros. Você pode definir um construtor padrão como mostrado neste snippet de código:
class SmartDevice constructor() {
...
}
O objetivo do Kotlin é ser conciso, portanto, é possível remover a palavra-chave constructor
se não houver anotações ou modificadores de visibilidade no construtor. Você vai aprender sobre isso em breve. Também é possível remover os parênteses se o construtor não tiver parâmetros, conforme mostrado neste snippet de código:
class SmartDevice {
...
}
O compilador Kotlin gera automaticamente o construtor padrão. O construtor padrão gerado automaticamente não está visível no código porque foi adicionado pelo compilador em segundo plano.
Definir um construtor parametrizado
Na classe SmartDevice
, as propriedades name
e category
são imutáveis. É preciso garantir que todas as instâncias da classe SmartDevice
inicializem as propriedades name
e category
. Com a implementação atual, os valores das propriedades name
e category
estão fixados no código. Isso significa que todos os dispositivos inteligentes são nomeados pela string "Android
TV"
e categorizados com a string "Entertainment"
.
Para manter a imutabilidade, mas evitar valores fixados no código, use um construtor parametrizado para inicializar:
- Na classe
SmartDevice
, mova as propriedadesname
ecategory
para o construtor sem atribuir valores padrão:
class SmartDevice(val name: String, val category: String) {
var deviceStatus = "online"
fun turnOn() {
println("Smart device is turned on.")
}
fun turnOff() {
println("Smart device is turned off.")
}
}
O construtor passa a aceitar parâmetros para configurar as propriedades, e a maneira de instanciar um objeto para essa classe também muda. A sintaxe completa para instanciar um objeto é mostrada neste diagrama:
Esta é a representação do código:
SmartDevice("Android TV", "Entertainment")
Ambos os argumentos para o construtor são strings. Não está claro a qual parâmetro o valor precisa ser atribuído. Para corrigir isso, da mesma forma que você transmitiu argumentos de função, crie um construtor com argumentos nomeados, como mostrado neste snippet de código:
SmartDevice(name = "Android TV", category = "Entertainment")
Há dois tipos principais de construtores no Kotlin:
- Construtor principal: uma classe pode ter apenas um construtor principal, que é definido como parte do cabeçalho da classe. Um construtor principal pode ser um construtor padrão ou parametrizado. O construtor principal não tem corpo. Isso significa que ele não pode conter código.
- Construtor secundário: uma classe pode ter vários construtores secundários. Você pode definir o construtor secundário com ou sem parâmetros. O construtor secundário pode inicializar a classe e tem um corpo que pode conter lógica de inicialização. Se a classe tiver um construtor principal, cada construtor secundário precisa inicializar o construtor principal.
É possível usar o construtor principal para inicializar propriedades no cabeçalho da classe. Os argumentos transmitidos ao construtor são atribuídos às propriedades. A sintaxe para definir um construtor principal começa com o nome da classe seguido pela palavra-chave constructor
e um conjunto de parênteses. Os parênteses contêm os parâmetros do construtor principal. Se houver mais de um parâmetro, use vírgulas para separar as definições de parâmetro. No diagrama abaixo, confira a sintaxe completa para definir um construtor principal:
O construtor secundário está incluído no corpo da classe, e a sintaxe dele inclui três partes:
- Declaração de construtor secundário: a definição do construtor secundário começa com a palavra-chave
constructor
seguida por parênteses. Se aplicável, os parênteses podem conter os parâmetros exigidos pelo construtor secundário. - Inicialização do construtor principal: a inicialização começa com dois pontos seguidos da palavra-chave
this
e um conjunto de parênteses. Se aplicável, os parênteses podem conter os parâmetros exigidos pelo construtor principal. - Corpo do construtor secundário: a inicialização do construtor principal é seguida por um conjunto de chaves que contêm o corpo do construtor secundário.
Confira a sintaxe neste diagrama:
Por exemplo, imagine que você quer integrar uma API desenvolvida por um provedor de dispositivos inteligentes. No entanto, a API retorna o código de status do tipo Int
para indicar o status inicial do dispositivo. O valor retornado é 0
se o dispositivo estiver off-line e 1
se estiver on-line. Para qualquer outro valor inteiro, o status é considerado desconhecido. Você pode criar um construtor secundário na classe SmartDevice
para converter esse parâmetro statusCode
em uma representação de string, como neste snippet de código:
class SmartDevice(val name: String, val category: String) {
var deviceStatus = "online"
constructor(name: String, category: String, statusCode: Int) : this(name, category) {
deviceStatus = when (statusCode) {
0 -> "offline"
1 -> "online"
else -> "unknown"
}
}
...
}
7. Implementar uma relação entre classes
A herança permite criar uma classe com base nas características e no comportamento de outra classe. Esse é um mecanismo avançado que ajuda você a escrever código reutilizável e a estabelecer relações entre as classes.
Por exemplo, há muitos dispositivos inteligentes no mercado, como smart TVs, iluminação e interruptores inteligentes. Quando você representa dispositivos inteligentes na programação, eles compartilham algumas propriedades comuns, como nome, categoria e status. Além disso, eles têm comportamentos comuns, como estarem ligados ou desligados.
No entanto, a maneira de ligar ou desligar cada dispositivo inteligente é diferente. Por exemplo, para ligar uma TV, talvez seja necessário ligar a tela e, em seguida, configurar o último nível de volume e canal conhecidos. Por outro lado, para acender uma luz, talvez seja necessário apenas aumentar ou diminuir o brilho.
Além disso, todo dispositivo inteligente pode realizar mais funções e ações. Por exemplo, com uma TV, é possível ajustar o volume e mudar o canal. Com a iluminação, é possível ajustar o brilho ou a cor.
Em resumo, todos os dispositivos inteligentes têm recursos diferentes, mas compartilham algumas características comuns. Com a herança, é possível duplicar essas características comuns para cada classe de dispositivo inteligente ou tornar o código reutilizável.
Para fazer isso, você precisa criar uma classe mãe SmartDevice
e definir as propriedades e os comportamentos comuns. Em seguida, você pode criar classes filhas, como SmartTvDevice
e SmartLightDevice
, que herdam as propriedades da classe mãe.
Em termos de programação, dizemos que as classes SmartTvDevice
e SmartLightDevice
estendem a classe mãe SmartDevice
. A classe mãe também é chamada de superclasse e as classes filhas de subclasses. Entenda a relação entre elas neste diagrama:
No Kotlin, todas as classes são finais por padrão, o que significa que não é possível estendê-las. É necessário definir as relações entre elas.
Defina a relação entre a superclasse SmartDevice
e as subclasses:
- Na superclasse
SmartDevice
, adicione uma palavra-chaveopen
antes da palavra-chaveclass
para que ela possa ser estendida:
open class SmartDevice(val name: String, val category: String) {
...
}
A palavra-chave open
informa ao compilador que essa classe pode ser estendida.
A sintaxe para criar uma subclasse começa com a criação do cabeçalho da classe, como foi feito até agora. O parêntese de fechamento do construtor é seguido por um espaço, dois pontos, outro espaço, o nome da superclasse e um conjunto de parênteses. Se necessário, os parênteses podem incluir os parâmetros exigidos pelo construtor da superclasse. Confira a sintaxe neste diagrama:
- Crie uma subclasse
SmartTvDevice
que estenda a superclasseSmartDevice
:
class SmartTvDevice(deviceName: String, deviceCategory: String) :
SmartDevice(name = deviceName, category = deviceCategory) {
}
A definição do constructor
para SmartTvDevice
não especifica se as propriedades são mutáveis ou imutáveis. Isso significa que os parâmetros deviceName
e deviceCategory
são apenas parâmetros do constructor
em vez de propriedades de classe. Eles não podem ser usados na classe, só transmitidos ao construtor da superclasse.
- No corpo da subclasse
SmartTvDevice
, adicione a propriedadespeakerVolume
que você criou quando aprendeu sobre as funções getter e setter:
class SmartTvDevice(deviceName: String, deviceCategory: String) :
SmartDevice(name = deviceName, category = deviceCategory) {
var speakerVolume = 2
set(value) {
if (value in 0..100) {
field = value
}
}
}
- Defina uma propriedade
channelNumber
atribuída a um valor1
com uma função setter que especifica um intervalo0..200
:
class SmartTvDevice(deviceName: String, deviceCategory: String) :
SmartDevice(name = deviceName, category = deviceCategory) {
var speakerVolume = 2
set(value) {
if (value in 0..100) {
field = value
}
}
var channelNumber = 1
set(value) {
if (value in 0..200) {
field = value
}
}
}
- Defina um método
increaseSpeakerVolume()
para aumentar o volume e mostrar uma string"Speaker
volume
increased
to
$speakerVolume."
:
class SmartTvDevice(deviceName: String, deviceCategory: String) :
SmartDevice(name = deviceName, category = deviceCategory) {
var speakerVolume = 2
set(value) {
if (value in 0..100) {
field = value
}
}
var channelNumber = 1
set(value) {
if (value in 0..200) {
field = value
}
}
fun increaseSpeakerVolume() {
speakerVolume++
println("Speaker volume increased to $speakerVolume.")
}
}
- Adicione um método
nextChannel()
para aumentar o número do canal e mostrar uma string"Channel
number
increased
to
$channelNumber."
:
class SmartTvDevice(deviceName: String, deviceCategory: String) :
SmartDevice(name = deviceName, category = deviceCategory) {
var speakerVolume = 2
set(value) {
if (value in 0..100) {
field = value
}
}
var channelNumber = 1
set(value) {
if (value in 0..200) {
field = value
}
}
fun increaseSpeakerVolume() {
speakerVolume++
println("Speaker volume increased to $speakerVolume.")
}
fun nextChannel() {
channelNumber++
println("Channel number increased to $channelNumber.")
}
}
- Na linha após a subclasse
SmartTvDevice
, defina uma subclasseSmartLightDevice
para estender a superclasseSmartDevice
:
class SmartLightDevice(deviceName: String, deviceCategory: String) :
SmartDevice(name = deviceName, category = deviceCategory) {
}
- No corpo da subclasse
SmartLightDevice
, defina uma propriedadebrightnessLevel
atribuída a um valor0
com uma função setter que especifica um intervalo0..100
:
class SmartLightDevice(deviceName: String, deviceCategory: String) :
SmartDevice(name = deviceName, category = deviceCategory) {
var brightnessLevel = 0
set(value) {
if (value in 0..100) {
field = value
}
}
}
- Defina um método
increaseBrightness()
para aumentar o brilho da luz e mostrar uma string"Brightness
increased
to
$brightnessLevel."
:
class SmartLightDevice(deviceName: String, deviceCategory: String) :
SmartDevice(name = deviceName, category = deviceCategory) {
var brightnessLevel = 0
set(value) {
if (value in 0..100) {
field = value
}
}
fun increaseBrightness() {
brightnessLevel++
println("Brightness increased to $brightnessLevel.")
}
}
Relações entre classes
Com a herança, você estabelece uma relação entre duas classes com algo conhecido como relação IS-A. Um objeto também é uma instância da classe de que ele herda. Em uma relação HAS-A, um objeto pode ser proprietário de uma instância de outra classe sem ser realmente uma instância da classe em si. Neste diagrama, há uma visão geral dessas relações:
Relações IS-A
Quando você especifica uma relação IS-A entre a superclasse SmartDevice
e a subclasse SmartTvDevice
, isso significa que a subclasse SmartTvDevice
pode fazer tudo que a superclasse SmartDevice
faz. A relação é unidirecional. Portanto, você pode dizer que todas as smart TVs são dispositivos inteligentes, mas não é possível dizer que todos os dispositivos são smart TVs. Confira a representação do código para uma relação IS-A neste snippet:
// Smart TV IS-A smart device.
class SmartTvDevice : SmartDevice() {
}
Não use a herança apenas para reutilizar o código. Antes de decidir, verifique se as duas classes estão relacionadas entre si. Se estiverem, verifique se realmente estão qualificadas para a relação IS-A. Considere se a subclasse realmente é igual à superclasse. Por exemplo, o Android é um sistema operacional.
Relações HAS-A
Uma relação HAS-A é outra maneira de especificar o relacionamento entre duas classes. Por exemplo, talvez você use a smart TV na sua casa. Nesse caso, há uma relação entre a smart TV e a casa. A casa contém um dispositivo inteligente ou, em outras palavras, tem um dispositivo inteligente. A relação HAS-A entre duas classes também é conhecida como composição.
Até o momento, você criou alguns dispositivos inteligentes. Agora, você vai criar a classe SmartHome
que contém dispositivos inteligentes. Você pode usar a classe SmartHome
para interagir com os dispositivos inteligentes.
Use uma relação HAS-A para definir uma classe SmartHome
:
- Entre a classe
SmartLightDevice
e a funçãomain()
, defina uma classeSmartHome
:
class SmartLightDevice(deviceName: String, deviceCategory: String) :
SmartDevice(name = deviceName, category = deviceCategory) {
...
}
class SmartHome {
}
fun main() {
...
}
- No construtor da classe
SmartHome
, use a palavra-chaveval
para criar uma propriedadesmartTvDevice
do tipoSmartTvDevice
:
// The SmartHome class HAS-A smart TV device.
class SmartHome(val smartTvDevice: SmartTvDevice) {
}
- No corpo da classe
SmartHome
, defina um métodoturnOnTv()
que chame o métodoturnOn()
na propriedadesmartTvDevice
:
class SmartHome(val smartTvDevice: SmartTvDevice) {
fun turnOnTv() {
smartTvDevice.turnOn()
}
}
- Na linha após o método
turnOnTv()
, defina um métodoturnOffTv()
que chame o métodoturnOff()
na propriedadesmartTvDevice
:
class SmartHome(val smartTvDevice: SmartTvDevice) {
fun turnOnTv() {
smartTvDevice.turnOn()
}
fun turnOffTv() {
smartTvDevice.turnOff()
}
}
- Na linha após o método
turnOffTv()
, defina um métodoincreaseTvVolume()
que chame o métodoincreaseSpeakerVolume()
na propriedadesmartTvDevice
e, em seguida, defina um métodochangeTvChannelToNext()
que chame o métodonextChannel()
na propriedadesmartTvDevice
:
class SmartHome(val smartTvDevice: SmartTvDevice) {
fun turnOnTv() {
smartTvDevice.turnOn()
}
fun turnOffTv() {
smartTvDevice.turnOff()
}
fun increaseTvVolume() {
smartTvDevice.increaseSpeakerVolume()
}
fun changeTvChannelToNext() {
smartTvDevice.nextChannel()
}
}
- No construtor da classe
SmartHome
, mova o parâmetro de propriedadesmartTvDevice
para a própria linha, seguido por uma vírgula:
class SmartHome(
val smartTvDevice: SmartTvDevice,
) {
...
}
- Na linha após a propriedade
smartTvDevice
, use a palavra-chaveval
para definir uma propriedadesmartLightDevice
do tipoSmartLightDevice
:
// The SmartHome class HAS-A smart TV device and smart light.
class SmartHome(
val smartTvDevice: SmartTvDevice,
val smartLightDevice: SmartLightDevice
) {
...
}
- No corpo de
SmartHome
, defina um métodoturnOnLight()
que chame o métodoturnOn()
no objetosmartLightDevice
e um métodoturnOffLight()
que chama o métodoturnOff()
no objetosmartLightDevice
:
class SmartHome(
val smartTvDevice: SmartTvDevice,
val smartLightDevice: SmartLightDevice
) {
...
fun changeTvChannelToNext() {
smartTvDevice.nextChannel()
}
fun turnOnLight() {
smartLightDevice.turnOn()
}
fun turnOffLight() {
smartLightDevice.turnOff()
}
}
- Na linha após o método
turnOffLight()
, defina um métodoincreaseLightBrightness()
que chame o métodoincreaseBrightness()
na propriedadesmartLightDevice
:
class SmartHome(
val smartTvDevice: SmartTvDevice,
val smartLightDevice: SmartLightDevice
) {
...
fun changeTvChannelToNext() {
smartTvDevice.nextChannel()
}
fun turnOnLight() {
smartLightDevice.turnOn()
}
fun turnOffLight() {
smartLightDevice.turnOff()
}
fun increaseLightBrightness() {
smartLightDevice.increaseBrightness()
}
}
- Na linha após o método
increaseLightBrightness()
, defina um métodoturnOffAllDevices()
que chame os métodosturnOffTv()
eturnOffLight()
:
class SmartHome(
val smartTvDevice: SmartTvDevice,
val smartLightDevice: SmartLightDevice
) {
...
fun turnOffAllDevices() {
turnOffTv()
turnOffLight()
}
}
Substituir métodos da superclasse nas subclasses
Como discutido anteriormente, mesmo que os recursos de ativação e desativação tenham suporte de todos os dispositivos inteligentes, a forma como eles fazem isso é diferente. Para oferecer esse comportamento específico ao dispositivo, substitua os métodos turnOn()
e turnOff()
definidos na superclasse. Substituir significa interceptar a ação para ter controle manual. Quando você substitui um método, o método na subclasse interrompe a execução do definido na superclasse e fornece a própria execução.
Substitua os métodos turnOn()
e turnOff()
da classe SmartDevice
:
- No corpo da superclasse
SmartDevice
antes da palavra-chavefun
de cada método, adicione uma palavra-chaveopen
:
open class SmartDevice(val name: String, val category: String) {
var deviceStatus = "online"
open fun turnOn() {
// function body
}
open fun turnOff() {
// function body
}
}
- No corpo da classe
SmartLightDevice
, defina um métodoturnOn()
com um corpo vazio:
class SmartLightDevice(deviceName: String, deviceCategory: String) :
SmartDevice(name = deviceName, category = deviceCategory) {
var brightnessLevel = 0
set(value) {
if (value in 0..100) {
field = value
}
}
fun increaseBrightness() {
brightnessLevel++
println("Brightness increased to $brightnessLevel.")
}
fun turnOn() {
}
}
- No corpo do método
turnOn()
, defina o métododeviceStatus
como a string "on
", defina a propriedadebrightnessLevel
como um valor2
e adicione uma instruçãoprintln()
. Em seguida, transmita uma string"$name
turned
on.
The
brightness
level
is
$brightnessLevel."
a ela:
fun turnOn() {
deviceStatus = "on"
brightnessLevel = 2
println("$name turned on. The brightness level is $brightnessLevel.")
}
- No corpo da classe
SmartLightDevice
, defina um métodoturnOff()
com um corpo vazio:
class SmartLightDevice(deviceName: String, deviceCategory: String) :
SmartDevice(name = deviceName, category = deviceCategory) {
var brightnessLevel = 0
set(value) {
if (value in 0..100) {
field = value
}
}
fun increaseBrightness() {
brightnessLevel++
println("Brightness increased to $brightnessLevel.")
}
fun turnOn() {
deviceStatus = "on"
brightnessLevel = 2
println("$name turned on. The brightness level is $brightnessLevel.")
}
fun turnOff() {
}
}
- No corpo do método
turnOff()
, defina o métododeviceStatus
como a string "off
", defina a propriedadebrightnessLevel
para um valor0
e adicione uma instruçãoprintln()
. Em seguida, transmita uma string"Smart
Light
turned
off"
a ela:
class SmartLightDevice(deviceName: String, deviceCategory: String) :
SmartDevice(name = deviceName, category = deviceCategory) {
var brightnessLevel = 0
set(value) {
if (value in 0..100) {
field = value
}
}
fun increaseBrightness() {
brightnessLevel++
println("Brightness increased to $brightnessLevel.")
}
fun turnOn() {
deviceStatus = "on"
brightnessLevel = 2
println("$name turned on. The brightness level is $brightnessLevel.")
}
fun turnOff() {
deviceStatus = "off"
brightnessLevel = 0
println("Smart Light turned off")
}
}
- Na subclasse
SmartLightDevice
antes da palavra-chavefun
dos métodosturnOn()
eturnOff()
, adicione a palavra-chaveoverride
:
class SmartLightDevice(deviceName: String, deviceCategory: String) :
SmartDevice(name = deviceName, category = deviceCategory) {
var brightnessLevel = 0
set(value) {
if (value in 0..100) {
field = value
}
}
fun increaseBrightness() {
brightnessLevel++
println("Brightness increased to $brightnessLevel.")
}
override fun turnOn() {
deviceStatus = "on"
brightnessLevel = 2
println("$name turned on. The brightness level is $brightnessLevel.")
}
override fun turnOff() {
deviceStatus = "off"
brightnessLevel = 0
println("Smart Light turned off")
}
}
A palavra-chave override
diz ao ambiente de execução do Kotlin para executar o código incluído no método definido na subclasse.
- No corpo da classe
SmartTvDevice
, defina um métodoturnOn()
com um corpo vazio:
class SmartTvDevice(deviceName: String, deviceCategory: String) : SmartDevice(name = deviceName, category = deviceCategory) {
var speakerVolume = 2
set(value) {
if (value in 0..100) {
field = value
}
}
var channelNumber = 1
set(value) {
if (value in 0..200) {
field = value
}
}
fun increaseSpeakerVolume() {
speakerVolume++
println("Speaker volume increased to $speakerVolume.")
}
fun nextChannel() {
channelNumber++
println("Channel number increased to $channelNumber.")
}
fun turnOn() {
}
}
- No corpo do método
turnOn()
, defina a propriedadedeviceStatus
como a string "on
" e adicione uma instruçãoprintln()
. Em seguida, transmita uma string"$name is turned on. Speaker volume is set to $speakerVolume and channel number is " + "set to $channelNumber."
a ela:
class SmartTvDevice(deviceName: String, deviceCategory: String) : SmartDevice(name = deviceName, category = deviceCategory) {
...
fun turnOn() {
deviceStatus = "on"
println(
"$name is turned on. Speaker volume is set to $speakerVolume and channel number is " +
"set to $channelNumber."
)
}
}
- No corpo da classe
SmartTvDevice
, depois do métodoturnOn()
, defina um métodoturnOff()
com um corpo vazio:
class SmartTvDevice(deviceName: String, deviceCategory: String) : SmartDevice(name = deviceName, category = deviceCategory) {
...
fun turnOn() {
...
}
fun turnOff() {
}
}
- No corpo do método
turnOff()
defina a propriedadedeviceStatus
como a string "off
" e adicione uma instruçãoprintln()
. Em seguida, transmita uma string"$name
turned
off"
a ela:
class SmartTvDevice(deviceName: String, deviceCategory: String) : SmartDevice(name = deviceName, category = deviceCategory) {
...
fun turnOn() {
...
}
fun turnOff() {
deviceStatus = "off"
println("$name turned off")
}
}
- Na classe
SmartTvDevice
antes da palavra-chavefun
dos métodosturnOn()
eturnOff()
, adicione a palavra-chaveoverride
:
class SmartTvDevice(deviceName: String, deviceCategory: String) :
SmartDevice(name = deviceName, category = deviceCategory) {
var speakerVolume = 2
set(value) {
if (value in 0..100) {
field = value
}
}
var channelNumber = 1
set(value) {
if (value in 0..200) {
field = value
}
}
fun increaseSpeakerVolume() {
speakerVolume++
println("Speaker volume increased to $speakerVolume.")
}
fun nextChannel() {
channelNumber++
println("Channel number increased to $channelNumber.")
}
override fun turnOn() {
deviceStatus = "on"
println(
"$name is turned on. Speaker volume is set to $speakerVolume and channel number is " +
"set to $channelNumber."
)
}
override fun turnOff() {
deviceStatus = "off"
println("$name turned off")
}
}
- Na função
main()
, use a palavra-chavevar
para definir uma variávelsmartDevice
do tipoSmartDevice
que instancia um objetoSmartTvDevice
que usa um argumento"Android
TV"
e um"Entertainment"
:
fun main() {
var smartDevice: SmartDevice = SmartTvDevice("Android TV", "Entertainment")
}
- Na linha após a variável
smartDevice
, chame o métodoturnOn()
no objetosmartDevice
:
fun main() {
var smartDevice: SmartDevice = SmartTvDevice("Android TV", "Entertainment")
smartDevice.turnOn()
}
- Execute o código.
A saída vai ser assim:
Android TV is turned on. Speaker volume is set to 2 and channel number is set to 1.
- Na linha após a chamada para o método
turnOn()
, reatribua a variávelsmartDevice
para instanciar uma classeSmartLightDevice
que usa um argumento"Google
Light"
e um argumento"Utility"
. Em seguida, chame o métodoturnOn()
na referência do objetosmartDevice
:
fun main() {
var smartDevice: SmartDevice = SmartTvDevice("Android TV", "Entertainment")
smartDevice.turnOn()
smartDevice = SmartLightDevice("Google Light", "Utility")
smartDevice.turnOn()
}
- Execute o código.
A saída vai ser assim:
Android TV is turned on. Speaker volume is set to 2 and channel number is set to 1. Google Light turned on. The brightness level is 2.
Esse é um exemplo de polimorfismo. O código chama o método turnOn()
em uma variável do tipo SmartDevice
e, dependendo do valor real da variável, diferentes implementações do método turnOn()
podem ser executadas.
Reutilizar o código de superclasse em subclasses com a palavra-chave super
Ao observar mais de perto os métodos turnOn()
e turnOff()
, há uma semelhança na forma como a variável deviceStatus
é atualizada sempre que os métodos são chamados nas subclasses SmartTvDevice
e SmartLightDevice
: o código é duplicado. É possível atualizar o status na classe SmartDevice
para reutilizar o código.
Para chamar o método substituído na superclasse da subclasse, use a palavra-chave super
. Chamar um método da superclasse é semelhante a chamar o método de fora dela. Em vez de usar um operador .
entre o objeto e o método, você precisa usar a palavra-chave super
que diz ao compilador Kotlin para chamar o método na superclasse em vez de na subclasse.
A sintaxe para chamar o método na superclasse começa com uma palavra-chave super
seguida pelo operador .
, pelo nome da função e por um conjunto de parênteses. Se aplicável, os parênteses podem incluir os argumentos. Confira a sintaxe neste diagrama:
Reutilize o código da superclasse SmartDevice
:
- Remova as instruções
println()
dos métodosturnOn()
eturnOff()
e mova o código duplicado das subclassesSmartTvDevice
eSmartLightDevice
para a superclasseSmartDevice
:
open class SmartDevice(val name: String, val category: String) {
var deviceStatus = "online"
open fun turnOn() {
deviceStatus = "on"
}
open fun turnOff() {
deviceStatus = "off"
}
}
- Use a palavra-chave
super
para chamar os métodos da classeSmartDevice
nas subclassesSmartTvDevice
eSmartLightDevice
:
class SmartTvDevice(deviceName: String, deviceCategory: String) :
SmartDevice(name = deviceName, category = deviceCategory) {
var speakerVolume = 2
set(value) {
if (value in 0..100) {
field = value
}
}
var channelNumber = 1
set(value) {
if (value in 0..200) {
field = value
}
}
fun increaseSpeakerVolume() {
speakerVolume++
println("Speaker volume increased to $speakerVolume.")
}
fun nextChannel() {
channelNumber++
println("Channel number increased to $channelNumber.")
}
override fun turnOn() {
super.turnOn()
println(
"$name is turned on. Speaker volume is set to $speakerVolume and channel number is " +
"set to $channelNumber."
)
}
override fun turnOff() {
super.turnOff()
println("$name turned off")
}
}
class SmartLightDevice(deviceName: String, deviceCategory: String) :
SmartDevice(name = deviceName, category = deviceCategory) {
var brightnessLevel = 0
set(value) {
if (value in 0..100) {
field = value
}
}
fun increaseBrightness() {
brightnessLevel++
println("Brightness increased to $brightnessLevel.")
}
override fun turnOn() {
super.turnOn()
brightnessLevel = 2
println("$name turned on. The brightness level is $brightnessLevel.")
}
override fun turnOff() {
super.turnOff()
brightnessLevel = 0
println("Smart Light turned off")
}
}
Substituir propriedades da superclasse nas subclasses
Assim como os métodos, também é possível substituir as propriedades seguindo as mesmas etapas.
Substitua a propriedade deviceType
:
- Na superclasse
SmartDevice
na linha após a propriedadedeviceStatus
, use as palavras-chaveopen
eval
para definir uma propriedadedeviceType
definida que tem uma string"unknown"
:
open class SmartDevice(val name: String, val category: String) {
var deviceStatus = "online"
open val deviceType = "unknown"
...
}
- Na classe
SmartTvDevice
, use as palavras-chaveoverride
eval
para definir uma propriedadedeviceType
que tem uma string"Smart
TV"
:
class SmartTvDevice(deviceName: String, deviceCategory: String) :
SmartDevice(name = deviceName, category = deviceCategory) {
override val deviceType = "Smart TV"
...
}
- Na classe
SmartLightDevice
, use as palavras-chaveoverride
eval
para definir uma propriedadedeviceType
que tem uma string"Smart
Light"
:
class SmartLightDevice(deviceName: String, deviceCategory: String) :
SmartDevice(name = deviceName, category = deviceCategory) {
override val deviceType = "Smart Light"
...
}
8. Modificadores de visibilidade
Os modificadores de visibilidade têm um papel importante para o encapsulamento:
- Em uma classe, eles permitem esconder propriedades e métodos de acessos não autorizados fora da classe.
- Em um pacote, eles permitem ocultar as classes e interfaces de acesso não autorizado fora do pacote.
O Kotlin oferece quatro modificadores de visibilidade:
public
: modificador de visibilidade padrão. Torna a declaração acessível em qualquer lugar. As propriedades e os métodos que você quer usar fora da classe são marcados como públicos.private
: torna a declaração acessível no mesmo arquivo de classe ou origem.
Provavelmente, há algumas propriedades e métodos que são usados apenas dentro de uma classe e que você não quer que outras usem. Essas propriedades e métodos podem ser marcados com o modificador de visibilidade private
para garantir que outra classe não os acesse acidentalmente.
protected
: torna a declaração acessível em subclasses. Além das subclasses, as propriedades e os métodos usados na classe que os define são marcados com o modificador de visibilidadeprotected
.internal
: torna a declaração acessível no mesmo módulo. O modificador interno é semelhante ao particular, mas você pode acessar propriedades e métodos internos de fora da classe, desde que eles sejam acessados no mesmo módulo.
Quando você define uma classe, ela é visível publicamente e pode ser acessada por qualquer pacote que a importe, o que significa que ela é pública por padrão, a menos que você especifique um modificador de visibilidade. Da mesma forma, quando você define ou declara propriedades e métodos na classe, por padrão eles podem ser acessados fora dela pelo objeto da classe. É fundamental definir uma visibilidade adequada para o código, principalmente para ocultar propriedades e métodos que outras classes não precisam acessar.
Por exemplo, considere o que um motorista pode acessar em um carro. As especificações sobre quais partes compõem o carro e como ele funciona internamente ficam ocultas por padrão. O objetivo é que operar um carro seja algo intuitivo. O ideal é que o carro não tenha uma operação tão complexa quanto uma aeronave comercial, da mesma forma que é ideal que outro desenvolvedor ou você no futuro não confunda as propriedades e os métodos de uma classe.
Os modificadores de visibilidade ajudam a mostrar as partes relevantes do código a outras classes do projeto e garantem que a implementação não seja usada acidentalmente. Isso torna o código fácil de entender e menos propenso a bugs.
O modificador de visibilidade precisa ser colocado antes da sintaxe da declaração quando você for declarar a classe, o método ou as propriedades, como mostrado no diagrama:
Especificar um modificador de visibilidade para propriedades
A sintaxe para especificar um modificador de visibilidade para uma propriedade começa com o modificador private
, protected
ou internal
, seguido pela sintaxe que define uma propriedade. Confira a sintaxe neste diagrama:
Por exemplo, confira como tornar a propriedade deviceStatus
particular neste snippet de código:
open class SmartDevice(val name: String, val category: String) {
...
private var deviceStatus = "online"
...
}
Também é possível definir os modificadores de visibilidade para funções setter. O modificador é colocado antes da palavra-chave set
. Confira a sintaxe neste diagrama:
Para a classe SmartDevice
, é necessário que os objetos de classe possam ler o valor da propriedade deviceStatus
fora da classe. No entanto, somente a classe e os filhos dela podem atualizar ou gravar o valor. Para implementar esse requisito, é necessário usar o modificador protected
na função set()
da propriedade deviceStatus
.
Use o modificador protected
na função set()
da propriedade deviceStatus
:
- Na propriedade
deviceStatus
da superclasseSmartDevice
, adicione o modificadorprotected
à funçãoset()
:
open class SmartDevice(val name: String, val category: String) {
...
var deviceStatus = "online"
protected set(value) {
field = value
}
...
}
Você não está realizando ações ou verificações na função set()
. Você está atribuindo o parâmetro value
à variável field
. Como aprendeu anteriormente, isso é semelhante à implementação padrão para setters de propriedade. É possível omitir os parênteses e o corpo da função set()
neste caso:
open class SmartDevice(val name: String, val category: String) {
...
var deviceStatus = "online"
protected set
...
}
- Na subclasse
SmartHome
, defina um conjunto de propriedadesdeviceTurnOnCount
com um valor0
usando uma função setter particular:
class SmartHome(
val smartTvDevice: SmartTvDevice,
val smartLightDevice: SmartLightDevice
) {
var deviceTurnOnCount = 0
private set
...
}
- Adicione a propriedade
deviceTurnOnCount
seguida pelo operador aritmético++
aos métodosturnOnTv()
eturnOnLight()
. Depois, adicione a propriedadedeviceTurnOnCount
seguida pelo operador aritmético--
aos métodosturnOffTv()
eturnOffLight()
:
class SmartHome(
val smartTvDevice: SmartTvDevice,
val smartLightDevice: SmartLightDevice
) {
var deviceTurnOnCount = 0
private set
fun turnOnTv() {
deviceTurnOnCount++
smartTvDevice.turnOn()
}
fun turnOffTv() {
deviceTurnOnCount--
smartTvDevice.turnOff()
}
...
fun turnOnLight() {
deviceTurnOnCount++
smartLightDevice.turnOn()
}
fun turnOffLight() {
deviceTurnOnCount--
smartLightDevice.turnOff()
}
...
}
Modificadores de visibilidade para métodos
A sintaxe para especificar um modificador de visibilidade de método começa com os modificadores private
, protected
ou internal
, seguidos pela sintaxe que define um método. Confira a sintaxe neste diagrama:
Por exemplo, este snippet de código mostra como especificar um modificador protected
para o método nextChannel()
na classe SmartTvDevice
:
class SmartTvDevice(deviceName: String, deviceCategory: String) :
SmartDevice(name = deviceName, category = deviceCategory) {
...
protected fun nextChannel() {
channelNumber++
println("Channel number increased to $channelNumber.")
}
...
}
Modificadores de visibilidade para construtores
A sintaxe para especificar um modificador de visibilidade de construtor é semelhante à definição do construtor principal, mas tem algumas diferenças:
- O modificador é especificado após o nome da classe, mas antes da palavra-chave
constructor
. - Se você precisar especificar o modificador do construtor principal, é necessário manter a palavra-chave
constructor
e os parênteses mesmo quando não houver parâmetros.
Confira a sintaxe neste diagrama:
Por exemplo, este snippet de código mostra como adicionar um modificador protected
ao construtor SmartDevice
:
open class SmartDevice protected constructor (val name: String, val category: String) {
...
}
Modificadores de visibilidade para classes
A sintaxe para especificar um modificador de visibilidade de classe começa com os modificadores private
, protected
ou internal
, seguidos pela sintaxe que define uma classe. Confira a sintaxe neste diagrama:
Por exemplo, este snippet de código mostra como especificar um modificador internal
para a classe SmartDevice
:
internal open class SmartDevice(val name: String, val category: String) {
...
}
O ideal é ter uma visibilidade restrita das propriedades e métodos. Portanto, use o modificador private
(particular) sempre que possível nas declarações. Se não for possível manter a visibilidade particular, use o modificador protected
. Se não for possível manter a visibilidade protegida, use o modificador internal
. Se não for possível manter a visibilidade interna, use o modificador public
.
Especificar os modificadores de visibilidade adequados
Esta tabela ajuda a determinar os modificadores de visibilidade adequados com base naquilo que pode ter acesso à propriedade ou aos métodos de uma classe ou um construtor:
Modificador | Acessível na mesma classe | Acessível na subclasse | Acessível no mesmo módulo | Acessível fora do módulo |
| ✔ | 𝗫 | 𝗫 | 𝗫 |
| ✔ | ✔ | 𝗫 | 𝗫 |
| ✔ | ✔ | ✔ | 𝗫 |
| ✔ | ✔ | ✔ | ✔ |
Na subclasse SmartTvDevice
, não permita que as propriedades speakerVolume
e channelNumber
sejam controladas de fora da classe. Apenas os métodos increaseSpeakerVolume()
e nextChannel()
podem controlar essas propriedades.
Da mesma forma, na subclasse SmartLightDevice
, a propriedade brightnessLevel
precisa ser controlada apenas pelo método increaseLightBrightness()
.
Adicione os modificadores de visibilidade adequados às subclasses SmartTvDevice
e SmartLightDevice
:
- Na classe
SmartTvDevice
, adicione um modificador de visibilidadeprivate
às propriedadesspeakerVolume
echannelNumber
:
class SmartTvDevice(deviceName: String, deviceCategory: String) :
SmartDevice(name = deviceName, category = deviceCategory) {
private var speakerVolume = 2
set(value) {
if (value in 0..100) {
field = value
}
}
private var channelNumber = 1
set(value) {
if (value in 0..200) {
field = value
}
}
...
}
- Na classe
SmartLightDevice
, adicione um modificadorprivate
à propriedadebrightnessLevel
:
class SmartLightDevice(deviceName: String, deviceCategory: String) :
SmartDevice(name = deviceName, category = deviceCategory) {
...
private var brightnessLevel = 0
set(value) {
if (value in 0..100) {
field = value
}
}
...
}
9. Definir delegados de propriedade
Na seção anterior, você aprendeu que as propriedades no Kotlin usam um campo de apoio para armazenar os valores na memória. Use o identificador field
para referenciar esse campo.
No que foi escrito até agora, você pode analisar o código duplicado para verificar se os valores estão dentro do intervalo das propriedades speakerVolume
, channelNumber
e brightnessLevel
nas classes SmartTvDevice
e SmartLightDevice
. Você pode reutilizar o código de verificação de intervalo na função setter com delegados. Não é necessário usar um campo e uma função getter e setter para gerenciar o valor, já que o delegado pode fazer isso.
A sintaxe para criar delegados de propriedade começa com a declaração de uma variável seguida pela palavra-chave by
e pelo objeto delegado que processa as funções setter e getter da propriedade. Confira a sintaxe neste diagrama:
Antes de implementar a classe a que você pode delegar a implementação, é necessário conhecer interfaces. Uma interface é um protocolo que precisa ser aderido pelas classes que a implementam. O foco da interface é o que fazer e não como fazer. Em resumo, uma interface ajuda você a alcançar a abstração.
Por exemplo, antes de construir uma casa, você fala para o arquiteto o que quer. Você quer um quarto, um quarto para crianças, uma sala de estar, uma cozinha e alguns banheiros. Isso significa que você diz o que quer, e o arquiteto diz como fazer isso. Este diagrama mostra a sintaxe para criar uma interface:
Você já aprendeu a estender uma classe e substituir a funcionalidade dela. A classe implementa as interfaces. A classe fornece detalhes de implementação dos métodos e propriedades declarados na interface. Para criar o delegado, você vai fazer algo semelhante na interface ReadWriteProperty
(link em inglês). Você vai aprender mais sobre interfaces na próxima unidade.
Para criar a classe delegada do tipo var
, é necessário implementar a interface ReadWriteProperty
. Também é necessário implementar a interface ReadOnlyProperty
para o tipo val
(links em inglês).
Crie o delegado para o tipo var
:
- Antes da função
main()
, crie uma classeRangeRegulator
que implemente a interfaceReadWriteProperty<Any?,
Int>
:
class RangeRegulator() : ReadWriteProperty<Any?, Int> {
}
fun main() {
...
}
Não se preocupe com os sinais de "maior e menor que" e o conteúdo dentro deles. Eles representam tipos genéricos, e você vai aprender sobre eles na próxima unidade.
- No construtor principal da classe
RangeRegulator
, adicione um parâmetroinitialValue
e propriedades particularesminValue
emaxValue
, todos os elementos do tipoInt
:
class RangeRegulator(
initialValue: Int,
private val minValue: Int,
private val maxValue: Int
) : ReadWriteProperty<Any?, Int> {
}
- No corpo da classe
RangeRegulator
, substitua os métodosgetValue()
esetValue()
:
class RangeRegulator(
initialValue: Int,
private val minValue: Int,
private val maxValue: Int
) : ReadWriteProperty<Any?, Int> {
override fun getValue(thisRef: Any?, property: KProperty<*>): Int {
}
override fun setValue(thisRef: Any?, property: KProperty<*>, value: Int) {
}
}
Esses métodos atuam como funções getter e setter das propriedades.
- Na linha antes da classe
SmartDevice
, importe as interfacesReadWriteProperty
eKProperty
:
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
open class SmartDevice(val name: String, val category: String) {
...
}
...
class RangeRegulator(
initialValue: Int,
private val minValue: Int,
private val maxValue: Int
) : ReadWriteProperty<Any?, Int> {
override fun getValue(thisRef: Any?, property: KProperty<*>): Int {
}
override fun setValue(thisRef: Any?, property: KProperty<*>, value: Int) {
}
}
...
- Na classe
RangeRegulator
, na linha antes do métodogetValue()
, defina uma propriedadefieldData
e a inicialize com o parâmetroinitialValue
:
class RangeRegulator(
initialValue: Int,
private val minValue: Int,
private val maxValue: Int
) : ReadWriteProperty<Any?, Int> {
var fieldData = initialValue
override fun getValue(thisRef: Any?, property: KProperty<*>): Int {
}
override fun setValue(thisRef: Any?, property: KProperty<*>, value: Int) {
}
}
Essa propriedade funciona como o campo de apoio da variável.
- No corpo do método
getValue()
, retorne a propriedadefieldData
:
class RangeRegulator(
initialValue: Int,
private val minValue: Int,
private val maxValue: Int
) : ReadWriteProperty<Any?, Int> {
var fieldData = initialValue
override fun getValue(thisRef: Any?, property: KProperty<*>): Int {
return fieldData
}
override fun setValue(thisRef: Any?, property: KProperty<*>, value: Int) {
}
}
- No corpo do método
setValue()
, verifique se o parâmetrovalue
que está sendo atribuído está no intervalominValue..maxValue
antes de o atribuir à propriedadefieldData
:
class RangeRegulator(
initialValue: Int,
private val minValue: Int,
private val maxValue: Int
) : ReadWriteProperty<Any?, Int> {
var fieldData = initialValue
override fun getValue(thisRef: Any?, property: KProperty<*>): Int {
return fieldData
}
override fun setValue(thisRef: Any?, property: KProperty<*>, value: Int) {
if (value in minValue..maxValue) {
fieldData = value
}
}
}
- Na classe
SmartTvDevice
, use a classe delegada para definir as propriedadesspeakerVolume
echannelNumber
:
class SmartTvDevice(deviceName: String, deviceCategory: String) :
SmartDevice(name = deviceName, category = deviceCategory) {
override val deviceType = "Smart TV"
private var speakerVolume by RangeRegulator(initialValue = 2, minValue = 0, maxValue = 100)
private var channelNumber by RangeRegulator(initialValue = 1, minValue = 0, maxValue = 200)
...
}
- Na classe
SmartLightDevice
, use a classe delegada para definir a propriedadebrightnessLevel
:
class SmartLightDevice(deviceName: String, deviceCategory: String) :
SmartDevice(name = deviceName, category = deviceCategory) {
override val deviceType = "Smart Light"
private var brightnessLevel by RangeRegulator(initialValue = 0, minValue = 0, maxValue = 100)
...
}
10. Testar a solução
Você pode observar o código da solução neste snippet de código:
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
open class SmartDevice(val name: String, val category: String) {
var deviceStatus = "online"
protected set
open val deviceType = "unknown"
open fun turnOn() {
deviceStatus = "on"
}
open fun turnOff() {
deviceStatus = "off"
}
}
class SmartTvDevice(deviceName: String, deviceCategory: String) :
SmartDevice(name = deviceName, category = deviceCategory) {
override val deviceType = "Smart TV"
private var speakerVolume by RangeRegulator(initialValue = 2, minValue = 0, maxValue = 100)
private var channelNumber by RangeRegulator(initialValue = 1, minValue = 0, maxValue = 200)
fun increaseSpeakerVolume() {
speakerVolume++
println("Speaker volume increased to $speakerVolume.")
}
fun nextChannel() {
channelNumber++
println("Channel number increased to $channelNumber.")
}
override fun turnOn() {
super.turnOn()
println(
"$name is turned on. Speaker volume is set to $speakerVolume and channel number is " +
"set to $channelNumber."
)
}
override fun turnOff() {
super.turnOff()
println("$name turned off")
}
}
class SmartLightDevice(deviceName: String, deviceCategory: String) :
SmartDevice(name = deviceName, category = deviceCategory) {
override val deviceType = "Smart Light"
private var brightnessLevel by RangeRegulator(initialValue = 0, minValue = 0, maxValue = 100)
fun increaseBrightness() {
brightnessLevel++
println("Brightness increased to $brightnessLevel.")
}
override fun turnOn() {
super.turnOn()
brightnessLevel = 2
println("$name turned on. The brightness level is $brightnessLevel.")
}
override fun turnOff() {
super.turnOff()
brightnessLevel = 0
println("Smart Light turned off")
}
}
class SmartHome(
val smartTvDevice: SmartTvDevice,
val smartLightDevice: SmartLightDevice
) {
var deviceTurnOnCount = 0
private set
fun turnOnTv() {
deviceTurnOnCount++
smartTvDevice.turnOn()
}
fun turnOffTv() {
deviceTurnOnCount--
smartTvDevice.turnOff()
}
fun increaseTvVolume() {
smartTvDevice.increaseSpeakerVolume()
}
fun changeTvChannelToNext() {
smartTvDevice.nextChannel()
}
fun turnOnLight() {
deviceTurnOnCount++
smartLightDevice.turnOn()
}
fun turnOffLight() {
deviceTurnOnCount--
smartLightDevice.turnOff()
}
fun increaseLightBrightness() {
smartLightDevice.increaseBrightness()
}
fun turnOffAllDevices() {
turnOffTv()
turnOffLight()
}
}
class RangeRegulator(
initialValue: Int,
private val minValue: Int,
private val maxValue: Int
) : ReadWriteProperty<Any?, Int> {
var fieldData = initialValue
override fun getValue(thisRef: Any?, property: KProperty<*>): Int {
return fieldData
}
override fun setValue(thisRef: Any?, property: KProperty<*>, value: Int) {
if (value in minValue..maxValue) {
fieldData = value
}
}
}
fun main() {
var smartDevice: SmartDevice = SmartTvDevice("Android TV", "Entertainment")
smartDevice.turnOn()
smartDevice = SmartLightDevice("Google Light", "Utility")
smartDevice.turnOn()
}
A saída vai ser assim:
Android TV is turned on. Speaker volume is set to 2 and channel number is set to 1. Google Light turned on. The brightness level is 2.
11. Desafio
- Na classe
SmartDevice
, defina um métodoprintDeviceInfo()
que mostra uma string"Device
name:
$name,
category:
$category,
type:
$deviceType"
. - Na classe
SmartTvDevice
, defina um métododecreaseVolume()
para diminuir o volume e um métodopreviousChannel()
que navega até o canal anterior. - Na classe
SmartLightDevice
, defina um métododecreaseBrightness()
que diminui o brilho. - Na classe
SmartHome
, faça com que todas as ações possam ser realizadas apenas quando a propriedadedeviceStatus
de cada dispositivo estiver definida como uma string"on"
. Além disso, verifique se a propriedadedeviceTurnOnCount
foi atualizada corretamente.
Depois de concluir a implementação:
- Na classe
SmartHome
, defina um métododecreaseTvVolume()
,changeTvChannelToPrevious()
,printSmartTvInfo()
,printSmartLightInfo()
edecreaseLightBrightness()
. - Chame os métodos apropriados das classes
SmartTvDevice
eSmartLightDevice
na classeSmartHome
. - Na função
main()
, chame os métodos adicionados para testar.
12. Conclusão
Parabéns! Você aprendeu a definir classes e instanciar objetos. Também mostramos como criar delegados de propriedade e relações entre classes.
Resumo
- Há quatro princípios importantes de programação orientada a objetos: encapsulamento, abstração, herança e polimorfismo.
- As classes são definidas com a palavra-chave
class
e contêm propriedades e métodos. - As propriedades são semelhantes às variáveis, mas podem ter getters e setters personalizados.
- Um construtor especifica como instanciar objetos de uma classe.
- Você pode omitir a palavra-chave
constructor
na definição de um construtor principal. - A herança facilita a reutilização de códigos.
- A relação IS-A se refere à herança.
- A relação HAS-A se refere à composição.
- Os modificadores de visibilidade têm um papel importante para o encapsulamento.
- O Kotlin oferece quatro modificadores de visibilidade:
public
,private
,protected
einternal
. - Um delegado de propriedade permite reutilizar o código getter e setter em várias classes.
Saiba mais
- Delegados integrados (link em inglês)