Object-oriented programming is based on four essential principles.
- Abstraction: Abstract behavior of objects is summarized in classes or prototypes.
- Data encapsulation: Properties and methods are encapsulated in the form of classes or prototypes and hidden from external access.
- Inheritance: Properties and methods can be inherited from one class to another class or from one object (the prototype) to another object.
- Polymorphism: Objects can take on different types depending on their use.
Other Principles of Object-Oriented Programming: There are several other special principles, such as aggregation, coupling, association, and composition (see, for example, https://en.wikipedia.org/wiki/Package_principles), but we won’t discuss them here.
Now, of course, the first question is what exactly classes and prototypes are. Therefore, we’ll first explain both terms and also discuss their connection with objects before returning to the principles of object orientation.
Classes, Object Instances, and Prototypes
In object orientation, a class is a kind of blueprint for objects. A class serves as a template, so to speak, from which individual instances (object instances) can be created at the runtime of a program. Within a class, the developer defines the properties and methods that the individual object instances should have. The properties represent the state of the object instances, the methods their behavior. In software development, class diagrams in Unified Modeling Language (UML) are often used to model individual classes or to represent the relationship of classes to each other, while the relationship of objects to each other is represented in object diagrams.
The figure below shows a combination of both diagram types, where you can see the Animal class, plus two object instances of this class, bello and bella. In class/object diagrams, both classes and objects are represented by rectangles divided into individual areas.
For classes, the name of the class is at the top, followed by an optional area listing the properties, followed by another optional area listing the methods of the class. Properties are listed with name and data type, methods also with names and data types of the individual parameters. Optionally, other aspects such as return value (for methods) and visibility (for properties and methods) can be specified.
For objects, the name of the respective object is also at the top (lowercase according to convention), followed by a colon and the name of the class of which the respective object is an instance. Properties of objects are specified with names and usually also with concrete values, but a list for methods doesn’t exist in object diagrams.
An example of a class-based programming language is the Java programming language. JavaScript, however, is not a class-based programming language, even though there has been a class syntax since ES2015 (more on that in a moment).
JavaScript is instead an object-based programming language: object instances are not created here on the basis of classes, but on the basis of other objects. These other objects then represent the prototype.
Deviation from UML Standard: Because JavaScript is an object-based programming language and methods are defined directly on objects (and not on classes), we’ve included some methods in the object diagrams to reflect this fact. Although this deviates somewhat from the UML standard, it simplifies the representation in this case.
Because of this principle of prototypes, the type of programming in JavaScript is also called prototype-based programming or prototypical programming. An alternative term is classless programming, because no classes are used.
Types of Object Orientation: The type of object orientation also has different names depending on the type of programming language: In the class-based programming you speak of class-based object orientation, in the object-based and/or prototype-based programming you speak of prototypical object orientation.
JavaScript Doesn’t Know Real Classes: JavaScript uses the term classes since ES2015, and the class syntax introduced with it, but these aren’t genuine classes (like the classes in Java); they’re ultimately only "syntactic sugar." Therefore, ahead, we’ll put the word "class" in quotation marks if we’re referring to these kind of "classes." In all cases where this distinction doesn’t matter, we omit the quotation marks.
Now that the concepts of class and prototype have been clarified and their connection to objects has been shown, let’s return to the basic principles of object-oriented programming in the following sections.
Principle 1: Define Abstract Behavior
In object-oriented programming, classes or prototypes define the basis for multiple object instances that have a similar state and behavior. Classes and prototypes can therefore also be considered abstractions of the (concrete) object instances.
Classes and prototypes define the abstract behavior (common to all object instances) or the properties representing the state (also common to all object instances), respectively. In the object instances, the properties are then provided with concrete values, thus defining the concrete state.
Principle 2: Encapsulate Condition and Behavior
In object orientation, the term data encapsulation (or information hiding) refers to the grouping of properties and methods into classes or prototypes, whereas the details of the implementation remain hidden: usually, you provide the properties only through methods to prevent direct access to them and also to prevent an object from being set to an unauthorized state.
In the figure below, this is illustrated in sketch form. The name, color, and age properties are marked as private by the preceding minus sign: direct access to these properties isn’t possible from outside, so the call of caller 1 fails.
Instead, the class provides setter and getter methods to access the properties, so the call to caller 2 succeeds. Such accessor methods or simply accessors can protect against invalid values being assigned to a property. But if you allow direct access to the properties (and thus do not fulfill the principle of data encapsulation), this isn’t guaranteed because values can then also be assigned that are not valid (e.g., the value Doe to the color property).
In programming languages such as Java, special keywords can be used to prevent the values of properties from being changed externally (i.e., from outside an object instance— namely, by marking them as private via the private keyword). JavaScript has long been somewhat cumbersome in this regard, inherently offering only the set and get keywords to mark properties as access properties, but not to make properties completely inaccessible from the outside. Only with ES2022 is it now possible to mark private properties as such via a special syntax.
Principle 3: Inherit Condition and Behavior
In class-based object orientation, a class can inherit or derive properties and methods from another class, which is referred to as inheritance.
For example, a Dog class could derive from an Animal class and thus inherit the methods defined there. The figure below shows the corresponding class diagram for this. You can see that inheritance relationships are indicated by arrows in UML: the Cat and Dog classes derive from the Animal class and inherit its methods, but they also define one further method each.
The class that inherits or deriving class is also called the subclass; the class that’s inherited from is correspondingly called the superclass.
Subtypes and Supertypes: In prototypical object orientation, it’s possible for objects to inherit from other objects (the prototypes). Because the terms subobject and superobject are rather uncommon in this context, the terms subtype and supertype are more commonly used here.
Note: The inheritance of classes or objects represents an as is relationship: An instance of Cat is also an instance of Animal. However, the reverse is not true: an instance of Animal is not necessarily an instance of Cat.
Principle 4: Accept Different Types
Polymorphism is the ability of objects to take on a different type (shape) or to present themselves as a different type, depending on the context or their use.
For example, if a function expects an object of the Animal type, then object instances of Animal, but also object instances of subclasses such as Cat or Dog, can be passed as arguments. You can use instances of Cat in all places where you expect instances of Animal or instances of Cat and you can use instances of Dog in all places where you expect instances of Animal or Dog. These instances are polymorphic: in one place they can assume the more general type Animal, in another place the more specific type Cat or Dog.
Statically Typed versus Dynamically Typed: In strictly or statically typed programming languages like Java, the language itself emphasizes the topic of polymorphism more than it does in weakly or dynamically typed languages like JavaScript. Because of the weak typing, you can pass any types of objects as arguments for functions, for example, without a compiler grumbling in case of not allowed types.
Note: Object instances of a subclass can be treated like object instances of the respective superclass.
JavaScript and Object Orientation
With regard to object-oriented programming, JavaScript doesn’t behave as unambiguously as other languages (like Java, for example, which we often use for comparison). Rather, there are several techniques in JavaScript to program in an object-oriented way. As a good JavaScript developer, you should master all of these techniques.
In principle, distinctions are made among the following:
- Prototypical object orientation: This is the kind of object orientation that is most natural in the JavaScript language because it uses only objects.
- Pseudoclassical object orientation: This is a type of object orientation where you pretend JavaScript is a class-based programming language. Constructor functions are used here.
- Object orientation with class syntax: This type of object orientation represents a syntactic simplification of pseudolassical object orientation.
Editor’s note: This post has been adapted from a section of the book JavaScript: The Comprehensive Guide by Philip Ackermann.
Comments