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

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

Время на прочтение5 мин
Количество просмотров232K
Всего голосов 68: ↑53 и ↓15+38
Комментарии474

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

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


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


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


  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 уже добавили?
событийном ООП мы просто в классе B делаем вызов событий. И в некотором модуле S — присваиваем событию обработчик из класса D.

… и с этого момента в классе B есть ссылка на объект класса D :-)


Если же вы скажите, что тип этой ссылки отличается от типа D — то напомню что в первой ситуации ("то мы в интерфейсе B должны сделать ссылку на объект D") тоже не обязаны использовать тип конкретной реализации. Интерфейсы в нормальных языках не просто так придумали.


Тем удивительней, что его нет в С++. Или в С++17 уже добавили?

Как бы тип std::function ввели еще в C++11, по функциональности он даже немного богаче чем Method Pointers из Delphi.

Как бы тип std::function ввели еще в C++11,
Значит не понимаете сути. По вашей же ссылке
// store a call to a member function
    std::function<void(const Foo&, int)> f_add_display = &Foo::print_add;

То есть там, где описывается f_add_display — надо иметь описание класса Foo. А мне надо что-то типа std::method<void(const class *&, int)> f_add_display, то есть ссылку на метод любого класса.

в первой ситуации («то мы в интерфейсе B должны сделать ссылку на объект D») тоже не обязаны использовать тип конкретной реализации.

Но какой-то — обязаны. А во второй ситуации — просто сигнатура. И классу D тоже не нужно об этой сигнатуре знать.

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

Вы даже не попытались разобраться, увидели первый попавшийся пример и обругали его… А надо-то было смотреть не только на четвертый пример, но и на третий!


К приведенной вами функции можно применить std::bind, зафиксировав тем самым первый аргумент. И получится ровным счетом то же самое что и Method Pointer из Delphi!


Вот так получается:


std::function<void(int)> f_add_display = std::bind(&Foo::print_add, foo);
А так получится?
extern std::function<void(int)> f_add_display;
if (foobar)
  f_add_display=std::bind(&Foo::print_add, foo);
else
  f_add_display=std::bind(&Bar::print_add, foo);


Вопрос, к сожалению, не праздный.
НЛО прилетело и опубликовало эту надпись здесь
Да, спасибо. Кстати, первый случай, когда фича из C++11 лично для меня полезна.
На самом деле, проекты достаточно большие, что бы было всё — и классы по 200 строк, и большие классы, и по 5 наследников, и длинные методы (в некоторых классах). Где что максимально удобно — там это и используется. От множественного наследования и интерфейсов пока как-то удалось воздержаться разве что.
И как? Есть желание заменить большие классы на ООП? :-)

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

Как бы я это всё без ооп поддерживал — не представляю.
Как видите — существенная часть кода вполне живет без ООП. А называется это по науке "компонентно-ориентированное программирование". И у него намного меньше проблем, чем у ООП.
И как? Есть желание заменить большие классы на ООП? :-)

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

у меня как раз таки довольно длинные методы бывают перегружеными :) так что — врятли это совсем 'без ооп', перегружать без ооп было бы нечем.
вообще — это всё спор о терминах. что считать ооп, а что нет.
моё мнение: если есть перегрузка + совмещение методов и полей — то это ооп, всё остальное — нет. насколько мелко классы и методы разбиты — это уже частности реализации.
у меня как раз таки довольно длинные методы бывают перегружеными :)

Довольно длинные методы — ещё одно отличие от классической ООП.

вообще — это всё спор о терминах. что считать ооп, а что нет.
Более-менее да, спор о терминах. Но как видите — вполне можно иметь большой код «с элементами ООП» и поддерживать его. То есть классический ООП — далеко не единственное решение.

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

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

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

Ну, выражение — именно "ява головного мозга", и связано оно с тем, что именно люди, работавшие на яве, привыкли делать 100500 слоёв, утраивая объём кода.
Как я понимаю, это специфика работы в кровавом энтерпрайзе — пусть разработчики в сумме выполнят втрое больше работы, зато каждый из них может быть легко заменён, ибо между его куском кода и остальным есть прокладка.

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

Во первых, когда я с этим столкнулся, я не знал даже о существовании теорката.
Во вторых, мне надо понимать не теорию, а как оно сделано. Пока у нас нет множественного наследования — реализация проста и почти очевидна. VMT — элегантная концепция, а новые поля при наследовании вообще просто добавляются в хвост. Как только добавляем множественное, а тем паче виртуальное, наследование — возникают какие-то горы костылей, разные в разных компиляторах.
В третьих, в какой-то момент оглядываешься и осознаёшь, что кроме двух основных случаев (наследование от интерфейса и микс-ин, причём если микс-ин использует методы объекта — он обычно сделан через CRTP) множественным наследованием особо и не пользовался, а виртуальное вообще применял примерно два раза, когда иерархию классов переделывать было лень. И задумываешься — а зачем оно всё?

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

Вы будете смеяться, но это работает:
<?php
$foo = new Foo;
$bar = new $foo;

Особенность реализации, которая не факт, что всегда будет работать.

Медленно, без лишних движений закройте тег!

Насколько я помню, по пхпшным стайлгайдам рекомендуется этот тэг не закрывать в PHP-only файлах
Да, лучше не закрывать. Таким образом все пробельные символы после кода, будут проигнорированы, иначе будут отправлены.
НЛО прилетело и опубликовало эту надпись здесь
А я бы так и сделал — объявил бы кучу примесей по функциональности и комбинировал бы из них нормальные классы
Чертёж — это точно конкретный класс. По нему изготавливаются конкретные экземпляры — инстансы класса, они же объекты.
А ошибки и дефекты, у каждого свои, куда записывать?
НЛО прилетело и опубликовало эту надпись здесь
Слишком много сущностей сразу становится.
А ведь ООП в частности и АТД в целом ведь совсем не об этом. Любые данные, как бы ни были они представлены, являются некоторой упрощённой моделью объектов реального мира, содержащие лишь значимые характеристики.
Если какая-то модель проектирования реализует обмен сообщениями, то он подходит для описания взаимодействий, моделируемых обменом сообщений. Если надо иерархия вида is-a, то моделируем наследованием. Если при хождении по иерархии надо изменчивость поведения — полиморфизм. Если надо защищать приватные данные или спрятать детали — инкапсуляция. Создание по шаблону — можно прототипы поюзать, если хотим заменить детали у конкретных сущностей без изменения общего поведения большинства — сделаем делегирование. И так далее и тому подобное.
No silver bullet.
Вы уверены, что проблема действительно есть?
Студентов на протяжении 4х лет такой жести учат (должны учить) по сравнению с основополагающей (но от этого не менее тривиальной) парадигмой, что проблема и статья о ней выглядят высосанными из пальца.
НЛО прилетело и опубликовало эту надпись здесь
В который раз убеждаюсь, что объяснить суть программирования вообще, а не только ООП, простому человеку, особенно не-инженеру, если не невозможно, то достаточно сложно.

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

Ну то есть как здесь, с самолётами. Мы берём конкретный объект и приводим пример как бы это было реализовано в рамках определённой парадигмы. То есть проводим реверс-инжениринг от того, что все понимают (самолёт) к тем чертежам, которые его моделируют, требованиям, которые его описыват и теориям, которые объясняют как эта шайтан-машина вообще в принципе может лететь.

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

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

Тут как бы не избранность. Тут проблема в объяснении простым, доступным, языком. И проблема не только в том, что бы объяснить как. А и объяснить зачем. Как я уже писал. Сложно объяснять абстрактные вещи. Многое, пока сам не пощупаешь, не понятно, увы. Хоть как объясняй.

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


Если мы говорим о "сути программирования вообще", там даже особых абстракций нет. Это просто процесс составления на специальном языке задания для вычислительной машины. :) Всё. Объясняется просто на конкретных примерах даже детям и домохозяйкам.


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

на специальном языке

Язык вообще, а специальный тем более — уже абстракция :)

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

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

технический ум — тот который на входе принимает минимум информации((a+b)в степени n) и выдает результат зачастую длиннее входной информации.

Как то так я вижу отличие.

Гуманитарный ум — тот, который в условиях неполноты информации больше доверяет интуиции и контексту, а технический — либо теорверу, либо выдает "undefined" :)

"уровню развития когнитивных способностей" чем отличается от "они чуть ли не рождаются такими"? Разница в развитии предполагает разницу в онтогенезе организмов, в т.ч. внутри одного вида. Разница эта настолько сильно выражена, что впору дополнить довольно толерастическое деление из "кровавого совка" на мат/гум склад ума чем-то более розовым пушистым и добрым из мира ласкового капитала: кретины, дебилы, гении, альфачи, беты, омеги, бездарности, нищеброды пожизненные, властелины и т.д.

Оба методы неправильные, если объясняется ООП, а не язык С++ или Java. :-)


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


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


Понятие виртуальных методов тоже не нужно: для того, чтобы его вводить, надо, чтобы существовали не виртуальные (что, опять же, деталь реализации конкретных языков). Можно просто считать все методы виртуальными по определению. Тем более, раз мы исключили наследование, то разница вообще отсутствует.


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

Собственно, здравствуй, Smalltalk?

В принципе, да. :-)


Остается только одна вариация, на мой взгляд, существенная (в отличие от наличия-отсутствия классов или там наследования). В smalltalk я могу послать любому объекту любое сообщение и заранее не знаю, отреагирует ли он на него — и это нормально. В языках, где явно (как, скажем, в Java) или неявно (как, скажем, в Go) объявляется контракт, нарушение контракта либо не скомпилируется, либо вызовет ошибку времени исполнения. Может быть и смесь подходов — как в Ruby с method_missing или в PHP с __call().


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

Поэтому и хорошо бы студенту объяснять ООП на примере какого-то простого языка.
А может быть даже фреймворка. Берешь Ruby on Rails, создаешь модельку, у нее 5-10 методов.
Ну и просто в консоли делается
post1 = Post.new(title: '123')
post1.save
post2 = Post.new(title: '234')
post2.save
puts post1.title
puts post2.title
И не важно, что на этом этапе студент не понимает, где тут инстанс, где тут класс, и то же, Post — это тоже инстанс, но другого класса, да еще унаследованный от десятка других.

Дальше дается задание — создать 10-20-30 таких моделек + методов.

Потом спускаемся ниже, на уровень Ruby.
Где уже ручками надо задать initialize, ручками прописать attr_reader, attr_writer.
Даем примеры на усвоение. Вот тут можно заикнуться, кто тут инстанс, и пару слов про наследование. Опять же примеры.

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

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

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


Под влиянием Rails и подобных ему фреймворков, а точнее, под влиянием Active Record, в массовом сознании откладывается некорректная идея о том, что модель — это такая штука, в которой есть данные, и в ответственность модели входит сохранение данных на постоянное хранилище и загрузка данных с оного. Что — при всей заслуженной популярности Active Record как средства быстрой разработки — в корне неверно. Если это так, почему модель называется "модель", а не какой-нибудь "слой работы с данными"? Мы знаем, что такое модель самолета, например. Речь идет о воспроизведении некоторого объекта в виде образца или схемы. Когда мы пишем программу, мы моделируем объекты из предметной области программным кодом. И у модели прежде всего важно ее поведение (то есть, реакция на сообщения — иными словами, реализация методов): совокупность моделей из предметной области и является в целом моделью предметной области. Персистенция же не имеет никакого отношения к моделированию предметной области, являясь вынужденной инфраструктурной задачей, которая возникает не из-за особенностей предметной области, а из-за технических ограничений (например, если SSD-накопители по цене и производительности сравняются с ОЗУ, необходимость в персистенции вообще отпадет). Обучение на примере ActiveRecord приводит к тому, что моделирование предметной области средствами ООП фактически заменяется банальным структурно-процедурным программированием: класс воспринимается как сишная структура или паскалевская запись, в которую воткнули функции load() и save(). Итогом будет такое умозаключение: "я понял: у нас была отдельно структура и отдельно функции load и save, а теперь мы объединили данные и код и получили объект!" Понял-то понял, только это не совсем ООП. Это антипаттерн "Anemic Model", совмещенный с [анти]паттерном ActiveRecord.


Если уж брать язык Ruby, то в качестве показательного примера стоит брать не ActiveRecord, а "чистые" модели и ROM. Впрочем, если говорить об изучении ООП, проще всего вообще вопроса персистенции не касаться — там всегда костыли :-)

Для меня более наглядным был бы пример математический. С логическими операциями. Например написать класс с работой над матрицами.

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

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

Да хоть к процедурному. Концепция АТД существует почти везде, включая чистый С и некоторые диалекты sql.
В ООП, насколько я его понимаю, в дополнение дается возможность строить иерархии АТД (наследование+полиморфизм).
В полном понимании ФП я сомневаюсь, но оно вряд ли про то, что мы просто объявим четыре функции.

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


В ООП, насколько я его понимаю, в дополнение дается возможность строить иерархии АТД (наследование+полиморфизм).

Полиморфизм это не прерогатива ООП. Вполне его можно встретить и в ФП.


Я к тому, что комментарий можно переписать вот так:


"Для работы с матрицами (ООП|ФП|ПП) излишне. Достаточно описать абстрактный тип данных с пачкой методов: транспонирования, ранга, умножения, сложения ну и еще чего-нибудь." и ничего не изменится.

НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь

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

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

ни один язык не может доказать корректность данных в файле на этапе компиляции.

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

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

"Если мы сделали унифицированную модель самолет с определенным размахом крыльев и 2 реактивными двигателями, мы можем быть уверены, что любой из наших самолетов (военных, гражданских или грузовых) сможет сесть на ВВП полосу предназначенную для данного типа самолета. Это полиморфизм"


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


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

Бомберы должны разгрузиться перед посадкой (наверное не актуально, но пусть), пассажирские объявить «пристегните ремни, не вставайте с кресел», грузовые — просто сесть :)
инстанс (объект) класса

экземпляр класса === объект

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


???
ФП самая простая парадигма. Минимум концепций, которыми надо научиться пользоваться, минимум возможности ошибиться в коде.
Не сказал бы, что ФП — самая простая парадигма. То же одно из важнейших понятий — каррирование — с великим трудом достучалось до моего сознания.
Есть доклад Рича Хикки «Simple Made Easy». ФП — простое, но не лёгкое. На самом деле не лёгкое оно только когда есть опыт в других языках и какое-то устоявшееся понимание программирования, которое в ФП отличается
Вот как раз каррирование — не более чем стиль, пусть и широко распространенный. Все что можно сделать с каррированной функцией — можно сделать и с обычной, общие концепции ФП на каррирование не завязаны.
Каррирование не такое уж и важное понятие. То есть оно становится очень важным, когда начинаешь связывать ФП с теорией категорий, а это не обязательно делать в самом начале. А без этой связи каррирование просто удобный прием.
Да и сложность в понимании скорее наведенная непривычностью при переходе с императивных языков, где принято считать, что функции — это магические сущности, создаваемые компилятором, а программист максимум может манипулировать ссылками на них. Еще стоит быть знакомым с элементарной теорией множеств и владеть понятием пары, а оно совсем не сложное.

ФП как парадигма проста в том же смысле, в каком просты RISC-архитектуры по сравнению с CISC. :-)

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

Мне кажется человек ошибся и говорил про процедурное программирование. ФП же нифига непростое, т.к. матан.

ФП математично, но оно и математика скорее дополняют друг друга. То есть параллельное изучение ФП и математики упрости оба процесса, но и не является необходимум.
Из математики для ФП необходимо знать только элеметнарную теорию множеств. Но она и для императивного программирования необходима, хоть и не все это осознают. Более продвинутые дисциплины, такие как теория категорий, теория типов, матлогика полезны, но и владение ФП при изучении их будет не менее полезно.
Критиковать всегда проще, но я не очень понимаю, зачем для объяснения ООП пытаться брать примеры из физического мира? Почему бы не взять документ (электронный) или файл, например? По-моему, гораздо ближе к идеологии ООП.
А Вы не объясните ООП на примере файла? Ну пожааалуйста
Шаблон — класс, документ на основе шаблона — объект.

Это вы прототипное ООП объяснили :-)

Прототипное — скопировать документ и начать редактировать его :) Под шаблоном я имею в виду не просто "рыбу", а отдельный формат типа .ott, а не odt. :)

Разве ott чем-то отличается от odt принципиально?

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

Ну да, в прототипном ООП все так же.
У файла есть не только содержимое, но и атрибуты (мета-данные), которые могут наследоваться от папок, файловых систем, протоколов доступа и носителей.
Например, read-only доступ к файлу на CD.
Например, так:
«Графический файл» — интерфейс, описывает методы «getHeight», «getWidth» и т. д.
«Файл JPEG», «Файл GIF» — классы, реализующие интерфейс.
«Я_и_моя_любимая_кошка.JPG» — экземпляр класса.
Открытие любого графического файла программой для просмотра — пример полиморфизма
Просто все UI-штуки в компутере берутся и брались из реального мира, чтобы человеку стало удобнее и комфортнее работать с компом («рабочий стол», «папка», «корзина», «шаблон», «документ», ...).
Скажу как начинающий.

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

Достаточно большое и сложное надо долго писать. Но это наверное единственный работающий способ.

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

Там нет наследования (во всяком случае я его там не встречал).
Как уже отметили выше, оно необязательно, но тем не менее обычно и его проходят.

Как минимум, все геометрические фигуры являются геометрическими фигурами :)

Мне кажется, что это будет интерфейс (концепт?) IFigure. Насколько я понимаю, после компиляции он будет вырезан.

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

Необязательно совсем большое. У меня было так. Нам задали сделать какой-нибудь класс со свйоствами и как-нибудь его использовать. Я решил сделать рисование формы на экране (в DOS, BGI-графика). Думаю, сделаю сначала как обычно, с переменными, потом буду разбираться как из этого класс сделать. Ну что там, x,y,w,h. Для 2 форм объявляю x1, y1, x2, y2… Блин, уже 8 переменных, как бы их объединить. И как их называть если будет неизвестное количество. Ага, наверно классы для этого и нужны. Еще метод отрисовки туда удобно запихнуть. И инициализацию в конструкторе сделать. И переопределить отрисовку в другом классе, если нужна особая форма. Какая удобная штука оказывается.

Да, быстрее. Садитесь и пишите.
Так и ЧТО писать? Суть же в том, чтобы ученика привести к этому.
Ответ на вопрос «ЧТО писать» неизменен — пишите код.

Что именно замоделировать — выбирать вам. На удивление хорошо помогает приблизить понимание ООП RTS а-ля StarCraft. А к учителю уже приходите с конкретными проблемами, e.g. «Анатолий Викторович, я тут для танка, авианосца и пехотинца общего абстрактоного предка сделал, и вот никак не могу понять...»
Как раз-таки в играх намного лучше подходит Entity Framework подход, нежели создание иерархии. Выделяя отдельные способы поведения в отдельные компоненты и собирая из них новых юнитов, как из лего, мы можем отказаться от метода «всех под одну гребенку» и избавляемся от огромного God Blob предка, в котором половина методов используются только в половине наследников а в остальных — заглушки.
Отвечу с другой стороны баррикад.

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

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

Звучит так: «Представь, что есть комната, в которой находятся несколько пылесосов. ООП нужно для того, что-бы дать каждому из этих пылесосов свою задачу по уборке комнаты.»
Так можно прекрасно дать свою задачу без всякого ООП.
Можно. Я вообще дизайнер. Я вижу всё в виде иерархий, в виде форм и объектов, если можно так выразиться. Для меня это показалось хорошей аналогией. Вот и всё. А так, я вообще, за нодовое программирование.
Объектно-ориентированное программирование это не описание объектов реального мира (точнее не только и столько описание реального мира)
Проблема ООП как раз в том и состоит, что в общем-то оно хорошо умеет делать только это, а вот разработка ПО — это как раз не только описание объектов реального мира. Например логирование — типичный пример сквозной функциональности, которую ООП вообще никак не решает, но это прекрасно ложится в Аспектно-Ориентированную парадигму. Туда же можно отнести проблемы тестирования, а так-же все то, где синглтон кажется хорошим решением — типичная точка среза (join point) в АОП.

Да, ООП хорош для описания высокоуровневых абстракций, но дальше он должен уступать место другим парадигмам, таким как функциональное программирование, метапрограмминг и пр. Поэтому мне так нравиться C++ — это мультипарадигменный язык. А попытка писать на нем исключительно в стиле ООП сводит на нет все его преимущества, для этого есть Java, C# и пр. Да и множественное наследование в C++ — это вообще не про ООП, зато мощнейший инструмент для метапрограммирования.
Вообще-то АОП — это развитие ООП, оно даже описывается в терминах ООП. А вы его как какую-то отдельную парадигму упомянули…

Кстати, C# тоже вообще-то мультипарадигменный язык. Причем куда более «мульти» чем тот же C++.
НЛО прилетело и опубликовало эту надпись здесь
Лучше ли примеры, предолженные автором? Не уверен.


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

Все нормально, требуем в описании самолета конкретную ветку в иерархии двигателей — Д123 с подвидами Д123-А, Д123-Б, Д123-В.

TLDR: слишком сложная тема для одного примера.

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

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

У наследования есть несколько проявлений и форм (класс\абстрактный класс\интерфейс\Generic), иногда его используют не по назначению (см. пример выше), иногда наследование вообще явно запрещают. И это лишь в C#, в иных языках и не так извратиться могут. Короче, для демонстрации наследования надо привести 3 разных примера как минимум. И желательно показать примеры на трех разных занятиях, а объединить картинку на четвертом занятии.

Инкапсуляция проще, но в терминах C# там желательно уже иметь представление о наследовании (ибо методы бывают protected), что приводит нас к трем-четырем занятиям (public\private, потом internal для тестов, потом protected, потом объединение знаний).

Наконец, полиморфизм. Навскидку, это наследование абстрактного класса, возможно использование Generic, override методы. Опять-таки 3-4 занятия.

Короче, 12 занятий, из них 9 примеров. Чтоб примеры впитались, желательно ввести студентов в ситуацию «сначала было плохо, потом хорошо». Девять разномастных примеров на ООП-темы. Еще желательно сильно связать языковой курс и курс ООП, как минимум на уровне 9 языковых фич для примеров (и всей базы для них).

ООП — это что-то около 19 часов в семестр. У меня нет идеи проекта на 19 часов и 9 примеров (плюс еще полезная функциональность-экзамен-резерв). Вот и получается, что простые примеры (одинаково) неэффективны, а значит можно давать любой. В процессе работы студент разберется, вектор ему задан.

Мысли выше — крайне грубо приближены к реальности. Есть альтернативы, цифры могут не совпадать, все персонажи выдуманы. Основное следствие: ООП слишком сложная тема для раскрытия одним примером.
ООП слишком сложная тема для раскрытия одним примером.
Я привел такой пример.
Инкапсуляция проще, но в терминах C# там желательно уже иметь представление о наследовании (ибо методы бывают protected), что приводит нас к трем-четырем занятиям (public\private, потом internal для тестов, потом protected, потом объединение знаний).

public/private/… формально к инкапсуляции не относятся, это сокрытие. Публичные члены класса так же инакпсулированы, как и приватные.

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

Просто заявить: «класс описывает некий набор объектов, и все значимые для объекта свойства\методы хранятся в классе, а не черте где. Это облегчает тестирование, проще ориентироваться по коду, сложнее прострелить себе ногу, и инкапсуляция нарушается через методы расширения (на самом деле нет).»?

В принципе, может сработать, но как-то сильно очевидно. Сильно теоретично.

"Всё своё ношу с собой" :)

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

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

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

Понять и объяснить что такое объектно-ориентированное программирование, лучше всего изучив историю возникновения предмета. В этом плане рекомендую классику — «some features of the simula 67 language». Когда читаете, следует обратить внимание на некоторые аспекты, остающиеся за кадром. Симула (что понятно даже исходя из названия) предназначалась для построения симуляторов некоторой предметной области. Для симуляции требовались небольшие самостоятельные взаимодействующие программки. Это и были self-containing program или как они иначе называются «объекты». Именно так. Объекты — вообще-то самостоятельные (замкнутые, самодостаточные, автономные, изолированные) программы. Это напрямую написано в документе «some features of the simula 67 language», почему я и говорю — начинать изучение чего-либо с первооснов самое благое дело.
Внутри компьютера такого устройства конечно не было (не так оно и сейчас — данные отдельно, а методы объектов как были старыми, добрыми процедурами, так и остались). Но для программиста организация программы выглядела как отдельные, активные блоки программ, «данные с кодом» или «активные данные», «данные с активностью» (data + activity). Несмотря на внешнее представление, всегда полезно помнить — ООП это лишь способ организации текста программы и не более того (хотя найдутся те кто будет протестовать, увы, это так). ООП можно «симулировать» в процедурных языках, создавая структуры (записи, пакеты, кортежи) данных и семантически связанные с ними функции. Полезно также заметить, что Страуструп, создавая C++ был занят в большом проекте симулятора, вот почему он обратил внимание на Simula и вот почему он столкнулся с объектно-ориентированной парадигмой вообще. Прикручивать объекты к Cи они решили после того как Garbage-collected симула их не устроила. Поэтому они стали прикручивать объекты к относительному быстрому системному Си. Первое расширение языка Си так сначала и называлось «Си с классами». Также полезно знать, как они это сделали — они не писали новый компилятор вообще. Они написали транслятор расширения языка в старый добрый Си, тексты которого компилировались исходно существующим компилятором cpre. И это само по себе говорит о многом о ООП. Грубо говоря, на входе заходил синтаксис в котором у структуры были приписаны функции, а на выходе выходил обычный текст си-программы, где были старые добрые структуры (с данными) и отдельно — функции для работы с ними. Отсюда мы видим — что ООП это вовсе несколько не то, что принято объяснять. ООП в исходном своем понимании, это прежде всего объекты, где объекты это полноценные изолированные программы. Эти программы также могут пониматься как «активные данные», получающие процессорное время отведенное в оригинале на симуляцию. И, соответственно, в исходной симуле внутренний «мир» состоял из таких вот самостоятельных программ-объектов, взаимодействующих и существующих параллельно. Так я рекомендую понимать ООП-программу и вам, потому что так оно без изменений сохранилось и сейчас. Такова самая-самая суть ООП, та самая, почему оно возникло.
К этому следует добавить, что наследование вообще не является существенной чертой объектного программирования, вопреки известной попсовой формуле “инкапсуляция+наследование+полиморфизм”. Поэтому объяснять сущность ООП через наследование – это как объяснять назначение самолёта через принцип работы двигателя внутреннего сгорания.

Возвращаясь к первоначальному примеру автора, кошечка умеет бегать и собачка умеет бегать. А в каких отношениях они находятся с рыбоньками и кактусиком, равно как кого из них мы назовём няшными питомчиками – вообще не имеет отношения к существу вопроса.
К этому следует добавить, что наследование вообще не является существенной чертой объектного программирования, вопреки известной попсовой формуле “инкапсуляция+наследование+полиморфизм”. Поэтому объяснять сущность ООП через наследование – это как объяснять назначение самолёта через принцип работы двигателя внутреннего сгорания.

Абсолютно верно! Я это выразил в одном из своих дальнейших опусов под этой статьей.

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

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

Стоит умственно осуществить такую подмену — и все таинственные откровения, aka принципы, например LSP (Liskov substitution principle) становятся ясны по-умолчанию. Кстати, у Гослинга наследование в Java обозначается как extends…

И есть еще одно.
Вообще говоря, реальный ООП-дизайн без полиморфных подстановок aka программирования на уровне интерфейсов aka «без конкретных типов» — это не очень хороший дизайн, поскольку он не расширяем без переписывания (не следует open-closed). Так вот, если бы механизм наследования назывался «версии», а не «наследники» как сейчас, то тогда по-умолчанию было бы понятно, что не надо трогать и постоянно переписывать первую (и обычно единственную и последнюю) версию. И вообще, возникали бы вопросы, для чего-то же нужна вся эта версионность?

Вот так, я считаю, один неудачный термин «наследование» породил непонимание что это такое, и как следствие привел ко всей этой перманетной трагедии…
Учился на мехмате МГУ в конце 80-х. Лекция по программированию в большой аудитории (на весь поток, половина курса). Лектор у доски невозмутимо рассказывает про «исполнитель — шагающий человечек», а в аудитории стоит шум, вой, летают бумажные самолётики, студенты-математики протестуют против изнасилования разума.
Сейчас по необходимости пытаюсь программировать. На моё неприятие ООП Сергей Трифонов (работал в Яндексе с 90-го года с перерывами семь лет) ответил:
«Эти языки предназначены для того, чтобы большие команды программистов могли вместе работать с обширным массивом кода. Эта задача требует введения очень серьёзной дисциплины, это ни разу не бесплатное удовольствие. Вам это ни за фигом не нужно, вот Вам и не нравится. Правильно, что не нравится, это просто не Ваше.»
66george.livejournal.com/381121.html#comments
Лектор у доски
— Кушниренко, поди?
Не помню. Помню, что семинары вёл Немытов.
Не очень понял, зачем чертежу методы взлёт(), полет(), посадка()
Или в конструкторе класса алгоритм складывания самолётика из листа?
Если не вдаваться в реализацию модели ООП в конкретном языке, то объект (хотя привычнее класс) является самой обычной заготовкой. Например, в реальном мире заготовками являются электрические выключатели и круглые металлические болванки, которые сами по себе ничего не делают, но из которых можно сделать что-то полезное – электрический выключатель встроить в схему освещения помещения, а из металлической болванки выточить на токарном станке спортивный кубок.

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

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

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

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

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

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


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

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

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

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

Метод "голос" у рыбки не делает ничего или возвращает звуковой фрагмент нулевьй длины (в зависимости от постановки задачи).


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

Кактус очень даже ест. Только способ питания у него другой.
Вот если был метод "глотает", пришлось бы поступить как с методом "голос".


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


Мы уже запутались,

Не обязательно.


но Вовочка спрашивает: «а где в этом зоопарке статические методы, интерфейсы, абстрактные классы и чем отличается объект класса от самого класса?». Объяснить, несомненно, можно, но сложно. Понять, еще сложнее.

А Вовочке остается просто объяснить все это на примере сформированной иерархии классов.

А потом приходят братья Райт, разработчики конвертопланов (bell v-22 osprey), конструкторы автожиров и «летабщих крыльев» и исходный чертёж летит в печку. Потому что абстрактный чертёж вашего самолёта не позволяет создать подходящюю имплементацию и интерфейсы не подходят.
А экземпляры данных классов тем не менее похожи на экземпляры имплементаций вашего чертежа.

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

Возможно для понимания интерфейса лучше применять аналогию «способ использования» (хотя название не совсем точное). Например «летательный аппарат», «многоразовый аппарат», «грузоперевозчик», «пассажироперевозчик» и тп. Для летательного аппарата достаточно уметь взлетать и лететь. Для многоразового аппарата добавить еще и умение садиться. Для грузоперевозчика достаточно описать грузоподъемность, скорость. И т. п.
Интерфейс относиться к более высокой логике, чем сам класс. Например, транспортная компания будет использовать интерфейс «грузоперевозчик». Туристическая «пассажироперевозчик». Им даже не нужно особо знать, что это вообще самолет. Из-за этого появляется требование о компактности, минималистичности интерфейсов. Кроме того, конкретный тип самолета может реализовать много интерфейсов.
Начал изучать программирование, как раз сейчас начал касаться ООП.
До этого примеры на кошках\собаках были понятны на 90%, здесь пазл сложился на 100%.

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

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

Ооо… вспомнил свое первое знакомство с ООП в универе. Наш «молодой» преподаватель по С++ на все вопросы отвечал одними и теми же мудренными фразами из лекции(бесконечно повторяя их на разные лады), приводил все тот же один пример с машинами. С презрением приговаривая: «Ну, что тут может быть не понятно?».
Что прикольно, нам казалось, что мы тупые, а препод настолько умный, что ему трудно изъясняться с нами дуболомами.
Первое, что мы сделаем, это начнем чертить некоторый общий чертеж для всех моделей. По этому чертежу еще нельзя построить реальный самолет, но он уже содержит общие детали реализации

При этом описаны абстрактные методы взлёта/полёта/посадки. В чертеже. Ну допустим :)

Второе, нам нужно описать требования для унифицированного самолета, у него должны быть определенны такие параметры как грузоподъемность, крейсерская и максимальная скорость, практический потолок (они могут отличаться у разных моделей, но они должны быть), он должен уметь взлетать и, крайне желательно, уметь садиться

При том что абстрактный класс ЧертежСамолета реализует интерфейс ТребованияКСамолету, спецификация требований к самолёту у нас появляется позже общего чертежа. Ну допустим.

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

Если поначалу была какая-то неопределённость с терминологией и в первой части можно себе представить просто эскиз, а во второй части — документ «спецификация требований», составляющие вместе пакет проектной документации (вместо слова «чертёж» каждый раз), то вот это прям чертёж. Чертёж, по которому будет строиться конкретный самолёт. Чертёж, в котором описана реализация взлёта через операции «поздороваться_с_пассажирами» и «провести_инструктаж». Что в случае отсутствия пассажиров в лучшем случае может привести к потере времени на проведение инструктажа для пустого салона. А то и к NPE.

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

Уверен? Тяговооружённость, лобовое сопротивление, тормоза, реверс тяги, интерцепторы… Не! только размах и количество двигателей.
Я уже молчу про «вПП», «независимо от» и прочее «от того, что как»

Вот чем вышеперечисленное принципиально лучше и понятнее этого:
вот есть класс Домашние любимцы с методами «голос» и «есть», от него мы наследуем Кошку и Собаку и все хорошо.
Но тут приходит Света и приносит аквариумных рыбок, которые не разговаривают, а потом приходит Вася, которые приносит любимый кактус, которые не только не разговаривает, но и не ест.
Мы уже запутались…

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

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

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

И в таком случае смысл нестатических методов и классов совершенно неочевиден. А ещё давит огромное различие между кодом фреймворка и объясняемых и в универе и в таких постах: «Матроскиным как классом». Вообще никакого сходства.
Надо разделять 2 концепции ООП.
1) Объект = Данные + Методы работы с ними
2) Объект — это сущность, можно сказать «одушевленная», у нее главное — что она может делать, а не ее свойства.
Пример первого варианта — любой объект который соответствует JavaBeans. Вместо того чтобы инкапсулировать он через геттеры отдает все свои основные параметры. а через сеттеры может их получить. Т.е. это даже не объект, а улучшенная структура данных. Часто такие объекты сопровождаются сервисами которые включают себя поведение этого «объекта». Именно в результате первой концепции возникают чаще всего странные сущности МенеджерЧегоТоТам, а код часто превращается в винегрет.
А вот вторая концепция — это то что нужно, но то что плохо осознано программистами. Когда чертеж самолета печатает себя на экране сам. А не все его данные достаются кем-то и печатаются
Надо разделять прежде всего саму парадигму и ее языковую поддержку.

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

Бывает и наоборот: дескриптор окна (HWND) в WinAPI с точки зрения ООП ведет себя как ссылка на объект. Но с точки зрения языка это просто число.
дескриптор окна (HWND) в WinAPI с точки зрения ООП ведет себя как ссылка на объект. Но с точки зрения языка это просто число.


Мой любимый пример полиморфизма без ООП — файловая система в Unix. В то время как хэндлер — это просто число, за ним может скрываться все что угодно: файл на диске, файл на удаленном компьютере, периферийное устройство, генератор случайных чисел, да хоть ничего (/dev/null).
Чтобы объяснить что-то, надо начинать с постановки проблемы.
Для чего вообще интерфейсы и классы, почему нельзя обойтись без них?
Чтобы объяснять наследование необходимо ввести понятие абстрактный тип данных. ru.wikipedia.org/wiki/%D0%90%D0%B1%D1%81%D1%82%D1%80%D0%B0%D0%BA%D1%82%D0%BD%D1%8B%D0%B9_%D1%82%D0%B8%D0%BF_%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B%D1%85
Абстра́ктный тип да́нных (АТД) — это математическая модель для типов данных, где тип данных определяется поведением (семантикой) с точки зрения пользователя данных, а именно в терминах возможных значений, возможных операций над данными этого типа и поведения этих операций.

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

Сложение. Умножение. Сравнение...


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

Ну, аналогия приведена хорошо. Сложно сказать насколько это понятно, когда ты уже и так в курсе того, что такое ООП (не будем в подробности тут).

К чему есть претензии по статье:
Аналогии то приведены, и объяснено как и что там. Теперь как это транслировать на «нормальный» язык, чтобы объясниться в профессиональной сфере. Не будет же человек говорить что-то вроде «Наследование это когда из 120 мест авиалайнера станет 130 без изменения исходного чертежа». Ну и т.д.
После внимательного прочтения статьи (с учетом опроса) и всего множества ранее сделанных комментариев у меня сложилось впечатление, что основная проблема при объяснении сути ООП заключается в трудности выбора достаточно короткого (т.е. легко обозримого) примера, в котором были бы ясно видны преимущества ОО подхода. Эту статью я писал не для объяснении сути ООП, однако думаю, что на основе приведенных там фрагментов кода для решения простой реальной задачи было бы очень легко обяснить эту суть. Выразительных средств простого общеизвестного ЯП для этого вполне достаточно. Всевозможные опциональные детали некоторых более продвинутых ЯП типа множественного наследования при объяснении сути упоминать не нужно.
Представить класс в виде чертежа — хорошая идея, но этот пример кода полностью всё портит:
ЧертежСамолета тотСамыйПотрепанныйБоингНаКоторомМыЛетелиВТурцию = new ЧертежПассажирскогоСамолета();

Самолёт = новый чертёж самолёта? Что за бред, мы сделали новый самолёт, а не нарисовали новый чертёж. И тип объекта, который получился в результате — самолёт, а никак не ЧертежСамолета. Меня бы это всё ещё больше запутало, чем в примере с котами и собаками.

Лучше уже сделать класс Боинг737, тогда хоть код логичный будет:
Самолет тотСамыйПотрепанныйБоингНаКоторомМыЛетелиВТурцию = new Боинг737();

Или делать чертёж как factory:
Самолет тотСамыйПотрепанныйБоингНаКоторомМыЛетелиВТурцию = ЧертежПассажирскогоСамолета.построить();

Построить чертеж? Или все-таки "построить по чертежу". И тогда это ничем не отличается от "сделать новый по чертежу".

ИМХО, чертеж не летает. Поэтому нельзя создать чертеж самолета со свойством полета. Разве, что мы запустим бумажный самолет, сделанный из бумаги, на которой будет чертеж самолета.
Вот смоделировать тип объектов, каждый из которых может летать, мы можем. Можем смоделировать класс (класс не равно тип). Если мы говорим о типе объектов (концепте), то можем сказать: каждый объект данного типа может летать.
Разве, что мы запустим бумажный самолет, сделанный из бумаги, на которой будет чертеж самолета.
Именно поэтому, когда речь о серьезном изучении, а не о науч.поп., лучше по возможности избегать аналогий и аллегорий. Будь то самолетики или котики.
Автор стремится к чему-то приблизиться. Это очень важно — иметь вопросы и пытаться на них ответить.
А еще объекты названы инстансами.
Где названы? Я всегда называю объекты объектами, а классы — классами :)

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


Вероятно, лучше объяснять абстрактно, в ключе "объект — это данные + методы для работы с ними; класс — это описание, по которому создаётся объект; интерфейс — это список сигнатур методов; полиморфизм — возможность работать с разнотипными объектами посредством единого интерфейса", но на примерах, более близких к реальности, например на API файловой системы (есть файлы, у которых есть имя; некоторые файлы — это каталоги, у которых вдобавок к имени есть список потомков; файлы можно создавать, переименовывать, удалять; и т.д.).

При прочтении возникают следующие вопросы:
1. Почему чертеж умеет летать
2. Почему чертеж располагает информацией о количестве пассажиров (ок, если это опечатка: что если несколько мест сломается? Нужно будет наследоваться?)
3. Какой практический толк от абстрактного класса ЧертежСамолета, если, очевидно, что «полет» будет принципиально разным не только для различных моделей, но и для различных объектов. Или мы все сводим к write(«самолет летит»);? Тогда не вижу разницы между этим и животными.
4. Не очень понятна зона ответственности класса: пассажирский самолет умеет здороваться с пассажирами. Это как? Может, все таки, этим занимается не самолет, а ответственный за это модуль (человек).
5. Странно, что это никто не заметил, но статическая переменная «аварийность»? Вы же в курсе должны быть, что статик переменные имеют одно значение для всех объектов класса. Плюс ко всему статик-методы и наследование работает не так, как вы ожидаете. Не знаю, как в жаве, вы на ней код приводите, но в C# нельзя вызывать статик метод у объекта, что очень логично, так как смысла в этом нет.
6. Ваше объяснение полиморфизма сможет понять только тот, кто уже прокрутил в голове, как это будет выглядеть в коде. Но проблема даже не в этом, как вы будете определять: может ли самолет сесть на данную полосу? Пытаться кастовать вниз по иерархии? Тогда в чем полиморфизм?
7. Просто наблюдение: студенты в университете очень ленивые и большинство вообще не знает, зачем учится. У них даже склад ума не у всех технический. Ваш пример пока от кошек и собак ушел не очень далеко, тогда вопрос: чем же он лучше, если априори более сложный для восприятия.

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

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

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

«В чертежах написано „самолет может летать“» != «чертеж умеет летать». Чертеж не отвечает и не должен отвечать за сам процесс полета, это не его функция, а значит этого метода в этом классе вообще быть не должно.
«Если более точно аналогию проводить, то чертеж это переменные класса, его состав, а методы это проектная документация.» Ну и как эта аналогия помогает понять, что вообще такое ООП. Я вот ход ваших мыслей совсем не улавливаю. А если у нас не самолеты, а люди. Что для людей такое: «проектная документация».
Примеры автора не проходят, к примеру, принципы SOLID, это довольно весомый аргумент в пользу того, что его архитектура неверная. Если вы хотите защитить его подход, то давайте не будем размышлять о том, как это сделано в мире реальном (это ни к чему не приводит), а ближе к программированию, плз.
а значит этого метода в этом классе вообще быть не должно.

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


Ну и как эта аналогия помогает понять, что вообще такое ООП.

Она помогает понять, что такое "класс" в языках программирования, зачем он используется, и как связан с объектом (экземпляром).

вы не конструктивно ведете беседу. Я не буду разводить демагогию. Хотите продолжить, перестаньте игнорировать факты и цепляться за аналогии, не относящиеся к сути вопроса. Докажите мне на примере известных принципов проектирования свою правоту, или я просто буду игнорировать вас, как некомпетентного в данном вопросе. (Я вам привел конкретику на примере SOLID, вы же просто вертите словами)
вы не конструктивно ведете беседу

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


перестаньте игнорировать факты и цепляться за аналогии, не относящиеся к сути вопроса.

Суть вопроса именно в обсуждении аналогии из статьи. Кстати, а какие именно факты я игнорирую?


Докажите мне на примере известных принципов проектирования свою правоту

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


Я вам привел конкретику на примере SOLID, вы же просто вертите словами

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

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


И самое главное. ООП не догма.
Описание некоторых аспектов поведения объекта вполне может выполняться в традициях структурного программирования. Могут быть и иные отклонения от ООП.
Главное при этом просто понимать место таких отклонений в общей картине взаимодействия объектов.

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

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

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

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

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

Статические члены, виртуальные методы, абстрактные классы, интерфейсы — это сугубо вспомогательные приемы, к сути ООП отношения не имеющие (в C++ интерфейсы вообще отсутствуют).

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

Тогда что такое ООП? Согласно названию — это программирование на основе объектов. Звучит тривиально и тавтологически, но это так. Что такое «объекты» в самой своей сути, как они должны пониматься — было описано выше, объекты, как нам прояснили авторы первого объектно-ориентированного языка Simula, это самостоятельные, самодостаточные (закрытые) программы (общающиеся между собой — это важно!). Программа же, и это понятно, содержит код и (опционально) состояние, т.е. данные. Понимание этой мысли, насчет «самостоятельных программ» — отправная точка для понимания всего ООП, проектирования ОО-программ, популярности ООП как таковой, а также вытекающих отсюда как само собой разумеющееся, принципов information hiding и separation of concerns. Понятие «общения» объектов-программ между собой автоматически влечет понятия протокола и интерфейса.

Теперь о еще одной причине возникновения ООП.
Программирование порождает проблемы не только в вычислениях и в алгоритмах, но и при самой инженерии больших программных систем. Это тот момент, который трудно понять не только начинающим, но и просто большинству людей, так как многим не довелось работать в коллективах в которых программные системы создаются годами и десятилетиями. И это тот же момент, почему в конце 80х-начале 90х ООП подход был прохладно принят в нашем отечестве. Дело в том, что масштабы систем, за редким исключением были разные, наши люди работали либо в одиночку, либо малыми коллективами из 3х-5ти человек, где наиболее насущна потребность скорее все еще в отдельных подпрограммах (процедурах и функциях), нежели чем в отдельных, самостоятельных программах (т.е. объектах). Верно и обратное, достаточно легко представить себе коллектив из 3х-5ти человек, работающий над некоторым программным решением. (Типичная ситуация большинства фирм и НИИ как в 70е-90е, так и по сей день). Но вот когда дело доходит до коллективов хотя бы из 50-ти человек (не говоря уже о смене людей за годы разработки), вот тут-то дело и становится тухлым. Стоит лишь представить себе что вся эта огромная толпа народа постоянно копошится в гигантской куче переменных, процедур и функций, как становится понятно, что все это добром не кончится. :) Приходится или прикладывать изрядные усилия, или всё достаточно быстро превращается в невообразимое месиво. Это происходит чисто статистически, по законам развития систем.

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

Конечно, можно возразить что многие очень крупные системы написаны без объектно-ориентированного подхода. Но если очень внимательно приглядеться, то то, как они написаны напоминает ООП-подход. К примеру, код крупных операционных систем написан небольшими, отдельно компилируемыми модулями, каждый со своими приватными переменными (в точности также как объект) и каждый со своим заголовочным файлом, содержащим «интерфейс» к этому модулю, что представляет собой аналог класса с объявленными в нем методами. Существует и своего рода «наследование», только не статически, в момент компиляции, а динамически, на уровне run-time библиотек. Если требуется унаследоваться, может быть написана новая динамически загружаемая библиотека-плагин, которая содержит новую функциональность, и которая, в свою очередь, делегирует к старым, уже написанным и подгружаемым библиотекам. Это не хак и не костыль, а самое настоящее наследование, имеющее своей сутью композицию, делегацию и перенесенное на уровень исполнения. Также на ум приходит Microsoft COM, своеобразный run-time аналог ООП.

Моя задача была привить правильное (с моей точки зрения) понимание что такое ООП в самой своей сути. Подчеркну, ООП — это метод организации программных текстов, или способ написания программ, состоящих в свою очередь из небольших изолированных, функционально самостоятельных и законченных программ. Эти программы называются «объекты» (сейчас с этим пониманием более ассоциируются классы, но как выше было замечено, классы далеко не всегда присущи ОО-системам). При этом важно обратить внимание на общение этих своеобразных «программ», откуда проистекает необходимость в определении протоколов общения, откуда, в свою очередь автоматически вытекает понятие интерфейса. Как видно, все связано, всё объяснимо, и когда мы сталкиваемся с некими абстракциями, типа data hiding — мы их воспринимаем уже не как «тайное знание», а как вполне очевидную и объяснимую вещь. Ну, если, конечно, достаточно глубоко задумываться над смыслом всех прозвучавших слов и если ознакомиться с упомянутым выше «Some features of the Simula 67 language».

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

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


Когда студент хочет понять ООП, он вряд ли хочет услышать рассказ смысле ООП в языке 60-70 годов. Ему нужно объяснить то, что большинство специалистов понимают под ООП сейчас. Потому что он хочет в итоге пройти собеседование и устроиться на работу. На этом собеседовании его скорее всего спросят "какие основные принципы ООП?" и будут выяснять понимание терминов: класс, абстракция, инкапсуляция, наследование, полиморфизм и т.п.


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

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

Студенты студентам рознь.
В общеобразовательных российских заведениях студенты готовятся именно так, как вы считаете нужным — бессмысленно натаскиваются на зубрёжку формул и определений. Ведь именно это будут у них спрашивать на экзамене, верно? Так происходит подготовка людей общей специальности. И только в ограниченном числе ВУЗ'ов, на отдельно взятых кафедрах готовят ученых-исследователей, тех кто будет открывать новые законы, устанавливать новые закономерности и т.д. У подобных студентов в курс обязательно входит литература по истории предмета. Обычным студентам это не дают или дают очень ограниченно. Также, историю развития и взгляды основоположников всегда очень тщательно и скурпулезно изучают в старейших университетах мира — Кембридже, Оксфорде, это не говоря о том порой сами лекции там можно услышать из уст самих «основоположников».

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

А мне кажется, вполне неплохая статья. Класс — это чертеж объекта.

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

Есть подозрение, что принцип этот вы поняли, но, как это часто бывает, применить его сумели не вполне…
Только вот меня всегда сбивала мысль о том что в обычной программе множество параллельно взаимодействующих объектов не возможно… всегда существует некая главная процедура которая которая приводит в действие иерархию объектов, причем последовательно.
Это «инерция мышления», идущая, вероятно, от процедурной парадигмы программирования. ОО реализация предполагает что может быть вызван любой публичный метод. Некая сущность извне может вызвать публичные, т.е. заранее выставленные наружу методы этого мира, образуя event-driven систему. А вот вопрос о параллельном вызове сразу двух и более методов объектного мира — это вопрос уже не из области ОО, а из области параллелизма (конкурентного исполнения). ОО не имеет к нему отношения, вопросов этих не решает и их не касается.
А так же я не могу понять как не запутаться меж связей объектов в реально работающей программе и связях классов от которых объекты произведены, от этой двойственности идет кругом голова.
Это получается от того, что вы сейчас представляете полезную программу как размытую по методам различных классов, то есть игнорируете тот факт, что класс должен быть цельной «программой».
ОО-программу надо представлять так. Надо сфокусироваться только на методах «своего» класса. Все остальные классы и их методы вас интересовать не должны. Они выполняют свою работу, и пусть, а вам должно быть достаточно только информации о их названиях, передаваемых параметрах и возвращаемых значениях, пусть это будут «черные ящики», выполняющие свою работу. Самое тяжелое — это научиться психологически «доверять» используемым типам, игнорируя код их методов.
Этот же подход освободит (или по крайней мере релаксирует) от попытки анализа всех связей в программе. ОО-программы часто используют полиморфную подстановку типов, так что какой там будет конкретный тип и какой выполнится код заранее может быть неизвестно.

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

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

Юмор-то еще вот в чем. ОО-системы можно построить во-первых, без всякой прямой ссылки классов друг на друга (или без включения). И во-вторых, построить без явного упоминания имен других классов и их методов. Вообще, в крупных системах к этому и стремятся, но это к несчастью, имеет свою цену…
Нам ООП объясняли на примере Half-Life. Абстрактный класс враг как нечто, что пытается тебя убить. Оно должно иметь размеры массу, желательно уметь двигаться(хотя это опционально) и уметь тебя убивать. Ну и тд.
Все проблемы изучения ООП состоят в том, что оно преподносится как техника проектирования системы. Но ООП это просто идея, к проектированию оно отношения не имеет.

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


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

Скорее всего вам потребуется что-то посложнее, начнутся всякие паттерны и прочее. А паттерны это, в частности, о том как делить систему на части. То что кто-то части называет объектами и классами, а кто-то функциями и модулями проблемы не меняет.

На моей памяти старый и избитый пример телефонной книги с контактами...

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

ООП придумали, чтобы оптимизировать компиляцию больших структур на ассемблере. Так то, кроме ассемблера ничего и нет. Подебажте асм код своих ООП поделок, удивитесь результатам, как все красиво там. Ну и да, это и для java vm актуально (там свой ассемблер)

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

В ООП наблюдается data и code locality, образующие один объект. Но эта локальность сугубо организационно-текстового характера, или говоря иначе, психологический, организационный аспект для инженера и программиста. Также, можно сказать что она образует «системообразующий» фактор, но это снова проявляется лишь на бумаге, т.е. при взаимодействии человек-машина. Системы подчиняющиеся какой-либо симметрии и локальности обладают меньшей системной энтропией, что и обуславливает их определенные преимущества.

Но я бы не советовал путать организационную locality, такую как она существует в тексте ОО-программ и реальную, физическую, ибо современные реализации ОО ЯВУ, компиляторы, операционные системы и форматы исполняемых модулей организованы так, что тела методов (код процессора) лежат в одном месте, vmt лежат во втором месте, и память для полей объектов выделяется из кучи или на стеке. Хотя в целом определенные выгоды от локальности (без предпринимаемых усилий) могут наблюдаться здесь и на физическом уровне. Хотя в большинстве случаев типичная, неконтролируемая ОО-реализация напротив, имеет худшие характеристики, если не предпринято иное (например пулирование объектов). Подчеркиваю, именно реализация, так как в самой объектной парадигме ничего такого нет, и если бы hardware было бы построено с учетом ОО, это можно было бы эффективно использовать.
А можно те же примеры, только на языке программирования С++, ибо он мне более понятен?
Недостаток всех этих подходов в оторваности от реальной практики проэктирования.
Задача ООА и ООД моделирование предметной области.
Так почему не взять маленькую задачу и смоделировать её от А до Я.
Поэтому в книге Лармана один из лучших подходов www.ozon.ru/context/detail/id/3105480 для введения в ООД и ООА.

Не существует никаких классов и сущностей, есть только интерфейсы. Абстрактный водитель может управлять всем чем угодно, что реализует стандартный интерфейс "автомобиль" и имеет руль, педали и прочие приблуды. Более того, для управления нам не нужна никакая сущность типа "абстрактный или конкретный автомобиль": водитель может ездить на тренажере по виртуальной локации еще вместе с десятком таких же водителей. Здесь "сущность", которой управляем, уже выделить сложно. Точно также с другой стороны у нас нет никакой необходимости в сущности "абстрактный водитель" — стандартным такси может управлять механизм, подключенный к единой сети, которая управляет его действиями.


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


  • собака.сожрать(сосиска)
  • сосиска.бытьСожранной(собака)
  • new ПожирательноеДействие(собака, сосиска).сделать()

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


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


Если в основе модели сам процесс, то третий.


Если у нас собака и сосиска равнозначные объекты, то можно в методе сожрать вызвать метод бытьСожранной или наоборот.


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


Все упирается в модель. А модель зависит от решаемой задачи.

Если у нас собака и сосиска равнозначные объекты, то можно в методе сожрать вызвать метод бытьСожранной или наоборот.

Тут, скорее, нужно делать аналогично "Если в основе модели сам процесс, то третий.". Собственно считается хорошей практикой при сомнениях к одному или другому объекту/классу отнести тот или иной метод, вынести его в третий.

НЛО прилетело и опубликовало эту надпись здесь

Если время пожирания имеет значение для задачи моделирования, то время физического исполнения метода не имеет значения для функциональности, время пожирания должно учитываться в методе пожирания, например, по событиям таймера, если нам нужно "онлайн пожирание" или просто храниться в состоянии и влиять на поведение методов типа "жрётЛиНаТаймстамп(таймстамп)

НЛО прилетело и опубликовало эту надпись здесь

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


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


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

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

shared_ptr<Sausage>

))

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

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

Куда важнее объяснить студентам, что ООП — это мишура. И то что это лишь одна из парадигм, а не единственный способ построения «архитектуры». То же наследование, в реальном ООП-проекте в 10-20% случаев нужно, в остальном без него будет лучше обойтись. Модификаторы доступа — инструмент для захламления кода, добавления гемора и траты кучи времени на лишнюю работу. Да, это спасает от дураков иногда, и уменьшает колво ошибок иногда, но во-первых оно и добавляет ошибок, но уже при проектировании, а во-вторых если у вас дураки пишут код, то проект уже ничего не спасет.

Адептам функциональньщины захотевшим поддержать меня сразу скажу что область применения функциональщины в чистом виде — 5% проектов. В остальных случаях она садит производительность и усложняет разработку. Есть кейсы где функциональный подход удобен, так вот в там ее и стоит использовать.

Проще говоря в проекте опитимально сочетать разные парадигмы, каждую для своего кейса. Людей которые освоили только одну парадигму, один язык или одну платформу(web например) специалистами не считаю.
НЛО прилетело и опубликовало эту надпись здесь
С потолка.

В моём опыте оно существенно выше.

А в какого рода проектах вы ФП используете?
НЛО прилетело и опубликовало эту надпись здесь
Думаю использование ФП в большинстве этих кейсов явно излишне
НЛО прилетело и опубликовало эту надпись здесь
В вашем ответе изящно переплелись «Думаю» и «явно излишне».
Что до ФП — в указанных кейсах оно вполне может быть оправдано.
Все познается в сравнении, чтобы не было вопросов: зачем оно нужно. Просто следует дать возможность слушателям сделать описание той же модели без ООП. Тогда все станет понятно. Нам, например, давали просто задание по написанию своей простейшей оконной системы. Даже без выбора языка и среды разработки. Нужно было просто показать, что оно работает и подтвердить кодом. Только теория в сочетании с практикой поможет. Пока сам не будешь ручками что-то трогать — объяснять можно сколько угодно.

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

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

Таким образом объект — это набор данных, с теми методами которые умеют с этими данными работать напрямую.

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

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

Для меня этот вопрос прояснила книга «PHP. Объекты, шаблоны и методики программирования», автор Мэт Зандстра. Мэт объясняет доступным языком, в удобном порядке, в качестве примеров использует приближенный к реальности проект интернет-магазина и поясняет как именно тот или иной принцип структурирования помогает решить конкретную проблему.
Я как раз такой преподаватель, который объясняет через кошечек и собачек. Однако пример приведенный в статье действительно не очень хорош, а есть гораздо более приближенный к реальности.
Люди ввели иерархическую классификацию организмов и она в купе с теорией эволюции дает отличный материал для ООП.
Например (можно и по-другому, но все же он отражает идею), есть абстрактный класс млекопитающие. Создать объект класса млекопитающее нельзя (ну просто нет отдельного животного такого), зато можно от него наследоваться. Причем можно даже опять абстрактным классом отнаследоваться ( класс сумчатое, как пример). В качестве интерфейса можно взять интерфейс-кислородное-дыхание или использование одного из трех видов крови.
Виртуальным методом можно обозначить питание (охота/растениеедство и т.п.).
А вот конкретный питомец это уже объект класса (и имя у питомца и переменной есть даже).

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

А мне нравится ООП парадигму рассматривать, как набор функций, процедур, подпрограмм, программ (и всё это, бывает, с нулевым мясом в них, когда хранятся только данные, а сам код нулевой по сути или когда мясо есть (минимальный метод вида return(), naprimer), a dannyh nikakih ne htanitsja). Ya пришёл в ООП с Радио-86рк (кр580), спектрума (z80) и Амиги (мс68000/68020) и ей-богу лучше сразу людей начинать погроммировать на котах и кошках со свойствами и вспом. методами стерильна/кастрирован/стерильна+кастрирован, чем сначала мучительно учить нас студентов, издеваясь, машшинномму коду, ассемблеру (несуществуюего, кстати, процессора), регистрам и микрокомандам и т.д. и т.п., а потом переучивать на паскали всякие, си, си++, пэхэхпэ и т.д. в ООП. Это наносит необратимый, катастрофический вред головному мозгу, сознанию и в целом личности обучающегося. P.S> я чудом избежал катастрофы, но повезло не всем

Это наносит необратимый, катастрофический вред головному мозгу, сознанию и в целом личности обучающегося.P.S> я чудом избежал катастрофы

Уверены, что избежали? :)

Хотел нажать вам +1 ("стрелка вверх"), но почему-то при наведении указателя мыши на эту пиктограмму высвечивается значок "запрет"…
Чтобы не приходить с пустыми руками расскажу анекдот:
— Абгам, догогой, что тебе пгиготовить, кугочку или гыбку?
— Гадость моя, мне все гавно...

Если мы делаем самолет, но двигатель разрабатывает другая команда, нам проще не указывать его на самом чертеже самолета (тем более что один двигатель может использовать на большом количестве самолетов), а только указать «смотри чертеж такого-то двигателя». Это композиция.

А где-то в параллельной вселенной потоке учащимся на QA показывают на этом примере интеграционное тестирование — что делать, если все болты и разъемы на двигателе подходят, но мощности для взлета не хватает.
Описание структуры сущностей в ООП — не главное.
Ради чего всё создаётся — взаимодействие между сущностями в предметке.
Сущности взаимодействуют по протоколам.
Описание актов взаимодействия сущностей предметной области через предоставляемый функционал, описываемый через интерфейсы/абстрактные классы — вырожденный случай описания последовательностей взаимодействия. Только лишь потому, что нет адекватных синтаксических конструкций описания последовательности такого взаимодействия во времени (в модельной временной шкале). Вернее такое средство есть только в одном единственном языке — Zonnon.
«Наследование» подавляющим большинством народа понимается в корне неверно. Люди путают наследование, как выделение общих свойств на этапе анализа предметной области с механизмом обеспечения повторного использования кода на этапе реализации.
ООП — НЕ подразумевает какой-то конкретный язык. Не помню, но кто-то из создателей Smalltalk-а сказал «когда мы создавали ООП, мы не имели в виду С++». В моей практике я использовал ОО[А|Д|П] во всех языках реализации, вне зависимости от наличия «поддержки ООП» в конкретном использованном языке (конкретно использовались: ассемблеры AVR, ADSP21xx, C, C++, Smalltalk, FORTH (про ФОРТ — отдельный, самый интересный, разговор ;) )) — главное, что бы была возможность адресоваться к области памяти, хранящей состояние экземпляра «класса». Сама модель поддержки ООП в данном языке значения не имеет. Есть — хорошо. Нет — достигается средствами её самодельной эмуляции и через препроцессинг. Порой (по задаче) не нужна даже VMT.

Дальше.

Неправильно народ также понимает и «полиморфизм» и «активность» объектов.

Полиморфизм НЕ требует наличия свойства наследования в языке. Это — лишь дань выбранному способу поддержки решений, принятых на этапе анализа системы + способ реализации повторного использования кода.
Полиморфизм можно реализовывать не только через «статические аспекты описания классов» — традиционный подход на сегодня, но и — через едиснтвенную «точку доступа» — канал связи с сущностью. Здесь полиморфность экземпляра реализуется через соответствие объекта-клиента, инициировавшего обмен с «сервером», тому протоколу обмена, который анонсировал объект«сервер», для реализации нужной функциональности. Например, типизированные каналы в Go — вырожденный случай описания на разновидности РБНФ дисциплины обмена по данному каналу с данным объектом-сервером в языке Zonnon. Конечный автомат, сгенерированный по такому описанию к каналу проверяет все аспекты обмена по своему каналу (последовательности присылаемых полей в сообщении, их тип и разрешённые значения). Прелесть здесь в том, что взаимодействие не сводится только к атомарным «одноразовым» обменам между клиентом и сервером. Такое взаимодействие может быть совершенно произвольным по временной протяжённости и количеству актов взаимодействий (с произвольными перерывами между актами обмена) — всё зависит от того, что вы описали в РБНФ-описании автомата на канале.

Активность.
Активный объект – это НЕ объект, в который «заходит» поток ОС.
Понятие «поток ОС» должно быть совершенно выброшено из лексикона разработчиков. Исключение может быть только в случае разработки частей, работающих с сущностями операционной системы, непосредственно на уровне ОС.
Да, активный объект имеет в своём составе «поток ОС», реализующий его «жизненный цикл» («активность»). Но на уровне прикладных задач потоков НЕТ и быть не может. «Поток» — название чего-то, с чем имеет дело операционная система, а не ваша задача. Стартующий поток ОС, в котором реализуется «активность/жизненный цикла активного объекта», должен начинать работать ТОЛЬКО в контексте его объекта-владельца. Ещё раз: поток операционной системы, реализующий жизненный цикл активного объекта НИЧЕГО не знает, кроме ссылки на экземпляр своего владельца-«запускателя» и точки входа метода класса этого объекта, реализующего активность в рамках экземпляра этого класса.
(Тут следует упомянуть пагубность широко распространённой практики получения «активных» объектов путём наследования от библиотечных классов-«оберток» над системными потоками. Это — в корне неверно. Авторы таких решений могут сколько угодно рассуждать об ООП, но совершают совершенно дичайшую ошибку уже на этапе анализа и дизайна своей системы: наследование подразумевает принадлежность потомка и наличие у него признаков наследуемого класса. НО! в 99.999999999% случаев ВАШ объект из ВАШЕЙ задачи НЕ ЯВЛЯЕТСЯ ПОТОКОМ! Активным объектом — ДА. ПОТОКОМ — НЕТ.)
Активности в процессе реализации жизненных циклов своих хозяев «посещают» контексты экземпляров других объектов.
Совокупность всех жизненных циклов активных объектов, определённых в вашей системе и составляют, собственно, её «функционирование».

В заключение ещё небольшое замечание, которое может устранить неимоверно огромное количество ошибок и послужить отличнейшим базисом для «тестопригодности» всей совокупности объектов вашей системы.
При проектировании ООП-системы все интерфейсные методы должны быть или командами, или запросами.
Команды не должны возвращать результата. Результат исполнения команды должен проверяться в отдельном запросе.
Запросы НИ В КОЕМ СЛУЧАЕ не могут иметь побочных действий.
Команды воздействую на состояние только указанного объекта. Что бы было понятно, то случай, типа пресловутого if( (result = obj.read( file, buffer, n)) ) должны быть просто исключены из практики программирования. Состояние объекта после команды проверяйте в последующем запросе.

Фууууухххх…
Пока — всё. :)

Публикации

Изменить настройки темы

Истории