Types of Programming

Object-Oriented Programming

Organising code around objects that combine state and behaviour.

Overview

Object-Oriented Programming organises code around objects that bundle state (fields) and behaviour (methods). The four pillars are encapsulation, abstraction, inheritance, and polymorphism. Alan Kay coined the term in the late 1960s at Xerox PARC while designing Smalltalk; he later said he regretted the focus on classes rather than message-passing.

Origin

Simula 67, designed by Ole-Johan Dahl and Kristen Nygaard, introduced classes and objects to model discrete-event simulations. Smalltalk (1972) refined the model. C++ (1983) brought it to systems programming; Java (1995) made it the dominant enterprise paradigm.

Examples

Polymorphism via inheritance in Ruby

class Shape
  def area
    raise NotImplementedError, '#{self.class} must implement area'
  end
end

class Circle < Shape
  def initialize(radius)
    @radius = radius
  end

  def area
    Math::PI * @radius ** 2
  end
end

class Rectangle < Shape
  def initialize(width, height)
    @width = width
    @height = height
  end

  def area
    @width * @height
  end
end

shapes = [Circle.new(5), Rectangle.new(4, 6)]
shapes.each { |s| puts s.area }

Each subclass satisfies the Shape contract without the caller knowing the concrete type. Adding a Triangle requires no changes to existing code.

Encapsulation with TypeScript private fields

class BankAccount {
  readonly id: string;
  #balance: number;

  constructor(id: string, initialBalance: number) {
    this.id = id;
    this.#balance = initialBalance;
  }

  deposit(amount: number): void {
    if (amount <= 0) throw new RangeError('Deposit must be positive');
    this.#balance += amount;
  }

  withdraw(amount: number): void {
    if (amount > this.#balance) throw new Error('Insufficient funds');
    this.#balance -= amount;
  }

  get balance(): number {
    return this.#balance;
  }
}

const account = new BankAccount('acc-001', 1000);
account.deposit(500);
console.log(account.balance); // 1500

The # prefix uses the ECMAScript private class fields proposal (stage 4, supported in Node 12+). Unlike TypeScript private keyword, # is enforced at runtime, not just compile time.

Use Cases

  • 01Modelling real-world domains with distinct entities (User, Order, Product) that have clear identity and lifecycle
  • 02Building GUI frameworks where widgets form class hierarchies with shared rendering behaviour
  • 03Game development where entities share attribute trees but specialise behaviour (Enemy extends Character)
  • 04Large team codebases where class contracts serve as explicit interfaces between contributors

When Not to Use

  • //Data transformation pipelines where state is mutable shared burden rather than asset; functional pipelines are cleaner
  • //Simple scripts or CLI tools where a flat set of functions is less ceremony and equally maintainable
  • //High-throughput numerical or scientific computing where struct-of-arrays data layouts and cache coherence matter more than encapsulation

Technical Notes

  • Ruby uses open classes and mixins (modules) rather than multiple inheritance; this avoids the diamond problem while enabling flexible composition via include and prepend
  • Java's early bet on single-rooted hierarchies (Object) made reflection-based frameworks like Spring possible but created the "inheritance vs composition" debate that Effective Java (Bloch, 2001) popularised
  • Prototype-based OOP in JavaScript (pre-ES6) is mechanically different: objects inherit from other objects directly, not classes; ES6 class syntax is syntactic sugar over prototype chains
  • The Liskov Substitution Principle (Barbara Liskov, 1987) is the formal constraint that makes polymorphism safe: subtypes must be substitutable for their base types without altering program correctness