Class Theory
“Class/Inheritance” describes a certain form of code organization and architecture — a way of modeling real world problem domains in our software.
OO or class oriented programming stresses that data intrinsically has associated behavior (of course, different depending on the type and nature of the data!) that operates on it, so proper design is to package up (aka, encapsulate) the data and the behavior together. This is sometimes called “data structures” in formal computer science.
For example, a series of characters that represents a word or phrase is usually called a “string”. The characters are the data. But you almost never just care about the data, you usually want to do things with the data, so the behaviors that can apply to that data (calculating its length, appending data, searching, etc.) are all designed as methods of a String
class.
Any given string is just an instance of this class, which means that it’s a neatly collected packaging of both the character data and the functionality we can perform on it.
Classes also imply a way of classifying a certain data structure. The way we do this is to think about any given structure as a specific variation of a more general base definition.
Let’s explore this classification process by looking at a commonly cited example. A car can be described as a specific implementation of a more general “class” of thing, called a vehicle.
We model this relationship in software with classes by defining a Vehicle
class and a Car
class.
The definition of Vehicle
might include things like propulsion (engines, etc.), the ability to carry people, etc., which would all be the behaviors. What we define in Vehicle
is all the stuff that is common to all (or most of) the different types of vehicles (the “planes, trains, and automobiles”).
It might not make sense in our software to re-define the basic essence of “ability to carry people” over and over again for each different type of vehicle. Instead, we define that capability once in Vehicle
, and then when we define Car
, we simply indicate that it “inherits” (or “extends”) the base definition from Vehicle
. The definition of Car
is said to specialize the general Vehicle
definition.
While Vehicle
and Car
collectively define the behavior by way of methods, the data in an instance would be things like the unique VIN of a specific car, etc.
And thus, classes, inheritance, and instantiation emerge.
Another key concept with classes is “polymorphism”, which describes the idea that a general behavior from a parent class can be overridden in a child class to give it more specifics. In fact, relative polymorphism lets us reference the base behavior from the overridden behavior.
Class theory strongly suggests that a parent class and a child class share the same method name for a certain behavior, so that the child overrides the parent (differentially). As we’ll see later, doing so in your JavaScript code is opting into frustration and code brittleness.
“Class” Design Pattern
You may never have thought about classes as a “design pattern”, since it’s most common to see discussion of popular “OO Design Patterns”, like “Iterator”, “Observer”, “Factory”, “Singleton”, etc. As presented this way, it’s almost an assumption that OO classes are the lower-level mechanics by which we implement all (higher level) design patterns, as if OO is a given foundation for all (proper) code.
Depending on your level of formal education in programming, you may have heard of “procedural programming” as a way of describing code which only consists of procedures (aka, functions) calling other functions, without any higher abstractions. You may have been taught that classes were the proper way to transform procedural-style “spaghetti code” into well-formed, well-organized code.
Of course, if you have experience with “functional programming” (Monads, etc.), you know very well that classes are just one of several common design patterns. But for others, this may be the first time you’ve asked yourself if classes really are a fundamental foundation for code, or if they are an optional abstraction on top of code.
Some languages (like Java) don’t give you the choice, so it’s not very optional at all — everything’s a class. Other languages like C/C++ or PHP give you both procedural and class-oriented syntaxes, and it’s left more to the developer’s choice which style or mixture of styles is appropriate.
JavaScript “Classes”
Where does JavaScript fall in this regard? JS has had some class-like syntactic elements (like new
and instanceof
) for quite awhile, and more recently in ES6, some additions, like the class
keyword (see Appendix A).
But does that mean JavaScript actually has classes? Plain and simple: No.
Since classes are a design pattern, you can, with quite a bit of effort (as we’ll see throughout the rest of this chapter), implement approximations for much of classical class functionality. JS tries to satisfy the extremely pervasive desire to design with classes by providing seemingly class-like syntax.
While we may have a syntax that looks like classes, it’s as if JavaScript mechanics are fighting against you using the class design pattern, because behind the curtain, the mechanisms that you build on are operating quite differently. Syntactic sugar and (extremely widely used) JS “Class” libraries go a long way toward hiding this reality from you, but sooner or later you will face the fact that the classes you have in other languages are not like the “classes” you’re faking in JS.
What this boils down to is that classes are an optional pattern in software design, and you have the choice to use them in JavaScript or not. Since many developers have a strong affinity to class oriented software design, we’ll spend the rest of this chapter exploring what it takes to maintain the illusion of classes with what JS provides, and the pain points we experience.