В сети нет недостатка в разговорах о том, насколько classes
плохо работает Javascript. Таким образом, я пытался выяснить, как можно перевести концепции ООП в их функциональный эквивалент (в частности, в Typescript) и возможно ли это вообще.
Обычный класс
class Trader { constructor( private item: string ) {} print() { console.log(this.item); } setItem(newItem: string) { this.item = newItem; } getItem() { return this.item; } trade(yours: Trader) { const temp = this.item; this.setItem(yours.getItem()); yours.setItem(temp); } }
Чтобы преобразовать это в его функциональный эквивалент, мы можем просто изменить класс на функцию и вернуть любые общедоступные атрибуты или методы.
function Trader(it: string) { let item = it; function print() { console.log(item); } function setItem(newItem: string) { item = newItem; } function getItem() { return item; } return { print, setItem, getItem } }
Передача экземпляров самого себя
Торговый метод немного сложнее. В обычном ООП использование торгового метода будет выглядеть примерно так:
trader1.trade(trader2);
Поскольку сейчас мы имеем дело с функциями, передача trader2 как функции не работает.
// The compiler here complains that Trader is being used as a type and // asks if you meant "typeof Trader", but that still isn't what we want. function trade(yours: Trader) { const temp = item; setItem(yours.getItem()); yours.setItem(temp); }
Несколько не интуитивно понятно, что вы на самом деле хотите здесь объявить ReturnType
функционального класса, который мы создаем.
function trade(yours: ReturnType<typeof Trader>) { const temp = item; setItem(yours.getItem()); yours.setItem(temp); }
Как насчет наследства?
export type Jewelry = "ring" | "necklace" function JewelryTrader(item: Jewelry) { const parent = Trader(item) function setItem(newItem: Jewelry) { parent.setItem(newItem) } function trade(yours: ReturnType<typeof JewelryTrader>) { parent.trade(yours as ReturnType<typeof Trader>); } return { ...parent, setItem, // here we override the functions from our parent trade } }
Обратите внимание на порядок здесь. Чтобы переопределить методы нашего родителя, нам нужно убедиться, что наши методы идут после родителя.
Как видите, здесь нет неявной заменяемости. Если вы хотите, чтобы торговая функция могла принимать сделки как от JewelryTrader
, так и от Trader
, вам придется явно определить ее.
Можем ли мы делать абстрактные классы?
Я уверен, что есть какой-то тупой способ взломать это, но в целом я бы сказал, что вам не следует пытаться это сделать. Большая проблема заключается в реализации абстрактных функций. Локальная область видимости Javascript защитит функции родительского класса от переопределения дочерним. Это означает, что функциональность «снизу вверх» для выполнения дочерних функций в родительском невозможна.
function Trader(it: string) { // An "abstract" function to be overridden function trade(yours: ReturnType<typeof Trader>) { console.log("Regular Trade"); } function executeTrade(yours: ReturnType<typeof Trader>) { trade(yours); } ... } function JewelryTrader(it: Jewelry) { // implementation of the "abstract" function function trade(yours: ReturnType<typeof Trader>) { console.log("Jewelry Trade"); } ... return { ..., trade } } trader1.executeTrade(trader2); // logs "Regular Trade" despite being overridden.
Результат
В целом, у каждой парадигмы программирования есть свои сильные и слабые стороны, и вы должны использовать ту, которая наиболее подходит для ваших нужд, поскольку они не могут быть заменены напрямую.