// Так как в ФП большая часть кода должны быть чистыми функциями
// все сайд эффекты придется выносить к краю и выполнять там
// таким "краем" будет такая обертка:
const withSideEffects = (runner) => (...args) => {
const [returnValue, sideEffects] = runner(...args);
sideEffects.forEach(se => se());
return returnValue;
};
// А чтоб доносить сайд эффекты до этой функции
// их нужно держать в изменяемом состоянии
// чистая функция может менять только свое локальное состояние
// инкапсулируем эту логику в таком функторе:
const createSideEffectAccumulator = () => {
const sideEffects = [];
const acc = {
with: (sideEffect) => (
sideEffects.push(sideEffect),
acc
),
map: ([returnValue, resolvedSideEffects]) => (
sideEffects.push(...resolvedSideEffects),
returnValue
),
resolve: (returnValue) => [returnValue, sideEffects]
};
return acc;
};
// подписка на событие (и отписка) - это сайд эффект
// а вот функция возвращающая парочку сайд эффектов
// созданных на основе своих аргументов - вполне себе чистая:
const subscriptionFactory = (target, eventName, listener) => [
() => target.addEventListener(eventName, listener),
() => target.removeEventListener(eventName, listener)
];
// изменение стиля - тоже сайд эффект,
// воспользуемся тем же приемом:
const setDisplayStyleFactory = (target, value) => () => target.style.setProperty('display', value);
// а еще для нашей задачи нужны условия
// абстрогируем их в функцию:
const condition = (predicate, truthyCallback, falsyCallback) => (...args) => (predicate(...args)
? truthyCallback(...args)
: falsyCallback(...args)
);
// для falsyCallback в нашей задаче понадобится фабрика пустых аккумуляторов сайд эффектов:
const emptySideEffectAccumulatorFactory = () => createSideEffectAccumulator().resolve();
// наша логика в виде чистых функций:
const openPopup = (modal, subscribeDocumentKeydown) => () => (createSideEffectAccumulator()
.with(setDisplayStyleFactory(modal, 'block'))
.with(subscribeDocumentKeydown)
.resolve()
);
const closePopup = (modal, unsubscribeDocumentKeydown) => () => (createSideEffectAccumulator()
.with(setDisplayStyleFactory(modal, ''))
.with(unsubscribeDocumentKeydown)
.resolve()
);
const onPopupCloseEscPress = (modal, unsubscribeDocumentKeydown) => condition(
event => event.key === 'Escape',
closePopup(modal, unsubscribeDocumentKeydown),
emptySideEffectAccumulatorFactory
);
const subscribeTriggers = (modal, triggers, subscribeDocumentKeydown) => () => triggers.reduce(
(acc, trigger) => acc.with(subscriptionFactory(trigger, 'click', withSideEffects(openPopup(modal, subscribeDocumentKeydown)))[0]),
createSideEffectAccumulator()
).resolve();
// соберем сайд эффекты произведенные нашим кодом
const bindModal = withSideEffects((modal, triggers, closeModalBtn) => {
const acc = createSideEffectAccumulator();
const [
subscribeDocumentKeydown,
unsubscribeDocumentKeydown
] = subscriptionFactory(document, 'keydown', withSideEffects(subscribeTriggers(modal, triggers, () => subscribeDocumentKeydown())))
return (acc
.with(subscriptionFactory(modal, 'click', withSideEffects(condition(
event => event.target === modal,
closePopup(modal, unsubscribeDocumentKeydown),
emptySideEffectAccumulatorFactory
)))[0])
.with(subscribeTriggers(modal, triggers, subscribeDocumentKeydown))
.with(subscriptionFactory(closeModalBtn, 'click', withSideEffects(closePopup(modal, unsubscribeDocumentKeydown)))[0])
.resolve()
);
});
// для выполнения нужно будет передать зависимости
bindModal(modal, triggers, closeModalBtn);
Я правильно понимаю, что если передать как:document.addEventListener(`click`, listener)
то в функцию/метод event попадает, т.е. его не обязательно передавать так как делал я?
fgehte, да, нет принципиальной разницы, сохранили Вы ссылку на функцию в переменную и потом передали аргументом, или сразу передали аргументом.А как должен выглядить код в таком случае?ООП - это про композицию объектов и их взаимодействие через вызовы методов, наследование вторично.
P.s. При описание вопроса я указал, что удалил второстепенную информаци такую как конструктор, наследование и тд.
class EventSubscription {
constructor(target, eventName, eventListener) {
this.target = target;
this.eventName = eventName;
this.eventListener = eventListener;
}
subscribe() {
this.target.addEventListener(this.eventName, this.eventListener);
}
unsubscribe() {
this.target.removeEventListener(this.eventName, this.eventListener);
}
}
class SubscriptionCollection {
constructor(subscriptions) {
this.subscriptions = subscriptions;
this.subscription = this;
}
subscribe() {
this.subscriptions.forEach(subscription => subscription.subscribe());
}
unsubscribe() {
this.subscriptions.forEach(subscription => subscription.unsubscribe());
}
}
class FilterEventListenerDecorator {
constructor(filter, eventListener) {
this.filter = filter;
this.eventListener = eventListener;
}
handleEvent(event) {
const {filter} = this;
if(!filter(event)) { return; }
return this.eventListener.handleEvent(event);
}
}
class RunEventListener {
constructor(runner) {
this.runner = runner;
}
handleEvent() {
this.runner.run();
}
}
class RunnerCollection {
constructor(...runners) {
this.runners = runners;
}
run() {
this.runners.forEach(runner => runner.run());
}
}
class StyleSetter {
constructor(target, property, value) {
this.target = target;
this.property = property;
this.value = value;
}
set() {
this.target.style.setProperty(this.property, this.value);
}
}
class StyleSetterRunnerDecorator {
constructor(styleSetter) {
this.styleSetter = styleSetter;
}
run() {
this.styleSetter.set();
}
}
class SubscriberRunnerDecorator {
constructor(subscriber) {
this.subscriber = subscriber;
}
run() {
this.subscriber.subscribe();
}
}
class UnsubscriberRunnerDecorator {
constructor(unsubscriber) {
this.unsubscriber = unsubscriber;
}
run() {
this.unsubscriber.unsubscribe();
}
}
class NextTickApplyRunnerDecorator {
constructor(runnerFactory) {
this.runner = null;
Promise.resolve().then(() => {
this.runner = runnerFactory();
});
}
run() {
if(!this.runner) { return; }
this.runner.run();
}
}
class DocumentKeydownSubscriptionFactory {
constructor(modal) {
this.subscription = new EventSubscription(document, 'keydown', new FilterEventListenerDecorator(
event => event.key === `Escape`,
new RunEventListener(new RunnerCollection(
new NextTickApplyRunnerDecorator(() => new UnsubscriberRunnerDecorator(this.subscription)),
new StyleSetterRunnerDecorator(new StyleSetter(modal, 'display', ''))
))
));
}
}
class ModalClickSubscriptionFactory {
constructor(modal, documentKeydownSubscription) {
this.subscription = new EventSubscription(modal, 'click', new FilterEventListenerDecorator(
event => event.target === modal,
new RunEventListener(new RunnerCollection(
new UnsubscriberRunnerDecorator(documentKeydownSubscription.subscription),
new StyleSetterRunnerDecorator(new StyleSetter(modal, 'display', ''))
))
));
}
}
class CloseModalBtnSubscriptionFactory {
constructor(closeModalBtn, modal, documentKeydownSubscription) {
this.subscription = new EventSubscription(closeModalBtn, 'click', new RunEventListener(new RunnerCollection(
new UnsubscriberRunnerDecorator(documentKeydownSubscription.subscription),
new StyleSetterRunnerDecorator(new StyleSetter(modal, 'display', ''))
)));
}
}
class TriggerSubscriptionFactory {
constructor(trigger, modal, documentKeydownSubscription) {
this.subscription = new EventSubscription(trigger, 'click', new RunEventListener(new RunnerCollection(
new SubscriberRunnerDecorator(documentKeydownSubscription.subscription),
new StyleSetterRunnerDecorator(new StyleSetter(modal, 'display', 'block'))
)));
}
}
function bindModal(modal, triggers, closeModalBtn) {
const documentKeydownSubscription = new DocumentKeydownSubscriptionFactory(modal);
new SubscriptionCollection([
new ModalClickSubscriptionFactory(modal, documentKeydownSubscription),
new CloseModalBtnSubscriptionFactory(closeModalBtn, modal, documentKeydownSubscription),
new SubscriptionCollection(triggers.map(trigger => new TriggerSubscriptionFactory(trigger, modal, documentKeydownSubscription)))
]).subscribe();
}
bindModal(modal, triggers, closeModalBtn);
Честно говоря, мне уже давно не интересны подобные дискуссии. Потому что аргументы у противника всегда одни и те же, проблемы всегда надуманные.Сергей delphinpro, вот только эти "надуманные проблемы" из раза в раз встречаются в реальных проектах.
.some-wtf-class1 .some-wtf-class2 .some-wtf-class3 {}тоже что-то из ряда вон выходящее, как раз говорящее о кривых ручках написавшего стили...
.app-header__logo {} — вполне очевидно, что это блок, содержащий логотип и расположенный в шапке сайтаможет и очевидно, вот только где разметка и логика этого блока по прежнему неизвестно, как и наоборот, из разметки найти ее стили невозможно.
.app-header {
// лютая портянка стилей
&__logo {}
// лютая портянка стилей
}
CREATE INDEX *** ON top_used_emojis (usages_count DESC)
Абстрактная фабрика все таки про набор связных сущностей