Как правильно заменить «расширяет», используя функциональное программирование? - PullRequest
1 голос
/ 05 апреля 2020

Я изучаю, как применить функциональное программирование в javascript, и я экспериментирую с попыткой избежать использования ключевого слова class.

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

До сих пор я имел успех, используя в основном функции, но есть одна ситуация что я не могу понять.

Когда у нас есть поведение, которое мы хотим повторно использовать между различными объектами, мы обычно (в OOP) создаем класс, который расширяет другой класс.

class FlyingThing {
   private let _isFlying = false

   fly() {
       _isFlying = true
       return this
   }

   land() {
       _isFlying = false
       return this
   }

   isFlying() {
      return _isFlying
   }
}

class Duck extends FlyingThing {
   quack() {
       return 'Quack!'
   } 
}

const duck = new Duck()
console.log(duck.fly().quack())

Теперь о функциональном подходе ...

Пример взят из: https://medium.com/javascript-scene/functional-mixins-composing-software-ffb66d5e731c

const flying = o => {
  let isFlying = false
  return Object.assign({}, o, {
    fly () {
      isFlying = true
      return this
    },
    isFlying: () => isFlying,
    land () {
      isFlying = false
      return this
    }
  })
}

const quacking = quack => o => Object.assign({}, o, {
  quack: () => quack
})

const createDuck = quack => quacking(quack)(flying({}))
const duck = createDuck('Quack!')
console.log(duck.fly().quack())

Хорошо, мне нравится эта идея; мы используем композицию, и у нас нет тесной связи между родителями и детьми. Круто.

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

class FlyingThing {
   private let _isFlying = false

   fly() {
       _isFlying = true
       return this
   }

   land() {
       _isFlying = false
       return this
   }

   isFlying() {
      return _isFlying
   }
}

class Duck extends FlyingThing {
   quack() {
       return 'Quack!'
   }

   // New method - Depends on 'isFlying' defined in parent
   layEgg() {
       if(isFlying) return
       return 'Laying egg...'
   }
}

const duck = new Duck()
console.log(duck.fly().quack())

Итак, вопрос в том, как мы можем решить эту проблему элегантно, используя только функции?

1 Ответ

1 голос
/ 09 мая 2020

предисловие

Механизм возможного решения, который ищет OP, все еще остается OO, каким может быть OO; в конце концов, то, с чем мы имеем дело, это составление объекта (или увеличение объекта / типа) посредством вызова функций javascript. Eri c Elliott - функциональный миксин - и Дуглас Крокфорд - функциональное наследование - каждый действительно объясняет свой подход довольно хорошо. Они могли пропустить название / маркировку. На мой взгляд, это должно быть так просто, как функция на основе функции mixin . JavaScript разработчиков будет меньше путаницы, поскольку термин функциональный больше не будет указывать или вводить в заблуждение в »The Land of FP« .

Могущественная сила JavaScript function поставляется с каждой из его способностей, во-первых, сохранять область посредством создания замыканий и, во-вторых, доступа к контексту через this и предоставления первого посредством одного из его методов вызова call или apply. Когда 3-й объект сам по себе является объектом первого класса, который можно обойти, он просто округляет весь пакет.

подхода

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

Eri c и Дугласа будет соблюдена / признана буквально , применяя it.

По моему мнению, модульное составное поведение в JavaScript всегда должно обеспечиваться функцией, которая не должна вызываться через ключевое слово new и не вызываться оператором вызова ... () .., но он всегда должен применяться к / на другие объекты / типы с помощью call или apply.

Пример кода OP с общим, но защищенным (локально ограниченным) состоянием полета ...

function withFlightStateAlteringFlightCapability(state) {
  const flightCapableType = this;

  flightCapableType.fly = () => {
    state.flying = true;
    return flightCapableType;
  };
  flightCapableType.land = () => {
    state.flying = false;
    return flightCapableType;
  };
  flightCapableType.isFlying = () => state.flying;

  return flightCapableType;
}

function withFlightStateDependedEggLayingBehavior(state) {
  const oviparousType = this;

  oviparousType.layEgg = () => {
    let returnValue;

    // if (!this.isFlying()) {
    if (!state.flying) {
      returnValue = 'Laying egg...'
    }
    return returnValue;
  };
  return oviparousType;
}

function withMetaBehavior(label, behavior) {
  this[label] = behavior;
}

class Duck {
  constructor() {

    // - glue code wrapped by constructor.
    // - type will feature a class signature.
    // - `state` gets preserved by the closure that is created with each instantiation.

    // local state (shared and protected)
    const state = {
      flying: false
    };
    const duck = this;

    withFlightStateAlteringFlightCapability.call(duck, state);
    withFlightStateDependedEggLayingBehavior.call(duck, state);
    withMetaBehavior.call(duck, 'quack', () => 'Quaaack...Quaaack...');
  }
}
const duck = new Duck;

function createDuckAlikeType() {

  // - glue code wrapped by factory function.
  // - type will be an augmented but ordinary `Object` type.
  // - `state` gets preserved by the closure that is created with each invocation of the factory.

  // local state (shared and protected)
  const state = {
    flying: false
  };
  const type = {};

  withFlightStateAlteringFlightCapability.call(type, state);
  withFlightStateDependedEggLayingBehavior.call(type, state);
  withMetaBehavior.call(type, 'quack', () => 'Quack!');

  return type;
}
const duckAlikeType = createDuckAlikeType();

console.log('composed "real duck" : ', duck);
console.log('composed "duck alike type" : ', duckAlikeType);

console.log('\nduck.fly() ...');
duck.fly();

console.log('\nduck.isFlying() ? ', duck.isFlying());
console.log('duckAlikeType.isFlying() ? ', duckAlikeType.isFlying());

console.log('\nduck.layEgg() ? ', duck.layEgg());
console.log('duckAlikeType.layEgg() ? ', duckAlikeType.layEgg());

console.log('\nduck.land().layEgg() ? ', duck.land().layEgg());
console.log('duckAlikeType.fly().layEgg() ? ', duckAlikeType.fly().layEgg());

console.log('\nduck.isFlying() ? ', duck.isFlying());
console.log('duckAlikeType.isFlying() ? ', duckAlikeType.isFlying());

console.log('\nduck.quack() ? ', duck.quack());
console.log('duckAlikeType.quack() ? ', duckAlikeType.quack());
.as-console-wrapper { max-height: 100%!important; top: 0; }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...