CMP-220 · Lesson 1 of 12 · 21 min
Why objects: from procedural code to objects
Northway Sensing in Kingston, Ontario, builds the NS-4 roadside traffic counter, a small sensor node bolted to a pole that counts and classifies every vehicle in up to four lanes. In CMP-200 you followed the firmware team as they chose data structures for the node itself. This course moves to the other end of the system. Every few minutes each NS-4 uploads a batch of detections to Northway Hub, the server software that stores the records, classifies vehicles, raises alerts when a node goes quiet and produces the counts that city and provincial traffic engineers buy. The Hub is written in Java, with Python tools for testing and data analysis, and it is about to be rebuilt.
Jonah Abara, the second-year co-op student from CMP-200, has joined the server team for his winter work term. His new lead is Dana Whitcombe, a software engineer who has maintained the Hub for six years. On his first morning Dana shows him the heart of the current system: a single Java file called Tally.java, 2,300 lines long, holding about ninety static methods and a dozen parallel arrays. It works, it has counted billions of vehicles, and nobody wants to touch it. Over the term Jonah will help turn it into a set of cooperating objects, and in doing so he will meet every idea in this course: classes and encapsulation, object lifetime, composition and inheritance, polymorphism, interfaces, exceptions, generics, design principles and patterns, and unit testing.
Procedural programming and its limits
Procedural programming organizes a program around procedures, also called functions or subroutines, that act on data passed to them or held in shared variables. It is how you learned to program in ENCM-100 and how most C firmware, including the NS-4's, is still written. Data and code are separate: a structure or a set of arrays holds the data, and any function anywhere may read or change it. For small programs this is simple and efficient, and it maps directly onto how a processor works. The trouble begins as programs grow and many people change them over many years.
Tally.java shows the trouble clearly. Each detection is stored across parallel arrays: nodeId[i], lane[i], timeMs[i], lengthCm[i], speedKmh[i] and vehicleClass[i] all describe the same vehicle, linked only by the index i. Any of the ninety methods may change any element of any array. When a new field was added three years ago, the axle count, someone had to find every method that copied, sorted or deleted detections and add the new array to each one; they missed two, and for eight months the axle counts in sorted reports were shuffled against the wrong vehicles. When a method set speedKmh[i] to a negative value to mean unknown, every other method had to remember that convention. The rules about what makes a detection valid were scattered across the file, and no single place guaranteed them.
// Tally.java (excerpt): data in parallel arrays, rules scattered across static methods
public class Tally {
static int[] nodeId = new int[MAX];
static int[] lane = new int[MAX];
static long[] timeMs = new long[MAX];
static int[] lengthCm = new int[MAX];
static int[] speedKmh = new int[MAX]; // -1 means "unknown"
static int[] axles = new int[MAX]; // added later; two methods forgot it
static int count = 0;
static void addDetection(int node, int ln, long t, int len, int spd) {
nodeId[count] = node; lane[count] = ln; timeMs[count] = t;
lengthCm[count] = len; speedKmh[count] = spd;
count++; // no check that count < MAX
}
static int classify(int i) { // every caller must pass a valid index
if (lengthCm[i] < 0) return 0;
if (lengthCm[i] < 600) return 2; // car
if (speedKmh[i] == -1) return 9; // magic number: unclassified
return lengthCm[i] < 1300 ? 5 : 9;
}
}None of these problems is a bug in procedural programming as such; disciplined C programmers avoid them with careful conventions. But conventions live in people's heads and in comments, and they are broken whenever someone new, rushed or tired changes the code. The core difficulty is that nothing in the language ties the data to the rules that keep it correct.
The object-oriented idea
Object-oriented programming, OOP for short, bundles data together with the operations that are allowed on it. A class is a blueprint that declares the data each object holds, its fields, and the operations it supports, its methods. An object is one instance built from that blueprint, with its own values. Instead of six parallel arrays, the Hub will have a Detection class; each detection is one object holding its own node, lane, time, length and speed, and the class's methods are the only code allowed to create or change those values. If a rule says a speed must be zero or more, the class enforces it in one place, and no other code can break it.
Four ideas are traditionally named as the pillars of OOP, and this course has a lesson or more on each. Encapsulation hides an object's internal data behind its methods so that outside code depends only on what the object does, not how it stores things (lesson 2). Abstraction means modelling only the features that matter for a purpose: a Detection in the Hub has a length and a speed but no colour, because the radar cannot see colour and nobody needs it. Inheritance lets one class build on another, sharing code and expressing an is-a relationship (lesson 5). Polymorphism lets code work with objects of different classes through a common interface, with each object responding in its own way (lessons 6 and 7).
Behind the pillars sits a simpler point: OOP is a way of managing complexity in large programs by dividing them into parts with clear responsibilities and narrow connections. A well-designed object is like a well-designed electronic module. You can use a voltage regulator without knowing how its internal feedback loop works; you rely on its datasheet, which specifies its pins, ratings and behaviour. A class's public methods are its datasheet. As long as the datasheet holds, the internals can be redesigned without disturbing the circuit around it.
Objects send messages
In object-oriented terms, code does not reach into an object's data; it asks the object to do something by calling one of its methods, which the early OOP literature described as sending a message. The call d.vehicleClass() asks the detection d to classify itself. The caller does not know, or need to know, whether the class is stored in a field, computed from the length each time, or looked up in a table. This is the same separation between an abstract data type and its implementation that CMP-200 described for stacks and queues, now applied to every kind of thing in the program.
# The same detection logic, procedural and object-oriented, in Python
# Procedural: data in parallel lists, functions that index into them
node_id, length_cm, speed_kmh = [], [], []
def add_detection(node, length, speed):
node_id.append(node); length_cm.append(length); speed_kmh.append(speed)
def classify(i):
if speed_kmh[i] < 0:
return "unclassified"
return "car" if length_cm[i] < 600 else "truck"
add_detection(17, 455, 62)
add_detection(17, 1520, -1)
print("procedural:", [classify(i) for i in range(len(node_id))])
# Object-oriented: each detection holds its own data and answers questions about itself
class Detection:
def __init__(self, node, length_cm, speed_kmh=None):
if length_cm <= 0:
raise ValueError("length must be positive")
self.node = node
self.length_cm = length_cm
self.speed_kmh = speed_kmh # None means unknown, not a magic -1
def vehicle_class(self):
if self.speed_kmh is None:
return "unclassified"
return "car" if self.length_cm < 600 else "truck"
batch = [Detection(17, 455, 62), Detection(17, 1520)]
print("objects: ", [d.vehicle_class() for d in batch])procedural: ['car', 'unclassified'] objects: ['car', 'unclassified']
The two halves give the same answers, but they fail in different ways. In the procedural version, appending to only two of the three lists, or calling classify with an index from another batch, produces wrong answers silently. In the object version, a detection cannot exist with a missing field, a negative length is rejected at the moment of construction, and the unknown speed is represented by None, a value that cannot be mistaken for a real speed. Python, Java and C++ all support this style; they differ in how strictly they enforce it, as later lessons show.
Languages in this course
The main language of this course is Java, because it is the language of the Hub and because its rules make object-oriented ideas explicit: every method lives in a class, fields and methods carry access modifiers, interfaces are declared formally, and the compiler checks types before the program runs. Runnable examples are in Python, which you can execute directly on this page and whose objects follow the same principles with fewer rules. Where C++ does something importantly different, especially in object lifetime and memory, the lesson shows a C++ listing, because many computer engineers write C++ for embedded and real-time systems. Java and C++ listings are shown for reading, not run here.
| Feature | Java | Python | C++ |
|---|---|---|---|
| Every function in a class | Yes (except lambdas inside methods) | No; module-level functions are common | No; free functions are common |
| Access control | public, protected, private, package | By convention (_name), not enforced | public, protected, private |
| Type checking | Static, at compile time | Dynamic, at run time (optional hints) | Static, at compile time |
| Memory release | Garbage collector | Reference counting plus collector | Deterministic destructors, smart pointers |
| Multiple inheritance of classes | No (interfaces only) | Yes | Yes |
When objects are the wrong tool
Object orientation is not always the best design. A short script that reads a file, sums a column and prints a total gains nothing from classes. Numerical code that transforms large arrays, such as the signal processing in ELE-250 or the matrix work in machine learning, is usually clearer and much faster written as operations on whole arrays. Functional programming, which builds programs from functions without changing data, makes some kinds of concurrency and reasoning easier, and modern Java, Python and C++ all borrow functional features such as lambdas and streams. The NS-4 firmware stays procedural C because its memory is tiny and its behaviour must be predictable to the microsecond. Good engineers use objects where they reduce complexity and something else where they do not.
The Hub is exactly the kind of system where objects pay off: it is large, long-lived, changed by many people, full of business rules about what a valid detection or a billable count is, and it must accept new sensor models and report formats every year. Dana's plan for the term is to carve Tally.java into classes one responsibility at a time, keeping the system running and the counts identical throughout. Lesson 12 returns to that refactoring, armed with unit tests that prove nothing changed.
Worked problem
Finding the objects in a requirement
A city contract says: Each counting station has one or more NS-4 nodes. Each node watches up to four lanes. Every detection records a time, lane, length and speed, and is classified as a motorcycle, car, light truck, bus or heavy truck. Every 15 minutes the station produces a count per lane and class. If a node sends nothing for 30 minutes, the operator is alerted. Identify candidate classes, their data and their behaviour.
Show solution
- Underline the nouns: station, node, lane, detection, time, length, speed, class, count, interval, operator, alert. Nouns that hold data and have rules become candidate classes; simple values become fields.
- Station: a name, location and list of nodes; it can produce interval counts.
- Node: an identifier, the lanes it watches and the time of its last upload; it can report whether it is silent.
- Detection: time, lane, length and speed; it can classify itself or be classified by a classifier object.
- VehicleClass: a fixed set of five values, ideal for a Java enum rather than integers 1 to 9.
- IntervalCount: a station, a 15-minute interval and a count per lane and class; Alert: a node, a reason and a time.
- Verbs suggest methods: produce counts, classify, alert.
Answer: Candidate classes are Station, Node, Lane, Detection, VehicleClass, IntervalCount and Alert. Some will merge or split as the design develops; the noun-and-verb pass is a starting point, not a final design.
Common mistakes
MistakeThinking that putting code inside a class makes it object-oriented.
InsteadA class with only static methods and public arrays is procedural code in a wrapper. Objects should own their data and protect their rules.
MistakeMaking every value a class.
InsteadSimple values such as a time or a lane number can be plain fields; create a class when data comes with rules or behaviour.
MistakeUsing magic values such as −1 to mean unknown.
InsteadRepresent absence explicitly, for example with null handled carefully, Optional in Java or None in Python, so it cannot be mistaken for real data.
MistakeRewriting a working system from scratch to make it object-oriented.
InsteadRefactor in small, tested steps so the system keeps working; big-bang rewrites often fail.
MistakeAssuming objects are always slower.
InsteadMethod calls and objects have small costs that modern compilers and runtimes largely remove; design clarity usually matters more, and hot spots can be optimized after measuring.
Summary and key terms
- Procedural programs separate data from functions; as programs grow, rules about the data scatter and break.
- Object-oriented programming bundles data (fields) with the operations allowed on it (methods) in classes; objects are instances of classes.
- The four pillars are encapsulation, abstraction, inheritance and polymorphism.
- Code asks objects to do things through methods rather than reaching into their data.
- Objects suit large, long-lived systems with many rules and frequent change; they are not the right tool for every program.
- Class
- A blueprint that declares the fields and methods shared by a kind of object.
- Object
- An instance of a class, with its own field values.
- Field
- A variable that belongs to an object (or, if static, to the class).
- Method
- A function defined in a class that operates on an object's data.
- Encapsulation
- Hiding an object's internal data so that outside code uses only its methods.
- Abstraction
- Modelling only the features of a thing that matter for a given purpose.
Practice problems
Work each problem on paper first, then reveal the answer to check.
Name two ways the parallel-array design of Tally.java can produce wrong answers without any error.
Show answer
A new field added to some copying methods but not others misaligns records; a magic value such as −1 is treated as real data by a method that forgets the convention.
In the requirement 'each node records the voltage of its battery every hour', is battery voltage a class or a field?
Show answer
Usually a field of a node's status reading; it becomes a class only if it carries its own rules or behaviour.
Give one program that should stay procedural and say why.
Show answer
The NS-4 firmware: tiny memory, strict timing and a small, stable code base where C structures and functions are simplest.
What does it mean that a class's public methods are its datasheet?
Show answer
They specify what the object does, so callers depend only on them and the internals can change freely.
Which pillar is at work when a Detection has length and speed but no colour?
Show answer
Abstraction: modelling only what matters for the purpose.
Lesson quiz · 5 questions
Score 60% or more to complete this lesson.
Create a free account to save your progress, take quizzes and earn a certificate.