Как стать автором
Обновить

Комментарии 473

Аналогия класс-чертеж, объект-изделие действительно намного лучше, чем попытки объяснить что "коты вообще это класс, а Матроскин — инстанс".


Как то странно объяснять абстрактные классы до того, как объяснены методы и понятие инстанса класса.


Далее возможные вопросы от слушателей:


  1. Зачем нужно ооп?
  2. Как вот это все переносится на код?
  3. А я еще слышал термины "инкапсуляция", "абстракция", "поле (field) класса". Что это?
… а когда внимательные слушатели спросят:

4. Почему это конструкторское бюро Боинга поставило заказчику чертежи CRJ-200 Bombardier?

заодно объяснить, что копирование готового кода со stackoverflow является хоть и распространённой, но не всегда верной практикой.

Ну тут как раз легче чем с котиками. Скопировав чертеж один в один мы не сможем воспроизвести его или часть потом. Соответственно, такой инженер будет стоить дешевле чем инженер, который понимает и сможет сделать такой самолёт сам.

Пока вы смеетесь, уже придумали название (example-centric programming, иногда opportunistic programming), пишут научные статьи и разрабатывают инструменты, поддерживающие этот подход :)

Using the web is integral to an opportunistic approach to programming when focusing on speed and ease of development over code robustness and maintainability.
По моему личному опыту объяснение вида «класс — шаблон для объекта» для новичков — плохое, лучше условно разделить все преимущества и сразу объяснять, зачем нужна та или иная вещь.
можно смело сказить что ООП это формальная модель.
как в математике система доказательств и терминология.
помогает более формально думать
А что означает «ВВП полосу»? Может ВПП? Тогда слово «полосу» лишнее. В Целом, идея интересная. Но вот я, к примеру, плохо мыслю абстракциями. Скажите, а вы могли бы попробовать написать сюда пример кода (скажем, на той-же Java), который бы вписывался в вашу концепцию пояснений? Мне вот, как новичку, гораздо проще было бы читать пояснения и видеть код.
Полностью согласен. Я тоже новичок и скажу даже проще — я из объяснений статьи понял совсем мало. Т.е. то, что уже понял до этого — понимание не углубилось, не стало ярче и крепче (инстанс, интерфейс), а то, что не понимал, не понял и сейчас (композиция, ассоциация).

Автору всё равно спасибо, мне кажется, если расширить пояснения с одного абзаца до одного раздела с несколькими абзацами, с небольшими примерами кода и теми же абстрактными примерами «из жизни КБ Боинг», эта заметка могла бы реально стать «путеводной звездой».
НЛО прилетело и опубликовало эту надпись здесь
В данном случае необходимо вводить интерфейс СовместимыйДвигатель.

class PassengerAirCraft extends AirCraft {
    /**
     * При проектировании указывается совместимость с двигателями
     */
    private EngineInterface $engine;
    /**
     * Конструктор это и есть наш сборочный конвейер,
     * который получает в требованиях модель двигателя
     * @param $engineModel
     */
    public function __construct($engineModel) {
        /**
         * Фабрика поставляет нам необходимую модель двигателя
         */
        $this->engine = EngineFactory::provide($engineModel);
    }
}

Одна проблема — объяснить что это. Другая — объяснить зачем всё это вообще нужно.
Совершенно верно, люди не понимают что такое ООП, потому что они начинают с небольших проектов, в которых у них и так всё по полочкам, но им говорят, ваш проект вырастет, делайте сразу хорошо, они долго думают, как это должно быть, что бы было правильно в ООП, а как только проект начинает расти, внезапно вся «раскладка» на ООП, становится другой, а потом ещё раз… И тогда, начинается искреннее недопонимание, почему изначальный ООП код не развивается, а мешает развиваться, причём все говорят, что вы сделали неправильно «первую раскладку», вы не понимаете ООП. Вот, последнее непонимание и важно, а не как сделать в первый раз. И поверьте, ваши «чертежи» и самолёты в этом никак не помогут.

Хм, можно попробовать показать именно пользу ООП с популярного проекта, где этот ООП реально нужен. Например, существующей UI библиотеки. А чтобы студенты не отлынивали, надо дать лабораторную на реальное использование библиотеки, с обязательным указанием в отчёте, какие принципы ООП где помогли.
А со своим кодом да, проблема, в учебных задачах обширное дерево наследования делает код хуже.

Хм… а как насчет ООП в случаи, когда классов нет? То же прототипно-ориентированное (пример, javascript).
Или вот есть пример Golang, есть ли там ООП или нет?

ООП — одна из разновидностей композиции (проектирование) систем.

В JS классы в принципе не нужны (их можно выкинуть или вообще не использовать) — если вам они нужны и нужно больше чем есть в стандарте JS то вы можете использовать… стороннюю библиотеку JS, которая позволяет вам организовать ООП и прочее гораздо круче и разнообразнее чем во всех языках где ООП объявлено на стадии описания самого языка.

Развитие ООП в JS смысла не имеет вовсе. Ну, если только — да, у нас не ахти какое ООП но пусть будет хоть такое — может кому и такое может хватить по жизни его (в разработке его), — ну, а если не хватит, то уж тогда использовать стороннюю библиотеку js и там уж и оттянуться с ООП уж так как никому (другим языкам с ООП) и не снилось.

Это я к тому, что ООП — это ООП, а классо-ориентированное программирование всего лишь подвид ООП.


В JS ооп есть, а классов нет (не было). Вот так вот это работает.

ООП — это ООП, а в JS ОП.
JS как бы мультипарадигменный язык и там можно применять ООП, а можно и не применять. Как в Python или C++.
Простите, а что такое ОП? Объектное программирование? Сами придумали?

Частным случаем ООП является «Прототипное программирование». Оно и есть в JS.

Объектное программирование — подмножество объектно-ориентированного без наследования.


Прототипное программирование — пересекающееся множество, где вместо наследования возможность создавать одни объекты на базе других.

Прототипы — это разновидность наследования, а не альтернативный механизм.

Не все источники с этим согласны.

вы можете использовать… стороннюю библиотеку JS, которая позволяет вам организовать ООП и прочее гораздо круче и разнообразнее чем во всех языках где ООП объявлено на стадии описания самого языка

А что за библиотека?

Я ещё не гуглил, но вангую class.js object.js или oop.js :) Если какая-то библиотека может быть написана на JS, она будет написана на JS :)

LionZXY
Я ещё не гуглил, но вангую class.js object.js или oop.js
Почти, но нет! ;-)

«Благодаря силе прототипов JavaScript получился на редкость гибким. На волне вдохновения разработчики создали огромное количество библиотек со своими собственными объектными моделями. Популярная библиотека Stampit выжимает из прототипной системы всё возможное для того, чтобы манипулировать объектами так, как это невозможно в традиционных языках, базирующихся на классах.»

Ну и сама Stampit и примеры.

И вот как быть дальше с ООП в JS, "втянуть в себя", в стандарт JS библиотеку Stampit? — Но как?
JS — не модульный — в том смысле что в нём нет «пакетов» как в Java (в которой, к примеру, при обновлении версии Java, «втянули» пакет стороннего разработчика для удобного использования программирования потоков, который(пакет) фактически ничего не изменил на нижнем уровне (Thread.suspend(), Thread.resume() и прочие), но оказался удобен).

Для JS есть стандартная библиотека, как минимум. Не считая API типа WebSockets

Для JS есть стандартная библиотека, как минимум.
Не понял вас. В JS вообще нет такого понятия как «стандартная библиотека» — Понятие «стандартная библиотека» это из мира C, С++, Java и прочих.
НЛО прилетело и опубликовало эту надпись здесь
staticlab
Понятия «стандартная библиотека» нет, а Standard Build-in Objects есть.

Понимаете в «Standard Build-in Objects» нет ни намёка на слово «library» или «file».

Ну нет такого понятия как «стандартная библиотека» в JS, хоть плач!
НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
staticlab
Ну и что? Я знаю, что вы любитель придираться именно к словам, но какая разница?
Так я же выше пояснил, так как в JS нет стандартных библиотек, так что запросто добавить в JS какую-нибудь стандартную библиотеку нельзя! Хоть плачь.


Кроме того, выделенное вами слово (у вас на выделениях какая-то нездоровая фиксация)
Не обращайте внимание, это я для того чтобы расставить ударение!

следует писать с мягким знаком.
Вот тут правы! И только тут. (Виноват, путаюсь, ибо "ч" в белорусском всегда твёрдая и после неё никаких мягких — никакого смягчения, никакой мягкости, только жёсткость одна, только.)

НЛО прилетело и опубликовало эту надпись здесь
staticlab
Пишите proposal к стандарту, продвигайте его в TC39, и будет вам счастье.
Написано, не принято. И это хорошо! :-)

ECMAScript 4
В самый разгар разработки ECMAScript 4 включал такие опции как:

Классы
Интерфейсы
Пространства имён
Пакеты
Опциональные аннотации типов
Опциональная статическая проверка типов
Структурные типы
Объявления типов
Мультиметоды
Параметризованные типы
Хвостовые рекурсии
Итераторы
Генераторы
Интроспекция
Разбор типа для обработчиков исключений
Связывание констант
Правильный обзор блоков
Деструктуризация
Сжатые функциональные выражения
Поддержка массивов



Однако, Stampit выглядит как надстройка над стандартным прототипным ООП, а потому в таких случаях правильнее будет перенести в стандарт непосредственно идеи этой библиотеки, а не тащить «как есть». То есть, возможно, это будут некоторые новые методы класса Object.
Вряд ли нужно тащить даже идеи этой библиотеки — она же есть и развивается сама по себе независимо от новых фич JS.

Аналогично и по идеям из иных библиотек, типа Underscore.js и прочих и прочих…

Зачем всё это тащить в стандарты JS — цель? — единственная разумная цель — тащить то, что может эффективно реализовано в NATIVE функциях JS-движков, используемых в броузерах и средах типа Node.js

Ну, а стандартных библиотек в JS нет. (С)
НЛО прилетело и опубликовало эту надпись здесь
staticlab
The ECMAScript library of built-ins was expanded to support additional data abstractions including maps, sets, and arrays of binary numeric values…
Точно, определить что стандартная библиотека — это не файл или не набор битов, который централизованно поставляется создателем (или дистрибутером) библиотеки, а это описание стандарта! — Это круто!

Но что нам говорит Википедия?
Стандартная библиотека языка программирования — набор модулей, классов, объектов, констант, глобальных переменных, шаблонов, макросов, функций и процедур, доступных для вызова из любой программы, написанной на этом языке и присутствующих во всех реализациях языка.
Да и не важно что тут нет слова ОПИСАНИЕ, а его нет, так как из ОПИСАНИЯ стандарта нельзя ничего вызвать из «любой программы, написанной на этом языке и присутствующих во всех реализациях языка.»

Определение — сила. Определите что ваши пожелания есть библиотека — и вы… на коне! ;-)
НЛО прилетело и опубликовало эту надпись здесь
staticlab
Какое ещё описание стандарта?

Обычное, на их сайте размещённое: This Ecma Standard defines the ECMAScript 2015 Language. It is the sixth edition of the ECMAScript Language Specification.

А вы бред про «The ECMAScript library...» где изволили откопать то?

НЛО прилетело и опубликовало эту надпись здесь
staticlab
«Данный стандарт Ecma описывает язык ECMAScript 2015. Это шестая редакция спецификации языка ECMAScript.»

Нет, не так:
«Данный стандарт Ecma описывает [спецификацию] язык[a] ECMAScript 2015. Это шестая редакция спецификации языка ECMAScript.»


staticlab
Я же дал ссылку.

Точно, ссылка ведёт на сайт www.ecma-international.org

4.4Organization of This Specification#

Clauses 17-26 define the ECMAScript standard library. It includes the definitions of all of the standard objects that are available for use by ECMAScript programs as they execute.

the definitions — определение, описание.

То есть если распечатать по функциям эту ECMAScript standard library — и каждую распечатку в коленкор и на полку то и будет вам ECMAScript standard library!

Ну, а стандартных библиотек, которые вы могли бы скачать откуда-нибудь и непосредственно использовать в своём коде в JS нет. (С)
НЛО прилетело и опубликовало эту надпись здесь
Кстати, если на то пошло, то некоторые части этой библиотеки вполне поддаются скачиванию… Например, в виде пакета core-js.
staticlab
Зачем её качать отдельно, если она стандартная?
Чтобы использовать.

staticlab
И множество классов из неё по определению есть «во всех реализациях языка»?
Этого не требуется по определению.

@staticlab
Согласен, для такого довольно низкоуровневого языка как C++ возможна независимая стандартная библиотека, но в большинстве случаев её реализация привязана к среде исполнения.
Но вы можете все их скачать из одного источника. — В случае JS скачивать нечего и негде.

mayorovp
если на то пошло, то некоторые части этой библиотеки вполне поддаются скачиванию… Например, в виде пакета core-js.
Это не стандартая библиотека JS и не часть её — это набор полифилов — то есть "временных костылей" для некоторых функций (методов).

Стандартных библиотек в JS нет. (С)
НЛО прилетело и опубликовало эту надпись здесь
staticlab
Реализации стандартных библиотек для движков V8 и SpiderMonkey скачать можно, но они написаны на C++.
Нет, неверно написано вами, более полно и правильно надо писать так:

Библиотеки, реализующее некоторые элементы стандарта JS для движков V8 и SpiderMonkey, скачать можно.

Стандартных библиотек в JS нет. (С)
НЛО прилетело и опубликовало эту надпись здесь
То есть с остальным вы не спорите, но всё равно упираетесь?

Мы начали с Стандартных библиотек в JS нет. (С)
И закончили Стандартных библиотек в JS нет. (С)

Всяческие попытки это опротестовать оказались (имхо) никчемными или отвлекающими. Не более того.

НЛО прилетело и опубликовало эту надпись здесь
Вашим тролльным способом можно доказать всё, что угодно.
Не будем развешивать ярлыки.
Вот, к примеру урл на стандартные Java библиотеки. Качайте и работайте.

Теперь — раз вы утверждаете что Стандартных библиотек есть в JS, то приведите мне урл где я их могу скачать.

Если такого урла нет — вы троль. (С)

Мимо проходил

Теперь — раз вы утверждаете что Стандартных библиотек есть в JS, то приведите мне урл где я их могу скачать.

нате
Спойлер
Да, это ссылка на скачивания гугла хром, но как виртуальная машина Java это намного больше, чем просто стандартная библиотека Java (какому-нибудь JPython'у может быть нафиг не нужна эта библиотека), так и хром поддерживает определенный стандарт JS. Не вижу никакой разницы в данном случае. И да, между прочим реализаций Java тоже несколько.

В случае JS скачивать нечего и негде.

Качайте:
1. браузеры,
2. nodeJs и т.п.

И там и там есть своя реализация стандарта JS (разная реализация во многом, но Java реализации J# в Net, Java в андроиде, у Oracle и OpenJDK тоже совсем не одинаковые)
vedenin1980
Да, это ссылка на скачивания гугла хром, но как виртуальная машина Java это намного больше, чем просто стандартная библиотека Java (какому-нибудь JPython'у может быть нафиг не нужна эта библиотека), так и хром поддерживает определенный стандарт JS. Не вижу никакой разницы в данном случае.

Вот и ссылка появилась на… браузер хрома. :-(

Меня один уверял, что между Java и JavaScript разницы он не видит. По крайней мере четыре первые буквы совпадают и оператор «new» там и там есть! ;-)

vedenin1980
В случае JS скачивать нечего и негде.
— А вот staticlab как-то сомневается в этом.

vedenin1980
Качайте:
1. браузеры,
2. nodeJs и т.п.

И там и там есть своя реализация стандарта JS (разная реализация во многом, но Java реализации J# в Net, Java в андроиде, у Oracle и OpenJDK тоже совсем не одинаковые)

Есть некая разница между «разными (и частичными) реализациями стандарта» и «отсутствием стандартной библиотеки вообще»! — Многие её не улавливают. А она… есть. :-)
Блин, придется удалять весь код теперь. Было так удобно писать поддерживаемый и читаемый код, но вы открыли мне глаза! Спасибо!

Не то чтоб я особенно любил ООП в JS, но в аббревиатуре ООП классы даже не упоминаются
— речь об объектах, которые несут данные и методы для работы с этими данными. Инкапсуляция, наследование и полиморфизм тоже не подразумевают классов.
Это просто самый распространенный способ воплощения этой парадигмы (может быть и самый лучший, но не обязательно).


Кстати, на прототипах классы эмулируются на ура, а как будет выглядеть попытка воспроизвести прототипное наследование на Java, мне даже представить страшно.

ООП — оно не в языке, а в голове разработчика.

Добавлю: ООП широко применяется в ядре Linux и "оконной" части WinAPI несмотря на полное отсутствие языковой поддержки.

Не не полное — есть структуры и в них можно хранить указатели на функции :)

НЛО прилетело и опубликовало эту надпись здесь
Не совсем. Указатели — это виртуальные методы. Обычные — это просто функции с неявным this-аргументом, возможно, перегруженные. А ещё правила каста указателей при наследовании, и это важнее всего остального по сути.
НЛО прилетело и опубликовало эту надпись здесь
Я немного не о том.
Вызов метода предполагает передачу this. Указатель на функцию, которая не примет объекта в качестве параметра, нельзя считать вызовом метода, потому что она не имеет контекста (this).

Что мешает в простом C передавать первым параметром указатель на структуру?

Ничего. Но тогда нет необходимости таскать за собой и указатель на функцию, если нет задачи сделать её виртуальной.

В Javascript классы есть, причем начиная с ES2015 даже на уровне синтаксиса. Чего там и правда нет — так это переопределения членов, когда в базовом классе this.foo это одно, а в наследнике this.foo это что-то другое и друг с другом они никак не связаны. Но на уровне "чертежей и самолетов" никаких отличий между Javascript-классами, С++-классами и Java-классами не наблюдается (за исключением того факта что в Javascript можно "унаследовать" не только чертеж, но готовое изделие — однако "можно" не означает что так нужно делать).

Не знаю, как сделаны классы в ES2015, но до него обычно это были костыли-обертки над прототипным наследованием.

Ну и да, вы бы хотя бы привели в пример тот же Python, потому что различия между классами в JavaScript и C++ катастрофические. Например, нет ограничения доступа для переменных.

Ограничения доступа — прежде всего в голове у программиста. Без них он напишет #define private public и будет радоваться как круто он все устроил. Или применит заклинание Звезды Пустоты. Или напишет Паблика Морозова...


Что же до костылей — то костыль был только один:


    function temp() {}
    temp.prototype = Foo.prototype;
    Bar.prototype = new temp();

Потом в ES5 его внесли в стандартную библиотеку, обозвав Object.create. Все остальное — просто реализация ООП.

По мне как раз возможность ограничения доступа на уровне языка важна, ведь по сути без ограничений доступа только по коду непонятно будет где интерфейс, а где реализация. Наличие и соблюдение контрактов важно тем что, имхо: 1) снижает сложность системы за счет вынесения в паблик только специально предназначенных для этого вещей; 2) без костылей в виде комментариев или памяти программиста с ходу позволяет использовать параметрический полиморфизм над объектами с одинаковым контрактом; 3) облегчает инструментальное взаимодействие с кодом и использование той же контекстной подсказки.

Важна она как средство принудительного дисциплинирования прежде всего.

Ну, как по мне наоборот расслабляет когда можешь рассчитывать на интерфейс модуля зная что вряд ли нарвешься на временную связность например при использовании его процедур и функций, или что все нужные зависимости у него установлены (либо будут получены во время вызова метода откуда нибудь) а также то что публичные методы писались хоть более менее в расчете на то что будут использоваться без понимания деталей их работы, тогда как при написании приватных можно сделать больше допущений о параметрах и вариантах поведения и дать абстракции немного «протечь»

Вас расслабляет, что разработчики модуля соблюдали дисциплину :)

Что то загнул с параметрическим полиморфизмом, в голове что то вывернулось. Любой конечно же.
Да, контракты приходится хранить где-то еще. В документации, например. И я тоже считаю что это недостаток языка.

Но отсутствие контрактов не означает отсутствия классов или отсутствия ООП.
Но отсутствие контрактов не означает отсутствия классов или отсутствия ООП.

Безусловно, но тут больше обсуждается значимость наличия или отсутствия ограничений доступа. По крайней мере я не согласен конкретно с высказыванием:
Ограничения доступа — прежде всего в голове у программиста.

Я как раз считаю что программист не должен помнить контракты для своих классов и предугадывать для чужих, а для этого должны использоваться средства языка.
Вместо средств языка можно использовать соглашения об именовании. Например, подчеркивание в начале имени означает приватное свойство.
Но это по сути уже эмуляция средств языка путем соглашений, не лучше ли непосредственно определять интерфейс чем добавлять подчеркивания в имена свойств и методов? Конкретный пример приведенный плох еще тем что по умолчанию подразумевается что все методы и свойства публичны, что не есть хорошо.

По сути это ограничение видимости средствами языка имплементация соглашений на уровне синтаксиса, а не наоборот.

В Python, кстати, тоже нет ограничения доступа для полей. И ничего, никто не умер.

В ассемблере еще меньше ограничений, и никто от этого не умирает :)

Вот не факт. Думаю, есть отрицательная корреляция между уровнем языка и количества смертей от ошибок программ на этих языках.

На самом деле небольшие есть) Но это не важно, как бы, я больше возражал фразе «классы в javascript почти такие же, как в C++».
Вообще-то в js классов нет и не было. То что вы называете классами в ES2015 — это синтаксический сахар, под капотом в себе кроет старые добрые прототипы.
НЛО прилетело и опубликовало эту надпись здесь
Чего там и правда нет — так это переопределения членов, когда в базовом классе this.foo это одно, а в наследнике this.foo

Как же нет? Есть даже ключевое слово super, чтобы из наследника вызывать метод родителя:


class A {
    foo() {
        console.log('A.foo()');
    }
}

class B extends A {
    foo() {
        console.log('B.foo()');
        super.foo();
    }
}

(new B).foo();

А теперь попробуйте отключить полиморфизм, чтобы получить доступ к foo() из класса A имея объект класса B :-)

Позднее связывание имеете в виду или что?

class A {
    name = "world";

    hello() {
        return $"Hello, {this.name}!";
    }
}
class B extends A {
    name = "B";

    who() {
        return $"I am {this.name}";
    }
}

var b = new B();
console.log(b.hello()); // Ну и куда тут надо было super засунуть? :-)
class A {
    name = "world";

    hello() {
        return $"Hello, {this.name}!";
    }
}
class B extends A {
    constructor() {
      	super(); // В конструктор? Хотя вызов работает и без него.
    }
  
    name = "B";

    who() {
        return $"I am {this.name}";
    }
}

var b = new B();
console.log(b.hello()); // Ну и куда тут надо было super засунуть? :-)
Да где работает-то? Если исправить синтаксические ошибки, то в консоль код выведет «Hello, B!». А надо было — «Hello, world!».

Да, вот исправленный неработающий код:

class A {
    constructor() {
    	this.name = "world";
    }

    hello() {
        return `Hello, ${this.name}!`;
    }
}
class B extends A {
    constructor() {
        super();
        this.name = "B";
    }
  
    who() {
        return `I am ${this.name}`;
    }
}

var b = new B();
console.log(b.hello()); // Ну и куда тут надо было super засунуть? :-)
В Golang есть все возможности ООП, хотя и под несколько непривычным соусом. Структуры + методы структур = класс, есть интерфейсы, есть инкапсуляция, нет наследования, но есть композиция, которая даёт практически те же возможности( в терминах статьи, например, ЧертёжПассажирскогоСамолёта включал бы в себя ЧертёжСамолёта в виде композиции, по сути наследуя с возможностью переопределения все его методы). Тут на хабре было несколько хороших статей на эту тему.

Проблема в том что сами авторы Go как бы не уверены. Поэтому все про это и спорят.

Ну собственно, всё зависит от того, какое определение ООП брать. На мой взгляд, Go реализует нужные возможности, называя их по другому. Но поскольку мнений о том, что считать ООП — дюже много, некоторым из определений Go не соответствует.

Читал и думал: как хорошо быть старпёром, который учился программировать, когда про ООП ещё толком не слышали, и все споры были "сверху вверх" vs "снизу вверх".
В итоге, когда оно пришло в наш кишлак, все вопросы были — "а как оно устроено". Прочитав про VMT — успокоился и вопросов больше не имел, пока не столкнулся с множественным наследованием в C++ — ибо не понимал, как оно сделано (кстати, убедился, что мои вопросы были обоснованы, когда последующие языки забанили множественное наследование от классов, разрешив только от интерфейсов и от микс-инов, это насквозь понятно).


Единственная проблема — при виде 15 слоёв абстракции начинаешь поминать "Яву головного мозга".

Угу, как старпер, подтверждаю.

Мне все больше и больше нравится мнение моего бывшего коллеги "ООП – неизменно стабильный результат"

Цитата для затравки
Учебники по ООП полны примеров, как легко и красиво решается задачка отображения геометрических фигур на холсте с одним абстрактным предком и виртуальной функцией показа. Но стоит применить такой подход к объектам реального мира, как возникнет необходимость во множественном наследовании от сотни разношёрстных абстрактных заготовок. Объект «книга» в приложении для библиотеки должен обладать свойствами «абстрактного печатного издания», в магазине – «абстрактного товара», в музее – «абстрактного экспоната», в редакции, типографии, в службе доставки… Можете продолжить сами.

Цитата — классическая подмена понятий: проблемы архитектуры, построенной на наследовании, выдаются за проблемы ООП. При этом, как я уже в соседнем комментарии написал, наследование в ООП вообще необязательно. Да и в контексте языков, поддерживающих наследование, общеизвестен принцип "Composition over inheritance", и чуть менее общеизвестен, но тоже неплох, принцип "Abstract or final".

Давайте не будем спорить о терминах, а возьмем их из словаря. В общеупотребительном определении ООП наследование является обязательным признаком. Если у вас есть иное авторитетное определение — дайте на него ссылку.

А то, что вы описали в своем комментарии обычно называется модульным программированием.
Ну примерно как мнение братьев Черепановых относительно современного локомотива. :-) Ещё больше беды в том, что мнение не является определением.

Но спорить о терминах не буду. Если вы найдете словарь или стандарт с устраивающим вас определением — пользуйтесь им.

Смешной факт
Тот, кто придумал слово ВУЗ, был твердо уверен, что ВУЗ — заведение, то есть женского рода. Но увы, русский язык решил иначе. Примерно так же и с ООП — большинство людей под ним имеют ввиду нечто с виртуальным наследованием.

Ещё можете посмотреть, что значили разные ругательства в тот момент, когда эти слова впервые появились в языке. Очень много интересного для себя откроете.

Строгого определения ООП автор Smalltalk не давал (впрочем, то, что он пишет в конце своего письма, вполне можно считать определением). Как, впрочем, такого определения и не давали авторы языка Simula. Оба языка при этом примерно одновременно ввели термин "объект". Соответственно, можно говорить о двух "школах" — "Смоллтолковской" и "Симуловской".


Появившийся позднее язык С++ был явным последователем Simula-школы, и именно в те времена — благодаря тому, что С++ был долгое время самым популярным объектно-ориентированным языком — в массовом сознании закрепился сформулированной Страуструпом триплет "инкапсуляция-последование-полиморфизм" и стал считаться чем-то вроде определения. Примерно в то же время появился и другой основанный на C язык, известный в то время в основном только немногочисленным обладателям компьютеров NeXT, следовавший принципам Smalltalk… :-)


Что касается определения.


Во-первых, предлагаю смотреть не в википедию курильщика, а в википедию здорового человека — то есть, в английскую. Никакого упоминания необходимости и достаточности свойств из того самого "триплета Страуструпа" вы там не найдете: они, несомненно, перечислены, но только в общем ряду других свойств некоторых объектно-ориентированных языков.


Во-вторых, сравним высказывание Алана Кея и Страуструповский триплет. Вот что пишет Алан Кей:


OOP to me means only messaging, local retention and protection and hiding of state-process, and extreme late-binding of all things.

Вызов метода можно считать частным случаем Messaging. "local retention and protection and hiding of state-process" — по сути, инкапсуляция. "extreme late-binding of all things" — по сути, полиморфизм.


Итого, общими являются инкапсуляция и полиморфизм. Наследования у Кея нет.


Более поздний принцип проектирования объектно-ориентированных программ, высказанный применительно к языкам Симула-школы, гласит: предпочитайте композицию наследованию. С этим принципом согласно подавляющее большинство специалистов по проектированию ПО: Мартин Фаулер, Джошуа Блох, Эрик Эванс… Все они рекомендуют по возможности избегать наследования в тех языках, которые наследование реализуют.


Логичный вывод: наследование не является обязательным признаком ООП-языка.

Вам очень хочется спорить о смысле слов? Ну спорьте, если найдете себе оппонента. Вы не понимаете главного — у слов нету одного единственного верного определения. А как только вы это поймете — вы придете к выводу, что никакого смысла в этом споре нет.

Даже законах, где термины максимально стандартизованы, есть разночтения. По УК "Несовершеннолетними признаются лица, которым ко времени совершения преступления исполнилось четырнадцать, но не исполнилось восемнадцати лет", а ГК подразумевает, что несовершеннолетним может быть и меньше 14 лет.

И ваша попытка поспорить о смысле терминов столь же бессмысленна, как и спор о том, возникает ли несовершеннолетие в 14 лет или нет.

То есть в смысле каких-то определений — вы правы, а в смысле других — нет.

Во-первых, предлагаю смотреть не в википедию курильщика, а в википедию здорового человека — то есть, в английскую.

Просите, вы определение какого термина ищите? Object-oriented programming или ООП? Очень многие слова при переводе меняют свой смысл. Так что отсылка к английской вики просто некорректна.

P.S. Строго по определению УК — если лицо 16 лет преступления не совершало, оно несовершеннолетним не является. :-) Хоть стой, хоть падай — но определение ровно такое.
Просите, вы определение какого термина ищите? Object-oriented programming или ООП? Очень многие слова при переводе меняют свой смысл. Так что отсылка к английской вики просто некорректна.

То есть, вы хотите сказать, что термин "Object-oriented programming" приобрел определение, соответствующее Страуструповскому триплету, только в русскоязычной традиции? Окей, я бы мог с этим поспорить, но не буду — пусть будет так, я ради смеха даже соглашусь, чтобы положить к себе в копилку еще один аргумент, почему во избежание недопонимания надо использовать только англоязычные термины :-)

Угу, это часто бывает. Вас не удивляет, что Metropolitan означает совсем не то, что Метрополитен.Ещё смешнее со словом секс, которое на английском означает просто пол.

во избежание недопонимания надо использовать только англоязычные термины :-)
Ну попробуйте с английским смыслом слова «секс». Буду очень удивлен, если вас поймут. А пришло это слово в русский язык примерно тогда же, когда ООП.

То есть, вы хотите сказать, что термин «Object-oriented programming» приобрел определение, соответствующее Страуструповскому триплету, только в русскоязычной традиции?
Если не путаю, то термин ООП пришел в русский язык вместе с книгой Страустрапа. Не знаю, как сейчас, но лет 25 назад даже считалось, что без множественного наследования — это не ООП.

Ох! Окей, продолжу, предполагая, что вы не троллите. Если что, покажите табличку "сарказм", пожалуйста. :-)


Ваша аналогия некорректна. Иностранные слова в русский язык заимствуются, после чего, как правило, живут сами по себе. Научные термины же международны по своей природе — ученые и инженеры бОльшую часть информации получают на принятом в их профессиональной области международном языке, а в нашем веке эту роль совершенно однозначно выполняет английский язык. Английские термины и их определения — это, в терминах DDD, ubiquitous language программистов. В связи с общедоступностью информации на этом самом ubiquitous language никакого самостоятельного развития и ответвлений не возникает; русскоязычные термины (которые, за исключением давным-давно (до 90-х) сложившихся терминов, либо являются прямым переводом, либо вообще англицизмами) в русской речи программиста используются только по той простой причине, что иначе было бы проще вообще все говорить по-английски.


А с тем, что считалось 20 лет назад — я не спорю: тогда на фоне С++ всех остальных объектно-ориентированных языков и видно не было. И считалось "так" не только "у нас", но и "у них". С популяризацией же таких языков, как Javascript и Ruby, вспомнили, что не все так просто.


Предлагаю сойтись на том, что "И-Н-П" является определением ООП-языков семейства Simula. :-)

Вообще-то смысл терминов дрейфует в любом языке, а не только в русском. Как пример — дрейф смысла слова hacker. При этом английский дрейфует побыстрее русского.

Более того, единый английский или единый русский язык — это всего лишь абстракция. На самом деле есть смесь диалектов (советую почитать по ссылке, Яндекс очень интересно об этом рассказывает).

Как пример: исторически сложилось так, что литературный русский язык практически эквивалентен питерскому диалекту. Но есть пресловутая булка, которая в Питере означает батон белого хлеба, а по всей стране — сдобное хлебобулочное изделие.

Вы правы лишь в одном. Для человека, у которого английский не является родным языком, английские термины лишены фоносемантического ореола и потому более однозначны.

Но в любом случае — лучше как в физике — «для начала введем несколько определений». И спорить не о смысле терминов, а в рамках уже введенных определений.

Что касается вашей трактовки ООП… Мне она не нравится тем, что тогда получается, что ООП возможен на любом языке, где есть структуры. Берем структуру, пишем набор методов для неё — и получаем собственно все, что вы имеете ввиду под ООП. Роль класса у нас исполняет модуль, но все нужные свойства вашей трактовки ООП вполне есть. Ну а в моем понимании ООП возможно лишь там, где есть VMT или его аналог.

А с тем, что считалось 20 лет назад — я не спорю: тогда на фоне С++ всех остальных объектно-ориентированных языков и видно не было. И считалось «так» не только «у нас», но и «у них».

Ну вот вы и признали дрейф англоязычного термина.
Ну вот вам кусочек кода на Си
struct usart_port;
bool usart_driver_initialize (const struct usart_port *port, int rx_buffer_size, int tx_buffer_size);
void usart_driver_set_bps (const struct usart_port *port, int bps);
void usart_driver_set_parity (const struct usart_port *port, enum USART_DRIVER_PARITY parity);
int usart_driver_send_byte_with_timeout (const struct usart_port *port,  uint8_t bt, int timeout_ticks);
uint8_t usart_driver_receive_byte_with_timeout (const struct usart_port *port, uint8_t *dst, int timeout_ticks);



Вы готовы признать, что это ООП? С моей точки зрения это модульное программирование. Можно назвать его программированием, ориентированным на объекты, но это не ООП. Ну хотя бы потому, что такой стиль написания придуман задолго до ООП.
Модульное программирование — это отдельная характеристика, которая не конфликтует с ООП. Программа может быть одновременно ОО и модульной, также как может не быть ни ОО ни модульной. Кстати, в приведенном вами куске кода разбиения на модули не видно, хотя и подразумевается :)

То что вы привели — это в таком виде обычное процедурное программирование. Кстати, не могли бы вы пояснить каким образом из слов вашего оппонента следует что этот кусок кода надо классифицировать как ООП?
Ну вот вам чуть урезанная цитата из @ymbix:
Класс — это деталь реализации конкретных языков, совершенно необязательная

Понятие наследования вообще не является необходимым — любое наследование заменяется композицией.

Понятие виртуальных методов тоже не нужно:

В итоге, из необходимого и достаточного остаются только объекты, взаимодействующие друг с другом в соответствии с контрактами. Все остальное — детали конкретных реализаций.

Ну вот «объект, взаимодействующие с другими в соответствии с контрактом» я и представил. :-) Но я тоже согласен, что это не ООП. Хотя инкапсуляция и некий полиморфизм тут есть.
Инкапсуляции тут нет: методы и данные отделены друг от друга. Полиморфизма тут тоже нет.

Вот если бы структура `usart_port` была объявлена как-то так:

struct usart_port {
    struct vmt_t { // Я не помню можно ли так делать в Сях, предположим что можно. Если нет - эту структуру без проблем можно вынести наружу.
        bool (*usart_driver_initialize) (const struct usart_port *port, int rx_buffer_size, int tx_buffer_size);
        void (*usart_driver_set_bps) (const struct usart_port *port, int bps);
        void (*usart_driver_set_parity) (const struct usart_port *port, enum USART_DRIVER_PARITY parity);
        int (*usart_driver_send_byte_with_timeout) (const struct usart_port *port,  uint8_t bt, int timeout_ticks);
        uint8_t (*usart_driver_receive_byte_with_timeout) (const struct usart_port *port, uint8_t *dst, int timeout_ticks);
    } *vmt;

    // ...
}


То получилось бы уже то самое ООП в языке Си.
ООП тут нет, разумеется. А инкапсуляция (в модуле) есть. Мой оппонент верно заявил, что классы не обязательны для ООП, вместо них могут быть модули или пространства имен. Полиморфизм есть, но куцый — времени линковки. То есть строго по определению — один вызов функции обрабатывает данные разных типов, ибо внутренняя структура типа фиксируется лишь при линковке.
Вот если бы структура `usart_port` была объявлена как-то так:

Э… батенька… это уж совсем никуда не годится. Не может понимание того, является ли вызывающий код ООП или нет базироваться на вызываемом коде. Иначе получится, что слинковали один модуль — это ООП, слинковали другой с тем же интерфейсов — не ООП. А это уж совсем бредово.

Но вам +1 в карму за верную догадку. Кое-что в этом духе в потрохах есть:
  void (*init_pins_and_clocks) (void);
  void (*set_rs485_tx_enable_pin) (int enable);
Правда и это — ну совсем не ООП, ибо все структуры формируются статически.
А вызывающий код и вызываемый совершенно не обязаны быть написаны в одной парадигме!

В данном случае, в вызываемом коде (драйвере UART), видимо, от ООП все же что-то есть. А вызывающий как выглядел процедурным, так и выглядит…
Да оба процедурные, просто по формальным критериям, названным symbix это ООП. Вот и захотелось доказать, что его критерии не верны.

Максимум это код, ориентированный на объекты (struct usart_driver — вполне себе объект). Но не ООП.

Модульность необходима, но не достаточна. Тут сделан один шаг в сторону ООП, но отсутствует возможность абстрагирования от конкретного типа объекта. Можно говорить об этом с точки зрения отсутствия разделения контракта и конкретной реализации, можно — с точки зрения отсутствия полиморфизма, можно — с точки зрения отсутствия late binding, это все разные стороны одной монеты.


Чтобы появилось ООП, в этом коде надо добавить указатели на функции в структуру и договориться, что снаружи модуля мы "не видим" никаких членов структуры, кроме этих указателей на функции. Похожим образом устроены модули в nginx: там, конечно, много performance hacks, но в целом — вполне себе ООП.

но отсутствует возможность абстрагирования от конкретного типа объекта.
Почему же? Абстрагирование полное. Это может быть RS323, а может быть RS485, RS422 или радиомодем. Если две функции сделать пустыми — это может быть USB, SPI или UDP. Если добавить пару функций — то можно и TCP/IP.

Уж не говорю о том, что конкретная реализация ком-порта тут не определена. А она бывает совсем разная на STM32, LPC, Atmel или 80x86.

Чтобы появилось ООП, в этом коде надо добавить указатели на функции в структуру
Вы имеете ввиду синтаксис или семантику? Если синтаксис, то отличие между port1.usart_driver_initialize(200, 300) и usart_driver_initialize(&port1, 200, 300) не существенно. Ну да, первый вариант красивее, но не более того. Если речь о семантике, то во многих (если не во всех) реализациях в VMT включаются только виртуальные функции. Таким образом, у класса без виртуальных функций нет ни VMT, ни указателей на функции.

и договориться, снаружи модуля мы «не видим» никаких членов структуры, кроме этих указателей на функции.
Гм, на таком уровне полезно язык Си знать. Вы тут видите хоть один член структуры? Не видите. И никто его не видит, кроме реализации функций и фабрики, выдающей указатели на структуру.

можно — с точки зрения отсутствия полиморфизма, можно — с точки зрения отсутствия late binding, это все разные стороны одной монеты.
Вот-вот-вот… Вы уже очень близко. Остался маленький шаг — понять, что при отсутствии наследования полиморфизм вырождается. То есть чтобы вести речь о полиформизме — должен быть выбор хотя бы из двух реализаций. А это означает, что без наследования — нет ООП, а есть лишь «программирование, ориентированное на объекты».
Вы имеете ввиду синтаксис или семантику?

Семантику, разумеется.


Почему же? Абстрагирование полное.

Ок, давайте, чтобы не углубляться в ненужные детали, считать, что у нас только initialize, send и receive. Как будет выглядеть код, который создаст массив из N портов разного типа, и шлет в цикле во все порты строку "Hello"?


Остался маленький шаг — понять, что при отсутствии наследования полиморфизм вырождается.

Почему же? Достаточно интерфейсов. Или вообще duck typing.

Почему же? Достаточно интерфейсов. Или вообще duck typing.
Интерейсы изоморфны множественному наследованию от абстрактных базовых классов без статических членов и методов. Утиная типизация — это те же интерфейсы, просто имя интерфейса не пишется явно, а вычисляется компилятором или рантаймом.

Так что увы, вы сами пришли к тому, что без наследования описаний методов нет ООП. Да, можно не наследовать данные, но необходимое условие ООП — это VMT или его эквивалент.

Семантику, разумеется.
Семантика ровно та же. Хотите сказать, что код с богатой иерархией классов, но без виртуальных функций -это не ООП? Эка как вас в разные стороны мотает. Не вы ли писали "Понятие виртуальных методов тоже не нужно"?

Как будет выглядеть код, который создаст массив из N портов разного типа, и шлет в цикле во все порты строку «Hello»?
Не понимаю, в чем у вас проблемы? Хотите через массив — ну ловите через массив.

Абсолютно очевидный код
#define N_PORTS 10
struct usart_port *ports[N_PORTS];
for (int i=0; i <N_PORTS; i++)
     ports[i] = usart_driver_get_driver_by_number(i);
for (int i=0; i <N_PORTS; i++) {
    usart_driver_initialize(ports[i],64,64);
    usart_driver_set_bps(ports[i],  115200);
    const char *str = "Hello";
    while (*str)
        usart_driver_send_byte_with_timeout
            (ports[i], *str++, 1); 
}


В свою очередь прошу объяснить, зачем вам массив? Экономия микросекунды на медленных операциях с портом? Не, конечно, как бы дитя не вешалось, лишь бы потешилось, но все-таки, зачем?

Массив был прямым намеком на late binding и LSP, на классический пример про геометрические фигуры. Полиморфизм времени линковки не считается, конечно же.


В общем, я понял, в чем у нас ключевое расхождение. Вы считаете систему контрактов вариацией механизма наследования. Но это вам так кажется под влиянием языка С++, в котором интерфейсы реализуются через pure abstract classes, а функцию "implements" выполняет множественное наследование. На самом деле это концептуально разные вещи: C extends A, B означает "C является A и является B", а C implements A, B означает "C поддерживает протоколы A и B". Да, наследование (множественное) позволяет реализовать интерфейсы. Но для реализации интерфейсов наследование не является необходимостью — существует масса иных способов, когда наследования нет, а интерфейсы есть (например, в Go). Да и на той же Джаве я могу написать объектно-ориентированную программу без единого extends, пользуясь только implements.

Чтоб вам было понятней, рассмотрим два примера.

  1. Классический С++ или Delphi, объекты с развитым наследованием, но без виртуальных методов. Наследники вовсю пользуются методами базового класса, но никаких виртуальных методов нет. Назовете ли вы этот стиль ООП?
  2. Классическая связь двух приложений через COM/DCOM. Клиент написан в процедурном стиле, сервер — тоже. Интерфейс — наш собственный. Назовете ли вы это ООП?

1 — не знаю, что там в Delphi, но в С++ точно нет — без виртуальных методов не получится реализовать late binding (хаки с прямым доступом к памяти по оффсету не рассматриваю, конечно).


2 — зависит от архитектуры приложений, вполне может быть, что и ООП. По крайней мере, сам COM-вызов это ООПшная штука.

Ага, то есть late binding для вас обязательное условие. Хорошо, пусть в программе есть виртуальные методы и их перекрытие, но эти методы не вызываются. Будет ли такая программа ООП?

Ещё усложним. Пусть при одних настройках late binding происходит, а при других — нет. Означает ли это, что программа становится ООП при изменении настроек?
В Delphi (как и в boost/C++) возможен late binding без виртуальных методов. Делается это через события — это такие указатели, указывающие на метод конкретного объекта. То есть семантически «событие» — это два указателя, один на объект другой на метод, но синтаксически — это единое целое. Вроде бы в С++17 эта фича уже добавлена.

Так что late binding возможен и без ООП.

Я уж не говорю про самые обычные указатели на процедуры, которые были ещё в Си. Они по сути — тоже late binding.

Late binding в прямом смысле слова, конечно, необязателен. Разумеется, может быть любой другой механизм — такой, как вы описываете, или диспетчер сообщений в Objective-C. Реализация вообще не имеет значения. Важно, что там, где мне надо знать только контракты, мне действительно достаточно знать только контракты, конкретный тип (как и вообще наличие в языке конкретных типов) меня абсолютно не волнует.


Псевдокодом:


interface Fooable {
    void foo();
}
class FooHandler {
    void handleFoo(Fooable fooable) {
        fooable.foo();
    }
}

Fooable fooable = getSomeFooableAnyhow();
FooHandler fooHandler;
fooHandler.handleFoo(fooable);

При этом абсолютно не имеет значения, какого конкретно типа тут foo. Что именно делает getSomeFooableAnyhow() — не имеет значения, за исключением того, что возвращает нечто, реализующее интерфейс Fooable. Конкретная реализация всегда может быть изменена на другую, код завязан на контракты а не не конкретную реализацию.

Разумеется, может быть любой другой механизм — такой, как вы описываете, или диспетчер сообщений в Objective-C.
Вы сейчас договоритесь до того, что любая GUI-программа для windows — это ООП. Там как раз есть диспетчеризация сообщений и late binding, сделанный на switch и if. Ну и объекты (окна) и их классы.
Что именно делает getSomeFooableAnyhow() — не имеет значения, за исключением того, что возвращает нечто, реализующее интерфейс Fooable
Так ровно это и делает usart_driver. Вы же не знаете, что именно возвращает фабрика, указатель на usart_driver или указатель на иную структуру (наследника), в начале которой сидит usart_driver.
Конкретная реализация всегда может быть изменена на другую, код завязан на контракты а не не конкретную реализацию.
Вот-вот-вот. Ровно это и есть в usart_driver. Может вы зря решили, что это не ООП? :-)
Это может быть RS323, а может быть RS485, RS422 или радиомодем.

Нет, это может быть только UART. То, что находится на другой стороне линии связи — абстракция железная, а не программная.

Да ну? У RS485 есть особенность: надо отдельно включать и выключать передатчик. Ибо если на линии два передатчика передают разные сигналы, то минимум один из них может сгореть. Так что код для RS485 чуть иной, чем для RS232.
Код для RS485 отличается от кода в RS232 в драйвере или в прикладной части программы?

Если второе — то тут вообще нет никакой ни абстракции, ни полиморфизма, одна лишь видимость.

Если первое — то об этом надо было говорить с самого начала.
Первое. И об этом говорилось. И ещё раз повторю, что код для драйвера выбирается в момент линковки. Ну, если точнее — у нас десяток разных кодов для фабрики, выдающей ссылку на драйвера (по одному на плату). Это и дает независимость прикладного кода от железа.

Но принципиально я могу написать код для USB с тем же интерфейсом и слинковаться с ним. Или на SPI. Или на UDP.

У меня в соседнем участке кода — 4 разных реализации на один H-файл. Это такой вот полиморфизм времени линковки.
Хм, а как вы с таким подходом будете делать плату с двумя передатчиками разного типа?
Это вы про что? Про 4 реализации? Ну если вы мне покажите SoC, в котором одни порты под linux, другие под FREERTOS на STM32F4/F7, третьи на LPC, а четвертые на К1879ВЯ1Я — то я подумаю. :-)

А если про usart_driver, то там все довольно просто. Он состоит из трёх частей: обработчик прерывания, процедура старта передачи при появлении символа в очереди и универсального чтения-записи в очередь. Как видите, обе не универсальные части — не имеют публичных имен.

Обработчики прерывания — вообще ставятся при инициализации. А процедура старта передачи… Ну тут лучше указатель на функцию. Хотя можно и без него — ветвление по значению поля, но это сильно хуже.
Нет, погодите. У вас если уже готовые модули usart_driver. И два порта, один RS232, а другой RS485. Что и с чем вы будете линковать чтобы все заработало?
Линкуется с двумя модулями. Один -универсальный с кодом драйвера, второй уникальный для платы usart_hw, содержащий фабрику. Там статически заданы структуры портов, а в них — прерывания, пины и так далее. Для RS485 там будет и код процедуры, управляющей пином TX_ENA.
То есть управление пином TX_ENA пишется заново (или копи-пастится) для каждой новой платы содержащей порт RS485?
Да, разумеется, это намного удобнее и компактнее, чем «универсальная» процедура, которой надо передавать адрес регистра на шине и бита в этом регистре. И которая к тому же не универсальна, ибо управление TX_ENA может идти не только одним битом GPIO.
Нет, в ООП универсальность достигается другими средствами: через указатель на функцию/интерфейс, которая уже управляет пином. Похоже на то как сделано у вас. Но я не могу понять что именно у вас сделано.

Управление пином TX_ENA у вас пишется в каких процедурах? В процедуре «вызвать перед передачей» или в процедуре «установить пин TX_ENA»?
Фактически у нас компонентно-ориентированное программирование. Ну или что-то близкое к нему.

Управление пином TX_ENA у вас пишется в каких процедурах?
Уже писал:

 void (*set_rs485_tx_enable_pin) (int enable);


Мелкое замечание. Точно так же как шитый код бывает прямым, косвенным, индексным и так далее, точно так же позднее связывание — это не только вызов процедуры по указателю. В конкретной реализации мы можем иметь и switch, выбирающий исполнение в зависимости от типа объекта, и указание в объекте индекса в таблице процедур и много много иных способов. Для кода, предполагающего расширение — это неудобно. Но если расширение не предполагается, то можно и так. А возможность расширения системы классов — не является определяющим свойством ООП.
Заведение — средний род.
Угу, это я описался.
> В общеупотребительном определении ООП наследование является обязательным признаком. Если у вас есть иное авторитетное определение — дайте на него ссылку.

Достаточно по вашей же ссылке переключить на на более употребительный английский

Наследование в ООП обязательно, но не потому, что оно нужно для определения ООП, а потому, что в ООП без него никак.
По сути наследование порождается необходимостью классификации объектов по их общим свойствам и аспектам поведения.
Есть, конечно, вырожденные случаи в виде конкретных простых проектов, когда объекты принципиально различны. Но в жизни так бывает редко.
Главная же проблема в том, что наследование как важный элемент ООП подменяют наследованием классов в языках программирования, в то время как наследование — это всего лишь выделение общей части поведения и свойств объектов для возможности "общения" с объектами разных классов единообразно. И неважно, какими средствами ЯП оно реализуется.
Или иначе, наследование классов в объектно-оринтированном языке является всего лишь одним из способов реализации наследования в ООП.

Собственно да. Композиция структур в Go вполне себе обеспечивает наследование поведения, хотя и не является наследованием классов(которых в Go, о ужас, тоже нет!) в классическом понимании)

Прототипное программирование в, например, JS является объектным, но не является объектно-ориентированным согласно некоторым определениям, для которых важным в ООП является наличие иерархии классов.

Объект «книга» в приложении для библиотеки должен обладать свойствами «абстрактного печатного издания», в магазине – «абстрактного товара»

Стоп-стоп. С чего вдруг он должен? Товар это отдельное понятие, именно с книгой никак не связанное. В магазине ведется учет объектов класса "Товар", в нем есть ссылка на объект "книга", или "журнал", или "булка хлеба".

Рекомендую все-таки не ограничиваться одной цитатой, а почитать хотя бы главу по ссылке. А уж после этого можно будет и поспорить — эффективно ли предложенное вами решение и какие таблицы в СУБД оно порождает.

Подсказка: цены где хранить будете? Хочу найти самый дешевый букварь, как поиск в СУБД пойдет? Хочу найти товары с наибольшей маржой, как поиск пойдет?

Там пишут вещи, противоположные тому, о чем я сказал.


Теперь объект «книга» это контейнер для чего-то продающегося, выдаваемого, хранящегося и пылящегося. Необходимо быстро менять контекст: в магазине вкладывать в книгу товар.

У меня в книгу ничего не вкладывается, это отдельная сущность. В этой терминологии надо "вкладывать" в товар книгу.


Подсказка: цены где хранить будете?

Как этот вопрос связан с логической ошибкой в вашей цитате? Вы считаете, что единственный способ назначить книге цену это сделать у нее свойство "цена"? У самой книги нет такого свойства. Автор есть, название есть, а цены нет.


Есть такое понятие SKU — stock keeping unit. На него обычно и назначается цена.

В этой терминологии надо «вкладывать» в товар книгу.

Это не важно, все равно сущности плодятся.
У самой книги нет такого свойства. Автор есть, название есть, а цены нет.

Это неудобно. У вас получаются двунаправленные связи. С одной стороны, когда печатается чек, мы из SKU лезем в книгу за названием. С другой стороны, когда ищем самый дешевый букварь — у нас сортировка книг по цене. Слишком тесные связи между объектами — признак плохого дизайна.

Ещё хуже, что мы получаем кучу JOIN на выборке из СУБД. И катастрофически теряем скорость.

Правильный метод проектирования таких систем — идти от СУБД. То есть прикинуть самые частые запросы, разработать структуру таблиц. А уже по ней — делать программные сущности. И понимать, что они вторичны относительно таблиц СУБД. В смысле, что для одной и то же СУБД разные программные модули могут иметь разные структуры классов.
Это не важно, все равно сущности плодятся.

Что значит "плодятся"? Оба понятия, и "книга" и "товар", есть в предметной области магазина. И это разные понятия.


Это неудобно. У вас получаются двунаправленные связи.
С одной стороны, когда печатается чек, мы из SKU лезем в книгу за названием.
С другой стороны, когда ищем самый дешевый букварь — у нас сортировка книг по цене

Когда печатается чек, у нас основаня сущность это SKU. Когда ищем самый дешевый товар определенного типа, у нас основная сущность тоже SKU.
Это сводится к запросу вида WHERE product_type_id = 123 ORDER BY price LIMIT 1, и это не куча джойнов, а 1-2, в зависимости от реализации, которые к тому же неплохо заменяются на отдельные запросы с IN.


Связи между сущностями есть, только они идут из предметной области и вы никуда от них не денетесь. А если попробуете, то да, у вас будут все эти "проблемы с ООП". Только это не из-за ООП, а из-за неправильной архитектуры.

Кстати, предлагаю вам задуматься, как в вашем подходе сделать, чтобы цена одной и той же книги в одном филиале была 100 р, а в другом 110.
В рамках ООП? Легко. Можно даже гибче.

Стандартный подход такой. Базовый класс — это таблица «книги». Наследником этого базового класса является view «книга-товар». В момент создания view клиент передает серверу СУБД всю информацию, нужную для формирования цены. При выполнении продажи в таблицу продаж добавляется проводка, в которой есть все данные, использованные для формирования view. Торговые остатки — это тоже view, но от таблицы проводок и таблицы поступлений.

Таким образом, мы имеем на одной базе разные ценовые планы не только по филиалам, но и по отдельным клиентам с их скидочной историей.

Кроме гибкости, тут есть ещё и обычные преимущества оперативной памяти перед диском. Мы имеем скорость и очень малое время блокирования, ведь блокируется только view торговых остатков, а оно располагается в памяти и не сбрасывается на диск. Остальные таблицы — только дописываются. Ну и соответственно устойчивость к сбоям и ошибкам софта. Ещё один плюс — нет проблем с репликацией. Мы можем без проблем реплицироваться на любое количество серверов, ибо у нас таблицы только дописываются.

Из недостатков — та самая хрупкость базового класса, о которой писал Сергей Тарасов ( cross_join ). То есть на этапе проектирования таблицы «книги» хорошо бы представлять, какие у нас будут view, Ещё недостаток — чуть увеличивается время старта.

А что блокируется в вашей модели при продаже? И какэту задачу решили вы, если ставите вопрос о разности цен в филиалах, а не гибкой скидочной политики для клиентов? Филиалы редко добавляются, с ними особой возни нет. А вот условия для клиентов меняются быстро, согласно полету фантазий маркетологов.
является view «книга-товар». В момент создания view клиент передает серверу СУБД всю информацию, нужную для формирования цены

Ну о чем и речь. Цена это не свойство книги, она находится вне её. Непонятно, зачем вам надо ту часть view, которая не относится к таблице "книги", каким-то образом от нее наследовать, если вы все равно передаете информацию отдельно.


И как эту задачу решили вы, если ставите вопрос о разности цен в филиалах, а не гибкой скидочной политики для клиентов?

Я же описал подход. В филиалах хранятся SKU со своей ценой. Если есть скидки, они применяются на эту цену. И это абсолютно не зависит от того, книги мы продаем или журналы, или сначала только книги были, а потом журналы добавились.


А что блокируется в вашей модели при продаже?

Технические проблемы решаются техническими средствами. Мы говорим о правильности моделирования.

Цена это не свойство книги, она находится вне её.
Свойство книги — это стоимость, то есть закупочная цена (для магазина) или себестоимость издания (для издательства). А все остальные цены — вычисляются исходя из себестоимости, и неких коэффициентов жадности и эластичности цены, которые тоже относятся к книге.

В филиалах хранятся SKU со своей ценой. Если есть скидки, они применяются на эту цену.
То есть в каждом филиале сидит тетенька-калькулятор и вручную считает для каждой книги её продажную цену? Зачем такая глупость, если все данные для расчета продажной цены и так есть в системе?

А если уж система считает цену, то зачем её хранить? Чтобы при падении было веселее восстанавливать?

Технические проблемы решаются техническими средствами. Мы говорим о правильности моделирования.
Ага, ваш уровень понятен. Ну интернет-магазинчики с десятком продаж в день можно писать и так. А как только у вас будет хотя бы сотня продаж в секунду — вы почувствуете на себе, зачем нужна правильная архитектура.

Одна моя СУБД вполне выживала при миллионе записей в секунду. Реальный пик там был порядка 10 тысяч, но мы захотели запас по производительности в 10 раз, а заказчик — ещё в 10 раз. Это на Pentium-II 300Мгц. В итоге — работает 15 лет в режиме 365 на 24 без сбоев и техобслуживания. И это — далеко не HighLoad.

На самом деле, что блокируется и на сколько — одна из самых важных характеристик структуры базы. Потому что там, где блокировка не влияет — можно и вообще без СУБД.
Свойство книги — это стоимость

То есть она все-таки хранится в таблице "книги"? Ну значит это и есть SKU, вы их объединили для технических целей, и у вас нет проблем потому что всего один тип товаров.
Только еще есть отдельный view и цена вычисляется на основе отдельной информации. Одного объекта не хватает. Явно есть большая внешняя связанность (high coupling). Если вас это устраивает в поддержке, то хорошо, только не надо говорить, что это правильная модель, и в любой системе надо делать такую же структуру классов.


То есть в каждом филиале сидит тетенька-калькулятор и вручную считает для каждой книги её продажную цену? Зачем такая глупость, если все данные для расчета продажной цены и так есть в системе?

Не вижу связи. Все что может быть автоматизировано, вычисляется автоматизировано.


А если уж система считает цену, то зачем её хранить?

Если у вас можно все свести к коэффициентам относительно цены в главном филиале, и вас устраивают эти пляски, дело ваше конечно.


На самом деле, что блокируется и на сколько — одна из самых важных характеристик структуры базы.

Ага, только это характеристика реализации, а не модели.


Ага, ваш уровень понятен. А как только у вас будет хотя бы сотня продаж в секунду — вы почувствуете на себе, зачем нужна правильная архитектура.
Одна моя СУБД вполне выживала при миллионе записей в секунду.

Я уже про это говорил. Можно делать что угодно для технических целей, но надо понимать, что является исходной моделью.
Иначе вот так и появляются все эти "так исторически сложилось", а почему никто не знает.
А что вы будете делать, "как только у вас" будет больше чем один тип товаров?


Еще раз, разговор о правильности модели. То что у вас миллион записей, совершенно не означает, что в другом проекте надо делать наследование так же как у вас. Еще можно критичные части на ассемблере написать, это тоже не значит, что это правильная модель и все должны так делать.

А что вы будете делать, «как только у вас» будет больше чем один тип товаров?
Да не вижу проблем. Один вариант: для все товаров — однотипные view, другой — рассматривать остальные товары как недокниги. то есть не заполнять часть полей. Ну вот вам второй метод во всей красе — полюбуйтесь на поля «Издательство» и ISBN у дырокола. А это — один из лучших книжных магазинов в стране.

Еще раз, разговор о правильности модели.
Это только в школе есть «правильное» решение и все остальные. А в реальной жизни у каждой модели свои плюсы и минусы.

Ага, только это характеристика реализации, а не модели.
Читайте книжки по внутреннему устройству СУБД и по организации баз данных. Тогда начнете понимать, что блокировки сильно зависят именно от модели. Честное слово, нет никакого желания рассказывать вам азы.
Ну вот вам второй метод во всей красе — полюбуйтесь на поля «Издательство» и ISBN у дырокола.

Во-во, начинаются костыли. Как и "Авторские, выставочные, технические экземпляры — это или продажа по нулевой цене или передача на «удаленный» склад."


Тогда начнете понимать, что блокировки сильно зависят именно от модели.

А я разве где-то сказал, что не зависят? Я сказал, что производительность здесь не обсуждается, так как в исходной вашей цитате категорично написано слово "должен" про все предметные области без указания на производительность.


Это только в школе есть «правильное» решение и все остальные. А в реальной жизни у каждой модели свои плюсы и минусы.

Я не понимаю, что вы хотите доказать. Что если у вас сделано через наследование и оно как-то работает, то высказывание "Объект «книга» в приложении для библиотеки должен обладать свойствами «абстрактного печатного издания», в магазине – «абстрактного товара»" является верным? Это не так, я показал, как сделать по-другому. Или что из этого следует, что проблемы в этом варианте — это проблемы использования ООП, и вместо него надо всё делать на ассоциативных массивах? Это тоже не так, в другом варианте с ООП этих проблем нет.

Я сказал, что производительность здесь не обсуждается,
Это одно из важных свойств, так что обсуждается. Собственно, если производительность совсем не важна — СУБД не нужна, можно и на штатом бухгалтеров на бумажках все операции делать.

Это не так, я показал, как сделать по-другому.
Угу, только ваш вариант — без ООП. А о том, что в таблицах СУБД бывает ООП, вы похоже даже не слышали.

СУБД для большинства приложений — это не способ увеличить производительность системы, а стандартизированное хранилище данных, знакомое (в идеале) каждому разработчику и позволяющее не изобретать велосипеды для каждого приложения, требующего постоянного хранения данных. В некоторых случаях использование популярных СУБД даже заметно уменьшает производительность системы, но значительно повышает скорость её разработки. Собственно, наверное, в большинстве случаев замедляет систему по сравнению с созданным специально для неё хранилищем.

Собственно, наверное, в большинстве случаев замедляет систему по сравнению с созданным специально для неё хранилищем.

С этим соглашусь, сами специализированную СУБД делали.

А в остальном — не очень согласен. Современная СУБД — не только слой хранения, это три слоя: хранения (table), абстракции (view), примитивов (SQL-процедуры). Просто если нам не нужна скорость — мы можем отказаться от нескольких слоев.

В большинстве приложений даже view не используется. Причём по умолчанию. Если нам нужна скорость, то начинаем использовать СУБД более полно, упираемся — пытаемся поменять на конкурирующую, упираемся — пишем свою. Как-то так. Это оптимизации. Да, бывает, что заранее понятно, что дорогие оптимизации нужно сразу делать, но именно бывает.

View — не для скорости, а для ООПшности. Оно дает возможность из таблиц с разной структурой получить однотипные представления и обрабатывать их одной и той же SQL-процедурой. Для скорости — SQL-слой.

А что уровень понимания СУБД крайне низок — это правда. Ещё цитата из cross_join:

Можно ли, будучи в здравом уме, представить себе, чтобы обработка данных шла быстрее их передачи между слоями системы и отображения? Для этого надо серьёзно постараться в освоении «паттернов», нагромоздить кучу вроде бы правильного, но бессмысленного кода с высокой цикломатической сложностью, глубинами иерархий и связностью классов. Ситуацию не спасала автоматическая генерация кода большей части этого Ада Паттернов по сравнительно простой модели с несколькими десятками сущностей.

Если пишешь нагруженную работу с СУБД — значит или привлекаешь DBA или сам им становишься. А иначе — хождение по граблям.

View на уровне СУБД часто нарушают ООПшность, создавая неконтролируемый на уровне основного языка каскад изменений. Грубо, разработчик должен не забыть обновить все объекты, использующую через view таблицу, в которую он только что внёс изменения через изменение и сохранения состояния объекта, представляющего запись этой таблицы.


Про понимание согласен в целом. Многие относятся к реляционным и подобным СУБД лишь как к реляционным хранилищам в принципе, не задумываясь даже о возможности хотя бы часть логики перенести на сторону СУБД. Лично я переношу в крайних случаях, когда не вижу иной относительно простой возможности удовлетворить нефункциональным, прежде всего, требованиям.


P.S. Не минусовал, если что.

Грубо, разработчик должен не забыть обновить все объекты, использующую через view таблицу, в которую он только что внёс изменения через изменение и сохранения состояния объекта, представляющего запись этой таблицы

УЖАС. Вы что, завязались, что у вас один клиент СУБД? А если у вас масштабирование и 100 серверов независимо друг от друга изменяют те же таблицы? Как вы тогда будете «обновлять объекты»?

В нормальной модели считается, что достоверное состояние объектов только в базе. И только на время одной транзакции оно достоверно в сервере.

Ну а слой view делается под слоем SQL-запросов именно для объектности. Он изолирует детали физических таблиц от таблиц логических.

Как общеизвестный пример — вертикальное шардирование. Бухгалтерские проводки каждого года хранятся в отдельной таблице. Prov2015, Prov2016, Prov2017, Prov2018 и так далее. Переключать имена таблицы в десятке мест — неудобно. Поэтому все SQL-запросы работают с view CurYearProv, а переключается это view в выборе отчетного периода.

Лично я переношу в крайних случаях, когда не вижу иной относительно простой возможности удовлетворить нефункциональным, прежде всего, требованиям.
Ну а я, если уж использую SQL-СУБД, то пишу на SQL. И более-менее универсальный клиент к ней.

Как раз не завязываюсь и поэтому каскадно либо обновляю объекты на сервере, либо перечитываю данные вьюх в рамках изменяющей транзакции, чтобы клиент получил достоверные данные сразу после своих изменений. Объекты обновлять проще.


SQL над view слоем универсальный и не зависящий от СУБД

Как SQL над view слоем может не зависеть от СУБД, если даже СУБД может оказаться не SQL?

Ну а как у нас одна и та же программа на С++ работает на windows и linux? C одной стороны ограничиваем язык до подмножества, исполняемого всюду, с другой — слой совместимости.

Chrome и gcc не перестают быть кроссплатформенным от того, что их нельзя запустить на MS-DOS. Так что невозможность запуска на не SQL-базах — это детали.
Чтобы вам было понятней, идеал выглядит так: SQL над view слоем универсальный и не зависящий от СУБД, а то, что под view-слоем — может зависеть и от СУБД и даже от тонкостей настройки на конкретной машине.
Оба понятия, и «книга» и «товар», есть в предметной области магазина. И это разные понятия.

Забудьте о магазине. Магазин действительно может торговать не только книгами. Возьмите библиотеку или книжное издательство. В чем для них разница между книгой и товаром?

Это сводится к запросу вида WHERE product_type_id = 123 ORDER BY price LIMIT 1,

Мда… Вообще-то поиск — полнотекстовый. И никаких типов в нем нет. Есть жанры, но книга запросто может принадлежать разным жанрам. Ну скажем "Дети капитана Гранта" — классика, приключения, учебник географии.

Связи между сущностями есть, только они идут из предметной области и вы никуда от них не денетесь.

Ну-ну, попробуйте доказать, что для книжного издательства товар и книга отличаются. Боюсь, что все дело не предметной области, а в том, что вы пишите придатки к 1С.
Возьмите библиотеку или книжное издательство. В чем для них разница между книгой и товаром?

Абсолютно в том же. Различие только в том, что товаров меньше (а именно один). А принципы те же.


Мда… Вообще-то поиск — полнотекстовый. И никаких типов в нем нет.

Вообще-то вы сказали про определенный тип книг, а не про поиск по названию. Допустим есть книга "Веселые буквы" — по какому тексту вы определите, букварь это или сказка?
Ок, допустим у нас есть жанр "букварь". Как я сказал, запрос по типу можно заменить на запрос с IN. Получаем ID всех книг в жанре "букварь", дальше вместо product_type_id = 123 будет product_id IN (...). Заодно и один джойн убрали. Еще можно использовать возможности движка полнотекстового поиска, это отдельный разговор.


Ну-ну, попробуйте доказать, что для книжного издательства товар и книга отличаются.

Очень просто. У товара есть цена, у книги нет. Вот у меня книга есть "Гарри Поттер и философский камень", какие у нее год выхода, автор, и цена? Первые 2 вы можете в независимых источниках, но цена везде будет разная. А еще может быть, что в пределах одного издательства книга отдельно имеет одну цену, а в составе подарочного издания другую.


Если в предметной области один тип товаров, это не значит, что книга имеет все свойства товара. Вы можете их объединить, если вам это нужно для технический целей (оптимизация запросов например), но надо понимать, что вы нарушаете правильную модель и это может привести к проблемам. Что и описано в статье, которую вы постоянно приводите.
И уж тем более это не значит, что для других предметных областей надо "в магазине вкладывать в книгу товар, в библиотеке – печатное издание, в отделе «книга-почтой» – ещё какую-нибудь хреновину".

Различие только в том, что товаров меньше (а именно один).
То есть в таблице «Товар» одна запись? ОК, опишите структуру СУБД для издательства исходя из этой вашей идеи.

Допустим есть книга «Веселые буквы» — по какому тексту вы определите, букварь это или сказка?
ОЗОН, например, ищет в том числе и по аннотации, потому и находит вот такое. А вот Буквоед ищет только по названию.

Ок, допустим у нас есть жанр «букварь».
Лучше допустим, что библиографии вас не учили. :-) ББК и УДК на букварях не пишется, а ISBN не дает возможности понять, что это букварь.

У товара есть цена, у книги нет.
Ох как интересно. То есть авторские экземпляры — это один товар, крупнооптовая поставка — другой товар, мелкооптовая — третий товар? В итоге вы дойдете до того, что раз для каждой крупной торговой сети цена разная, то и товар для каждой сети разный.

А что с количеством (торговыми остатками)? Вы его небось привязываете к товару? А теперь представьте, что торговые остатки для продажи по более высокой цене сошли в 0. Зато есть та де книга по цене для крупного опта, то есть по низкой. Как вы будете организовывать её продажу?

Одно из решений — у книги есть себестоимость, то есть цена производства. И некие коэффициенты жадности и эластичности спроса, определяющие наценку. Все остальные цены — рассчитываются динамически.

надо понимать, что вы нарушаете правильную модель
Мда… Диагноз ясен. Ничего страшного, лет через 20 вы сами будете смеяться над теми, кто видит лишь одну, истинно верную модель.

И уж тем более это не значит, что для других предметных областей надо «в магазине вкладывать в книгу товар,
Гм, вы хоть раз с view работали? А вообще, ООП в структуре СУБД использовали? Я вам в другом ответе написал, как происходит это вкладывание и что оно дает.
ОК, опишите структуру СУБД для издательства исходя из этой вашей идеи.

Я не знаком с предметной областью и описывать не буду. Как и отвечать на остальные вопросы по технической реализации, потому что реализация зависит от технических требований, в частности прозводительность и сложность поддержки. Часть касательно наследования классов я уже описал, ни что ни от чего не наследуется, между сущностями есть связи.


ОЗОН, например, ищет в том числе и по аннотации
Лучше допустим, что библиографии вас не учили.

Лучше допустим, что вы недоговорили постановку задачи. Сначала сказали, что вам нужны именно буквари, а потом оказалось, что подходят любые книги с упоминанием этого слова в любом свойстве. Все равно, это ничего не меняет.


В итоге вы дойдете до того, что раз для каждой крупной торговой сети цена разная, то и товар для каждой сети разный.

Разные единицы продажи (которые SKU). Они ссылаются на один и тот же объект "книга".


А что с количеством (торговыми остатками)? Вы его небось привязываете к товару?

И это тоже привязывается к SKU. Stock keeping unit — складская учётная единица.


Зато есть та же книга по цене для крупного опта, то есть по низкой. Как вы будете организовывать её продажу?

Это организационные вопросы, к структуре классов они не имеют никакого отношения.


кто видит лишь одну, истинно верную модель.

Видите ли какая штука. Модель одна, потому что реальность у нас одна. Это не отменяет того, что в каждой информационной системе может быть своя модель этой реальности. Как и разные реализации этой модели.

Реальность одна (ну, если придерживаемся объективизма :) ), а моделей её множество, в зависимости от задач и степени познания реальности. Причём даже в одной системе может быть несколько моделей одного физического объекта. Например, взять конкретного человека, который в системе выступает то пользователем системы, то сотрудником, то клиентом.

Ну можно и так сказать, хотя это все-таки разные проекции более общей модели.

Все равно высказывание «Объект «книга» в приложении для библиотеки должен обладать свойствами «абстрактного печатного издания», в магазине – «абстрактного товара»» не является верным.

Не согласен. Модель и есть проекция объекта в нашем сознании. И их может быть множество одновременно в одном сознании в зависимости от контекста, от решаемой задачи.


Когда вы хотите читать книгу (которая уже есть у вас), вы, в общем случае, не думаете о ней как о товаре. Когда покупаете в подарок — не думаете о том, где дома её хранить будет одариваемый. В приложениях то же самое.

И их может быть множество одновременно в одном сознании в зависимости от контекста, от решаемой задачи.

Ага, и всё это вместе составляет более общую модель.
В приложении можно сделать противоречивые под-модели, но если условия изменятся или потребуется интеграция, будут проблемы. Только это не проблемы из-за применения ООП.

Возможно и представляет, но вот если две модели для разных целей противоречат друг другу, то сложно назвать их суперпозицию адекватной моделью. Придётся прилагать множество усилий для устранения противоречий, причём без видимой цели и гарантий получения непротиворечивой общей модели, например, потому что юридические законы, участвующие в моделировании, могут противоречить друг другу даже в одной юрисдикции.

Я не знаком с предметной областью и описывать не буду.
Угу. Как критиковать — так знакомы, как придумать — так не знакомы.

Сначала сказали, что вам нужны именно буквари, а потом оказалось, что подходят любые книги с упоминанием этого слова в любом свойстве.

Это такой секрет полишинеля, известный каждому первокласснику: букварь называется «Букварь», азбука «Азбука», а учебник чтения «Чтение». В учебниках для взрослых это не всегда так и учебник физики для ВУЗов может называться "Фейнмановские лекции по физике", но для малышей — только так.

Что касается подмешивания в поиск всего, где упомянуто слово «Букварь» — это решение разработчиков конкретных сайтов. Озон ещё и отличия в падежах игнорирует.

В любом случае, пока речь не идет о софте для библиографов, работает только полнотекстовый поиск.

И это тоже привязывается к SKU. Stock keeping unit — складская учётная единица.

Это организационные вопросы, к структуре классов они не имеют никакого отношения.

Модель одна, потому что реальность у нас одна.
Вот это и есть ваша модель — фиксированная цена у SKU и наличие нескольких SKU на один и тот же артикул товара.

А есть другая модель — одна книга — один артикул — одно SKU. Зато цен на одну книгу может быть много. В этой модели нет проблемы «Мы не можем продать по дорогой цене, хотя книга есть в запасе».

А есть второй срез. Мы можем изменять остатки при помощи UPDATE прямо в SKU, а можем — использовать двойную запись. И это тоже будут разные модели.

Модель одна, потому что реальность у нас одна
Оптику не помните? Корпускулярно волновой-дуализм? То, что у нас одна реальность, абсолютно не мешает использовать разные модели.
Угу. Как критиковать — так знакомы, как придумать — так не знакомы.

Я указал на логическую ошибку в вашем комментарии.
Придумывать реализацию не буду, потому что вы опять скажете, что я придумал неправильно, как с букварем.


Это такой секрет полишинеля, известный каждому первокласснику

Во-первых, я исходил из вашего описания задачи в несколько слов. Во-вторых, я уже показал, как это сделать с полнотекстовым или любым другим сторонним поиском.


Вот это и есть ваша модель — фиксированная цена у SKU и наличие нескольких SKU на один и тот же артикул товара.
А есть другая модель — одна книга — один артикул — одно SKU.

"Одно" — это частный случай "несколько".


Зато цен на одну книгу может быть много. В этой модели нет проблемы «Мы не можем продать по дорогой цене, хотя книга есть в запасе».

Это все можно перераспределять автоматически или по нажатию кнопки оператором. Это примерно то же самое, как добавить новые напечатанные книги.
Если цена вычисляется динамически, можно вообще ее не назначать на SKU, будут только записи по факту "продана такая-то книга по такой-то цене". В моем примере с продажами билетов именно такая ситуация. И все равно здесь не нужно никакое наследование.


Оптику не помните? Корпускулярно волновой-дуализм? То, что у нас одна реальность, абсолютно не мешает использовать разные модели.

Ага, только это разные следствия одних и тех же причин. Давайте не будем использовать аналогии с малоизученными вещами.

«Одно» — это частный случай «несколько».
Вы, конечно, можете строить базу исходя из того, что у клиента может быть несколько имен, отчеств и фамилий. Но вряд ли это хорошая идея. :-)

Это все можно перераспределять автоматически или по нажатию кнопки оператором
Особенно это кайфно на HiighLoad. :-) Ну в общем, как только вы уходите от интернет-магазинчика с десятком продаж в сутки на что-то высоконагруженное, лучше от таких мест избавляться, выправив дизайн. Потому что сама такая перекладка показывает, что дизайн базы крив.

И все равно здесь не нужно никакое наследование.
Отлично, что вы наконец-то признали, что ООП — не панацея. И далеко не всегда нужно.

Вы, конечно, можете строить базу исходя из того, что у клиента может быть несколько имен, отчеств и фамилий. Но вряд ли это хорошая идея.

Ага, только это ситуация маловерятная ввиду особенностей предметной области, а несколько товаров это нормальная ситуация. И ваш пример с дыроколом это подтверждает.


Особенно это кайфно на HiighLoad.

А как вы новые книги в этом хайлоаде добавляете? Там ведь тоже количество меняется. Значит это возможно.


Потому что сама такая перекладка показывает, что дизайн базы крив.

Такая перекладка показывает только, что вам удобнее чтобы SKU для одной книги у вас был один, с общим количеством на складе, потому что и склад у вас один. Она не показывает, что надо применять наследование.


Отлично, что вы наконец-то признали, что ООП — не панацея. И далеко не всегда нужно.

Почему вы приравниваете наследование к ООП? Вы хотите сказать, что можно сделать либо только через наследование, либо только через нетипизированные ассоциативные массивы с процедурами? Оно может быть в других частях программы, но в обсуждаемом примере оно не нужно. Но это все равно будут классы со специфическими методами и типизированные объекты, связанные композицией.

А как вы новые книги в этом хайлоаде добавляете?
Добавление записей — это не обновление. При добавлении не блокируется чтение и очень слабо блокируется добавление других записей. А при обновлении блокируется прежде всего чтение. То есть все SELECT ждут, пока у вас пройдет UPDATE.

Такая перекладка показывает только, что вам удобнее чтобы SKU для одной книги у вас был один,
Не только мне (разработчику) удобнее, а бизнесу (заказчику). В моем варианте как пришел товар — так его можно продавать. В вашем варианте надо ждать, пока менеджер вручную выберет, под каким SKU его зачислить. То есть скорость работы софта зависит от человеческого придатка.

Для выделения нового SKU товар должен хоть чем-то отличаться — цветом, размером, упаковкой, закупочной ценой, наконец. Но не продажной политикой. Какие есть скидки-наценки — определяются вовсе не «единица учёта запасов», которым является SKU.

Почему вы приравниваете наследование к ООП?
Потому что все остальное — не ООП. Обязательный признак ООП — это позднее связывание. То есть можно ограничиться наследованием интерфейсов и не наследовать данные, но без позднего связывания — это не ООП.

Вы хотите сказать, что можно сделать либо только через наследование, либо только через нетипизированные ассоциативные массивы с процедурами?
Вариантов намного больше двух. Например — структуры + процедуры для работы с ними.

Но это все равно будут классы со специфическими методами и типизированные объекты, связанные композицией.
Это не ООП. Например в Ада изначально были package, в турбо-паскале — модули со схожими свойствами, и никто не считал их за ООП.
Вы, конечно, можете строить базу исходя из того, что у клиента может быть несколько имен, отчеств и фамилий. Но вряд ли это хорошая идея. :-)

Да нет, иногда это просто-таки необходимо. На Госуслугах, например, я больше чем уверен, у физ. лиц хранится не по одному имени/фамилии. Да и в налоговой. Да и в банках… Да много где. Не самый удачный пример, короче )
Везде хранят по одному. При смене — обычно заводится новая запись. Куча проблем при перемене фамилии связано именно с этим. Более того — и номер паспорта обычно хранится только один, редко кто позволяет авторизоваться по нескольким номерам паспорта, везде хотят один — тот, который база считает верным.

P.S. Собственно с этим связана "проблема несчастной Королевы", которая в половине документов КоролЁва. Никто не умеет сливать две записи в одну. Риелторы — стонут.
P.P.S. Лично я год назад мучался с оформлением пенсий для одной ВалерЬевны, которая в половине документов ВалерИевна. Увы, всего одна запись, отсюда и мучения.

У женщин довольно часто бывает несколько фамилий.
Для изменения имени, фамилии и отчества есть специальная процедура, сопровождающаяся заменой паспорта.

Замена паспорта вызывает замену данных в базе. Меняли бы фамилию — увидели бы сами. :-)

От базы зависит. В некоторых, даже публично доступных государственных, отображается по идентификатору текущие ФИО, но поиск по ФИО учитывает изменение ФИО в прошлом.

Можно примеры баз, в которых решена проблема КоролЕвы — КоролЁвы?

Это не проблема баз. Уж поверьте человеку с фамилией "Чернышев", которая читается как "Чернышов".

Вряд ли у вас есть документы, в которых написано ЧернышОв. А вот у старой КоролЁвой в половине документов будет КоролЕва. Как писалось в учебнике 1948 года: На клавиатурах большинства работающих в настоящее время в СССР пишущих машин нет… буквы «ё».

До перехода на КИС это не было проблемой. Ну увидит чиновник, что е вместо ё — ему это неважно.

Но как только КИС стали требовать совпадения фамилий в разных документах, так сразу проблема встала в полный рост. Чиновники так и говорят — «я бы рада внести документы в базу, но база их не примет».

И это проблема именно СУБД — для её решения нужно или нормализовать фамилии перед сравнением путем замены «ё» на «е» или сделать их эквивалентность при сравнении.

Есть, если не обратил внимания на написание при получении и решил не заморачиваться позже. Причём довольно значимые на практике, типа отношения к воинской повинности.


До революции наша фамилия была "Чернышов" и устно, и письменно, где-то до войны одна паспортистка решила писать через "ё", где-то ближе к застою другая решила писать через "е". А устное произношение мы так и не меняли. И при записи документов, особенно если один "чиновник" диктует другому, а не мы лично, встречаются все три варианта.


Но я не считаю это проблемой СУБД, поскольку мне неизвестно о существовании каких-то законных или подзаконных актов, обязывающих органы власти, бизнес и т. п. считать букву "ё" аналогом буквы "е". Это проблема нормотворчества.

Значит невнимательно читали по моей ссылке. Вот вам Правила русской орфографии и пунктуации (это действующий документ):

Именно он и определяет, где нужно писать ё, а где йо или е
§ 10. Буква ё пишется в следующих случаях:
1. Когда необходимо предупредить неверное чтение и понимание слова, например: узнаём в отличие от узнаем; всё в отличие от все; вёдро в отличие от ведро; совершённый (причастие) в отличие от совершенный (прилагательное).
2. Когда надо указать произношение малоизвестного слова, например: река Олёкма.
3. В специальных текстах: букварях, школьных учебниках русского языка, учебниках орфоэпии и т.п., а также в словарях для указания места ударения и правильного произношения.

Примечание. В иноязычных словах в начале слов и после гласных вместо буквы ё пишется йо, например: йод, йот, район, майор.

Как видите — фамилий в нём нет. А вот решение суда, которое на основе этих правил устанавливает эквивалентность е и ё.

Выдержка из решения
Исходя из Правил русской орфографии и пунктуации, на практике написание букв «е» и «ё» приравнивается. Написание в документах буквы «е» вместо «ё» и наоборот в фамилии, имени и отчестве не искажает данных владельца документов при условии, что данные, на основании которых можно идентифицировать лицо в таких документах, соответствуют. В современном русском языке буквы «е» и «ё» являются равнозначными, использование буквы «ё» имеет главным образом смыслоразличительный характер. В официальных и иных документах допускается указание буквы «е» вместо «ё», что не является нарушением действующего законодательства и не может рассматриваться как основание для ограничения или препятствий в реализации прав и свобод человека и гражданина.

"Правила..." не являются нормативным документом для МВД/ФМС, судя по всему. Ну, или, являются, но судя по позиции ответчика по делу, однозначно в юридическом смысле не трактуются, на что указывает и текст "Грамоты", указывающий, что именах собственных букву "ё" писать надо. Если бы суд руководствовался той же позиций, об обязательности буквы "ё" в именах собственных, то счёл бы, что имена "Петр" и "Пётр", фамилии "Чернышев" и "Чернышёв" разные.


В общем на месте разработчика я бы не стал самостоятельно вводить эквивалентность "е" и "ё", без особого на то указания заказчика.

На месте разработчика я бы не стал самостоятельно вводить и различие «е» и «ё» без особого указания заказчика.

Беда в том, что заказчики зачастую не разбираются нив ИТ, ни в законодательстве. Например в одном ТЗ, попашем мне на «тестирование» перепутали граждан РФ и жителей. А жители — это не только граждане РФ, но и иностранцы на ПМЖ и лица без гражданства. Авторы ТЗ это не поняли, а заказчики — подписали.

Позиция ответчика по делу тесно связана с СУБД. Он бы и рад помочь, да технических возможностей мало. Проверка соответствия фамилий делается СУБД. Так что варианты:

  1. Попросить админа в обход бизнес-логи поставить флаг прохождения контроля документов. Для этого нужно обоснование, например, решение суда. Ну и сами понимаете все минусы.
  2. Внести коррекцию в цифровую версию документа. Такая коррекция без обоснований — это при знак подлога. Нормальные систему периодически проверяют базу на предмет подозрительных коррекций.
  3. При первичной оцифровке внести намеренную ошибку и оцифровать «ё» как «е». Если первичная оцифровка идет руками, а не OCR — это проще всего. Но надо уговаривать чиновника (борзыми щенками, например) именно в момент первичной оцифровки

Если бы суд руководствовался той же позиций, об обязательности буквы «ё» в именах собственных,
Суд должен руководствоваться правилами, бывшими в момент создания документа. Если у вас документ 1940ого года, когда на клавиатуре пишмашинок не было буквы «ё», не так важно, что в нынешних или будущих правилах.

А разумная позиция — писать Ё в новых документах и принимать старые без Ё.

Очень советую почитать замечательную статью «Человек и власть: одно-оконный интерфейс». Там много примеров, как чиновники и рады бы следовать закону, но СУБД им не дает этого сделать.

А правильное решение со стороны разработчиков СУБД — галка «игнорировать отличия между Е и Ё» или на уровне всей базы или на уровне конкретных документов и личных дел.
Или вы ЧернышЁв?
Ну-ну, попробуйте доказать, что для книжного издательства товар и книга отличаются.

Как минимум, какие-то экземпляры не для продажи. Для производства или склада два экземпляра книги ничем не отличаются друг от друга, а для продажников отличаются — один товаром, который нужно или можно продать, является, а другой — нет.

Авторские, выставочные, технические экземпляры — это или продажа по нулевой цене или передача на «удаленный» склад.

Как только мы пытаемся сделать несколько товаров на одну книгу, сразу встает проблема товарных остатков и пересортицы. То есть мы не можем продать книгу по высокой цене, потому что в таблице у товара с высокой ценой закончились экземпляры. А на складе есть эта же книга, но в таблице она записана по низкой цене. А поскольку угадать запасы трудно: или постоянная пересортица и кидание экземпляров из товара в товара или теряем в деньгах.

Ну а если товарные останки это атрибут самой книги, то она и является товаром. Товары с неизвестной ценой бывают — например, а аукционе. А вот товар без подсчета остатков — это уже нонсенс.

Продажа по нулевой цене — нонсенс :) Сомневаюсь, что кто-то это пропустит, поскольку имеет такая сделка очевидные признаки фиктивной.


Цену реализации можно вообще с единицами хранения и даже реализации не связывать до начала оформления заказа, передачу со склада в реализацию с назначением конкретной цены оформлять в момент когда есть уже готовый заказ.

Продажа по нулевой цене — нонсенс :) Сомневаюсь, что кто-то это пропустит, поскольку имеет такая сделка очевидные признаки фиктивно
Давайте не путать бухгалтерский учёт с коммерческим учетом. Для бухучета все равно, выкинули мы книжки в мусорное ведро или подарили их — все равно это операция списания. А в коммерческом учете не всегда есть смысл делать отдельные операции: выдача авторских экземпляров, раздача промо-экземпляров, передача на выставку…

А чеки с нулевой ценой выдает, например, Почта России (при оплате марками)
image


Так что ничего фиктивного, просто оплата не налом и не банковской картой.

Цену реализации можно вообще с единицами хранения и даже реализации не связывать до начала оформления заказа
Уже писал об этом. Есть только закупочная цена или себестоимость, остальное рассчитывается динамически.
Правильный метод проектирования таких систем — идти от СУБД

В разработке какого интернет-магазина или системы учета товаров вы участвовали?

ad hominem, но отвечу.

Библиотечная система (внедрена в десятке ЦРБ), объектная КИС Ultima-S (несостоявшийся убийца 1С), несколько КИС на UltraProject. А какое это имеет значение? Я же не свои идеи озвучиваю, а идеи моего бывшего коллеги, у которого за плечами опыт десятков КИС.

А вы, простите, полноценные КИС писали? Или только интернет-придатки к 1С? Просто не понимаю, почему у вас в голове только интернет-магазин, а не издательство или библиотека?

Интернет-магазин по торговле книгами почти не отличается от интернет-магазина по торговле утюгами и потому не интересен. Возьмите в качестве основы издательство и у вас произойдет та самая "дефрагментация мозга"
А какое это имеет значение?

Чтобы понимать, какие задачи вы решали, и что имеете в виду.


Я же не свои идеи озвучиваю, а идеи моего бывшего коллеги, у которого за плечами опыт десятков КИС.

Мне неважно, сколько у него опыта, я вижу, что у него есть ошибки в логике рассуждений. Он привел проблему с книгой, но не привел ни одного правильного решения. Ни с наследованием, которое тут вообще не подходит, ни с агрегацией, которую он пытается применить в обратном направлении.


А вы, простите, полноценные КИС писали? Или только интернет-придатки к 1С? Просто не понимаю, почему у вас в голове только интернет-магазин, а не издательство или библиотека?

Потому что принципы моделирования предметной области и там и там одинаковые. На то они и принципы.
С 1С вообще никогда дела не имел.


Я делал например систему продажи билетов для небольшого театра. Вот вроде один товар — билеты, но даже там у билета не было свойства "Цена". Потому что ценовые зоны в зале, скидки, тарифы, и отчеты по всему этому.


То есть, что получается. Вы писали с использованием ООП в том стиле, который описываете. И у вас были проблемы с ООП, которые вы описываете. Но при этом вы продолжаете утверждать, что этот способ единственно правильный, и раз он приносит проблемы, то ООП не нужно. А на другой способ, в котором нет этих проблем, единственный ваш довод "это неудобно".

Чтобы понимать, какие задачи вы решали, и что имеете в виду.
В таком случае, вы неверно сформулировали вопрос. Ну что же, расскажите на каких проектах вы были в роли архитектора клиентского приложения и на каких — в роли архитектора структуры СУБД.

Он привел проблему с книгой, но не привел ни одного правильного решения.
Ничего страшного, что вы не увидели ответ. Прочтите книгу целиком. Если вы всерьез намерены дальше заниматься КИС — она вам будет очень полезна.

Потому что принципы моделирования предметной области и там и там одинаковые.
Ну да, когда вы только что окончили институт школу, вам и должно казаться, что есть какое-то единственно верное решение. А на самом деле для любой задачи всегда есть несколько решений, причем в зависимости от требований. Через 20-30 лет работы это станет для вас очевидным, вы на своем опыте увидите, как «незыблемые» истины уходят в небытие, сменяясь новыми идеями.

Я делал например систему продажи билетов для небольшого театра.
Отлично. Надеюсь вы там были архитектором, а не девелопером? Ну вот и расскажите, какие были варианты структуры СУБД и чем выбранная вами структура лучше альтернатив. И аналогично — архитектуру слоя view, сервера и клиента.

даже там у билета не было свойства «Цена».
То есть у вас были отдельные таблицы мест, билетов и SKU? Ну вот и сравните свою архитектуру с альтернативной. Чем она лучше?

А на другой способ, в котором нет этих проблем, единственный ваш довод «это неудобно».
Отлично. Про ООП тоже расскажите. Есть ли ООП в структурах базы данных, есть ли оно в клиенте. Приведите максимальную глубину и ширину дерева наследования. Например, является ли объектом билет, от кого он наследуется, кто ещё наследуется от того же базового класса.

И у вас были проблемы с ООП, которые вы описываете.
Давайте все-таки не путать меня с cross_join. Сергей вполне есть на хабре и за свой опыт может ответить сам.

Что касается меня, то я писал на двух десятках языков в самых разных стилях. И пришел к выводу, что за исключением отдельных областей, хорошо ложащихся на модель ООП, во всех остальных — лучше классический ООП не использовать.

Можете ознакомиться с тем, чем отличает классический ООП-стиль от недоОПП. Собственно вопрос к вам — какое у вас отношение количества строк кода к количеству классов? Если меньше сотни — это классическйи ООП, если больше тысячи — это максимум недоООП, если посередке — надо смотреть глубину и шири ну иерархии.

Если вам интересно, я могу рассказать, как будет выглядеть решение одной и той же задачи в ООП-стиле и в стиле недоООП.
Ничего страшного, что вы не увидели ответ. Прочтите книгу целиком.

Если он там есть, могли бы прямую ссылку дать. А читать книги с заведомо неправильными советами мне как-то неохота.


А на самом деле для любой задачи всегда есть несколько решений, причем в зависимости от требований

За всеми решениями стоит одна модель. Каждое решение некоторым образом ее воплощает, то бишь реализует.


То есть у вас были отдельные таблицы мест, билетов и SKU? Ну вот и сравните свою архитектуру с альтернативной. Чем она лучше?

С альтернативной какой именно? У меня там единственная архитектура, которая соответствует предметной области.
В качестве SKU используются свободные места, конкретная цена определяется при продаже по сочетанию тарифа и ценового пояса, тариф выбирается оператором.


Ну вот и расскажите, какие были варианты структуры СУБД и чем выбранная вами структура лучше альтернатив.

Не вижу смысла. Чтобы оценить правильность, надо давать полное ТЗ.
И это никак не связано с тем, что книгу от товара или наоборот наследовать не надо.


| А на другой способ, в котором нет этих проблем, единственный ваш довод «это неудобно».
Отлично. Про ООП тоже расскажите. Например, является ли объектом билет, от кого он наследуется, кто ещё наследуется от того же базового класса.

Я не собираюсь спорить сам с собой. Если у вас нет других доводов, то закончим этот разговор.
И раз уж вы спросили, билет это самостоятельная сущность и ни от кого не наследуется.


Давайте все-таки не путать меня с cross_join

Вы используете его слова в качестве подтверждения своей точки зрения, значит вы с ними согласны.


Можете ознакомиться с тем, чем отличает классический ООП-стиль от недоОПП. Собственно вопрос к вам — какое у вас отношение количества строк кода к количеству классов?

Классы определяются ответственностью, а не строками кода. Не знаю, откуда вы взяли правила, которые там написали ниже, но к ООП они отношения не имеют.

За всеми решениями стоит одна модель. Каждое решение некоторым образом ее воплощает, то бишь реализует.

У меня там единственная архитектура, которая соответствует предметной области.
Ну что же, ваш сегодняшний уровень понятен. Через 20 лет вы посмеетесь над тем, какими вы были глупым и смешным в юности.

И раз уж вы спросили, билет это самостоятельная сущность и ни от кого не наследуется.
Тогда причем тут ООП? Или ООП у вас только в клиенте?

Вы используете его слова в качестве подтверждения своей точки зрения, значит вы с ними согласны.
Ну взрослые люди с большим опытом часто согласны между собой. Не вижу тут ничего удивительного.

А читать книги с заведомо неправильными советами мне как-то неохота.
Ничего страшного. Вырастите, поймете что у каждой задачи есть много альтернативных решений, тогда и дорастете до этой книги. А пока вам кажется, что у задачи есть лишь одно верное решение — вам ещё рано читать книги для инженеров.

Это вполне нормальный путь профессионального становления. Junior видит только один вариант, который ему кажется единственно верным. Middle уже видит 2-3 варианта, а Senior — не только видит варианты, но и сразу представляет их отдаленные последствия. Эта книга — написана Senior для Senior и продвинутых Middle, немудрено, что она слишком сложна для вас.
Тогда причем тут ООП?

Потому что в описании модели предметной области с помощью классов необязательно должно быть наследование.


Ну взрослые люди с большим опытом часто согласны между собой. Не вижу тут ничего удивительного.

Ну так а что же вы тогда говорите, что я вас с кем-то путаю? Вы согласны, значит у вас тоже были эти проблемы с ООП, вы их описываете, ссылаясь на статью другого автора.


А пока вам кажется, что у задачи есть лишь одно верное решение

Давайте вы не будете играть словами. Вы здесь используете слово "решение" в смысле "реализация", а я говорю про модель. Реализовать модель можно по-разному, я об этом сразу сказал.

в описании модели предметной области с помощью классов необязательно должно быть наследование.
Значит у вас нет ни наследования, ни классов, ни ООП. Обычная реляционная модель данных

Вы согласны, значит у вас тоже были эти проблемы с ООП
Гениально! Вы так проецируетесь! Вообще-то люди обычно умеют учиться на чужом опыте. Так что к моменту, когда я стал архитектором, я уже насмотрелся на архитектурные проблемы, вызванные ООП. Так что у меня у самого таких проблем не было. А вот у у кое-кого из моих сотрудников — были.

Вы здесь используете слово «решение» в смысле «реализация», а я говорю про модель
Так и модели бывают разными. Та же самая двойная запись — не единственная модель бухучета. Например для контроля своих трат вы её вряд ли используете. Для того же театра я могу придумать несколько моделей продажи билетов.
Значит у вас нет ни наследования, ни классов, ни ООП. Обычная реляционная модель данных

Нет. ООП от кортежей отличается наличием типизации.
Цепочку "в описании модели предметной области с помощью классов" — "значит у вас нет ни классов..." я не понял, извините.


Для того же театра я могу придумать несколько моделей продажи билетов.

Ага, только им нужна та, которая у них. Ее можно сделать на ассоциативных массивах и написать на ассемблере, но за всеми переменными будут стоять понятия модели.


Гениально! Вы так проецируетесь! Так что у меня у самого таких проблем не было.

Если вы приводите некие слова в качестве подтверждения своих, то отвечайте за них. Не хотите отвечать, незачем приводить. У вас, в вашем опыте, у ваших сотрудников. Вы продолжаете играть словами, я в этом участвовать не хочу, так что давайте закончим.


Объект «книга» в приложении для библиотеки может обладать свойствами «абстрактного печатного издания» если кому-то хочется, но не должен.

Нет. ООП от кортежей отличается наличием типизации.
ОК. Приведите пример таблицы. в которой у вас находятся записи нескольких типов.

Объект «книга» в приложении для библиотеки может обладать свойствами «абстрактного печатного издания» если кому-то хочется, но не должен.
Вы все-таки решите: или вы не лезете в ту область, в которой не разбираетесь или раз уж лезете — то будьте готовы представить модель СУБД, основанную на ваших идеях.

Журнал — это не книга. у него нет ISBN и автора, зато есть периодичность. А общее у книги и журнала — это то, что относится к печатному изданию, то есть возможность выдачи на абонементе.

А чтобы после выдачи поставить обратно на полку — нужны свойства печатного издания. То есть наклеивается формуляр, а на нем — место хранения. Грубо говоря номер шкафа и полка. А без него — никак, бардак будет.

Не видели как в библиотеке неделями на полу лежат свежекупленные книги, пока им место хранения не присвоят? :-)

Так что увы — должен. Иначе весь порядок нарушится.
ОК. Приведите пример таблицы. в которой у вас находятся записи нескольких типов.

Как вариант Single Table Inheritance

я ещё пару вариантов описал. Первый у нас был в Ultima-S. То есть вообще ООП-СУБД, надстроенная над реляционной.
ОК. Приведите пример таблицы. в которой у вас находятся записи нескольких типов.

С чего бы мне это делать? Я ничего подобного не говорил.
Без ООП мне в приложении придется с данными из любой таблицы работать как с нетипизированным ассоциативным массивом.
С ООП можно для каждой таблицы добавить тип (класс) в коде приложения, и связать с ним методы работы с данными.


то будьте готовы представить модель СУБД, основанную на ваших идеях

Я уже приводил, здесь и здесь.


А общее у книги и журнала — это то, что относится к печатному изданию, то есть возможность выдачи на абонементе.

Ну и зачем здесь наследование? Это же типичный список (таблица), который существует и в реальности. "Книги и журналы выдаем, дыроколы не выдаем".


А чтобы после выдачи поставить обратно на полку — нужны свойства печатного издания. То есть наклеивается формуляр, а на нем — место хранения.

Ну вот, сами же вводите другой объект, который и связывается с книгой или журналом. А как вы свежекупленные книги в систему занесете, если они наследуются от печатного издания, у которого уже должен быть формуляр?

С чего бы мне это делать? Я ничего подобного не говорил. Без ООП мне в приложении
Причем тут приложение? Речь о ООП в СУБД. То есть в таблицах базы данных. Ещё раз повторю, что ООП в СУБД у вас нет и не было. Поэтому грамотно судить о нем вы не можете.

Чуть цитат из переписки
Я же вас спрашивал: «Тогда причем тут ООП? Или ООП у вас только в клиенте?»

На что получил ответ «Потому что в описании модели предметной области с помощью классов необязательно должно быть наследование.» Модель предметной области — она в таблицах СУБД. Клиент вторичен.

Ну и зачем здесь наследование?
Мы может сделать таблицу «печатные издания» и включить туда общие поля. Это будет базовый класс. Одним полей этой таблицы будет поле типа. По нему мы определяем из какой таблицы брать оставшиеся поля. Это такой прямой вариант ООП.

А есть обратный вариант. Мы добавляем (вкладываем) общие поля «печатного издания» во таблицы книги, журналы, газеты, а потом делаем view, которое берет общие поля из этих таблиц. Это view и будет виртуальным базовым классом.

Есть ещё и третий вариант, о нем рассказал VolCh

Во всех трех вариантах применяются полиморфные хранимые процедуры. То есть выполняемый СУБД код зависит от типа конкретной записи. иными словами — это настоящее ООП с поздним связыванием.

А как вы свежекупленные книги в систему занесете, если они наследуются от печатного издания, у которого уже должен быть формуляр?
Про NULL не слышали? Есть такая особенность у СУБД — значения полей могут быть nullable. Но в целом это аргумент в пользу второго варианта, то есть вкладывания.

А вообще проблема ровно та же, что у вас в интернет магазине при поступлении товара — на него тоже изначально цена не назначена. Более того, в магазине может быть товар, который закончился, а по какой он цене прибудет — непонятно. То есть ещё полезно уметь и сбрасывать цену в NULL.

Ну а теперь перейдем к от уровня СУБД к приложению.
Без ООП мне в приложении придется с данными из любой таблицы работать как с нетипизированным ассоциативным массивом.
Почему? Структуры отменили? :-) Вот вам свежая статья про Anaemic Domain Model и её преимущества.

С ООП можно для каждой таблицы добавить тип (класс) в коде приложения, и связать с ним методы работы с данными.
Это можно и без ООП. Инкапсуляция в класс — не означает ООП. Для ООП нужно позднее связывание. То есть наличие полиморфизма.

Если у вас на самом деле ООП, то приведите пример написанного вами позднего связывания в вашем приложении. То есть один вызов процедуры должен вызывать разные тела процедуры в зависимости от типа данных.

Вполне верю, что в используемой вами библиотеке есть ООП. Но есть ли оно в написанном вами коде?
Модель предметной области — она в таблицах СУБД. Клиент вторичен.

А, ну все понятно. Да, в Delphi примерно так и есть. В базе модель, на форме компоненты, в свойствах компонентов запросы, клиент коннектится напрямую к БД. Только в других (трехуровневых) архитектурах бизнес-логику рекомендуется делать так, чтобы она не зависела от хранилища данных.

В Delphi для трехзвенки есть CORBA. Но трехзвенка — это вчерашний день. Гонять таблицы целиком между СУБД и сервером приложений — отвратительная идея. Современная архитектура -пятизвенная:

  1. слой хранения (таблицы)
  2. слой абстракции (view)
  3. слой примитивов (SQL-процедур)
  4. слой сервисов (в сервере)
  5. тонкий клиент.


Смысл пятизвенной архитектуры — в хайлоаде. В том, чтобы максимально уменьшить блокировки.

В любом случае, у вас есть клиент СУБД. Не важно, называете ли вы его сервером приложений или клиентом. Так вот, вопрос остается открытый. Докажите, что в клиенте СУБД вы написали ООП, а не просто пользуетесь уже написанными кем-то ООП-классами и их наследниками.

Какое позднее связывание придумано и реализовано лично вами?
Гонять таблицы целиком между СУБД и сервером приложений

Никто их целиком не гоняет.


Докажите, что в клиенте СУБД вы написали ООП, а не просто пользуетесь уже написанными кем-то ООП-классами

Я написал объектно-ориентированный код, потому что пользуюсь своими и написанными кем-то объектно-ориентированными классами, в методах которых используются объекты других классов.


Composition over inheritance


Composition over inheritance (or composite reuse principle) in object-oriented programming (OOP) is the principle that classes should achieve polymorphic behavior and code reuse by their composition (by containing instances of other classes that implement the desired functionality) rather than inheritance from a base or parent class.
Никто их целиком не гоняет.
Это уже не смешно, это очень грустно. Если у вас трехзвенка, то СУБД выполняет лишь роль слоя хранения данных. А для обработки вы копируете таблицы на сервер приложений. Целиком за одну операцию или за тысячи операций при помощи курсора — не важно.

А если у вас есть SQL-запросы и они выдают сразу нужную запись — то это уже не трехзвенка. У вас появляется SQL-слой. Конечно, плохо, что ваши SQL-запросы компилируются каждый раз. Но это все равно не трехзвенка.

Так что уж решите сами: или у вас трехзвенка и таблицы тащатся в сервер целиком или — как минимум четырехзвенка.

Я написал объектно-ориентированный код, потому что пользуюсь своими и написанными кем-то объектно-ориентированными классами, в методах которых используются объекты других классов.
Понятно, что раз у вас в фреймворке ООП — то вы его используете. Было бы в фреймворке событийное программирование — вы бы и его использовали. Было бы процедурное — и его вы использовали. Но это вопрос выбора фреймворка, он не интересен.

Вопрос о написанных вами классах. Там есть ООП? То есть написали ли вы сами хоть один виртуальный или интерфейсный метод и хотя бы две его реализации?

Судя по тому, как вы отнекиваетесь — вы ООП используете, но сами ООП не пишите. Ни в СУБД, ни в сервере, ни в клиенте.

Подход в общем верный, но зачем тогда спорить с теми, кто ООП писал и пришел к выводу, что лучше его не использовать?
А для обработки вы копируете таблицы на сервер приложений. Целиком за одну операцию или за тысячи операций при помощи курсора — не важно.
Зачем? Я же говорю, возможно в Delphi это так, в других системах так никто не делает.

А если у вас есть SQL-запросы и они выдают сразу нужную запись — то это уже не трехзвенка. У вас появляется SQL-слой.
Приведите источник, в котором написано ваше определение трехзвенки.

Грубо говоря, трехзвенная архитектура это когда бизнес-логика находится отдельно и от базы и от клиента. Использует ли она запросы к базе, запросы к кэшу, запросы к сторонним сервисам, это неважно. Веб-приложение это типичная трехзвенная архитектура.

Трехзвенная архитектура
Трехзвенная архитектура

Браузер (тонкий клиент) — Web cервер — Сервер БД

Популярные связки

Apache, Nginx — PHP — MySql, Postgresql — Linux

Понятно, что раз у вас в фреймворке ООП — то вы его используете.
Оно не во фреймворке, а средство языка.

Судя по тому, как вы отнекиваетесь
Где именно я отнекиваюсь? Еще раз, ООП это объектно-ориентированное программирование. Ключевое понятие в нем — объекты. Один из подходов организации объектов — Composition over inheritance. ООП без наследования это тоже ООП. Я даже цитату привел из стороннего источника, а вы все равно пользуетесь каким-то своими придуманными критериями.

Интерфейсы и виртуальные методы у меня есть, только они обычно не в сущностях, а в сервисах и в инфраструктуре.

А то, что вы называете «ООП в СУБД» это обычный джойн таблиц, без всяких интерфейсов и виртуальных методов.

Подход в общем верный, но зачем тогда спорить с теми, кто ООП писал и пришел к выводу, что лучше его не использовать?
Потому что проблемы не в ООП, а в том, что вы используете наследование там где не нужно. Если вам это помогает выдерживать нагрузку, дело ваше, но это не единственный вариант организации классов.
Я привел пример как этот сделать, и потому считаю высказывание 'Объект «книга» в магазине должен обладать свойствами «абстрактного товара»' неправильным. Остальное это оффтоп, и обсуждать его я не буду.
Я же говорю, возможно в Delphi это так, в других системах так никто не делает.
1C написано на MSVS, но устроен именно так. Забавная цитата:

в качестве БД при решении задач небольшого масштаба может применяться собственный файловый движок, а для работы в масштабе предприятия - MS SQL Server.

Так что огромное число интернет-магазинов, интегрированных с 1С, в своей бухгалтерской части — классическая трехзвенка. То есть гоняют таблицы или целиком, или елозят о ним курсором.

Грубо говоря, трехзвенная архитектура это когда бизнес-логика находится отдельно и от базы и от клиента.
Вот именно. Из-за отделенности бизнес-логики от базы и возникает необходимость или гонять таблицы целиком или ползать по ним при помощи курсора.

А есть эффективная архитектура, когда бизнес-логика частично находится в слое SQL-процедур, а представление данных — в слое view. В пределе — слой сервера вообще исчезает и остается SQL-сервер и тонкий универсальный клиент.

Я даже цитату привел из стороннего источника, а вы все равно пользуетесь каким-то своими придуманными критериями.
Ну вот вам авторитетные цитаты, что такое ООП. Русская вики:

Объе́ктно-ориенти́рованное программи́рование (ООП) — методология программирования, основанная на представлении программы в виде совокупности объектов, каждый из которых является экземпляром определенного класса, а классы образуют иерархию наследования

А вот и английская вики:

An important distinction in programming languages is the difference between an object-oriented language and an object-based language. A language is usually considered object-based if it includes the basic capabilities for an object: identity, properties, and attributes. A language is considered object-oriented if it is object-based and also has the capability of polymorphism and inheritance.

ООП без наследования это тоже ООП.
Как видите — нет. Это объектное программирование (object-based), но не объектно-ориентированное (object-oriented). А если вы внимательно почитаете свои же ссылочки, то увидите, что там перед композицией идет наследование от интерфейсов или абстрактных классов. Которого в написанном вами коде нет.

А то, что вы называете «ООП в СУБД» это обычный джойн таблиц, без всяких интерфейсов и виртуальных методов.
Да ну? Неужели вас так плохо учили СУБД, что пропустили объектно-ориентированные СУБД? Или вы этот раздел просто прогуляли?

Виртуальные методы в Ultima-S были. Как и классы, и их свойства и довольно длинное наследование… А сводилось это не к JOIN, а к тому что виртальная функция getProperty, вызванная для класса «ABC» вначале пыталась вызвать getProperty_ABC, при его отсутствии — искала базовый класс для ABC (класс AB) и вызывала getProperty_AB и так далее, вплоть до корневого базового класса.

Потому что проблемы не в ООП, а в том, что вы используете наследование там где не нужно.
Поскольку без наследования — это не ООП, а всего лишь объектное программирование, то будем считать, что вы все-таки согласись с тем, что ООП лучше не использовать.

Ну как минимум потому, что ООП в вашей бизнес-логике нет. Вы лишь используете написанный не вами ООП-фундамент.

Не будете ли вы говорить, что открытие файла в Windows NT/2000/XP/7/10 — это ООП? Хотя при трансляции имени файла ядро как раз ООП и использует.
Из-за отделенности бизнес-логики от базы и возникает необходимость или гонять таблицы целиком или ползать по ним при помощи курсора.

Или получать данные отдельными запросами. Результаты которых можно кэшировать отдельно, не полагаясь на планировщик БД. Это все равно трехзвенная архитектура.


В пределе — слой сервера вообще исчезает и остается SQL-сервер

Который один на всех и потому плохо масштабируется.


а классы образуют иерархию наследования
has the capability of polymorphism and inheritance.

Там разве где-то сказано, что абсолютно каждый класс должен от чего-то наследоваться? А вершина иерархии от чего будет наследоваться?
Правильно, ключевые слова "has the capability". Имеет возможность, то есть можно применять там, где надо. Это ничем не противоречит тому, что я сказал.


А вот и английская вики

Ага, на русском привели ссылку на "Объектно-ориентированное программирование", а на английском "Object (computer science)".
Потому что английский вариант не подтверждает ваши тезисы.


Object-oriented programming


The doctrine of composition over inheritance advocates implementing has-a relationships using composition instead of inheritance.

Выделеннное и есть та ссылка, цитату из которой я привел.


то увидите, что там перед композицией идет наследование от интерфейсов или абстрактных классов. Которого в написанном вами коде нет.

"Classes implementing the identified interfaces are built and added to business domain classes as needed. Thus, system behaviors are realized without inheritance. In fact, business domain classes may all be base classes without any inheritance at all."


Выделенное это именно то, что я описываю. Так что нет, "without any inheritance" является ООП. Поэтому "считать" мы так не будем.


И нет, интерфейсы не наследуются, а реализуются. Это просто соглашение о вызовах, без кода и данных, которые можно было бы унаследовать.


Виртуальные методы в Ultima-S были.

Ну вот опять, так же как с букварем. Разговор был про SQL, что по умолчанию подразумевает реляционные БД, а вы оказывается имели в виду свою объектно-ориентированную разработку. Понятно, для дискуссии с вами требуется телепатия.

Результаты которых можно кэшировать отдельно, не полагаясь на планировщик БД.
Ну после этого при масштабировании вам придется поддерживать когерентность кэшей. Справитесь?

Который один на всех и потому плохо масштабируется.
Почему??? Ну как пример масштабирования — контакт, у них там Котенок и Ко лет 10 назад написали свою СУБД, причем опубликовать код они не могут — из-за высокой степени интеграции с остальными сервисам, то есть в них прямо в коде СБУД — бизнес-логика.

А вот архитектура контакта 2016 года. Посмотрите насколько сложный запрос летит на СУБД (это, видимо, так лайк ставится):

image

Так что масштабируется.

Более того, первое, что надо сделать, если производительности не хватает — это перенести бизнес-логику работы с базой в слой СУБД. Потому что СУБД как раз для этого и написан.

Второй уровень — это много webv-серверов к одной СУБД. По такой схеме тоже работает немало проектов.

И уж потом — шардирование для масштабирования слоя СУБД.

Правильно, ключевые слова «has the capability». Имеет возможность, то есть можно применять там, где надо.
Ну в таком случае процедурный код на С++ — это тоже ООП, ибо есть возможность использовать классы и полиморфные методы. :-)

Потому что английский вариант не подтверждает ваши тезисы.
Просто там нету четкого определения. Но есть такая цитата:

Languages that support object-oriented programming typically use inheritance for code reuse and extensibility in the form of either classes or prototypes.

composition over inheritance в вашем варианте вполне применима и в обычном процедурном программировании со структурами и процедурами. И если уж вам так нравится это ссылка, то обратите внимание:

Any business domain class that contains a reference to the interface can easily support any implementation of that interface and the choice can even be delayed until run time.

То есть позднее связывание вся равно присутствует. А позднее связывание — ещё более важный признак ООП, чем наследование.

Ну и взгляните на приведенный пример: The following C# example demonstrates the principle of using inheritance and interfaces to achieve code reuse and polymorphism. Вот в таком виде, как в примере — это ООП. А у вас — лишь использование чужого ООП. Ну примерно как в windows при открытии файла.

И нет, интерфейсы не наследуются, а реализуются.
Интерфейсы и наследуются и реализуются. В том же COM частая ситуация, когда я беру чужой объект и заменяю ему реализацию нескольких методов в одном из интерфейсов. А все остальные интерфейсы и методы — наследуются.

Это просто соглашение о вызовах, без кода и данных, которые можно было бы унаследовать.
Нескромный вопрос — а школу вы уже окончили?! Ну почитайте самостоятельно что такое интерфейс
в ООП.

Разговор был про SQL, что по умолчанию подразумевает реляционные БД, а вы оказывается имели в виду свою объектно-ориентированную разработку.
Да, разумеется, реляционный MS SQL 6.5 на котором и был написан ООП слой. Если бы вы прочитали написанное по ссылке, то сами бы это увидели. То есть там ООБД над реляционной СУБД, как, собственно и у многих.

Понятно, для дискуссии с вами требуется телепатия.
Для спора со мной требуется очень внимательно читать тексты по тем ссылкам, что я привожу. И понимать, что более 30 лет работы программистом дают мне немалую фору в споре. Если у вас в школе остались УКНЦ, то вы даже учились на моих электронных таблицах и на паскаль-москале, который я поддерживал.
Интерфейсы и наследуются и реализуются.

Интерфейсы наследуются другими интерфейсами, а реализуются классами. Это так и в COM, и в C#, и во всех языках где интерфейсы отделены от классов.


Однако, предлагаю не придираться к словам: и так понятно что имелось в виду.

Ну я могу реализовать COM-интерфейсы на чистом С без классов. И могу наследовать часть методов одного интерфейса (в том же Delphi есть соответствующая конструкция). Но к словам действительно нет смысла придираться.
Ну я могу реализовать COM-интерфейсы на чистом С без классов

В терминологии COM у вас все равно получится класс компонента (CoClass). Просто потому что других вариантов вообще нет, все что реализует интерфейс автоматически становится классом :-)


И могу наследовать часть методов одного интерфейса (в том же Delphi есть соответствующая конструкция).

Это вы про какую конструкцию?

Вот реальный пример из сорцов Dlphi 7 (WebSnap\WebModu.pas):
  TCustomWebAppDataModule = class(TCustomWebDataModule, IGetWebAppServices, IInterface
    {IGetWebAppComponents also implemented})
  private
    FAppServices: IWebAppServices;
    procedure SetAppServices(const Value: IWebAppServices);
  protected
    procedure Notification(AComponent: TComponent; Operation: TOperation); override;
    { IInterface }
    function QueryInterface(const IID: TGUID; out Obj): HResult; override;
    { IGetWebAppServices }
    function GetWebAppServices: IWebAppServices;
  public
    { Public declarations }
    constructor Create(AOwner: TComponent); override;
    constructor CreateNew(AOwner: TComponent; Dummy: Integer=0); override;
    destructor Destroy; override;
    property AppServices: IWebAppServices read FAppServices write SetAppServices;
  end;
  end;

IWebAppServices делегирован целиком, а IInterface унаследован, но его метод QueryInterface перекрыт. Можете проверить TCustomWebDataModule — там нету _AddRef и _Release. Магия!
А это важно? В наследнике TCustomWebAppDataModule я могу и один из методов IWebAppServices перекрыть.
Ну разумеется можете, они же есть в базовом классе…
Ну вот и получается, что я могу получить интерфейсы от чужого объекта (ну скажем MS Excel), подменить реализацию одних методов и унаследовать (делегировать) другие. Так что наследование части методов интерфейса — реальность.
Это называется «реализация интерфейса через делегирование», а не «наследование интерфейса».
Вначале делегируем, потом наследуемся и подменяем один из методов, а остальные наследуем. Спорить о точном употреблении терминов — не хочу.

Вначале делегируем реализацию интерфейса, потом наследуемся от класса. Тут нет наследования от интерфейса.


Спорить о точном употреблении терминов — не хочу.

В таком случае зачем вы это делаете? Я сразу предложил не придираться к словам...

Есть такой анекдот
— Добро пожаловать в Общество зануд! Возьмите себе стул.
— Вообще-то, у этого, как вы выразились, стула, нет спинки, так что технически это табуретка.
— Похоже, у нас новый председатель!

Ну в общем признаю себя патентованным занудой. Тяжелое наследие профессии тестера.
А если у вас есть SQL-запросы и они выдают сразу нужную запись — то это уже не трехзвенка. У вас появляется SQL-слой. Конечно, плохо, что ваши SQL-запросы компилируются каждый раз. Но это все равно не трехзвенка.

Вот не согласен. Особенно, если SQL не пишется руками, а где-то под капотом их ORM обрабатывает. Будь на месте SQL базы какое-то key-value хранилище, которое только и может выдавать записи по одной по ключу, вы бы тоже назвали приложение четырехзвенным?

Зависит от насыщенности. Вариант вконтакте, когда из-за высокой степени интеграции с остальными сервисам нельзя опубликовать код СУБД — явная четырыхслойка, а постоянные select без where — явная трехслойка. Остальное — посредине.

Что касается трансляции с С++ (HQL, LINQ) на SQL — не удивляет, если какой-то слой ORM её делает хорошо. Равно как и не удивляет, что обычно она делается плохо.

Можно придумать критерий, вроде такого «Вывести фамилии менеджеров, чьи зарплаты более чем в два раза превышают среднюю зарплату программистов». Если это выразится одним SQL-запросом, выдающим список фамилий — значит есть SQL-слой. Если это будет два запроса (первый для вычисления средней зарплаты) — нечто среднее, а если в итоге мы получаем табличку, по которой фильтруем уже на сервере — это явно трехзвенка.

Основной критерий четырехзвенки — мы не фильтруем результаты на сервере, вся фильтрация идет в SQL.

Ваш критерий понятен, но я ним не согласен. Грубо, я не отношу к отдельному звену механизм формирования сложных DQL-запросов вообще, а с простым WHERE по PK особенно, пока они формируются на сервере приложений. Вот создали вью, хранимку или триггер — появилось отдельное звено, тесно интегрированное с "тупым" хранилищем. Пока его нет, то максимум SQL-слой в звене сервера приложений можно выложить. Да и если есть уже, то не факт, что можно говорить о четырехзвенной архитектуре, если создаются они не в ходе работы приложения, как в вашем примере с шардингом таблиц, просто добавление "ума" хранилищу.

А вот интересная аналогия из АСУТП. Следите за руками SCADA + сервер на персоналка с датчиками по RS-485 — это двухзвенка. Добавили конвертор интерфейсов из RS-485 в TCP/IP — все равно двухзвенка. Сменили конвертор на более умный — это по-прежнему двухзвенка.

А дальше установкой галочек в дизайнере часть функциональных блоков начинают работать на контроллере — и это уже трехзвенка. Почему? Да потому что при отключении персоналки часть функций системы сохраняется. И это ещё более важный критерий, чем место исполнения кода.

Поскольку я в этой системе писал прежде всего компиляторы внутреннего мини-языка на персоналку и контроллер — то не могу согласится с тем, что язык написания кода и место его написания хоть сколько-нибудь существенно.

Существенны два критерия: возможность автономной работы нижнего звена и место исполнения кода.

Теоретически, я вполне представляю себе хитрый ORM, умеющий работать в трех режимах:

  1. Запрос таблицы целиком, фильтрация на сервере
  2. Запросы через SQL
  3. Автоматическое формирование хранимых процедур

Возможно, что-то из этого умеет 1С последних версий.

Что касается «создали вью, хранимку или триггер», то как развертываться у клиента будете? Если у вас что-то тиражное, то есть смысл все это (начиная с CREATE TABLE) создавать прямо из сервера. Подключились к чистой базе — и создали там все нужное.

Но для критерия автономной работы звена — действительно нужно иметь «вью, хранимку или триггер», в этом вы правы.
А дальше установкой галочек в дизайнере часть функциональных блоков начинают работать на контроллере — и это уже трехзвенка. Почему? Да потому что при отключении персоналки часть функций системы сохраняется. И это ещё более важный критерий, чем место исполнения кода.

Вот мы и пришли к тому, что звеном может быть только отдельный сервер, отдельный процесс или что-то еще, что можно отключить.


Таблицы в БД просто так взять и отключить нельзя.

Знаете, контроллер тоже просто так отключить нельзя — вся система встанет.

Речь шла о другом — о том, что при отключении вышестоящих звеньев часть функций системы сохраняется.

Для слоя БД — сохраняется функция хранения данных. Для слоев SQL и view — функция выдачи информации из базы, а иногда — и функция добавления записей в базу. Дело в том, что простые поставщики записей (вроде ККМ) могут быть подключены прямо к view-слою СУБД, минуя сервер приложений.

Насколько я знаю, MySQL — это двухуровневая система. На верхнем уровне — SQL-движок, а на нижнем — несколько database engine, то есть собственно БД.

Похоже, да, способность части системы работать автономно, пускай и без обеспечения полной функциональности всей системы — хороший критерий для определения количества звеньев в архитектуре.


Тогда хранимые процедуры и т. п. в классической SQL-СУБД выделять в отдельное звено получается только с натяжкой, если они неразрывно связаны с базой. Что можно из консоли подключиться к базе и поработать с табличками — сложно назвать "частичной функциональностью", если сервер приложения или иные штатные клиенты СУБД не могут нормально работать без хранимок. Хотя, можно представить себе и ORM, которая при отсутствии возможности подключения к СУБД начинает работать с CSV или JSON файлами :)


Развёртываться у клиента можно с помощью процедур/скриптов развертывания/обновления/миграции, не являющихся де-факто частью системы. Да даже с помощью инструкции наладчику, типа наберите mysql, введите CREATE TABLE…

Что можно из консоли подключиться к базе и поработать с табличками — сложно назвать «частичной функциональностью», если сервер приложения или иные штатные клиенты СУБД не могут нормально работать без хранимок.


А если могут? Не так уж это сложно устроить, чтобы при отказе MySQL касса читала бы цены товаров из InnoDB. Да, обращения только по ключу, но кассе этого хватит. Ну и добавить сколько продано она тоже может.

Собственно кассе SQL уровень нужен в основном для скидок и подарочных карт (ну может ещё что забыл). А минимальный уровень работоспособности — без SQL.

Развёртываться у клиента можно с помощью процедур/скриптов развертывания/обновления/миграции
Можно. :-) Особенно прекрасно это, когда у нас коробочный продукт и встроенная в него СУБД. Ну скажем тот же libmysql.dll

При каких-то особо критических задачах это может и имеет смысл. Но для большинства будет оверхедом переключение между двух и трехзвенной схемами в зависимости от доступности сервера приложений. Обычно проще сделать дублирование сервера приложений, в крайнем случае автономную работу кассы какое-то время при недоступности, чем реализовывать редкий сценарий "база доступна, а сервер приложений лежит", гораздо чаще падает либо канал точки, либо дата-центр/серверная полностью. В том числе это проще не только с технической точки зрения, но и с организационной — персоналу, зачастую с минимальной квалификацией, не нужно иметь несколько сценариев работы, максимум три: система полностью работоспособна, система неработоспособна вообще (нет электричества, например), система работает а автономном режиме (нет связи). Вот для больших точек присутствия с развитой локальной сетью имеет смысл делать четырехзвенную архитектуру с промежуточным сервером приложения в локальной сети, который обрабатывает информацию точки и передаёт её в центр для агрегации, ну или штатно просто проксирует и реплицирует, а в случае отказа центра обслуживает клиентов, дожидаясь восстановления чтобы отправить накопленную информацию.


Какой-нибудь setup.exe вполне может и развернуть новый сервер локально, и создать на нём таблицы и хранимки, и записать в конфиги приложения его параметры. Да, может быть принята схема что продукт поставляется в виде одного бинарника, статически слинкованного со всем что нужно, который ожидает наличия конфига в известном ему месте, и при отсутствии запускает сценарий настройки, но как по мне, то это должна быть отдельная задача от бизнеса, с объяснением ему последствий, хотя бы в виде усложнения обновления на местах.

редкий сценарий «база доступна, а сервер приложений лежит»

Он не редкий, он ежедневный. Каждую ночь — техобслуживание базы. А продажи идут круглосуточно. Посидите ночь в аэропорту — увидите, что у кого ночью не работает, а что работает в усеченном режиме. :-)

Ну так при техобслуживании базы лежит база, а сервер приложений работает :)

Наоборот. Продажи по шрихкоду идут, то есть база живая.
Проблема с книгой приведена для того, чтобы читатель начал думать сам. Во многих простых случаях наследования от «абстрактного товара» достаточно, проблема расширения может не возникнуть вовсе, тем более в аджайлах — кто об этом будет думать загодя. Агрегацию вы путаете с интерфейсами, «товар» — свойство книги, а не наоборот. Насчет цены вы частично правы, это не имманентное свойство товара (в реальном мире это баланс спроса и предложения), а значение из очень многомерного массива. Вопрос, самостоятельный ли это класс объектов «Цена» или просто многокритериальная функция извлечения нужного значения однозначно не решается, но никто не запрещает сделать функцию «GetPrice(...)» товара фасадом.
«товар» — свойство книги

Но ведь это приводит к проблемам, про которые вы пишете. Вот эти вот "в магазине вкладывать в книгу товар, в библиотеке – печатное издание". Зачем так делать, если можно сделать по-другому без этих проблем?

Ваш вариант реализации?

Товар, вернее единица продажи — отдельная сущность, у которой есть цена. У нее есть ссылка на определенный вид товаров (product_id). У product есть product_type_id. На каждый тип можно сделать в коде соответствующий класс, если нужно. Данные достаются через связи по id. Запросы по id простые, их можно кэшировать. Как-то так, с поправкой на предметную область. Основная идея в том, что наследования тут нет, а агрегация идет от товара к книге.

Как-то лихо вы перешли с объектов на таблицы, давайте все же вернемся в терминологии ООП. То что вы предлагаете — правильный путь, но это не решение проблемы, а надстройка еще одного уровня is a part of.
Поясню. Товарная позиция (aka SQU) — действительно контейнер для товара. Но чтобы засунуть объект в этот контейнер, нужно чтобы этот объект обладал свойствами товара (нечто пригодное к продаже). Сделать это для условной «книги» можно тремя способами: наследованием от абстрактного товара, агрегацией реализации абстрактного товара и его экспозицией через свойство (уже упомянуто выше), или интерфейсом.

Ещё адаптером, реализующим интерфейс (контракт) абстрактного товара через работу с контрактом условной книги в, например, системе складского учёта.

По сути, «Товарная позиция» и есть тот самый адаптер, используемый, например, для включения в разные документы. Но можно навернуть еще один уровень.
Как-то лихо вы перешли с объектов на таблицы

Таблицы отображаются на классы в языке программирования. ORM и все такое.


Но чтобы засунуть объект в этот контейнер, нужно чтобы этот объект обладал свойствами товара (нечто пригодное к продаже).

Зачем это нужно? В моем примере туда можно засунуть что угодно, хоть материальный предмет, хоть услугу.

А, ну в целом так и есть. Таблица products хранит все возможные объекты product. А где хранятся данные, специфичные для конкретно этого product_type_id дело десятое. Можно в отдельной таблице, можно в EAV, можно тут же в json. Все равно книгу незачем наследовать от чего бы то ни было, все работает через связи.

А можно отображать любой из этих способов хранения на объектную модель, отображая связь таблиц product<->product_type через наследование. В одних ситуациях это удобно и естественно, например, когда типы очень сильно отличаются друг от друга поведением. В других — нет, когда, например, тип по сути является лишь тегом, субконто для целей учёта, но никак на поведение не влияет.

Вполне допускаю, что в какой-то ситуации это может быть удобно и естественно, но разговор-то идет о проблемах.
Объект «книга» в приложении для библиотеки может обладать свойствами «абстрактного печатного издания» если кому-то хочется, но не должен.

Не должен, но тогда все преимущества ООП использовать не получится. Будете для библиотеки делать объекты "журнал", "газета" и т. д. копипастя кучу кода. Где-то может помочь утиная типизация, но не везде.

Должен. Свойства печатного издания — это место хранения. Без него не получится расставить книги и журналы по полкам после их возвращения читателями на абонементе.

Вы правы, что можно добавлять эти свойства отдельно к книгам, журналам, газетам и так далее. Но добавлять придется. Библиотечная специфика.

Ну так если оно помогает, то какой разговор, все хорошо. Еще можно всякие трейты/миксины использовать.

Засунуть можно и абстрактный Object, от которого все наследуется. Кроме доступа к Id и типу (классу) вам это ничего не даст. Поэтому обычно туда засовывают нечто более конкретное, чтобы дергать нужные в данном контексте свойства и функции.
Ладно, понятно, не будем по кругу ходить.
что? Вы не понимаете разницы между товарами и услугами? Вас бухгалтерии вообще учили?
Ну расскажите мне про складские остатки у услуг. :-)

Ещё интересно про продажу прав, например при продаже электронных книг.

А с чего вы решили, что у услуги в этом подходе обязательно должны быть остатки?


Я пожалуй определение приведу:


SKU — идентификатор товарной позиции (артикул), единица учёта запасов, складской номер, используемый в торговле для отслеживания статистики по реализованным товарам/услугам. Каждой продаваемой позиции, будь то товар, вариант товара, комплект товаров (продаваемых вместе), услуга или некий взнос, назначается свой SKU.

То есть в целом SKU это идентификатор. Связан он с количеством или нет, определяется предметной областью.

Эффективность в общем случае дело десятое. Важнее адекватность предложенной модели для предметной области и стоящих перед системой конкретных задач.


Для создания интернет-магазина путь наследования объекта класса "книга" от абстрактного класса "Товар" скорее всего тупиковый. Вообще вызывает сомнения необходимость создания класса "книга".

Конечно, для чисто интернет-магазина, объект «книга» не важен. Поэтому очевидно, что речи о нем не идет. А вот если у нас издательство или залоговая библиотека. Или вообще полный комплекс — издательство + оптовый инет-магазин + розничный инет-магазин + оффлайновая залоговая библиотека + много-много что?

В любом случае, главное — это выводы автора

Почитайте
Когда многие технические проблемы были решены, оказалось, что ООП очень требовательно к проектированию, так и оставшемуся сложным и недостаточно формализуемым процессом.

Мой субъективный опыт подтверждает, что за исключением фреймворков весьма абстрактного уровня, сделанных «с чистого листа» небольшими группами профессионалов высокого класса, Объектно-Ориентированный Подход на практике в большинстве случаев превращает проект или продукт, переваливший за сотню-другую тысяч строк, в упомянутый Ад Паттернов, который, несмотря на формальную архитектурную правильность и её же функциональную бессмысленность, никто без помощи авторов развивать не может.

С другой стороны, любая неясность в постановке задачи вынуждает разработчиков сосредотачиваться не на её решении, а на архитектуре, позволяющей «без особых затруднений» менять логику приложения и переходить с расчёта зарплаты колхоза на прогноз удоев фермы.

Результат неизменно стабильный…


Если вы участвовали в достаточно больших проектах с ООП, то сталкивались со всеми его недостатками.
Как раз таки на больших проектах все достоинства ооп и проявляются. Участвую (ну как участвую — мой проект :) ) в довольно крупном проекте, больше миллиона строк, Delphi, 15+ лет. Как бы я это всё без ооп поддерживал — не представляю. Классы крайне удобны, на больших проектах особенно!
Ну Delphi… сам его люблю ровно за то, что он не критичен к куче ошибок при проектировании иерархии классов. Одна из особенностей, позволяющих не заметить дурную структуру проекта — это механизм событий, то есть указатель на произвольный метод произвольного класса, лишь бы он совпал по сигнатуре.

Вы прикиньте, что будет с вашим кодом, если вместо TNotifyEvent и им подобных у вас будут две альтернативы:

  1. Вызов функции (не метода объекта, а просто процедуры или функции).
  2. Uses на модуль с описанием объекта и вызов конкретной процедуры. Передать при этом вы можете только ссылку на объект.


Чувствуете в какое спагетти превратится ваш код? Уж не говорю про то, что в дельфи нет множественного наследования, автоматического приведения типов, переопределениz операций. А оно все вносит свою лепту в ООП-хаос.

Ещё один важный нюанс состоит в том, что если вы пишете a la TCustomGrid — это не ООП. Это обычный процедурный код, завернутый внутрь объекта. 6 тысяч строк на модуль, из них 4 тысячи на один объект — это ни в какие рамки ООП не лезет.

Я тоже участвовал в больших проектах на Delphi. Сделанный моей командой — 135 тысяч строк и второй — 450 тысяч строк. В обоих — примерно как в TCustomGrid — большие объекты с завернутым в них процедурным кодом, минимум собственных виртуальных методов зато достаточно много собственных событий. Померьте сколько у вас vurtual и dynamic и сколько of object, какие типичные размеры кода классов (в строках)

Событийное программирование — это все-таки не ООП, а чуть иная техника. Зато — техника очень хорошая.
У нас не гриды, бизнес-логика (медицинское по, сети, просмотр изображений, уже и видео, базы, 3д рендер, очень много всего). Гриды и вся визуальщина покупная. Без ООП я бы просто закопался. А с ним очень удобно. Переопределил несколько методов, сделал очередной слой абстракции и всё отлично работает. Я даже сам удивляюсь, как многие вещи получатся почти автоматически: описываешь поведение разных слоёв, потом всё само выстраивается, многие вещи работают сразу как нужно, хотя я этого даже не писал, и даже не задумывался :)
Интересно, а почему вы не ответили на вопрос? На всякий случай повторю: померьте сколько у вас vurtual и dynamic и сколько of object, какие типичные размеры кода классов (в строках).

В дельфи есть много путей написать хороший код не в стиле ООП:

  • Когда у вас классы по тысяче строк и более — это не ООП. Это процедурное программирование, завернутое в классы.
  • Когда событий у вас намного больше, чем virtaul и dynamic — это не ООП, это событийное программирование.
  • Когда вы активно переопределяете проперти через функции — это отличный механизм, но это не ООП.
  • Когда у вас 1-2 наследника на базовый класс — это не ООП.
  • Когда у вас много солитонов — это не ООП.
  • Когда вы пишете наследников заданных в системе классов (вроде TThreadObject) — это не ООП.
  • Когда бизнес логика вас волнует больше, чем полнота функций объекта — это не ООП.


А ООП — это совсем другой стиль программирования:

  • Когда вы пишете много мелких, до 200 строк, универсальных классов, а потом соединяете их в приложение — это ООП.
  • Когда вас заботит больше универсальность классов, чем бизнес-функции — это ООП.
  • Когда вы разбиваете один класс на несколько наследующих друг друга классов. потому так архитектурно правильней — это ООП.
  • Когда у каждого базового класса по 5=10 наследников — это ООП.
  • Когда вы регулярно слышите от сотрудников: «Мы не модем этого сделать, это нарушит структуру классов» — это ООП.
  • Когда вам постоянно не хватает множественного наследования и вы взамен используете интерфейсы — это ООП.

многие вещи работают сразу как нужно, хотя я этого даже не писал, и даже не задумывался :)
Это свойство робастного кода, оно бывает не только у ООП. У меня аналогично, хотя написано в процедурном стиле, завернутом в большие объекты. И только на уровне контейнеров (наследников TList) есть капелька ООП.

Событийное программирование и ООП — это все же не взаимно исключающие парадигмы, ничего не мешает им присутствовать в проекте совместно.


Когда вы регулярно слышите от сотрудников: «Мы не модем этого сделать, это нарушит структуру классов» — это ООП.

Нет, это ленивые сотрудники.

Когда вы регулярно слышите от сотрудников: «Мы не модем этого сделать, это нарушит структуру классов» — это ООП.

Нет, это ленивые сотрудники.

Можно переформулировать: "мы не успеем этого сделать за отведённое время" :)

Нет, это ленивые сотрудники.
Не совсем так, ибо структура классов реально рушится. Просто не все удается предвидеть заранее.

В обычном ООП, если мы хотим делегировать обработку из класса B в класс D, то мы в интерфейсе B должны сделать ссылку на объект D. Если таких связей много — мы получаем плохой дизайн и труднораспутываемсую лапшу.

В событийном ООП мы просто в классе B делаем вызов событий. И в некотором модуле S — присваиваем событию обработчик из класса D. Таким образом, классы B и D друг о друге не знают и лишних зависимостей не возникает.

Так что событийный ООП менее требователен к качеству дизайна. Тем удивительней, что его нет в С++. Или в С++17 уже добавили?