Освобождаемся от оков классового мышления и жесткой структуры, которую так сильно поддерживает TypeScript.

В объектно-ориентированном программировании наследование на основе классов является фундаментальной концепцией. Это одна из первых вещей, которую вы изучаете на любом курсе программирования. Это также распространенная тема для вопросов или упражнений на собеседовании по программированию.

Наследование — это механизм расширения функциональности и типа от одного класса к другому. Я не буду подробно объяснять, как работает наследование. На данный момент в Google есть 65 миллионов результатов поиска по термину наследование на основе классов, поэтому я предполагаю, что тема уже хорошо объяснена. Скорее я просто выберу статью MDN, которая объясняет это для JavaScript, используя классический класс Person.

Класс «Человек»

Вот как выглядит очень простой класс Person. Я взял это из MDN, но немного изменил, чтобы лучше соответствовать этой статье:

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

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

Я выбрал JavaScript в качестве языка для примеров кода, потому что это язык, с которым я лучше всего знаком, а также потому, что это мой любимый язык программирования, когда речь идет о наследовании. Однако мы будем использовать TypeScript для демонстрации иерархии классов.

Механизм классового наследования — это не только очень полезная концепция в ООП, но и то, как организовано типичное буржуазное общество. Это также известно как «социальная демократия», и именно так устроено большинство стран западного мира.

В типичной социал-демократии есть три основных социальных класса: рабочий класс, средний класс и высший класс. Иерархия между классами поддерживается в основном за счет силы, которая реализуется системой Правосудия. Как утверждает английский философ Томас Гоббс в своей работе Левиафан, для обеспечения соблюдения законов и поддержания порядка в обществе, где люди имеют право на частную собственность, требуется сильная централизованная власть (Гоббс, Левиафан, 1651).

Учитывая наш предыдущий класс Person, используя TypeScript, мы можем написать базовую иерархию классов в социал-демократии. Во-первых, давайте напишем класс Person, используя интерфейс:

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}`;
  }
}

В типичном капиталистическом буржуазном обществе социальные классы в основном представляют собой экономические конструкции, и поэтому они структурированы вокруг дохода и других экономических результатов. Поэтому мы расширим наш базовый класс Person и добавим метод с именем income().

Иерархия социального класса

Мы начнем с WorkingClass, который является самым простым, потому что обычно единственным источником дохода экземпляра этого класса является зарплата.

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;
  }
}

Затем перейдем к MiddleClass, или, как его иногда называют, буржуйскому. Член MiddleClass обычно наследует какие-то активы, такие как недвижимость, дома для отдыха, доли в бизнесе и т. д.

Поэтому при расчете дохода мы также должны учитывать дополнительный доход, полученный от активов, например, арендную плату, полученную от дополнительной собственности, или дивиденды от акций в бизнесе и т. д.

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);
}

И, наконец, мы приходим к UpperClass, или элитам, как их иногда называют. Это, без сомнения, самый желанный социальный класс. Аристократия была фаворитом Аристотеля, который считал, что те, у кого есть богатство и знатное происхождение, естественным образом подходят для правления ( Аристотель, Политика, 350 г. до н.э.).

Руссо, французский философ эпохи Просвещения швейцарского происхождения, с другой стороны, был более критичен по отношению к высшим классам и концепции частной собственности, утверждая, что она является корнем неравенства и социальных болезней (Руссо , Рассуждение о происхождении неравенства, 1755 г.).

Вот как будет выглядеть UpperClass:

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;
  }
}

Абстрактные классы

Мы использовали абстрактный класс для UpperClass вместо интерфейса. В то время как интерфейсы просто определяют структуру объекта, абстрактные классы могут включать фактические детали реализации, в нашем случае унаследованные активы.

При переходе от класса MiddleClassInterface к классу UpperClassAbstract крайне важно признать проблематичный характер накопления богатства в капиталистическом буржуазном обществе. Разительный контраст между средним и высшим классами часто возникает из-за значительных различий в масштабах, разнообразии и сложности их активов.

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

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

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

  abstract income(): number;
}

Например, классу UpperClassAbstract может потребоваться учитывать различные типы активов, такие как недвижимость, инвестиции в бизнес, акции, облигации и даже произведения искусства или предметы роскоши, каждый из которых имеет разные методы получения дохода и темпы роста.

Люди из высшего класса часто используют свое богатство, чтобы использовать неэффективность рынка и использовать передовые финансовые инструменты и стратегии, которые позволяют им накапливать еще больше богатства, увековечивая неравенство в доходах.

Чтобы приспособиться к этим сложностям, тип Asset в классе UpperClassAbstract должен быть спроектирован так, чтобы обрабатывать более широкий спектр свойств и функций, демонстрируя присущий дисбаланс в распределении ресурсов между социальными классами.

Псевдонимы типов

В TypeScript псевдоним типа позволяет вам определить новое имя для определенного типа, и вы можете использовать его для определения более сложных типов, таких как формы объектов, типы объединения, типы пересечения и т. д.

В нашем случае мы определили тип Asset для представления различных типов активов, которыми может обладать человек из высшего общества.

Тип Asset требует включения дополнительных свойств, таких как типы активов или темпы роста, а также методы расчета дохода или стоимости на основе конкретных характеристик каждого актива.

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

Основное различие между псевдонимом типа и интерфейсом заключается в том, что псевдоним типа может представлять любой допустимый тип TypeScript, а интерфейс предназначен специально для определения формы объекта. Интерфейсы могут быть расширены и реализованы классами, тогда как псевдонимы типов не могут.

Еще одно отличие заключается в том, что интерфейсы могут быть объединены, если они имеют одно и то же имя, а псевдонимы типов — нет. Это означает, что если у вас есть два интерфейса с одинаковыми именами, TypeScript автоматически объединит их в один интерфейс со всеми свойствами. Это поведение не применяется к псевдонимам типов.

В контексте нашего класса UpperClassAbstract использование псевдонима типа для типа Asset позволяет нам кратко описать форму объекта актива без необходимости его расширения или реализации в классе.

О наследстве

Руссо, всегда критиковавший социальное неравенство, утверждал, что концепция наследования была одной из основных причин неравенства между богатыми и бедными. Он считал, что накопленное богатство в руках немногих лишь увековечивает социальные разногласия. По его мнению, более эгалитарным обществом было бы общество, в котором наследование было бы ограничено или полностью исключено.

С другой стороны, Джон Локк, выдающийся английский философ эпохи Просвещения и активный защитник прав частной собственности, считал наследование естественным продолжением этих прав. Он утверждал, что люди имеют право накапливать и распоряжаться своей собственностью по своему усмотрению, в том числе передавать ее своим наследникам. Однако даже Локк признавал, что должны быть определенные ограничения для накопления, чтобы предотвратить чрезмерную концентрацию богатства и власти.

Теперь давайте рассмотрим парадоксальную связь между созданием богатства и его распределением в капиталистических обществах и то, какую важную роль в этой системе играет концепция наследования.

В своей книге Разговор с моей дочерью об экономике (2013 г.) греческий экономист и политик Янис Варуфакис объясняет, что богатство создается за счет труда, которым обычно занимается рабочий класс. Они занимаются различными видами деятельности, от ручного труда до предоставления услуг, которые создают ощутимую стоимость и стимулируют экономический рост. Затем плоды их труда превращаются в товар и обмениваются на рынке, принося доход компаниям, в которых они работают.

Однако, несмотря на то, что рабочий класс является самой основой создания богатства, он часто получает лишь небольшую часть производимой им стоимости. Их заработная плата, как правило, низкая, и они имеют ограниченный доступ к льготам или гарантиям занятости. Напротив, средний и высший классы, владеющие средствами производства, извлекают прибавочную стоимость, созданную рабочим классом, накапливая богатство и ресурсы, которые намного превышают их собственный вклад в экономику.

Это асимметричное распределение богатства еще больше усугубляется тем фактом, что высший класс или элиты часто имеют возможность влиять на экономическую политику и регулирование в свою пользу. Это может привести к налоговым льготам, лазейкам и другим преимуществам, позволяющим им сохранять и приумножать свое богатство с минимальными усилиями. Следовательно, богатство, создаваемое рабочим классом, перекачивается вверх, накапливаясь на вершине пирамиды, где оно непропорционально потребляется очень немногими.

Концепция наследования влияет на распределение богатства и возможностей и определяет доступ людей к таким ресурсам, как образование. Неравенство в унаследованном богатстве способствует неравенству в доступе к качественному образованию, увековечивая социальное расслоение и ограничивая возможности людей из менее привилегированных слоев населения накапливать богатство и продвигаться в обществе.

Наследование от нескольких классов

Множественное наследование — это функция, при которой класс может наследовать поведение и свойства более чем одного суперкласса. Однако TypeScript/JavaScript, как и многие другие языки программирования, не поддерживает множественное наследование напрямую. Вместо этого он полагается на альтернативные механизмы, такие как примеси и интерфейсы, для достижения аналогичной функциональности.

Давайте попробуем продемонстрировать концепцию множественного наследования через использование налогообложения в социал-демократии. Налогообложение направлено на то, чтобы сбалансировать рыночный капитализм с сильной социальной политикой. Он играет решающую роль в финансировании общественных товаров и услуг, способствуя перераспределению доходов с целью сокращения неравенства доходов. Хотя Руссо утверждал, что богатые могут использовать программы социального обеспечения как способ оправдать свое богатство и власть, одновременно снимая с себя какие-либо социальные обязанности. Он считал, что истинное равенство и справедливость могут быть достигнуты только путем разрушения социальной иерархии, которая поддерживала богатство и власть привилегированного меньшинства.

Мы пока не будем пытаться демонтировать социальный порядок, поэтому пока давайте просто определим базовый интерфейс для налогообложения и отдельные интерфейсы для разных типов налогообложения:

// 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;
}

Теперь давайте создадим миксины-функции, которые реализуют каждый из этих налоговых интерфейсов:

// 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
    }
  };

Мы также можем использовать служебную функцию для составления нескольких миксинов:

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

Наконец, давайте применим соответствующие налоговые примеси к нашим существующим социальным классам, чтобы добиться множественного наследования:

// 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();

Переопределение метода calculateTax

На самом деле не спорно сказать, что высшие классы обычно не платят так много налогов, и чтобы проиллюстрировать, как члены UpperClassTaxPayer могут использовать сложные налоговые льготы и схемы, чтобы избежать уплаты налогов, мы переопределим метод calculateTax в UpperClassTaxPayer.

Помимо сложных налоговых льгот и других схем, UpperClassTaxPayer могут также использовать налоговые убежища, чтобы избежать уплаты налогов в своих странах. убежищами являются страны или юрисдикции, которые предлагают сверхнизкие налоговые ставки и законы о секретности для привлечения богатых людей и корпораций.

В своей книге The Global Minotaur (2011 г.) Янис Варуфакис подверг критике глобальную финансовую систему и роль налоговых убежищ в сохранении имущественного неравенства. Варуфакис подчеркивает, как богатые используют лазейки в международной налоговой системе, что приводит к потере доходов для стран, которые могли бы быть использованы для государственных услуг и социального обеспечения.

Вот пример класса UpperClassTaxPayer, который переопределяет метод calculateTax:

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;
  }
}

По иронии судьбы, те самые технологические компании, в которых работают разработчики программного обеспечения, которые, возможно, читают эту статью, часто используют изощренные меры и схемы, чтобы не платить свою справедливую долю налогов. Например, многие американские технологические компании, такие как Apple, Google, Facebook и Amazon, имеют офисы в Ирландии, чтобы воспользоваться более низкими ставками корпоративного налога, стратегиями трансфертного ценообразования и мягкими правилами. Это позволяет им минимизировать свои налоговые обязательства, даже если они приносят миллиардные доходы.

В 2016 году Европейская комиссия постановила, что Apple получила незаконную государственную помощь от Ирландии, в результате чего неуплаченные налоги составили 13 миллиардов евро (14,5 миллиардов долларов). Хотя Apple и правительство Ирландии подали апелляцию на это решение, дело подчеркивает сложные налоговые механизмы, которые транснациональные корпорации используют для минимизации своих налоговых обязательств.

Альтернативы наследованию на основе классов

В заключение, наследование на основе классов позволяет нам смоделировать архитектуру нашего программного обеспечения по аристократическим нормам прошлых столетий, используя жесткую иерархию и социальное неравенство, которое оно отражает. Он предлагает восхитительную возможность увековечить систему, которая сопротивляется изменениям и адаптации.

Тем не менее, для тех смелых душ, которые стремятся вырваться из цепей традиции, альтернативные парадигмы прототипного наследования и функционального программирования предлагают соблазнительный вкус равенства и гибкости. Наследование прототипов с его упором на динамические отношения между объектами служит маяком надежды для тех, кто жаждет более эгалитарного подхода в своем коде.

Точно так же функциональное программирование с его фокусом на чистых функциях и неизменности предлагает мир, в котором сторонние эффекты избегаются, а манипулирование состоянием осуждается. Принимая эти парадигмы, разработчики могут создавать приложения, которые не только более просты в сопровождении и надежны, но и воплощают в себе принципы справедливости и адаптивности, к которым мы стремимся в справедливом обществе.

Вот пример, который использует прототипное наследование, чтобы отменить жесткие социальные классы и бросить вызов статусу-кво:

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;
  } 
  
  if (income <= 80000) {
    return 0.2;
  } 
  
  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)));

В этот просвещенный век чистого JavaScript мы освободились от оков классового мышления и жесткой структуры, которую так сильно поддерживает TypeScript. Имея в своих руках мощь прототипного наследования и функционального программирования, нам больше не нужны ограничения TypeScript для создания гибкого и адаптируемого кода, который способствует равенству и общему процветанию.

Действительно, наш подход идеально согласуется с видением Руссо, который осмелился представить себе общество, в котором люди не ограничены произвольными различиями социального класса. Поднявшись над этими ограничениями в нашем кодексе, мы всем сердцем принимаем эгалитарные идеалы, которые горячо защищал Руссо. При этом мы создаем программное обеспечение, которое более тесно связано с благородным стремлением способствовать человеческому росту и благополучию.

Первоначально опубликовано на https://bottleneck.dev