Relationships between Objects
One thing we might have noticed in the real world is that objects and classes don't exist in isolation/independently. For instance, objects can interact with each other. Additionally, objects can be similar, group in more general terms:
For instance, Ford and Fiat both make trucks. These two classes are related in that they are both trucks. But trucks are pretty similar to minivans, not exactly the same, but similar. They are related in that both are automobiles, like SUVs, sports cars, etc. So cars and trucks are both types of automobiles. So let's say that we have a couple of classes, Automobile, Minivans, and Trucks. Minivans and trucks are subclasses of automobiles, and we can reflect this relationship in our code. The Minivan and the Truck class inherit from the Automobile class.
For instance, Ford and Fiat both make trucks. These two classes are related in that they are both trucks. But trucks are pretty similar to minivans, not exactly the same, but similar. They are related in that both are automobiles, like SUVs, sports cars, etc. So cars and trucks are both types of automobiles. So let's say that we have a couple of classes, Automobile, Minivans, and Trucks. Minivans and trucks are subclasses of automobiles, and we can reflect this relationship in our code. The Minivan and the Truck class inherit from the Automobile class.
Inheritance
Inheritance defines a relationship between classes. The child (sub-) class inherits the fields and methods from the parent (super-) class. The child class then gets to have additional fields and methods that the parent class does not have. For instance, recall the point class from the previous section.
class point x_val y_val z_val =
object
val mutable x_pos = x_val
val mutable y_pos = y_val
val mutable z_pos = z_val
method get_pos = (x_pos, y_pos, z_pos)
method move_pos x_val y_val z_val =
x_pos <- x_val;
y_pos <- y_val;
z_pos <- z_val
method distance () =
sqrt (float_of_int (x_pos*x_pos + y_pos*y_pos + z_pos*z_pos))
end
;;
But let's say that we have a more interesting type of point, perhaps a colored point (yeah, I know, it's weird) that needs to have a color field and a couple of methods for retrieving the color and changing the color. This colored point should have all of the fields and methods as our point class but have a couple more. Let's see quickly how to do this in OCaml before reviewing more generally what we did.
class colored_point x_val y_val z_val start_color =
object
inherit point x_val y_val z_val
val mutable color : string = start_color
method get_color = color
method change_color new_color = color <- new_color
end
;;
class colored_point :
int ->
int ->
int ->
string ->
object
val mutable color : string
val mutable x_pos : int
val mutable y_pos : int
val mutable z_pos : int
method change_color : string -> unit
method distance : unit -> float
method get_color : string
method get_pos : int * int * int
method move_pos : int -> int -> int -> unit
end
So, let's break down the new syntax. In our declaration we use the inherit keyword followed by the class we inherit from and the arguments needed for that class. Think of this as a copy and paste of the fields and methods of the parent class into our child class declaration. That's what's new so far. As we see in the OCaml response, our new class has all of the fields and methods of our parent class as well as all of the new ones. We can instantiate objects of both the point and colored_point class, and calling methods works with our new class. Play around with colored_points!
Overriding Methods
Sometimes, child classes need to perform their methods differently from from their parent class. This happens (they are the future after all). For instance, let's look at a trivial parent and child class.
class person birthday =
object
val birthday : string = birthday
method get_info = birthday
end
;;
class child birthday birthplace =
object
inherit person birthday
val birthplace : string = birthplace
end
When we call get_info on an instance of the child class, we only get the birthday. We should really print all of the info contained in the object, including the birthplace. We can simply redefine the get_info method in our child class to do this. Let's see how:
class child birthday birthplace =
object
inherit person birthday
val birthplace : string = birthplace
method get_info = birthday ^ birthplace
end
Well, we can simply rewrite it. When we rewrite methods there are a couple of restrictions. The method must have the same type. That means all of the arguments must be the same type as the parent's method arguments and the return type must be the same. This method rewriting is called method overriding. We'll see another type of method rewrite called method overloading a little while later.
Super
As we've seen previously, we need to use a special (self) keyword in our class declaration so that we have access to the methods in our declaration. When we inherit methods from a superclass, we can't simple call those functions, such as calling get_info from before.
class child birthday birthplace =
object
inherit person birthday
val birthplace : string = birthplace
method another_get_info = get_info
end
;;
Error: Unbound value get_info
Instead, we have to tell OCaml that we are using methods from inherited superclasses like we did (self) previously. We use the as keyword followed by a name. (Convention says we use super, just as convention says we use self or this.) Like the following:
class child birthday birthplace =
object
inherit person birthday as super
val birthplace : string = birthplace
method another_get_info = super#get_info
end
;;
class child :
string ->
string ->
object
val birthday : string
val birthplace : string
method another_get_info : string
method get_info : string
end
We can override a function by calling the superclass function of the same name^^.
class child birthday birthplace =
object
inherit person birthday as super
val birthplace : string = birthplace
method get_info = super#get_info ^ birthplace
end
;;
class child :
string ->
string ->
object
val birthday : string
val birthplace : string
method get_info : string
end
Great! Inheritance, overriding functions and calling methods!
Multiple Inheritance
Classes can inherit from multiple other classes (I guess something akin to genetics?). When methods have conflicting names, only the last method (in terms of declaration) is kept. However, we can continue to access both superclasses's methods because we can give them different names (such as super and super2 or something much much much better than that).
class mother () = class mother : |
class father () = class father : |
class child () =
object (self)
inherit mother () as mom
inherit father () as dad
method combine = mom#combine
end
;;
Well, okay, so the whole hiding thing got changed in a new release of OCaml (PROGRAMMING LANGUAGES CHANGE OKAY?), but we can still do it.
Warning 13: the following instance variables are overridden by the class father : field1
The behaviour changed in ocaml 3.10 (previous behaviour was hiding.)
class child :
unit ->
object val field1 : string val field2 : string method combine : string end
let example = new child ();;
example#combine;;
- : string = "Hi! World!"
Cool! Inheritance and Multiple Inheritance! This kind of makes sense right? Yay!