Unit 2: Layouts
Get user input in an app: Part 1
Create a base class
介紹抽象類別建立
Class hierarchy of dwellings
In this codelab, you are going to build a Kotlin program that demonstrates how class hierarchies work, using dwellings (shelters in which people live) with floor space, stories, and residents as an example.
Below is a diagram of the class hierarchy you are going to build. At the root, you have a Dwelling
that specifies properties and functionality that is true for all dwellings, similar to a blueprint. You then have classes for a square cabin (SquareCabin
), round hut (RoundHut
), and a round tower (RoundTower
) which is a RoundHut
with multiple floors.
The classes that you will implement:
Dwelling
: a base class representing a non-specific shelter that holds information that is common to all dwellings.SquareCabin
: a square cabin made of wood with a square floor area.RoundHut
: a round hut that is made of straw with a circular floor area, and the parent ofRoundTower
.RoundTower
: a round tower made of stone with a circular floor area and multiple stories.
Create an abstract Dwelling class
Any class can be the base class of a class hierarchy or a parent of other classes.
An “abstract” class is a class that cannot be instantiated because it is not fully implemented. You can think of it as a sketch. A sketch incorporates the ideas and plans for something, but not usually enough information to build it. You use a sketch (abstract class) to create a blueprint (class) from which you build the actual object instance.
A common benefit of creating a superclass is to contain properties and functions that are common to all its subclasses. If the values of properties and implementations of functions are not known, make the class abstract. For example, Vegetables
have many properties common to all vegetables, but you can't create an instance of a non-specific vegetable, because you don't know, for example, its shape or color. So Vegetable
is an abstract class that leaves it up to the subclasses to determine specific details about each vegetable.
The declaration of an abstract class starts with the abstract
keyword.
Dwelling
is going to be an abstract class like Vegetable
. It is going to contain properties and functions that are common to many types of dwellings, but the exact values of properties and details of implementation of functions are not known.
abstract class Dwelling(){
}
Add a property for building material
In this Dwelling
class, you define things that are true for all dwellings, even if they may be different for different dwellings. All dwellings are made of some building material.
- Inside
Dwelling
, create abuildingMaterial
variable of typeString
to represent the building material. Since the building material won't change, useval
to make it an immutable variable.
val buildingMaterial: String
The buildingMaterial
property does not have a value. In fact, you CAN'T give it a value, because a non-specific building isn't made of anything specific. So, as the error message indicates, you can prefix the declaration of buildingMaterial
with the abstract
keyword, to indicate that it is not going to be defined here.
- Add the
abstract
keyword onto the variable definition.
abstract val buildingMaterial: String
- Run your code, and while it does not do anything, it now compiles without errors.
- Make an instance of
Dwelling
in themain()
function and run your code.
val dwelling = Dwelling()
- You’ll get an error because you cannot create an instance of the abstract
Dwelling
class.
Cannot create an instance of an abstract class
- Delete this incorrect code.
Your finished code so far:
abstract class Dwelling(){
abstract val buildingMaterial: String
}
Add a private property for number of residents
All dwellings will have a number of residents
who reside in the dwelling (which may be less than or equal to the capacity
), so define the residents
property in the Dwelling
superclass for all subclasses to inherit and use.
- You can make
residents
a parameter that is passed into the constructor of theDwelling
class. Theresidents
property is avar
, because the number of residents can change after the instance has been created.
abstract class Dwelling(private var residents: Int) {
Notice that the residents
property is marked with the private
keyword. Private is a visibility modifier in Kotlin meaning that the residents
property is only visible to (and can be used inside) this class. It cannot be accessed from elsewhere in your program. You can mark properties or methods with the private keyword. Otherwise when no visibility modifier is specified, the properties and methods are public
by default and accessible from other parts of your program. Since the number of people who live in a dwelling is usually private information (compared to information about the building material or the capacity of the building), this is a reasonable decision.
With both the capacity
of the dwelling and the number of current residents
defined, you can create a function hasRoom()
to determine whether there is room for another resident in the dwelling. You can define and implement the hasRoom()
function in the Dwelling
class because the formula for calculating whether there is room is the same for all dwellings. There is room in a Dwelling
if the number of residents
is less than the capacity
, and the function should return true
or false
based on this comparison.
- Add the
hasRoom()
function to theDwelling
class.
fun hasRoom(): Boolean {
return residents < capacity
}
- You can run this code and there should be no errors. It doesn’t do anything visible yet.
Your completed code should look like this:
abstract class Dwelling(private var residents: Int) {
abstract val buildingMaterial: String
abstract val capacity: Int
fun hasRoom(): Boolean {
return residents < capacity
}
}
練習1
Create subclasses (子類別)
Create a SquareCabin subclass
- Below the
Dwelling
class, create a class calledSquareCabin
.
class SquareCabin
- Next, you need to indicate that
SquareCabin
is related toDwelling
. In your code, you want to indicate thatSquareCabin
extends fromDwelling
(or is a subclass toDwelling)
becauseSquareCabin
will provide an implementation for the abstract parts ofDwelling
.
Indicate this inheritance relationship by adding a colon (:
) after the SquareCabin
class name, followed by a call to initialize the parent Dwelling
class. Don't forget to add parentheses after the Dwelling
class name.
class SquareCabin : Dwelling()
- When extending from a superclass, you must pass in the required parameters expected by the superclass.
Dwelling
requires the number ofresidents
as input. You could pass in a fixed number of residents like3
.
class SquareCabin : Dwelling(3)
However, you want your program to be more flexible and allow for a variable number of residents for SquareCabins
. Hence make residents
a parameter in the SquareCabin
class definition. Do not declare residents
as val,
because you are reusing a property already declared in the parent class Dwelling
.
class SquareCabin(residents: Int) : Dwelling(residents)
因宣告class會錯,需宣告抽象
When you declare abstract functions and variables, it is like a promise that you will give them values and implementations later. For a variable, it means that any subclass of that abstract class needs to give it a value. For a function, it means that any subclass needs to implement the function body.
In the Dwelling
class, you defined an abstract
variable buildingMaterial
. SquareCabin
is a subclass of Dwelling
, so it must provide a value for buildingMaterial
. Use the override
keyword to indicate that this property was defined in a parent class and is about to be overridden in this class.
- Inside the
SquareCabin
class,override
thebuildingMaterial
property and assign it the value"Wood"
. - Do the same for the
capacity
, saying 6 residents can live in aSquareCabin
.
class SquareCabin(residents: Int) : Dwelling(residents) {
override val buildingMaterial = "Wood"
override val capacity = 6
}
Your finished code should look like this.
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
}
To test your code, create an instance of SquareCabin
in your program.
Use SquareCabin
- Insert an empty
main()
function before theDwelling
andSquareCabin
class definitions.
fun main() {
}
abstract class Dwelling(private var residents: Int) {
...
}
class SquareCabin(residents: Int) : Dwelling(residents) {
...
}
- Within the
main()
function, create an instance ofSquareCabin
calledsquareCabin
with 6 residents. Add print statements for the building material, the capacity, and thehasRoom()
function.
fun main() {
val squareCabin = SquareCabin(6)
println("\nSquare Cabin\n============")
println("Capacity: ${squareCabin.capacity}")
println("Material: ${squareCabin.buildingMaterial}")
println("Has room? ${squareCabin.hasRoom()}")
}
Notice that the hasRoom()
function was not defined in the SquareCabin
class, but it was defined in the Dwelling
class. Since SquareCabin
is a subclass to Dwelling
class, the hasRoom()
function was inherited for free. The hasRoom()
function can now be called on all instances of SquareCabin
, as seen in the code snippet as squareCabin.hasRoom().
- Run your code, and it should print the following.
Square Cabin
============
Capacity: 6
Material: Wood
Has room? false
You created squareCabin
with 6
residents, which is equal to the capacity
, so hasRoom()
returns false
. You could experiment with initializing SquareCabin
with a smaller number of residents
, and when you run your program again, hasRoom()
should return true
.
Use with to simplify your code
In the println()
statements, every time you reference a property or function of squareCabin
, notice how you have to repeat squareCabin.
This becomes repetitive and can be a source of errors when you copy and paste print statements.
When you are working with a specific instance of a class and need to access multiple properties and functions of that instance, you can say "do all the following operations with this instance object" using a with
statement. Start with the keyword with
, followed by the instance name in parentheses, followed by curly braces which contain the operations you want to perform.
Use with to simplify your code (簡化,用with代替參數)
In the println()
statements, every time you reference a property or function of squareCabin
, notice how you have to repeat squareCabin.
This becomes repetitive and can be a source of errors when you copy and paste print statements.
When you are working with a specific instance of a class and need to access multiple properties and functions of that instance, you can say “do all the following operations with this instance object” using a with
statement. Start with the keyword with
, followed by the instance name in parentheses, followed by curly braces which contain the operations you want to perform.
code
fun main() {
//簡化版
val squareCabin = SquareCabin(6)
//no simplify add suarecabin add para
println(“\nSquare Cabin\n============”)
println(“Capacity: ${squareCabin.capacity}”)
println(“Material: ${squareCabin.buildingMaterial}”)
println(“Has room? ${squareCabin.hasRoom()}”)
//use with
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
}
Create a RoundHut subclass
- In the same way as the
SquareCabin
, add another subclass,RoundHut
, toDwelling
. - Override
buildingMaterial
and give it a value of"Straw"
. - Override
capacity
and set it to 4.
class RoundHut(residents: Int) : Dwelling(residents) {
override val buildingMaterial = "Straw"
override val capacity = 4
}
- In
main()
, create an instance ofRoundHut
with 3 residents.
val roundHut = RoundHut(3)
- Add the code below to print information about
roundHut
.
with(roundHut) {
println("\nRound Hut\n=========")
println("Material: ${buildingMaterial}")
println("Capacity: ${capacity}")
println("Has room? ${hasRoom()}")
}
- Run your code and your output for the whole program should be:
Square Cabin
============
Capacity: 6
Material: Wood
Has room? false
Round Hut
=========
Material: Straw
Capacity: 4
Has room? true
You now have a class hierarchy that looks like this, with Dwelling
as the root class and SquareCabin
and RoundHut
as subclasses of Dwelling
.
Create a RoundTower subclass
The final class in this class hierarchy is a round tower. You can think of a round tower as a round hut made of stone, with multiple stories. So, you can make RoundTower
a subclass of RoundHut
.
- Create a
RoundTower
class that is a subclass ofRoundHut
. Add theresidents
parameter to the constructor ofRoundTower
, and then pass that parameter to the constructor of theRoundHut
superclass. - Override the
buildingMaterial
to be"Stone"
. - Set the
capacity
to4
.
class RoundTower(residents: Int) : RoundHut(residents) {
override val buildingMaterial = "Stone"
override val capacity = 4
}
- Run this code and you get an error.
This type is final, so it cannot be inherited from
This error means that the RoundHut
class cannot be subclassed (or inherited from). By default, in Kotlin, classes are final and cannot be subclassed. You are only allowed to inherit from abstract
classes or classes that are marked with the open
keyword. Hence you need to mark the RoundHut
class with the open
keyword to allow it to be inherited from.
建立不同房子給人居住
fun main() {
//簡化版
val squareCabin = SquareCabin(6)
//no simplify add suarecabin add para
println(“\nSquare Cabin\n============”)
println(“Capacity: ${squareCabin.capacity}”)
println(“Material: ${squareCabin.buildingMaterial}”)
println(“Has room? ${squareCabin.hasRoom()}”)
//use with
with(squareCabin) {
println(“\nSquare Cabin\n============”)
println(“Capacity: ${capacity}”)
println(“Material: ${buildingMaterial}”)
println(“Has room? ${hasRoom()}”)
}
val roundHut = RoundHut(3)
with(roundHut) {
println(“\nRound Hut\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
}
//圓型屋子
class RoundHut(residents: Int) : Dwelling(residents) {
override val buildingMaterial = “Straw”
override val capacity = 4
}