Дверь.Замок.Повернуть(
обычныйКлюч,
обороты: 2,
контекстЗдания: мойДом.мояКвартира.ПолучитьКонтекст());
Встречали подобное в реальной жизни? Я тоже нет.
Именно поэтому ООП ориентировано вовсе не на объекты. По меньшей мере, не на объекты как модели окружающих нас предметов и явлений. Проблема вот в чем: сначала мы благочестиво наделяем их соответствующими свойствами — и тут же совершается акт грехопадения: на прекрасную в своей выразительной простоте модель кощунственно навешивается совершенно противоестественное поведение.
Сама архитектура ООП вынуждает нас грешить, смешивая вышеназванные разнокачественности. Героическая попытка вопреки всему отделить поведение от свойств выглядит уродливо. Появляется целая армия классов, само название которых кричит о том, что они, собственно, никакие не классы в полном смысле, а… ну да, просто чье-то поведение. Обычно это здесь_могла_быть_ваша_реклама-Presenter, -Builder, -Updater, -Visualizer и, обязательно, -Provider… Они выглядят как объекты, а на самом деле — лишь придатки к нашим (непорочным) моделям.
Я это вот к чему. Мы либо отказываемся от слова «объектно» — и заменяем его более адекватным. Тогда никаких проблем: пусть будут замки, которые сами открываются после того, как им скормлен контекст квартиры. Либо мы думаем какой должна бы стать (в будущем) архитектура ООП, чтобы она не мешала, а способствовала отделению свойств от поведения.
По ходу дискуссии — хорошая мысль по теме.
Еще умная мысль: habrahabr.ru/post/320976/#comment_10049064
Комментарии (182)
vordzer
01.02.2017 15:49+5Если представить в виде электронного замка — то идеальный рабочий пример. Замок сам открывается при сигнале, и только с контекстом строго определённой квартиры.
Saffron
01.02.2017 15:51+15Ещё один человек, не осилил понять, что объекты в ООП — это не то же самое, что объекты в реальном мире. Это абстракция, которая применима многими способами. Например, можно рассматривать сам код программы как предметную область, и искать объекты и свойства в нём. И тогда уже не удивляет паттерн visitor.
lair
01.02.2017 15:51+6Именно поэтому ООП ориентировано вовсе не на объекты. По меньшей мере, не на объекты как модели окружающих нас предметов и явлений.
Может вы просто не так трактуете эти модели?
на прекрасную в своей выразительной простоте модель кощунственно навешивается совершенно противоестественное поведение.
И что же противоестественного в поведении?
Либо мы думаем какой должна бы стать (в будущем) архитектура ООП, чтобы она не мешала, а способствовала отделению свойств от поведения.
Вообще-то объединение свойств (точнее, состояния) и поведения — это одна из определяющих особенностей ООП. А если вы состояние и поведение разделите, то вы получите… ну не знаю, ближе всего это будет к старому доброму процедурному программированию.
52hertz
01.02.2017 15:57-5Ок, можете привести ту трактовку, при которой все становится на свои места?
lair
01.02.2017 16:02+3Трактовку чего? Приведенного вами кода? Так это просто плохой код, зачем его трактовать?
52hertz
01.02.2017 16:18-3Т.е. вы тоже считаете что должно быть ключ.Открыть(мояКварита.входнаяДверь.Замок)?
lair
01.02.2017 16:19Нет, я считаю, что должно быть
дверь откройся ключом
(ну или "замок" вместо "двери", не суть).52hertz
01.02.2017 16:25-5ну и вас не смущает, что на самом деле дверь не открывается? ее открывают. я понимаю, что пример до смешного примитивный, но это к вашему вопросу о противоестественном поведении. в реальных проектах обычно идут дальше: дверь.ОткройсяКлючем(ФабрикаКлючей.Создать(typeof(БронированнаяДверь), возбуждатьСобытиеДверьОткрылась: true). Что-то в таком духе.
lair
01.02.2017 16:28+4ну и вас не смущает, что на самом деле дверь не открывается?
Конечно, нет. Потому что то, открывается дверь, или нет, зависит от двери. Какие-то отпираются, какие-то нет.
ее открывают
И кто же "открывает" дверь, которая открылась сервоприводом в ответ на поднесение RFID-метки?
в реальных проектах обычно идут дальше: дверь.ОткройсяКлючем(ФабрикаКлючей.Создать(typeof(БронированнаяДверь), возбуждатьСобытиеДверьОткрылась: true)
Вы опять приводите пример плохого кода в качестве аргументации недостатков парадигмы. Это заведомо порочный подход.
52hertz
01.02.2017 16:42-6Вообще-то, это очень сильно зависит от типа задачи. Я вам приведу массу примеров, когда ваша дверь не будет описываться тем же набором свойств, что и обычная дверь. Следовательно, это будет ДРУГАЯ МОДЕЛЬ. С другим поведением. Это мой ответ по поводу трактовки моделей. Я понимаю как соблазнительно унаследоваться от абстрактной двери и дойти так аж до ГлактическойДевриДартаВейдера, но в реальном коде вы неизбежно испохабите ваши методы, особенно входящие параметру и логику их вызова. И в итоге придете к контекстам квартир и контекстам ключей, и фабрикам, которые будут делаться фабриками. Я вас уверяю.
Zenitchik
01.02.2017 16:45+3вы неизбежно испохабите ваши методы
Был у нас такой разработчик. К счастью, ушёл.
lair
01.02.2017 16:46+2Вообще-то, это очень сильно зависит от типа задачи. Я вам приведу массу примеров, когда ваша дверь не будет описываться тем же набором свойств, что и обычная дверь. Следовательно, это будет ДРУГАЯ МОДЕЛЬ. С другим поведением.
И что? Да, модель зависит от решаемой задачи. Я выбрал какую-то одну модель, кто-то другой выберет другую модель. Это никак не влияет на тот факт, что в моей модели у объектов есть поведение, и оно не противоестественно.
И в итоге придете к контекстам квартир и контекстам ключей, и фабрикам, которые будут делаться фабриками. Я вас уверяю.
… но нет же.
ggrnd0
01.02.2017 15:52+1А контекст зачем?
ключ.Открыть(дверь: мойДом.мояКвартира.ВходнаяДверь, обороты: 2);
Если утка не крякает как утка, не плавает как утка и не выглядит как утка, то это — не утка.
52hertz
01.02.2017 16:02-5Т.е. вы всерьез считаете, что при постоянном давлении в виде дедлайнов и бла-бла можно везде писать вот такой код? Лично по моему опыту — приходится либо в ущерб стройности выдавать контексты квартир, либо не работать там, где более-менее налаженный поток проектов.
tmnhy
01.02.2017 16:24В идеале, если это проект с больше чем одним разработчиком, то должен быть свой «style guide».
И отход от него оправдывать дедлайнами совсем не стоит.
Это в идеале, но стремиться к этому надо, в том числе и менеджментом, чтоб дедлайнов не было.52hertz
01.02.2017 16:31-3Так я вот у вас и спрашиваю — архитектура ООП как-то способствует вам в этом или мешает? Вот написанию функционального кода она в общем-то препятствует, как бы подталкивает в другую сторону. Писать функционально в ООП сложно, а писать в стиле недо- или пере-объектов — легко. А теперь я ставлю вопрос: каким должно быть ООП, чтобы в нем точно также сложно было смешивать свойства/состояние с поведением.
tmnhy
01.02.2017 16:38архитектура ООП как-то способствует вам в этом или мешает?
и не способствует и не мешает.
lair
01.02.2017 16:41+2А теперь я ставлю вопрос: каким должно быть ООП, чтобы в нем точно также сложно было смешивать свойства/состояние с поведением.
Никаким. Как уже говорилось, объединение состояния и поведения — основополагающая часть ООП. Если вас это не устраивает, ООП вам никогда не подойдет.
ggrnd0
01.02.2017 16:52+1ООП — это не архитектура.
Архитектура — это например DDD
А теперь я ставлю вопрос: каким должно быть ООП, чтобы в нем точно также сложно было смешивать свойства/состояние с поведением.
DDD+SOLID+MVC:
- Состояния — в сущности.
- Поведение (модификация состояния) — в сервисы.
- Чтение/запись состояния через DAL.
DAL — не сервис, он не модифицирует состояния, только читает состояния из хранилища и пишет в него. - доступ к сервисам/DAL по средствам DI.
- отображение — во вьюхи.
- сервисы/DAL/вьюхи — stateless
52hertz
01.02.2017 16:58-2Ага. А сервисы это не классы, нет? Не объекты? Получается, объект ключ взаимодействует с объектом «СервисКлюча»? Вот тут начинается уже настоящее мракобесие, хотя я прекрасно понимаю, что это очень эффективные и доказавшие свою работоспособность подходы. Это чем-то похоже на то, как раньше бросали уголь в топку котла на пароходах.
ggrnd0
01.02.2017 17:15+3Сервис — это конечно объект.
Вот только он должен быть stateless, то есть не должен хранить состояния.
Так что хоть и объект, но если вы ему ничего не дали, он вам ничего не вернет.
в итоге приходим к такому примеру
Entity Дом Entity Квартира Entity Дверь Entity Замок Entity Ключ DAL ДверьDAL Service ДверьService
и код
bool ДверьService.Открыть(дверь, ключ) if(ключ.Открыть(дверь)) дверь.Открыта = true ДверьDAL.Сохранить(дверь) return true return false
Детали DI опустил.
В ключ.Открыть только проверяется возможность открыть дверь, никаких действий по открытию двери не производится.
На мой взгляд единственная сложность указанного подхода, в том что надо создать сервис и DAL.
Но DAL можно унаследовать от базового класса, который 1 на 100500 сущностей и стоимость его реализации стремится ко времени необходимом создать пустой класс и зарегистрировать DAL в DI-контейнере.
А код сервиса все равно придется писать, что так что эдак. И доп расходов на сервис опять же только на создание еще одного пустого класса и регистрирование DAL в DI-контейнере.
В итоге в сумме имеем накладных расходов:
- создать 2 пустых класса — 2*10 сек.
зарегистрировать 2 класса в DI — 2*10 сек.
di.Зарегистрировать(ДверьDAL) di.Зарегистрировать(ДверьService)
Ей богу, не представляю почему у некоторых с этим возникают трудности...
ggrnd0
01.02.2017 16:37+1Тут дело вообще не в ООП, а в архитектуре.
Конкретно по вашему примеру, это скорее кривая имплементация DDD.
либо не работать там, где более-менее налаженный поток проектов.
Вот не надо, налаженный поток проектов как раз дает возможность работать нормально.
asm0dey
01.02.2017 21:38У меня весь код такой, сколько бы десятков тысяч строк кода в проекте не было. Дедлайны мне ставит бизнес, я говорю сколько в эти дедлайны успею. Или задачи ставит бизнес — я говорю в какие сроки эти задачи успею.
Что бизнес ставит и сроки, и задачи — так быть не должно. А в нормальные сроки я успеваю и тесты писать, и за качеством кода следить.Neikist
02.02.2017 13:23Дедлайны мне ставит бизнес, я говорю сколько в эти дедлайны успею. Или задачи ставит бизнес — я говорю в какие сроки эти задачи успею.
Скажите кому душу продать или убить чтобы в таком месте работать? Был уверен что вездебизнес ставит и сроки, и задачи
. Буду благодарен, если поясните какой уровень для такого места нужно иметь и куда копать.
Kane
02.02.2017 15:15Там где поток проектов, это всё вообще не нужно. Написал один раз и перешел к следующему проекту. Это нужно там где есть один проект и множество инсталляций, непрерывное развитие и постоянное количество разработчиков.
staticlab
01.02.2017 16:00-1Эмм,
мояКвартира.дверь.замок.повернуть( ключ, обороты: 2 );
Хотя, например, в Джаваскрипте можно было бы сделать и так:
Замок.прототип.повернуть.вызвать( мояКвартира.дверь, ключ, { обороты: 2 } );
madnut
01.02.2017 16:03На самом деле автор затрагивает сложную тему, просто недостаточно точно её раскрывает.
Есть проповедник «чистого» объектно-ориентированного подхода, yegor256.
Посмотрите его статьи в блоге, например про то каким должен быть правильный объект или про паттерны. Или исходный код проекта, который он и его команда написали по таким правилам если обзорных статей недостаточно.
Ну и про объекты-поведения вы правильно отметили, так и хочется добавить к окончанию имени -er
Подход у Егора достаточно жесткий и радикальный, но направление мысли на мой взгляд крайне верное.52hertz
01.02.2017 16:07-2Да я сейчас и не стремлюсь ничего раскрывать. Мне нужна живая дискуссия и мнение тех, кто занимается именно программированием не ради красоты, а у кого дедлайны, сроки, тысячи строк чужого кода и тд.
ohotNik_alex
01.02.2017 16:33+3а почему вы считаете, что дедлайны не дают писать красивый код?
если вы напишете быстро какую-то малочитаемую чушь — вы потратите больше сил на ее поддержку, чем могли бы. овчинка выделки не стоит.
тысячи строк чужого кода
для тысяч строк существуют метрики. если их не ввести — компания тонет и начинает срывать те же дедлайны или неоправданно раздувает штат.
они для того и нужны чтобы десятки людей писали код так, будто бы это писал один человек. в одном стиле.52hertz
01.02.2017 16:53Это все прекрасно. И я с этим согласен. В теории так и должно быть. И разноцеветные единороги должны быть тоже. И клеверные поля. Вот только в результате вы все равно приходите к контекстамКвартир. Просто потому что это быстрее — и с поддержкой тоже проблем не будет, если все это делать «по-человечески»: без абстрактного яПровайдерКотекстовКвартир, которые внутри себя зависит от еще трех интерфейсов и 4 его наследников праздника Release не будет.
ohotNik_alex
01.02.2017 17:02не вижу повода для сарказма. я это все на практике регулярно вижу.
а какая разница что там внутри у этого провайдера? он вам отдаст контекст. и все.
или вы для каждого нового контекста будете вносить новые методы и пилить дополнительные интерфейсы? тогда проблема в недостаточной абстракции. это уже не ООП должно меняться, а стиль разработки.
если вы плодите миллионы лишних объектов — это проблема уже ваша. пишите более гибкий код.
и все-таки вы не ответили — чем deadline мешает лично вам написать красивый код? почему вы противопоставляете эти вещи?
j_wayne
01.02.2017 16:55Тоже пытаюсь применять подход yegor256
Может быть это недостаточно «Elegant Objects» ООП, но у меня получилось вот так:
Key key = pocket.key(); Lock lock = door.lock(); InsertedKey insertedKey = lock.insert(key); // throws Exception if not match insertedKey.turnClockwise(new RoundsNumber(2)); insertedKey.release(); door.handle().pull();
Дверь.Замок.Повернуть(
обычныйКлюч,
обороты: 2,
контекстЗдания: мойДом.мояКвартира.ПолучитьКонтекст());
Встречали подобное в реальной жизни? Я тоже нет.
Именно поэтому ООП ориентировано вовсе не на объекты.
Потому что, это не ООП. Повернуть(ключ, обороты, контекст здания) — это процедура. Просто оформленная в виде метода класса.52hertz
01.02.2017 17:04-1Если я завтра начну генерить такой код всерьез для своих проектов, то нам, товарищи, полная хана. Посмотрите сколько получилось ненужной ерунды. Особенно объект «RoundsNumber». Это ну просто замечательный объект, очень жизненно выглядит. И объект «InsertedKey» мне тоже нравится. В чем его принципиальное (т.е. с точки зрения набора свойств-полей) отличие?
j_wayne
01.02.2017 17:07Тогда не пойму смысла статьи) Пишите процедурно. Некоторым еще функциональный подход нравится. Рекомендую ознакомиться все же с ссылочками, что madnut выше дал.
Sirikid
01.02.2017 17:10И объект «InsertedKey» мне тоже нравится. В чем его принципиальное (т.е. с точки зрения набора свойств-полей) отличие?
В том что это отдельный тип и он не приведется неявно к типу секунд например.
Sirikid
01.02.2017 17:38Черт, не то. Ну например вставленный в замок ключ можно повернуть, а не вставленный соответственно нет.
ggrnd0
01.02.2017 17:47+1Протестую, верчу ключи как и когда хочу...
А приведенный вами пример это паттерн команда.
new КомандаПовернутьКлюч(ключ, замок, 2 оборота).Исполнить()
Vjatcheslav3345
01.02.2017 21:36ООП по армейски: new Майо?р muМайо?р.Голос_комадный.КомандаПовернутьКлюч(ключ, замок, 2 оборота, new Солдат Солдат_Петров.Исполнить())
Temirkhan
01.02.2017 16:41Все верно, до фразы «Я это вот к чему.». Мы систематизируем данные и анализируем на основе абстракции. ООП, как и Ваши примеры с замками и ключами, это тоже абстракция, как бы это странно не звучало. И то, что в реализации абстракции присутствует куча деталей, на которые абстракция не ложится, вполне нормально. Не ищите проблему там, где ее нет. Вас беспокоит качество кода, а не терминология, которой «все начиналось»…
napa3um
01.02.2017 16:44Это называется DSL, и относительно него «точечки», указывающие на объекты и свойства, — просто издержки выразительности базового языка, поверх которого построен DSL, а не «проблемы ООП».
tea.should.have.property('flavors').with.lengthOf(3)
Перестаньте искать Абсолютную Объектную Систему, она всегда будет зависеть от решаемых программистом задач [управления сложностью логики проекта].
Durimar123
01.02.2017 16:55А не стоит слово «объект» из словаря Ожегова коррелировать с типом переменной, и уйдут многие сомнения и помутнения.
SBKarr
01.02.2017 17:00+4Есть такая беда, в университетах порой учат, что ООП — единственно правоверный подход, серебряная пуля в мире программирования. А потом выпускник сталкивается с Rust. И не знает даже, откуда начинать его понимать.
Концепция работы в Rust, по моему скромному мнению, самая адекватная из ООПшных. Есть структуры данных (в том числе они же — дескрипторы объектов реального мира. Структуры можно вкладывать друг в друга для получения более сложных структур.
struct Point { x: i32, y: i32, } struct Size { width: i32, height: i32, } struct Rect { origin: Point, size: Size, }
Есть описание поведения структур, их характерные черты. (кстати, сообщество уже определилось, как переводить traits?). Их могут наследовать более глобальные описания.
trait HasArea { fn area(&self) -> i32; } impl HasArea for Rect { fn area(&self) -> i32 { self.size.width * self.size.height } }
Структуры могут иметь методы для работы с собой, но порой нам нужно работать с разнородными данными, имеющими какую-то общую черту. В таком случае мы используем trait. Вот и всё, данные отдельно, поведение отдельно. Половина проблем с разработкой иерархии классов решается сама собой.
А если смотреть глобально — случаи бывают разные. Где-то ООП с классами удобно, где-то старый добрый императивный подход, где-то функциональный, бывает, что нужен какой-то дикий гибрид.
Например, моё мнение: не нужно городить ООП поверх файловой системы. Ну, хорошо, File — это нормально. А вот функции типа mkdir, touch, stat, rename — не ложатся на парадигму никак. Поэтому люди выдумывают синглтоны, непонятные классы, FileManager и прочую ересь. Но зачем плодить лишние сущности, когда можно всё оставить, как есть (ну, может, сделать кроссплатформенные обёртки).
Очень большая боль ниже спины в ООП — различного рода оповещения. Для их реализации создаётся огород классов в 5-10. Хотя в функциональном программировании всё уже давно придумано. Почему не используем, если язык позволяет? Скорее всего, потому, что не научили…
Так что, не стоит в очередной раз разоблачать ООП, просто идите и учите другие парадигмы. Каждая из них имеет смысл и область применения.Serg046
01.02.2017 17:30+1функции типа mkdir, touch, stat, rename
Так ведь это должны быть методы класса File (ну или Directory), а не другого объекта. Какие еще синглтоны здесь?
SBKarr
01.02.2017 17:49Можете взглянуть, например, на реализацию в рантайме MacOS/iOS.
В Java это часто выглядит как ( new File(«path/to/file») ).mkdir(), что есть чудовищная аберрация. Всё-таки File чаще всего представляется как открытый файловый дескриптор, как фактический, а не абстрактный ресурс системы. А предложенные функции работают без доступа к файловой системе в пространстве пользователя. Они спроектированы так, что там не важно, существует ли такой именованый объект системы. Нам важно, чтобы он принял определённое состояние, если это возможно, или прочитать его состояние, если возможно. И они работают таким образом для того, чтобы ОС могла оптимизировать работу непосредственно с файловой системой. А ООП как парадигма заставляет нас либо создавать аберрации (а как ещё назвать фактический объект, представляющий абстрактный объект?), либо тащить ненужные операции в пространство пользователя.
P.S. Ещё примеры ужасов можно посмотреть в коде cocos2d-x и Unreal Engine.Serg046
01.02.2017 18:20Ужасов я видел тоже много, но это не значит, что нужно брать пример, или что ОПП плохое.
По поводу "абстрактности" File, то конкретно ООП никаких здесь ограничений не накладывает. И если уж на то пошло, то может быть FileDescriptor и File отдельно. Более того, так это и должно быть, если есть отличимые разные ответственности.
По поводу пользовательских/непользовательских пространств имен тоже не важно. Ну вот допустим библиотека поставляет File c ограниченными возможностями. Так кто мешает создать обертку над ним, но не в виде менеджера, а в виде "MyFile" (мысль думаю ясна) и в системе использовать уже новый тип? Так мы еще и дополнительно поможем себе, если потом поменяем/добавим в поддержку операционную систему.
SBKarr
01.02.2017 18:27Я не про пространства имён, а про выполнение. User space/kernel space. И я не говорю, что ООП плохое. Я о том, что ООП в данном случае — хреновый вариант, по сравнению с процедурным. Конкретно, для работы с ФС, а не с дескриптором. С дескриптором ООП, возможно, лучший вариант.
Serg046
01.02.2017 18:39-1Но почему? Где аргументация? Почему пучок процедур должен быть лучше, чем один/два лаконичных типа? И если так, то почему тогда синглтоны выставлены как нечто плохое. Ведь пучок процедур — это по сути тоже самое, только еще и неймспейс захламляется.
SBKarr
01.02.2017 18:52+1Лишний код, лишнее время на разработку, лишнее время на обучение. Процедурный вариант проще в написании, в понимании, лучше защищён от дураков (неправильно вызвать функцию сложнее, чем неправильно использовать объект, ибо использование объекта включает в себя возможность неправильно вызвать функцию). Объекто-ориентированный подход не может реализовать свои преимущества — нет смысла что-то наследовать, всё необходимое уже инкапсулировано ядром, полиморфизм в должной мере тоже реализован ядром. В итоге ООП даёт нам надстройку, не дающую преимуществ, но стоящую ресурсов и имеющую характерные недостатки.
Синглтон в системах с виртуальной машиной небесплатен, Да и зачем, если в языке есть неймспейсы — ничего не захламляется.
Кроме того, сравните, как лаконичнее:
filesystem::mkdir(path); Filesystem::getInstance()->mkdir();
ggrnd0
01.02.2017 19:05Кроме того, сравните, как лаконичнее:
так:
var local = FileSystem::Local(); var remote = FileSystem.remote(uri); FileSystem::copy(local.dir('source'), remote.dir('destination'), opts);
или так:
local.dir('source').copyTo(remote.dir('destination'), opts);
Лишний код, лишнее время на разработку, лишнее время на обучение.
О накладных рассходах ООП.
Конечно они есть, но на мой взгляд плюсы перевешивают минусы.SBKarr
01.02.2017 19:18-1Вы забыли посчитать расходы на обучение использованию вашего кода у других программистов. (личный опыт: в процедурных вещах мне иногда приходится лезть в детали реализации, в случае ООП — практически всегда)
Банальные численные метрики: число новых сущностей, не являющихся ключевыми словами, число языковых единиц на конечный логический блок.
Абстрактные метрики: соответствие сложности подготовки операции контексту операции (копировать файл в рамках одной платформы без сетевых взаимодействий или копировать файл в сетевой распределённой системе). Во втором случае — сойдёт. В первом случае — идёт в хлам.ggrnd0
01.02.2017 20:11Если падован знает функциональщину, а в ООП ни в зуб ногой, то да придется обучить ООП.
А сколько придется времни и нервов на SOLID+DDD+MVC потратить...
личный опыт
крайне субъективно.
Я в реализацию лезу только если что-то не работает.
А так ориентируюсь исключительно на передаваемые в метод параметры.
Абстрактные метрики: соответствие сложности подготовки операции контексту операции (копировать файл в рамках одной платформы без сетевых взаимодействий или копировать файл в сетевой распределённой системе).
Я эти операции уравниваю.
И моя абстракция упрощает копирование данных.
Так как мне не надо писать 4 разнух релизации копирования.
copyLocalToLocal copyLocalToRemote copyRemoteToLocal copyRemoteToRemote
я не знаю что за FS мне дали, и при реализации метода copy мне не придется над этим думать.
SBKarr
01.02.2017 20:45-1А вам самому не нужно обучаться использовать чужие классы, только падавану? Вы уже обладаете знанием всех возможных функций и классов, когда либо написанных в прошлом и в будущем?
Да и кто сказал, что это будут четыре реализации? Можно ведь сделать определение типа url в строке, и будет всего одна. И вообще, внутренние детали и способы повторного использования кода могут быть до неузнаваемости разные.ggrnd0
01.02.2017 21:11А вам самому не нужно обучаться использовать чужие классы, только падавану? Вы уже обладаете знанием всех возможных функций и классов, когда либо написанных в прошлом и в будущем?
Ему не нужно учиться читать, ему нужно учиться писать…
А в идеале в реализации загляждывать вообще не нужно. Что я и стараюсь делать...
Можно ведь сделать определение типа url в строке, и будет всего одна.
Подскажите, как вы организуете доступ к вируальной фс, хранящейся в БД, подключиться к которой можно только через ssh-тунель по VPN подключению?
Серьезно, нет причин инкапсулировать в один метод подключение, обработку и освобождение ресурсов.
Лучше инкапсулируй логику полного копирования дирекории с одной фс в другую с проверкой даты изменения файла.
А коннекты к ФС я и сам могу создать/освободить.
Внутри бы система сама управляла соединениями, кешами, повторным использованием, инвалидацией.
это все можно сделать на уровне реализаций FileSystem/File оставив унифицированный интерфейс доступа к ним.
SBKarr
01.02.2017 21:30-1С помощью url можно описать любой тип соединения, если постараться. Например, userspace_ssh://user:passwd@database_name.vpn_tunnel_id/path/to/file
Что инкапсулировать, а что — нет, зависит от задачи. В подавляющем большинстве случаев, чем больше — тем лучше. У вас должен быть техпроцесс, который определяет задачи каждого разработчика на каждом уровне, от этого и нужно плясать. Вопрос: как именно инкапсулировать.
Если использовать ООП — мы можем дать больше контроля, но и больше поводов для ошибок. В процедурном стиле — никакого контроля, но и возможностей ошибиться меньше. В процедурно-функционально-объектном гибриде, как я показывал ниже — контроль даётся со строгими ограничениями. С таким подходом у нас JS программист может чего-то поковырять в C++ коде, не отстрелив никому ног и не поймав SIGSEGV.ggrnd0
01.02.2017 21:45С таким подходом у нас JS программист может чего-то поковырять в C++ коде, не отстрелив никому ног и не поймав SIGSEGV.
не верю. Он может это сделать если он знаком с c++.
иначе он даже строку скопировать не сможет…
Либо уйдет в SIGSEGV, либо пол строки не скопирует.
С помощью url можно описать любой тип соединения, если постараться.
я знаю, еще и форматировать ее с использование xml можно.
Но каждый раз когда используется только url, возникает проблема, что разработчик системы что-то не предусмотрел.
Хотя есть свидетели подтверждающие, что тот старался...
Выделение же абстракции FS, позволяет делать все что угодно в прямом смысле, а не только то, что предусмотрел разработчик...
SBKarr
01.02.2017 22:17-1Просто, предусматривать должен не разработчик, это не его работа. Его работа — создать софт с заданными характеристиками.
А про с++ вы зря не верите. На нём с помощью некоторого упорства можно добиться, чтобы код с явными ошибками не компилировался. Вот здесь написано, как.
Абстракция FS в каком-то виде нужна, это очевидно, но зачем её показывать пользователю? Она нужна разработчиком на уровне ядра приложения, и нафиг не нужна прикладникам и мордоделам. Она может быть в процедурном стиле старого доброго Си.typedef struct { int (*open) (const char *url, int modes); int (*close) (int fd); // etc... } fs_struct;
Или более функционально на С++struct FileSystemApi { FsCallback<int(const string &, int)> open; FsCallback<bool(int)> close; // etc... }
Или в виде иерархии классов.
Но для тех, кто будет использовать наш код это роли не играет. Для тех, кто будет поддерживать, в сущности, тоже. Поэтому, мы вольны выбрать тот стиль, который принесёт наименьшее число проблем при наиболее эффективном решении задачи. В отношении работы с локальной ФС ООП — неэффективно. Как я сейчас вижу, и с удалённой, наверное, тоже.
nehaev
01.02.2017 21:07copyLocalToLocal
copyLocalToRemote
copyRemoteToLocal
copyRemoteToRemote
В «функциональщине» это можно абстрагировать вот так (язык Scala):
def copy(read: String => Array[Byte], write: (String, Array[Byte]) => Unit)
ggrnd0
01.02.2017 18:05+1Например, моё мнение: не нужно городить ООП поверх файловой системы.
А как вы будете работать с удаленной файловой системой?
Конечно можно смонтировать удаленную FS локально, например в /tmp/fs-random-guid/ и работать с ней.
Но тогда, придется все пути писать с исплользованием префикса /tmp/fs-random-guid/.
И не забывать об этом.
Другое дело, когда ты можешь создать объект FileSystem и указать какую FS он представляет: локальную, удаленную, inmemory или какую-то другую.
Благодаря этой же абстракции можно легко добавить поддержку сессии/транзкции с отложенной записью данных в целевую FS или с возморжностью отката.
Например, абстракцию от FS использует Lucene. Благодаря чему, может прозрачно хранить индексы как в файловой системе, так и в памяти.
SBKarr
01.02.2017 18:21-1Но зачем? Зачем воротить в юзерспейсе абстракции, которые уже есть у ядра ОС? Нормальная ОС умеет и в удалённые ФС, и memory io, и ещё кучу всего. Префикс — вообще не повод, если про него нужно помнить — это лажа архитектуры приложения.
Кстати, ООП != абстракции. Процедурные и функциональные выражения тоже абстракции, просто с другой базой для построения.
Специализированные системы — другое дело. Математикам вот порой нужны целые числа, которые не влазят в 64 бита, но они же не предлагают сделать это отраслевым стандартом.
Кроме того, пока вы не видите, проекты стремятся в первую очередь использовать API ОС, а когда нельзя — скатываются к собственным велосипедам. А указанную вами фичу умеет даже sqlite, если абстрагироваться от специфики (лично, глупый я, не вижу принципиальной разницы в способе реализации)ggrnd0
01.02.2017 18:46+1Но зачем? Зачем воротить в юзерспейсе абстракции, которые уже есть у ядра ОС? Нормальная ОС умеет и в удалённые ФС, и memory io, и ещё кучу всего.
кроссплатформенность.
Префикс — вообще не повод, если про него нужно помнить — это лажа архитектуры приложения.
Что бы не помнить и создают абстракции...
(лично, глупый я, не вижу принципиальной разницы в способе реализации)
что бы не зависить от реализации, необходим интерфейс. А это уже абстракция.
SBKarr
01.02.2017 18:57-1Похоже, предложение о том, что ООП не владеет монополией на абстракции вы пропустили. Если на то пошло, весь код — одна большая абстракция.
А для кроссплатформенности: чем тоньше прослойка — тем лучше. ООП даёт самые толстые прослойки.ggrnd0
01.02.2017 19:11Похоже, предложение о том, что ООП не владеет монополией на абстракции вы пропустили.
Как следующий код будет выглядеть без ООП?
var fileSystem = new RemoteFileSystem(uri, credentials); fileSystem.Connect(); var file = fileSystem.Open('path**/file'); file.Write(data); file.Close(); fileSystem.Disconnect();
SBKarr
01.02.2017 19:24-1Некорректный вопрос. Корректный вопрос был бы: «как выполнить задачу в другой парадигме?» И решение было бы для всей задачи, а не для конкретной функции.
ggrnd0
01.02.2017 19:35Хорошо, как записать массив байтов произвольного размера в файл на удаленном сервере с использованием функционального подхода?
nehaev
01.02.2017 19:49Вы серьезно считаете, что проблема записи в файл решена только в ООП-парадигме?
ggrnd0
01.02.2017 19:53Нет, но из не-ООП подходов к записи в файл я знаю только как это реализовано в POSIX, условно так:
write(filePtr, data); close(filePtr);
Покажите пример кода уже кто-нибудь?
Я свой написал меньше чем за минуту...SBKarr
01.02.2017 20:14-1Например,
open(url, [data] (Handle *h) { h << data; });
P.S. Кстати, все решения — мультипарадигменные. Просто одна из парадигм — ключевая в решении. Ибо во всех решениях, так или иначе, есть и процедуры, и объекты.ggrnd0
01.02.2017 20:22Весь код, пожалуйста.
SBKarr
01.02.2017 20:29В таком случае, с вас вся реализация RemoteFileSystem и сопутствующего. А этот код делает ровно то, что вы описывали ранее. Только контроль ресурсов типа close убран внутрь функции, чтобы ноги себе не отстреливали.
P.S. Мы же не будем соревноваться в написании кода, верно?SBKarr
01.02.2017 20:34Если нужно давать внешний API для системы, я, пожалуй, только такую функцию бы и оставил. Внутри бы система сама управляла соединениями, кешами, повторным использованием, инвалидацией. А разработчику уровнем ниже этого знать не надо. Для него есть одна функция, «делающая хорошо».
ggrnd0
01.02.2017 20:42Я не привык к функциональщине, так еще у вас запятая пропущена?
[data],
Я могу такую же абстракцию навесить:
open(uri, data, file => file.Write(data)) open(uri, data, handler) { var fileSystem = new RemoteFileSystem(uri, credentials); fileSystem.Connect(); var file = fileSystem.Open('path**/file'); handler(file); file.Close(); fileSystem.Disconnect(); }
И теперь вы не увидите разницу.
В таком случае, с вас вся реализация RemoteFileSystem и сопутствующего.
я изначально просил показать код метода
open
, детали обращения с удаленной FS не нужны. только способ подключения к ней и открытия файла.
Точь в точь что и у меня.SBKarr
01.02.2017 20:59-1C++ Lambda Expressions
Я имею в виду код, который мы будем поставлять дальше по производственной цепочке. Согласитесь, намного проще, если код за вас установит соединение и корректно его закроет после завершения операции. Если вы можете написать код, при использовании которого ваш коллега с меньшей вероятностью совершит глупую ошибку (забудет чего-нибудь закрыть), по аналогии с моим примером, почему не сделали этого сразу?
Получается, у меня это — финальный вариант, глубже коллегам смотреть не надо, это детали реализации. У вас другой финальный вариант, который вы же можете улучшить.
Вот, спрашивает вас коллега, как мне в системе записать в файл, вы начинаете ему рассказывать, что нужно создать объект ФС, подключить, открыть там файл, записать, закрыть файл, отключить ФС.
Я скажу — вызови функцию fs::open с твоим url и колбеком, который принимает дескриптор, и прямо в дескриптор пиши, как в поток.
В обоих случаях мы объясняем коллеге, как ему с помощью нашего кода выполнить одну и ту же задачу. Какая разница, как именно будет реализована моя функция, или ваши классы?ggrnd0
01.02.2017 21:22В этом и дело.
При наличии абстракций FS/File можно написать код, который будет одинаково хорошо копировать файлы из одной директории в другую и совершено не иметь понятия где они расположены.
Написать же универсальный метод подключения FS у вас не выйдет.
Кроме того,
Я скажу — вызови функцию fs::open с твоим url и колбеком, который принимает дескриптор, и прямо в дескриптор пиши, как в поток.
Как вы объяните ему, как сформировать url?
Вот надо ему скопировать Readme.txt с одного сервера на другой.SBKarr
01.02.2017 21:52-1А как вы объясните, как формировать uri вот здесь?
var fileSystem = new RemoteFileSystem(uri, credentials);
Такие вещи описываются проектным менеджером в техзадании и документации, ибо они ещё и для админов важны.
А про абстракции, ладно:
bool ftw(const string &url, const Callback<Handle &> &); bool open(const string &url, const Callback<Handle &> &); ftw(sourceUrl, [destUrl] (Handle &h) { string destFile = filepath::merge(destUrl, h.name()); open(destFile, [h] (Handle &destHandle) { if (destHandle.mtime() < h.mtime()) { destHandle.clear(); destHandle << h; } }); });
Приятный такой глазу декоратор, без дополнительных классов, с вполне понятным именем, общего назначения. ЧЯДНТ?
ggrnd0
01.02.2017 21:33Согласитесь, намного проще, если код за вас установит соединение и корректно его закроет после завершения операции.
Ну пример выше деревянный, вообще я пишуту так:
using(var fs = new RemoteFS(uri, credaentials)){ using(var file = fs.Open(path)){ file.Write(data); } }
и все закрывается само и корректно.
SBKarr
01.02.2017 21:57-1Но сразу вы об этом не подумали, верно?
То есть, могло получиться, что вы написали код без декораторов, забыли закрыть соединение, и организовали утечку ресурсов. Это же ключевой момент момент промышленной разработки. Если что-то можно сделать неверно — именно так оно и будет сделано.ggrnd0
02.02.2017 00:07Просто, предусматривать должен не разработчик, это не его работа. Его работа — создать софт с заданными характеристиками.
Это только если вы пишите конечное приложение, а не библиотеку.
Внесение изменений в библиотеку — большой риск, так как это может затронуть все приложения ее использующие.
А как вы объясните, как формировать uri вот здесь?
uri — это некий объект, обычно я в него и credentials инкапсулирую.
Такие вещи описываются проектным менеджером в техзадании и документации
согласен, но опять же комбинировать фичи за счет композиции декораторов, адаптеров и прочих вкусностей намного удобнее, чем прятать это все от пользователя.
в конце концов, не для детей же софт пишется...
Одно дело сделать еще удобнее, другое запретить шаг в сторону.
Может быть в мире c++ иначе и нельзя конечно, но я из мира managed-языков.
Но сразу вы об этом не подумали, верно?
Как это не подумал? Я про IDisposable никогда не забываю.
А первый вариант, для того что бы расскрыть все что делается, и не прятать за фичами яп.
Там я еще и обработки ошибок нет, не скажите же вы тчто я о них забыл?
То есть, могло получиться, что вы написали код без декораторов, забыли закрыть соединение, и организовали утечку ресурсов. Это же ключевой момент момент промышленной разработки.
Нет…
А если использовать DI, то все ресурсы будут освобождены автоматически при закрытии scope — окончании обработки запроса пользователя…
Да и про деструктор не забуду, если это столь критично.
Если что-то можно сделать неверно — именно так оно и будет сделано.
от rm -rf / вас это не спасет...
А опечатки в обработчиках куда страшнее незакрытого соединения...
SBKarr
02.02.2017 08:12-1Сам факт того, что нужно о чём-то помнить, если этого можно избежать — архитектурная ошибка системы. Из-за этого сейчас в мире у каждого второго пользователя на глазах падало мобильное приложение, у каждого десятого — серверное и настольное. Их разработчики тоже считают, что в нужный момент ничего не забудут.
Создаёте вы библиотеку или конечное приложение — не важно, у вас всегда должна быть бумажка, написанная манагером проекта, с указанием требований к функциональности. Но это всё к теме отношения не имеет.
Речь о том, что для создания определённой функциональности нет никакой разницы, будет ваш API в функциональном стиле, ООП или процедурном. Зато разница есть в количестве возможных ошибок при его использовании, сложности освоения, и соответствии этой сложности решаемой задаче.
Если вы вполне осознанно всё это посчитали и выбрали ООП — ваше право. Но большинство разработчиков не может оценить с такой стороны то, что сами написали. Как писатели не могут адекватно работать редакторами и корректорами для самих себя. Поэтому, разработчики сами по себе не работают: есть ПМ, архитектор, тимлид.
Sirikid
01.02.2017 20:23bracket (connectRemoteFileSystem uri credentials) disconnect \fs -> bracket (openFile fs path) closeFile \file -> write file data_
Можно так
ggrnd0
01.02.2017 20:31Понятное форматирование:
bracket (connectRemoteFileSystem uri credentials) disconnect \fs -> bracket (openFile fs path) closeFile \file -> write file data_
Sirikid
01.02.2017 20:35Я постарался перенести максимально точно, получилось так неидиоматично что я даже форматировать не стал
withRemoteFileSystem uri credentials \fs -> withFile fs path \file -> write file data_
Так лучше?
52hertz
01.02.2017 17:15-1Ваш ответ мне очень нравится и я в принципе с ним согласен. Наверно, я вынесу его в свой очерк. Но у меня есть одна поправка. Важная. Сейчас мир таки движется в сторону «взять самое лучше от функциональных языков» и скомбинировать с тем, что уже есть в ООП. И получить нечто гораздо более гибкое и выразительное. Именно такого ответа, как ваш, я и ждал.
SBKarr
01.02.2017 18:09Ну, за представлением о парадигмах в сообществе я не слежу. Раньше следил. Понял, что порой трендовые вещи затмевают верные решения. Прочитал про какую-то крутую фичу, вроде народ использует её там, где и тебе бы пригодилось. И, вместе с остальными леммингами, теряешь пару недель наработок, переделывая то. что не взлетело. С новыми вещами в устоявшихся проектах стоит проявлять осторожность, граничащую с консерватизмом.
Типичный пример — распространённое мнение, что процедурно писать не модно. Функционально — круто, объекто-ориентировано — норм, а процедурно — прошлый век. Я часто использую функции работы с путями к файлу в процедурном стиле, например, так:
string rootPath = filepath::root(path); string tmp = filepath::merge(rootpath, filepath::name(path) + ".tmp"); filesystem::mkdir(rootPath);
По-моему, более чем выразительно.vintage
01.02.2017 20:04Так и короче, и понятней:
let file = File.fromString( path ) file.root().resolve( `${ file.name() }.tmp` ).type( 'dir' )
ggrnd0
01.02.2017 20:17+1Нифига не понятно.
1) root() — это директория файла или
/
([leter]:\ для winfows)?
2) что делает resolve( str )? — создает абстрактный FSUnit который имеет тип (File/Directory) указанный в файловой системе и если его нет, то можно указать тип вызвав type( 'dir' )?
3) А если объекта ФС нет, будет ли он создан при вызове type() ?
ужас...
vintage
01.02.2017 20:42- Это не ко мне вопрос. Я просто транслировал код один в один.
- Он резолвит путь относительно File. На выходе получается инстанс File.
- Вызов type c новым значением и приводит к созданию реальной сущности соответствующего типа.
ggrnd0
01.02.2017 21:48+2Вы сказали, что так понятнее, на сомом же деле нет...
vintage
01.02.2017 23:03Вы хотите сказать, что "filepath::root" — понятно, а "file.root()" — нет? Или что "filepath::merge" — понятно, а "file.resolve" — нет?
Zenitchik
01.02.2017 23:25Да, именно так.
Что такое root от filepath (путь файла) — очевидно, а что за root у файла — с ходу не допрёшь.
Что такое объединение путей — понятно, а разрешение путей — это, видимо, какой-то специальный термин.
Назвать метод СОЗДАНИЯ сущности type — это вообще за гранью добра и зла.vintage
01.02.2017 23:48Что такое root от filepath (путь файла) — очевидно
Для меня не очевидно. Объясните?
Что такое объединение путей — понятно, а разрешение путей — это, видимо, какой-то специальный термин.
Это правильный термин: https://www.w3.org/TR/2012/WD-url-20120524/#resolve-a-url
Разрешение путей — это куда более хитрая операция, чем просто "слияние".
Назвать метод СОЗДАНИЯ сущности type — это вообще за гранью добра и зла.
https://ru.wikipedia.org/wiki/Fluent_interface#JavaScript
type — свойство. type( 'dir' ) говорит о нашем желании, чтобы тип у файла был 'dir'. При этом, если такая директория уже есть, то ничего сделано не будет. Если по этому пути ничего нет, то будет создана директория. А если там находится файл другого типа, то будет брошено исключение, так как поменять тип файла нельзя.
Zenitchik
02.02.2017 00:00root от filepath
Корневая директория данного пути.
При этом, если такая директория уже есть, то ничего сделано не будет. Если по этому пути ничего нет, то будет создана директория. А если там находится файл другого типа, то будет брошено исключение, так как поменять тип файла нельзя.
Вам не кажется, что это слишком замороченная логика для свойства? От свойства я обычно ожидаю отсутствия побочных эффектов.vintage
02.02.2017 09:30+1Корневая директория данного пути.
В *никсах это всегда одна и та же директория. В любом случае класть временные файлы в корень вам никто не даст.
Вам не кажется, что это слишком замороченная логика для свойства? От свойства я обычно ожидаю отсутствия побочных эффектов.
Это декларативная логика, которая всегда предпочтительнее, чем полагаться на то, что программист не забудет написать такую портянку:
if( filesystem::exists( rootPath ) ) { if( !filesystem::stat( rootPath ).isDir ) { throw new Exception( 'Already exists: ' ~ rootPath ); } } else { filesystem::mkdir( rootPath ); }
Zenitchik
02.02.2017 11:15В *никсах это всегда одна и та же директория. В любом случае класть временные файлы в корень вам никто не даст.
Это уже трудности того, кто начал эту ветку. Он хотел, чтобы это был рут — вот в коде и появилось вычисление рута.
Это декларативная логика, которая всегда предпочтительнее, чем полагаться на то, что программист не забудет написать такую портянку:
Наилучший вариант — метод, который создаёт папку или возвращает существующую. И назвать его адекватно действию.vintage
02.02.2017 11:58Наилучший вариант — программист описывает что он хочет получить, а программа сама разбирается как этого достичь или кидает исключение, если достижение цели невозможно. А вот описывать каждое действие нет никакого смысла. Я сейчас все приложения строю на реактивных свойствах, что позволяет поддерживать консистентность различных состояний, не прилагая никаких усилий.
Zenitchik
02.02.2017 12:04Про Ваши приложения я уже начитался, спасибо.
Ваше мнение о том, что декларативный подход лучше императивного — не разделяю.
LonelyDeveloper97
01.02.2017 18:03А если так?
Key key1 = new Key(key attrs); DoorCallback doorCallbback = new DoorCallback{ public void onResult(Door door){ switch(door.getState()){ case: Broken .... case: Opened ... case: Closed ... } } } door.tryToOpen(key1,doorCallback);
LonelyDeveloper97
01.02.2017 18:14А, да, tryToOpen — является частью интерфейса OpenableByKey, который является расширением общего Openable.
Ну, т.е. мы получаем, что есть объекты, которые могут быть открыты ключом, каждый класс таких объектов как-то по-своему реализует взаимодействие с ключами в зависимости от атрибутов, а потом передает коллбэк, в котором мы можем узнать состояние объекта и что-то с ним сделать…
Ведь когда я вставляю ключ в дверь, мне пофигу на механизм внутри, но я пытаюсь ее открыть этим ключом, а затем получаю результат, и анализирую что с ним сделать дальше. И мне нужны только ключ и дверь, количество оборотов обычно определено не мной, а состоянием двери в текущий момент (насколько ее в прошлый раз закрыли я могу знать, а могу и не знать)
Uint32
01.02.2017 18:18Дверь.Замок.Повернуть(
обычныйКлюч,
обороты: 2,
контекстЗдания: мойДом.мояКвартира.ПолучитьКонтекст());А зачем иметь возможность открывать одну и ту-же дверь в разных зданиях?
Вероятно, контекст квартиры как-то хранится в самой двери, не?
Дверь.Открыть(обычный ключ...)
Zenitchik
01.02.2017 18:26+3контекст квартиры как-то хранится в самой двери
Самое смешное, что его вообще не нужно хранить. В конструкции замка захардкоден закрытый ключ, который сравнивается с ключом, после чего замок позволяет или не позволяет себя повернуть. Находится замок в какой-то двери или нет — замку до лампочки.
52hertz
01.02.2017 18:33-2Я вот медленно начинаю убеждаться, что в рамках современного ООП эта проблема неразрешима. Если говорить о моем чувстве ситуации, то мне ближе всего какой-нибудь делегат, который принимает дверь в закрытом состоянии, принимает ключ, которым вы пытаетесь открыть дверь — и либо открывает ее, либо вышвыривает эксепшин, мол, эти ключем дверь не открывается. Но кто будет поставщиком таких делегатов? Опять получается какой-нибудь Provider!
Akon32
01.02.2017 18:57+1какой-нибудь делегат, который принимает дверь в закрытом состоянии, принимает ключ, которым вы пытаетесь открыть дверь — и либо открывает ее, либо вышвыривает эксепшин, мол, эти ключем дверь не открывается. Но кто будет поставщиком таких делегатов? Опять получается какой-нибудь Provider!
Вы не понимаете ООП.
ООП-объект — это модель некой сущности из реального мира или абстрактной. В зависимости от требований в момент написания программы, эта модель представляет различные аспекты моделируемой сущности. Для ООП аспекты записываются в виде полей и поведения (методов). В разных задачах может требоваться моделирование различных аспектов с помощью различных наборов полей и методов. ООП само по себе не требует фабрик фабрик, билдеров контекстов и синглтонов, это всё вытекает из требований, и часто это даже упрощения.
А поэтому, моя дверь с замком открывается так:
дверь.открыть(ключ);
Если ваша дверь открывается иначе, вы просто
неправильнопо-другому её видите.
Uint32
01.02.2017 20:21Лично я вообще не вижу проблемы, кроме не правильного изначального проектирования классов, которое и приводит к появлению контекстов квартиры и прочих химерных сущностей.
Все, что может быть инкапсулированно, должно быть инкапсулированно. (имхо)
nehaev
01.02.2017 18:43+1Смотрите на ООП не как на максимально близкий к оригиналу способ моделирования мира, а просто как на инженерную практику, позволяющую компоновать состояние и поведение таким образом, чтобы это потом было относительно удобно расширять и поддерживать.
Посмотреть на тот же SOLID, он оперирует понятиями исключительно на уровне абстракции ООП (классы, интерфейсы, наследование и т.п.) Там ни слова про то, насколько близко должна быть модель к реальности, надо ли чтобы кто-то открывал дверь, или чтобы она сама открывалась.52hertz
01.02.2017 19:01Нет, не совсем. Я тоже вижу за этим сухую инженерную практику, заточенную под решение вполне определенного круга проблем. Наравне с этим я часто задумываюсь о том, могли бы наши инструменты подталкивать нас в сторону написания более «живого» кода? Я не вижу ничего дурного в том, чтобы пытаться копировать и воспроизводить процессы, протекающие в реальном мире. Наоборот, именно это даст нам неслыханную эффективность: яркий пример нашего времени — нейронные сети.
Что же касается SOLID, то это поверхностное обманчивое впечатление. На самом деле проблема не в том, кто открывает дверь или откуда вызывается ее метод само-открывания. Проблема в том, что по ходу все это обрастает целой кучей абстракций, порожденных необходимостью заставить все это работать. И тут наверняка можно выявить вот такую СОСТАВЛЯЮЩУЮ: наши инструменты никак не предохраняют нас от смешивания поведения и состояния. Проект развивается, усложняется — и приходится прибегать вот к таким ужасам как паттерны. При всей их неоспоримой эффективности.ggrnd0
01.02.2017 19:16наши инструменты никак не предохраняют нас от смешивания поведения и состояния.
тогда уже пинайте языки программирования и компиляторы, а не ООП.
Это они вам все позволяют.
Попробуйте взглянуть на это
Там в кратце объясняется как надо делать и что из этого получится.
Самое главное, правил там очень мало, а эффект на лицо.
nehaev
01.02.2017 19:26Никто не мешает делать простые проекты без паттернов и абстракций (типа HelloWorld). Правда, за такие проекты не особо платят. Хорошо платят почему-то за сложные проекты, и первоисточником сложности является как правило предметная область или требования, а не инструменты. Инструменты и практики (ООП одна из них) как раз призваны сократить сложность задачи.
Если не нравится (не подходит) ООП — никаких проблем, берите что-нибудь другое. Например, в процедурном программировании нет проблемы состояния и поведения, потому что нет объектов.
alexeykuzmin0
06.02.2017 11:42яркий пример нашего времени — нейронные сети
Известный миф. Нейронные сети, помимо названия, не имеют с биологией ничего общего и являются лишь логичным обобщением логистической регрессии.
Cryvage
01.02.2017 19:42Дверь.Замок.Повернуть(
обычныйКлюч,
обороты: 2,
контекстЗдания: мойДом.мояКвартира.ПолучитьКонтекст());
А если так:
Вася.ОткрытьЗамок(ВасинДом.Квартиры[35].Дверь.Замок, Вася.Инвентарь.Найти("СвязкаКлючей")["КлючОтКвартиры"], 2);
где
//фрагмент класса Человек ОткрытьЗамок(замок,ключ,количествоПоворотов){ замок.ВставитьКлюч(ключ); для(поворот=0;поворот<количествоПоворотов;++поворот){ ключ.Повернуть(); } }
и
//фрагмент класса Замок ВставитьКлюч(ключ){ если((ключ != пуcтой) && (этот.КлючПодходит(ключ))){ ключ.наПоворот += этот.ОбработчикПоворотаКлюча; } } ОбработчикПоворотаКлюча(){ если(этот.ОсталосьПоворотов > 0){ этот.ОсталосьПоворотов--; } иначе { этот.Открыться(); } }
ну и так далее.
Вполне логично выходит. Всё зависит от того как код написать. Объекты это всего лишь абстракция, как вы будете их использовать, зависит только от вас. Да и от того что объекты в программе не соответствуют объектам из реального мира, они не перестают быть объектами. Если в вашей программе замок сам поворачивает ключи внутри себя, то так тому и быть. Просто это такой вид замка.ggrnd0
01.02.2017 19:50Вы забыли ключ в замке и отписаться от ключ.наПоворот
Cryvage
01.02.2017 20:03Я же написал «ну и так далее». Понятно что это только фрагмент кода, и он не закончен. Чтобы вынуть ключ из замка, Вася должен подписаться на событие замка «наОткрытие». Тогда в обработчике он вынет ключ из замка с помощью «замок.ВынутьКлюч()» и отпишется от события «наОткрытие», а замок в методе «ВынутьКлюч» отпишется от события наПоворот. Как-то так. Ещё, для простоты, я не учитывал направление поворота. Можно же и в другую сторону крутить, тем самым закрывая замок. Именно поэтому замок не должен отписываться от поворота ключа, пока тот не вынут.
AlexTest
02.02.2017 04:44Как по мне, то было бы здорово чтобы все выглядело примерно так:
Есть Некто, кто хочет получить Доступ в Помещение открыв Дверь Ключом.
По логике Дверь не может вести в несколько Помещений, т.е. если Некто откроет одну конкретную Дверь он попадет в одно конкретное Помещение, значит про Помещение вообще не стоит упоминать в этом действии. Далее на Двери может быть несколько Замков в разных состояниях открытости, но каждый конкретный замок Может быть только на одной конкретной Двери. Т.o. если Некто откроет один конкретный Замок одним конкретным Ключом, то при условии, что все остальные Замки на этой двери открыты — он откроет эту Дверь и попадет в Помещение.
Т.е. все, что нужно этому объекту Некто — это отправить сообщение Открыть объекту Ключу с параметром Замок для получения объекта Доступ. Назвать этот метод можно например получить_доступ_в_помещение. Объект Доступ будет знать про какое Помещение и через какую Дверь на основании данных из Замка.
Некто.получить_доступ_в_помещение(Ключ, Открыть, Замок)AlexTest
02.02.2017 04:50ошибся, вместо:
каждый конкретный замок Может быть только на одной конкретной Двери
следует читать:
каждый конкретный Замок может быть только на одной конкретной Двери
vsurjaninov
02.02.2017 10:37А как такой вариант? Просто исполнителем наверняка должна быть сущность, которая может выполнять какие-то действия, а не сами предметы для использования. Вместо объекта Человек может быть например Робот или ДрессированыйПёс.
человек.открыть(дверь); ... class Человек implements ДвероОткрыватель { @Override void открыть(Дверь дверь) throws Exception { if (!this.естьКлючДляДвери(дверь)) { thrown Exception("Нет ключа"); } Ключ ключ = this.достатьКлючКДвери(дверь); this.вставить(ключ, дверь.замочнаяСкважина); this.повернуть(ключ, 3); this.толкнутьВперёд(дверь); }
Chamie
03.02.2017 11:42Логично. Потому что не ключ сам поворачивается в замке и не замок сам в себе поворачивает ключ.
P.S. Мой вариант
пусто открыть(Дверь дверь) { ЗнанияОДвери знанияОДвери = this.память.искать(дверь); Список<Метод> методыОткрытияДвери = знанияОДвери.методыОткрытия.сортироватьПо(вероятностьУспеха, лёгкость); если (методыОткрытияДвери.пустой()){ вернуть Результат(неуспех, "Не знаю, как открыть эту дверь"); } если (!методыОткрытияДвери.хотьОдин(метод=>метод.испробовать(дверь).статус == успех)) { вернуть Результат(неуспех, "Не удалось открыть дверь"); } вернуть Результат(успех, Строка.пустая); }
Temirkhan
03.02.2017 12:05Что-то как-то адок… Да и к тому же, мне кажется, «пусто» подразумевает лишь «вернуть;»?
Chamie
03.02.2017 14:01Точно, косяк.
А адок потому, что псевдокод на русском, или по структуре?Temirkhan
03.02.2017 16:13Скорее всего так. У vsurjaninov неправильное разделение ответственности. У Вас, помимо этого, потеряна ветвь, на основе которой шел диалог. Вы не просто инкапсулировали куда-то ключи и замки. Вы поменяли контекст: в Вашем контексте дверь может не иметь механизмов lock/unlock вовсе.
Chamie
03.02.2017 16:57в Вашем контексте дверь может не иметь механизмов lock/unlock вовсе.
Да, я это и хотел учесть — что дверь может не требовать ключа, например, быть незапертой или не иметь замка. Или требовать чего-то кроме ключа.
maslyaev
02.02.2017 10:37Мне кажется, косяк в самой основе ООП. В глубинном принципе, который можно сформулировать примерно так: «Если реальный мир состоит из объектов, то если и наши программы будут состоять из объектов, то моделировать ими реальный мир будет надёжнее, естественнее и результативнее».
В таком (и всех подобных ему) рассуждении есть сразу два косяка:
1. Реальный мир ни из каких объектов не состоит. Он, гад такой, вообще хрен знает из чего сам по себе состоит. Мировая теоретико-физическая мысль корчится в конвульсиях, пытаясь представить себе реальность в виде струн, стремящихся минимизировать покрываемую ими по десятимерному пространству-времени площадь, но всё глубже и глубже запутывается в измышлятине и поправочных коэффициентах. О каком стопудово объективном существовании таких вещей, как камни, деревья, люди и т.п. можно говорить в таких непотребных обстоятельствах? Любое разбиение реального мира на объекты (любое!!!) — результат волюнтаризма субъекта и особенностей тех обстоятельств, в которые в данный конкретный момент его, субъекта, занесло. Чуть изменились обстоятельства, и всё, капец. У нас уже на том же материале уже другие объекты. Будем в тысяче первый раз перетряхивать объектную модель нашей ООПшной проги. Или лепить очередную систему костылей, которая нам позволит классом «Собака» описывать не только собак и кошек (это было костылём в прошлый раз), но и аквариумных рыбок.
2. «Моделирование» — зачётная тема, но её ценность слегка (на самом деле не слегка, а очень сильно) преувеличена. Можно всю жизнь протрудиться программером и накодить гигабайты кода, но ни разу не написать ничего, что можно было бы назвать моделью чего-то. Веб-сервер не моделирует выдачу веб-страниц клиентам, а выдаёт их. Браузер не моделирует прорисовку страниц, а прорисовывает их. Даже позорный сумматор не моделирует суммирование, а делает его. И кошка не моделирует охоту на мышку. Автомобиль не моделирует перевозку пассажиров и грузов. И мы сами пишем тут комменты, а не моделируем это самое написание. Моделирование — узкая нишевая задача, почти совсем не встречающаяся в реальной промышленной практике. Ну и какого хрена мы должны решающим преимуществом инструмента считать его способность решать те задачи, решение которых нам заведомо не нужно? (В скобках замечу, что даже для моделирования ООП подходит не для всякого, а только для имитации взаимодействия небольших количеств дискретных сущностей)
Как-то так получается, что реально вкусными и повсеместно полезными у нас оказываются чисто служебные классы. Такие, как String, Array, HashTable и т.п. А все многочисленные ООА и ООД оказываются весьма опасным с точки зрения проектных рисков ментальным мусором.52hertz
02.02.2017 10:41-1Ваша мысль о том, что объекты реального мира не есть объекты ООП мне видится ключевой. Одной из. Ее уже высказывали несколько человек выше. И это одна из проблем — Object Oriented пришло к нам из англоязычных работ, где само слово object на тот момент не было, как у русскоговорящих, синонимом слова «предмет». Мы бездумно приняли это название (как и много других названий — чего стоит только «теория вероятностей», хотя по сути она изучает меры неопределенности; таких примеров много в самых разных сферах науки). Поэтому я и закончил фразой — давайте переименовывать.
maslyaev
02.02.2017 14:42«Предметно-ориентированное программирование» — тоже как-то не очень. Сразу возникает вопрос типа «а что, бывает беспредметное программирование?»
По мотивам этого всего придумался недомемчик «Objection Oriented» :))
ggrnd0
02.02.2017 11:21- Реальный мир ни из каких объектов не состоит.
Согласен, нет тут объектов. Есть только суперпозиция огромного количества волновых функций...
lair
02.02.2017 11:50В глубинном принципе, который можно сформулировать примерно так: «Если реальный мир состоит из объектов, то если и наши программы будут состоять из объектов, то моделировать ими реальный мир будет надёжнее, естественнее и результативнее».
Вы ошибаетесь в этой формулировке. Правильная — как мне кажется — звучит иначе: "поскольку нам удобно использовать абстракцию объекта при описании окружающей действительности, будет удобно, если мы сможем использовать ту же абстракцию в программировании".
Моделирование — узкая нишевая задача, почти совсем не встречающаяся в реальной промышленной практике.
О нет. Каждая программа содержит ту или иную модель бизнес-области. Браузер? DOM и прочие модели сначала HTML, а потом отрисовки. Веб-сервер? Запрос-ответ (это если не считать конвеера).
maslyaev
02.02.2017 19:18поскольку нам удобно использовать абстракцию объекта при описании окружающей действительности, будет удобно, если мы сможем использовать ту же абстракцию в программировании
Абсолютно верно!
С этой точки зрения мы, конечно, обречены работать с объектами. Таков наш способ взаимодействия с реальностью: мы выделяем из неё объекты, и уже потом с ними взаимодействуем. Не важно, что мы делаем — ботинок чиним, стенку шпаклюем или ваяем веб-морду. И в этом плане нет ничего странного в том, чтобы наши программы состояли из объектов.
Но возникает целый ряд «но». Хорошо, объекты. Но почему они должны в себе инкапсулировать данные и поведение? Возьмём, например, адресную книгу (объект), содержащую в себе из записи (тоже объекты). Запись адресной книги — это просто структурированный кусок бинарных данных. Какое собственное поведение должно быть у куска бинарных данных? Программист морщит лоб и начинает ваять конструктор и геттеры-сеттеры. Хоть такое, высосанное из пальца, но всё же поведение. Ну не глупость?
В базах данных, на мой взгляд, более здравый подход: адресная книга — это таблица, а что и как с ней делать (то есть вся прикладная логика) вынесено на усмотрение тех, кто её юзает. Надо по имени найми мэйл — пожалуйста. Надо по мэйлу узнать имя — тоже нет проблем. SQL в руки. Встроить какую-то логику в триггеры, конечно, бывает полезно, но о том, чтобы всё, что может происходить с адресной книгой, объявить её поведением и попытаться инкапсулировать в этот объект — это никому даже в страшном сне в голову не придёт.
Каждая программа содержит ту или иную модель бизнес-области.
Нет. Она строится с учётом разнообразных моделей предметной области, но чтобы они в программу закладывались — про такое я не слышал. Да и в каком виде их туда лучше закладывать? В Визио или лучше в PNG? :))
А если серьёзно, то в молоток не закладывается модель забивания гвоздей, в автомобиль не закладывается модель пассажира. Почему в программу должна закладываться какая-то там модель? То есть сущности, свойства которых в достаточной мере соответствует свойствам элементов моделируемой системы?
Между тем, чтобы «являться моделью» и «быть сконструированным с учётом результатов моделирования» — большая логическая разница. Нужно просто перестать путаться.
Мне кажется, лучше говорить не о моделях, а об инструментах и метафорах. Адресная книга у нас будет не моделью блокнотика, а инструментом накопления фактов о корреспондентах. А текстовый редактор пусть не будет моделью листа бумаги, но метафора «лист бумаги» пусть будет идейной основой визуального представления.lair
02.02.2017 21:56Хорошо, объекты. Но почему они должны в себе инкапсулировать данные и поведение?
Давайте начнем с простого вопроса: зачем вообще нужна инкапсуляция? Инкапсуляция нужна для того, чтобы снизить воспринимаемую сложность модуля кода, скрыть от пользователя (которым выступает программист) ту информацию, которая ему не нужна и избыточна. А дальше вам нужен механизм, который позволит сгруппировать скрытую информацию, и провести границу — вот этот код эту информацию видит, а этот — нет. Объект — это одна из подобных возможных границ, вполне логичная внутренне.
Программист морщит лоб и начинает ваять конструктор и геттеры-сеттеры. Хоть такое, высосанное из пальца, но всё же поведение. Ну не глупость?
Глупость — это ваять конструкторы и геттеры-сеттеры только потому, что поведение "должно быть" — вместо того, чтобы взять поведение, которое есть, и использовать его.
Возьмем в качестве примера простой заказ: позиция, количество, общая сумма. Общая сумма — это вычислимое поле, оно считается как "цена позиции * количество". Это правило — это как раз поведение. Что хуже, вы не можете реализовать это поведение "снаружи" заказа, потому что для этой реализации вам нужно будет иметь возможность записывать в поле "общая сумма" у заказа — а это значит, что кто-то другой может записать туда сумму, которая не удовлетворяет этому правилу, и тем самым нарушит целостность. Вот вам и поведение, которое логично принадлежит сущности "заказ".
Более того, благодаря инкапсуляции, в тот момент, когда у нас появятся оптовые скидки для заказов, где количество превышает 100, мы просто модифицируем это поведение в сущности "заказ" — и никто из потребителей не будет потревожен.
В базах данных, на мой взгляд, более здравый подход: адресная книга — это таблица, а что и как с ней делать (то есть вся прикладная логика) вынесено на усмотрение тех, кто её юзает.
Это плохо масштабируется: один программист никогда не может быть уверен, что другой программист не записал в таблицу данных, которые не удовлетворяют бизнес-правилам.
Нет. Она строится с учётом разнообразных моделей предметной области, но чтобы они в программу закладывались — про такое я не слышал.
Вы не "не слышали", вы не задумывались.
Да и в каком виде их туда лучше закладывать? В Визио или лучше в PNG?
В том виде, в котором пишется программа.
Почему в программу должна закладываться какая-то там модель?
Она не "должна закладываться", она там есть. Когда вы создаете в БД таблицу "АдреснаяКнига" с колонками — вы получаете явную физическую модель предметной области (в виде таблицы с таким-то набором колонок и такими-то правилами). Когда я пишу в коде
record OrderLine {ItemId: string, Quantity: decimal, Total: decimal}
— я тоже создаю модель предметной области, только выраженную кодом (и, в зависимости от парадигмы, она может быть объектной или не объектной).
А дальше речь идет только о том, насколько близки или далеки друг от друга различные модели одной и той же предметной области, используемые в одном проекте. Чем они дальше, тем больше ресурсов уходит на трансляцию.
Адресная книга у нас будет не моделью блокнотика, а инструментом накопления фактов о корреспондентах.
… при этом она все еще будет содержать в себе модель предметной области "корреспонденты" в виде, предположительно, таблицы — ну или документо-ориентированной БД, если вам так захотелось, или чего-то еще.
maslyaev
03.02.2017 18:00… снизить воспринимаемую сложность модуля кода...
Для этого у нас есть функции. Здесь же мы берёмся снижать воспринимаемую сложность пары «код + данные, с которыми он работает». Выбранный способ (строительство заборчика вокруг данных), ИМХО, не самый лучший. Единственное, на мой взгляд, преимущество этого способа в том, что он является первым, что приходит в голову.
… и провести границу...
Ага. Построить заборчик. Потом другой. Возвести заборостроение в базовый принцип, и после этого удивляться, что ландшафт жёстко фрагментировался и превратился в нагромождение какой-то немыслимой хреновни.
Заборы строить безусловно нужно. Иметь системы, построенные по принципу «гуляй, ветер», не хочется никому. Мотивов тому может быть масса, начиная от банального разграничения сфер ответственности команд разработчиков и до локализации катастроф. Но, как всегда бывает в таких случаях, к вопросам нужно подходить с умом, а не тупо следовать моде.
Возьмем в качестве примера простой заказ: позиция, количество, общая сумма. Общая сумма — это вычислимое поле, оно считается как «цена позиции * количество». Это правило — это как раз поведение.
Хороший пример, но, к сожалению жизнь бывает слегка сложнее. Начать хотя бы с того, что цена и количество — это не «просто циферки». Цена в валюте, а количество в единицах измерения. Помимо двух циферок есть ещё договор клиента, в котором указана валюта взаиморасчётов, есть прайс, в котором указана рекомендуемая цена товара, но, как на зло, не по той единице, которая в заказе, а по другой, и между ними таблица пересчёта (это литры в кубометры легко пересчитываются, а кубометры в тонны уже не очень). Да, и ещё цена в прайсе не в той валюте, которая в договоре. Сумму-то мы посчитаем простым умножением, но это всего 1% нашей задачи, если не меньше. А всё остальное пускает свои щупальца по всей системе. И ладно бы если бы это можно было решить APIшками классов, так ведь ещё возникает всякое гадство типа «быстро одной кнопкой получить список имеющихся в наличии товарных позиций с ценами, пересчитанными в выдранную валюту по курсу на указанную дату». SQLю это как два пальца. Юзер моргнуть не успеет. А как оно и, главное, сколько времени будет продираться через интерфейсы объектной модели?
Когда я пишу в коде record OrderLine {ItemId: string, Quantity: decimal, Total: decimal} — я тоже создаю модель предметной области, только выраженную кодом
Вы не модель создаёте, а работающий механизм. В корне неправильно называть моделированием факт пригодности этого механизма к использованию в той предметной области, для которой он предназначен. Не, ну вдумайтесь. Разве автомобиль содержит в себе модель пассажира, груза и дороги? Разве в автомобиле есть какая-то деталька, которая там служит моделью дороги? Только, умоляю, не говорите «колесо». Колесо — круглое, а дорога плоская.lair
03.02.2017 18:08-1Для этого у нас есть функции.
… которых в какой-то момент перестает хватать.
Выбранный способ (строительство заборчика вокруг данных), ИМХО, не самый лучший.
Предложите лучше.
Хороший пример, но, к сожалению жизнь бывает слегка сложнее.
Ну так и пример масштабируется вместе с усложнением жизни.
Начать хотя бы с того, что цена и количество — это не «просто циферки». Цена в валюте, а количество в единицах измерения
Это называется value type, и давно отработано.
SQLю это как два пальца.
… но нет, на самом деле. Где-то начиная с "на заданную дату".
А как оно и, главное, сколько времени будет продираться через интерфейсы объектной модели?
"Как" — легко как раз, вся сложность пересчетов прячется внутрь каждого конкретного value type. "Сколько"… ну да, скорость работы обычно противоречит остальным критериям качества системы. Так что тут приходится балансировать. Но при разумном проектировании — "достаточно быстро".
Вы не модель создаёте, а работающий механизм.
А почему одно противоречит другому? Почему работающий механизм не может содержать в себе модель?
Понимаете, структура БД — это модель предметной области. "Под моделью понимается некоторый материальный или мысленно представляемый объект (образ объекта), который в процессе изучения замещает объект-оригинал, сохраняя некоторые важные для данного исследования типичные его черты." вики.
В корне неправильно называть моделированием факт пригодности этого механизма к использованию в той предметной области, для которой он предназначен.
Я и не называю.
maslyaev
03.02.2017 21:41-2Почему работающий механизм не может содержать в себе модель?
Потому что она там не нужна.
Примерно потому же, почему в конструкции автомобиля нигде не содержится моделька дороги с игрушечными мостиками и светофорчиками.
Мы все находимся в плену, как я её называю, «отражательной» парадигмы понимания понимания (пардон за тавтологию). Типа реальный мир находит своё отражение в представлениях познающего субъекта. Что должно представлять собой это отражение? Правильно, модель. Чрезвычайно убогая парадигма. Начать хотя бы с того, что у нас в мозгах нет ни одного зеркала, чтобы отражать, и закончить хотя бы тем, что моделирование подразумевает не только моделируемое явление, но и того гаврика, который этим моделированием «в процессе изучения замещает объект-оригинал». Короче, прямиком выходим на теорию внутримозгового гомункла. Полная ерунда. Бессмысленная и беспощадная логическая петля. Если мне не изменяет память, эта тухлятина была обильно полита грязью ещё аж в знаменитом сентябрьском 79-го года номере SciAm.
В общем, пусть моделирование останется там, где ему самое место. Как метод исследования. А мы, технари, будем к нему прибегать только тогда, когда нам нужно именно моделирование. Например, для проведения нагрузочных испытаний будем делать роботов, которые действительно будут моделировать пользовательскую активность. Кстати, моделирование бизнес-процессов — тоже зачётная тема. Очень помогает.
Предложите лучше.
Декомпозиция программных продуктов — весьма популярная и живая тема. Например клиент-серверные архитектуры — про это. SOA — тоже про это. На ОО свет белый клином не сошёлся. ОО тоже бывает полезно, но только если без фанатизма.
Ну так и пример масштабируется вместе с усложнением жизни.
Плохо масштабируется. Необходимость постоянной перетряски иерархий объектов по мере уточнения задачи — родовая травма ОО.
Да ладно… Я сам сто раз так делал. Нормально прокатывает.SQLю это как два пальца.
… но нет, на самом деле. Где-то начиная с «на заданную дату».lair
03.02.2017 21:50-2Потому что она там не нужна.
Но она там есть. Просто в силу определения, приведенного выше.
Если это определение вас не устраивает — значит, у вас другое понятие слова "модель", и, значит, в вашем понимании программа не содержит модели, а ООП ничего не моделирует… что означает, что ваша претензия к ООП, основанная на том, что ООП что-то моделирует, все равно беспочвенна.
Например клиент-серверные архитектуры — про это.
… которые инкапсулируют в себе данные и поведение.
SOA — тоже про это.
… которая инкапсулирует в себе данные и поведение.
Ну то есть оба приведенных вами примера используют то же самое, чего они призваны быть "лучше". Не вышло.
Необходимость постоянной перетряски иерархий объектов по мере уточнения задачи — родовая травма ОО.
А напомните мне, какая парадигма разработки не требует доработок по мере уточнения задачи?
Да ладно… Я сам сто раз так делал. Нормально прокатывает.
Да понятно, что прокатывает. Вопрос сложности.
maslyaev
06.02.2017 12:20Но она там есть. Просто в силу определения, приведенного выше.
Внимательно читаем определение:
«Под моделью понимается некоторый материальный или мысленно представляемый объект (образ объекта), который в процессе изучения замещает объект-оригинал, сохраняя некоторые важные для данного исследования типичные его черты».
Долго и упорно гипнотизируем слово «изучение». В нём — ключ к пониманию происходящего. Если в программе нужно иметь некий механизм, который другой части программы будет давать прогноз относительно того, как будут складываться обстоятельства, то этот механизм безусловно будет моделью. Насколько часто бывает востребована такая конструкция? Ну, это смотря в какой предметной области работаем. Если программа составляет метеопрогноз, то можно предположить, что тема моделирование атмосферных процессов в ней будет раскрыта основательно. Но если программа является адресной книжкой, то ценность моделирования в ней весьма сомнительна.
Очень странно инкапсулируют. Данные инкапсулируют на сервер, а поведение — на клиент :))Например клиент-серверные архитектуры — про это.
… которые инкапсулируют в себе данные и поведение.
Для примера посмотрим на сервис электронного архива. Data warehouse для, грубо говоря, бинарников заранее не определённого назначения. Нормальный такой объект, да? При этом, что забавно, этот самый сервис совсем не обязательно должен быть написан с применением ООП. В конце концов, его можно написать на чистом C без единого плюса.SOA — тоже про это.
… которая инкапсулирует в себе данные и поведение.
Не знаю кому как, но лично мне бывает не удобно работать с ОО-реализацией SOA (SOAP). Слишком много лишней дури наворочено. Перекидываться по-простянке XMLями или JSONами через банальный HTTPRequest и удобнее, и практичнее.
А напомните мне, какая парадигма разработки не требует доработок по мере уточнения задачи?
Все требуют. Такова жизнь. Вопрос здесь в уровне отчаяния, с которым встречается каждое уточнение задачи. По своему опыту скажу, что проще всего получается выкручиваться, когда ООП применено в очень куцем варианте. Например, когда количество уровней иерархии ограничено двумя (есть набор базовых классов, на основе которых лепим потомков, но налепить потомка потомка — ни-ни). Дурацкое ограничение, которое поначалу дико бесит, но потом, когда случается уточнение постановки, начинаешь понимать, как сказочно повезло.
Да понятно, что прокатывает. Вопрос сложности.
5 минут на написание запроса — не ахти какая сложность ;)lair
06.02.2017 12:27Долго и упорно гипнотизируем слово «изучение». В нём — ключ к пониманию происходящего.
Это если вы считаете, что "изучение" обязано происходить во время работы программы. Я не считаю.
Данные инкапсулируют на сервер, а поведение — на клиент :))
Вообще-то в клиент-серверной архитектуре и данные, и поведение могут быть на сервере. См. "тонкий клиент".
Для примера посмотрим на сервис электронного архива. Data warehouse для, грубо говоря, бинарников заранее не определённого назначения.
А SOA ли это?
При этом, что забавно, этот самый сервис совсем не обязательно должен быть написан с применением ООП. [...] Не знаю кому как, но лично мне бывает не удобно работать с ОО-реализацией SOA (SOAP).
А вот тут вы начинаете путать реализацию и концепцию.
Во-первых, я говорил о том, что SOA инкапсулирует данные и поведение, я не говорил, что это обязательно ООП. Но...
Во-вторых, адекватная реализация SOA — это все-таки ООП, причем ООП в его "первоначальном понимании" — именно потому, что каждый сервис инкапсулирует свои данные и поведение. Другое дело, что это ООП "на большом уровне", где объектом является каждый сервис (все потому же). Внутри программы может никакого ООП не быть, это не имеет значения на уровне выше.
По своему опыту скажу, что проще всего получается выкручиваться, когда ООП применено в очень куцем варианте.
Так может это в вашем опыте так было, а у других опыт другой?
Например, когда количество уровней иерархии ограничено двумя
Я сейчас, может, что-то странное скажу, но можно иметь ООП вообще без наследования.
5 минут на написание запроса — не ахти какая сложность
Они, знаете ли, складываются.
Zenitchik
06.02.2017 12:37можно иметь ООП вообще без наследования.
Я знакомство с ООП c VB начинал ))) Долго не мог врубиться, что такое «наследование» — в пособиях по VB того времени наследованием называли что-то странное и непонятное, никак не относящееся к наследованию в общепринятом понимании )))
Однако, да, отсутствие наследования не то чтобы сильно мешает. Замечательно выезжал на композиции.
maslyaev
06.02.2017 12:47Это если вы считаете, что «изучение» обязано происходить во время работы программы. Я не считаю.
Вообще, про моделирование это на редкость сложная и глубокая тема. Предлагаю для ясности остаться каждый при своей позиции.
Вообще-то в клиент-серверной архитектуре и данные, и поведение могут быть на сервере. См. «тонкий клиент».
Случаи разные бывают. 3-х уровневый К/С тоже никто не отменял.
А SOA ли это?
Обязательно. Почему нет? На микросервисную архитектуру оно, конечно, мало похоже, но SOA в чистом виде.
сервис инкапсулирует свои данные и поведение
Опять же случаи разные бывают. Сервис может, например, просто шлюзовать инфопоток, вообще не вникая, что за беда через него струится.
это ООП «на большом уровне», где объектом является каждый сервис
Вот в этом-то и дело, что на таком большом уровне мы приходим к тому, с чего начали рассуждение, а именно что объект — это просто некий кусок системы, который объектом-то назван только потому, что нам нужно им оперировать как единой целой сущностью.
что-то странное скажу, но можно иметь ООП вообще без наследования
В обморок не упал. Оно, конечно, против классики, но когда дело доходит до дела, то это бывает вполне рабочий вариант.lair
06.02.2017 12:503-х уровневый К/С тоже никто не отменял.
В этом случае с точки зрения клиента апп-сервер инкапсулирует данные и поведение.
Обязательно. Почему нет? На микросервисную архитектуру оно, конечно, мало похоже, но SOA в чистом виде.
Правильный вопрос "а почему да". Наличие какого-то сервиса еще не означает, что у вас SOA.
Сервис может, например, просто шлюзовать инфопоток, вообще не вникая, что за беда через него струится.
Может. Но (если это SOA) для внешнего пользователя он либо делает вид, что он — сервис получатель (и это снова инкапсуляция), либо, наоборот, внешний получатель вообще не в курсе его существования (если это часть шины).
Вот в этом-то и дело, что на таком большом уровне мы приходим к тому, с чего начали рассуждение, а именно что объект — это просто некий кусок системы, который объектом-то назван только потому, что нам нужно им оперировать как единой целой сущностью.
… и у этой сущности есть и данные, и поведение.
Оно, конечно, против классики
Это зависит от того, что вы классикой считаете.
maslyaev
06.02.2017 13:04Это зависит от того, что вы классикой считаете
Три источника и три составные части ООПизма: инкапсуляция, наследование, полиморфизм.
Правильный вопрос «а почему да». Наличие какого-то сервиса еще не означает, что у вас SOA.
Дело же совсем не в том, что какой-то сервис слепили, а в том, что существенный кусок функциональности вынесли в отдельную подсистему, снабдили удобными интерфейсами и сделали сквозной функциональностью для самых разных систем и нужд.lair
06.02.2017 13:20Три источника и три составные части ООПизма: инкапсуляция, наследование, полиморфизм.
О нет. Это всего лишь три атрибута, которые стали ассоциироваться с ООП позже.
Дело же совсем не в том, что какой-то сервис слепили, а в том, что существенный кусок функциональности вынесли в отдельную подсистему, снабдили удобными интерфейсами и сделали сквозной функциональностью для самых разных систем и нужд.
Функциональности. Здравствуй, инкапсуляция поведения.
maslyaev
06.02.2017 13:45Это всего лишь три атрибута, которые стали ассоциироваться с ООП позже.
Именно в тот момент, когда данные (родимые наши нолики и единички) решили снабдить собственным поведением. То есть когда подумалось, что неплохо было бы, чтобы то ли замок научился в себе проворачивать ключи, то ли ключи научились проворачивать себя в замках.
Здравствуй, инкапсуляция поведения.
Конечно. Но вопрос в масштабе. Когда за интерфейсом, скрытым за доступным по некоему адресу кроется отказоустойчивый кластер с полноценной приложухой, то это уже не совсем то, что принято в ООП называть объектом.lair
06.02.2017 13:50Именно в тот момент, когда данные (родимые наши нолики и единички) решили снабдить собственным поведением.
Неа. У Кея не было никакого наследования.
Когда за интерфейсом, скрытым за доступным по некоему адресу кроется отказоустойчивый кластер с полноценной приложухой, то это уже не совсем то, что принято в ООП называть объектом.
Опять-таки, у кого принято?
Но речь, впрочем, не об этом, а том, противоестественно ли объединять данные и поведение. Как видим, не противоестественно.
maslyaev
06.02.2017 15:47Но речь, впрочем, не об этом, а том, противоестественно ли объединять данные и поведение. Как видим, не противоестественно.
Похоже на то, что Вы прикалываетесь. Ну конечно, объединять данные и поведение никоим образом не противоестественно. Чёрт возьми, они, как ни крути, сливаются в экстазе в любом вычислительном процессе, что бы этот процесс собой ни представлял. Хоть ООП, хоть что угодно другое, включая «as is» машину Тьюринга или даже машину Бэббиджа.
ООП — это всё же, насколько я понимаю, особенное объединение данных и алгоритмов. Такое объединение, когда вместе с данными болтаются ссылки на методы, манипулирующие этими данными. То есть кроме бинарных данных замка есть ещё ссылка на метод «Провернуть».lair
06.02.2017 15:52+1Эмм, тогда я перестал понимать, что вы понимаете под "объединением". Вот в ООП все понятно: есть данные (состояние), есть набор операций над этим состоянием, в идеальном случае к состоянию вообще нет доступа кроме как через эти операции. Объединение и инкапсуляция. То же самое в клиент-серверной архитектуре, то же самое в сервисной архитектуре.
А вот в "любом" вычислительном процессе — не объединение. Операция "сложение" никак не объединена со своими операндами (не считая общего типа).
maslyaev
06.02.2017 17:13я перестал понимать, что вы понимаете под «объединением»
Когда мы на ассемблере пишем
ADD AX BX
мы разве не объединяем в единое целое данные (AXи BX) и операцию ADD? Объединяем, и даже понятно, как это объединение материализуется в аппаратуре сумматора.
Данные — это ведь просто нолики и единички. +3.3V / 0V. Есть намагниченность / нет намагниченности. Смысл свой они получают в контексте алгоритма. В любом случае. Можно, конечно, это назвать инкапсуляцией, но только зачем? Инкапсуляция получается тогда, когда кроме самих данных до кучи есть ещё и адрес исполняемого кода, на который нужно передать исполнение для выполнения операции. Логика становится частью данных, и за счёт этого мы получаем дополнительную гибкость. Ту самую, которая называется «полиморфизм». Но за такую гибкость приходится расплачиваться тем, что у нас появляется понятие абстрактного метода, и, как следствие, необходимость нагородить иерархию. А любая иерархия более двух уровней неизбежно содержит внутри себя логическую ошибку. Если хотите, могу рассказать, какую. Выйти за пределы 2-х уровней и нагородить хотя бы третий — очень соблазнительно, и отсюда масса весьма гадских проблем.
А К/С и в SOA напрямую не предполагают описанный выше вариант инкапсуляции, поэтому к ОО их можно отнести только с ооооочень большой натяжкой.lair
06.02.2017 17:31Когда мы на ассемблере пишем
ADD AX BX
мы разве не объединяем в единое целое данные (AXи BX) и операцию ADD?Нет.
Инкапсуляция получается тогда, когда кроме самих данных до кучи есть ещё и адрес исполняемого кода, на который нужно передать исполнение для выполнения операции.
Тоже нет. Инкапсуляция получается тогда, когда внешний пользователь не имеет доступа к ненужным ему данным/функциональности.
Логика становится частью данных, и за счёт этого мы получаем дополнительную гибкость. Ту самую, которая называется «полиморфизм».
Нет, полиморфизм не вытекает из инкапсуляции.
Но за такую гибкость приходится расплачиваться тем, что у нас появляется понятие абстрактного метода, и, как следствие, необходимость нагородить иерархию.
И нет, для полиморфизма не нужны ни абстрактные методы, ни иерархии.
А любая иерархия более двух уровней неизбежно содержит внутри себя логическую ошибку. Если хотите, могу рассказать, какую.
Хочу.
А К/С и в SOA напрямую не предполагают описанный выше вариант инкапсуляции, поэтому к ОО их можно отнести только с ооооочень большой натяжкой.
Вот только "описанный выше вариант инкапсуляции" — это не то, что понимается под инкапсуляцией в ООП.
maslyaev
06.02.2017 18:47Инкапсуляция получается тогда, когда внешний пользователь не имеет доступа к ненужным ему данным/функциональности.
Тётя Вика здесь говорит нам следующее:
Инкапсуляция (англ. encapsulation, от лат. en capsula) — в информатике упаковка данных и функций в единый компонент.
С какой целью это всё засунуто в одну капсулу — уже следующий вопрос.
То, что Вы называете инкапсуляцией, по ссылке выше называется сокрытием, и про это дело там сказано следующее:
В ООП инкапсуляция тесно связана с принципом абстракции данных (не путать с абстрактными типами данных, реализации которых предоставляют возможность инкапсуляции, но имеют иную природу). Это, в частности, приводит к другому распространённому заблуждению — рассмотрению инкапсуляции неотрывно от сокрытия. В частности, в сообществе С++ принято рассматривать инкапсуляцию без сокрытия как неполноценную. Однако, некоторые языки (например, Smalltalk, Python) реализуют инкапсуляцию в полной мере, но не предусматривают возможности сокрытия в принципе.
От себя добавлю, что в JavaScript «сокрытие» делается методом добавления "_" в начало имени атрибута. При этом атрибут, конечно, остаётся видимым извне, но товарищи по команде оповещены, что если есть такая пометка, то трогать нельзя.
Хочу.
Тут немножко придётся залезть в дебри.
У нас, человеков, среди прочего есть две базовые операции: декомпозиция и обобщение. Оно же анализ и синтез. Оно же дедукция и индукция. Обе — крайне полезные штуки. Можно, не сильно покривив душой, сказать, что на них построено всё наше мышление. При декомпозиции мы выдумываем разделяющий принцип и, применяя его, делим что там нам надо поделить, на части. При обобщении мы выдумываем обобщающий критерий и применяем его. Всё просто и буднично. Как правило, эта операция нам сразу даёт мощный позитивный эффект. Набор бессвязной хрени становится чем-то таким, чем можно осмысленно манипулировать. Воодушевившись успехом, мы наивно полагаем, что повторное применение той же операции к достигнутому результату тоже будет успешным. И тут нам облом. Тот же принцип, который мы применили на первом шаге, мы применить не можем (он уже естественным образом себя исчерпал), и придумывать приходится другой принцип. Казалось бы, нет проблем, но возникает резонный вопрос: а почему второй принцип после первого? Почему не наоборот? Они ведь разные, и поэтому не могут не быть равноправны. Применение второго шага декомпозиции оказывается подвержено влиянию первого шага, но в реальности никакого такого влияния нет. Критерии независимы, но у нас они оказались зависимы, и поэтому об эту не соответствующую действительности зависимость теперь мы будем всю дорогу спотыкаться.
Ради интереса возьмите любой иерархический классификатор (хотя бы даже ОКВЭД) и насладитесь непроходимостью творящегося безобразия.
Мне как автоматизатору по роду деятельности, часто приходится до хрипоты рвать глотки с заказчиками, пытаясь отучить их от того, что я называю деревянным мышлением. Каждое (без исключения) выращенное в системе дерево — это мина замедленного действия. Когда конкретно она рванёт — лишь вопрос времени. Продавить свою позицию, к сожалению, удаётся далеко не всегда, и поэтому приходится тратить куски своей драгоценной жизни на ликвидацию последствий катастроф, неизбежность которых была ясно видна с самого начала.lair
06.02.2017 18:56+1Тётя Вика здесь говорит нам следующее:
Теперь сравните с английской.
Тут немножко придётся залезть в дебри.
Угу. Доказательства, что ошибка обязательно содержится в любой иерархии глубже двух уровней, не воспоследовало. А жаль.
Впрочем, не суть. Для ООП глубокие иерархии не обязательны; собственно, можно иметь ООП и вовсе без иерархий.
maslyaev
06.02.2017 19:50...used to refer to one of two related but distinct notions...
Бардак.
Доказательства, что ошибка обязательно содержится в любой иерархии глубже двух уровней, не воспоследовало. А жаль.
По-хорошему, конечно, нужно было бы как-нибудь опереться на какой-нибудь математический формализм и строго это дело доказать. Но только конкретно сейчас у меня нет никаких идей относительно того, что это может быть за формализм.
Факт семантической разнородности разных уровней иерархий наблюдается каждый раз. Более того, начиная со второго уровня почти всегда семантика начинает скакать от ветки к ветке. Если говорить о классификаторах, то примерно в 100% случаев гораздо лучшим решением бывает срыть к едреням дерево, замутить плоский список и обвесить его дополнительной атрибутикой (которая, собственно, и реализует разные классификационные разрезы).
Надо отдать должное, предоставление услуг по вытаскиванию несчастных из той помойки, куда они загоняют себя деревянным мышлением, бывает неплохим источником вкусного дохода. Не знаю кому как, но мне не доставляет особого морального удовлетворения наживаться на глупостях человеческих.lair
07.02.2017 00:31Бардак.
Такова жизнь. Плохо у нас с устоявшейся терминологией.
Факт семантической разнородности разных уровней иерархий наблюдается каждый раз.
… но еще не факт, что это логическая ошибка.
Ну и да, добрая половина проблем любой иерархии решается возможностью иметь одну и ту же сущность больше чем в одной ветке.
maslyaev
07.02.2017 13:39Ну и да, добрая половина проблем любой иерархии решается возможностью иметь одну и ту же сущность больше чем в одной ветке.
Дубли?
… но еще не факт, что это логическая ошибка
Логическая ошибка становится понятной, если посмотреть структуру хранения. Допустим, эта беда у нас в реляционной базе. Стандартный способ хранения иерархий — это ссылка таблицы на саму себя. Простор для извращений там, конечно, есть, но базовое решение — именно такое: имеем ключевое поле ID и ссылку на родителя ParentID.
Допустим, наплодили такую иерархию:
1 Круглые
- 1.1 Зелёные круглые
-- 1.1.1 Деревянные зелёные круглые
-- 1.1.2 Железные зелёные круглые
- 1.2 Красные круглые
-- 1.2.1 Деревянные красные круглые
-- 1.2.2 Железные красные круглые
2 Квадратные
- 2.1 Зелёные квадратные
-- ...
- 2.2 Красные квадратные
-- ...
У элементов второго уровня семантика атрибута ParentID — цвет. У элементов третьего уровня — что? По ходу дела, сочетание форма+цвет. Сразу бросается в глаза, что:
1. Отдельный предикат «форма» у нас отсутствует. То есть если возникнет потребность дёрнуть запросом «выбрать все квадратные ништяки», то ээээ… проблема. Не удобно, но не смертельно. В принципе, можно выкрутиться.
2. Семантика предиката ParentID стала зависеть от аргумента. А вот это уже смертельно, потому что такая петля, боюсь, исчислением предикатов не предусмотрена.
По сути, у нас в лице ParentID в базе появился предикат:
САМ_АВОСЬ_ДОГАДАЕШЬСЯ_ЧТО(x, y)
Хуже всего, когда на эту плавающую логику (на самом деле это уже стало не логикой, а её отсутствием) придётся завязать какую-нибудь функциональность типа «круглое катить, квадратное кантовать». Противологичность начнёт давать метастазы по системе.oxidmod
07.02.2017 13:46Помойму у вас проблема с логикой, не в обиду сказано. Единсвтенный смысл хранить дерево в бд — это отношение belongsTo между чайлдом и парентом. Форма, цвет, материал — это атрибуты, которые удобно хранить в EAV, если коилчество атрибутов неопределнно изначально/может меняться по ходу дела. Тогда никаких пробелм с поиском всего круглого и всего красного не взникнет
maslyaev
07.02.2017 16:05Что значит «чайлд» и «пэрент» применительно к живым людям, я знаю, но что это может быть применительно к таким абстрактным сущностям, как, например, номенклатурная позиция — это я как-то совсем не врубаю. Может быть, потому что по жизни тупой ;)
lair
07.02.2017 13:57Дубли?
Нет, зачем?
Логическая ошибка становится понятной, если посмотреть структуру хранения.
А если у меня иерархия без структуры хранения? Например, иерархия классов?
У элементов второго уровня семантика атрибута ParentID — цвет. У элементов третьего уровня — что?
А вот и ошибка моделирования. Семантика атрибута
ParentID
— это родительский элемент, все, без вариантов. Если вам нужен цвет, то сделайте атрибутColor
.
И да, в ООП эта проблема традиционно решается интерфейсами (не важно, явными или имплицитными): IShape, IMaterial, IColor.
maslyaev
07.02.2017 15:07
Множественное наследование? В принципе, в ОО-делах тема зачётная, хотя есть фанаты её повсеместного запрещения.Дубли?
Нет, зачем?
родительский элемент
Это просто словосочетание из двух слов. Как только начинаешь расковыривать его смысл, клиент обычно путается в показаниях. Вы правда думаете, что круглое рожает круглое зелёное? Хотел бы я посмотреть на эти роды :))
Я же говорю, это предикат, смысл которого теряется в пучине мифов и сказок народов мира.
А если у меня иерархия без структуры хранения? Например, иерархия классов?
А здесь что, лучше? Что означает «являться потомком»? С технической точки зрения (то есть как такая конструкция отрабатывается компилятором) всё понятно, но расскажите мне, в чём смысл отношения наследования между классами. Класс-потомок является подмножеством множества, символизируемого классом-предком? Или нет? Или что-то другое? Исходя из ответа и будем разбираться, почему в данном случае иерархия тоже неизбежно содержит внутри себя логическую ошибку.
И да, в ООП эта проблема традиционно решается интерфейсами (не важно, явными или имплицитными): IShape, IMaterial, IColor.
Всё правильно. Когда базовая идея кривая, то приходится выкручиваться, городить всякие уродливые костыли типа миксинов, темплейтов и прочих ужасов скорбного существования.
Если вам нужен цвет, то сделайте атрибут Color
В этом-то и фишка, что чем страдать бессмысленными перверсиями, добавить атрибуты Color, Shape и Material, а иерархию срыть к едреням за ненадобностью.lair
07.02.2017 16:24Множественное наследование?
Например, но не обязательно.
А здесь что, лучше? Что означает «являться потомком»?
Формальный ответ: "is a". "А потомок Б" означает "А есть Б" (но не наоборот).
Всё правильно. Когда базовая идея кривая, то приходится выкручиваться, городить всякие уродливые костыли типа миксинов, темплейтов и прочих ужасов скорбного существования.
… вот только ничего из описанного в моем решении нет, и в базовой идее тоже нет (и прекрасно работает без).
В этом-то и фишка, что чем страдать бессмысленными перверсиями, добавить атрибуты Color, Shape и Material, а иерархию срыть к едреням за ненадобностью.
Расскажите это биологам с их иерархической систематикой.
maslyaev
07.02.2017 18:02Формальный ответ: «is a». «А потомок Б» означает «А есть Б» (но не наоборот).
Понятно. «Что такое осень — это небо, плачущее небо под ногами».
Сразу же классический кейс: окружность и эллипс. С одной стороны окружность — это эллипс, у которого совпадают фокусы. С другой стороны эллипс — это проекция окружности. Но это я так, просто вспомнилось. Не заморачивайтесь.
Отношение «is a» — это как раз про подмножества, правда ведь? Чётное число — это целое число. Целое число — это рациональное число. Рациональное число — это действительное число. Хорошо, когда множества так ловко целиком входят подмножествами в другие, правда? Но в реальной жизни оно всё не похоже на такую красоту. В реальности картинка больше похожа на чёрт знает что с взаимоналожениями и взаимопересечениями. Местами Пикассо, местами Эшер. В таких условиях рассчитывать на аккуратные серии взаимовложенных множеств — детский наив.
вот только ничего из описанного в моем решении нет
Утиная типизация? Да, хорошая штука. Самому нравится :)
Расскажите это биологам с их иерархической систематикой.
Ну, у них в данном случае есть хорошее обоснование в виде особенностей процесса, порождающего их многообразие. Я имею в виду эволюцию, распочковывающую генетические линии. Там действительно по факту получается дерево, и биологам лишь остаётся разобраться, что считать разделяющим признаком в первую очередь — какую-нибудь форму крылышек или какое-нибудь строение щетинок. По крайней мере, на многоклеточных такой фокус удаётся. Но всё равно, насколько мне известно, древовидная типизация бактерий — непроходимый кошмар, который, не исключено, что навсегда.lair
07.02.2017 18:10Сразу же классический кейс: окружность и эллипс. С одной стороны окружность — это эллипс, у которого совпадают фокусы. С другой стороны эллипс — это проекция окружности.
Это классический пример того, где наследование не надо применять.
(впрочем, и то, и другое — фигура)
Но в реальной жизни оно всё не похоже на такую красоту.
Вопрос того, как вы строите модель, вот и все. Скажем так, "в реальной жизни" достаточно много случаев, когда глубокие иерархии работают, а во всех остальных случаях просто никто не заставляет.
Ну, у них в данном случае есть хорошее обоснование в виде особенностей процесса, порождающего их многообразие
Ну вот видите, значит, не все иерархии содержат логические ошибки. Что, собственно, и требовалось доказать.
maslyaev
07.02.2017 20:11Скажем так, «в реальной жизни» достаточно много случаев, когда глубокие иерархии работают
Или нам кажется, что работают, потому что мы к ним привыкли и приучились их считать неизбежным злом. Но при внимательном рассмотрении оказывается, что зло это совсем не неизбежное, и мы просто попали в ситуацию выученной беспомощности.
а во всех остальных случаях просто никто не заставляет
Если есть тру-ООПшная библиотека и её нужно заюзать, то мы просто вынуждены бываем рыться в куче жёстко фрагментированной логики, матерясь и понимая, что цимуса здесь процентов 10, а всё остальное — просто ООПшный шлак.
Ну вот видите, значит, не все иерархии содержат логические ошибки. Что, собственно, и требовалось доказать.
В данном случае ошибкой является привязка к таксономическим признакам, в результате чего вполне рядовая ситуация, когда (гипотетический пример) во что-нибудь «щетинковое» приходится относить существа, напрочь лишённые щетинок. По-хорошему, конечно, нужно как-то (не понятно как) всё привязывать к генам, или даже не только к ним. В общем, пусть ребята сами разбираются. Нам своего геморроя хватает.
Моё утверждение «любая иерархия более двух уровней неизбежно содержит внутри себя логическую ошибку», конечно, можно (и нужно) фальсифицировать. Авось найдётся что-то действительно стопудово иерархическое, на фоне чего ущербность привычных древовидных конструкций будет ещё нагляднее.lair
08.02.2017 08:54Или нам кажется, что работают, потому что мы к ним привыкли
… а почему мы к ним привыкли?
Если есть тру-ООПшная библиотека и её нужно заюзать
Ну так возьмите другую, кто мешает-то?
В данном случае ошибкой является привязка к таксономическим признакам, в результате чего вполне рядовая ситуация
А (а) есть ли такая привязка и (б) ошибка ли это? Ведь если группировка есть, значит, она зачем-то нужна.
Cryvage
08.02.2017 02:50Почему все так зациклились на применении наследования для описания предметной области? Наследование нужно только с одной целью — исключение дублирования кода. Но это не идеальный, и не единственно возможный инструмент для этого. Там где оно не подходит есть другие приёмы: интерфейсы, шаблоны, миксины, методы расширения, утиная типизация, кодогенерация.
Что касается непосредственно описания предметной области, то основной инструмент для этого — алгоритмы, ну и соответственно методы, являющиеся их контейнерами. Ведь если говорить о наследовании, то объекты могут быть объединены через него довольно ограниченным количеством способов. Всё что мы можем таким образом сказать это то, что класс А является разновидностью класса Б. Что можно описать таким скудным словарём? Да практически ничего. Только самую основу, если иерархичность явно выражена. В свою очередь, метод может описывать отношение любого типа. Поэтому правильным подходом при проектировании является создание такой структуры классов и интерфейсов, которая хорошо сочетается с заданным набором методов. Если А и Б являются разными сущностями, но при этом они настолько похожи, что большинство наших методов могут работать с ними одинаково, то это наследование (хотя при масштабировании, скорее всего окажется что мы погорячились и надо было просто выделить интерфейс). Если сущности А и Б сильно отличаются, но есть сущность В, которая иногда может быть обработана как А, а иногда как Б, то следует выделить соответствующие интерфейсы. Если какому-то методу вообще фиолетово что за объект ему передан, но в процессе нам важно не потерять информацию о типе, то это шаблонный метод. И т.д. Везде основой для выбора является метод и его алгоритм. Наследование, интерфейсы, шаблоны — инструменты для повторного использования кода, но пока у нас нет самого кода, или хотя бы представления о том, каким он будет, мы не можем принять решение о том, какой из инструментов нам удобнее в каждом конкретном случае.
К тому же странно слышать критику ООП, основанную на критике механизма наследования. Наследование при ОО подходе — не главный приоритет. Главным приоритетом является инкапсуляция. А наследование, как раз, сильнее всего нарушает инкапсуляцию. Эти механизмы являются антагонистами.
Отношение «являться» это слишком сильная взаимосвязь. Можем ли мы утверждать что окружность является эллипсом? Наверное можем, с определённой натяжкой. И вот в этом основная ошибка — использовать наследование там, где его можно как-то притянуть за уши. Наследование можно применять только тогда, когда оно не подлежит сомнению. Даже в вашем примере с числами я бы поостерёгся. Целое число является рациональным, только если мы учитываем исключительно диапазон допустимых значений. Но у чисел могут быть и другие важные характеристики, например допустимые операции, или способ представления в памяти компьютера. В частности, целые числа являются перечислимым типом, а рациональные — нет. Это кстати вскрывает суть проблемы. Выстраивая иерархию классов мы проводим классификацию по определённым признакам. И как только возникает необходимость учитывать признак, на который мы изначально не закладывались, вся иерархия может быть разрушена. Это проблема всех иерархических моделей, в том числе и наследования.
В то же время, интерфейсы больше похожи на систему тегов. Тут с масштабируемостью и гибкостью всё гораздо лучше. Если вместо наследования использовать сочетание композиции и интерфейсов, огромное количество проблем сразу отпадает. Впрочем, совсем отказываться от наследования было бы неправильно, ведь не всё нам нужно масштабировать, иногда простота и скорость разработки важнее.Zenitchik
08.02.2017 12:11Почему все так зациклились на применении наследования для описания предметной области?
Не все, а один конкретный товарищ, который не умеет готовить ООП.
Zenitchik
03.02.2017 22:20Необходимость постоянной перетряски иерархий объектов
За шесть лет один раз.
Постоянная перетряска иерархий — признак негодного первоначального замысла. Как говорится, если не умеешь пользоваться — это проблема не парадигмы.
Cryvage
02.02.2017 19:45Реальный мир ни из каких объектов не состоит.
А из чего он состоит? Из струн? А струна это не объект? Свойства у неё есть, поведение тоже. Значит объект. Да на самом деле и не важно из чего состоит мир. Важно как мы его воспринимаем. Какую абстракцию используем. При этом ни одна из абстракций в мозгу человека не соответствует реальности на 100%. Да и не важно какая абстракция больше соответствует. Важно какая из них удобнее в конкретной ситуации. Та же Ньютоновская физика (если уж вы про физику начали) оказалась лишь приближением Эйнштейновской, но при этом она используется до сих пор. Почему? Потому что просто и удобно, при этом во многих случаях точность более чем достаточная.
Веб-сервер не моделирует выдачу веб-страниц клиентам, а выдаёт их. Браузер не моделирует прорисовку страниц, а прорисовывает их.
Ни браузер, ни веб-сервер не делают ничего. Всю работу делает компьютер. А моделирует эту работу программист. Браузер и веб-сервер, как и все остальные программы — это всего лишь текст. План, в котором сказано что должен делать компьютер, чтобы выдать веб-страницу, или прорисовать её. То есть, по сути, это модель действий компьютера в той или иной ситуации.
К тому же, я не понимаю, почему вы, говоря о моделировании, выделили именно ООП, мол оно специально для моделирования. Ведь моделирование это некоторое описание предмета или процесса. А функциональный и декларативный подходы разве хуже подходят для описания? Отнюдь, они являются описанием в чистом виде.
Описывать окружающий мир, и моделировать его можно и тем и другим способом. Даже Вселенная с этим согласна, вот например фотон, в зависимости от ситуации может вести себя и как волновая функция, и как объект-корпускуляр. Получается объектно-функциональный дуализм. Если продолжить развивать эту мысль, то можно дойти до волновых функций высшего порядка.maslyaev
02.02.2017 20:58А из чего он состоит?
Ни из чего. Нет у него встроенной опции «состоять из...». Любое деление на объекты — результат счастливого сочетания следующих двух вещей:
1. (естественно) Пригодности обозреваемого куска (или аспекта) реальности к тому, чтобы на нём мы смогли выделить отдельные объекты. Если говорить о материальном мире, то предметы в твёрдом агрегатном состоянии в этом плане удобны, а с жидкостями и газами приходится изворачиваться с разной степенью успешности. С нематериальными объектами тоже бывает по-разному.
2. Способностей субъекта и особенностей той ситуации, в которой он в данный конкретный момент находится. Мой любимый пример — стакан воды. Когда мы говорим «принеси стакан воды», мы имеем в виду ёмкость+жидкость, а когда говорим «выпей стакан воды», мы имеем в виду только жидкость.
Применительно к ООП ситуация выглядит драматично. Допустим, сегодня мы твёрдо уверены, что стаканы воды — это всегда с ёмкостью, и колбасим логику исходя именно из такого способа выделения объекта из реальности. А завтра оказывается, что нам нужно запрограммировать логику ситуации «в желудке 2 стакана воды». А у нас стеклянный стакан ну никак не пролазит через пищевод :))
Никакое разделение мира на объекты не является окончательным и единственно правильным. А мы мало того, что на это закладываемся, так ещё и придумываем себе на свою голову отягчающие обстоятельства в виде иерархий классов.
Ни браузер, ни веб-сервер не делают ничего. Всю работу делает компьютер.
В некотором смысле это правда, но правда совершенно никчёмная. Что с ней делать? Что она нам даёт? Мы в своих делах никогда не имеем дело с той аппаратурой, которая непосредственно выполняет наш код. Вы имеете физический доступ к тем узлам процессора, на которых исполняется Ваш код? Я — точно нет. Я всегда имею дело с буковками на экране.
Где проверяется пользовательский ввод? Да вот здесь, в функции CheckUserInput. Где ошибка, завалившая сервак? Да вот здесь, в запросе, в соединении таблиц забыли вписать доп. условие и получили, по сути, cross join. Не знаю как кто, но обычно я даже примерно себе не представляю, где физически находятся те компьютеры, которые «делают всю работу». Где-то на планете Земля. Это точно. Для космических аппаратов я ещё ничего не программил.Cryvage
08.02.2017 03:56Ни из чего.
Ну давайте придумаем парадигму: «Ни на что ориентированное программирование» («Nothing Oriented Programming»). Основным постулатом будет «Всё есть ничто». Думаю из всех языков, whitespace наиболее близок к её реализации.
Собственно я согласен с вами, что нет единственно верного деления на объекты. Но это если говорить в общефилософском смысле. А если применительно к конкретному ТЗ и предметной области, то можно подобрать один из наиболее удобных вариантов. Плюс можно добавить абстрактности, и совместить так несколько возможных схем деления. Как я и написал выше, не важно из чего мир состоит, важно как мы его воспринимаем, или как нам удобно его воспринимать в конкретной ситуации. Человек мыслит с помощью сравнения. А сравнивать удобно дискретные сущности. Чтобы их сравнивать — нужно выделить их свойства. Вот вам и объекты. Отсюда они и берутся. А результатом сравнения является классификация по ряду признаков. Вот вам и классы. Объектный подход не отражает реальность, он отражает наш способ восприятия этой реальности.
Что касается вашего примера со стаканами, то там у вас требования изменились. Изначально нам ничего не известно о стаканах воды в желудке, а потом вдруг оказывается что надо это запрограммировать. Естественно, если в своей архитектуре мы не предусмотрели какой-то задел на будущее, то нам придётся архитектуру переделывать. Но то же самое можно сказать и про простейшую программу состоящую из одной процедуры. Если процедуру захотели применить для случая, который изначально не оговаривался, и она оказалась недостаточно абстрактна, то её придётся переписывать. Написали мы «Hello world», а теперь заказчик хочет чтобы программа писала не только «Hello», но и другие приветствия в зависимости от времени суток, типа «Good morning». И не только по отношению к миру, но и обращаясь к пользователю по имени, переданному в параметре. То есть тут проблема фундаментальная. Я даже не представляю какой должна быть парадигма, чтобы при изменении требований мы были полностью застрахованы от переписывания кода. Хотя, если расширить принцип «Всё есть ничто», и считать что и требования заказчика — ничто, и зарплата, и работа тоже. Тогда может и прокатить.
В некотором смысле это правда, но правда совершенно никчёмная.
Мы в своих делах никогда не имеем дело с той аппаратурой, которая непосредственно выполняет наш код.
Вы меня тут не совсем правильно поняли. Суть не в аппаратуре. И вообще не в том, кто конкретно выполняет программу. В случае с интерпретируемым языком, например, вполне можно остановиться на уровне интерпретатора. Он выполняет то что мы написали. Суть в том, что программу кто-то выполняет, она не выполняется сама. И как правило мы не пишем просто последовательный код в духе: сначала выведи на экран это, а потом вон то, а после завершись. Мы описываем различные ситуации которые могут произойти в процессе выполнения, и что в этих ситациях надо делать. При этом далеко не факт что все эти ситуации возникнут, даже наоборот они не могут возникнуть все, т.к. многие из них взаимоисключают друг друга. Так что программа это модель. Исполнитель берёт эту модель и действует в соответствии с ней. Это не совсем то, что вы подразумевали под моделированием — имитация процессов и сущностей из реального мира. Я это понимаю. Лишь хотел подчеркнуть, что написание практически любой программы не отличается от написания программы моделирующей, скажем, физические процессы. Просто, в одном случае мы будем воплощать в коде модель физического мира, а в другом — модель которую сами придумали и держим в своей голове. И там, и там мы будем описывать в коде все сущности, задействованные в процессе, и возможные варианты их взаимодействия, которые хотим учесть. Объектный подход может быть одинаково удобен в обоих случаях. Как, впрочем, и функциональный, и какой-нибудь ещё. Любой подход будет давать одинаковые результаты в обоих случаях. Ведь разницы между моделированием реального процесса и придуманного, на этом уровне нет.Saffron
08.02.2017 08:27> А если применительно к конкретному ТЗ и предметной области, то можно подобрать один из наиболее удобных вариантов
И вот мы уже выделяем ирерахию объектов в алгоритмах решения конкретной ТЗ, и не пытаемся натянуть объекты на весь наблюдаемый мир. О том и речь, нет никаких объектов в объективной реальности, они появляются только когда мы начинаем анализировать конкретную задачу. И в зависимости от задачи объекты выделяются по-разному.
michael_vostrikov
03.02.2017 18:51+1Веб-сервер не моделирует выдачу веб-страниц клиентам, а выдаёт их. Браузер не моделирует прорисовку страниц, а прорисовывает их.
Это конкретный выполняемый процесс в операционой системе выдает страницы или прорисовывает их. А в коде программы содержится модель этих процессов. И объектов, в них участвующих. Потому что в момент написания программного кода никакой выдачи веб-страниц не происходит. Нужно смоделировать этот процесс, который произойдет в будущем. Именно поэтому в программе надо учесть все возможные ошибки — потому что модель одна, а выполняться она будет многократно.
Lailore
А по мне, такое может быть в мозгу вообще легко( если вас смущает контекст).
52hertz
В чьем мозгу?