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("

" + this.name + " is " + this.age + " years old.

"); }; // // 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("

" + this.name + " is " + this.age + " years old and attends " + this.school + ".

"); }; // // main code // document.write("

JavaScript OOP Example

"); 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("

" + this.name + " is " + this.age + " years old.

"); } } 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("

" + this.name + " is " + this.age + " years old and attends " + s.school + ".

"); } 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.