Комментарии 15
Не совсем корректно сравнивать реализации композиции и наследования. Ведь композицию можно сделать иначе — без пересоздания объекта на каждый чих.
А если сделать объекты без методов? Пусть будут stateless сервисы по колдунству, что умеют работать с колдунами и паладинами, а с рыцарями пусть не работают? Взамен они будут возвращать или измененный объект или новый инстанс объекта с измененными свойствами.
А если сделать объекты без методов? Пусть будут stateless сервисы по колдунству, что умеют работать с колдунами и паладинами, а с рыцарями пусть не работают? Взамен они будут возвращать или измененный объект или новый инстанс объекта с измененными свойствами.
Не совсем корректно сравнивать реализации композиции и наследования. Ведь композицию можно сделать иначе — без пересоздания объекта на каждый чих.
Только если логика приложения позволяет нам иметь одни и те же экземпляры функций для каждого юнита. В целом, конечно, это идеально, если мы можем их сохранить чистыми, да.
Вечная проблема с этим воображаемым противостоянием композиции и наследования и грубые примеры из разряда «я ехал на машине, не останавливаясь вышел покурить и сламал себе ноги, давайте лучше ездить на велосипедах». И, конечно, примеры из разряда: «давайте сделаем как можно повыё, типа функционально у нас все, потом за смузи будет обсуждать этих идиотов джавистов, которые создают гориллу, жующую банан».
Во-первых, нафиг такое усложнение с Object.assign, которое ломает автодополнение?
Но про такую фигню статью не напишешь, да? Да и бгомеркий this, который используют только грязныё джависты. О, кстати, давайте соблюдать DRY.
Опс, наследование получилось! Фигня какая. Это ведь почти горила-жрущая-банан! Упс. Тут или как мерзкие джависты, или по-модному, копипастя самого себя три раза в каждом объекте.
Я уж молчу, что ваше предложение вообще не гарантирует, что добавив fight в очередной инстанс программист добавит так же stamina.
Я уж молчу, что последнее решение точно так же делается на классах, как и без них.
А как гейм-девелопер скажу, что решение — отвратительное и вообще не близко к практике. У мага и паладина вообще разные спелы, а какой-то спел может использовать стамину вместо маны. Более того, персонаж может получить возможность кастовать спелы, подняв «посох безумного огня» и потом внезапно её потерять, когда в этом посохе закончатся заряды. А ещё персонаж может использовать магию со свитков, которые тоже не затрачивают ману, но требуют определенного уровня интеллекта. Я уж молчу о том, что нельзя просто взять и изменить «стамину» — она должна измениться через анимацию.
Потому такие действия делают отдельными событиями-классами, на которые можно подписаться через эвент-систему и которые статически типизируются, потому что в отличии от клепания одинаковых формочек на редаксе в геймдеве такой быдлокод не пройдет.
Ну вот, к примеру: habr.com/ru/company/pixonic/blog/413729
А наследование и композиция просто должны применятся в разных местах. ОБА этих инструмента по-своему полезны.
Во-первых, нафиг такое усложнение с Object.assign, которое ломает автодополнение?
function castSpell () {
console.log(`${this.name} casts ${spell}!`);
this.mana--;
}
function fight () {
console.log(`${this.name} slashes at the foe!`);
this.stamina--;
}
const fighter = (name) => ({{
name,
health: 100,
stamina: 100,
fight
})
const mage = (name) => ({
name,
health: 100,
mana: 100,
castSpell
})
const paladin = (name) => ({
name,
health: 100,
mana: 100,
stamina: 100,
fight,
castSpell
});
Но про такую фигню статью не напишешь, да? Да и бгомеркий this, который используют только грязныё джависты. О, кстати, давайте соблюдать DRY.
const character = (name) => ({
name,
health: 100,
});
const fighter = (name) => ({
...character(name),
stamina: 100,
fight
})
const mage = (name) => ({
...character(name),
mana: 100,
castSpell
})
const paladin = (name) => ({
...character(name),
mana: 100,
fight,
castSpell
});
Опс, наследование получилось! Фигня какая. Это ведь почти горила-жрущая-банан! Упс. Тут или как мерзкие джависты, или по-модному, копипастя самого себя три раза в каждом объекте.
Я уж молчу, что ваше предложение вообще не гарантирует, что добавив fight в очередной инстанс программист добавит так же stamina.
найти ложку дегтя в предложенном решении...… достаточно зачерпнуть ложкой в любом месте вашего решения.
Я уж молчу, что последнее решение точно так же делается на классах, как и без них.
А как гейм-девелопер скажу, что решение — отвратительное и вообще не близко к практике. У мага и паладина вообще разные спелы, а какой-то спел может использовать стамину вместо маны. Более того, персонаж может получить возможность кастовать спелы, подняв «посох безумного огня» и потом внезапно её потерять, когда в этом посохе закончатся заряды. А ещё персонаж может использовать магию со свитков, которые тоже не затрачивают ману, но требуют определенного уровня интеллекта. Я уж молчу о том, что нельзя просто взять и изменить «стамину» — она должна измениться через анимацию.
Потому такие действия делают отдельными событиями-классами, на которые можно подписаться через эвент-систему и которые статически типизируются, потому что в отличии от клепания одинаковых формочек на редаксе в геймдеве такой быдлокод не пройдет.
Ну вот, к примеру: habr.com/ru/company/pixonic/blog/413729
А наследование и композиция просто должны применятся в разных местах. ОБА этих инструмента по-своему полезны.
И кстати, от того, что вы используете грязные процедуры в сокращенном виде, ваш процедурный код аж никак не становится функциональным.
Так что тег «функциональное программирование» можете смело убирать.
const canCast = (state) => ({
cast: (spell) => {
console.log(`${state.name} casts ${spell}!`);
state.mana - ; // <=== процедурщина
}
})
return Object.assign(
state, // <=== процедурщина
canCast(state)
);
Так что тег «функциональное программирование» можете смело убирать.
Спасибо за подробный разбор. По поводу совместного использования стамины и маны, безусловно в статье дико упрощенный пример далекий от реальности. Конечно, существует масса логики, усложняющей отношения между характеристиками, но он на то и пример. И, опять же, с анимацией, рассматривается простой пример внутренней бизнес логики приложения, как это будет отображаться и анимироваться — не ее зона ответственности.
В примере ниже у каждого персонажа будет один и тот же экземпляр функции fight() или cast(). Вероятно, при усложнении логики этих функций потребуется внутреннее состояние, ссылка на оружие(свиток) и т.д. При конкатенации мы имеем уникальный экземпляр функции для каждого юнита.
Да, в комментариях ниже предложили более красивое решение с сохранением той же логики.
Во-первых, нафиг такое усложнение с Object.assign, которое ломает автодополнение?
В примере ниже у каждого персонажа будет один и тот же экземпляр функции fight() или cast(). Вероятно, при усложнении логики этих функций потребуется внутреннее состояние, ссылка на оружие(свиток) и т.д. При конкатенации мы имеем уникальный экземпляр функции для каждого юнита.
Но про такую фигню статью не напишешь, да? Да и бгомеркий this, который используют только грязныё джависты. О, кстати, давайте соблюдать DRY.
Да, в комментариях ниже предложили более красивое решение с сохранением той же логики.
Комментарий полезный, спасибо, но откуда такая агрессия, особенно к самому себе (вы про «джавистов» — это про себя же?) — не понятно. У автора статьи не было никаких наездов с личностными оттенками и он старался использовать ссылки на уважаемые ресурсы.
Потому что: «вот был плохой код с классами, мы отказались от классов, теперь у нас хороший код потому что без классов, а мы пишем в функциональном стиле!»
Если послушаете всяких фанатов Редакса — поймете, откуда агрессия)
«Джависты» — это все те устаревшие разработчики, которые продались корпорациям, не пьют смузи и до сих пор используют такие неудобные классы вместо того, чтобы перейти на столь гениальные и удобные подходы без единых изъянов. Я к ним, как вы уже поняли, отношусь
Если послушаете всяких фанатов Редакса — поймете, откуда агрессия)
«Джависты» — это все те устаревшие разработчики, которые продались корпорациям, не пьют смузи и до сих пор используют такие неудобные классы вместо того, чтобы перейти на столь гениальные и удобные подходы без единых изъянов. Я к ним, как вы уже поняли, отношусь
Починил ваш код, чтобы компилятор смог его cоптимизировать:
const canCast = (state) => {
state.mana = 100
state.cast = function() {
console.log(`${this.name} casts fireball!`);
this.mana -- ;
}
}
const character = (state) => {
state.health = 100
}
const mage = (name) => {
let state = { name }
character(state)
canCast(state)
return state;
}
Красота, и читабельно и быстро, особенно в Firefox'e, спасибо за такой вариант.
1. cast и fight можно не инлайнить и пересоздавать, а объявить заранее.
2. тесты ОЧЕНЬ сильно скачут, то одно, то другое быстрее, разница в 2-3 раза иногда
2. тесты ОЧЕНЬ сильно скачут, то одно, то другое быстрее, разница в 2-3 раза иногда
А зачем пересоздавать функции если все равно this используем?
function cast() {
console.log(`${this.name} casts fireball!`);
this.mana--;
}
const canCast = (state) => {
state.mana = 100
state.cast = cast
}
// ну и как альтернатива замыканию
const boundCast = (state) => {
state.mana = 100
state.cast = cast.bind(state)
}
если немного изменить пример, то станет быстрее и меньше памяти будет потреблять, не больше чем с наследованием из примера
но я практически уверен, что можно сделать еще лучше
Код
const canCast = (state) => {
state.cast = (spell) => {
console.log(`${state.name} casts ${spell}!`);
state.mana--;
}
}
const canFight = (state) => {
state.fight = () => {
console.log(`${state.name} slashes at the foe!`);
state.stamina--;
}
}
function FighterComp(name) {
this.name = name;
this.health = 100;
this.stamina = 100;
canFight(this);
}
function MageComp(name) {
this.name = name;
this.health = 100;
this.mana = 100;
canCast(this);
}
function Paladin(name) {
this.name = name;
this.health = 100;
this.mana = 100;
this.stamina = 100;
canCast(this);
canFight(this);
}
но я практически уверен, что можно сделать еще лучше
Да, так лучше, немного дальше пошли в предыдущем комментарии
Мне кажется есть проблемма с пониманием JS и фабрик. Попрубуйте так:
Результат 57 против 496. То есть в 6ть раз быстрее.
const allProperties = {
addCommon(state, name) {
state.name = name;
state.health = 100;
},
addMagic(state) {
state.mana = 100;
},
addFight(state, name) {
state.stamina = 100;
}
};
const allMethods = {
cast(spell) {
console.log(`${this.name} casts ${spell}!`);
this.mana--;
},
fight(spell) {
console.log(`${this.name} slashes at the foe!`);
this.stamina--;
},
};
function Mage2(name) {
allProperties.addCommon(this, name);
allProperties.addMagic(this);
}
Mage2.prototype.cast = allMethods.cast;
function Fighter2(name) {
allProperties.addCommon(this, name);
allProperties.addFight(this);
}
Fighter2.prototype.fight = allMethods.fight;
Результат 57 против 496. То есть в 6ть раз быстрее.
а чем плох вариант наследования, описанный в MDN, последний пример (mix-ins)?
Зарегистрируйтесь на Хабре, чтобы оставить комментарий
Цена композиции в Javascript-мире