Комментарии 398
Вот только она обычно плохо решается путём перенесения объекта в код. Обычно получается что-то околоподобное но со странными смысловыми извращениями. Например, огнестрельное оружие вместо того, чтобы мочь стрелять, на самом деле лишь добавляет экипированному им персонажу способность выстрелить.
В общем, любой фанатизм хорош в меру.
Все нормально моделируется в объектах реального мира. Просто методы должны быть в обоих объектах — отдающий и принимающий.
class Человек
{
method накормитьКошку()
{
var корм = this->взятьКорм();
this->положитьКормВКормушку(корм)
this->позватьКошку();
}
method взятьКорм()
{
var пакетКорма = this->найтиПакетКорма();
var корм = пакетКорма.взятьКорм();
return корм;
}
method положитьКормВКормушку(Корм корм)
{
this->кормушка->добавитьКорм(корм);
}
method позватьКошку()
{
this->сказать("Кис-кис-кис");
...
}
...
}
class Кормушка
{
method добавитьКорм(Корм корм)
{
...
}
}
А что должен делать метод кормушки?
В реальном мире это же не актор, у нее не может быть методов, это просто контейнер для корма.
Сходить за кормом :)
В реальном мире это вообще набор молекул, это мы в воображении объединяем их в один объект. Метод кормушки содержит технические детали реализации, а не бизнес-логику. Например, так:
class Кормушка
{
int количествоКорма;
method добавитьКорм(Корм корм)
{
this->количествоКорма += корм.количество;
}
}
Вообще можно и снаружи писать кормушка->количествоКорма += корм.количество
, с точки зрения моделирования нет большой разницы. '+='
можно считать методом типа int
, то есть тоже есть какая-то цепочка вызовов.
Ну тогда надо просто держать ссылку на корм в кормушке, а прибавлять убавлять в самом корме.
В кормушку же можно разный корм положить, и прибавление и убавление разное будет… Там молоко налить или сухой корм положить.
Это и есть детали реализации. Можно сделать перегрузку функции добавитьКорм()
по типу аргумента. Смысл в том, что мы моделируем передачу какого-то объема корма в кормушку, посылаем кормушке сообщение путем вызова метода. А уж как она его обработает, к вызывающему объекту не относится.
этом ее функционал закончен.
Это не сама кормушка, это представление кормушки. Она может быть вообще за интерфейсом скрыта. С технической точки зрения важный момент то, что это ссылка на объект, а не сами данные объекта. Ссылка представляет объект, так же как у реального человека в воображении есть образ кормушки, который представляет реальную кормушку.
Несложно заметить, что закон Ома, как и любую другую формулу из трех переменных, можно записать тремя способами: I=U/R, R=U/I, U=IR
И все три записи будут верными. Если известны две величины, то мы можем вычислить третью. И ничего более.
U=IR «Напряжение на концах проводника СТАНОВИТСЯ равным его сопротивлению, умноженному на силу тока через проводник» — не верно.
Если через резистор R пропускать ток силой I, то падение напряжения на резисторе будет равно U.
Ну и так далее.
И все три записи будут верными
Задача была — сделать вариант записи единственным.
Если через резистор R пропускать ток силой I, то падение напряжения на резисторе будет равно U.
Если вы умеете через резистор R пропускать ток силой I, не прикладывая напряжение, то да, в результате этого оно возникнет конечно… Наверно, так можно описать динамо-машину.
То есть вариант записи зависит от ситуации, от решаемой задачи.
Э-э-э, а откуда возникает требование "не прикладывая напряжение", которое прямо противоречит формуле?
"Становится" в примерах автора несет причинно-следственную связь, а не только описывает факт равенства.
Сила тока, по предложенной логике, появляется как результат приложения напряжения, но не наоборот.
Аналогично, сопротивление проводника не возникает как следствие приложенного тока, а существует само по себе.
В том-то и дело, что причинно-следственных связей эта формула не даёт. Сопротивление — да, само по себе, а вот и ток, и напряжение порождаются одними и теми же свободными электронами.
В некотором смысле причиной возникновения как напряжения, так и тока можно считать ЭДС, которая в формуле U = IR отсутствует.
А, например, в формуле связи градусов Фаренгейта с градусами Цельсия какая причинно-следственная связь? Фаренгейты меняются как результат изменения Цельсиев, или наоборот?
Формулы вроде закона Ома — это просто преобразование из одних единиц измерения в другие (ток <-> напряжение) при помощи коэффициентов преобразования (сопротивление). В них не заключено никакой причинно-следственной связи.
Если вы умеете через резистор R пропускать ток силой I, не прикладывая напряжение, то да, в результате этого оно возникнет конечно…
«Прикладывание напряжения U» никак принципиально не отличается от «пропускания тока I» — ни в физике как таковой, ни в реальной электронике. Есть объекты, которые хорошо делают первое, есть которые хорошо делают второе.
Теперь, расскажите, как именно вы напрямую приложите к резистору силу тока.
Есть сопротивление, у него есть два контакта. Вы прикасаетесь ими к источнику напряжения. Вот, вы приложили напряжение к резистору.
Вот только вопрос — какое именно напряжение вы к нему приложили? В общем случае оно не будет совпадать с тем напряжением, которое было у источника до прикладывания резистора. Как величину, которая меняется от подключения резистора, можно считать первопричиной?
Теперь, расскажите, как именно вы напрямую приложите к резистору силу тока.
Прикладываю резистор к источнику тока и включаю источник тока.
Насколько я помню электротехнику (25+ лет назад), источники напряжения — это различного рода химические генераторы элекктричесвта — батарейки, проще говоря, а источники тока — "физические" генераторы, базирующая на электромагнитных явлениях, прежде всего на электростанциях. Что розетка ведёт себя как источник напряжения в бытовых сценариях — просто "случайное" соотношение различных сопротивлений полной системы.
Вот так же, мы поступим и в ООП. Условимся, что метод принадлежит тому, кто воздействует
Вот с таких допущений и начинаются холивары, особенно если они, допущения, неявные. Часто ровно наоборот, вызов метода одного объекта другим, свидетельствует о намерении воздействовать второго на первый или сообщить первому о событии во втором.
Должен определить какой корм в кормушку поместить.
Ну в идеальной модели именно так, человеку все равно кого кормить, это определяется в рантайме, кошке все равно кто ее кормит, кормушке все равно кто в нее кладет корм и кто из нее ест. Иначе вам под робокормушку придется пол приложения переписать, или забить костыль человек-таймер.
вызов метода «покормить» может закончиться только двумя исходами:
Забыли про:
3. Ожидание: кошка не проголодалась
Чтобы проконтролировать, что все прошло по замыслу всевышнего имеет смысл создавать более общие методы, которые проверят все предусловия и проконтролируют результата — сервисы, команды, контроллеры, стратегии, саги и прочие поведесчкие шаблоны.
В них мы можем реализовывать уже более сложные стратегии — начиная от генерации ошибки, если кормушка не пустая перед началом следующего кормления и заканчивая измерением глюкозы в крови кошки в процессе питания и привязкой человека к кошке, пока эта глюкоза не окажется в заданных пределах.
Человеку даже не обязательно знать, какой корм и в какую кормушку насыпать. Вдруг завтра к двум кошкам добавится хомяк?
Описанный выше код легко отработает все перечисленные ситуации. Надо лишь давать ему правильные внешние объекты кормёжки и кормушки.
Ну, если готовиться к "в целом нормально, но есть нюансы" от заказчика, например, "у вас получается, что человек не в доме или в доме, но спящий, может покормить кошку не приходя домой или не просыпаясь — такого быть не должно. Вы вообще чем думали?", то предположить стейт у Человека и возможность проверять по нему внутреннюю способность покормить кошку вполне разумно, пускай и сначала метод будет тупым как пробка, но инкапсулировать будет, по-моему, полезно именно в инстансе Человек.
А почему жена обращается к человеку, а не к кошке?
Значит ли это, что метод "покормить кошку" есть только в объекте "Человек"?
Разве
Условимся, что метод принадлежит тому, кто воздействует:
Кто_действует.Метод(Объект_воздействия);
не противоречит
И, наконец, Монстр_1.НанесёноПовреждение(сила_повреждения:Float)
?
Последнее больше похоже на Измаил.СменаСобственника(Турки, Суворов)
.
Особенно с учётом
появился бы новый объект — ракета. Со своим методом Tick, обрабатывающим действия за один тик игрового времени
, из которого по предложенной логике следует, что надо Ракета.НанестиПовреждение(Монстр_1, сила_повреждения)
.
Имеется в виду, что первым методом, который в дальнейшем вызывает другие методы, будет метод объекта, совершающего это действие.
В нашем случае изначально вызывается как раз метод игрока, который вызывает метод оружия, который вызывает метод монстра, который меняет своё состояние. То есть, вопрос не в том, какой метод писать, писать придётся все, а в том, какой метод будет вызываться при выстреле.
2) Нет. Если бы нанесение повреждений ракетой было в момент выстрела, создавать объект Ракета бы не потребовалось. Смысл именно в том, что создан объект Ракета, он обрабатывает Tick (перемещение за 1 тик игрового времени — если ракета не самонаводящаяся, то эта обработка выполнится стандартным методом родительского объекта), и Explosion — взорваться. И только в момент взрыва будет нанесено повреждение тем существам (включая и монстров и игроков), которые были рядом.
1) у кормушки есть метод «добавить еду»
Вот именно с такого подхода и начинается переусложнение систем, из-за которого потом программисты восстают против ООП в целом.
Кормушка — это пластмассовая мисочка. Кормушка не имеет собственных методов, это глобальная переменная, либо запись в базе данных (смотря, что и как пишем). Это не объект.
иметь дело с глобальными переменными я вам не рекомендуюПредставьте, что вы пишете игру-бродилку — симулятор Гарри Поттера от первого лица, напоминаю что Гарри Поттер имел питомца — сову. Ваша игра не позволяет играть за класс в целом, только за Поттера. Очевидно, что его сова — это глобальная переменная.
кормушек может быть множество все-такиГлобальные переменные могут быть массивом.
Я имел ввиду, что изменение объёма пищи в миске не стоит выделять в отдельный функционал.
Очевидно, что его сова — это глобальная переменная.
Ну уж нет, не очевидно.
Очевидно, что его сова — это глобальная переменная
Нет, сова — самостоятельный объект со своими тасками.
Нет, сова — самостоятельный объект со своими тасками.Конечно. Как это противоречит тому что она глобальная переменная?
И как обратиться к ней из кода программы?
Наверное мы по-разному понимаем термин глобальная переменная.
Напишите код, возвращающий какой угодно параметр конкретной совы.
Реальный код в любом языке.
Другие животные игрового мира, кроме некоторых таких же как она, существуют только внутри сцены с ними. Их может в сцене быть любое количество. Они не сохраняют состояние за пределами сцены, выстрелив зайцу в лапку вы не встретите после этого одноногого зайца на следующей неделе. Состояние рандомного зайчика не сохранится после того как он ушёл за пределы сюжета одного дня (сцены).
В этом разница между Буклей и прочим животным миром.
Тем, что существует в единственном экземпляре. Эту сову, принадлежащую Поттеру, зовут Букля. Вы не можете создать сцену, в которой их будет две.
К глобальным переменным, внезапно, все это не имеет никакого отношения. Это обычный такой объект с идентичностью. Собственно, в каком-то смысле, любая доменная сущность (в смысле domain entity по DDD) — она такая же: существует в одном экземпляре, обладает continuity и так далее.
Другой пример глобальной переменной — игровое время суток, игровой сезон года. Это простые переменные. Хогвартс и Букля — сложные, их структура читается из файлов конфигурации.
Глобальная переменная не зависит от сеанса по определению. Программа запущена — глобальная переменная есть, даже если пользователь ещё не нажал "новая игра" или "загрузить игру".
Это если допустить, что программа чисто однопользовательский монолит.
Не сеанс юзера в ней.
Зачем создавать Сову, до того как пользователь начал играть?
Но ваш вопрос не имеет отношения к написанному мной: глобальная переменная просто есть. До того как пользователь зашёл в игру, и до того как сова появилась по сюжету, и после того как её убили из-за неаккуратных действий игрока, в ней может быть nil. Пусто. Но переменная-то есть.
В каком языке программирования вы пишете, что глобальные переменные в нём требуется «создавать» в рантайм?
А точно ли на заставке должна быть та же самая сова, которую потом в игре надо кормить? Что если пользователь покормит её на экране этой самой заставки?
В каком языке программирования вы пишете, что глобальные переменные в нём требуется «создавать» в рантайм?
Это вы на каком-то странном языке пишете, в котором кроме глобальных переменных ничего нет.
Но переменная-то есть.
Вот совершенно не понятно, зачем она "есть", особенно когда мы наконец выяснили, что объект, на который она ссылается, может как быть, так и не быть.
В каком языке программирования вы пишете, что глобальные переменные в нём требуется «создавать» в рантайм?
Питон.
Python 3.6.8 |Anaconda, Inc.| (default, Feb 11 2019, 15:03:47) [MSC v.1915 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> q
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'q' is not defined
>>> q = 3
>>> q
3
Вот совершенно не понятно, зачем она «есть», особенно когда мы наконец выяснили, что объект, на который она ссылается, может как быть, так и не быть.А затем, что «у меня нет совы» (переменная пуста) и «мне не известно что такое сова» (переменная отсутствует) — не одно и то же.
На PHP и JS. В них просто не требуется создавать глобальных переменных. Можно, но не требуется. Более того, это считается плохим стилем по умолчанию. Чтобы ввести глобальную переменную, нужно очень серьёзно обосновать это обычно, и то не факт, что поможет.
Теперь осталось понять, зачем этой несчастной сове видимость везде и время жизни весь сеанс программы.
И что?
Мне вот очень интересно: а что, Гарри правда всегда имеет связь со своей совой? Или это все-таки обычные отношения человека с питомцем (пусть и очень умным), и поэтому если улететь на другой континент, то, что сова перелетела из комнаты в комнату, ее владельцу не известно?
Не всегда. Но сова — глобальная переменная.
Это так же, как время суток. Глобальная переменная время суток имеет значение — сейчас день. А я в данный момент могу быть в лифте. И это может быть не тот лифт который в ТЦ Европейский возле м. Киевская в Москве, который снаружи здания, а самый обычный лифт. Я не контактирую в данный момент с природой напрямую. Но день остаётся днём.
Но сова — глобальная переменная.
Но почему?
Это так же, как время суток.
Нет, не так же. Между совой и временем суток общего приблизительно столько же, сколько между вороном и конторкой.
Глобальная переменная время суток имеет значение — сейчас день.
… это до тех пор, пока для всех наблюдателей время суток одно. Что, скажем, в многопользовательской системе не обязательно одно и то же. Но не суть.
Да, текущее время — это хороший пример "глобальной переменной". Но даже это иногда оказывается неудобно (я уже приводил пример с ITimeProvider
). Но вот сова тут не при чем.
Но день остаётся днём.
… а вы, случайно, не путаете состояние с переменной?
Глобальная переменная — это про область видимости. Срок жизни может быть любым. Если вы считаете иначе, то стоит пересмотреть свое мнение.
А теперь, давайте сделаем диалог менее философским. Напишите на любом языке программирования глобальную переменную с ограниченным сроком жизни. (Я могу, кстати. Drop Sequence в Оракле. Но это настолько редкий и вырожденный случай......)
Только не путать удаление переменной с обнулением её значения.
Напишите на любом языке программирования глобальную переменную с ограниченным сроком жизни.
Питон.
>>> q = 3
>>> q
3
>>> del q
>>> q
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'q' is not defined
Во многих языках есть деструкторы.
В цитируемом вами Delphi — тоже, причем в переопределенном деструкторе я могу не обнулять ее значение. Но доступа к переменной уже не будет.
Мне неизвестно, как в Delphi прервать существование глобальной переменной до завершения работы программы. Можно освободить память, можно присвоить длину ноль динамическому массиву (присвоением nil), можно сделать экземпляру объекта Free или FreeAndNil, но удалить переменную в этом языке, как мне представляется, нельзя.
Смысл, которого так можно добиться — а именно, когда переменная больше не нужна, то использовать ту же область памяти для хранения других данных другого типа — в Delphi реализовать можно, используя механизм record case of. Но развоплощения переменных в этом языке нет.
Видимо, я не понял как работает деструктор в Делфи. Мои ожидания
- память помечается как неиспользуемая и может быть занята другими данными
обращение по указателю (все объекты — это указатели?) на эту область памяти приведет к ошибке.
Т.е. код вида
GlobalVar := TObject.Create; LocalVar := GlobalVar; GlobalVar.Destroy; WriteLn(LocalVar);
вызовет ошибку.
Если после этого данные из этой переменной можно достать без трюков — значит, переменная действительно живет все время работы программы. В Делфи. Это не отменяет исходного постулата, что в общем случае глобальность переменной — про область видимости, а не цикл жизни.
var GlobalVar,LocalVar:TObject;
begin
GlobalVar := TObject.Create;
LocalVar := GlobalVar;
GlobalVar.Destroy;
Здесь, переменные GlobalVar и LocalVar работают как тип pointer. Они состоят из 4х байт (если выбрать 64х-битный режим компиляции, из 8 байт), а сама структура лежит в ОЗУ.
Вы создали две переменных, указывающих на одну и ту же область памяти. Затем, командой Destroy (если в самом деле будете писать на Delphi коммерческий продукт, никогда не используйте Destroy, используйте Free или FreeAndNil.) эту память освободили, а ссылка осталась.
Затем… я даже не понимаю что вы делаете. Строка
WriteLn(LocalVar);
ожидаемо, не компилируется, она не имеет смысла.Но допустим, вы имели ввиду что надо считать что-нибудь из объекта. Вы ожидаемо получите ошибку чтения (либо, что намного хуже, её не получите), так как память на которую ссылался LocalVar удалена. Но это не говорит о том что переменной не существует.
Представьте, что переменная это билет на поезд, в нём написано что вам надо сесть в вагон номер 10. Приехал поезд, а в нём только 7 вагонов. Продолжает ли существовать купленный вами билет? Да.
Ну что же, я удовлетворен, что вы не спорите с тем что
в общем случае глобальность переменной — про область видимости, а не цикл жизни
Если мы говорим о глобальных переменных, то должны быть и локальные. Я дал вполне говорящие имена, но вы привели пример с глобальной переменной LocalVar :-) Я имел в виду что-то вида
var GlobalVar:TObject;
procedure SomeProcedure()
var LocalVar:TObject;
begin
GlobalVar := TObject.Create;
LocalVar := GlobalVar;
{GlobalVar.Destroy;}
Возможно, я снова что-то упустил — моя цель продемонстрировать идею, а не написать компилирующийся код на языке, который не знаю изучал 20 лет назад. Идея же в том, что хотя у нас есть переменная, она ссылается в Неизведанное — даже не в null — и потому "померла".
Делфи вообще уникальна тем, что описание переменной синтаксически жестко разделено с ее инициализацией. Для меня было неожиданностью прочитать, что есть глобальная переменная модуля, которая не совсем глобальна.
Также странно для меня звучат слова (из той же ссылки и ваши), что Глобальные переменные создаются во время запуска приложения. В моем понимании цикл жизни переменной начинается с момента ее инициализации, т.е. присвоения ей значения (справа либо конструктор, либо другая переменная, либо выражение) и заканчивается деструктором (вызываемом явно или неявно).
Видимо, мое понимание отличается от идеологии Делфи, но справедливо для многих других языков.
В моем понимании цикл жизни переменной начинается с момента ее инициализации
На самом деле — с момента выделения памяти. Вот чем эта память инициализирована — вопрос занятный, и в каждом языке свой.
В моем понимании цикл жизни переменной начинается с момента ее инициализации, т.е. присвоения ей значенияА вам не кажется, что для того, чтобы присвоить «ей» значение, сначала «она» должна уже существовать?
Физически, переменные это области ОЗУ. Допустим, программа использует 200 переменных. Значит, машинный код внутри exe файла будет переменную номер 19 рассматривать как место в блоке ОЗУ, выделенном программе, начальный адрес которого равен размеру всех переменных с 1 по 18. (Там чуть сложнее, так как размер переменной компилятор часто дополняет до чуть большего ради роста скорости обработки).
Программа при запуске не запрашивает у операционной системы (любителям свеххточности — у менджера памяти) каждую из 200 переменных отдельно. Программа сразу говорит винде, сколько памяти уже занято блоком из переменных. Выделение этой памяти выполняется на уровне виндовс до того как программа начнёт работу.
При чём тут присваивание значения? Это произойдёт позже, по мере выполненния кода.
Программа сразу говорит винде, сколько памяти уже занято блоком из переменных.
Я надеюсь, вы сейчас про какие-то конкретные программы говорите, а не про все вообще?
«Говорит» — неудачный термин, так как код к этому моменту ещё не начал выполняться. Я говорю про exe файлы.
… про все exe-файлы? Потому что, насколько мне известно, .net работает не так. Да и вообще под Windows, говорят, запускающийся поток просто получает выделенный стек фиксированного размера (и у этого размера есть значение по умолчанию, которое никак не связано с тем, какие у вас переменные).
(собственно, в случае с .net даже не очень понятно, размер каких "переменных" надо сообщать Windows)
Допустим, что первая строка программы — такая: MyArray[12345]:=6.
Объясните, как программа может начать выполняться до того, как под статический массив MyArray была выделена память?
Физически, переменные это области ОЗУ
Не факт. Это могут быть регистры.
Поскольку речь шла про выделение памяти переменных в момент запуска exe файла, можно было додумать, что речь идёт про глобальные. Глобальные переменные могут храниться только в ОЗУ. Есть исключение: delphi — реализация цикла for, в котором переменная хранится и в ОЗУ и в регистрах, а по завершению цикла delphi-программистам предписывается считать, что её значение неопределено.
Если этого не знаешь заранее, то додумать такое сложно. Особенно для "в момент запуска". То, что я помню из exe файлов — там память для "глобальных переменных" выделяется до запуска программы. ОС выделяет память, копирует туда данные из файла и только тогда запускает
по завершению цикла delphi-программистам предписывается считать, что её значение неопределено.
Вот для меня состояние глобальной переменной "значение не определено" является синонимом "цикл жизни завершился". Если после этого мы присваиваем ей какое-то новое значение, начинается новый цикл жизни.
Но это на мой дилетантский взгляд. Возможно, создатель какого-то языка считал иначе.
А вам не кажется, что для того, чтобы присвоить «ей» значение, сначала «она» должна уже существовать?
Существование переменной можно трактовать по-разному. Я вижу минимум два варианта
- Переменная существует с момента, когда она объявлена. Код еще не выполняется, память под переменную не выделена, но переменная как GlobalVar уже существует (например, ей можно присвоить null). С этой точки зрения трудно объяснить, почему обращение существующей, но непроинициализированной переменной должно вызывать ошибку.
- Переменная существует с момента, когда для нее выделена память/ей присвоено значение. В этом случае объявленная GlobalVar2, но не используемая в коде не существует. У нее есть область видимости, но нет времени жизни.
Вполне возможно, существуют и другие точки зрения.
Выделение памяти в разных языках происходит в разные моменты времени. В managed (Java, C#) память выделяется VM в момент создания объекта, т.е. когда отрабатывает конструктор.
В Си и Делфи все иначе. Как я понимаю, и там и там память не выделяется при объявлении переменной (ведь непонятно, сколько выделить для SomeVar: TObject — в будущем она может указывать и на объект без полей, и на мегабайтного монстра). Если это не так — скиньте ссылки, почитаю для развития.
С этой точки зрения трудно объяснить, почему обращение существующей, но непроинициализированной переменной должно вызывать ошибку.
Ну вот в PHP, если ничего не путаю, разделяется переменная как имя в качестве ключа в "таблице" переменных, где значение — указатель на отдельную структуру данных zvalue. И вот этот указатель может быть null, и этот не PHP null, а C и какой-нибудь var_value->type выдаст ошибку типа NPE.
память под переменную не выделена, но переменная как GlobalVar уже существует (например, ей можно присвоить null).Вы путаете три разных понятия, которые не стоит путать.
1) Переменная может быть указателем, ссылающимся на область ОЗУ, где хранятся данные. Тогда память (не под переменную!!! а под данные на которые она смотрит!!!) не выделена, и поэтому в переменной хранится nil (а не Null!!!).
2) Переменная может допускать значение Null. Например, в Delphi это класс переменных variant, а в СУБД — все типы столбцов, которым не задан атрибут Not Null. В этом случае в переменной может храниться Null, как значение. В теории БД, Null должно означать: значение есть, но мы не знаем его. (Из за такой трактовки, условие Null=Null считается равным False.) В практике не так, например для строчных ORACLE переменных, Null — пустая строка.
3) Переменная может ещё не существовать. Например, в ORACLE возникнет ошибка при чтении из Sequence, которой ещё не создано.
В теории БД, Null должно означать: значение есть, но мы не знаем его. (Из за такой трактовки, условие Null=Null считается равным False.)
Как раз наоборот, в БД NULL это отсутствие любого значения. И именно поэтому (NULL=NULL) = FALSE. Значение отсутствует, поэтому оно не может быть ничему равно.
Грубо говоря, допустим, есть 100 автомобилей и мы ходим вокруг запертой стоянки и переписываем их номера глядя через забор. Переписали номера 98 автомобилей, на одном автомибиле номеров нет, и один так поставлен что номеров увидеть нельзя. Значит, имеем: 98 значений, 1 Null в смысле описанном вами, 1 Null в смысле описанном мной.
Можно ли сказать, что у Васи и Пети одинаковый цвет волос, если мы их никогда не видели, и фотографий нет? (мой вариант Null) — нельзя.
Можно ли сказать, что у Васи и Пети одинаковый цвет волос, если вот они перед нами, и они оба лысые? (ваш вариант Null) — я бы сказал что да, поэтому в вашем случае Null=Null — истина.
Значит, имеем: 98 значений, 1 Null в смысле описанном вами, 1 Null в смысле описанном мной.
Это значит, что у вас плохо сделан тип данных, раз вы не можете отличить "номера нет" от "номера не видно".
Меня больше всего интересует, какой из перечисленных разных смыслов вы придадите Null-у.
Меня больше всего интересует, какой из перечисленных разных смыслов вы придадите Null-у.
Никакой.
Добро пожаловать в мир типов-сумм:
type CarNumber =
| Unknown
| Known of string
| None
Навскидку, nullable свойство number. null — ничего не знаем про номер. Нормальный номер — инстанс класса Number. Отсутствие номера, пустой номер, неразборчивый номер, какие-то другие особые случаи — special case наследники этого класса или их общего предка AbstractNumber
NULL означает отсутствие значения поля при сохранении данных. Оно не говорит, по какой причине оно отсустствует. Если вам надо это хранить, одного признака "NULL или не NULL" недостаточно.
Ваша формулировка "значение есть, но..." не подходит для случаев типа автомобиля без номеров, потому я и возразил.
Внезапно, это глобальная переменная. То есть переменная, существующая в рамках одного сеанса работы одного пользователя, до того как он закрыл программу.
Вам уже это успели написать, но я еще раз повторю: вы путаете область видимости и жизненный цикл (причем еще и несколько разных).
"Глобальная переменная" — это про область видимости. Что характерно, и русская, и английская здесь согласны.
А "переменная, существующая в рамках одного сеанса работы одного пользователя, до того как он закрыл программу" — это про жизненный цикл. Вот вам простейший пример такой переменной, которая при этом не глобальная (не вдаваясь в разницу между сроком жизни программы и сроком жизни сеанса пользователя в ней, которые тоже могут не совпадать):
class Program
{
static void Main(string[] args)
{
var iWantToLiveForever = true;
//forever...
}
}
Но дальше веселее. Время жизни переменной не обязательно эквивалентно времени жизни объекта в ней. Ваша сова — это, в первую очередь, как раз объект, а уж в какую переменную ее запихивать (и, понятное дело, далеко не всегда только в одну) — дело пятнадцатое.
Ну и наконец, кроме времени жизни (программного) объекта, который за небольшими терминологическими оговорками, не превышает времени жизни самой программы, есть время жизни данных, которые могут быть сохранены в персистентное хранилище (и это ваша сова между запусками программы), и, наконец, время жизни моделируемой сущности, которая сущность может существовать до того, как вы в первый раз запустили программу и инициализировали данные.
Другой пример глобальной переменной — игровое время суток, игровой сезон года.
Это пример того, что можно хранить в глобальной переменной, но даже это не всегда удобно (не зря же появляются абстракции типа ITimeProvider
).
Кстати в мире Гарри Поттера есть машина времени временные петли. Например, там было два Гарри Поттера — один наблюдал за другим.
Я не помню наблюдалось ли там две Совы но не помню также фундаментального ограничения на этот счёт.
Синглтоны также неудобны при юнит тестировании — многопоточное тестирование не запустить и каждый раз надо аккуратно восстанавливать состояние.
Передавать по ссылке, например. Сам объект находится глобально в оперативной памяти, а на него есть локальные ссылки, которые представляют этот объект внутри конкретного класса или метода.
Она не глобальная переменная. Она, по идее, свойство либо объекта ГарриПоттер, либо объекта ИгровойМир.
Нет, он свойство объекта Игра, а игр столько сколько запустили пользователи
ПОЖАЛУЙСТА НАПИШИТЕ ПРОГРАММНЫЙ КОД ТОГО ЧТО ВЫ ГОВОРИТЕ.
Или вы считаете что делать игру, в которой каждый играет за Гарри Поттера лично, сетевой — это хорошая идея?
Вы, похоже, в Death Stranding не играли.
А чем она плохая?
Тогда надо рандомизировать персонажей и это будет другой проект.
Факультет — такая же часть ИгровогоМира.
Если игра однопользовательская по геймплею, то у каждого пользователя свой ИгровойМир, у которого свой ГарриПоттер, Сова и Факультет. При том на игровом сервере может быть много ИгровыхМиров
Даже в такой реализации, глобальная переменная на клиентском устройстве тоже нужна. Аппендекс в виде ненужного сервера не меняет этот факт.
Например для прозрачной миграции игрока с одного компьютера на другой. Поиграл на работе, пришёл домой и там продолжаешь с того места, где остановился.
Так же для минимизации возможности читинга (например, интернет-казино)
Глобальные переменные нужны прежде всего, чтобы получать к ним прямой доступ из любого места программы. Вам реально нужен доступ к сове из любого места программы?
1) День. На сову пролилась чернильница.
2) Вечер. Поттер ужинает. Рядом есть сова.
3) Ночь. Поттер спит, и ему снится, что он бабочка. Рядом нет совы.
4) Утро следующего дня. Поттер завтракает. Рядом есть сова.
Если мы удалим сову в начале пункта 3 и создадим в начале пункта 4, то как при отрисовке совы мы узнаем, что на неё менее семи дней назад пролилась чернильница, и поэтому на ней есть синее пятнышко?
Если мы удалим сову в начале пункта 3 и создадим в начале пункта 4,
Вот только это никак не связано с доступом к сове из любого места программы. Удалять ее никто не предлагал.
Update, решил развернуть:
Будем честными, "сова" в программе — это просто область (или несколько областей) памяти (если мы не говорим о персистентности, а мы о ней явно не говорим пока). Эта область — это не переменная, это структура данных (или даже объект, если нам так удобнее). Чтобы получить информацию о состоянии совы (или поменять это состояние), нам нужен адрес этой области, или более абстрактно, ссылка на эту структуру. Вот эта ссылка и лежит в переменной.
Так зачем этой переменной быть глобальной? На срок жизни совы (в разумной реализации) это все равно никак не повлияет.
Да вот вам пример, найдите ошибку в коде (не совсем Delphi, но смысл ясен):
var ЯТусуюсь:boolean;
Procedure ДавайДавай!;
var ЯПьян?:boolean:=False;
begin
case Random(4) of
1:Потанцевать;
2:Поорать;
3:begin
Выпивать;
ЯПьян:=True;
end;
else Тусить;
end;
if Time>=23:30 then
begin
ЯТусуюсь:=False;
if ЯПьян? then ЕхатьНаТаксиДомой
else ЕхатьНаСвоейМашинеДомой;
end;
end;
Procedure Тусовка;
begin
ЯТусуюсь:=True;
while ЯТусуюсь do
ДавайДавай!
end;
Ошибка в том, что ЯПьян? — локальная переменная. Должна быть глобальной.
Затем, что если она будет локальной, то после выполнения процедуры в которой эта локальная переменная выполнилась, её значение будет потеряно.
Ее "значение" — это всего лишь ссылка на область памяти. Что с того, что она потеряется? Или в известных вам языках программирования локальные переменные не могут ссылаться на объекты за пределами области видимости?
Смотрите, как прекрасно работает все:
void PetARandomOwl(Owl[] owls)
{
var owl = ChooseRandomFrom(owls); //локальная переменная
if (owl.AllowsToBeCaught)
owl.Pet();
}
Локальная переменная owl
прекратила свое существование вместе с закрывающей скобкой (а скорее всего и не было ее вообще никогда, компилятор все на стек положил). Однако объект, на который она ссылалась, никуда не делся, и его состояние, измененное в ходе вызова метода Pet
, так и осталось измененным.
Собственно, в C#, насколько я его знаю, вообще невозможны глобальные переменные. И ничего, живет как-то, не теряет состояние.
Не должна она быть глобальной, она должна сохранять своё значение между вызовами ДавайДавай!
— больше она нигде тут не используется. Локальная статическая переменная в терминах PHP.
А вот тот, как вы используете глобальную переменную ЯТусуюсь
является отличным примером того, почему глобальные переменные — зло по умолчанию.
А вот тот, как вы используете глобальную переменную ЯТусуюсь является отличным примером того, почему глобальные переменные — зло по умолчанию.
… хотя ее прекрасно можно сделать возвращаемым результатом ДавайДавай
.
Вы видели только две процедуры. И даже если так, почему в будущем я не захочу узнать пьян я или нет, в другом месте кода?
Ошибка в том, что ЯПьян? — локальная переменная. Должна быть глобальной.
Вот в том-то и дело, что нет. Достаточно сделать пьян
и тусуюсь
свойствами я
, которое я
и передавать в ДавайДавай
. Здравствуй, ООП.
Вот в том-то и дело, что нет. Достаточно сделать пьян и тусуюсь свойствами я, которое я и передавать в ДавайДавай. Здравствуй, ООП.
Смотрите как рассуждает религиозный человек: разве весь этот прекрасный мир мог возникнуть сам собой? Нет!!! Значит, его создал Бог. Поэтому, я поклоняюсь Богу.
При этом религиозного человека никак не смущает тот факт, что этот самый бог возник сам собой, ага.
Так же рассуждаете вы. Разве красиво, чтобы были глобальные переменные — сова, ЯПьян?, и т.д.? Нет!!! Значит, это свойства глобального объекта Я. И при этом вас не смущает, что теперь, Я — это глобальная переменная…
И при этом вас не смущает, что теперь, Я — это глобальная переменная…
Так в том-то и дело, что она не глобальная. Она локальная для Тусовка
(там создается, там и умирает).
(я же вам приводил уже такой же пример с совами)
Собственно, речь же не о том, что можно сделать глобальной переменной (хотя вот в C# ничего нельзя). Речь о том, зачем что-то нужно делать глобальной переменной.
Так в том-то и дело, что она [переменная «Я», со свойствами пьян(да/нет) и т.д.] не глобальная. Она локальная для Тусовка (там создается, там и умирает).
Правда? Подобная мысль была у Пелевина (Арлекин не существует за пределами карнавала).
А если в Procedure ДавайДавай! версии 2.0, помимо Я, реализуется взаимодействие с другими восхитительными объектами, то надо менять список параметров процедуры?
Правда?
Да, правда. И дело тут не в философии, а в вашем коде.
(ну и information hiding, конечно)
Аналогично, выше:
Вы видели только две процедуры.
… и именно по двум процедурам и судим. Невозможно предугадать, что находится за их пределами. Если что-то было важно, нужно было это приводить.
И даже если так, почему в будущем я не захочу узнать пьян я или нет, в другом месте кода?
Вот когда захотите (=изменение бизнес-требований), тогда и будем смотреть, как это реализовать. С очень большой вероятностью вам там нужны будут и другие свойства/действия я
, поэтому это не будет представлять проблемы.
А если в Procedure ДавайДавай! версии 2.0, помимо Я, реализуется взаимодействие с другими восхитительными объектами, то надо менять список параметров процедуры?
Конечно. Это позволяет явно отслеживать, от чего зависит ее поведение.
(ну то есть еще есть методы на сервисах и вбрасывание сервисов, но это запутает нашу дискуссию)
Именно здесь и проявляется разница между ООП и процедурным подходом, реализованным на языке с поддержкой ООП :-)
Если переписать эти две процедуры в ООП стиле (идеи lair уже описывал), то гораздо реже приходится менять список входных параметров аргументов и в целом беспокоиться а что если в версии 2.
Я сделаль!
Сова должна быть описана как локальная переменная класса «железнодорожный перрон»habr.com/ru/post/511572/#comment_21863620
Кормушка это просто хранилище, без методов. А для метода положить еду должен быть промежуточный объект, который знает как положить еду в конкрентную кормушку. В данном случае таким является человек, не важно что за кормушка, кладёт еду человек, и он знает как. Поэтому у кормушки не должно быть методов.
Исторически, ООП возникло после процедурного программирования — когда данные передавались в процедуру, которая их изменяла. Есть определенные отличия функционального программирования от процедурного, но хочу обратить внимание на другое.
Одним из преимуществ ООП над процедурным является то, что объект отвечает за свое правильное состояние. В ООП метод кормушка.положитьЕду() может проверять переполнение, например. Если же кормушка — это просто контейнер, то проверки на переполнение/пустоту кормушки расползаются и/или дублируются — это типичная проблема процедурного подхода.
Кормушка1.Isoverflow:boolean
и не ООП:
Кормушка_Isoverflow(@Кормушка1):boolean
(Здесь, "@" — означает что передаётся указатель, а не структура).
А почему вы считаете, что первое — это ООП, а второе — нет? Если читать только запись, то может быть строго наоборот. Что в деталях реализации заставляет вас утверждать иначе?
Она означает, что я прочитал код, который вы написали, и не понимаю, почему вы считаете, что что-то там ООП, а что — нет.
не ООП — классы не создаются
Классы (в Delphi) — это type ...=class(...)
Кроме классов визуальных форм, которые делает сама среда.
Простите, что приземлил вашу высокую философию.
ООП — классы создаются
не ООП — классы не создаются
Ээээ… то есть вы серьезно считаете, что ООП — это классы?
Википедия
Object-oriented programming (OOP) is a programming paradigm based on the concept of "objects", which can contain data, in the form of fields (often known as attributes or properties), and code, in the form of procedures (often known as methods).
Можете выяснить, что бывает ООП без классов. А если почитать Кея, то можно выяснить, что ООП вообще требует только… объектов и сообщений.
procedure TOverridingPricesHolder.TParallelSearchOverrideContainerAction.TParallelSearcher.TSearchTask.TSPOWorker.DoGetSpoOverContainerCol;
Программисты Delphi меня поймут.
Вы в своих идеях про ООП похожи на теоретика коммунизма, который приехал в постреволюционный СССР. А под красивыми названиями совершенно другое делают (см. выше).
Я не читаю русскую википедию (равно как и английскую)
Тогда зачем вы на нее ссылаетесь?
я работаю с Legacy code который написан на ООП
… и делаете вывод, что весь ООП такой же? Зря.
А под красивыми названиями совершенно другое делают
То, что "другие люди" под красивые названиями делают что угодно, совершенно не мешает мне делать под этими же названиями то, что я считаю нужным.
ООП — это подход к программированию, если хотите, способ.
Я видел немало кода, написанного на ООП языке, с классами, но в процедурном стиле. Шаблонный пример — GodObject.Кормушка_Isoverflow(@Кормушка1):boolean
И я сам писал в ООП стиле в системе, исповедующей функциональный подход — за счет чего решения получались кривыми и малопонятными даже мне.
То есть если использовать инструмент (классы) как задумано, результат не гарантирован (выйдет не-ООП код).
Согласен, что нет большой разницы.
В то же время, разница проявляется в том, кто отвечает за проверки. В ООП — объект кормушка, в процедурном — тот, кто хочет ее пополнить. Когда моделируемый мир состоит из нескольких сущностей, это непринципиально.
В случае больших систем проверки if not Кормушка_Isoverflow(@Кормушка1) then пополнить_кормушку() оказываются в разных процедурах, ответственность за переполнение размывается. Это может привести к ошибкам, особенно при изменении бизнес-логики: придется смотреть где проверку изменять, и возможно пропустить какие-то пограничные случаи. То есть эта разница, небольшая, позволяет избегать ошибок — в больших системах.
Вот именно с такого подхода и начинается переусложнение систем
Неправильно составленное ТЗ ведёт к неправильному решению задачи.
Метод Человек.НакормитьКошку предполагает следующую последовательность действий:
1) Надеть на руки перчатки;
2) Взять в левую руку кошку;
3) Взять в правую руку ложку с едой;
4) Предпринять попытку засунуть ложку в рот кошке;
Скорость поглощения пищи зависит от отношения свойств Ложка.Вместимость/Кошка.УровеньСопротивления, время кормления зависит от отношения свойств объектов (Перчатки.Толщина*Человек.Упорство)/(Кошка.ДлинаКогтей*Кошка.УровеньСопротивления).
Метод Человек.ДатьКормКошке предполагает уже совсем другие действия посредством взаимодействия с объектом Кормушка. При этом к объекту Кормушка может быть применён метод Destroy, если количество корма очень маленькое, а свойство объекта Кошка.Настроение имеет очень большую отрицательную величину.
Кормушка — это пластмассовая мисочка. Кормушка не имеет собственных методов, это глобальная переменная, либо запись в базе данных (смотря, что и как пишем). Это не объект.
Вас неприятно удивит, что у кормушки может быть встроенный таймер, и она может выдавать корм по расписанию. Может самостоятельно отправлять уведомление на телефон, что запас корма подходит к концу. Может вести суточный график потребления корма котами. Кормушка — это точно не глобальная переменная или запись в базе данных.
Другие условия — это другая задача, имеющая другое решение.
Та кормушка о которой вы говорите не требует участия человека для одного действия кормления кошки. С тем же успехом можно рассмотреть в качестве животного робота от Boston Dynamics. Другие условия — другие решения.
Тогда надо определить, что такое «покормить»:
- насыпать корма в кормушку;
- заставить кошку съесть корм из кормушки.
В первом случае это метод объекта Человек, он устанавливает КоличествоКорма у объекта Кормушка. Во втором случае — приватная функция объекта Кошка, кошка внутри себя хранит ссылку на Кормушку и самостоятельно уменьшает\обнуляет переменную КоличествоКорма у Кормушки.
Как ни странно, функциональщики решили задачу вторым способом через метод объекта: «функция покормитьКошку() принимающая в качестве аргумента ссылку на кошку и кормушку» есть то же самое, когда объект Кошка сам уменьшает количество корма в кормушке, «ссылка на кошку» в ФП — это неявный this объекта Кошка в ООП.
В таком случае, задача поставлена неверно, так как изначально кормить должен человек, а не кормушка. А кормушка это лишь средство расшаривания еды.
А в вашем примере это уже реальный актор, который независимо от человека асинхронно его действиям по наполнению, выдает порцию корма
Это другая задача с 3 акторами.
Кормушка — это пластмассовая мисочка. Кормушка не имеет собственных методов, это глобальная переменная, либо запись в базе данных (смотря, что и как пишем). Это не объект.
Это зависит только и исключительно от задачи. Если у вас кормушек больше одной, у них есть общие и различающиеся свойства и поведение, то это, несомненно, объект.
А противопоставлять переменную и объект вообще бессмысленно, это, гм, сущности разного порядка.
В реальности это взаимодействие двух black box систем — человека и кошки. Под «покормить» мы имеем в виду исполнение кошка.есть(<предложенная нами еда>), но этот метод относится к скрытой части реализации, и напрямую быть вызван не может — зато можно проверить, исполняется ли он или нет. Поэтому реализация, отличная от человек.покормитьКошку(человек.выбратьСтратегиюКормленияКошки(кошка), кошка) — заведомо будет обречена на провал. Кормушка тут и вовсе не нужна, это тривиальные внутренние детали реализации одной из возможных стратегий.
Функциональная версия будет обязана содержать в себе все детали по разным стратегиям (например, описание того факта, что сыпать еду в кормушку можно только живым настоящим кошкам), и при встрече с каким-то не описанным типом кошек — либо паниковать, либо действовать наугад.
А «уберите от меня это ужасное ООП» обычно происходит из-за бесконтрольного нарушения связности. Скажем, за отсутствием необходимости моделирования физики и вообще всего-всего — реализовали кормушку с методами «добавить еду» и «убавить еду» (как, собственно, оно и должно быть как максимум), а потом выяснилось, что кошка еду может и разбросать, и нужно срочно чинить расхождения — и какой-нибудь программист в мыле влепляет добавляет в кормушку private окружение: World, а спустя некоторое время кормушка у нас вдруг отращивает глаза, крылья, ноги, и сжирает кошку, человека, его дом, и вообще останавливает пространство и закукливает время через некорректное использование World. В ФП связность тоже можно внаглую сломать, кстати.
У кормушки есть интерфейс добавить еду, чем человек и пользуется.
А кот, он просто подписан на событие кормушки, отвечающее за то, что еда там обновилась.
Дальше у кота вступает в дело какой-то сложный алгоритм на основании которого он решает, что же делать с этой едой, то ли мимо пройти, то ли всё же поесть.
Кстати, вы тоже поняли задачу как предоставить еду, при чем тут часы.
Никто не собирается именно «кормить» кошку.
Человек, уехавший в отпуск, оставляет инструкцию соседке — и она использует её. Назовем это стратегией.
У человека есть набор стратегий по обработке объектов. Эти стратегии могут меняться. Этими стратегиями можно обмениваться, потому что они сделаны — вот сюрприз! — как чистые функции, не зависящие от текущего владельца стратегии. Им максимум приходит абстрактный объект с интерфейсом «контекст текущего пользователя», если нужно позаимствовать какие-то особенности текущего контекста.
Стратегии — это мощный паттерн, делайте их! Если язык поддерживает standalone-функции — их часто можно делать чистыми функциями.
Но повторюсь, вот такие рассуждения о человеке-кошке только затуманивают суть )
Человек покормил кошку, в монстра ударила ракета
… Сотрудник переходит в соседнее подразделение с изменением должности. Кто на ком стоял?
company.personnelDepartment.moveEmployee(employee, srcDepartment, dstDepartment, newPosition)
Как правильно:
— Человек.FillUpCatsPlate
— Кошка.Update() {
if(хочу кушать) {
// найти полную кормушку и покушать из нее
}
}
Человек не может и не должен управлять кошкой.
Есть три объекта: кошка, кормушка и человек. Вам необходимо написать метод, который бы позволял человеку покормить кошку, воспользовавшись кормушкой.
Вопрос: методом какого класса будет являться метод.покормить()?
Постановки задачи не совсем понятная, кошка будет кушать кормушку? или кто то забыл про корм?
ну ок:
Человек.СделатьКормушкуСъедобной;
Кошка.КушатьИзКормушки;
Какого объекта этот метод? а четвертого объекта, который называется СистемаВЦелом.
СистемаВЦелом получает на вход всех этих Человек Кошка Кормушка и ими оперирует в соответствии с бизнес логикой.
DDD for ever. Человек думает понятиями, поэтому разработка в понятиях (объектах) это естественно и удобно для человеческого мышления.
Кто там что то имеет против ООП идёт на Ютуб смотреть видео Егора Бугаенко, его лекции об ООП, о том что самое главное в ООП это правильно создать объект — в конструктор надо передать все объекты нашей предметной области или фабрики что бы создать такие объекты в достаточном количестве.
Просто не нужно мучать кошку. У вас дома наверняка есть объекты под итерфейсом «дляНасыпания» и «можноНасыпать». Вот у вашего человека должен быть метод, который насыпает насыпаемые объекты в объеты в которые можно насыпать. Кормится или не кормится тем что насыпано, проблемы кошки, а не человека.
2. Стать хозяином Измаила не метод, а результат действия «победить в сражении».
3. Действие происходит между человеком и пищей, человек перемещает пищу из холодильника в тарелку. Мотивация — это относится к бизнес логике, кормить кошку, кормить собаку, кормить себя.
4. Вы напутали с интерфейсами. Выстрел — это часть интерфейса «стрелковое оружие». Для ножа говорить о выстреле, как и об оставшихся патронах неуместно.
5. Чтобы что — то потрогать руками нужно, чтобы они у вас были, и объект, который вы собираетесь потрогать допускал это (попробуйте потрогать свет :)). Если эти 2 требования выполнены, то реализация операции «прикосновение» становится достаточно простым делом.
2. В сражении по какому поводу? Может быть, всё таки по поводу завоевания крепости Измаил? То есть основная процедура — это получить Измаил, а сражение — основное действие для её реализации.
3. И я о том же.
4. И я о том же. Ок, не выстрел, а обобщённое Атаковать. Для ножа, действие по изменению состояния объекта в игре станет «измазан свежей кровью». Как я и писал, изменение состояния объекта программируется на уровне дочернего класса (нож и шотган — дочерние классы от TWeapon).
5. Набирая сейчас это сообщение, я трогаю ноутбук. Чтобы полноценно описать это, нам потребуется использовать интерфейс, характерный для ноутбука.
Если потребуется затем описать как я трогаю глину во время лепки, то это будет другой интерфейс, и другая терминология.
закон физики это не только формула, но и смысл.
И смысл закона Ома — взаимосвязь 3-х величин. Там нет "ведущей" величины и "ведомых", они все равноправные, и выбор величины слева от знака равенства определяется лишь тем, какая из них неизвестна.
Или на входе электроплиты образуется 220 вольт потому, что она греет сковородку?
Вообще-то причина и следствие в этом вопросе довольно просто определяется.
Это лишь потому, что вы знаете, что подано 220 в, это вам задано как начальное условие. Возьмите задачу, где задано, что через плиту протекает ток 2 ампера, известно её сопротивление, и нужно определить мощность. Величины напряжение
там даже не возникнет: P = I^2*R
. Если сопротивление меняется по длине, то на каждом участке будет выделяться своя мощность — значит сопротивление "главное"?
Или возьмите задачу, где подключены последовательно 2 разных плиты. Вы не знаете, какое напряжение на клеммах каждой плиты, но знаете, что там одинаковый ток. И теперь что, напряжение вызывает ток, или ток вызывает падение напряжения? Как плиты узнали и договорились, кому какая доля напряжения достанется?
- Смысл в том и есть, что вы неверно выделили объекты для реализации. Отсюда все противоречия. Если взять "источник питания" со свойством "напряжение", "нагрузку" со свойством "сопротивление" и объект "электрическая цепь", который будет содержать инстансы первых двух как свойства, то ваши 3 формулы станут методами последнего, и я не понимаю, что по вашему не так с ООП в данном случае.
- Сражение за Измаил часть более крупной системы под названием русско-турецкая война, где есть масса взаимодействующих объектов и условий. У вас возникло противоречие, потому что вы результат, в общем — то побочный, объявили главным, и кроме этого, вы его рассматриваете вне контекста системы. Битва за это место шла, потому что с него удобно атаковать неприятеля, или наоборот, защищаться. Здесь вообще могло не быть города.
- Здесь, наверное, был не прав, когда исключил кота из рассмотрения. Кот имеет свою реализацию важных методов "этоЕдаДляДанногоКота()", "требоватьЕдыОтХозяина()" и свойства "съедает за раз". Человек имеет свою реализацию метода "повестисьНаТребованияЭтойСкотины()". Миска вряд ли имеет важное значение и потребует реализации, главное, чтобы она была (ну или бог с ней, пусть будет). А вот корм скорее всего придется выделить ради хранения величины его запаса, только это будет не пакет, а корм как таковой. При желании можно пересчитывать в пакеты, килограммы, или миски. Естественно корм должен удовлетворять критерию "этоЕдаДляДанногоКота". Ну и нужен объект, который объединит все это в композицию, конечно, если вы не собрались прикармливать чужого кота, назовем этот объект "Семья". "Семья" корректно свяжет кота со своим хозяином и местом для хранения запаса кормов. Тогда реализация метода хозяина "покормитьСкотину()" станет простым перемещением количества корма в миску, в объеме который определяется потребностями питомца.
- Здесь прям не знаю что сказать. Для нужд игровой логики нужно хранить одни величины, например запас патронов, для нужд визуализации — другие, например, если вы в бою, то оружие может быть испачкано кровью. Что отнести к состоянию игрока, а что к его амуниции, наверное программисту виднее, вряд ли здесь все так уж однозначно можно расписать.
- Чтобы прикоснуться к предмету, он должен обладать поверхностью, ну или чем — то, что можно ею счесть (по этой причине, прикосновение к свету скорее всего невозможно, а прикосновение к воде можно принять с некоторыми оговорками). Раз уж мы собрались все трогать руками, они у нас должны быть. Прикосновение — это приведение расстояния между какой — то частью поверхности предмета и какой — то частью поверхности руки к нулю. Чтобы описать это, вам потребуется описать геометрическую форму руки и форму предмета. Какие нафиг интерфейсы!?!?
Поддержу ту точку зрения что нельзя отождествлять ООП с познанием или моделированием. Так и хочется сказать словами Гоголя, при чем тут турки. Нам будет плохо а не туркам.
Но откуда взялась эта маркетинговая чушь я бы уточнил. Она исходит не из необходимости продвигать ООП языки а от расплодившихся в 90 гуру, которые закидали рынок книгами которые по объему раза в четыре превышают печально известный краткий курс истории КПСС в простонародье кирпич.
Я принципы ООП к счастью изучал по вышедшей в начале 90 х книге Барбары Лисков Использование абстракций и спецификаций при разработке программ. Если сказать по простому то объект это данные и методы работы с данными. У нас есть простые типы. При помощи простых мы строим более сложные типы. И никакой философии.
А кому все же философии нужно то рекомендую познакомиться с трудом Уемова Вещи, свойства, отношения. Там как раз об этом. И о инвариантности разных моделей тоже есть.
Очевидно, что множество всех квадратов является подмножеством множества всех прямоугольников. Откуда необходимым и достаточным образом следует, что квадрат — это прямоугольник (квадрат «является» прямоугольником). В ООП это не так. Объясните почему.
В ООП это не так.
Да нет, в ООП это тоже так. Просто модель надо строить соответствующим задаче образом.
Да нет, в ООП это тоже так. Просто модель надо строить соответствующим задаче образом.Не очень понято, что вы хотите сказать: что для одних задач квадрат это прямоугольник, а для других нет — в зависимости от того, как модель построить под решение конкретной частной задачи?
Нет, я хочу сказать, что для разных задач могут быть интересны разные свойства (и операции) квадрата и прямоугольника.
Для многих задач на самом деле. Вернее для них не имеет значения, прямоугольник квадрат или нет.
Банальное перемещение по экрану в графредакторе — Нужно только координаты какой-то узловой точки менять.
Для меня сложнее представить предметную область, где обязательно нужно знать, что квадрат — это прямоугольник и он должен полностью соблюдать контракт прямоугольника, что исключает в этом контракте операции, подразумевающее независимое изменение ширины и высоты. Разве что геометрические задачки какие-то.
Наследуем класс квадрат от класса прямоугольник, в конструкторе проверяем равна ли ширина высоте. Опционально добавляем метод isRegular().
Upd: Сорян, промахнулся.
В ООП детализация абстракции выбирается под конкретную задачу. Смена базиса абстракции без смены задачи приведет к крайне забавным эффектам.
Вариант 1 — определяем прямоугольник как нечто с 4 прямыми углами. Тогда какой бы дизайн квадрата не выбрали — квадрат это прямоугольник. Разница объектов выражает разницу в использовании — разные стороны прямоугольника вынуждают иметь два метода для получения длин. Квадрат вполне спокойно будет иметь эти же методы.
Вариант 2 — определяем прямоугольник через ширину и длину. В этом случае есть соблазн для квадрата убрать избыточность параметров, и у нас получается квадрат(с единственным параметром конструирования) — совсем не прямоугольник.
В целом, перенос абстрагирования из одной области в другую оказывается неверным(читай, ведет к нежелательным результатам или непомерной ценой).
Объекты идут от абстракции, абстракция — от задачи. Абстракции не должны идти от объектов, имеющихся в наличии, как иллюстрация задачи.
Допустим, есть класс Прямоугольник.
У него есть метод ЗадатьРазмеры(Ширина, Высота);
И у него есть свойство Площадь.
Теперь создаю дочерний класс Квадрат.
Переопределяю метод ЗадатьРазмеры(Ширина); с ключевым словом Reintroduce.
Реализация:
ЗадатьРазмеры(Ширина);
begin
inherited ЗадатьРазмеры(Ширина, Ширина);
end;
И всё, имеем квадрат. Можно смело читать из свойства Площадь дочернего класса.
… и что будет, если у "дочернего класса Квадрат
" вызвать метод ЗадатьРазмеры(2, 4)
?
Список входных параметров метода ЗадатьРазмеры при наследовании был изменён. Поэтому Reintroduce, а не Override.
В таком случае ваш Квадрат
больше не Прямоугольник
, потому что он не выполняет контракт Прямоугольника
.
type TKvadrat=Class(TPriamougolnik)
...
var v:TKvadrat;
begin
v:=TKvadrat.Create;
v.SetSize(123);
if v is TPriamougolnik then ShowMessage('да') else ShowMessage('нет')
Любой другой язык программирования, насколько я знаю, скажет тоже «да».
Да и свойство для чтения Площадь, наследуемое от прямоугольника, работает.
Delphi с вами не согласен.
Мне, если честно, все равно, что по этому поводу думает Delphi.
Любой другой язык программирования, насколько я знаю, скажет тоже «да».
В C# вам не удастся сделать так, чтобы у Квадрата
, унаследованного от Прямоугольника
, не было метода ЗадатьРазмеры
с двумя параметрами. Потому что с точки зрения C#, если что-то — это прямоугольник, то у него есть все методы и свойства, определенные для прямоугольника. И это, знаете ли, соответствует моим ожиданиям.
Потому что вот у вас есть метод, который принимает на вход объект типа Прямоугольник
. Можете ли вы передать туда ваш Квадрат
? А можете ли вы вызвать метод ЗадатьРазмеры(Ширина,Высота)
?
Можно, но его сигнатура не изменится, и его все так же можно будет вызвать (возможно, иногда через явное указание типа, но это не важно). А выше по треду утверждается, что в Delphi можно сделать так, что изменится именно сигнатура:
Список входных параметров метода ЗадатьРазмеры при наследовании был изменён. Поэтому Reintroduce, а не Override.
ЗадатьРазмеры(Ширина, Высота); override;
…
ЗадатьРазмеры(Ширина, Высота);
begin
if Ширина <> Высота raise(Exception.Create('Это не квадрат.'));
inherited ЗадатьРазмеры(Ширина, Высота);
end;
Годится вам?
Конечно, не годится. Вы принцип подстановки нарушили. Собственно, проблема "Прямоугольник ли Квадрат" — она именно в этом.
Прямоугольник = это разновидность Параллелепипеда
Параллелепипед = это разновидность Четырёхугольника
Что не так?
Чем вам так не нравится наследование квадрата от прямоугольника унаследованного от параллелепипеда унаследованного от четырёхугольника?
Вот смотрите. У номера кредитной карты должно быть не менее 16 цифр. У квадрата должна быть ширина равна высоте.
Допустим есть диалоговое окно ввода номера карты. Человек ввёл 15 цифр. Ему выдают ошибку.
Допустим есть диалоговое окно ввода размера прямоугольников, но при этом конкретно сейчас с его помощью вводят размер квадрата. Человек ввёл 3х4 метра. Ему выдают ошибку. При этом не важно как выглядит окно, ошибка придёт из серверной стороны при попытке сохранить.
То есть сначала мы пишем в поля, потом делаем Save. На этом этапе серверная часть распознаёт есть ли в данных расхождения. Так же как с номером карты — выбирается тип платежа (банковская карта, а не Яндекс кошелёк или что-то ещё) и номер. До ввода всех полей и вызова сохранения, данные не обязаны быть согласованными.
Почему наличие ограничений по сравнению с классом предка делает квадрат не прямоугольником?
Конечно, у этого есть решения в рамках ООП (даже на википедии), но сама проблема-то существует.
Допустим теперь, что на склад поступили ноутбуки где ОЗУ увеличить нельзя. Значит, на ноутбуках этой марки, вызов метода начнёт выдавать ошибку, с осмысленным сообщением: выбранная марка ноутбука не позволяет увеличить ОЗУ.
Допустим, для них создан подкласс, по названию их фирмы производителя, скажем, TNotebookAdidas.
Следуя вашей логике, такие ноутбуки не являются ноутбуками?
Следуя вашей логике, такие ноутбуки не являются ноутбуками?
Да нет, просто как и большая часть аналогий, эта аналогия ложна. В ноутбуке либо можно добавить память, либо нельзя, это такое вот свойство ноутбука, которое неплохо бы изначально заложить в метод "добавить память". Но вне зависимости от того, заложили мы это свойство, или нет, ноутбук от добавления памяти марку не поменяет.
А вот с прямоугольниками все наоборот: любому прямоугольнику можно задать разные длины сторон. Просто те из них, которые были квадратами, перестанут быть квадратами.
Квадрат этому условию полностью удовлетворяет.
И? Я вроде бы сказал, что прямоугольник, у которого все стороны были одинаковой длины, а потом стали (попарно) разной, перестает быть квадратом.
Каким именно образом у вас прямоугольник вдруг меняет свои размеры? Это в каком мире происходит, что вы моделируете? Прямоугольник это иммутабельная сущность.
Ну, во-первых, не у меня, а у комментатора сильно выше по треду, который вводит классы:
Допустим, есть класс Прямоугольник.
У него есть метод ЗадатьРазмеры(Ширина, Высота);
И у него есть свойство Площадь.
Теперь создаю дочерний класс Квадрат.
Переопределяю метод ЗадатьРазмеры(Ширина)
А во-вторых, если бы речь шла обо мне, то предположим, что я моделирую рисовалку диаграмм, где есть фигура "прямоугольник", размер которой пользователь может менять.
Ну и
1) В такой рисовалке «прямоугольник» тоже можно сделать иммутабельным.
2) Это вообще-то не прямоугольник, а некоторая другая сущность, с прямоугольником связанная только формой.
А вы поддерживаете
Я, наоборот, спорю, если вы не обратили внимания.
В такой рисовалке «прямоугольник» тоже можно сделать иммутабельным.
Можно. А можно не делать.
Это вообще-то не прямоугольник, а некоторая другая сущность, с прямоугольником связанная только формой.
Это ничего особо не изменит в дискуссии.
Если вы в вашей предметной области называете прямоугольником не все прямоугольники, а какую-то из разновидности, это уже не будет выглядеть парадоксально: Изменяемый ВсегдаКвадрат не очевидно что должен быть разновидностью Изменяемого ВсегдаПрямоугольникаСПроизвольным соотношениемСторон. Правда тогда вы должны сделать какой-то другой прямоугольник если вы захотите найти что-то общее у всех предметов произвольной формы. Например ввести какой-то абстрактный прямоугольник.
- Здесь слово "всегда" означает что он не может перестать быт квадратом (в некоторых смолтоках можно поменять класс объекта, насколько я знаю, а в языке cecil есть предикатные подклассы)
Если вы в вашей предметной области называете прямоугольником не все прямоугольники, а какую-то из разновидности, это уже не будет выглядеть парадоксально
Вы о чем сейчас?
Я про это:
Это вообще-то не прямоугольник, а некоторая другая сущность, с прямоугольником связанная только формой.
- Это ничего особо не изменит в дискуссии *
Вся дискуссия ведётся от противоречия между свойствами математического прямоугольника, математического квадрата и того, что было выражено на языке программирования.
Если понять что на языке программирования кто-то словом "прямоугольник" и "квадрат" назвал нечто обладающее дополнительными ограничениями по сравнению с тем что мы привыкли называть прямоугольником и квадратом в обыденной жизни, то противоречие снимется.
Например, если сказать что сиреневый квадрат не является голубым прямоугольником это не породит дополнительной дискуссии.
Если у вас в рисовалке есть
изменяемые прямоугольники и квадраты, то либо класс фигуры должен меняться от изменения параметров; либо не надо требовать чтобы квадраты были прямоугольниками; либо надо обозначить что при изменении одной стороны прямоугольника не было бы ожидания, что у прямоугольника размеры сторон независимы. (Выделить Изменяемый прямоугольник отдельно и Изменяемый прямоугольник с независимыми сторонами отдельно)
Т.е. в этом как бы парадоксе вся соль в том что от прямоугольников и квадратов требуется больше чем прямоугольность и квадратность.
по сравнению с тем что мы привыкли называть прямоугольником и квадратом в обыденной жизни, то противоречие снимется.
Надо еще помнить, что в обыденной жизни разные люди квадратом и прямоугольником привыкли называть разное, как прекрасно видно в этой дискуссии.
Это как раз менее важно — как только у того, кто пишет код квадрат и прямоугольник в коде станут обладать абсолютно теми же свойствами как и квадрат и прямоугольник в голове — противоречие снимется. Главное очень четко осознавать что именно он называет квадратом а что прямоугольником.
Если человек решит, что мутабельный прямоугольник с гарантией незвисимого изменения сторон для него тождественен понятию Прямоугольник, то он как логическое существо должен не наследовать от него квадрат и в обыденной жизни считать что. Если он решит, что прямоугольник это все прямоугольное, соответственно он должен либо убрать у прямоугольника в программе все остальные требования либо переименовать прямоугольник в программе, либо считать их омонимами.
Некоторые люди умеют переключать контексты у себя в голове. И омонимами считают все слова по умолчанию. :) Более того, даже в одной кодовой базе могут быть омонимы.
Это как раз менее важно — как только у того, кто пишет код квадрат и прямоугольник в коде станут обладать абсолютно теми же свойствами как и квадрат и прямоугольник в голове — противоречие снимется.
… это если считать, что противоречие — между кодом и тем, что у него в голове. В то время как по моим наблюдениям противоречия обычно между тем, что в коде одного человека, и тем, что в голове другого.
Прямоугольник это иммутабельная сущность.
От предметной области зависит. В некоторых он так не просто мутабельный, но и мутабельный в третьем измерении. Например в производстве металлических бочек — берём листовое железо, мутируем (обрезаем) его размеры до нужных, а затем мутируем (гнём) вокруг цилиндра.
Не знаю, првда, как это согласуется с ООП — судя по комментариям, там все слишком сложно; и невозможно выполнить даже банальную операцию, так как она требует бесконечных уточнений…
Прямоугольник с заданными сторонами остается таким навсегда, это математическая абстракция.
Это если ваш домен — это математические абстракции. А это совершенно не обязательно.
Если у прямоугольника поменять стороны, то это будет уже другой прямоугольник.
В DDD это называется value object. Так можно моделировать, но это не обязательно правильно.
ООП тут, кстати, не при чем, это вопрос именно моделирования.
Кроме математической это может быть абстракция, например, языка/API устройства вывода типа принтера или дисплея, или даже человека — вы же не возмутитесь, если ваш ребёнок попросит вас нарисовать прямоугольник: "это невозможно, это математическая абстракция". А если нарисуете, а он попросит "сделай его покороче" будете говорить "это невозможно! прямоугольник иммутабельный" или возьмёте резинку и сотрёте лишнее и дорисуете одну сторону?
Но тут, на мой взгляд, и возникает противоречие — когда мы хотим выражать абстракции с любым поведением и любыми связями [т.е. без ограничения на сложность], но при этом сознательно ограничив себя в выразительных средствах — через обязательное использование наследования для построения системы типов, размещение методов для взаимодействя объектов в «единственно правильных местах» и пр.
К данной статье уже под 200 комментариев, и никак не получается прийти к общему согласию, хотя с точки зрения того же процедурного программирования вообще говорить вроде как не о чем:
захватитьГород(Измаил, Суворов)
кормить(Кошка, Корм)
рассчитатьСилуТока(Напряжение, Сопротивление)
рассчитатьНапряжение(СилаТока, Сопротивление)
…
кормить(Кошка, Корм)
А где человек? :)
вообще говорить вроде как не о чем:
Говорить там не о чем ровно до тех пор, пока вы не начинаете задавать (настолько же произвольные) вопросы вида "в каком модуле лежит захватитьГород
", "какого типа Измаил
", "как учесть потери РусскойАрмии
во время захватитьГород
".
(у меня просто в качестве n+1
-го языка — Питон, на котором можно писать процедурно, и все эти вопросы в полный рост пляшут)
Ага, и кучу ошибок при этом допустили.
Вот тут я разбирал ваши клетки для животных: https://habr.com/ru/post/477448/#comment_20931358
Да нет, вы просто не поняли суть статьи. Всё, что вы сделали — это поменяли статическую проверку типа на динамическую. Суть от этого не поменялась — аргумент подтипа как нельзя было передавать в мутирующую функцию так и осталось нельзя. А супертип как можно было так и… стало вдруг нельзя, ибо во имя LSP вы сломали контравариантность.
Да нет, вы просто не поняли суть комментария.
Я ничего не ломал, я ввёл два разных типа клеток там, где у вас почему-то используется один. И как-то так оказалось, что никакая "контравариантность" (которая на самом деле не она) тут не нужна.
Кстати, у вас там на самом деле статических проверок не было, ведь во имя ковариантности вы их отменили.
Что не так?
Мне "не так" то, что у меня есть метод Scale(rectangle, ratioX, ratioY)
, я внутри него делаю rectangle.SetSize(rectangle.x * ratioX, rectangle.y * ratioY)
, и какие-то получаемые мной снаружи объекты на эту совершенно валидную для прямоугольника операцию бросают ошибку. Это нарушение принципа подстановки Лисков.
PS Прямоугольник — не разновидность параллелепида. У них размерность разная.
Параллелепипед = это разновидность Четырёхугольника
Да? Как интересно… Мне казалось, что у параллелепипеда побольше углов ;)
Квадрат: пересечение множеств «прямоугольники» и «параллелограммы».
Ещё точнее: пересечение множеств «прямоугольники» и «ромбы» (которые суть подмножества «параллелограммов»).
В ООП это не так. Объясните почему.
Проблема идет из мутабельности, а именно из наличия метода setWidth()
или setHeight()
. В квадрате один из них меняет и другую величину, поэтому наследовать в любом порядке нельзя, так как наследника нельзя использовать так же как родителя. Если фигуры иммутабельны, то лично я не вижу особых проблем в наследовании квадрата от прямоугольника.
Решить эту проблему можно введением рантайм-типов, чего нет в современных императивных языках. То есть имеем объект типа Rectangle, но можем в нужных местах преобразовать его в объект типа Square (на тех же данных, без создания нового объекта) и использовать поведение Square (то есть вызывать методы, которые там есть). При преобразовании типов вызывается специальный метод, который делает нужные проверки, соответствуют ли данные объекта новому типу.
Мне кажется michael_vostrikov прав. для имутабельных объектов проблемы нет. Если объекты могут изменяться, то они просто имеют общий интерфейс (наличее четырёх прямых углов).
А почему ширина не более широкая? )
У стороны прямоугольника, как отрезка, нет ширины, внезапно, не правда ли?
Давайте всё-таки не слишком убегать от реального мира.
Абстракция "Прямоугольник" есть и в WinAPI, насколько я помню. А тот прямоугольник о котором вы говорите, как раз в реально мире не существует — это чистая математическая абстракция, причём только, если верить вашей ссылке на вики, в евклидовой геометрии существующая.
Нет такого определения. Насчёт длины и ширины я бы ещё согласился, длиной обычно называют бо́льший размер — но вот в паре "ширина — высота" названия сторон всегда отражают ориентацию прямоугольника.
Нет уж, мы говорим про моделирование реального мира. В реальном мире есть прямоугольник, у него есть ширина и длина. Также прямоугольник это параллелограмм, для которого определена высота. И про параллельность осям координат там ничего нет.
Прямоугольник, ромб, параллелограм — это всё единичный квадрат под разными углами зрения и в разном масштабе. А все эллипсы — это, соответсвенно, единичный круг под разными углами зрения и в разном масштабе. :)
Условимся, что метод принадлежит тому, кто воздействует:
Проблема этого "условимся" в том, что оно произвольно, и, как вам верно написали выше, именно от этой произвольности и растут холивары. А то, что оно произвольно, прекрасно видно на примере множества существующих библиотек классов, где ваша условность не действует (ну вот хотя бы, возьмем класс StringBuilder
в .net, чтобы далеко не ходить).
Я вот поразмыслил немного, и внезапно понял, что — ну, по крайней мере, в моей системе ценностей — это не просто произвольное "условимся", а неверное "условимся". Потому что — опять же, для меня — фундаментальное правило ООП состоит в том, что состояние объекта может быть изменено только через вызов метода (в оригинале — через отправку сообщения). Это объясняет, почему методы у класса StringBuilder
, а не у того, кто их вызывает. И это объясняет, почему "метод принадлежит тому, кто воздействует" — ошибочно. Метод обязательно принадлежит тому, чье состояние меняется, потому что нет никакого другого способа это состояние изменить
А вот теперь посмотрим на пример с кошкой. Должно ли в задаче измениться состояние кошки? Если да, то у нее есть метод feed
. Должно ли измениться состояние кормушки (в ней был корм, в ней есть корм, в ней стало меньше корма)? Тогда у нее есть метод fill
(используемый человеком) и consume
, используемый кошкой. Должно ли меняться состояние человека (чтобы он мог сам помнить, кормил ли он кошку, а не смотреть в кормушку и пакет корма)? Тогда у человека есть метод tryFeedTheCat
. И так далее, далее, далее.
Этот подход, конечно, не дает однозначного ответа на все возможные варианты задач. Но он упрощает рассуждение для многих из них, и у него есть мотивация, что особенно важно.
Для начла нужно описать модели кошки и человека, декомпозировать их на прмитивы а затем с помощью композиции получить кошку и человека
@Position() @Move() @Physics() @Health() @Hunger() @MasterSlave() @CatLogicScript()
class Cat extends Object {}
@Position() @Move() @Physics() @Health() @Hunger() @MasterSlave() @Feed() @HumanLogicScript() @FeedLogicScript()
class Human extends Object {}
@Position() @Physics() @Container()
class Feeder extends Object {}
const human = new Human()
const cat = new Cat()
const feeder = new Feeder()
human.addSlave(cat)
human.addFeeder([cat, feeder])
World.add(human, cat, feeder)
World.loop()
class FeedLogicScript extends Component {
update () {
if (this.object.hasComponent(MasterSlave)) {
this.object.getSlaves()
.filter(slave => slave.hasComponent(Hunger))
.filter(slave => slave.isHunger())
.forEach(starving => this.object.feed(starving))
}
}
}
Как-то так. Человек кладет корм в кормушку кота. Кот идет к той кормушке в которой есть еда.
А мне кажется что проблем в том что мы пытаемся к сложным объектам человек/кошка применить логику примитивов по типу квадрата/прямоугольника.
Неа. Это они "в реальности" сложные, причем бесконечно сложные. А для целей задачи они сложные ровно настолько, насколько нужно для задачи; то есть, в этом конкретном случае, весьма простые.
"human.addSlave(cat)"
Суля по последним трендам, следует переименовать метод. Например, secondary или follower :)
Скажите что это за язык/фреймворк, или это псевдокод?
Современные IDE умеют в рефакторинг, нет нужды сильно погружаться в абстракции, пока у абстракции только один потребитель.
Конечный код будет сильно зависеть от того, в какое направление будет развиваться конечная система. Конечная система может оказаться как симулятором существования корма, так и системой телепатического влияния на кошку заставляющей её есть корм. Если говорить про систему охватывающую все аспекты мироустройства, то там вполне может оказаться описание всех объектов на субатомном уровне, а кормление кошки — это будет здоровенная цепочка методов большого количества объектов и метода кормления кошки не будет в принципе — будет событие соответствующего сдвига нейронов в голове человека, которое можно вызвать например будильником с напоминанием.
В общем случае метод позволяющий покормить — подразумевает взаимодействие с объектами, каждый объект может взаимодействовать с другим — эти методы можно определить с родительском для всех объектов классе и вызывать в зависимости от контекста.
Так же здесь очень большой вопрос детализации процесса кормления (ТЗ плохое): по задаче достаточно cat.feed(feeder), т.к. задача говорит о существовании человека, но не говорит о необходимости его участия в процессе кормления («позволял человеку покормить кошку» — человек вызывает метод кошки, тем самым кормит её, требования физического участия в процессе здесь нет).
Выше были различные примеры кода, напишу свой вариант:
human->take(cats_food)
human->put(feeder, cats_food)
human->beckon(cat)
Соответственно метод принадлежит классу человека, т.к. задача требует описать метод позволяющий человеку выполнить действие: в привычном мироустройстве человек не может напрямую вызывать методы других объектов.
PS: Поклонники функционального программирования при описании всех аспектов мироустройства окажутся в гораздо большей ловушке, на мой взгляд.
Это фейспалм. По вашему сопротивление не зависит от силы тока и напряжения? А если проводник нагревается в результате протекания тока?
Все остальное построено на ложном предположении о том, что все участвующие объекты не могут меняться и влиять друг на друга одновременно. В реальном мире обычно именно так и бывает.
надо записать с левой стороны производную величину, т.е. ту, которая становится имеющей определённое значение, в зависимости от остальных величин.
Что делать, когда любая величина может быть производной от двух других? Например: время начала, время конца и диапазон времени.
кто воздействует?
Это называется субъект. Собственно, вы предлагаете типичный триплет: Субъект+Действие+Объект или Подлежащее+Сказуемое+Дополнение.
Игнорируется назначение ООП — решать задачи, которые НЕ решаются процедурно.
ООП появилось не взамен процедурному, не чтобы его уничтожить, а для расширения возможностей программистов — решения задач не решаемых процедурно.
Не в один день, не самые глупые программисты обнаружили, что часть задач, не решаемых процедурно, решаются общением объектов, имеющих память.
Обобщили, абстрагировали и теперь в случаях, когда разработчик не может алгоритмами решить задачу — он придумывает объекты, организовывает из взаимодействие и надеется, чтобы решение и способ решения совпадёт с расчётными, радуется, когда получилось лучше ожидаемого и переделывает свои объекты и свою модель взаимодействия объектов).
Надеется, потому что заранее неизвестно, к чему приведёт общение объектов — в коде решение не прописано, оно может лишь проявится.
Если вы описали в коде хорошее решение — хорошо — Вы хороший программист, процедурный.
Один из способов облажаться с иллюзией знания ООП:
Выбирается задача, которая запросто решается и без ООП, затем из-за неумения мыслить объектно, решается сложно, избыточно, используя ПседвоООП и делается вывод, «Не используйте ООП. Никогда. Это ошибка.»
Мы обычно хорошо мы манипулируем неодушевлёнными объектами, с неодушевлёнными сложнее.
В случае появления одушевлённых объектов в задаче, мы можем лишь подготовить условия для происхождения каких-то событий, оно они не обязательно произойдут — даже в нашем собственном коде.
В живом ООП кошка не обязана есть — неизвестны состояния объектов.
Нужно придумать и написать код, который будет нам давать знания о состоянии животного и код, который будет корректировать наше поведение в зависимости от поведения других объектов.
В задаче «человеку покормить кошку, воспользовавшись кормушкой» есть допущение, что это неизбежно произойдёт, и она «элегантно» решается алгоритмически: в мясорубку-функцию закинем кошку и кормушку. У Вас кошка не объект живой, а предмет. Кто тут с кем общается?
Пусть задача сложная, важная, нужная, но если она решается и без общения объектов, алгоритмически, решите понятным Вам способом.
Объектное мышление придёт на помощь, когда полезное, нужное процедурное мышление упрётся в свои границы.
Допустим, есть гирлянда типа BuBuBu, у неё есть кнопка, переключающая состояние, всего у неё есть 25 состояний: гореть красным, гореть зелёным и т.д…
Можно написать так:
type TGirliandaBuBuBu=class(...
var МояЛюбимаяГирляндочка:TGirliandaBuBuBu;
procedure TGirliandaBuBuBu.DoNextState;
begin
StateNum:=(StateNum+1)%25;
end;
А можно так:
type TGirliandaBuBuBu=record StateNum:byte; ... end;
PGirliandaBuBuBu=^TGirliandaBuBuBu;
var МояЛюбимаяГирляндочка:TGirliandaBuBuBu;
procedure GirliandaBuBuBu_DoNextState(p:PGirliandaBuBuBu);
begin
p.StateNum:=(p.StateNum+1)%25;
end;
И вот мы в рамках процедурного программирования вызываем метод, который меняет внутреннее состояние объекта.
Я уж не говорю про хранение в Record переменных процедурного типа…
«И мы когда-то были рысаками».
Объектное мышление, отличное от процедурного появилось не случайно, а вынужденно.
Пара случаев:
1963 СССР: «При внесении исправлений в транслятор мы столкнулись со своеобразным принципом «кибернетической неопределенности», состоящим в том, что существует такой критический предел сложности некоторой системы, за которым любая попытка исправить некоторую ошибку вносит в систему новые ошибки из-за невозможности точно учесть все последствия какого бы то ни было изменения в системе. Пока что нам удавалось не переступать этот предел, однако не раз случалось, что исправление пустяковой ошибки надолго выводило из строя АЛЬФА-транслятор».
1985 США: "Дэвид Lorge Парнас ушёл из Группы SDIO по вычислениям ..., утверждая ..., что программы требуемые стратегической оборонной инициативе не могут быть сделаны, чтобы быть надежными и, что такая система неизбежно будет ненадежна"
А потом пришёл маркетинг и начали изучать ООП процедурно.
И систему надо бить на изолированные блоки. Тогда сложность приводящая к ошибкам будет сложностью блока, а не системы в целом. Я имею ввиду блоки трудоёмкостью разработки от 0.5 до 10 человекомесяцев.
========================================
Говоря о задачах, не решаемых без помощи ООП, обычно имеют ввиду следующее: вот вы подошли к работающему ноутбуку, за котором сидела Маша. Известно, что Маша любит Петю. Напишите функцию, которая возвратит максимально много информации о действии при нажатии на кнопку F7.
На что принято отвечать, что ноутбук это ООП объект, который должен пройти все стадии с момента включения, загрузки и т.п., и только тогда возможен ответ. Чтобы объект Ноутбук хранил корректное состояние.
Я же показываю, что этой цели можно достичь и функцией, передав в неё указатель на структуру состояний объекта. Лучше ли это — другой вопрос.
Проблемы возникают, если эту программу потом потребовалось существенно модфицировать, то в этом случае сложность переделки начинает очень быстро расти с размерами чисто процедурной программы, и в этом случае лучше использовать ООП.
Если вы берете ECS то ни у человека ни у кошки не будет никаких методов по кормлению, они будут представлять из себя лишь состояние которым будут управлять различные подсистемы. PhysicsSystem HumanSystem CatSystem…
Если решили взять MVC то… пользователь сам накормит и человека и кошку :D
Во многих языках программирования наследование называется словом extend, что как бы намекает на порядок. Опять если взять пресловутые квадрат/прямоугольник у вас уйма способов сделать реализации этих объектов: наследование, композиция, декомпозиция… Попробуйте все возможные подходы, посмотрите с чем проще работать.
В университете преподаватель над нами издевался, по заданию нужно было сделать класс Matrix тремя различными способами: через наследование от вектора, через композицию с вектором и без использования вектора. Помимо этого были реализованы классы дробей, полином и т.д. И в конце года была проверка, нужно чтобы все эти классы работали друг с другом. Например нужно было создать Fraction<Polynom<Vector>> pressFToPayRespect = {...} или матрицу из полиномов, матрицу матриц и т.д. Веселые были деньки :D
В лисповской объектной системе CLOS есть множественная диспетчеризация, т.е. сразу (Кошка, Человек, Кормушка)
В случае Суворова, взявшего Измаил, действие по взятию крепости было целенаправленным.
В случае земных или космических ДТП, очевидно, целенаправленных действий нет. Могу предложить у каждого из участников ДТП вызвать метод его инвентаризации, в каком состоянии он теперь. + Можно создать объект столкновения, с методами типа Список_виновников и т.п.
знаем количество выстреленных патронов (1 или 2), знаем расстояние (сравнивая Игрок.Position и Монстр_1.Position), и внутри метода класса Шотган, рассчитываем ущерб.
Нет, шотган не знает о типе монстров. Только о количестве патронов (1 или 2) и о расстоянии до мишени. Только исходя из этого. Компьютерные игры обычно имеют нереалистично малый радиус, за которым ущерба не наносится вообще.
Впрочем, вот первое, что лично мне пришло в голову. У кого-то может быть другой человек, другая кошка и другой корм.
миска - общая переменная (глобальная, по ссылке у каждого и т.п.)
класс человек:
пакет корма
покормить кошку():
если миска пуста и в пакете остался корм, то:
пересыпать единицу корма из пакета в миску
periodic_update():
покормить кошку
если пакет пуст, то:
купить корма и добавить в пакет
класс кошка:
питаться():
если хочется есть и в миске не пусто, то:
съесть единицу корма из миски
periodic_update():
питаться
U=IR «Напряжение на концах проводника СТАНОВИТСЯ равным его сопротивлению, умноженному на силу тока через проводник» — не верно.
Вы плохо учили ТОЭ, и не знаете что такое источник тока. Дальше даже читать не стал, вы слишком безграмотны чтобы учитывать ваше мнение.
Что конкретно изменилось в Суворове от взятия крепости? Или взятьКрепость Суворова неявно правит Измаил?
В общем видно, что ООП — это не программирование, а такая теология. В ней ведутся споры на тему, чему более присуще взятие крепости — Измаилу, Суворову или туркам, или вот плюс а б, он присущ а или присущ б, или он вообще ничему не присущ, а а и б присущи плюсу? Вот какие проблемы у людей. Святой Августин потратил немало чернил на то, чтобы доказать, что Бог наиболее похож на круг, потому что круг самая совершенная из фигур. Логикой не пользовался, а просто выражал свое мнение. ООП-программисты тоже выражают свое мнение (нет ни единой логической причины предпочесть считать взятие крепости присущим полководцу, побежденным или самой крепости), а затем удивляются, откуда у них постоянные баги и рефакторинг. Оказывается, у других людей другое мнение, а единой логики нет.
Что конкретно изменилось в Суворове от взятия крепости?
Чин новый получил.
В общем видно, что ООП — это не программирование, а такая теология. В ней ведутся споры на тему, чему более присуще взятие крепости — Измаилу, Суворову или туркам, или вот плюс а б, он присущ а или присущ б, или он вообще ничему не присущ, а а и б присущи плюсу?
[...] видно, что программирование — это не программирование, а такая теология. В ней ведутся споры на тему [...]
Вы два дня примерно обсуждали, является ли квадрат прямоугольником, или же прямоугольник является квадратом, или же они абсолютно независимы друг от друга, или квадрат вообще связан с кругом.
Я? Нет.
Это не программирование, это схоластика, которая присуща только ООП.
Просто "программированию" присуща другая схоластика, вот и все.
Это не программирование, это схоластика, которая присуща только ООП. Причём это буквально так. В реальном мире не существует никакого наследования.
Это все про то, как человеку удобно думать про окружающий мир. Наследование это отражение того факта, что человеку удобно делить предметы на категории и подкатегории обладают по умолчанию свойствами надкатегорий (если все собаки хвостаты, а пудели — собаки, то пудели хвостаты).
что человеку удобно делить предметы на категории
Я бы даже сделал более сильное утверждение — это удобно и для математики. Поэтому, собственно, и существует такая вещь как теория категорий. Проблема в том, что в ООП мало что из нее используется должным образом. Категории, кстати говоря, существуют и без сабтайпинга.
если все собаки хвостаты, а пудели — собаки, то пудели хвостаты
Тут еще отмечу, что очень многое зависит от задачи. Если мы пытаемся моделировать условных пуделей и их хвосты… то — возможно да, в ООП это хорошо зайдет. А если мы работаем с данными (что в подавляющем большинстве случаев истинно), то при одинаковых ресурсозатратах ООП код будет проигрывать (по критериям: восприятие кода\рефакторинг\кол-во ошибок\легкость расширения\time to market\качество абстракий) функциональному подходу.
при одинаковых ресурсозатратах ООП код будет проигрывать (по критериям: восприятие кода\рефакторинг\кол-во ошибок\легкость расширения\time to market\качество абстракий) функциональному подходу
С этим утверждением есть та проблема, что оно не верифицируемо. Ну или не фальсифицируемо, как вам больше нравится.
И это крайне распространённое утверждение таких программистов.
Почему же.
Потому что чтобы оно было верифицируемым или фальсифицируемым, должна быть объективная воспроизводимая методика его проверки. А оценка собственной производительности всегда субъективна и, если я не ошибаюсь, очень плохо воспроизводима. Более того, как в случае собственной производительности доказать одинаковую квалификацию?
А то я же могу сделать строго обратное утверждение, сравнивая свою собственную производительность, и что дальше?
Но можно провести сравнительный анализ, результатом которого будет, скажем, табличка: критерий|ООП|ФП. То я искренне полагаю, что можно получить достаточно точную оценку.
Но опять же, я повторюсь: все зависит от задачи. Где-то выиграет ООП, где-то ФП.
PS Под ресурсозатратами я имел ввиду человеко-часы и (условный) скилл разработчика.
PSS В частности такой вывод я делал на основе того, что можно выкинуть тонну нетривиальных паттернов в пользу функций (или композиции функций), потому что они были придуманы в те времена, когда функции не были объектами первого класса (first class objects\citizens) и это порождало определенное количество проблем.
Но можно провести сравнительный анализ, результатом которого будет, скажем, табличка: критерий|ООП|ФП.
И как вы будете доказывать объективность этого анализа?
В частности такой вывод я делал на основе того, что можно выкинуть тонну нетривиальных паттернов
А я их уже выкинул, и продолжаю писать на ОО-языке. Просто с функциями.
И как вы будете доказывать объективность этого анализа?
Это крайне сложно сделать на 100% объективно.
Я не пытаюсь навязать свою точку зрения. Воспринимайте это с позиции человека, который долгое время писал (и даже сейчас продолжает писать) в парадигме ООП, но вдруг обнаружил нечто действительно крутое и воодушевлен этим.
Если Вам, вдруг, интересна эта тема — мы можем пообщаться. Я могу скинуть пару-тройку докладов, которые мне зашли. Возможно Вы захотите провести свой подобный анализ. Возможно, что кто-то другой захочет. Я думаю, что если достаточное количество человек это сделает, то в среднем, оценка мат ожидания результатов и будет близка к объективной.
А разве нельзя нарушить SRP в функции? )
Тут имелось ввиду, что у функция, в парадигме ФП, выполняет одно преобразование, которое указано в ее типе: A => B. В «крестьянских» (не чисто функциональных) языках никто не запретит делать что угодно. Но только тогда Ваш код уже будет не совсем про ФП :) Тоже самое верно и про ООП — вы можете поместить любую логику внутрь ваших методов. Паттерны проектирования — это не механизм языка, а общий подход к решению задачи. Ему можно следовать, можно не следовать.
Ну а возвращать монаду или типа того, которая при вычислении и в базу сходит, и в файлы что-то запишет — не нарушение? Если что в монадах я слабо разбираюсь
Да и с чистой функцией может быть несколько логических преобразований, например получить объект структуру/словарь юзера, "изменить" (заменить в возвращаемой копии) слабосвязанные вещи типа email адреса и баланса одновременно. Да, сама структура может нарушать принципы SRP и ISP, но и функция нарушит.
Ну а возвращать монаду или типа того, которая при вычислении и в базу сходит, и в файлы что-то запишет — не нарушение? Если что в монадах я слабо разбираюсь
Я пишу на scala, поэтому все примеры будут из этой области
Монада — это описание некоторого вычисления. Фраза «возвращать монаду» другими словами звучит как «возвращать описание вычисления». Есть так же понятие (сайд) эффекта. Ваша монада может как поддерживать различные эффекты (IO\Zio\Monix Task), так и не поддерживать (только чистые вычисления — Optional\Either).
Для приведенного Вами примера мы можем выразить нечто подобное:
def saveInvoice[F[_]: DBClient: Logging](invoice: Invoice): F[InvoiceId]
Где:
- saveInvoice — название функции
- F[_] — тайп параметр функции (читается как эф с дыркой, тип высшего порядка. Дырка означает, что конструктор этого типа принимет один аргумент. Пример: Option[T], Task[T], List[T], Array[T]). Этот параметр позволяет абстрагироваться от конкретного типа эффекта\монады
- : DBClient — показывает, что внутри функции мы описываем сайд эффект в виде обращения к базе
- : Logging — показывает, что внутри функции мы описываем сайд эффект в виде логгирования (в консоль)
- invoice: Invoice — параметр функции
- F[InvoiceId] — тип возвращаемого значения. В данном случае мы декларируем, что возвращаем описание некоторого процесса, результатом выполнения которого будет InvoiceId. При этом в процессе выполнения мы можем совершать описанные выше сайд-эффекты
Формально у функции одна задача — описать процесс сохранения некого инвойса в БД. Это важный момент, т.к. когда мы вызываем данную функцию — ничего не происходит, а только лишь возвращается описание процесса, который мы уже сможем запустить в самом конце (end of the world, обычно это 1 раз на все приложение, в конце main):
val invoice: Invoice = ???
val res = saveInvoice[IO](invoice)
// res: IO[InvoiceId]
res.unsafeRunSync(); // Запускаем процесс сохранения
Внутри могут вызываться другие функции, которые описывают процесс логирования или запрос к базе. Таким образом сохраняется и SRP, и чистота функций.
Подход, который я описал, называется Tagless Final. Есть и другиие: скажем, Free Monad. В нем структура программы описывается не как выражение (композиция функций), а как данные (деревоподобная структура).
Их (подходы) можно комбинировать как удобно, чтобы достичь лучшего результата, т.к. все, что идет в рамках ФП можно описать словом compositional, в то время как остальной код (ООП) можно описать словом composable (тут очень тонкая разница в значении, но она выливается в колоссальную разницу на практике).
Нетривиальные паттерны вы так не выкините, только тривиальные вроде фабрики или стратегии.
Пример аналогий паттернов ООП в ФП (на картинке) — вполне реальный.
Там четыре паттерна, из которых нетривиальный — только один (visitor).
Конечно, не имеется ввиду, что DI заменяет буквально одна функция.
DIP вообще ничего не заменяет, кроме DIP, вы уж извините. И в мире ФП он используется ровно так же — просто у вас в качестве зависимостей не классы, а функции и типы. Ну и что?
Пример аналогий паттернов ООП в ФП (на картинке) — вполне реальный.
Можно с таким же успехом в левой колонке написать "Objects", а в правой расписать функторы, монады и прочее. Кстати, вы нижнюю строчку на картинке читали?
Кстати, вы нижнюю строчку на картинке читали?
Конечно, конечно. И полностью согласен. Только эти паттерны нужны гораздо реже. Потому что паттерн — это обобщенное описание решения какой либо задачи\проблемы, а, скажем, монада
— это моноид в моноидальной категории эндофункторов
То есть это вполне себе математическая сущность с определенными свойствами, которые выражены и доказаны в рамках теории категорий.
Подчеркну — обобщенное описание решения (паттерн) никакого отношения к математике (теоретическим выкладкам\доказательствам) не имеет. Это просто опыт какого-то (пусть и большого) кол-ва разработчиков и итоговая реализация может отличаться от языка к языку, от проекта к проекту. А функтор — он что в хаскеле, что в скале: реализация разная, рантайм разный, а предоставляемые гарантии — одинаковы.
Активный объект — кошка ( в статусе голод проверить контейнер и при необходимости послать сигнал сервису Человек об отсутствии данных );
Сервис — человек ( получить сигнал, проверить статус контейнера, пополнить данные )
Контейнер — кормушка (методы: проверка объема данных, сохранить/выгрузить данные).
Чем больше читаю таких статей, где автор пересказывает сказки из 90-х (которые до сих пор рассказывают вузовские преподы), тем больше понимаю, что ничего более противоестественного, чем статическая типизация для ООП нет. Когда Алану Кею (позвольте первый раз в этом треде упомянуть создателя ООП) пришла в голову эта идея, он в первую очередь подумал о полиморфизме: "Благодаря математическому образованию я понял, что каждый объект может иметь несколько ассоциированных с ним алгебр, возможно, даже целые семейства, и они могут быть очень-очень полезны".
И ещё: "Одна из ключевых идей — делать системы, которые бы продолжали работать во время тестирования и, в особенности, во время внесения изменений. Даже большие изменения должны быть инкрементарными и тратить не больше секунды перед вступлением в силу". Эти мысли уже предтечи agile подходов, extreme programming и т.д. И да, буквы D из SOLID, потому что без dependency inversion это невозможно. А ешё лучше — динамическая типизация, как в SmallTalk и сделано.
Ну и ещё несколько его цитат (такое полезно повторять):
"Я придумал термин «объектно-ориентированный», и я уверяю вас, что не имел в виду C++"
"Я жалею, что придумал термин «объекты» много лет назад, потому что он заставляет людей концентрироваться на мелких идеях. По-настоящему большая идея — это сообщения" — не поленитесь, узнайте как в SmallTalk создаются и вызываются методы.
В реальной разработке это обычно очевиднее. Хотя хотя иерархию владения данными можно организовывать бесконечно разными способами, но тут дело не в ООП, а исключительно в моделировании.
Допустим, я шеф-повар, я хочу обучить новых поваров, говорю: на бутерброды надо класть масло, на него колбасу, рыбу, икру или сыр. Крем для обуви и цемент на бутерброды не кладут.
Кто-то1: — Кладут. Если бутерброд это тип сэндвич панелей для строительства жилых домов!!!
Кто-то2: — Если вы не делаете бутерброды с кремом для обуви это ваши проблемы!
Кто-то3: — Внезапно, сыр это не еда. Сыр — это доменная сущность.
Да идите… Как я перейду к обсчёту крыла для самолётов если мы спорим о том что дважды два не пять?
Моя карма уже стала -6, карма того кто доказывает что сыр доменная сущность стала +27.
Положат на бутерброд машинное масло, ну, пускай подсолнечное… Вы довольны будете? Без нормального ТЗ результат...
Вот только, в вашей аналогии, вы учите всех класть масло, потом добавлять снизу хлеб, а сверху колбасу.
Допустим, я шеф-повар, я хочу обучить новых поваров
Простите за переход на личности, но ваша проблема в том, что вы считаете, что вы — шеф-повар, но при этом допускаете ошибки, которые исправляют на этапе обучения поваренка.
Игрок_1.Оружие[Игрок_1.Номер_выбранного_оружия].Выстрел(Монстр_1)
Итак, мы видим, что благодаря ООП, мы можем легко добавить новое оружие: достаточно описать его как дочерний класс от TWeapon, определить Выстрел и разместить на карте. Класс Игрок уже умеет подбирать и добавлять в свой игровой набор объекты TWeapon. Всё
Разрабочики игр заплачут в этом месте.
Имхо при моделировании главное, понять, какой аспект реальности мы моделируем. В аэродинамических моделях продуваемых в аэжротрубе вряд ли делают маленьких пассажиров.
Модель это какая-то штука, которая в интересующем нас разрезе ведет себя как какая-то другая штука, но в чем-то удобнее.
Для меня из этого следует, что к какому классу принадлежит метод зависит от того, что мы моделируем а что отбрасываем. Например, если мы хотим смоделировать то, как Суворов берет крепости, вполне допустим и Измаил.Взять() (так как никаких других полководцев мы не рассматриваем и алгоритм взятия крепости зависит только от нее самой).
Поэтому, желательно, при рассмотрении каких-то, примеров приводить более подробные задания — что и для чего мы делаем.
При испытании лифта человека можно смоделировать мешком аналогичного веса, но другой формы, а при показе одежды манекеном похожей формы, но другого веса.
Условимся, что метод принадлежит тому, кто воздействует
Но ведь с точностью до наоборот, метод это то что можно сделать с объектом
По классике, метод — это, образно говоря, способ сообщить объекту о каком-нибудь событии, включая возникшее намерение, чтобы целевой объект что-то сделал с собой :)
Такой команды быть не должно — это нарушение инкапсуляции :)
А это ничем не отличается от любой другой ситуации. Вы называете "команду" по производимому действию, а производится оно "внутрь" объекта или "наружу" — дело совершенно неважное.
Про DOOM напомнило. Оригинальный DOOM при сохранении игры сбрасывал объекты в файл в виде POD'ов, с сырыми указателями — которые при загрузке, конечно же, в общем случае показывают куда не надо, поэтому игровой движок при загрузке просто обнулял эти указатели, т.о. монстры в сохраненной игре "засыпали" (забывали, на кого у них было аггро — игрока или другого монстра). Некоторые порты DOOM'а, кстати умеют восстанавливать эти указатели, но вот у меня такой вопрос: "а как сделать правильно"?
Однопользовательский вариант:
Монстры это элементы глобальной переменной — динамического массива. У каждого есть индекс. Значит, в каждом монстре надо писать индекс того, кого он атакует; -1 — никого, -2 — игрока.
Многопользовательский: 0 — никого, -1,-2… — игрока 0,1,..., 1,2,… — монстра 0,1,…
… а потом в одном месте забыли добавить проверку на диапазон.
(это не говоря о том, что число монстров, вообще-то, меняется, в том числе и в сторону уменьшения)
*смерть монстра с точки зрения кода наступает позже, чем с точки зрения игрока.
P.S. Кто работал с динамическими массивами, знает, что лучше увеличивать массив сразу на 30-100%, а реальное количество держать в переменной. Применительно к монстрам, я выделял бы сразу 2000 указателей. Массив не статический так как никакого фиксированного числа может не хватить — там вроде бы был монстр, генерящий какодемонов.
Число монстров на локации не так велико, обычно от 30 до 300
Вот только у вас переменная — глобальная. Поэтому не "на локации", а во всем игровом мире.
При смерти* одного и затем добавлении нового можно просто перебрать массив в поисках дырочки.
И не забыть обойти все места, которые ссылаются на эту "дырочку" и их поправить.
Кто работал с динамическими массивами, знает, что лучше увеличивать массив сразу на 30-100%, а реальное количество держать в меременной.
Кто работал с нормальными динамическими масссивами знает, что массив это прячет за абстракцией.
На локации. Игра не обсчитывает все локации одновременно. Для того они и существуют чтобы одновременно их не рассчитывать.
>> И не забыть обойти все места, которые ссылаются на эту «дырочку» и их поправить.
Это надо делать при смерти-с-точки-зрения-игрока монстра.
>> Кто работал с нормальными динамическими масссивами знает, что массив это прячет за абстракцией.
И что он прячет?
На локации. Игра не обсчитывает все локации одновременно. Для того они и существуют чтобы одновременно их не рассчитывать.
… а как же состояние монстров в локации, когда мы оттуда ушли? Оно не сохраняется? А в многопользовательской игре (это вы про нее написали, не я!) все игроки — в одной локации?
Это надо делать при смерти монстра (с точки зрения игрока).
Это надо не забыть сделать.
И что он прячет?
То, на сколько эффективно увеличивать массив при добавлении элемента. Разницу между заполненным и незаполненным массивом. Проще говоря, вам не нужна никакая переменная, чтобы держать "реальное количество".
Правильный разработчик «устанавливает алтари там, где кожа земли истончается» (De Vermis Misterius)
Допускаю что в языке на котором вы пишете — да, но не уверен что это хорошо.
Не в языке, а на платформе. И это хорошо, потому что у меня никто не отбирает ручного контроля там, где это критично, зато меня оберегают от дурацких ошибок вида "полез в незаполненную область". Managed runtime, вот это вот всё. И, что характерно, у них намного больше ресурсов (разработческих), чтобы вылизать это для общего случая. А для частного есть другие инструменты.
Допускаю, что в языке на котором вы пишете, вы и правда не можете создать даже такую простейшую абстракцию не потеряв в скорости, но не уверен что это хорошо.
Давайте вы, с опытом программирования на таком ужасном языке, не будете рассказывать нам как правильно писать программы на наших языках?
Просто надо все сериализуемые объекты перенумеровать, а указатели заменить на индексы.
Или сделать таблицу смещений (подобно тому как при загрузке exe в досе было)
Т.е. не использовать ООП. Понятно.
"а как сделать правильно"?
Ну так это, обыденная задача сериализации графа объектов. Сейчас есть куча сериализаторов, которые это умеют.
Допустим нас не интересует как кошку кормят разные люди и разница в кормушках для нас несущественна. Нам нужно знать только каким кошкам надо насыпать какой корм (в зависимости от породы, например)
Если мы смоделируем разницу в кормлении разных кошек в ооп языке внутри класса человека, то нам надо будет развозить какой-то паттерн матчинг и прочее.
Куда проще завести разные классы кошек с методом "прокормить" и описывать процесс кормления там а человека и кормушку вынести за скобки. Например человека вообще не включать в модель а кормушку использовать только для хранения ёмкости.
Куда проще завести разные классы кошек с методом
Нет, куда проще завести, например, таблицу Корма x Породы кошек, с ячейками типа Boolean, и встроить в код как константу-массив.
Лучше карту с породой кошек в качестве ключа и кормом в качестве значения.
То что вы написали после "нет" никак не отрицает ни основной мысли комментария ни процитированного текста :D.
" — собака куда меньше слона.
- Нет, муравей куда меньше слона"
— собака куда меньше слона.
Нет, муравей куда меньше слона"
Ну ок, представьте что мы ищем НАИЛУЧШИЙ вариант.
Критерий оценки такой: БЫТЬ МЕНЬШЕ СЛОНА.
Вот только в разработке очень, очень редко один критерий оценки. В частности, у кода их (обычно) как минимум два: простота написания и простота поддержки.
Тогда оба варианта наилучшие. Так как оба меньше слона. Но вы, наверное, имели ввиду "быть как можно меньше".
Для основной мысли моего высказывания, впрочем, это не особенно важно — я просил т хотел показать что Ви зависимости от постановки меняется дизайн и без понимания того что мы дизайном и с какой целью трудно нельзя сказать что само собой разумеется, что метод X должен быть в классе Y
ООП: Кто взял Измаил? Вопрос принадлежности методов объекту