Ontological Stability: The Hidden Principle Behind the Correct Use of Inheritance

Software developers often debate whether inheritance is "good" or "bad." The truth is more nuanced than a simple binary choice. Inheritance isn't always good, and it isn't always bad—it is highly effective in some domains and a complete failure in others.

The secret to knowing when to use it lies in a concept called Ontological Stability.

What is Ontological Stability?

Ontology is a fancy word for a simple concept: a list of things within a specific subject and the connections between them.

Think of it as creating a "map" of everything important in a topic and showing how those things relate. It is:

  • A set of categories.
  • The rules that describe how those categories are linked.
  • Essentially, the "structure of knowledge" for that specific topic.

Ontological Stability refers to how much that map (the structure of knowledge) stays the same over time as your system grows.

Why Ontological Stability Matters for Inheritance

Inheritance is built on several key assumptions:

  1. The hierarchy structure is correct.
  2. The "is-a" relationship is true and remains true.
  3. A subclass will not behave in a way that contradicts its parent.
  4. The domain will not change so drastically that the hierarchy breaks.

If your domain's ontology is "stable," inheritance works beautifully. If it is "unstable" (meaning the definitions of things change frequently), inheritance becomes a nightmare of technical debt.


Modeling Examples: Stable vs. Unstable

Here are simple Object-Oriented examples (using Scala) to demonstrate stable vs. unstable inheritance through the lens of ontological stability.

1. Stable (Good)

In this example, the base class captures a constant "invariant" meaning.

sealed trait Vehicle {
  def maxSpeedKmh: Int
}

final case class Car(maxSpeedKmh: Int) extends Vehicle
final case class Bike(maxSpeedKmh: Int) extends Vehicle

Why it is stable:

  • A Vehicle is defined simply as "something that has a maximum speed."
  • You can add new types (Truck, Scooter, Plane) without changing the existing hierarchy or the definition of the parent.
  • The "is-a" relationship (A Car is-a Vehicle) remains logically sound.

2. Unstable (Bad)

In this example, the base class is too broad, or its meaning is tied to attributes that change across different types.

sealed trait Vehicle {
  def maxSpeedKmh: Int
  def numWheels: Int // This is the problem
}

final case class Car(maxSpeedKmh: Int, numWheels: Int) extends Vehicle
final case class Bike(maxSpeedKmh: Int, numWheels: Int) extends Vehicle

Why it is unstable:

  • By adding numWheels to the base Vehicle, you have made a specific ontological claim: "All vehicles must have wheels."
  • The Breakdown: What happens when you need to add a Sled (no wheels) or a Boat?
  • To accommodate these, you would have to change the parent class or give the Boat a nonsensical value like numWheels = 0.
  • The ontology was not stable because the definition of "Vehicle" was too specific to certain sub-categories.

This is the translation of your deep dive into domains that benefit from Ontological Stability and how to model them correctly using inheritance.


Examples of Domains with Ontological Stability (Suitable for Inheritance)

A business domain is a prime candidate for Ontological Stability if its "Core Concepts" are slow to change and have a long lifespan. In these industries, changing a core definition can break hundreds of interconnected systems.

High-Stability Domains:

Banking & Finance: Concepts like Account, Transaction, Ledger, and Instrument are foundational and strictly defined.

  • Insurance: Policy, Claim, Coverage, and Premium are regulated definitions that have remained consistent for decades.
  • Healthcare: Patient, Encounter, Diagnosis, and Procedure must have stable meanings to ensure interoperability between different hospital systems.
  • Supply Chain & Logistics: Shipment, Package, Route, and Warehouse form the physical and digital infrastructure.
  • Legal & Government: Citizen, Permit, Case, and Statute require clear, legally-binding, and stable definitions.

Case Study: Banking Transactions

In banking, an Account is ontologically stable because its core definition—a "financial container with a balance"—is universally understood and regulated.

The Stable Base Model

Scala

sealed trait Account {
  def accountId: String
  def balance: BigDecimal
}

final case class Checking(accountId: String, balance: BigDecimal) extends Account
final case class Savings(accountId: String, balance: BigDecimal) extends Account
final case class LoanAccount(accountId: String, balance: BigDecimal) extends Account

Why this domain is stable:

  1. Regulated: Transaction types are governed by law.
  2. Immutable Meaning: An "Account" always implies a balance and an ID.
  3. Clear Relationships: The hierarchy is logically sound; no subtype contradicts the parent.
  4. Extensible: You can add new types (like CreditCardAccount) without ever touching the base Account trait.

The "Bad" Way: Polluting the Base Class

A common mistake occurs when developers try to force-fit attributes that don't apply to every subtype into the base class. Let's look at the "Interest Rate" trap:

// ❌ BAD PRACTICE: Putting specific attributes in the base trait
sealed trait Account {
  def accountId: String
  def balance: BigDecimal
  def interestRate: BigDecimal // This assumes ALL accounts have interest
}

final case class Savings(accountId: String, balance: BigDecimal, interestRate: BigDecimal) extends Account
final case class Checking(accountId: String, balance: BigDecimal, interestRate: BigDecimal) extends Account // Awkward!

The Breakdown: Now, Account mandates that an interest rate exists for every account. This is factually untrue for many Checking or LoanAccount types. Developers often end up "hacking" this by setting interestRate = 0, which is a sign of poor modeling and low ontological stability.


The "Good" Way: Decoupled Capabilities

In a stable banking ontology, interest rates should exist at the Product or Capability level, not in the base Account class. Here are two superior approaches:

Option A: Modular Capabilities (Traits/Mixins)

Only apply the "Interest" capability to accounts that actually support it.

sealed trait Account {
  def accountId: String
  def balance: BigDecimal
}

trait InterestBearing {
  def interestRate: BigDecimal
}

final case class Savings(accountId: String, balance: BigDecimal, interestRate: BigDecimal)
  extends Account with InterestBearing

final case class Checking(accountId: String, balance: BigDecimal) 
  extends Account

Option B: Product-Based Modeling (Composition)

Link the account to a "Product" definition that holds the variable rules.

final case class Product(id: String, interestRate: BigDecimal)

final case class Savings(accountId: String, balance: BigDecimal, product: Product) 
  extends Account

Core Takeaway

Ontological Stability is the ultimate litmus test for inheritance.

Never put an attribute in a base class just because "most" children use it. Only put it there if it defines the very essence of the parent. If a concept like "Interest Rate" is variable, treat it as a modular capability or an external product rule. This keeps your hierarchy stable and your code resilient to change.

Summary

The decision to use inheritance should not be based on a desire to "save code" or "reuse functions." It should be based on the stability of the relationship.

  • Use Inheritance when the relationship between the parent and child is an immutable truth within your domain (High Ontological Stability).
  • Use Composition when the relationship is based on shared features or behaviors that might change or don't apply to every potential subclass (Low Ontological Stability).

If you build your hierarchy on a foundation that isn't ontologically stable, your code will eventually collapse under the weight of its own assumptions.

Read more

ทำไมผมถึงเขียน Brag document

ทำไมผมถึงเขียน Brag document

มาทำความรู้จัก brag document กันก่อน ถ้าแปลตรงตัว brag document คือเอกสารที่เอาไว้อวด เป็นเอกสารส่วนตัว ที่แต่ละคนเอาไว้จด ว่าฉันทำอะไรเจ๋ง ๆ มาบ้าง หรือได้สร้าง impact อะไรให้กับองค์กร จดคำชมที่ได้รับ หรือบันทึกการเติบโตของตั

By Chokchai

Run e2e tests บน TravisCI (ถึงกับต้อง blog อ่ะ)

ขณะที่ผมกำลังทำ Perlclip เป็น Prograssive Web App เพื่อให้ใช้บนมือถือได้นั้น ผมก็พบว่ามัน run e2e test บน travis ไม่ได้ ลองมาดู log ตอน error กัน Error ไม่ค่อยสื่อเท่าไหร่ รู้แค่ว่ามัน start selenium server ไม่ได้

By Chokchai

Run robotframework ใน docker

บ่อยครั้งที่ผมอยากจะ run robotframework ใน docker ความยากของงานนี้คือ มันมี public image เต็มเลย แต่ส่วนใหญ่ใช้งานไม่ได้แล้ว ณ วันนี้ อันที่ผมลองแล้วยังใช้ได้อยู่มี 2 อันคือ ppodgorsek/docker-robot-frameworkdocker-robot-framework - Robot Framework in Dockergithub.comjuacompe/robot-docker-chrome-alpineContribute to

By Chokchai

เวลา co-train ผมทำอะไร

ช่วงที่ผ่านมา ผมมีโอกาสได้ co-train กับ trainer หลายคนในหลายๆคอร์ส เช่น บาส, เจน, พี่รูฟ, เก๋, พี่อู, นิ้ง เป็นต้น และหลังแต่ละครั้งที่ train ก็มีโอกาสได้นั่งคุยกับผู้ร่วมสอน ว่าตอนที่ผมไม่ได้จับไมค์ มีอะไรที่ผมทำได้

By Chokchai