Lecture 1: The Essence of Objects
One of the key concepts behind so-called object-oriented programming (OOP) is the notion of an object. An object is a new kind of value that can, as a first cut, be understood as a pairing together of two familiar concepts: data and function.
An object is like a structure in that it has a fixed number of fields, thus an object (again, like a structure) can represent compound data. But unlike a structure, an object contains not just data, but functionality too;
An object is like a (set of) function(s) in that it has behavior—
it computes; it is not just inert data.
This suggests that objects are a natural fit for well-designed programs since good programs are organized around data definitions and functions that operate over such data. An object, in essence, packages these two things together into a single programming apparatus. This has two important consequences:
You already know how to design programs oriented around objects.
Since objects are just the combination of two familiar concepts that you already use to design programs, you already know how to design programs around objects, even if you never heard the term “object” before. In short, the better you are at programming with functions, the better you will be at programming with objects.
Objects enable new kinds of abstraction and composition.
Although the combination of data and function may seem simple, objects enable new forms of abstraction and composition. That is, objects open up new approaches to the construction of computations. By studying these new approaches, we can distill new design principles. Because we understand objects are just the combination of data and function, we can understand how all of these principles apply in the familiar context of programming with functions. In short, the better you are at programming with objects, the better you will be at programming with functions.
Let’s start by looking at a very simple program developed using the systematic approach of last semster. This program operates on Cartesian coordinates and consists of a couple simple functions for computing the distance of a coordinate from the orgin and for moving a point by given offsets:
; A Coord is a (make-coord Real Real) ; Interp: Cartesian coordinate (define-struct coord (x y)) ; dist0 : Coord -> Real ; Distance of given coordinate to origin (check-expect (dist0 (make-coord 3 4)) 5) (define (dist0 c) (sqrt (+ (sqr (coord-x c)) (sqr (coord-y c))))) ; move : Coord Real Real -> Coord ; Move given coordinate by offsets (check-expect (move (make-coord 3 4) 2 -1) (make-coord 5 3)) (define (move c dx dy) (make-coord (+ (coord-x c) dx) (+ (coord-y c) dy)))
This program consists of two parts: a data definition for coordinates and a set of function definitions that operate on coordinates.
An object, is in essence, just the fusing together of those two parts. Here is a similar program developed using objects:
; A Coord is a (new coord% Real Real) ; Interp: Cartesian coordinate (define-class coord% (fields x y) ; dist0 : -> Real ; Distance of this coordinate to origin (check-expect (send (new coord% 3 4) dist0) 5) (define (dist0) (sqrt (+ (sqr (send this x)) (sqr (send this y))))) ; move : Real Real -> Coord ; Move this coordinate by offsets (check-expect (send (new coord% 3 4) move 2 -1) (new coord% 5 3)) (define (move dx dy) (new coord% (+ (send this x) dx) (+ (send this y) dy))))
Notice that this programs consists of a single class definition. A class definition is a mechanism for defining a set of objects; similar to how a struct definition is a mechanism for defining a set of structures.
The coord% class defines a new kind of value, which are objects consisting of two fields x and y, and two methods dist0 and move.
To create an object that is an member of the coord% class, you use new, the name of the class (coord%), and the appropriate number (and kind) of values to place in the fields of the object. For example, (new coord% 3 4) will construct a Coord that represents a point at (3,4). This is analogous to using the make-coord constructor in the original program.
To access the data within an object, you use send, a Coord value, and the name of the field you’d like to extract. For example, (send (new coord% 3 4) x) will produce 3, which is the value of the x field within the object (new coord% 3 4). This is analogous to using the coord-x accessor function in the oroginal program.
To compute with an object, you use send, a Coord value, the name of the method you’d like to compute, and any additional arguments the method expects. For example, (send (new coord% 3 4) dist0) computes 5, the distance of (3,4) to the origin. Notice that this method takes no additional arguments; it is a function of just the data within the coordinate. On the other hand, (send (new coord% 3 4) move 1 2) takes two arguments: the change in x and y. It computes a new coordinate representing (4,6), namely (new coord% 4 6).
Notice that the definitions of dist0 and move make use of the variable this, which doesn’t seem to be defined anywhere. The name this is implicitly defined to me “the object on which the method was invoked.” So for example, when computing (send (new coord% 3 4) dist0), the name this means (new coord% 3 4) in the body of dist0.
Notice that there are subtle changes to the signature and purpose statement. Spend some time comparing them and try to formulate why these differences exist.