ОСП   ООП   к алгоритмизации   СУБД   Экспертные системы   ЯиМП   3GL   4GL   5GL   ТП

Объектно-ориентированное программирование

Развитие идей ООП к реляционной парадигме

  1. Проблемы ООП
  2. Основные понятия
  3. Классификация объектов
  4. Что же предлагается?
  5. Синергизм классов
  6. Абстрактные классы
  7. Реализации интерфейсов при мн. классификации
  8. Статические и динамические методы
  9. Мутация объектов
  10. Строгая типизация
  11. Реляционные БД и множественная классификация
  12. Изменения в технологии проектирования
  13. Что дальше?
  14. Заключение

		Александр Масляев

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

Проблемы ООП

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

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

Обычно декларируются следующие преимущества использования объектно-ориентированного программирования:

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

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

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

Основные понятия

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

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

Заметьте, что под понятием "Объект" здесь подразумевается не тип данных, а именно экземпляр (instance). Например, дата - это не объект (а класс, но понятием класса сейчас голову не забиваем, а подойдём к нему позже), а переменная, содержащая значение 01.04.2003 - это объект; целое число - не объект, а переменная, содержащая целое число 8934 - объект.

Интерфейс объекта - способ информационного воздействия на него. Например, "Установить реквизит", "Получить реквизит", "Инициализировать", "Распечатать", "Отформатировать", "Обработать текстовый запрос".

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

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

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

Классификация объектов

Работа программы - это выполнение кода, инкапсулированного в объекты, и взаимодействие объектов между собой. Взаимодействие объектов - это вызов интерфейсов.

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

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

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

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

Наследование - это заложенный в методологию ООП способ объявить тот факт, что всякий объект класса X также является и объектом класса Y. То есть если у нас есть класс "собака", то в описании классов записано, что каждый объект этого класса также принадлежит классу "млекопитающее", каждое млекопитающее также принадлежит классу "животное", каждое животное также принадлежит некоему базовому классу "объект". В результате в нашей программе каждая собака автоматически является ещё и млекопитающим, животным и объектом.


Рисунок 1.

Множественная классификация через наследование - (объект класса Class4 принадлежит также классам Class3 и Class5, объект класса Class5 принадлежит также классам Class3, Class1 и Class2).

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

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


Рисунок 2.

Множественная классификация через агрегирование - (объект класса Class4 также поставляет интерфейсы классов Class1, Class2 и Class3).

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

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


Рисунок 3.

Множественная классификация с использованием mixins - (объект класса Class3 принадлежит также классам Class1 и Class2, методы и свойства класса Class2 доступны из методов класса Class1 и наоборот через функцию Self).

Особая прелесть заключается в том, что при объявлении составного класса мы можем скомпоновать его именно из тех частей, которые нам необходимы. Например, вместо Class1<Class3> мы можем взять Class1_xp<Class3> (важно только, чтобы Class1_xp<> был потомком IClass1).

Эта технология свободна ото всех недостатков, перечисленных мной при рассмотрении методики "Агрегация", но остаётся ещё ряд нерешённых проблем:

 

I1

I2
  С1I1 С2I1 С1I2 С2I2
CC11 X   X  
CC12 X     X
CC21   X X  
CC22   X   X
  if (Self.I1_GetVersion() == 1) {...

Кроме того, применение Mixin-технологии удобно только в том случае, если средство разработки поддерживает шаблоны. Чаще всего о Mixins говорят именно как об идиоме, применение которой бывает полезно разработчику, использующему C++.

Что же предлагается?

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

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

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

Заметьте, нельзя сказать, что во мне содержатся перечисленные объекты. Я ими всеми являюсь одновременно. Все мои ипостаси мало того, что сосуществуют, но ещё и взаимно переплетаются, давая качественно новый эффект. Например, придя в магазин, я говорю: "Здравствуйте, у вас есть памперсы 4-го размера?". Слово "здравствуйте" - это продукт ипостаси "вежливый человек". Интересуюсь памперсами потому, что я отец. Смысл фразы - это предложение начать транзакцию между магазином и мной, розничным покупателем.

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

  1. Некоторые наборы интерфейсов подразумевают, что объект обладает и другими наборами интерфейсов (например, из того, что человек является отцом, однозначно следует, что он мужчина). В этом случае мы с чистой совестью на своей UML-диаграмме можем нарисовать отношение "Generalization", обозначив этим классическое ООП-овское наследование.
  2. Некоторые наборы интерфейсов уживаются вместе, хотя могут жить и порознь (файл может являться документом, а может им и не являться; документ может быть файлом, а может файлом и не быть).
  3. Некоторые наборы интерфейсов логически противоречат друг другу (кошка может быть кем угодно - животным, членом семьи, другом, игрушкой, но собакой быть не может).
  4. Бывает так, что присутствие у объекта набора интерфейсов определяется свойством другого набора интерфейсов, присущего этому объекту (человек может быть клиентом табачного ларька только в том случае, если значение его свойства "Возраст" больше либо равно 18).
  5. Бывает так, что присутствие у объекта набора интерфейсов определяется контекстом, т.е. свойствами других объектов. Это всего лишь некоторая глобализация предыдущего пункта.

Пункт 2 будет использован как обоснование необходимости множественной классификации на уровне экземпляров, пункты 4 и 5 - для обоснования мутации объектов, которая будет рассмотрена ниже.

А теперь давайте (наконец-то) введём определение:

Класс - это именованный набор интерфейсов.

И ещё одно:

Тип - это вся совокупность интерфейсов и их реализаций, присущая объекту.

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


Рисунок 4.

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

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

Теоретически возможно два подхода к типизации объектов:

  1. Каждый из возможных типов (сочетаний элементарных классов) программист должен объявить явно, и аккуратно, с любовью, отшлифовать его логику. Можно чуть-чуть слукавить, применив шаблоны, но суть от этого не меняется. Этот подход применяется в 100% языков программирования. Даже удивительно.
  2. Явного объявления требуют только элементарные классы. Тип объекта определяется как сочетание элементарных классов.

Пока программа оперирует достаточно простыми, однозначными, и, как правило, искусственными сущностями (Button, ComboBox, Stack, Connection, 3DLine и т.д.), первого способа типизации оказывается вполне достаточно. Но при столкновении с чудовищным многообразием реального мира отсутствие персонализированной "тонкой настройки" набора интерфейсов объекта может иметь неприятные последствия. А именно:

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

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

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

Синергизм классов

Принадлежность объекта двум классам может качественно его преображать.

Рассмотрим, что может произойти, если объект А принадлежит классам X и Y одновременно.

  1. Объект А поддерживает все интерфейсы Ix и Iy классов X и Y.
  2. Интерфейсы Ix объекта А могут быть реализованы не так, как интерфейсы другого объекта, принадлежащего классу X, но не принадлежащего классу Y.

Сочетание классов X и Y назовём виртуальным классом XY, появляющимся в системе как следствие появления классов X и Y. Вооружившись комбинаторикой, несложно подсчитать, что с ростом количества элементарных классов количество виртуальных растёт лавинообразно. Например, для трёх элементарных классов количество виртуальных равно 4, для четырёх - 10, для десяти - 1013. Но это не должно нас пугать.

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

Допустим, проектируется система, в которой некоторые прикладные объекты сохраняются в базу данных. Например, клиенты записываются в таблицу Clients, заказы - в таблицу Orders. Обычно создают родительский класс clPersistent, имеющий методы Read() и Save(), и классы clClient и clOrder порождают (возможно, косвенно) от Persistent.

Мы тоже создадим класс clPersistent, имеющий методы Read() и Save() и классы clClient и clOrder, таких методов не имеющие. Запись реквизитов контрагента в базу данных будет производиться в процедуре Save() виртуального класса (clPersistent, clClient), реквизитов заказа - в процедуре, реализованной для (clPersistent, clOrder).

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

Абстрактные классы


The man thinks, the horse thinks,
the sheep thinks, the cow thinks,
the dog thinks. The fish doesn't think.
The fish is mute. Expressionless.
The fish doesn't think, because the fish knows everything.
Iggy Pop, "This is a Film"

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

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

Реализации интерфейсов при множественной классификации

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

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


Рисунок 4.

Рассмотрим абстрактный пример. В системе есть классы X и Y. Класс X имеет метод M, реализованный в контексте класса X (Mx) и в контексте виртуального класса XY (Mxy). Если вызвать метод M объекта A, одновременно принадлежащего классам X и Y, какая из реализаций (Mx или Mxy) должна отработать? "Mxy" - неправильный ответ. В системе ведь может оказаться ещё один класс Z и ещё одна реализация метода M (Mxz), и выбор между Mxy и Mxz объекта B, принадлежащего одновременно классам X, Y и Z, может стать совсем невыносимым.

Выход из этой ситуации только один - всегда вызывать все подходящие реализации методов. В каком порядке вызывать реализации методов? Можно предложить следующие варианты:

Сначала в каком-то определённом порядке вызываются реализации, принадлежащие самым виртуальным из всех виртуальных классов, в самом конце - реализацию, заложенную в элементарный класс. В приведённом выше примере цепочка будет выглядеть так: Mxyz - Mxy - Mxz - Mx

  1. Можно сделать наоборот: Mx - Mxy - Mxz - Mxyz
  2. Вызывать все реализации в произвольном порядке (либо даже в параллельных потоках).

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

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

Статические и динамические методы

Рассмотренные ранее "умные" методы всем хороши. И полиморфны, и расширяемы. Но есть у них два недостатка:

  1. Реализация таких методов весьма накладна. Определение перечня задействованных реализаций в любом случае намного дольше отработки процессорной инструкции CALL.
  2. Они никогда не смогут быть заменены inline-подстановкой.

Поэтому в любом случае должна присутствовать возможность делать методы статическими. Если разработчик твёрдо уверен, что для метода Mx никогда не придётся делать расширение Mxy или Mxz, почему бы не вызывать такой метод простой инструкцией CALL?

Мутация объектов

Мутация - динамическое изменение типа экземпляра в ходе выполнении программы.

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

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

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

CFoo *Foo = new CFoo();
Foo->Name = "My Foo";
DoSomethingStrange(&Foo); // переменная Foo мутировала в CBar*
cout << Foo->Name; // Не работает! Класс CBar не имеет свойства Name.

Компилятор твёрдо уверен, что переменная Foo имеет тип CFoo*, знает, по какому смещению лежит свойство Name, и в результате либо считывает какой-то мусор вместо Name, либо сразу вылетает с GPF.

В тех языках программирования, где все переменные принадлежат типу Variant (как правило, такое бывает в интерпретируемых языках), иногда можно встретить тексты вроде такого:

MyVar = 1;
Message(MyVar + 1); // "2"
MyVar = "Text string";
Message(MyVar + 1); // "Text string1"

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

В тех языках программирования, где класс объекта не является составным (т.е. во всех существующих на настоящий момент), любая мутация представляет собой кардинальную смену типа объекта. Если переменная Foo меняет свой класс с CFoo на CBar, то в результате выполняется две операции:

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

enum eGender 
{
  Male, Female
}

class cMale 
{
  // Проинформируем компилятор о том, что cMale = (cHuman, cMale)
  autoclassify as cHuman;
  public
    var Wife as cFemale;
}

class cFemale 
{
  autoclassify as cHuman;
  public
    var Husband as cMale;
}

class cHuman 
{
  private
    var Geneder as eGender;
  public
    var Name as String;

    function GetGender() as eGender 
    {
      Result = Gender;
    }

    procedure SetGender(AGender as eGender) 
    {
      if (AGender = Female) 
      {
        declassify this as cMale;
        classify this as cFemale;
      } 
      else if (AGender = Male) 
      {
        declassify this as cFemale;
        classify this as cMale;
      } 
      else 
      {
        declassify this as cMale;
        declassify this as cFemale;
      }
    }
} // cHuman
...
 // Процедура, внутри которой проблем не возникнет
procedure QueryAndSetGender(Somebody as cHuman) 
{
  var Choice as eGender;
  Choice = App.AskUserChoice("Somebody is", Somebody.GetGender(),
           eGender::Male, "male",
           eGender::Female, "female"); // Такая гипотетическая функция
  if (Choice <> null)
    Somebody.SetGender(Choice);
  // Ничего не нарушилось. Somebody по-прежнему принадлежит классу cHuman
  // declassify Somebody as cHuman;  
  // <<<< А вот это не пропустит компилятор
} // QueryAndSetGender

// Процедура, в которой мы обманем компилятор
procedure Male2Female(AMale as cMale) 
{
  if (App.MessageBox("Are you sure?", MB_YES + MB_NO + MB_DEFBUTTON2) = IDYES)
    // Проблема: как такое запретить компилятором?
    AMale.SetGender(eGender::Female);
  if (AMale.Wife <> null) // Ой, началось...
    App.MessageBox(AMale.Name + " is married to " + AMale.Wife.Name, MB_OK);
  else
    App.MessageBox(AMale.Name + " isn`t married", MB_OK);
} // Male2Female

Компилятор можно обмануть и каким-нибудь более изощрённым методом.

Как всегда, проблема обмана компилятора за счёт запутывания логики может быть решена несколькими способами:

  1. Сделать компилятор до такой степени интеллектуальным, что его нельзя будет обмануть. Вряд ли это возможно. Фантазия багописателя поистине виртуозна и неистощима. Возможно, я ошибаюсь.
  2. Ограничиться предупреждениями для простейших случаев, но в принципе такие безобразия разрешить. В этом случае придётся легитимизировать NULL. Т.е. все свойства и функции тех классов, к которым объект уже не принадлежит, возвращают NULL, вызовы процедур и изменения атрибутов состояния системы не изменяют. Таким образом, процедура Male2Female сообщит, что клиент не женат.
  3. Можно не вводить в язык опасный оператор declassify, а весь механизм мутации ограничить совершенно безопасным оператором classify.

Вывод: мутация не так страшна, как о ней говорят. Особенно, если она применяется в сочетании с суперметодикой множественной классификации.

Строгая типизация

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

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

Предположим, у нас есть переменная objWindow. Есть класс clDialog (диалоговое окно, которое можно открыть функцией Open) и класс clFoldingWindow (створчатое окно, элемент справочника товаров, который можно прочитать из БД функцией Open) В программе встречается следующая строка:

  objWindow.Open();

Если нет строгой типизации на этапе синтаксического разбора текста программы, то может возникнуть такая ситуация, что objWindow в момент выполнения этого оператора будет одновременно принадлежать к классу clDialog и к классу clFoldingWindow (очень удобно, между прочим). Что мы при этом должны сделать? Открыть диалоговое окно или прочитать информацию из БД? В системах программирования с поздним связыванием хотя бы на этапе выполнения можно это выяснить. Получается, что мы вынуждены использовать строгую типизацию на этапе синтаксического разбора. И если уж у нас получилось так, что переменная objWindow объявлена как:

Var objWindow as (clDialog, clFoldingWindow);

то будь добр написать конструкцию вроде

clDialog::(objWindow.Open());

Если же переменная objWindow объявлена как:

Var objWindow as clDialog;

то вызов команды открытия окна можно написать и так:

objWindow.Open();

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

Будем считать вопрос строгой типизации закрытым.

Реляционные БД и множественная классификация

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

Теорию и практику РБД, естественно, развивать нужно, но вектор развития не должен быть направлен на устранение тех недостатков, которых, по сути, нет.

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

Смею заявить, что отказ от концепции наследования в пользу множественной классификации снимает проблему отображения классов на таблицы РБД раз и навсегда. Из всех подходов к проблеме отображения (mapping) остаётся один (самый логичный!): один класс - одна таблица (или таблица с подчинёнными таблицами). Подчинённые таблицы бывают нужны потому, что требование атомарности полей никто не отменял.

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

Изменения в технологии проектирования

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

  1. Выделение сущностей, присутствующих в предметной области. Например, при проектировании ИС для зоопарка такими сущностями могут быть слон, тигр Атилла, тигрица Лада, жираф, страус, корм для хищников, банковский счёт, клетка для птиц, вольер, подсобное хозяйство, заблудившийся посетитель, ветеринар Семён Палыч, налоговая инспекция, докладная записка о нецелевом использовании мяса и многое другое. Всё это проектировщик загружает в свою голову в ходе долгих бесед со специалистами, вникания в суть документооборота, наблюдений за происходящими процессами и т.д.
  2. Обобщение. На все выделенные ранее сущности навешиваем ярлычки. Про слона, жирафа и тигров скажем, что они млекопитающие, про тигров и сов - что они хищные, про ветеринара, уборщицу и системного администратора - что они сотрудники, про вольеры и клетки - что они являются местами обитания. Когда ни один из объектов не останется без ярлычка, нужно проанализировать сочетания ярлычков и сделать так, чтобы на каждом объекте или явлении оказалось повешено по одному главному ярлыку, называемому классом. Все повешенные ранее ярлычки выстраиваем в иерархию так, чтобы они стали предками (субклассами) тех классов, от которых мы будем порождать объекты.
  3. Описание взаимодействия объектов. Хищники поедают мясо, травоядные - сено, ветеринар лечит животных, билетный кассир принимает деньги, а завхоз их тратит, экскурсовод обслуживает посетителей, а главбух - налоговую инспекцию.
  4. Описание интерфейсов (свойств и методов) классов. Хотя, впрочем, обычно это делается параллельно двум предыдущим пунктам.
  5. Реализация модели в программном коде и в структуре базы данных.

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

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

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

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

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

  1. Выделение сущностей, присутствующих в предметной области. Куда же без этого?
  2. Обобщение. На все выделенные ранее сущности навешиваем ярлычки (классы). Если есть возможность и желание заняться обобщением ярлычков, то делаем это. При проектировании системы классов руководствуемся исключительно максимизацией наиболее точного соответствия системы классов тому множеству понятий, которое присутствует в предметной области.
  3. Описание взаимодействия объектов.
  4. Описание интерфейсов (свойств и методов) классов.
  5. Реализация модели в программном коде и в структуре базы данных.

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

Что дальше?

Дальше можно двигаться в следующих направлениях:

Заключение

- Ты всёсла поняласла? - спросила Тофсла.
- Ни каплисла! - сказала Вифсла и плюнула вишнёвой косточкой в судью.

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

Выяснили, что множественная классификация:

И самое главное, что множественная классификация в принципе реализуема.

ОСП   ООП   к алгоритмизации   СУБД   Экспертные системы   ЯиМП   3GL   4GL   5GL   ТП

Знаете ли Вы, что наследование, Inheritance - Наследование в объектно-ориентированном программировании - это свойство объекта, заключающееся в том, что характеристики одного объекта (объекта-предка) могут передаваться другому объекту (объекту-потомку) без их повторного описания. Наследование упрощает описание объектов.

НОВОСТИ ФОРУМАФорум Рыцари теории эфира
Рыцари теории эфира
 18.11.2019 - 19:10: ВОЙНА, ПОЛИТИКА И НАУКА - War, Politics and Science -> Проблема государственного терроризма - Карим_Хайдаров.
16.11.2019 - 16:57: СОВЕСТЬ - Conscience -> РУССКИЙ МИР - Карим_Хайдаров.
16.11.2019 - 16:53: ВОСПИТАНИЕ, ПРОСВЕЩЕНИЕ, ОБРАЗОВАНИЕ - Upbringing, Inlightening, Education -> Просвещение от Марины Мелиховой - Карим_Хайдаров.
16.11.2019 - 12:16: ВОСПИТАНИЕ, ПРОСВЕЩЕНИЕ, ОБРАЗОВАНИЕ - Upbringing, Inlightening, Education -> Просвещение от Игоря Кулькова - Карим_Хайдаров.
16.11.2019 - 07:23: ВОСПИТАНИЕ, ПРОСВЕЩЕНИЕ, ОБРАЗОВАНИЕ - Upbringing, Inlightening, Education -> Просвещение от Вячеслава Осиевского - Карим_Хайдаров.
15.11.2019 - 06:45: ВОЙНА, ПОЛИТИКА И НАУКА - War, Politics and Science -> РАСЧЕЛОВЕЧИВАНИЕ ЧЕЛОВЕКА. КОМУ ЭТО НАДО? - Карим_Хайдаров.
14.11.2019 - 12:35: ВОСПИТАНИЕ, ПРОСВЕЩЕНИЕ, ОБРАЗОВАНИЕ - Upbringing, Inlightening, Education -> Просвещение от Светланы Вислобоковой - Карим_Хайдаров.
13.11.2019 - 19:20: ЭКОНОМИКА И ФИНАНСЫ - Economy and Finances -> ПРОБЛЕМА КРИМИНАЛИЗАЦИИ ЭКОНОМИКИ - Карим_Хайдаров.
12.11.2019 - 11:53: ВОСПИТАНИЕ, ПРОСВЕЩЕНИЕ, ОБРАЗОВАНИЕ - Upbringing, Inlightening, Education -> Просвещение от Бориса Сергеевича Миронова - Карим_Хайдаров.
12.11.2019 - 11:49: ВОСПИТАНИЕ, ПРОСВЕЩЕНИЕ, ОБРАЗОВАНИЕ - Upbringing, Inlightening, Education -> Просвещение от Веры Лесиной - Карим_Хайдаров.
10.11.2019 - 23:14: ВОСПИТАНИЕ, ПРОСВЕЩЕНИЕ, ОБРАЗОВАНИЕ - Upbringing, Inlightening, Education -> Просвещение от Кирилла Мямлина - Карим_Хайдаров.
05.11.2019 - 21:56: ВОСПИТАНИЕ, ПРОСВЕЩЕНИЕ, ОБРАЗОВАНИЕ - Upbringing, Inlightening, Education -> Декларация Академической Свободы - Карим_Хайдаров.
Bourabai Research Institution home page

Bourabai Research - Технологии XXI века Bourabai Research Institution