DEV Community

Christopher Smith
Christopher Smith

Posted on

OOP Bootcamp 2: The Why of OOP

Object-Oriented Programming has been consistently in the spotlight for the past 2 decade by some individuals for its verbosity, lack of good function handling, and overall easy-of- "complexifying" an entire stack with bad design practices.
 
Some of these videos do a very good job of breaking down (or at the very least reacting to) the problems that exist within OOP. While some of these are generated from years of experience in the field, others are functional programming purists caught up in a wave OOP-backlash that has been building for some time.

It seems to be one of the endless debates in the computer science world: Vim or Emacs, Linux or Microsoft, OOP or FP. It is easy as new learner to get caught up into this debate and shut down OOP without fully understanding the why and how of OOP. 

In this post, I want to, in brief and in no way truly comprehensively cover, dive into the surface level of the history of object-oriented programming and why it exists as it does today.

It starts with a LISP

Our story of objects starts in the late 1950's with LISP.

Prior to lisp, programming was by and large strictly procedural. This form of programming can be seen in something like C or Fortran, where functions are defined then ran as-is, with no real functionality to pass them as first-class, meaning functions couldn't be stored in variables, or passed to other functions as the parameter of that function (think of f(g(x)) type functions in math).

Lisp allowed developers to encapsulate their functions inside of other structures, and even introduced the work "objects" into the academic vernacular.

Here is what I mean:

(define (make-atom name)
  (let ((properties '()))  ; An association list for properties
    (define (set-property key value)
      (set! properties (cons (cons key value) properties)))
    (define (get-property key)
      (let ((prop (assoc key properties)))
        (if prop (cdr prop) #f))) ; Return #f if the key is not found
    (define (describe)
      (display "Atom: ") (display name) (newline)
      (for-each (lambda (prop)
                  (display (car prop)) (display ": ") (display (cdr prop)) (newline))
                properties))
    (lambda (message . args)
      (cond ((eq? message 'set) (apply set-property args))
            ((eq? message 'get) (apply get-property args))
            ((eq? message 'describe) (describe))
            (else (error "Unknown message"))))))

;; Usage
(define my-atom (make-atom 'example-atom))

;; Set properties
(my-atom 'set 'color 'blue)
(my-atom 'set 'size 'medium)

;; Get properties
(display (my-atom 'get 'color))  ; Output: blue
(newline)

;; Describe the atom
(my-atom 'describe)
Enter fullscreen mode Exit fullscreen mode

We aren't defining an object in the traditional sense, rather we are creating an atom, then defining certain properties and methods within a list. This code might be meaningless to you, or you might not know LISP, but that is okay, just know that we are defining our "object" inside of a list and then accessing the list with a key-value pair.
These objects are missing just about... well, everything that makes objects, objects, but there are several reasons as to how we go from LISP and FORTRAN to Simula and Smalltalk.


From Science to Simulation

During the 1940s and 1950s, computers were about science and mathematics. Fortran was and is the premier scientific language, and much of the world around computers were about how to do mathematical and scientific calculation.

But starting in the late 1950s and into the 1960s, there began to be an interest in a computers ability to simulate sequences, behavior, and actions like are found in the real world: traffic systems, network systems, and phone systems were a bit focus at this time, in finding new ways to model real world items and events. Even with further improvements in programming theory, there still wasn't a perfect or even a good way to model these sorts of events.

However, object-based programming would fully take form with the Simula language. At the time, while developers could build complex systems, the ability to truly simulate data wasn't quite there.

Imagine for a second how you would simulate a dog. It has properties (like fur) and actions (like barking. You can simulate one for sure, but imagine simulating hundreds of dogs, some of whom are different from others, with actions and properties that are different from others.

Now add other animals, and plants, and then humans with all of their behaviors. Sometimes we want for our dogs and creatures to be in certain states and at the time there was no language that could do that.


Its Simula Time!

Initially, Simula was nothing more than an Algol extension, hoping to improve its ability to model real-world data. However, a later version would revolutionize the OOP method of programming.
Simila 67 introduced:
Classes: Templates that describe data and associated behaviors (e.g., methods).

  1. Objects: Instances of classes, each with its own state.
  2. Inheritance: Mechanism for sharing and extending behavior between classes.
  3. Encapsulation: Bundling data and methods, hiding implementation details from the user.
  4. Dynamic Binding: Enabling methods to be overridden in derived classes (later refined in other OOP languages). Impact on Simulation Simula allowed users to model real-world systems intuitively: Objects represented physical entities (e.g., cars, customers). Classes could inherit behaviors, making code reuse and extension easy.

Influence Beyond Simulation
Though developed for simulations, Simula's object-oriented features proved broadly applicable to any domain requiring complex systems.

Why Simula Was Revolutionary
Shift in Paradigm: Simula moved programming from procedural instructions to modeling real-world systems.
Reusability: The introduction of classes and inheritance allowed developers to write modular, reusable code.
Domain Modeling: Simula formalized the idea that programming should reflect the problem domain, not just computational logic.

Basis for Future Languages:
Direct influence on Smalltalk, C++, and other OOP languages.
Simula introduced the mental model of software as interacting objects, which persists in modern programming.

Simula demonstrated that programming could go beyond numbers and algorithms, ushering in a new era of software design focused on systems, interactions, and reuse.


Talking Small: OOP comes into its own

The next round of change would come from the development of SmallTalk. Invented in 1972, the same year as C, SmallTalk would revolutionize how OOP was concieved, not just because it was continued the ideas from Simula, but also because in SmallTalk, EVERYTHING was an object. In Simula, primitive types (like integers) were not objects, however in SmallTalk, everything was an object. Chars? Objects. Bools? Objects. Strings? You guessed it, it was an object.

Another thing about SmallTalk that made it different from other languages was that it was dynamically typed meaning that all type checking happened at runtime, rather than compile time. This would drastically impact future languages like Python and Javascript.
Another idea that would eventually impact so much of the development of OOP languages is that SmallTalk is compiled into intermediary bytecode and then ran by a virutal machine. Java would eventually do something similar with their JVM as well as Python and C#. This made SmallTalk portable, able to be executed on any device where a SmallTalk virtual machine can be run.

Within about 20 years, Object-Oriented Programming went from just a quirk of LISP programming, to a fully-fledged, integrated concept within the software development industry


Pros of Object-Oriented Programming

Like with any good programming paradigm, OOP has its pros and cons. I personally use C# for large, complex system design where state management and simulation is key (for example, an enterprise-level system or a video game), but would never use it for low-level, performant systems like an operating system or a compiler or interpreter (leave that to Rust or C). I would also never use it for highly mathematical or function-based systems like in finance or banking. Leave that to Haskell, OCaml, or some other functional language that can handle function composition and transformation, like you would find in Fintech systems.

  1. Modularity Through Encapsulation
    What It Is: Encapsulation bundles data (fields) and methods (functions) operating on the data into a single unit (class).
    Benefit:
    Keeps related functionality together, making code easier to understand and modify.
    Changes to an object's internal implementation do not affect external code that uses the object, as long as the interface remains the same.
    Example: A BankAccount class might encapsulate methods like deposit() and withdraw(), protecting its balance from unauthorized access.

  2. Reusability with Inheritance
    What It Is: Inheritance allows a new class (subclass) to derive properties and behaviors from an existing class (superclass).
    Benefits:
    Promotes code reuse by enabling the sharing of common functionality between classes.
    Reduces redundancy and accelerates development.
    Example: A Car class can inherit from a Vehicle class and reuse general methods like start() or stop(), while adding specific behavior like openTrunk().

  3. Flexibility with Polymorphism
    What It Is: Objects of different types can be treated uniformly through shared interfaces or parent classes.
    Benefit:
    Enhances flexibility by allowing code to operate on objects of different types without knowing their specific details.
    Simplifies code and reduces conditional logic.
    Example: A draw() method might work on any shape (Circle, Rectangle, etc.) without needing to know the specific type of shape.

  4. Improved Code Maintainability
    What It Is: OOP structures programs into smaller, more manageable units (classes and objects).
    Benefit:
    Easier to debug and maintain because individual classes can be tested and modified independently.
    Changes to one part of the codebase are less likely to propagate errors elsewhere.
    Example: Modifying a PaymentProcessor class doesn't require changes to other parts of the system, like Order or Customer.

  5. Enhanced Scalability
    What It Is: OOP is well-suited for complex systems by enabling the addition of new classes and features with minimal disruption to existing code.
    Benefit:
    Modular design makes large systems easier to extend.
    Promotes a clear separation of concerns.
    Example: In an e-commerce platform, adding a new payment method (like cryptocurrency) may only require creating a new class that adheres to the Payment interface.

  6. Real-World Modeling
    What It Is: OOP mirrors real-world entities by using objects that represent "things" with attributes and behaviors.
    Benefit:
    Makes it easier to reason about and design software systems.
    Aligns with human thinking and real-world analogies.
    Example: A Person object might have attributes like name and age, and behaviors like walk() or speak().

  7. Security Through Access Control
    What It Is: Access specifiers (e.g., private, protected, public) control how data and methods are accessed.
    Benefits:
    Prevents unintended access or modification of sensitive data.
    Ensures that an object's internal state is only modified in predictable ways.
    Example: A password field in a User class might be private, accessible only via methods like setPassword() and validatePassword().

  8. Easier Collaboration
    What It Is: OOP encourages modular design and separation of concerns.
    Benefit:
    Teams can work on different classes or modules without interfering with each other.
    Clear interfaces and responsibilities make it easier to divide work.
    Example: One team might develop the Authentication module while another focuses on ProductCatalog.

  9. Extensibility Through Design Patterns
    What It Is: OOP lays the groundwork for many design patterns (e.g., Singleton, Factory, Observer).
    Benefit:
    These patterns solve common software design problems, enabling efficient reuse of proven solutions.
    Example: A Factory pattern might simplify the creation of different types of objects (e.g., CarFactory producing Sedan or SUV).

  10. Dynamic Binding
    What It Is: Decisions about which method to invoke are made at runtime rather than compile time.
    Benefit:
    Enables more flexible and adaptive behavior.
    Facilitates polymorphism and runtime adaptability.
    Example: A variable of type Shape might invoke the draw() method of a Circle or Rectangle object, depending on the runtime type.


Criticisms of Object-Oriented Programming

  1. Overhead and Complexity
    What It Is: OOP often requires more code and structure compared to procedural or functional programming.
    Criticism:
    The abstraction layers (classes, objects, inheritance, etc.) can add unnecessary complexity to smaller projects.
    Over-engineering can result in verbose, harder-to-read code.
    Example: Creating a class hierarchy for a simple program like a calculator may be overkill compared to a procedural approach.

  2. Inflexibility Due to Rigid Hierarchies
    What It Is: Class inheritance creates tightly coupled relationships between parent and child classes.
    Criticism:
    Modifying a base class can inadvertently break or impact child classes.
    Deep inheritance hierarchies can be difficult to refactor and maintain.
    Example: A change in the behavior of a Vehicle superclass might cause unintended consequences in its subclasses (Car, Truck, Motorcycle).

  3. Encapsulation is Not Always Enforced
    What It Is: OOP relies on encapsulation to hide internal details, but it's not foolproof.
    Criticism:
    Developers can bypass encapsulation using reflection or other language features.
    Misuse of access modifiers (e.g., making everything public) can lead to poor design.
    Example: Exposing private data through poorly designed getter and setter methods can break encapsulation principles.

  4. Difficulty with Parallelism
    What It Is: OOP revolves around objects and their interactions, which can be at odds with parallel or distributed computing.
    Criticism:
    Managing mutable state within objects is challenging in concurrent environments.
    Functional programming paradigms (e.g., immutability) are often better suited for parallelism.
    Example: A shared object in a multithreaded application may require extensive locking mechanisms to avoid race conditions.

  5. Inheritance vs. Composition
    What It Is: OOP often emphasizes inheritance over composition (reusing classes by combining objects).
    Criticism:
    Inheritance can lead to fragile and overly coupled designs.
    Composition is more flexible but often underused in traditional OOP.
    Example: A Bird class inheriting from an Animal class might struggle with representing flightless birds. A "has-a" relationship (composition) might better model such cases.
    NOTE: There is more emphasis being placed in teaching composition alongside inheritance. Both are useful and have their place in the world of Object-Oriented Design.

  6. Performance Overhead
    What It Is: OOP languages and paradigms can introduce runtime inefficiencies.
    Criticism:
    Dynamic method dispatch (polymorphism) and abstraction layers add execution overhead.
    Object creation and garbage collection are resource-intensive compared to stack-based memory in procedural programming.
    Example: Virtual method calls in C++ or Java are slower than direct function calls in C.

  7. Difficulty with Functional or Data-Oriented Problems
    What It Is: OOP is object-centric, which doesn't always align with functional or data-oriented problem domains.
    Criticism:
    Functional programming excels in domains like data transformation, while OOP struggles with immutability and declarative constructs.
    Data-oriented programming (DOP) focuses on processing data structures efficiently, which can clash with OOP's encapsulation of state and behavior.
    Example: Transforming a large dataset using OOP may require excessive boilerplate code compared to functional languages like Python or Haskell.

  8. Misuse of Design Patterns
    What It Is: OOP often encourages the use of design patterns to solve common problems.
    Criticism:
    Over-reliance on design patterns can lead to unnecessarily complex solutions for simple problems.
    Developers may implement patterns dogmatically, even when simpler alternatives exist.
    Example: Using a Singleton pattern for a configuration object might introduce unnecessary constraints when a plain struct or dictionary would suffice.

  9. Tendency Toward "God Objects"
    What It Is: A "God Object" is an anti-pattern where a single class becomes too large and central to the system.
    Criticism:
    Violates principles like Single Responsibility and Separation of Concerns.
    Leads to tightly coupled, monolithic code that's hard to maintain.
    Example: A GameManager class that controls every aspect of a game, from rendering to input handling.

  10. Steep Learning Curve for Beginners
    What It Is: OOP concepts like polymorphism, inheritance, and design patterns can be challenging to grasp.
    Criticism:
    Beginners often struggle to understand and apply abstract concepts.
    Misunderstanding OOP principles can lead to poorly designed code.
    Example: A new developer might misuse inheritance to model unrelated entities, like making a Car inherit from Chair.

  11. Overemphasis on Objects
    What It Is: OOP focuses on modeling real-world entities as objects, which may not align with all problem domains.
    Criticism:
    Some problems are better solved with procedural, functional, or declarative approaches.
    Modeling everything as an object can lead to convoluted solutions.
    Example: Writing a Math class for simple arithmetic functions may unnecessarily complicate a straightforward task.


The Multiparadigmed Era

In response to these criticisms, languages like Java have had to evolve, sometimes forming new languages and frameworks as a result of the the shifting world of software development.
Kotlin, C# are prime examples of the slow march toward becoming multiparadigmed, even if OOP is still the primary methodological approach to their respective languages and frameworks.

Kotlin
Kotlin is by and large and extension of Java, a modernization of the language. It does something that Java cannot and accepts functions as first class and as higher-order, meaning you can have methods be parameters of other methods, store methods inside of variables, and return methods from other methods. 

Lambda functions are fully supported in Kotlin and domain-specific languages are fully supported, allowing for declarative behavior.

C#
Conversely, C# is the same language in many respects as it was 20 years ago, however with some key, notable differences. The .NET ecosystem has evolved completely from its origins, with features like LINQ that not only allow data calls from a database, but also brings functional, declarative practices to the language.

To also reduce initial boilerplate, C# can now run top-level statements that are compiled like top-level statements in any other language. You'll see more what I mean later on as I use C# as an example.

As well, C# now supports delegates, lambdas, dynamic typing, manual garbage collection when needed, "unsafe" mode, with manual memory management and pointers included.

Conclusion: OOP is cool and fun, I promise y'all

I love a variety of languages: C#, Go, OCaml, Rust, C; All are good languages in their own right. They all have their pros, cons, and quirks about them that make them different. In my opinion the worst thing you can do with a programming language is to write it off because someone told you it was bad. I did that once and now I work with C# on a daily basis in one way or another (and I LOVE it. Seriously). 

The goal of this post isn't to convince you that OOP is God's True Programming Paradigm, rather to show you in brief WHY OOP was developed as well as to give you reasons why people like/don't like it.

Whether or not you end up working in OOP, it is best to understand how it works as it touches every single area of tech today.

UP NEXT: The "4 Core" Principles of OOP. See You Then!

Top comments (0)