Comments 182
Именно поэтому ООП ориентировано вовсе не на объекты. По меньшей мере, не на объекты как модели окружающих нас предметов и явлений.
Может вы просто не так трактуете эти модели?
на прекрасную в своей выразительной простоте модель кощунственно навешивается совершенно противоестественное поведение.
И что же противоестественного в поведении?
Либо мы думаем какой должна бы стать (в будущем) архитектура ООП, чтобы она не мешала, а способствовала отделению свойств от поведения.
Вообще-то объединение свойств (точнее, состояния) и поведения — это одна из определяющих особенностей ООП. А если вы состояние и поведение разделите, то вы получите… ну не знаю, ближе всего это будет к старому доброму процедурному программированию.
Трактовку чего? Приведенного вами кода? Так это просто плохой код, зачем его трактовать?
Нет, я считаю, что должно быть дверь откройся ключом
(ну или "замок" вместо "двери", не суть).
ну и вас не смущает, что на самом деле дверь не открывается?
Конечно, нет. Потому что то, открывается дверь, или нет, зависит от двери. Какие-то отпираются, какие-то нет.
ее открывают
И кто же "открывает" дверь, которая открылась сервоприводом в ответ на поднесение RFID-метки?
в реальных проектах обычно идут дальше: дверь.ОткройсяКлючем(ФабрикаКлючей.Создать(typeof(БронированнаяДверь), возбуждатьСобытиеДверьОткрылась: true)
Вы опять приводите пример плохого кода в качестве аргументации недостатков парадигмы. Это заведомо порочный подход.
вы неизбежно испохабите ваши методы
Был у нас такой разработчик. К счастью, ушёл.
Вообще-то, это очень сильно зависит от типа задачи. Я вам приведу массу примеров, когда ваша дверь не будет описываться тем же набором свойств, что и обычная дверь. Следовательно, это будет ДРУГАЯ МОДЕЛЬ. С другим поведением.
И что? Да, модель зависит от решаемой задачи. Я выбрал какую-то одну модель, кто-то другой выберет другую модель. Это никак не влияет на тот факт, что в моей модели у объектов есть поведение, и оно не противоестественно.
И в итоге придете к контекстам квартир и контекстам ключей, и фабрикам, которые будут делаться фабриками. Я вас уверяю.
… но нет же.
И отход от него оправдывать дедлайнами совсем не стоит.
Это в идеале, но стремиться к этому надо, в том числе и менеджментом, чтоб дедлайнов не было.
архитектура ООП как-то способствует вам в этом или мешает?
и не способствует и не мешает.
А теперь я ставлю вопрос: каким должно быть ООП, чтобы в нем точно также сложно было смешивать свойства/состояние с поведением.
Никаким. Как уже говорилось, объединение состояния и поведения — основополагающая часть ООП. Если вас это не устраивает, ООП вам никогда не подойдет.
Вы сейчас немножечко DDD… того...
У меня весь код такой, сколько бы десятков тысяч строк кода в проекте не было. Дедлайны мне ставит бизнес, я говорю сколько в эти дедлайны успею. Или задачи ставит бизнес — я говорю в какие сроки эти задачи успею.
Что бизнес ставит и сроки, и задачи — так быть не должно. А в нормальные сроки я успеваю и тесты писать, и за качеством кода следить.
Дедлайны мне ставит бизнес, я говорю сколько в эти дедлайны успею. Или задачи ставит бизнес — я говорю в какие сроки эти задачи успею.
Скажите кому душу продать или убить чтобы в таком месте работать? Был уверен что везде
бизнес ставит и сроки, и задачи. Буду благодарен, если поясните какой уровень для такого места нужно иметь и куда копать.
Там где поток проектов, это всё вообще не нужно. Написал один раз и перешел к следующему проекту. Это нужно там где есть один проект и множество инсталляций, непрерывное развитие и постоянное количество разработчиков.
Есть проповедник «чистого» объектно-ориентированного подхода, yegor256.
Посмотрите его статьи в блоге, например про то каким должен быть правильный объект или про паттерны. Или исходный код проекта, который он и его команда написали по таким правилам если обзорных статей недостаточно.
Ну и про объекты-поведения вы правильно отметили, так и хочется добавить к окончанию имени -er
Подход у Егора достаточно жесткий и радикальный, но направление мысли на мой взгляд крайне верное.
если вы напишете быстро какую-то малочитаемую чушь — вы потратите больше сил на ее поддержку, чем могли бы. овчинка выделки не стоит.
тысячи строк чужого кода
для тысяч строк существуют метрики. если их не ввести — компания тонет и начинает срывать те же дедлайны или неоправданно раздувает штат.
они для того и нужны чтобы десятки людей писали код так, будто бы это писал один человек. в одном стиле.
а какая разница что там внутри у этого провайдера? он вам отдаст контекст. и все.
или вы для каждого нового контекста будете вносить новые методы и пилить дополнительные интерфейсы? тогда проблема в недостаточной абстракции. это уже не ООП должно меняться, а стиль разработки.
если вы плодите миллионы лишних объектов — это проблема уже ваша. пишите более гибкий код.
и все-таки вы не ответили — чем deadline мешает лично вам написать красивый код? почему вы противопоставляете эти вещи?
Может быть это недостаточно «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,
контекстЗдания: мойДом.мояКвартира.ПолучитьКонтекст());
Встречали подобное в реальной жизни? Я тоже нет.
Именно поэтому ООП ориентировано вовсе не на объекты.
Потому что, это не ООП. Повернуть(ключ, обороты, контекст здания) — это процедура. Просто оформленная в виде метода класса.
tea.should.have.property('flavors').with.lengthOf(3)
Перестаньте искать Абсолютную Объектную Систему, она всегда будет зависеть от решаемых программистом задач [управления сложностью логики проекта].
Концепция работы в 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. Хотя в функциональном программировании всё уже давно придумано. Почему не используем, если язык позволяет? Скорее всего, потому, что не научили…
Так что, не стоит в очередной раз разоблачать ООП, просто идите и учите другие парадигмы. Каждая из них имеет смысл и область применения.
функции типа mkdir, touch, stat, rename
Так ведь это должны быть методы класса File (ну или Directory), а не другого объекта. Какие еще синглтоны здесь?
В Java это часто выглядит как ( new File(«path/to/file») ).mkdir(), что есть чудовищная аберрация. Всё-таки File чаще всего представляется как открытый файловый дескриптор, как фактический, а не абстрактный ресурс системы. А предложенные функции работают без доступа к файловой системе в пространстве пользователя. Они спроектированы так, что там не важно, существует ли такой именованый объект системы. Нам важно, чтобы он принял определённое состояние, если это возможно, или прочитать его состояние, если возможно. И они работают таким образом для того, чтобы ОС могла оптимизировать работу непосредственно с файловой системой. А ООП как парадигма заставляет нас либо создавать аберрации (а как ещё назвать фактический объект, представляющий абстрактный объект?), либо тащить ненужные операции в пространство пользователя.
P.S. Ещё примеры ужасов можно посмотреть в коде cocos2d-x и Unreal Engine.
Ужасов я видел тоже много, но это не значит, что нужно брать пример, или что ОПП плохое.
По поводу "абстрактности" File, то конкретно ООП никаких здесь ограничений не накладывает. И если уж на то пошло, то может быть FileDescriptor и File отдельно. Более того, так это и должно быть, если есть отличимые разные ответственности.
По поводу пользовательских/непользовательских пространств имен тоже не важно. Ну вот допустим библиотека поставляет File c ограниченными возможностями. Так кто мешает создать обертку над ним, но не в виде менеджера, а в виде "MyFile" (мысль думаю ясна) и в системе использовать уже новый тип? Так мы еще и дополнительно поможем себе, если потом поменяем/добавим в поддержку операционную систему.
Но почему? Где аргументация? Почему пучок процедур должен быть лучше, чем один/два лаконичных типа? И если так, то почему тогда синглтоны выставлены как нечто плохое. Ведь пучок процедур — это по сути тоже самое, только еще и неймспейс захламляется.
Синглтон в системах с виртуальной машиной небесплатен, Да и зачем, если в языке есть неймспейсы — ничего не захламляется.
Кроме того, сравните, как лаконичнее:
filesystem::mkdir(path);
Filesystem::getInstance()->mkdir();
Банальные численные метрики: число новых сущностей, не являющихся ключевыми словами, число языковых единиц на конечный логический блок.
Абстрактные метрики: соответствие сложности подготовки операции контексту операции (копировать файл в рамках одной платформы без сетевых взаимодействий или копировать файл в сетевой распределённой системе). Во втором случае — сойдёт. В первом случае — идёт в хлам.
Да и кто сказал, что это будут четыре реализации? Можно ведь сделать определение типа url в строке, и будет всего одна. И вообще, внутренние детали и способы повторного использования кода могут быть до неузнаваемости разные.
Что инкапсулировать, а что — нет, зависит от задачи. В подавляющем большинстве случаев, чем больше — тем лучше. У вас должен быть техпроцесс, который определяет задачи каждого разработчика на каждом уровне, от этого и нужно плясать. Вопрос: как именно инкапсулировать.
Если использовать ООП — мы можем дать больше контроля, но и больше поводов для ошибок. В процедурном стиле — никакого контроля, но и возможностей ошибиться меньше. В процедурно-функционально-объектном гибриде, как я показывал ниже — контроль даётся со строгими ограничениями. С таким подходом у нас JS программист может чего-то поковырять в C++ коде, не отстрелив никому ног и не поймав SIGSEGV.
А про с++ вы зря не верите. На нём с помощью некоторого упорства можно добиться, чтобы код с явными ошибками не компилировался. Вот здесь написано, как.
Абстракция 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...
}
Или в виде иерархии классов.
Но для тех, кто будет использовать наш код это роли не играет. Для тех, кто будет поддерживать, в сущности, тоже. Поэтому, мы вольны выбрать тот стиль, который принесёт наименьшее число проблем при наиболее эффективном решении задачи. В отношении работы с локальной ФС ООП — неэффективно. Как я сейчас вижу, и с удалённой, наверное, тоже.
copyLocalToLocal
copyLocalToRemote
copyRemoteToLocal
copyRemoteToRemote
В «функциональщине» это можно абстрагировать вот так (язык Scala):
def copy(read: String => Array[Byte], write: (String, Array[Byte]) => Unit)
Это должны быть методы FileSystem, имхо.
Кстати, ООП != абстракции. Процедурные и функциональные выражения тоже абстракции, просто с другой базой для построения.
Специализированные системы — другое дело. Математикам вот порой нужны целые числа, которые не влазят в 64 бита, но они же не предлагают сделать это отраслевым стандартом.
Кроме того, пока вы не видите, проекты стремятся в первую очередь использовать API ОС, а когда нельзя — скатываются к собственным велосипедам. А указанную вами фичу умеет даже sqlite, если абстрагироваться от специфики (лично, глупый я, не вижу принципиальной разницы в способе реализации)
А для кроссплатформенности: чем тоньше прослойка — тем лучше. ООП даёт самые толстые прослойки.
open(url, [data] (Handle *h) {
h << data;
});
P.S. Кстати, все решения — мультипарадигменные. Просто одна из парадигм — ключевая в решении. Ибо во всех решениях, так или иначе, есть и процедуры, и объекты.
P.S. Мы же не будем соревноваться в написании кода, верно?
Я имею в виду код, который мы будем поставлять дальше по производственной цепочке. Согласитесь, намного проще, если код за вас установит соединение и корректно его закроет после завершения операции. Если вы можете написать код, при использовании которого ваш коллега с меньшей вероятностью совершит глупую ошибку (забудет чего-нибудь закрыть), по аналогии с моим примером, почему не сделали этого сразу?
Получается, у меня это — финальный вариант, глубже коллегам смотреть не надо, это детали реализации. У вас другой финальный вариант, который вы же можете улучшить.
Вот, спрашивает вас коллега, как мне в системе записать в файл, вы начинаете ему рассказывать, что нужно создать объект ФС, подключить, открыть там файл, записать, закрыть файл, отключить ФС.
Я скажу — вызови функцию fs::open с твоим url и колбеком, который принимает дескриптор, и прямо в дескриптор пиши, как в поток.
В обоих случаях мы объясняем коллеге, как ему с помощью нашего кода выполнить одну и ту же задачу. Какая разница, как именно будет реализована моя функция, или ваши классы?
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;
}
});
});
Приятный такой глазу декоратор, без дополнительных классов, с вполне понятным именем, общего назначения. ЧЯДНТ?
То есть, могло получиться, что вы написали код без декораторов, забыли закрыть соединение, и организовали утечку ресурсов. Это же ключевой момент момент промышленной разработки. Если что-то можно сделать неверно — именно так оно и будет сделано.
Создаёте вы библиотеку или конечное приложение — не важно, у вас всегда должна быть бумажка, написанная манагером проекта, с указанием требований к функциональности. Но это всё к теме отношения не имеет.
Речь о том, что для создания определённой функциональности нет никакой разницы, будет ваш API в функциональном стиле, ООП или процедурном. Зато разница есть в количестве возможных ошибок при его использовании, сложности освоения, и соответствии этой сложности решаемой задаче.
Если вы вполне осознанно всё это посчитали и выбрали ООП — ваше право. Но большинство разработчиков не может оценить с такой стороны то, что сами написали. Как писатели не могут адекватно работать редакторами и корректорами для самих себя. Поэтому, разработчики сами по себе не работают: есть ПМ, архитектор, тимлид.
Типичный пример — распространённое мнение, что процедурно писать не модно. Функционально — круто, объекто-ориентировано — норм, а процедурно — прошлый век. Я часто использую функции работы с путями к файлу в процедурном стиле, например, так:
string rootPath = filepath::root(path);
string tmp = filepath::merge(rootpath, filepath::name(path) + ".tmp");
filesystem::mkdir(rootPath);
По-моему, более чем выразительно.
Так и короче, и понятней:
let file = File.fromString( path )
file.root().resolve( `${ file.name() }.tmp` ).type( 'dir' )
- Это не ко мне вопрос. Я просто транслировал код один в один.
- Он резолвит путь относительно File. На выходе получается инстанс File.
- Вызов type c новым значением и приводит к созданию реальной сущности соответствующего типа.
Вы хотите сказать, что "filepath::root" — понятно, а "file.root()" — нет? Или что "filepath::merge" — понятно, а "file.resolve" — нет?
Что такое root от filepath (путь файла) — очевидно, а что за root у файла — с ходу не допрёшь.
Что такое объединение путей — понятно, а разрешение путей — это, видимо, какой-то специальный термин.
Назвать метод СОЗДАНИЯ сущности type — это вообще за гранью добра и зла.
Что такое 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'. При этом, если такая директория уже есть, то ничего сделано не будет. Если по этому пути ничего нет, то будет создана директория. А если там находится файл другого типа, то будет брошено исключение, так как поменять тип файла нельзя.
root от filepath
Корневая директория данного пути.
При этом, если такая директория уже есть, то ничего сделано не будет. Если по этому пути ничего нет, то будет создана директория. А если там находится файл другого типа, то будет брошено исключение, так как поменять тип файла нельзя.
Вам не кажется, что это слишком замороченная логика для свойства? От свойства я обычно ожидаю отсутствия побочных эффектов.
Корневая директория данного пути.
В *никсах это всегда одна и та же директория. В любом случае класть временные файлы в корень вам никто не даст.
Вам не кажется, что это слишком замороченная логика для свойства? От свойства я обычно ожидаю отсутствия побочных эффектов.
Это декларативная логика, которая всегда предпочтительнее, чем полагаться на то, что программист не забудет написать такую портянку:
if( filesystem::exists( rootPath ) ) {
if( !filesystem::stat( rootPath ).isDir ) {
throw new Exception( 'Already exists: ' ~ rootPath );
}
} else {
filesystem::mkdir( rootPath );
}
В *никсах это всегда одна и та же директория. В любом случае класть временные файлы в корень вам никто не даст.
Это уже трудности того, кто начал эту ветку. Он хотел, чтобы это был рут — вот в коде и появилось вычисление рута.
Это декларативная логика, которая всегда предпочтительнее, чем полагаться на то, что программист не забудет написать такую портянку:
Наилучший вариант — метод, который создаёт папку или возвращает существующую. И назвать его адекватно действию.
Наилучший вариант — программист описывает что он хочет получить, а программа сама разбирается как этого достичь или кидает исключение, если достижение цели невозможно. А вот описывать каждое действие нет никакого смысла. Я сейчас все приложения строю на реактивных свойствах, что позволяет поддерживать консистентность различных состояний, не прилагая никаких усилий.
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);
Ну, т.е. мы получаем, что есть объекты, которые могут быть открыты ключом, каждый класс таких объектов как-то по-своему реализует взаимодействие с ключами в зависимости от атрибутов, а потом передает коллбэк, в котором мы можем узнать состояние объекта и что-то с ним сделать…
Ведь когда я вставляю ключ в дверь, мне пофигу на механизм внутри, но я пытаюсь ее открыть этим ключом, а затем получаю результат, и анализирую что с ним сделать дальше. И мне нужны только ключ и дверь, количество оборотов обычно определено не мной, а состоянием двери в текущий момент (насколько ее в прошлый раз закрыли я могу знать, а могу и не знать)
Дверь.Замок.Повернуть(
обычныйКлюч,
обороты: 2,
контекстЗдания: мойДом.мояКвартира.ПолучитьКонтекст());
А зачем иметь возможность открывать одну и ту-же дверь в разных зданиях?
Вероятно, контекст квартиры как-то хранится в самой двери, не?
Дверь.Открыть(обычный ключ...)
какой-нибудь делегат, который принимает дверь в закрытом состоянии, принимает ключ, которым вы пытаетесь открыть дверь — и либо открывает ее, либо вышвыривает эксепшин, мол, эти ключем дверь не открывается. Но кто будет поставщиком таких делегатов? Опять получается какой-нибудь Provider!
Вы не понимаете ООП.
ООП-объект — это модель некой сущности из реального мира или абстрактной. В зависимости от требований в момент написания программы, эта модель представляет различные аспекты моделируемой сущности. Для ООП аспекты записываются в виде полей и поведения (методов). В разных задачах может требоваться моделирование различных аспектов с помощью различных наборов полей и методов. ООП само по себе не требует фабрик фабрик, билдеров контекстов и синглтонов, это всё вытекает из требований, и часто это даже упрощения.
А поэтому, моя дверь с замком открывается так:
дверь.открыть(ключ);
Если ваша дверь открывается иначе, вы просто неправильно по-другому её видите.
Лично я вообще не вижу проблемы, кроме не правильного изначального проектирования классов, которое и приводит к появлению контекстов квартиры и прочих химерных сущностей.
Все, что может быть инкапсулированно, должно быть инкапсулированно. (имхо)
Какая проблема неразрешима?
Посмотреть на тот же SOLID, он оперирует понятиями исключительно на уровне абстракции ООП (классы, интерфейсы, наследование и т.п.) Там ни слова про то, насколько близко должна быть модель к реальности, надо ли чтобы кто-то открывал дверь, или чтобы она сама открывалась.
Что же касается SOLID, то это поверхностное обманчивое впечатление. На самом деле проблема не в том, кто открывает дверь или откуда вызывается ее метод само-открывания. Проблема в том, что по ходу все это обрастает целой кучей абстракций, порожденных необходимостью заставить все это работать. И тут наверняка можно выявить вот такую СОСТАВЛЯЮЩУЮ: наши инструменты никак не предохраняют нас от смешивания поведения и состояния. Проект развивается, усложняется — и приходится прибегать вот к таким ужасам как паттерны. При всей их неоспоримой эффективности.
Если не нравится (не подходит) ООП — никаких проблем, берите что-нибудь другое. Например, в процедурном программировании нет проблемы состояния и поведения, потому что нет объектов.
яркий пример нашего времени — нейронные сети
Известный миф. Нейронные сети, помимо названия, не имеют с биологией ничего общего и являются лишь логичным обобщением логистической регрессии.
Дверь.Замок.Повернуть(
обычныйКлюч,
обороты: 2,
контекстЗдания: мойДом.мояКвартира.ПолучитьКонтекст());
А если так:
Вася.ОткрытьЗамок(ВасинДом.Квартиры[35].Дверь.Замок,
Вася.Инвентарь.Найти("СвязкаКлючей")["КлючОтКвартиры"],
2);
где
//фрагмент класса Человек
ОткрытьЗамок(замок,ключ,количествоПоворотов){
замок.ВставитьКлюч(ключ);
для(поворот=0;поворот<количествоПоворотов;++поворот){
ключ.Повернуть();
}
}
и
//фрагмент класса Замок
ВставитьКлюч(ключ){
если((ключ != пуcтой) && (этот.КлючПодходит(ключ))){
ключ.наПоворот += этот.ОбработчикПоворотаКлюча;
}
}
ОбработчикПоворотаКлюча(){
если(этот.ОсталосьПоворотов > 0){
этот.ОсталосьПоворотов--;
} иначе {
этот.Открыться();
}
}
ну и так далее.
Вполне логично выходит. Всё зависит от того как код написать. Объекты это всего лишь абстракция, как вы будете их использовать, зависит только от вас. Да и от того что объекты в программе не соответствуют объектам из реального мира, они не перестают быть объектами. Если в вашей программе замок сам поворачивает ключи внутри себя, то так тому и быть. Просто это такой вид замка.
Есть Некто, кто хочет получить Доступ в Помещение открыв Дверь Ключом.
По логике Дверь не может вести в несколько Помещений, т.е. если Некто откроет одну конкретную Дверь он попадет в одно конкретное Помещение, значит про Помещение вообще не стоит упоминать в этом действии. Далее на Двери может быть несколько Замков в разных состояниях открытости, но каждый конкретный замок Может быть только на одной конкретной Двери. Т.o. если Некто откроет один конкретный Замок одним конкретным Ключом, то при условии, что все остальные Замки на этой двери открыты — он откроет эту Дверь и попадет в Помещение.
Т.е. все, что нужно этому объекту Некто — это отправить сообщение Открыть объекту Ключу с параметром Замок для получения объекта Доступ. Назвать этот метод можно например получить_доступ_в_помещение. Объект Доступ будет знать про какое Помещение и через какую Дверь на основании данных из Замка.
Некто.получить_доступ_в_помещение(Ключ, Открыть, Замок)
человек.открыть(дверь);
...
class Человек implements ДвероОткрыватель {
@Override
void открыть(Дверь дверь) throws Exception {
if (!this.естьКлючДляДвери(дверь)) {
thrown Exception("Нет ключа");
}
Ключ ключ = this.достатьКлючКДвери(дверь);
this.вставить(ключ, дверь.замочнаяСкважина);
this.повернуть(ключ, 3);
this.толкнутьВперёд(дверь);
}
P.S. Мой вариант
пусто открыть(Дверь дверь) {
ЗнанияОДвери знанияОДвери = this.память.искать(дверь);
Список<Метод> методыОткрытияДвери = знанияОДвери.методыОткрытия.сортироватьПо(вероятностьУспеха, лёгкость);
если (методыОткрытияДвери.пустой()){
вернуть Результат(неуспех, "Не знаю, как открыть эту дверь");
}
если (!методыОткрытияДвери.хотьОдин(метод=>метод.испробовать(дверь).статус == успех)) {
вернуть Результат(неуспех, "Не удалось открыть дверь");
}
вернуть Результат(успех, Строка.пустая);
}
А адок потому, что псевдокод на русском, или по структуре?
В таком (и всех подобных ему) рассуждении есть сразу два косяка:
1. Реальный мир ни из каких объектов не состоит. Он, гад такой, вообще хрен знает из чего сам по себе состоит. Мировая теоретико-физическая мысль корчится в конвульсиях, пытаясь представить себе реальность в виде струн, стремящихся минимизировать покрываемую ими по десятимерному пространству-времени площадь, но всё глубже и глубже запутывается в измышлятине и поправочных коэффициентах. О каком стопудово объективном существовании таких вещей, как камни, деревья, люди и т.п. можно говорить в таких непотребных обстоятельствах? Любое разбиение реального мира на объекты (любое!!!) — результат волюнтаризма субъекта и особенностей тех обстоятельств, в которые в данный конкретный момент его, субъекта, занесло. Чуть изменились обстоятельства, и всё, капец. У нас уже на том же материале уже другие объекты. Будем в тысяче первый раз перетряхивать объектную модель нашей ООПшной проги. Или лепить очередную систему костылей, которая нам позволит классом «Собака» описывать не только собак и кошек (это было костылём в прошлый раз), но и аквариумных рыбок.
2. «Моделирование» — зачётная тема, но её ценность слегка (на самом деле не слегка, а очень сильно) преувеличена. Можно всю жизнь протрудиться программером и накодить гигабайты кода, но ни разу не написать ничего, что можно было бы назвать моделью чего-то. Веб-сервер не моделирует выдачу веб-страниц клиентам, а выдаёт их. Браузер не моделирует прорисовку страниц, а прорисовывает их. Даже позорный сумматор не моделирует суммирование, а делает его. И кошка не моделирует охоту на мышку. Автомобиль не моделирует перевозку пассажиров и грузов. И мы сами пишем тут комменты, а не моделируем это самое написание. Моделирование — узкая нишевая задача, почти совсем не встречающаяся в реальной промышленной практике. Ну и какого хрена мы должны решающим преимуществом инструмента считать его способность решать те задачи, решение которых нам заведомо не нужно? (В скобках замечу, что даже для моделирования ООП подходит не для всякого, а только для имитации взаимодействия небольших количеств дискретных сущностей)
Как-то так получается, что реально вкусными и повсеместно полезными у нас оказываются чисто служебные классы. Такие, как String, Array, HashTable и т.п. А все многочисленные ООА и ООД оказываются весьма опасным с точки зрения проектных рисков ментальным мусором.
В глубинном принципе, который можно сформулировать примерно так: «Если реальный мир состоит из объектов, то если и наши программы будут состоять из объектов, то моделировать ими реальный мир будет надёжнее, естественнее и результативнее».
Вы ошибаетесь в этой формулировке. Правильная — как мне кажется — звучит иначе: "поскольку нам удобно использовать абстракцию объекта при описании окружающей действительности, будет удобно, если мы сможем использовать ту же абстракцию в программировании".
Моделирование — узкая нишевая задача, почти совсем не встречающаяся в реальной промышленной практике.
О нет. Каждая программа содержит ту или иную модель бизнес-области. Браузер? DOM и прочие модели сначала HTML, а потом отрисовки. Веб-сервер? Запрос-ответ (это если не считать конвеера).
поскольку нам удобно использовать абстракцию объекта при описании окружающей действительности, будет удобно, если мы сможем использовать ту же абстракцию в программированииАбсолютно верно!
С этой точки зрения мы, конечно, обречены работать с объектами. Таков наш способ взаимодействия с реальностью: мы выделяем из неё объекты, и уже потом с ними взаимодействуем. Не важно, что мы делаем — ботинок чиним, стенку шпаклюем или ваяем веб-морду. И в этом плане нет ничего странного в том, чтобы наши программы состояли из объектов.
Но возникает целый ряд «но». Хорошо, объекты. Но почему они должны в себе инкапсулировать данные и поведение? Возьмём, например, адресную книгу (объект), содержащую в себе из записи (тоже объекты). Запись адресной книги — это просто структурированный кусок бинарных данных. Какое собственное поведение должно быть у куска бинарных данных? Программист морщит лоб и начинает ваять конструктор и геттеры-сеттеры. Хоть такое, высосанное из пальца, но всё же поведение. Ну не глупость?
В базах данных, на мой взгляд, более здравый подход: адресная книга — это таблица, а что и как с ней делать (то есть вся прикладная логика) вынесено на усмотрение тех, кто её юзает. Надо по имени найми мэйл — пожалуйста. Надо по мэйлу узнать имя — тоже нет проблем. SQL в руки. Встроить какую-то логику в триггеры, конечно, бывает полезно, но о том, чтобы всё, что может происходить с адресной книгой, объявить её поведением и попытаться инкапсулировать в этот объект — это никому даже в страшном сне в голову не придёт.
Каждая программа содержит ту или иную модель бизнес-области.Нет. Она строится с учётом разнообразных моделей предметной области, но чтобы они в программу закладывались — про такое я не слышал. Да и в каком виде их туда лучше закладывать? В Визио или лучше в PNG? :))
А если серьёзно, то в молоток не закладывается модель забивания гвоздей, в автомобиль не закладывается модель пассажира. Почему в программу должна закладываться какая-то там модель? То есть сущности, свойства которых в достаточной мере соответствует свойствам элементов моделируемой системы?
Между тем, чтобы «являться моделью» и «быть сконструированным с учётом результатов моделирования» — большая логическая разница. Нужно просто перестать путаться.
Мне кажется, лучше говорить не о моделях, а об инструментах и метафорах. Адресная книга у нас будет не моделью блокнотика, а инструментом накопления фактов о корреспондентах. А текстовый редактор пусть не будет моделью листа бумаги, но метафора «лист бумаги» пусть будет идейной основой визуального представления.
Хорошо, объекты. Но почему они должны в себе инкапсулировать данные и поведение?
Давайте начнем с простого вопроса: зачем вообще нужна инкапсуляция? Инкапсуляция нужна для того, чтобы снизить воспринимаемую сложность модуля кода, скрыть от пользователя (которым выступает программист) ту информацию, которая ему не нужна и избыточна. А дальше вам нужен механизм, который позволит сгруппировать скрытую информацию, и провести границу — вот этот код эту информацию видит, а этот — нет. Объект — это одна из подобных возможных границ, вполне логичная внутренне.
Программист морщит лоб и начинает ваять конструктор и геттеры-сеттеры. Хоть такое, высосанное из пальца, но всё же поведение. Ну не глупость?
Глупость — это ваять конструкторы и геттеры-сеттеры только потому, что поведение "должно быть" — вместо того, чтобы взять поведение, которое есть, и использовать его.
Возьмем в качестве примера простой заказ: позиция, количество, общая сумма. Общая сумма — это вычислимое поле, оно считается как "цена позиции * количество". Это правило — это как раз поведение. Что хуже, вы не можете реализовать это поведение "снаружи" заказа, потому что для этой реализации вам нужно будет иметь возможность записывать в поле "общая сумма" у заказа — а это значит, что кто-то другой может записать туда сумму, которая не удовлетворяет этому правилу, и тем самым нарушит целостность. Вот вам и поведение, которое логично принадлежит сущности "заказ".
Более того, благодаря инкапсуляции, в тот момент, когда у нас появятся оптовые скидки для заказов, где количество превышает 100, мы просто модифицируем это поведение в сущности "заказ" — и никто из потребителей не будет потревожен.
В базах данных, на мой взгляд, более здравый подход: адресная книга — это таблица, а что и как с ней делать (то есть вся прикладная логика) вынесено на усмотрение тех, кто её юзает.
Это плохо масштабируется: один программист никогда не может быть уверен, что другой программист не записал в таблицу данных, которые не удовлетворяют бизнес-правилам.
Нет. Она строится с учётом разнообразных моделей предметной области, но чтобы они в программу закладывались — про такое я не слышал.
Вы не "не слышали", вы не задумывались.
Да и в каком виде их туда лучше закладывать? В Визио или лучше в PNG?
В том виде, в котором пишется программа.
Почему в программу должна закладываться какая-то там модель?
Она не "должна закладываться", она там есть. Когда вы создаете в БД таблицу "АдреснаяКнига" с колонками — вы получаете явную физическую модель предметной области (в виде таблицы с таким-то набором колонок и такими-то правилами). Когда я пишу в коде record OrderLine {ItemId: string, Quantity: decimal, Total: decimal}
— я тоже создаю модель предметной области, только выраженную кодом (и, в зависимости от парадигмы, она может быть объектной или не объектной).
А дальше речь идет только о том, насколько близки или далеки друг от друга различные модели одной и той же предметной области, используемые в одном проекте. Чем они дальше, тем больше ресурсов уходит на трансляцию.
Адресная книга у нас будет не моделью блокнотика, а инструментом накопления фактов о корреспондентах.
… при этом она все еще будет содержать в себе модель предметной области "корреспонденты" в виде, предположительно, таблицы — ну или документо-ориентированной БД, если вам так захотелось, или чего-то еще.
… снизить воспринимаемую сложность модуля кода...Для этого у нас есть функции. Здесь же мы берёмся снижать воспринимаемую сложность пары «код + данные, с которыми он работает». Выбранный способ (строительство заборчика вокруг данных), ИМХО, не самый лучший. Единственное, на мой взгляд, преимущество этого способа в том, что он является первым, что приходит в голову.
… и провести границу...Ага. Построить заборчик. Потом другой. Возвести заборостроение в базовый принцип, и после этого удивляться, что ландшафт жёстко фрагментировался и превратился в нагромождение какой-то немыслимой хреновни.
Заборы строить безусловно нужно. Иметь системы, построенные по принципу «гуляй, ветер», не хочется никому. Мотивов тому может быть масса, начиная от банального разграничения сфер ответственности команд разработчиков и до локализации катастроф. Но, как всегда бывает в таких случаях, к вопросам нужно подходить с умом, а не тупо следовать моде.
Возьмем в качестве примера простой заказ: позиция, количество, общая сумма. Общая сумма — это вычислимое поле, оно считается как «цена позиции * количество». Это правило — это как раз поведение.Хороший пример, но, к сожалению жизнь бывает слегка сложнее. Начать хотя бы с того, что цена и количество — это не «просто циферки». Цена в валюте, а количество в единицах измерения. Помимо двух циферок есть ещё договор клиента, в котором указана валюта взаиморасчётов, есть прайс, в котором указана рекомендуемая цена товара, но, как на зло, не по той единице, которая в заказе, а по другой, и между ними таблица пересчёта (это литры в кубометры легко пересчитываются, а кубометры в тонны уже не очень). Да, и ещё цена в прайсе не в той валюте, которая в договоре. Сумму-то мы посчитаем простым умножением, но это всего 1% нашей задачи, если не меньше. А всё остальное пускает свои щупальца по всей системе. И ладно бы если бы это можно было решить APIшками классов, так ведь ещё возникает всякое гадство типа «быстро одной кнопкой получить список имеющихся в наличии товарных позиций с ценами, пересчитанными в выдранную валюту по курсу на указанную дату». SQLю это как два пальца. Юзер моргнуть не успеет. А как оно и, главное, сколько времени будет продираться через интерфейсы объектной модели?
Когда я пишу в коде record OrderLine {ItemId: string, Quantity: decimal, Total: decimal} — я тоже создаю модель предметной области, только выраженную кодомВы не модель создаёте, а работающий механизм. В корне неправильно называть моделированием факт пригодности этого механизма к использованию в той предметной области, для которой он предназначен. Не, ну вдумайтесь. Разве автомобиль содержит в себе модель пассажира, груза и дороги? Разве в автомобиле есть какая-то деталька, которая там служит моделью дороги? Только, умоляю, не говорите «колесо». Колесо — круглое, а дорога плоская.
Для этого у нас есть функции.
… которых в какой-то момент перестает хватать.
Выбранный способ (строительство заборчика вокруг данных), ИМХО, не самый лучший.
Предложите лучше.
Хороший пример, но, к сожалению жизнь бывает слегка сложнее.
Ну так и пример масштабируется вместе с усложнением жизни.
Начать хотя бы с того, что цена и количество — это не «просто циферки». Цена в валюте, а количество в единицах измерения
Это называется value type, и давно отработано.
SQLю это как два пальца.
… но нет, на самом деле. Где-то начиная с "на заданную дату".
А как оно и, главное, сколько времени будет продираться через интерфейсы объектной модели?
"Как" — легко как раз, вся сложность пересчетов прячется внутрь каждого конкретного value type. "Сколько"… ну да, скорость работы обычно противоречит остальным критериям качества системы. Так что тут приходится балансировать. Но при разумном проектировании — "достаточно быстро".
Вы не модель создаёте, а работающий механизм.
А почему одно противоречит другому? Почему работающий механизм не может содержать в себе модель?
Понимаете, структура БД — это модель предметной области. "Под моделью понимается некоторый материальный или мысленно представляемый объект (образ объекта), который в процессе изучения замещает объект-оригинал, сохраняя некоторые важные для данного исследования типичные его черты." вики.
В корне неправильно называть моделированием факт пригодности этого механизма к использованию в той предметной области, для которой он предназначен.
Я и не называю.
Почему работающий механизм не может содержать в себе модель?Потому что она там не нужна.
Примерно потому же, почему в конструкции автомобиля нигде не содержится моделька дороги с игрушечными мостиками и светофорчиками.
Мы все находимся в плену, как я её называю, «отражательной» парадигмы понимания понимания (пардон за тавтологию). Типа реальный мир находит своё отражение в представлениях познающего субъекта. Что должно представлять собой это отражение? Правильно, модель. Чрезвычайно убогая парадигма. Начать хотя бы с того, что у нас в мозгах нет ни одного зеркала, чтобы отражать, и закончить хотя бы тем, что моделирование подразумевает не только моделируемое явление, но и того гаврика, который этим моделированием «в процессе изучения замещает объект-оригинал». Короче, прямиком выходим на теорию внутримозгового гомункла. Полная ерунда. Бессмысленная и беспощадная логическая петля. Если мне не изменяет память, эта тухлятина была обильно полита грязью ещё аж в знаменитом сентябрьском 79-го года номере SciAm.
В общем, пусть моделирование останется там, где ему самое место. Как метод исследования. А мы, технари, будем к нему прибегать только тогда, когда нам нужно именно моделирование. Например, для проведения нагрузочных испытаний будем делать роботов, которые действительно будут моделировать пользовательскую активность. Кстати, моделирование бизнес-процессов — тоже зачётная тема. Очень помогает.
Предложите лучше.Декомпозиция программных продуктов — весьма популярная и живая тема. Например клиент-серверные архитектуры — про это. SOA — тоже про это. На ОО свет белый клином не сошёлся. ОО тоже бывает полезно, но только если без фанатизма.
Ну так и пример масштабируется вместе с усложнением жизни.Плохо масштабируется. Необходимость постоянной перетряски иерархий объектов по мере уточнения задачи — родовая травма ОО.
Да ладно… Я сам сто раз так делал. Нормально прокатывает.SQLю это как два пальца.… но нет, на самом деле. Где-то начиная с «на заданную дату».
Потому что она там не нужна.
Но она там есть. Просто в силу определения, приведенного выше.
Если это определение вас не устраивает — значит, у вас другое понятие слова "модель", и, значит, в вашем понимании программа не содержит модели, а ООП ничего не моделирует… что означает, что ваша претензия к ООП, основанная на том, что ООП что-то моделирует, все равно беспочвенна.
Например клиент-серверные архитектуры — про это.
… которые инкапсулируют в себе данные и поведение.
SOA — тоже про это.
… которая инкапсулирует в себе данные и поведение.
Ну то есть оба приведенных вами примера используют то же самое, чего они призваны быть "лучше". Не вышло.
Необходимость постоянной перетряски иерархий объектов по мере уточнения задачи — родовая травма ОО.
А напомните мне, какая парадигма разработки не требует доработок по мере уточнения задачи?
Да ладно… Я сам сто раз так делал. Нормально прокатывает.
Да понятно, что прокатывает. Вопрос сложности.
Но она там есть. Просто в силу определения, приведенного выше.Внимательно читаем определение:
«Под моделью понимается некоторый материальный или мысленно представляемый объект (образ объекта), который в процессе изучения замещает объект-оригинал, сохраняя некоторые важные для данного исследования типичные его черты».
Долго и упорно гипнотизируем слово «изучение». В нём — ключ к пониманию происходящего. Если в программе нужно иметь некий механизм, который другой части программы будет давать прогноз относительно того, как будут складываться обстоятельства, то этот механизм безусловно будет моделью. Насколько часто бывает востребована такая конструкция? Ну, это смотря в какой предметной области работаем. Если программа составляет метеопрогноз, то можно предположить, что тема моделирование атмосферных процессов в ней будет раскрыта основательно. Но если программа является адресной книжкой, то ценность моделирования в ней весьма сомнительна.
Очень странно инкапсулируют. Данные инкапсулируют на сервер, а поведение — на клиент :))Например клиент-серверные архитектуры — про это.… которые инкапсулируют в себе данные и поведение.
Для примера посмотрим на сервис электронного архива. Data warehouse для, грубо говоря, бинарников заранее не определённого назначения. Нормальный такой объект, да? При этом, что забавно, этот самый сервис совсем не обязательно должен быть написан с применением ООП. В конце концов, его можно написать на чистом C без единого плюса.SOA — тоже про это.… которая инкапсулирует в себе данные и поведение.
Не знаю кому как, но лично мне бывает не удобно работать с ОО-реализацией SOA (SOAP). Слишком много лишней дури наворочено. Перекидываться по-простянке XMLями или JSONами через банальный HTTPRequest и удобнее, и практичнее.
А напомните мне, какая парадигма разработки не требует доработок по мере уточнения задачи?Все требуют. Такова жизнь. Вопрос здесь в уровне отчаяния, с которым встречается каждое уточнение задачи. По своему опыту скажу, что проще всего получается выкручиваться, когда ООП применено в очень куцем варианте. Например, когда количество уровней иерархии ограничено двумя (есть набор базовых классов, на основе которых лепим потомков, но налепить потомка потомка — ни-ни). Дурацкое ограничение, которое поначалу дико бесит, но потом, когда случается уточнение постановки, начинаешь понимать, как сказочно повезло.
Да понятно, что прокатывает. Вопрос сложности.5 минут на написание запроса — не ахти какая сложность ;)
Долго и упорно гипнотизируем слово «изучение». В нём — ключ к пониманию происходящего.
Это если вы считаете, что "изучение" обязано происходить во время работы программы. Я не считаю.
Данные инкапсулируют на сервер, а поведение — на клиент :))
Вообще-то в клиент-серверной архитектуре и данные, и поведение могут быть на сервере. См. "тонкий клиент".
Для примера посмотрим на сервис электронного архива. Data warehouse для, грубо говоря, бинарников заранее не определённого назначения.
А SOA ли это?
При этом, что забавно, этот самый сервис совсем не обязательно должен быть написан с применением ООП. [...] Не знаю кому как, но лично мне бывает не удобно работать с ОО-реализацией SOA (SOAP).
А вот тут вы начинаете путать реализацию и концепцию.
Во-первых, я говорил о том, что SOA инкапсулирует данные и поведение, я не говорил, что это обязательно ООП. Но...
Во-вторых, адекватная реализация SOA — это все-таки ООП, причем ООП в его "первоначальном понимании" — именно потому, что каждый сервис инкапсулирует свои данные и поведение. Другое дело, что это ООП "на большом уровне", где объектом является каждый сервис (все потому же). Внутри программы может никакого ООП не быть, это не имеет значения на уровне выше.
По своему опыту скажу, что проще всего получается выкручиваться, когда ООП применено в очень куцем варианте.
Так может это в вашем опыте так было, а у других опыт другой?
Например, когда количество уровней иерархии ограничено двумя
Я сейчас, может, что-то странное скажу, но можно иметь ООП вообще без наследования.
5 минут на написание запроса — не ахти какая сложность
Они, знаете ли, складываются.
можно иметь ООП вообще без наследования.
Я знакомство с ООП c VB начинал ))) Долго не мог врубиться, что такое «наследование» — в пособиях по VB того времени наследованием называли что-то странное и непонятное, никак не относящееся к наследованию в общепринятом понимании )))
Однако, да, отсутствие наследования не то чтобы сильно мешает. Замечательно выезжал на композиции.
Это если вы считаете, что «изучение» обязано происходить во время работы программы. Я не считаю.Вообще, про моделирование это на редкость сложная и глубокая тема. Предлагаю для ясности остаться каждый при своей позиции.
Вообще-то в клиент-серверной архитектуре и данные, и поведение могут быть на сервере. См. «тонкий клиент».Случаи разные бывают. 3-х уровневый К/С тоже никто не отменял.
А SOA ли это?Обязательно. Почему нет? На микросервисную архитектуру оно, конечно, мало похоже, но SOA в чистом виде.
сервис инкапсулирует свои данные и поведениеОпять же случаи разные бывают. Сервис может, например, просто шлюзовать инфопоток, вообще не вникая, что за беда через него струится.
это ООП «на большом уровне», где объектом является каждый сервисВот в этом-то и дело, что на таком большом уровне мы приходим к тому, с чего начали рассуждение, а именно что объект — это просто некий кусок системы, который объектом-то назван только потому, что нам нужно им оперировать как единой целой сущностью.
что-то странное скажу, но можно иметь ООП вообще без наследованияВ обморок не упал. Оно, конечно, против классики, но когда дело доходит до дела, то это бывает вполне рабочий вариант.
3-х уровневый К/С тоже никто не отменял.
В этом случае с точки зрения клиента апп-сервер инкапсулирует данные и поведение.
Обязательно. Почему нет? На микросервисную архитектуру оно, конечно, мало похоже, но SOA в чистом виде.
Правильный вопрос "а почему да". Наличие какого-то сервиса еще не означает, что у вас SOA.
Сервис может, например, просто шлюзовать инфопоток, вообще не вникая, что за беда через него струится.
Может. Но (если это SOA) для внешнего пользователя он либо делает вид, что он — сервис получатель (и это снова инкапсуляция), либо, наоборот, внешний получатель вообще не в курсе его существования (если это часть шины).
Вот в этом-то и дело, что на таком большом уровне мы приходим к тому, с чего начали рассуждение, а именно что объект — это просто некий кусок системы, который объектом-то назван только потому, что нам нужно им оперировать как единой целой сущностью.
… и у этой сущности есть и данные, и поведение.
Оно, конечно, против классики
Это зависит от того, что вы классикой считаете.
Это зависит от того, что вы классикой считаетеТри источника и три составные части ООПизма: инкапсуляция, наследование, полиморфизм.
Правильный вопрос «а почему да». Наличие какого-то сервиса еще не означает, что у вас SOA.Дело же совсем не в том, что какой-то сервис слепили, а в том, что существенный кусок функциональности вынесли в отдельную подсистему, снабдили удобными интерфейсами и сделали сквозной функциональностью для самых разных систем и нужд.
Три источника и три составные части ООПизма: инкапсуляция, наследование, полиморфизм.
О нет. Это всего лишь три атрибута, которые стали ассоциироваться с ООП позже.
Дело же совсем не в том, что какой-то сервис слепили, а в том, что существенный кусок функциональности вынесли в отдельную подсистему, снабдили удобными интерфейсами и сделали сквозной функциональностью для самых разных систем и нужд.
Функциональности. Здравствуй, инкапсуляция поведения.
Это всего лишь три атрибута, которые стали ассоциироваться с ООП позже.Именно в тот момент, когда данные (родимые наши нолики и единички) решили снабдить собственным поведением. То есть когда подумалось, что неплохо было бы, чтобы то ли замок научился в себе проворачивать ключи, то ли ключи научились проворачивать себя в замках.
Здравствуй, инкапсуляция поведения.Конечно. Но вопрос в масштабе. Когда за интерфейсом, скрытым за доступным по некоему адресу кроется отказоустойчивый кластер с полноценной приложухой, то это уже не совсем то, что принято в ООП называть объектом.
Именно в тот момент, когда данные (родимые наши нолики и единички) решили снабдить собственным поведением.
Неа. У Кея не было никакого наследования.
Когда за интерфейсом, скрытым за доступным по некоему адресу кроется отказоустойчивый кластер с полноценной приложухой, то это уже не совсем то, что принято в ООП называть объектом.
Опять-таки, у кого принято?
Но речь, впрочем, не об этом, а том, противоестественно ли объединять данные и поведение. Как видим, не противоестественно.
Но речь, впрочем, не об этом, а том, противоестественно ли объединять данные и поведение. Как видим, не противоестественно.Похоже на то, что Вы прикалываетесь. Ну конечно, объединять данные и поведение никоим образом не противоестественно. Чёрт возьми, они, как ни крути, сливаются в экстазе в любом вычислительном процессе, что бы этот процесс собой ни представлял. Хоть ООП, хоть что угодно другое, включая «as is» машину Тьюринга или даже машину Бэббиджа.
ООП — это всё же, насколько я понимаю, особенное объединение данных и алгоритмов. Такое объединение, когда вместе с данными болтаются ссылки на методы, манипулирующие этими данными. То есть кроме бинарных данных замка есть ещё ссылка на метод «Провернуть».
Эмм, тогда я перестал понимать, что вы понимаете под "объединением". Вот в ООП все понятно: есть данные (состояние), есть набор операций над этим состоянием, в идеальном случае к состоянию вообще нет доступа кроме как через эти операции. Объединение и инкапсуляция. То же самое в клиент-серверной архитектуре, то же самое в сервисной архитектуре.
А вот в "любом" вычислительном процессе — не объединение. Операция "сложение" никак не объединена со своими операндами (не считая общего типа).
я перестал понимать, что вы понимаете под «объединением»Когда мы на ассемблере пишем
ADD AX BX
мы разве не объединяем в единое целое данные (AXи BX) и операцию ADD? Объединяем, и даже понятно, как это объединение материализуется в аппаратуре сумматора.
Данные — это ведь просто нолики и единички. +3.3V / 0V. Есть намагниченность / нет намагниченности. Смысл свой они получают в контексте алгоритма. В любом случае. Можно, конечно, это назвать инкапсуляцией, но только зачем? Инкапсуляция получается тогда, когда кроме самих данных до кучи есть ещё и адрес исполняемого кода, на который нужно передать исполнение для выполнения операции. Логика становится частью данных, и за счёт этого мы получаем дополнительную гибкость. Ту самую, которая называется «полиморфизм». Но за такую гибкость приходится расплачиваться тем, что у нас появляется понятие абстрактного метода, и, как следствие, необходимость нагородить иерархию. А любая иерархия более двух уровней неизбежно содержит внутри себя логическую ошибку. Если хотите, могу рассказать, какую. Выйти за пределы 2-х уровней и нагородить хотя бы третий — очень соблазнительно, и отсюда масса весьма гадских проблем.
А К/С и в SOA напрямую не предполагают описанный выше вариант инкапсуляции, поэтому к ОО их можно отнести только с ооооочень большой натяжкой.
Когда мы на ассемблере пишем ADD AX BX
мы разве не объединяем в единое целое данные (AXи BX) и операцию ADD?
Нет.
Инкапсуляция получается тогда, когда кроме самих данных до кучи есть ещё и адрес исполняемого кода, на который нужно передать исполнение для выполнения операции.
Тоже нет. Инкапсуляция получается тогда, когда внешний пользователь не имеет доступа к ненужным ему данным/функциональности.
Логика становится частью данных, и за счёт этого мы получаем дополнительную гибкость. Ту самую, которая называется «полиморфизм».
Нет, полиморфизм не вытекает из инкапсуляции.
Но за такую гибкость приходится расплачиваться тем, что у нас появляется понятие абстрактного метода, и, как следствие, необходимость нагородить иерархию.
И нет, для полиморфизма не нужны ни абстрактные методы, ни иерархии.
А любая иерархия более двух уровней неизбежно содержит внутри себя логическую ошибку. Если хотите, могу рассказать, какую.
Хочу.
А К/С и в SOA напрямую не предполагают описанный выше вариант инкапсуляции, поэтому к ОО их можно отнести только с ооооочень большой натяжкой.
Вот только "описанный выше вариант инкапсуляции" — это не то, что понимается под инкапсуляцией в ООП.
Инкапсуляция получается тогда, когда внешний пользователь не имеет доступа к ненужным ему данным/функциональности.Тётя Вика здесь говорит нам следующее:
Инкапсуляция (англ. encapsulation, от лат. en capsula) — в информатике упаковка данных и функций в единый компонент.
С какой целью это всё засунуто в одну капсулу — уже следующий вопрос.
То, что Вы называете инкапсуляцией, по ссылке выше называется сокрытием, и про это дело там сказано следующее:
В ООП инкапсуляция тесно связана с принципом абстракции данных (не путать с абстрактными типами данных, реализации которых предоставляют возможность инкапсуляции, но имеют иную природу). Это, в частности, приводит к другому распространённому заблуждению — рассмотрению инкапсуляции неотрывно от сокрытия. В частности, в сообществе С++ принято рассматривать инкапсуляцию без сокрытия как неполноценную. Однако, некоторые языки (например, Smalltalk, Python) реализуют инкапсуляцию в полной мере, но не предусматривают возможности сокрытия в принципе.
От себя добавлю, что в JavaScript «сокрытие» делается методом добавления "_" в начало имени атрибута. При этом атрибут, конечно, остаётся видимым извне, но товарищи по команде оповещены, что если есть такая пометка, то трогать нельзя.
Хочу.Тут немножко придётся залезть в дебри.
У нас, человеков, среди прочего есть две базовые операции: декомпозиция и обобщение. Оно же анализ и синтез. Оно же дедукция и индукция. Обе — крайне полезные штуки. Можно, не сильно покривив душой, сказать, что на них построено всё наше мышление. При декомпозиции мы выдумываем разделяющий принцип и, применяя его, делим что там нам надо поделить, на части. При обобщении мы выдумываем обобщающий критерий и применяем его. Всё просто и буднично. Как правило, эта операция нам сразу даёт мощный позитивный эффект. Набор бессвязной хрени становится чем-то таким, чем можно осмысленно манипулировать. Воодушевившись успехом, мы наивно полагаем, что повторное применение той же операции к достигнутому результату тоже будет успешным. И тут нам облом. Тот же принцип, который мы применили на первом шаге, мы применить не можем (он уже естественным образом себя исчерпал), и придумывать приходится другой принцип. Казалось бы, нет проблем, но возникает резонный вопрос: а почему второй принцип после первого? Почему не наоборот? Они ведь разные, и поэтому не могут не быть равноправны. Применение второго шага декомпозиции оказывается подвержено влиянию первого шага, но в реальности никакого такого влияния нет. Критерии независимы, но у нас они оказались зависимы, и поэтому об эту не соответствующую действительности зависимость теперь мы будем всю дорогу спотыкаться.
Ради интереса возьмите любой иерархический классификатор (хотя бы даже ОКВЭД) и насладитесь непроходимостью творящегося безобразия.
Мне как автоматизатору по роду деятельности, часто приходится до хрипоты рвать глотки с заказчиками, пытаясь отучить их от того, что я называю деревянным мышлением. Каждое (без исключения) выращенное в системе дерево — это мина замедленного действия. Когда конкретно она рванёт — лишь вопрос времени. Продавить свою позицию, к сожалению, удаётся далеко не всегда, и поэтому приходится тратить куски своей драгоценной жизни на ликвидацию последствий катастроф, неизбежность которых была ясно видна с самого начала.
Тётя Вика здесь говорит нам следующее:
Теперь сравните с английской.
Тут немножко придётся залезть в дебри.
Угу. Доказательства, что ошибка обязательно содержится в любой иерархии глубже двух уровней, не воспоследовало. А жаль.
Впрочем, не суть. Для ООП глубокие иерархии не обязательны; собственно, можно иметь ООП и вовсе без иерархий.
...used to refer to one of two related but distinct notions...Бардак.
Доказательства, что ошибка обязательно содержится в любой иерархии глубже двух уровней, не воспоследовало. А жаль.По-хорошему, конечно, нужно было бы как-нибудь опереться на какой-нибудь математический формализм и строго это дело доказать. Но только конкретно сейчас у меня нет никаких идей относительно того, что это может быть за формализм.
Факт семантической разнородности разных уровней иерархий наблюдается каждый раз. Более того, начиная со второго уровня почти всегда семантика начинает скакать от ветки к ветке. Если говорить о классификаторах, то примерно в 100% случаев гораздо лучшим решением бывает срыть к едреням дерево, замутить плоский список и обвесить его дополнительной атрибутикой (которая, собственно, и реализует разные классификационные разрезы).
Надо отдать должное, предоставление услуг по вытаскиванию несчастных из той помойки, куда они загоняют себя деревянным мышлением, бывает неплохим источником вкусного дохода. Не знаю кому как, но мне не доставляет особого морального удовлетворения наживаться на глупостях человеческих.
Бардак.
Такова жизнь. Плохо у нас с устоявшейся терминологией.
Факт семантической разнородности разных уровней иерархий наблюдается каждый раз.
… но еще не факт, что это логическая ошибка.
Ну и да, добрая половина проблем любой иерархии решается возможностью иметь одну и ту же сущность больше чем в одной ветке.
Ну и да, добрая половина проблем любой иерархии решается возможностью иметь одну и ту же сущность больше чем в одной ветке.Дубли?
… но еще не факт, что это логическая ошибкаЛогическая ошибка становится понятной, если посмотреть структуру хранения. Допустим, эта беда у нас в реляционной базе. Стандартный способ хранения иерархий — это ссылка таблицы на саму себя. Простор для извращений там, конечно, есть, но базовое решение — именно такое: имеем ключевое поле 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)
Хуже всего, когда на эту плавающую логику (на самом деле это уже стало не логикой, а её отсутствием) придётся завязать какую-нибудь функциональность типа «круглое катить, квадратное кантовать». Противологичность начнёт давать метастазы по системе.
Дубли?
Нет, зачем?
Логическая ошибка становится понятной, если посмотреть структуру хранения.
А если у меня иерархия без структуры хранения? Например, иерархия классов?
У элементов второго уровня семантика атрибута ParentID — цвет. У элементов третьего уровня — что?
А вот и ошибка моделирования. Семантика атрибута ParentID
— это родительский элемент, все, без вариантов. Если вам нужен цвет, то сделайте атрибут Color
.
И да, в ООП эта проблема традиционно решается интерфейсами (не важно, явными или имплицитными): IShape, IMaterial, IColor.
Множественное наследование? В принципе, в ОО-делах тема зачётная, хотя есть фанаты её повсеместного запрещения.Дубли?Нет, зачем?
родительский элементЭто просто словосочетание из двух слов. Как только начинаешь расковыривать его смысл, клиент обычно путается в показаниях. Вы правда думаете, что круглое рожает круглое зелёное? Хотел бы я посмотреть на эти роды :))
Я же говорю, это предикат, смысл которого теряется в пучине мифов и сказок народов мира.
А если у меня иерархия без структуры хранения? Например, иерархия классов?А здесь что, лучше? Что означает «являться потомком»? С технической точки зрения (то есть как такая конструкция отрабатывается компилятором) всё понятно, но расскажите мне, в чём смысл отношения наследования между классами. Класс-потомок является подмножеством множества, символизируемого классом-предком? Или нет? Или что-то другое? Исходя из ответа и будем разбираться, почему в данном случае иерархия тоже неизбежно содержит внутри себя логическую ошибку.
И да, в ООП эта проблема традиционно решается интерфейсами (не важно, явными или имплицитными): IShape, IMaterial, IColor.Всё правильно. Когда базовая идея кривая, то приходится выкручиваться, городить всякие уродливые костыли типа миксинов, темплейтов и прочих ужасов скорбного существования.
Если вам нужен цвет, то сделайте атрибут ColorВ этом-то и фишка, что чем страдать бессмысленными перверсиями, добавить атрибуты Color, Shape и Material, а иерархию срыть к едреням за ненадобностью.
Множественное наследование?
Например, но не обязательно.
А здесь что, лучше? Что означает «являться потомком»?
Формальный ответ: "is a". "А потомок Б" означает "А есть Б" (но не наоборот).
Всё правильно. Когда базовая идея кривая, то приходится выкручиваться, городить всякие уродливые костыли типа миксинов, темплейтов и прочих ужасов скорбного существования.
… вот только ничего из описанного в моем решении нет, и в базовой идее тоже нет (и прекрасно работает без).
В этом-то и фишка, что чем страдать бессмысленными перверсиями, добавить атрибуты Color, Shape и Material, а иерархию срыть к едреням за ненадобностью.
Расскажите это биологам с их иерархической систематикой.
Формальный ответ: «is a». «А потомок Б» означает «А есть Б» (но не наоборот).Понятно. «Что такое осень — это небо, плачущее небо под ногами».
Сразу же классический кейс: окружность и эллипс. С одной стороны окружность — это эллипс, у которого совпадают фокусы. С другой стороны эллипс — это проекция окружности. Но это я так, просто вспомнилось. Не заморачивайтесь.
Отношение «is a» — это как раз про подмножества, правда ведь? Чётное число — это целое число. Целое число — это рациональное число. Рациональное число — это действительное число. Хорошо, когда множества так ловко целиком входят подмножествами в другие, правда? Но в реальной жизни оно всё не похоже на такую красоту. В реальности картинка больше похожа на чёрт знает что с взаимоналожениями и взаимопересечениями. Местами Пикассо, местами Эшер. В таких условиях рассчитывать на аккуратные серии взаимовложенных множеств — детский наив.
вот только ничего из описанного в моем решении нетУтиная типизация? Да, хорошая штука. Самому нравится :)
Расскажите это биологам с их иерархической систематикой.Ну, у них в данном случае есть хорошее обоснование в виде особенностей процесса, порождающего их многообразие. Я имею в виду эволюцию, распочковывающую генетические линии. Там действительно по факту получается дерево, и биологам лишь остаётся разобраться, что считать разделяющим признаком в первую очередь — какую-нибудь форму крылышек или какое-нибудь строение щетинок. По крайней мере, на многоклеточных такой фокус удаётся. Но всё равно, насколько мне известно, древовидная типизация бактерий — непроходимый кошмар, который, не исключено, что навсегда.
Сразу же классический кейс: окружность и эллипс. С одной стороны окружность — это эллипс, у которого совпадают фокусы. С другой стороны эллипс — это проекция окружности.
Это классический пример того, где наследование не надо применять.
(впрочем, и то, и другое — фигура)
Но в реальной жизни оно всё не похоже на такую красоту.
Вопрос того, как вы строите модель, вот и все. Скажем так, "в реальной жизни" достаточно много случаев, когда глубокие иерархии работают, а во всех остальных случаях просто никто не заставляет.
Ну, у них в данном случае есть хорошее обоснование в виде особенностей процесса, порождающего их многообразие
Ну вот видите, значит, не все иерархии содержат логические ошибки. Что, собственно, и требовалось доказать.
Скажем так, «в реальной жизни» достаточно много случаев, когда глубокие иерархии работаютИли нам кажется, что работают, потому что мы к ним привыкли и приучились их считать неизбежным злом. Но при внимательном рассмотрении оказывается, что зло это совсем не неизбежное, и мы просто попали в ситуацию выученной беспомощности.
а во всех остальных случаях просто никто не заставляетЕсли есть тру-ООПшная библиотека и её нужно заюзать, то мы просто вынуждены бываем рыться в куче жёстко фрагментированной логики, матерясь и понимая, что цимуса здесь процентов 10, а всё остальное — просто ООПшный шлак.
Ну вот видите, значит, не все иерархии содержат логические ошибки. Что, собственно, и требовалось доказать.В данном случае ошибкой является привязка к таксономическим признакам, в результате чего вполне рядовая ситуация, когда (гипотетический пример) во что-нибудь «щетинковое» приходится относить существа, напрочь лишённые щетинок. По-хорошему, конечно, нужно как-то (не понятно как) всё привязывать к генам, или даже не только к ним. В общем, пусть ребята сами разбираются. Нам своего геморроя хватает.
Моё утверждение «любая иерархия более двух уровней неизбежно содержит внутри себя логическую ошибку», конечно, можно (и нужно) фальсифицировать. Авось найдётся что-то действительно стопудово иерархическое, на фоне чего ущербность привычных древовидных конструкций будет ещё нагляднее.
Или нам кажется, что работают, потому что мы к ним привыкли
… а почему мы к ним привыкли?
Если есть тру-ООПшная библиотека и её нужно заюзать
Ну так возьмите другую, кто мешает-то?
В данном случае ошибкой является привязка к таксономическим признакам, в результате чего вполне рядовая ситуация
А (а) есть ли такая привязка и (б) ошибка ли это? Ведь если группировка есть, значит, она зачем-то нужна.
Что касается непосредственно описания предметной области, то основной инструмент для этого — алгоритмы, ну и соответственно методы, являющиеся их контейнерами. Ведь если говорить о наследовании, то объекты могут быть объединены через него довольно ограниченным количеством способов. Всё что мы можем таким образом сказать это то, что класс А является разновидностью класса Б. Что можно описать таким скудным словарём? Да практически ничего. Только самую основу, если иерархичность явно выражена. В свою очередь, метод может описывать отношение любого типа. Поэтому правильным подходом при проектировании является создание такой структуры классов и интерфейсов, которая хорошо сочетается с заданным набором методов. Если А и Б являются разными сущностями, но при этом они настолько похожи, что большинство наших методов могут работать с ними одинаково, то это наследование (хотя при масштабировании, скорее всего окажется что мы погорячились и надо было просто выделить интерфейс). Если сущности А и Б сильно отличаются, но есть сущность В, которая иногда может быть обработана как А, а иногда как Б, то следует выделить соответствующие интерфейсы. Если какому-то методу вообще фиолетово что за объект ему передан, но в процессе нам важно не потерять информацию о типе, то это шаблонный метод. И т.д. Везде основой для выбора является метод и его алгоритм. Наследование, интерфейсы, шаблоны — инструменты для повторного использования кода, но пока у нас нет самого кода, или хотя бы представления о том, каким он будет, мы не можем принять решение о том, какой из инструментов нам удобнее в каждом конкретном случае.
К тому же странно слышать критику ООП, основанную на критике механизма наследования. Наследование при ОО подходе — не главный приоритет. Главным приоритетом является инкапсуляция. А наследование, как раз, сильнее всего нарушает инкапсуляцию. Эти механизмы являются антагонистами.
Отношение «являться» это слишком сильная взаимосвязь. Можем ли мы утверждать что окружность является эллипсом? Наверное можем, с определённой натяжкой. И вот в этом основная ошибка — использовать наследование там, где его можно как-то притянуть за уши. Наследование можно применять только тогда, когда оно не подлежит сомнению. Даже в вашем примере с числами я бы поостерёгся. Целое число является рациональным, только если мы учитываем исключительно диапазон допустимых значений. Но у чисел могут быть и другие важные характеристики, например допустимые операции, или способ представления в памяти компьютера. В частности, целые числа являются перечислимым типом, а рациональные — нет. Это кстати вскрывает суть проблемы. Выстраивая иерархию классов мы проводим классификацию по определённым признакам. И как только возникает необходимость учитывать признак, на который мы изначально не закладывались, вся иерархия может быть разрушена. Это проблема всех иерархических моделей, в том числе и наследования.
В то же время, интерфейсы больше похожи на систему тегов. Тут с масштабируемостью и гибкостью всё гораздо лучше. Если вместо наследования использовать сочетание композиции и интерфейсов, огромное количество проблем сразу отпадает. Впрочем, совсем отказываться от наследования было бы неправильно, ведь не всё нам нужно масштабировать, иногда простота и скорость разработки важнее.
Необходимость постоянной перетряски иерархий объектов
За шесть лет один раз.
Постоянная перетряска иерархий — признак негодного первоначального замысла. Как говорится, если не умеешь пользоваться — это проблема не парадигмы.
Реальный мир ни из каких объектов не состоит.
А из чего он состоит? Из струн? А струна это не объект? Свойства у неё есть, поведение тоже. Значит объект. Да на самом деле и не важно из чего состоит мир. Важно как мы его воспринимаем. Какую абстракцию используем. При этом ни одна из абстракций в мозгу человека не соответствует реальности на 100%. Да и не важно какая абстракция больше соответствует. Важно какая из них удобнее в конкретной ситуации. Та же Ньютоновская физика (если уж вы про физику начали) оказалась лишь приближением Эйнштейновской, но при этом она используется до сих пор. Почему? Потому что просто и удобно, при этом во многих случаях точность более чем достаточная.
Веб-сервер не моделирует выдачу веб-страниц клиентам, а выдаёт их. Браузер не моделирует прорисовку страниц, а прорисовывает их.
Ни браузер, ни веб-сервер не делают ничего. Всю работу делает компьютер. А моделирует эту работу программист. Браузер и веб-сервер, как и все остальные программы — это всего лишь текст. План, в котором сказано что должен делать компьютер, чтобы выдать веб-страницу, или прорисовать её. То есть, по сути, это модель действий компьютера в той или иной ситуации.
К тому же, я не понимаю, почему вы, говоря о моделировании, выделили именно ООП, мол оно специально для моделирования. Ведь моделирование это некоторое описание предмета или процесса. А функциональный и декларативный подходы разве хуже подходят для описания? Отнюдь, они являются описанием в чистом виде.
Описывать окружающий мир, и моделировать его можно и тем и другим способом. Даже Вселенная с этим согласна, вот например фотон, в зависимости от ситуации может вести себя и как волновая функция, и как объект-корпускуляр. Получается объектно-функциональный дуализм. Если продолжить развивать эту мысль, то можно дойти до волновых функций высшего порядка.
А из чего он состоит?Ни из чего. Нет у него встроенной опции «состоять из...». Любое деление на объекты — результат счастливого сочетания следующих двух вещей:
1. (естественно) Пригодности обозреваемого куска (или аспекта) реальности к тому, чтобы на нём мы смогли выделить отдельные объекты. Если говорить о материальном мире, то предметы в твёрдом агрегатном состоянии в этом плане удобны, а с жидкостями и газами приходится изворачиваться с разной степенью успешности. С нематериальными объектами тоже бывает по-разному.
2. Способностей субъекта и особенностей той ситуации, в которой он в данный конкретный момент находится. Мой любимый пример — стакан воды. Когда мы говорим «принеси стакан воды», мы имеем в виду ёмкость+жидкость, а когда говорим «выпей стакан воды», мы имеем в виду только жидкость.
Применительно к ООП ситуация выглядит драматично. Допустим, сегодня мы твёрдо уверены, что стаканы воды — это всегда с ёмкостью, и колбасим логику исходя именно из такого способа выделения объекта из реальности. А завтра оказывается, что нам нужно запрограммировать логику ситуации «в желудке 2 стакана воды». А у нас стеклянный стакан ну никак не пролазит через пищевод :))
Никакое разделение мира на объекты не является окончательным и единственно правильным. А мы мало того, что на это закладываемся, так ещё и придумываем себе на свою голову отягчающие обстоятельства в виде иерархий классов.
Ни браузер, ни веб-сервер не делают ничего. Всю работу делает компьютер.В некотором смысле это правда, но правда совершенно никчёмная. Что с ней делать? Что она нам даёт? Мы в своих делах никогда не имеем дело с той аппаратурой, которая непосредственно выполняет наш код. Вы имеете физический доступ к тем узлам процессора, на которых исполняется Ваш код? Я — точно нет. Я всегда имею дело с буковками на экране.
Где проверяется пользовательский ввод? Да вот здесь, в функции CheckUserInput. Где ошибка, завалившая сервак? Да вот здесь, в запросе, в соединении таблиц забыли вписать доп. условие и получили, по сути, cross join. Не знаю как кто, но обычно я даже примерно себе не представляю, где физически находятся те компьютеры, которые «делают всю работу». Где-то на планете Земля. Это точно. Для космических аппаратов я ещё ничего не программил.
Ни из чего.
Ну давайте придумаем парадигму: «Ни на что ориентированное программирование» («Nothing Oriented Programming»). Основным постулатом будет «Всё есть ничто». Думаю из всех языков, whitespace наиболее близок к её реализации.
Собственно я согласен с вами, что нет единственно верного деления на объекты. Но это если говорить в общефилософском смысле. А если применительно к конкретному ТЗ и предметной области, то можно подобрать один из наиболее удобных вариантов. Плюс можно добавить абстрактности, и совместить так несколько возможных схем деления. Как я и написал выше, не важно из чего мир состоит, важно как мы его воспринимаем, или как нам удобно его воспринимать в конкретной ситуации. Человек мыслит с помощью сравнения. А сравнивать удобно дискретные сущности. Чтобы их сравнивать — нужно выделить их свойства. Вот вам и объекты. Отсюда они и берутся. А результатом сравнения является классификация по ряду признаков. Вот вам и классы. Объектный подход не отражает реальность, он отражает наш способ восприятия этой реальности.
Что касается вашего примера со стаканами, то там у вас требования изменились. Изначально нам ничего не известно о стаканах воды в желудке, а потом вдруг оказывается что надо это запрограммировать. Естественно, если в своей архитектуре мы не предусмотрели какой-то задел на будущее, то нам придётся архитектуру переделывать. Но то же самое можно сказать и про простейшую программу состоящую из одной процедуры. Если процедуру захотели применить для случая, который изначально не оговаривался, и она оказалась недостаточно абстрактна, то её придётся переписывать. Написали мы «Hello world», а теперь заказчик хочет чтобы программа писала не только «Hello», но и другие приветствия в зависимости от времени суток, типа «Good morning». И не только по отношению к миру, но и обращаясь к пользователю по имени, переданному в параметре. То есть тут проблема фундаментальная. Я даже не представляю какой должна быть парадигма, чтобы при изменении требований мы были полностью застрахованы от переписывания кода. Хотя, если расширить принцип «Всё есть ничто», и считать что и требования заказчика — ничто, и зарплата, и работа тоже. Тогда может и прокатить.
В некотором смысле это правда, но правда совершенно никчёмная.
Мы в своих делах никогда не имеем дело с той аппаратурой, которая непосредственно выполняет наш код.
Вы меня тут не совсем правильно поняли. Суть не в аппаратуре. И вообще не в том, кто конкретно выполняет программу. В случае с интерпретируемым языком, например, вполне можно остановиться на уровне интерпретатора. Он выполняет то что мы написали. Суть в том, что программу кто-то выполняет, она не выполняется сама. И как правило мы не пишем просто последовательный код в духе: сначала выведи на экран это, а потом вон то, а после завершись. Мы описываем различные ситуации которые могут произойти в процессе выполнения, и что в этих ситациях надо делать. При этом далеко не факт что все эти ситуации возникнут, даже наоборот они не могут возникнуть все, т.к. многие из них взаимоисключают друг друга. Так что программа это модель. Исполнитель берёт эту модель и действует в соответствии с ней. Это не совсем то, что вы подразумевали под моделированием — имитация процессов и сущностей из реального мира. Я это понимаю. Лишь хотел подчеркнуть, что написание практически любой программы не отличается от написания программы моделирующей, скажем, физические процессы. Просто, в одном случае мы будем воплощать в коде модель физического мира, а в другом — модель которую сами придумали и держим в своей голове. И там, и там мы будем описывать в коде все сущности, задействованные в процессе, и возможные варианты их взаимодействия, которые хотим учесть. Объектный подход может быть одинаково удобен в обоих случаях. Как, впрочем, и функциональный, и какой-нибудь ещё. Любой подход будет давать одинаковые результаты в обоих случаях. Ведь разницы между моделированием реального процесса и придуманного, на этом уровне нет.
И вот мы уже выделяем ирерахию объектов в алгоритмах решения конкретной ТЗ, и не пытаемся натянуть объекты на весь наблюдаемый мир. О том и речь, нет никаких объектов в объективной реальности, они появляются только когда мы начинаем анализировать конкретную задачу. И в зависимости от задачи объекты выделяются по-разному.
Веб-сервер не моделирует выдачу веб-страниц клиентам, а выдаёт их. Браузер не моделирует прорисовку страниц, а прорисовывает их.
Это конкретный выполняемый процесс в операционой системе выдает страницы или прорисовывает их. А в коде программы содержится модель этих процессов. И объектов, в них участвующих. Потому что в момент написания программного кода никакой выдачи веб-страниц не происходит. Нужно смоделировать этот процесс, который произойдет в будущем. Именно поэтому в программе надо учесть все возможные ошибки — потому что модель одна, а выполняться она будет многократно.
Нетрадиционная ориентация ООП