In this demo, you’ll take a quick look at the difference between procedural programming and object-oriented programming. You’ll be working with the Kotlin playground which is an interactive environment to run Kotlin code on the web.
You can find this at “play.kotlinlang.org” To get the starter code, open up the starter folder for this lesson. Copy the content of the file and use it to replace the code in the Kotlin playground.
This Playground has one import statement:
import java.util.UUID;
TravelMode
class to generate a unique random ID for a travel mode. Its a Java class and you won’t actually be using it but this is just to demonstrate that Kotlin is interoperable with Java.
These are the two locations you’ll be using for this demo:
val lagos = Pair(6.465422, 3.406448)
val london = Pair(51.509865, -0.118092)
These are pairs that you’ll use to represent the coordinates. The first argument of the pair is the latitude, while the second argument is the longitude.
from
and to
values and use some average speed, probably assuming the travel mode is driving. For now, the function just returns a double value of 42.0.
computeTravelTime
function. Then update it to the following:
fun main() {
println(computeTravelTime(from = lagos, to = london)) // Updated code
}
from
and to
arguments of the computeTravelTime
function. For this demo, you’ll be using the println()
function when calling functions and methods so you can see their results in the Playground’s console.
Go ahead and run the code. You can see the result in the console.
Now, suppose the programmer decides or is told, to modify the function so it’s more accurate for other travel modes, like walking, cycling or public transport.
You could add some parameters for average speed and actual distance like so:
fun computeTravelTime(
from: Pair<Double, Double>,
to: Pair<Double, Double>,
averageSpeed: Double,
actualDistance: Double
): Double {
return actualDistance/averageSpeed
}
actualDistance
and averageSpeed
arguments to the call. You probably need to include some branching code to set these new parameter values for the different travel modes.
Later, you might want to fine-tune the driving calculation to allow for traffic level and the average time to find parking. But these values aren’t relevant to the other travel modes, so you’d need to call a different function.
computeTravelTime()
.
TravelMode
class. TravelMode
is a very general concept that covers walking, cycling, driving, and public transport.
class TravelMode(private val mode: String, val averageSpeed: Double) {
val id = UUID.randomUUID().toString()
mode
and averageSpeed
. A constructor is used to initialize a class. This is what you call when creating an object, as you’ll see shortly.
mode
could be walking, cycling, driving or public transport. After you create a TravelMode
object, the app’s code can’t access the mode
outside the class because it is marked as private
. You’ll check this for yourself soon.
averageSpeed
when you create a TravelMode
object. And the averageSpeed
is an estimate that depends on the mode and user, or time-specific factors like how fast the user can walk or cycle, or whether the driving is on city streets or highways.
TravelMode
class is the id
property, which must be a unique value for each instance, and UUID.randomUUID()
takes care of this without any fuss. You don’t need to know what its actual value is, it’ll just do its job quietly.
TravelMode
below the call to computeTravelTime
function like so:
val sammy = TravelMode(mode = "walking", averageSpeed = 4.5)
That’s 4.5 km/hr.
mode
:
println(sammy.mode)
mode
is marked as private
in the class definition.
sammy.mode
.
TravelMode
, look at the two methods:
fun actualDistance(from: Pair<Double, Double>, to: Pair<Double, Double>): Double {
// use relevant map info for each specific travel mode
throw IllegalArgumentException("Implement this method for ${mode}.")
}
fun computeTravelTime(from: Pair<Double, Double>, to: Pair<Double, Double>): Double {
return actualDistance(from, to)/averageSpeed
}
computeTravelTime()
looks just like the original procedural programming function, but it uses the actualDistance()
method, which uses data that’s specific to each travel mode, and the averageSpeed
that was used to instantiate the TravelMode
object. Everything you need for computeTravelTime()
is encapsulated in the object, and the app’s code just needs to provide the from
and to
locations.
actualDistance()
is a placeholder function. Having a method like this means you shouldn’t create instances of this class. Instead, you should define a subclass and override this method to suit objects of that type. It’s a kind of back-door way of creating an abstract class. More on abstract classes in a future lesson.
TravelMode
object:
sammy.actualDistance(lagos, london)
Click the run button to execute the Playground.
There’s an error flag, and, in the console, this message appears:
Exception in thread "main" java.lang.IllegalArgumentException: Implement this method for walking.
actualDistance()
to return the exact point-to-point distance.
For now, comment out or delete this method call.
actualDistance()
as a placeholder because you’re about to create some subclasses!
Add in a walking subclass like so:
class Walking(mode: String, averageSpeed: Double): TravelMode(mode, averageSpeed) {
}
:TravelMode
after the class name means Walking
is a TravelMode
. It inherits all the properties and methods of TravelMode
. This is inheritance at play. You can see the Walking
class has the properties from the TravelMode
parent class, and it can also have its additional properties unique to walking. The only thing you need to write is the method that isn’t implemented in TravelMode
, and that’s the actualDistance()
method.
But before you do that, go ahead and run the Playground. You’ll get this error in the console:
This type is final, so it cannot be inherited from
open
keyword.
Let’s do that now:
open class TravelMode(private val mode: String, val averageSpeed: Double) {
//...
open fun actualDistance(from: Pair<Double, Double>, to: Pair<Double, Double>): Double {
//...
}
//...
}
actualDistance()
method open
. You do this for the actualDistance()
method because you want to override and implement it in the Walking class. Let’s do that now.
Add in the following code:
override fun actualDistance(from: Pair<Double, Double>, to: Pair<Double, Double>): Double {
// use map info about walking paths, low-traffic roads, hills
return 42.0
}
override
keyword. Failure to do so would lead to an error because this new method would hide the one in the parent class.
Go ahead and instantiate your subclass and print it out to the console:
val tim = Walking(mode = "walking", averageSpeed = 6.0)
println(tim)
Tim is younger and taller than Sammy, so I guess he walks faster.
Then call that stubborn method that refused to run earlier:
println(tim.actualDistance(from = lagos, to = london))
computeTravelTime()
:
println(tim.computeTravelTime(from = lagos, to = london))
actualDistance
is 42, so travel time is 7 hours.
Also, you can see the walking object printed in the console with some strange values. That’s the hashcode of the object, and a hashcode is just a numeric representation of the contents of an object.
Now, go ahead and create one more subclass for the driving travel mode:
class Driving(mode: String, averageSpeed: Double): TravelMode(mode, averageSpeed) {
override fun actualDistance(from: Pair<Double, Double>, to: Pair<Double, Double>): Double {
// use map info about roads, tollways
return 57.0
}
}
57.0 is another placeholder value, different from Walking’s 42.0.
Driving
object:
val car = Driving(mode = "driving", averageSpeed = 50.0)
averageSpeed
, as much of the distance is on a highway.
And compute its travel time:
val hours = car.computeTravelTime(from = lagos, to = london)
println(car)
println("Hours: "+ hours)
Run the Playground.
Walking
object’s travel time, because it’s using its own version of actualDistance()
and averageSpeed
. That’s polymorphism in action! You could say the actualDistance()
behavior exists in different forms depending on the type of travel mode.
Driving
works the same as Walking
. How do you set it up to use traffic level and parking time?
computeTravelTime()
, you overload it. That is, you add parameters to create a different function signature. A Driving
object can call this version of computeTravelTime()
, but no other subclass type can.
Let’s add that in:
fun computeTravelTime(from: Pair<Double, Double>,
to: Pair<Double, Double>,
traffic: Double,
parking: Double): Double {
return actualDistance(from, to)/averageSpeed * traffic + parking
}
You can see it has a different method signature from its parent class. This is called method overloading.
Alright, let’s try it out.
Add the following code under the print statement:
val realHours = car.computeTravelTime(
from = lagos,
to = london,
traffic = 1.2,
parking = 0.5
)
println("Actual Hours: " + realHours)
Run the Playground.
As you’d expect, travel time is now more than the inherited method’s calculation due to the new factors like traffic and parking time.
realHours
by 60, you’ll get the time in minutes.
tim
can call this method:
Start typing…
tim.c
TravelMode
method appears in the menu.
If you force it, by copy-pasting, then running the Playground, you get two errors that says kotlin can find the traffic and parking parameters in the method’s definition.
Go ahead and delete this call.
That ends this demo. Continue with the lesson for a summary.