Class Based Inheritance and the Real World
11 min read

Class Based Inheritance and the Real World

In Object Oriented Programming, inheritance is the mechanism of extending functionality and type from one class to another. Class-based inheritance is not just a useful concept in OOP, it’s also how a typical bourgeois society is organised.

In Object Oriented Programming, class based inheritance is a fundamental concept. It's one of the first things you learn in any programming course. It's also a common topic for programming job interview questions or exercises.

Inheritance is the mechanism of extending functionality and type from one class to another. I will not go very much into explaining how inheritance works. There are at the moment 65 Million search results on Google for the term “class based inheritance” so I am assuming that the topic is well explained already. Rather I will just pick the MDN article which explains it for JavaScript, using the classic “Person” class.

The "Person" class

Here’s how a very basic Person class looks like. I’ve taken this from MDN but slightly modified it to better suit this article:

class Person {
  constructor({first, last, occupation}) {
    this.name = {
      first,
      last
    };
    this.occupation = occupation;
  }

  greeting() {
    console.log(`Hi! I'm ${this.name.first}`);
  }
}

I have picked JavaScript as the language for the code samples because it’s the language with which I am most familiar and also because it’s my favourite programming language when it comes to inheritance. However, we will use TypeScript to demonstrate the class hierarchy.

The class-based inheritance mechanism is not just a very useful concept in OOP, it’s also how a typical bourgeois society is organized. This is also known as a “social democracy” and it’s how most countries are structured in the Western world.

In a typical social democracy, there are three main social classes: working class, middle class, and the upper class. The hierarchy between classes is maintained mostly through force, which is implemented by the Justice system. As the English philosopher Thomas Hobbes argues in his work Leviathan, a strong centralized power is required to enforce laws and maintain order in a society where individuals have the right to private property (Hobbes, Leviathan, 1651).

Considering our previous Person class, using TypeScript we can write the class base hierarchy in a social democracy. First, let’s write the Person class using an interface:

interface PersonInterface {
  name: {
    first: string;
    last: string;
  };
  occupation: string;
  description(): string;
  fullName(): string;
}

class Person implements PersonInterface {
  constructor(public name: {first: string; last: string}, public occupation: string) {}

  fullName(): string {
    return `${this.name.first} ${this.name.last}`;
  }

  description(): string {
    return `Person ${this.fullName()} is a ${this.occupation}`;
  }
}

In the typical capitalist bourgeois society, the social classes are mostly economic constructs, and so they are structured around income and other economic outputs. Therefore, we will extend our base Person class and add a method called income().

Social class hierarchy

We’ll start with the WorkingClass, which is the most straightforward because usually the only source of income for an instance of this class is the salary.

interface WorkingClassInterface extends PersonInterface {
  salary: number;
  income(): number;
}

class WorkingClass extends Person implements WorkingClassInterface {
  constructor(name: {first: string; last: string}, occupation: string, public salary: number) {
    super(name, occupation);
  }

  income(): number {
    return this.salary;
  }
}

Then we’ll move on to the MiddleClass, or the bourgeois as it is sometimes called. A member of the MiddleClass is usually inheriting some kind of assets, like properties, vacation homes, shares in businesses, etc. So when calculating the income, we must also include additional income generated from assets, such as rent received from additional properties, or dividends from shares in businesses, etc.

interface Asset {
  type: string;
  value: number;
}

interface MiddleClassInterface extends PersonInterface {
  assets: Asset[];
  income(): number;
}

class MiddleClass extends Person implements MiddleClassInterface {
  assets: Asset[];
  salary: number;

  constructor({first, last, occupation, salary, assets}: {first: string, last: string, occupation: string, salary: number, assets: Asset[]}) {
    super({first, last, occupation});
    this.salary = salary;
    this.assets = assets;
  }

  assetsValue(): number {
    return CalculateValue(this.assets);
  }

  income(): number {
    return this.assetsValue() + this.salary;
  }
}

function CalculateValue(assets: Asset[]): number {
  // return the current value of assets
  return assets.reduce((total, asset) => total + asset.value, 0);
}

And finally, we arrive at the UpperClass, or the elites, as they are sometimes known. It is without doubt the most aspired-to social class. Aristocracy was a favorite of Aristotle, who believed that those with wealth and noble birth were naturally suited to rule (Aristotle, Politics, 350 BCE). Rousseau, the Swiss-born French philosopher of the Enlightenment period, on the other hand, was more critical of the upper classes and the concept of private property, asserting that it was the root of inequality and social ills (Rousseau, Discourse on the Origin of Inequality, 1755).

Here’s how the UpperClass will look like:

type Asset = {
  type: 'property' | 'stock' | 'bond' | 'business';
  value: number;
  income?: number;
  capitalGains?: number;
  capitalLosses?: number;
};

function CalculateValue(assets: Asset[]): number {
  return assets.reduce((acc, asset) => acc + asset.value, 0);
}

function CalculateIncome(assets: Asset[]): number {
  return assets.reduce((acc, asset) => {
    const income = (asset.income || 0) + (asset.capitalGains || 0) - (asset.capitalLosses || 0);
    return acc + income;
  }, 0);
}

abstract class UpperClassAbstract extends Person {
  assets: Asset[];
  salary?: number;

  assetsValue(): number {
    return CalculateValue(this.assets);
  }

  assetsIncome(): number {
    return CalculateIncome(this.assets);
  }

  abstract income(): number;
}

class UpperClass extends UpperClassAbstract {
  constructor({first, last, occupation, salary, assets}: {first: string, last: string, occupation: string, salary?: number, assets: Asset[]}) {
    super({first, last, occupation});
    this.salary = salary;
    this.assets = assets;
  }

  income(): number {
    if (!this.salary) {
      // sometimes upper class members don't need a salary
      return this.assetsIncome();
    }

    return this.assetsIncome() + this.salary;
  }
}

Abstract classes

We have used an abstract class for the UpperClass instead of an interface. While interfaces merely define the structure of an object, abstract classes can include actual implementation details, in our case the inherited assets.

When transitioning from the MiddleClassInterface to the UpperClassAbstract class, it is crucial to recognize the problematic nature of wealth accumulation in capitalist bourgeois society. The stark contrast between the middle and upper classes often stems from the significant differences in their assets' scale, diversity, and complexity.

abstract class UpperClassAbstract extends Person {
  assets: Asset[];
  salary?: number;

  assetsValue(): number {
    return CalculateValue(this.assets);
  }

  assetsIncome(): number {
    return CalculateIncome(this.assets);
  }

  abstract income(): number;
}

For instance, the UpperClassAbstract class may need to account for various types of assets such as real estate properties, business investments, stocks, bonds, and even artwork or luxury goods, each with different income generation methods and growth rates. Upper-class individuals often leverage their wealth to exploit market inefficiencies and employ advanced financial instruments and strategies that enable them to accumulate even more wealth, perpetuating income inequality.

To accommodate these complexities, the Asset type in the UpperClassAbstract class should be designed to handle a broader range of properties and functions, showcasing the inherent imbalance in resource allocation between social classes.

Type Aliases

In TypeScript, a type alias allows you to define a new name for a particular type, and you can use it to define more complex types, such as object shapes, union types, intersection types, and more. In our case, we have defined an Asset type to represent various types of assets an upper-class individual might possess. The Asset type requires the incorporation of additional properties, such as asset types or growth rates, and methods to calculate income or value based on the specific characteristics of each asset.

type Asset = {
  type: 'property' | 'stock' | 'bond' | 'business';
  value: number;
  income?: number;
  capitalGains?: number;
  capitalLosses?: number;
};

The primary difference between a type alias and an interface is that a type alias can represent any valid TypeScript type, while an interface is specifically for defining the shape of an object. Interfaces can be extended and implemented by classes, whereas type aliases cannot.

Another distinction is that interfaces can be merged when they share the same name, while type aliases cannot. This means that if you have two interfaces with the same name, TypeScript will automatically merge them into a single interface with all properties. This behavior does not apply to type aliases.

In the context of our UpperClassAbstract class, using a type alias for the Asset type allows us to describe the shape of an asset object concisely without the need for extending or implementing it in a class.

On Inheritance

Rousseau, ever the critic of social inequality, argued that the concept of inheritance was one of the primary reasons for the disparity between the rich and the poor. He believed that accumulated wealth in the hands of a few only served to perpetuate social divisions. In his view, a more egalitarian society would be one where inheritance was limited or eliminated altogether.

On the other hand, John Locke, a prominent English philosopher of the Enlightenment era and a strong advocate for private property rights, considered inheritance a natural extension of those rights. He argued that individuals had the right to accumulate and dispose of their property as they saw fit, including passing it on to their heirs. However, even Locke acknowledged that there should be some limits to accumulation to prevent the excessive concentration of wealth and power.

Now let's examine the paradoxical relationship between wealth creation and wealth distribution in capitalist societies and how the concept of inheritance plays a critical role in this system.

In his book Talking to My Daughter about the Economy (2013), Yanis Varoufakis, a Greek economist and politician, explains that wealth is generated through labour, which is usually performed by the working class. They engage in various activities, from manual labour to providing services, that create tangible value and drive economic growth. The fruits of their labour are then commodified and exchanged in the marketplace, generating revenue for the companies they work for.

However, despite being the very foundation of wealth creation, the working class often receives only a small portion of the value they produce. Their wages are typically low, and they have limited access to benefits or job security. In contrast, the middle and upper classes, who own the means of production, extract the surplus value generated by the working class, accumulating wealth and resources that far exceed their own contributions to the economy.

This skewed distribution of wealth is further exacerbated by the fact that the upper class, or the elites, often have the power to influence economic policies and regulations in their favor. This can result in tax breaks, loopholes, and other advantages that allow them to maintain and grow their wealth with minimal effort. Consequently, the wealth generated by the working class is siphoned upwards, accumulating at the top of the pyramid where it is disproportionately consumed by the very few.

The concept of inheritance influences the distribution of wealth and opportunities, and shapes the access individuals have to resources such as education. Inequalities in inherited wealth contribute to disparities in access to quality education, perpetuating social stratification and limiting the ability of individuals from less privileged backgrounds to accumulate wealth and advance in society.

Inheriting from Multiple Classes

Multiple inheritance is a feature in which a class can inherit behaviors and properties from more than one superclass. However, TypeScript/JavaScript, like many other programming languages, does not support multiple inheritance directly. Instead, it relies on alternative mechanisms such as mixins and interfaces to achieve similar functionality.

Let's try to demonstrate the concept of multiple inheritance through the use of taxation in a social democracy. Taxation aims to balance market capitalism with strong social policies. It plays a crucial role in financing public goods and services, promoting income redistribution in order to reduce income inequality. Although Rousseau argued that the wealthy may use social welfare programs as a way to justify their wealth and power while also relieving themselves of any social responsibilities. He believed that true equality and justice could only be achieved by dismantling the social hierarchies that maintained the wealth and power of the privileged few.

We will not attempt to dismantle the social order just yet, so for now let's just define the base interface for taxation and the individual interfaces for different types of taxation:

// Base Taxation interface
interface BaseTaxation {
  calculateTax(): number;
}

// Income Tax interface
interface IncomeTax extends BaseTaxation {
  readonly incomeTaxRate: number;
}

// Dividend Tax interface
interface DividendTax extends BaseTaxation {
  readonly dividendTaxRate: number;
}

// Capital Gains Tax interface
interface CapitalGainsTax extends BaseTaxation {
  readonly capitalGainsTaxRate: number;
}

// Inheritance Tax interface
interface InheritanceTax extends BaseTaxation {
  readonly inheritanceTaxRate: number;
}

Now let's create mixin functions that implement each of these tax interfaces:

// Helper type for mixin constructors
type Constructor<T = {}> = new (...args: any[]) => T;

// Income Tax mixin
const IncomeTaxMixin = <TBase extends Constructor>(Base: TBase) =>
  class extends Base {
    readonly incomeTaxRate = 0.25;

    calculateTax() {
      // Calculate income tax
    }
  };

// Dividend Tax mixin
const DividendTaxMixin = <TBase extends Constructor>(Base: TBase) =>
  class extends Base {
    readonly dividendTaxRate = 0.15;

    calculateTax() {
      // Calculate dividend tax
    }
  };

// Capital Gains Tax mixin
const CapitalGainsTaxMixin = <TBase extends Constructor>(Base: TBase) =>
  class extends Base {
    readonly capitalGainsTaxRate = 0.20;

    calculateTax() {
      // Calculate capital gains tax
    }
  };

// Inheritance Tax mixin
const InheritanceTaxMixin = <TBase extends Constructor>(Base: TBase) =>
  class extends Base {
    readonly inheritanceTaxRate = 0.40;

    calculateTax() {
      // Calculate inheritance tax
    }
  };

We can also use a utility function to compose multiple mixins:

function composeMixins<TBase extends Constructor>(Base: TBase, mixins: Constructor[]) {
  return mixins.reduce((baseClass, mixin) => mixin(baseClass), Base);
}


Finally, let's apply the appropriate tax mixins to our existing social classes to achieve multiple inheritance:

// Applying multiple inheritance using mixins
class WorkingClassTaxPayer extends IncomeTaxMixin(WorkingClass) {}
class MiddleClassTaxPayer extends DividendTaxMixin(IncomeTaxMixin(MiddleClass)) {}
class UpperClassTaxPayer extends composeMixins(UpperClass, [IncomeTaxMixin, DividendTaxMixin, CapitalGainsTaxMixin, InheritanceTaxMixin]) {}


const workingClassTaxPayer = new WorkingClassTaxPayer();
const middleClassTaxPayer = new MiddleClassTaxPayer();
const upperClassTaxPayer = new UpperClassTaxPayer();

Overriding the calculateTax method

It's not really a controversial matter to say that the upper classes don't usually pay that much tax and to illustrate how members of the UpperClassTaxPayer may use complicated tax breaks and schemes to avoid paying tax, we will override the calculateTax method in the UpperClassTaxPayer.

Beside complicated tax breaks and other schemes, the UpperClassTaxPayer may also use tax havens to avoid paying taxes in their home countries. as havens are countries or jurisdictions that offer ultra-low tax rates and secrecy laws to attract wealthy individuals and corporations.

In his book The Global Minotaur (2011), Yanis Varoufakis has criticized the global financial system and the role of tax havens in perpetuating wealth inequality. Varoufakis highlights how the wealthy exploit the loopholes in the international tax system, resulting in a loss of revenue for countries that could have been used for public services and social welfare.

Here's an example of the UpperClassTaxPayer class which overrides the calculateTax method:

class UpperClassTaxPayer extends composeMixins(UpperClass, [
  IncomeTaxMixin,
  DividendTaxMixin,
  CapitalGainsTaxMixin,
  InheritanceTaxMixin,
]) {
  // Override the calculateTax method to account for tax breaks, schemes, and tax havens
  calculateTax() {
    let totalTax = 0;
    for (const mixin of [IncomeTaxMixin, DividendTaxMixin, CapitalGainsTaxMixin, InheritanceTaxMixin]) {
      const instance = new mixin(this);
      const taxAmount = instance.calculateTax();

      // Apply tax breaks and schemes for the upper class
      const taxBreak = this.calculateTaxBreak(taxAmount);

      // Use tax havens to further reduce tax liability
      const taxHavenReduction = this.useTaxHaven(taxAmount - taxBreak);

      totalTax += taxAmount - taxBreak - taxHavenReduction;
    }
 
    return totalTax;
  }

  // Calculate tax break based on the tax amount
  private calculateTaxBreak(taxAmount: number): number {
    // Logic for calculating tax breaks and schemes
    const taxBreak = taxAmount * 0.5; // Assume a 50% tax break for illustration purposes
    return taxBreak;
  }

  // Use tax havens to further reduce tax liability
  private useTaxHaven(taxAmount: number): number {
    // Logic for using tax havens to avoid tax
    const taxHavenReduction = taxAmount * 0.9; // Assume a 90% reduction in tax liability through tax havens for illustration purposes
    return taxHavenReduction;
  }
}


Ironically, the very tech companies that employ software developers who might be reading this article often employ sophisticated measures and schemes to avoid paying their fair share of taxes. For instance, many US tech companies, like Apple, Google, Facebook, and Amazon have offices in Ireland to take advantage of lower corporate tax rates, transfer pricing strategies, and lenient regulations. This allows them to minimize their tax liabilities, even as they generate billions in revenue.

In 2016, the European Commission ruled that Apple had received illegal state aid from Ireland, leading to €13 billion ($14.5 billion) in unpaid taxes. Although Apple and the Irish government appealed the decision, the case highlights the complex tax arrangements that multinational corporations use to minimize their tax liabilities.

Alternatives to Class-based inheritance

In conclusion, class-based inheritance allow us to model our software architecture after the aristocratic norms of centuries past, using the rigid hierarchies and social inequalities it mirrors. It offers a delightful opportunity to perpetuate a system that resists change and adaptability.

However, for those daring souls who seek to break free from the chains of tradition, the alternative paradigms of prototypal inheritance and functional programming offer a tantalizing taste of equality and flexibility. Prototypal inheritance, with its emphasis on dynamic relationships between objects, serves as a beacon of hope for those who yearn for a more egalitarian approach in their code.

Similarly, functional programming, with its focus on pure functions and immutability, offers a world where side effects are shunned and state manipulation is frowned upon. By embracing these paradigms, developers can create applications that are not only more maintainable and robust, but also embody the principles of fairness and adaptability that we aspire to in a just society.

Here's an example which uses prototypal inheritance to abolish the rigid social classes and challenge the status quo:

const taxPayerPrototype = {
  init: function (income) {
    this.income = income;
    return this;
  },
  calculateTax: function (taxRate) {
    return this.income * taxRate;
  }
};

function createTaxPayer(income) {
  return Object.create(taxPayerPrototype).init(income);
}

function getTaxRate(income) {
  if (income <= 30000) {
    return 0.1;
  } else if (income <= 80000) {
    return 0.2;
  } else {
    return 0.3;
  }
}

const taxPayerInstance1 = createTaxPayer(30000);
console.log(taxPayerInstance1.calculateTax(getTaxRate(taxPayerInstance1.income)));

const taxPayerInstance2 = createTaxPayer(80000);
console.log(taxPayerInstance2.calculateTax(getTaxRate(taxPayerInstance2.income)));

const taxPayerInstance3 = createTaxPayer(150000);
console.log(taxPayerInstance3.calculateTax(getTaxRate(taxPayerInstance3.income)));

In this enlightened age of pure JavaScript, we've liberated ourselves from the shackles of class-based thinking and the rigid structure that TypeScript so keenly enforces. With the power of prototypal inheritance and functional programming in our hands, we no longer require the strictures of TypeScript to create flexible and adaptable code that promotes equality and shared prosperity.

Indeed, our approach aligns perfectly with the vision of Rousseau, who dared to imagine a society where individuals are not confined by the arbitrary distinctions of social class. By rising above these limitations in our code, we wholeheartedly adopt the egalitarian ideals that Rousseau fervently advocated. In doing so, we construct software that aligns more closely with the noble endeavor of fostering human growth and well-being.