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 class object. 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 variable self, 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 via self. 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 called this. 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 and age 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 from Person. Notice the statement in Student.__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 and y 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 setter x, and $y gets replaced by the results of the calling setter y.

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.