Lecture 14¶
The following continues the discussion of OOP in various programming languages.
Python¶
Now lets see how to implement the Person
/Student
in Python. The
general spirit of Python’s OOP is similar to C++’s, although the details
differ quite a bit.
Here are the same two classes from above rendered in to Python 2 new-style classes:
class Person(object):
def __init__(self, n, a):
self.name = n
self.age = a
def display(self):
print '%s is %s years old.' % (self.name, self.age)
class Student(Person):
def __init__(self, n, a, s):
super(Student, self).__init__(n, a) # call superclass initializer
self.school = s
def display(self):
print '%s is %s years old and attends %s.' % (self.name,
self.age,
self.school)
if __name__ == '__main__':
p = Person('Mary', 67)
p.display()
s = Student('Barry', 12, 'Sun Ray Elementary')
s.display()
Perhaps the most noticeable difference is the simpler, shorter syntax. This is one of Python’s major features. You can also see the same general structure of classes with methods.
Let’s look at the differences:
The
Person
class extends the special built-in classobject
. This is a technical trick that forces Python to use what it calls new-style classes. Originally, Python used different rules for its OOP, and then changed them.In C++, a constructor initializes an object and has the same name as the class. But in Python, object initializes are always named
__init__
, and they can take any number of parameters.The variable
self
is passed into__init__
, and every other method in a Python class. You don’t need to name this variableself
, but that’s the traditional name.self
is a variable that is the object itself. Whenever you want to access a variable or method within a class, you must do so viaself
. While this is less convenient than C++, it makes the basic mechanism for how OOP works explicit and less mysterious.C++ has the equivalent of
self
calledthis
. In C++,this
is a special keyword that, within an object, is a pointer that always refers to the object itself. It is often unnecessary to use explicitly.The
name
andage
variables are defined directly in__init__
, not outside of it as in C++.Python has no explicit public or private declarations. Effectively, all object variables and methods are public, and it is up to the programmer to not mess with them in any bad way.
There is a trick for simulating a sort of private variable/method by putting to underscores in front of a name, e.g.
__name
or__age
.The reason for Python’s lack of private is often said to be “cultural”. There is a general sense among many Python programmers that explicitly indicating private parts of an object is more trouble than it’s worth, and that it is easy to “simulate” private variables by just not modifying variables that shouldn’t be modified. This approach is fine for the relatively small, simple programs that Python is good for writing, but for very large programs it strikes me as unwise.
Because there is no private variables in Python, we have not bothered to create explicit getters. Python does have some interesting support for defining special getters and setters that, syntactically, act just like variables, but in fact are functions in disguise.
The
Student
class inherits fromPerson
. Notice the statement inStudent.__init__
that calls the superclass initializer:super(Student, self).__init__(n, a)
You can call this anywhere you want, but you should call it as the first line of the
__init__
function of the subclass.This is different than C++, where the class super-constructor is called with a simpler syntax that prevents you from calling any code before the super- constructor. The reason for this restriction is to ensure that the super- class object is properly initialized before you start adding the new variables and methods of the subclass.
JavaScript¶
Both C++ and Python provide class-based OOP, which means that to create an object you first create a class that describes that object. However, JavaScript provides a different, and more flexible approach.
In JavaScript, there are no classes, just objects. In JavaScript, objects are
mutable collections of key-value pairs, similar to maps in Go or dictionaries
in Python. While numbers, strings, and booleans are not objects in
JavaScript, all other values are (except for the special values, null
and
undefined
). For instance, both functions and array are objects in
JavaScript.
In JavaScript, every object has a reference to another object called its
prototype. For example, if obj
is any JavaScript object, then
obj.prototype
refers to some other object. By default, obj.prototype
are set to refer to the built-in object Object.prototype
. However, you can
change an object’s prototype reference whenever you like, which is how
JavaScript implements its version of inheritance.
Suppose you call obj.x
in JavaScript (where obj
is any object). Then,
if obj
has x
, that is returned. But if obj
does not have x
,
JavaScript checks to see if x
is in the object obj.prototype
refers
to; if it is, that x
is returned. But if it’s not there, JavaScript
follows the prototype reference of the prototype, looking for x` there. This
following of prototype references is called delegation (or sometimes
prototype chaining), and it continues until either x
is found, or
Object.prototype
is reached without having found x
.
Here is one way of simulating inheritance using basic JavaScript:
//
// Person object
//
function Person(n, a) { // calling Person constructs a new object
this.name = n;
this.age = a;
}
Person.prototype.display = function() {
document.write("<p>" + this.name + " is " + this.age + " years old.</p>");
};
//
// Student object (inherits from person)
//
function Student(n, a, s) {
Person.call(this, n, a); // call Person constructor
this.school = s;
}
Student.prototype = new Person();
Student.prototype.constructor = Student; // make constructor point to Student
Student.prototype.display = function() {
document.write("<p>" + this.name + " is "
+ this.age + " years old and attends "
+ this.school + ".</p>");
};
//
// main code
//
document.write("<h1>JavaScript OOP Example</h1>");
var p = new Person('Mary', 67);
p.display();
var s = new Student('Barry', 12, 'Sun Ray Elementary');
s.display();
Here we have essentially mimicked the traditional kind of inheritance used in languages like C++, Java, and Python. But JavaScript is more flexible than those languages, and so allows you to use other approaches to OOP which, in many cases, are superior.
For example, you can use prototypal inheritance. The idea here is to create an explicit person object, copy it to make student objects, and then modify the student object as needed:
// JSON: JavaScript Object Notation
var p = { // p is prototype object
name: "Mary",
age: 67,
display: function ( ) {
document.write("<p>" + this.name + " is "
+ this.age + " years old.</p>");
}
}
p.display()
var s = Object.create(p); // Object.create copies another object
s.name = 'Barry'
s.age = 12
s.school = 'Sun Ray Elementary'
s.display = function ( ) {
document.write("<p>" + this.name + " is "
+ this.age + " years old and attends "
+ s.school + ".</p>");
}
s.display()
Go¶
Like JavaScript, Go is an object-oriented language that does not have
classes. But, unlike JavaScript, it does not use prototypes. Instead, methods
are created on types (even primitive types, such as int
) and inheritance
can be simulated using composition (although composition can be used on its
own instead of just a technique for implementing inheritance).
Here is a Go version of the program:
type Person struct {
name string
age int
}
func (p Person) display() {
fmt.Printf("%s is %d years old.\n", p.name, p.age)
}
type Student struct {
Person
school string
}
func (s Student) display() {
fmt.Printf("%s is %d years old and attends %s.\n", s.name, s.age, s.school)
}
func main() {
p := Person{"Mary", 67}
p.display()
s := Student{Person{"Barry", 12}, "Sun Ray Elementary"}
s.display()
}
Like C++, Go is statically typed, so the compiler will catch most errors
related to incorrect types. But note that Student
is not a subclass of
Person
. Instead, Student
is composed of Person
, plus a school
string.
A limitation of this approach is that you cannot create a slice in Go that
contains both Person
and Student
objects because they are different
types (and slices can only contain a single type of object). A way around this
is to use Go interfaces, e.g.:
// ... other code as before ...
type Displayer interface {
display()
}
func main() {
// people is a slice of Displayer objects
people := []Displayer{p, s}
for _, x := range people {
x.display()
}
}
Here we’ve created an interface called Displayer
. In Go, an object
implements the Displayer
interface if it has a method named display()
.
Thus, objects of type Person
and Student
both implement Displayer
.
Displayer
is the name of a type, and so we can create a slice of
Displayer
objects, e.g.:
people := []Displayer{p, s}
p
is of type Person
, and s
is of type Student
, but because they
both implement the Displayer
type, we can put them into the same slice of
Displayer
objects.
Now we can call display()
on any element in people
:
for _, x := range people {
x.display()
}
Dart¶
Dart is a language that runs in the web browsers, and is meant to be a replacement for JavaScript. Time will tell if it succeeds, but for now it instructive to look at a few of its OOP features.
Consider this class for representing a two-dimensional (x, y) point:
class Vec {
double _x, _y;
Vec(x, y) {
_x = x.toDouble();
_y = y.toDouble();
}
Vec.origin() : _x=0.0, _y=0.0;
Vec.copy(other) : _x=other.x, _y=other.y;
Vec.random(lo, hi) : _x=randNum(lo, hi), _y=randNum(lo, hi);
double get x => _x;
set x(double val) => _x = val;
double get y => _y;
set y(double val) => _y = val;
double get length => sqrt(_x * _x + _y * _y);
scale(double sf) {
_x *= sf;
_y *= sf;
}
translate(double dx, double dy) {
_x += dx;
_y += dy;
}
addVec(other) => translate(other.x, other.y);
toString() => "Vec($x, $y)";
} // class Vec
As you can see, the general style of a Dart class is reminiscent of C++ and Java. However, there are some interesting differences:
Dart has named constructors. So you can write code like this:
Vec center = new Vec.origin(); Vec center2 = new Vec.copy(center);
This seems to be help the readability of the code quite a bit.
Type information is always required in Dart, which makes it a cross between a statically typed language and dynamically typed language. The idea is that when you are prototyping a program you can skip types, and then add them in later to get the benefit of better performance and error messages.
For example, in this example the type of
x
andy
is not explicitly given:Vec(x, y) { _x = x.toDouble(); _y = y.toDouble(); }
Dart provides a special notation for getters and setters, e.g.:
double get x => _x; set x(double val) => _x = val;
This lets us write code like this:
Vec center = new Vec.origin(); center.x = 3;
We can access
x
using the a syntax that looks like we are accessing a variable. This reduces code clutter.The
=>
operator is used to define single-line functions. It is a syntactic convenience that helps make the source code more readable.The
toString
is interesting because it is so short:toString() => "Vec($x, $y)";
It has not explicit return type, uses the single-line function definition with
=>
, and also uses string interpolation to create its return string. In the expression “Vec($x, $y)”,$x
gets replaced by the results of calling setterx
, and$y
gets replaced by the results of the calling settery
.
Dart has a number of other neat features, such as factory constructors that can return, for instance, cached objects instead of new objects when they are called. It also supports implicit interfaces, i.e. the methods in a class implicitly define an interface that other classes can implement (implementing an interface is distinct from extending a class in Dart). In addition to inheritance, Dart also has mixins, which is a restricted form of a class that allows for some implementation inheritance.