Поиск по сайту
 
РЕШЕНИЯ
    Медицина
    Строительство
    Министерства и ведомства
    Компьютерные залы
    Казино
    Сфера услуг
    Спортивные комплексы
    Страховые компании
    Финансовые общества
    Другие
НОВОСТИ
17.05.2015
Обновление № 1552 для систем DentExpert, MedExpert и MedExpert Cosmetology
Общие направления доработок:
— ускорение работы Системы в некоторых режимах
— допол...
             подробнее
11.04.2015
Обновление № 1520 для систем DentExpert, MedExpert и MedExpert Cosmetology
Общие направления доработок:
— дополнительные возможности отображения расписания приёма ...
             подробнее
21.03.2015
Обновление № 1484 для систем DentExpert, MedExpert и MedExpert Cosmetology
Общие направления доработок:
— добавлена возможность привязки цен услуг к базовому курсу...
             подробнее
26.12.2014
Обновление № 1446 для систем DentExpert, MedExpert и MedExpert Cosmetology

Общие направления доработок:
- дополнительные возможности картотеки пациентов
...

             подробнее
26.06.2014
Обновление № 1365 для систем DentExpert, MedExpert и MedExpert Cosmetology

Общие направления доработок:
- в Регистратуру добавлены дополнительные отчёты по па...

             подробнее
10.06.2014
Обновление № 1323 для систем DentExpert, MedExpert и MedExpert Cosmetolog

Общие направления доработок:
- в финансовый монитор Руководителя добавлен учёт част...

             подробнее
Публикации>АДС-технология программирования
В.В.Соколов
АДС-ТЕХНОЛОГИЯ ПРОГРАММИРОВАНИЯ

    УДК 004.4

     В.В.Соколов
    
     ТЕХНОЛОГИЯ ПРОГРАММИРОВАНИЯ АКТИВНЫХ ДИНАМИЧЕСКИХ СОЕДИНЕНИЙ ОБЪЕКТОВ
    
     В статье описана технология программирования активных динамических соединений объектов (АДС- технология), представляющая собой новую парадигму использования объектно-ориентированного программирования (ООП).
     Декларируются принципы построения программных объектов, позволяющие повысить эффективность разработки сложных программных систем с использованием ООП:
     - сепарация интерфейса – разделение внешних свойств объекта на входные и выходные;
     - активность – возможность передачи управления объекту путем вызова его метода ядра;
     - транзакционность - контроль состояния объекта при изменениях значений входных свойств;
     - динамические соединения – способность объектов к взаимодействию посредством соединений.
    
     Введение
    
     Большим шагом вперед в ООП [1] было объединение данных и функций их обработки в одном объекте, что должно было повысить модульность программ. Однако синтаксически и по способу использования объект остался все-таки структурой данных, что наложило свой отпечаток на структуру программы и процесс ее разработки.
     Основным эффектом от применения ООП является повышение модульности программы, но при этом вопросы межмодульного взаимодействия проработаны недостаточно. Взаимодействие объектов производится либо косвенно через посредника (например, главную программу), либо непосредственно через указатели на объекты конкретных классов (агрегация). При этом косвенное взаимодействие является неэффективным, а агрегация накладывает жесткие ограничения на классы взаимодействующих объектов.
     Структура объектов и иерархия классов в ООП, как правило, статичны, программист лишен возможности произвольно динамически управлять взаимосвязями объектов различных классов, строить динамические иерархии объектов.
     При практическом использовании ООП объекты оказываются внутренне слабо интегрированными вследствие слабых взаимосвязей между членами классов. Это приводит к тому, что объект не обладает функциональной целостностью и используется поэлементно. Объект по своей природе пассивен, и только внешнее воздействие на его отдельные открытые члены приводит к некоторым действиям или локальным изменениям объекта.
     Именно отсутствие функциональной целостности объекта и ее контроля может приводить к ошибкам в программе, когда, например, используются предыдущие значения зависимых свойств объекта, которые еще не пересчитаны для уже измененных значений независимых свойств.
     Современные системы программирования, поддерживающие ООП, предлагают средства высокоуровневой автоматизации проектирования на основе языка UML (например, Rational Rose, Borland Together), но не развивают инструменты для автоматизации собственно программирования и не решают проблемы построения законченной программы из описаний классов. ООП не отвечает на главный вопрос, как из объектов построить систему.
     Несмотря на указанные недостатки, ООП является интересной технологией программирования и содержит все необходимые средства для ее эффективного использования. Важно иметь определенную методологию применения ООП для построения эффективных программ.
     Как в свое время технология структурного программирования утвердила рекомендации по написанию «правильных» программ на алгоритмических языках, так и АДС-технология декларирует определенные принципы использования ООП для эффективного создания компьютерных программ.
     Вероятно, что отдельные принципы АДС-технологии уже использовались многими программистами, и они не увидят для себя ничего нового. Действительно, АДС-технология с первого взгляда не является революционной, однако она определяет интересную и достаточно целостную парадигму программирования с использованием ООП, которая может придать программам и самому процессу разработки новые свойства.
     Основным достоинством АДС-технологии является то, что она не требует внесения изменений в существующие языки программирования и может использоваться сразу после прочтения этой статьи.
    
     1. Концепция АДС-технологии
    
     Будам рассматривать объект как «черный ящик», содержащий:
     - вход – набор свойств, доступных для изменения извне объекта, значения которых используются для вычисления производных значений (выхода);
     - выход – набор свойств, доступных только для чтения извне объекта, значения которых вычисляются и являются производными от значений входа;
     - ядро – главный метод, реализующий преобразование данных входа в данные выхода.
    
    
             Рисунок 1. Схема объекта.
    
     Кратко концепцию АДС-технологии можно изложить в виде следующей системы взглядов на объект:
     - объект – это не переменная структурного типа данных, а программный модуль;
     - свойства объекта – это локальная статическая среда модуля, его данные, сохраняющие свои значения;
     - свойства объекта можно разделить на исходные данные (вход), выходные результаты (выход) и промежуточные значения;
     - методы объекта – это операции над данными объекта;
     - объект может получать управление (активироваться) для обработки исходных данных и получения результатов путем применения операций, заданных в виде методов, к исходным данным в определенной последовательности (алгоритм ядра);
     - объект должен контролировать соответствие текущих выходных результатов текущим исходным данным;
     - процесс от начала изменения исходных данных до окончания вычисления всех выходных результатов объекта может выполняться транзакционно, с откатом изменений или без отката;
     - объект может пассивно взаимодействовать с другими объектами любых классов путем чтения значений их свойств;
     - объект может активно взаимодействовать с другими объектами любых классов путем передачи им управления (активации).
    
     Исходя из вышеизложенного, можно сформулировать следующее видение процесса программирования:
     - объект является атомарной единицей программы, его структура не может динамически изменяться во время выполнения программы;
     - объекты могут создавать динамические соединения путем связывания выходных свойств одних объектов с входными свойствами других объектов, а также получать возможность активации соединенного объекта путем вызова его метода ядра;
     - любая программа может быть построена в виде соединений объектов;
     - процесс разработки программы можно свести к разработке классов и синтезу соединений объектов; этот процесс можно визуализировать в интегральной среде разработки;
     - один объект в разные моменты времени выполнения может участвовать в различных соединениях, следовательно, из одних и тех же объектов можно динамически во время выполнения синтезировать разные соединения;
     - соединения объектов можно оформлять в виде классов соединений, которые могут соединяться с другими соединениями, образуя сложные полиморфные конструкции;
     - возможно создание интерактивных программ, в которых пользователи сами будут синтезировать нужные соединения для решения своих задач или использовать интеллектуальный интерфейс для автоматического синтеза нужного соединения по заданной постановке задачи.
    
     Реализация концепции АДС-технологии изложена ниже в виде совокупности принципов, раскрывающих более детально механизмы использования данной концепции.
    
     2. Принцип сепарации интерфейса объекта
    
     В соответствии с концепцией АДС-технологии принцип сепарации интерфейса объекта можно сформулировать таким образом:
         доступные извне свойства объекта должны быть разделены на входные и выходные свойства.
     Входные свойства должны быть доступны для записи и чтения извне объекта, и только для чтения изнутри объекта.
     Выходные свойства объекта должны быть доступны только для чтения извне объекта и чтения и записи изнутри объекта.
     Такое разделение интерфейса позволяет более формально рассматривать объект как некоторую функцию, сократить ошибки использования объекта и создать предпосылки для реализации остальных принципов АДС-технологии.
    
     3. Принцип внутренней активности объекта
    
     Как известно, в ООП объект, как экземпляр класса, содержит набор свойств и методов.
     С точки зрения традиционного программирования объект можно рассматривать как программный модуль, содержащий множество данных и функций их обработки (операций). При этом для объекта характерно следующее:
     - данные являются статическими (их время жизни совпадает со временем жизни объекта и их значения сохраняются);
     - данные являются глобальными в пределах объекта (все функции имеют доступ ко всем данным), что избавляет от необходимости передавать их в качестве параметров;
     - объект используется путем обращения к его переменным (для получения или изменения их значений) и вызова его отдельных функций извне объекта (например, из главной программы).

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

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

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

     Исходя из выше изложенного, сформулируем принцип внутренней активности объекта:
         объект должен содержать главный метод, выполняющий вычисления значений выходов по заданным значениям входов, а также контроль состояния и другие действия внутри объекта.
     Главный метод объекта, естественно, может наследоваться и переопределяться.
     Реализация принципа внутренней активности объекта позволит получить следующий положительный эффект:
     - объекты станут более функциональными и автономными;
     - упрощается механизм генерации произвольного количества программных модулей- объектов;
     - упрощается реализация моделей управления подпрограммами (параллельные задачи, сопрограммы) на основе активных объектов;
     - объекты получат свойство активности, совмещая свойства структур данных и функций;
     - уменьшатся издержки на использование объекта в нескольких программах;
     - уменьшится количество ошибок.
    
     Пример 1. Использование принципа активности.
     Задача: в геометрической фигуре призма с основанием прямоугольник (параллелепипед) заданы стороны основания A, B и высота H;
     требуется определить площадь основания S и объем фигуры V.
     Т.к. имя класса в языке С++ нельзя использовать в качестве имени главного метода (оно зарезервировано за конструктором), будем использовать имя act().
     Традиционный подход:
     class Tprizm
     {protected:
     int a, b, h;
     public:
     void set_a(int x) {a=x;}
     void set_b(int x) {b=x;}
     void set_h(int x) {h=x;}
     int get_a() {return a;}
     int get_b() {return b;}
     int get_h() {return h;}
     int get_s() {return a*b;}
     int get_v() {return get_s()*h;}
     };
     void main()
     {
     Tprizm prz;
     prz.set_a(4);
     prz.set_b(5);
     prz.set_h(10);
     cout<<"s="<< prz.get_s()<<" v="<< prz.get_v();
     }
    
     Простой АДС-подход (всё вычисляется в главном методе):
     class Tprizm
     { protected:
     int a, b, h, s, v;
     public:
     void set_a(int x) {a=x;}
     void set_b(int x) {b=x;}
     void set_h(int x) {h=x;}
     int get_a() {return a;}
     int get_b() {return b;}
     int get_h() {return h;}
     int get_s() {return s;}
     int get_v() {return v;}
     void act()
     {
     s=a*b;
     v=s*h;
     }
     };
    
     Модульный АДС-подход(главный метод использует готовые методы):
     class Tprizm
     { protected:
     int a, b, h, s, v;
     public:
     void set_a(int x) {a=x;}
     void set_b(int x) {b=x;}
     void set_h(int x) {h=x;}
     int get_a() {return a;}
     int get_b() {return b;}
     int get_h() {return h;}
     int get_s() {return s;}
     int get_v() {return v;}
     void calc_s() {s= a*b;}
     void calc_v() {v= s*h;}
     void act()
     {
     calc_s();
     calc_v();
     }
     };
    
     void main()
     {
     Tprizm prz;
     prz.set_a(4);
     prz.set_b(5);
     prz.set_h(10);
     prz.act();
     cout<<"s="<< prz.get_s()<<" v="<< prz.get_v();
     }
    
     4. Принцип транзакционности изменения объекта
    
     Если вход объекта обозначить как X, выход – Y, а ядро – F, то можно сказать, что объект выполняет преобразование X -> Y по закону F:
             Y = F (X).

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

     Т.к. функция ядра F реализована программно, то:
     - преобразование требует определенного времени на выполнение функции ядра;
     - после задания (изменения) входных значений X выходные значения Y могут оказаться еще не пересчитанными или находиться в процессе пересчета.

     Таким образом, объект может находиться в следующих состояниях:
     - «1» адекватное состояние - значение выхода Yо вычислено по заданному значению Xо (функция ядра F конкретизирована парой значений (Xo, Yo), F успешно выполнила преобразование Xo->Yo, Yo соответствует Xo);
     - «-1» неадекватное состояние - значение выхода Yо еще не вычислено по заданному (измененному) значению Xо или не может быть вычислено (Yo не соответствует Xo);
     - «0» переходное состояние – начато и еще не закончено изменение объекта (изменение значения входа Xо и/или выполнение функции ядра F для вычисления выхода Yо).

     Схема перехода состояний объекта может быть следующей:
     - «-1» является начальным состоянием объекта (после его создания);
     - перед началом изменения значения Xo и/или запуском функции ядра F объект из любого состояния переходит в состояние «0»;
     - после окончания изменения значения Xo объект из состояния «0» переходит в состояние «-1», если непосредственно после окончания ввода не запускается функция ядра F;
     - после окончания функции ядра F объект переходит из состояния «0» в состояние «1» при успешном вычислении, иначе - в состояние «-1».

     Задача состоит в том, чтобы:
     - контролировать текущее состояние объекта (использовать значения выхода Yo только в адекватном состоянии объекта и применять метод ядра F для перевода его в адекватное состояние при необходимости);
     - корректно переводить объект из одного адекватного состояния в другое адекватное состояние или выполнять откат к предыдущему состоянию.
     Такой механизм используется в базах данных и носит название «транзакции».
     Суть транзакции, как известно, заключается в том, чтобы поддерживать объект (базу данных) в корректном (непротиворечивом) состоянии.
     Механизм транзакции заключается в том, что все изменения либо целиком принимаются, либо целиком отменяются с откатом сделанных изменений до начального состояния (до начала транзакции).
     Аналогичный механизм полезно применить и к объекту. Это обеспечит невозможность получения некорректных результатов от объекта.

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

     Принцип транзакционности особенно важен при событийной модели управления вычислениями и параллельном доступе к объекту. В этом случае целесообразно применять механизм изоляции транзакции на уровне объекта или отдельных его свойств.
     Т.е. состояние «0» можно использовать в качестве флага блокировки доступа к объекту, что позволит избежать одновременного изменения и пересчета объекта из нескольких точек в программе.
     Одним из способов реализации принципа транзакционности без блокировок, например, может быть следующий:
     - объект должен содержат свойство, хранящее его текущее состояние, например, «state», которое конструктор установит в начальное состояние «-1»;
     - любой метод изменения входных свойств типа set() должен устанавливать состояние state = -1 и сохранять для возможности отката предыдущие значения входных свойств, которые он изменил, и выходных свойств, которые зависят от этого входного свойства;
     - любой метод get() для выходного свойства должен проверять состояние state и возвращать значение выходного свойства, только если объект находится в адекватном состоянии;
     - объект должен содержать метод commit(), который принимает сделанные изменения (делает их текущими) и устанавливает признак адекватности state=1;
     - объект должен содержать метод rollback(), который откатывает сделанные изменения к предыдущему состоянию (восстанавливает сохраненные значения свойств и состояние state);
     - функция ядра должна по возможности «интеллектуально» пересчитывать значения выходных свойств по измененным входным свойствам (пересчитывать только те выходные свойства, которые зависят от измененных входных свойств) и вызвать метод commit() при успешном выполнении или rollback() при неуспешном.
     Вариантами реализации могут быть:
     - транзакционность без возможности отката – отпадает необходимость сохранять предыдущие значения свойств, упрощаются или могут отсутствовать методы commit() и rollback() (функция ядра сама будет управлять состоянием state);
     - хранить состояния адекватности по каждому выходному свойству, что позволит более гибко использовать объект (разрешать доступ к тем выходным свойствам, которые находятся в адекватном состоянии);
     - транзакционность с блокировками – объект должен содержать метод начала транзакции begin_transact(), который установит state=0 с запретом доступа к объекту, а методы commit() и rollback() закончат транзакцию;
     - разрешать доступ к выходным свойствам при неадекватном состоянии объекта (главная программа сама контролирует состояние) или запрещать (генерировать ошибку).
    
     Пример 2. Использование принципа транзакционности без отката и блокировки.
     В качестве условий задачи возьмем пример 1 и усовершенствуем программу, добавив в объект принцип транзакционности.
    
     class Tprizm
     {
     protected: // скрываем прямой внешний доступ
     int state; // состояние объекта
     int a, b, h, s, v;
     // отдельные методы вычисления выходных свойств
     void calc_s() {s= a*b;}
     void calc_v() {v= s*h;}
     public:
     // методы изменения входных свойств – перевод объекта в неадекватное состояние
     void set_a(int x) {state = -1; a=x;}
     void set_b(int x) {state = -1; b=x;}
     void set_h(int x) {state = -1; h=x;}
     // методы чтения входных свойств – без ограничений
     int get_a() {return a;}
     int get_b() {return b;}
     int get_h() {return h;}
     int get_state() {return state;}
     // методы чтения выходных свойств – в любом состоянии
     int get_s() {return s;}
     int get_v() {return v;}
     // методы commit и rollback – тривиальны при отсутствии откатов
     void commit() {state = 1;}
     void rollback() {state = -1;}
     // конструктор установит начальное состояние - неадекватное
     Tprizm() {state = -1;}
     // функция ядра управляет транзакционностью интеллектуально
     void act()
     {
     if (state != 1) // не делает лишнюю работу
     {
     calc_s();
     calc_v();
     commit();
     }
     }
     };
     void main()
     {
     Tprizm prz;
     prz.set_a(4);
     prz. set_b(5);
     prz.set_h(10);
     prz.act();
     // объект можно использовать только в адекватном состоянии
     if (prz.get_state() == 1) cout<<"s="<< prz.get_s()<<" v="<< prz.get_v();
     else cout<<"Объект находится в неадекватном состоянии!";
     }
    
     5. Принцип взаимодействия объектов посредством соединений
    
     Принцип активности позволил перенести часть кода из главной программы в объект и придал ему возможность активироваться подобно функции для выполнения заданных действий, а также появилась возможность генерировать требуемое количество активных объектов как программных модулей, что создает потенциальные предпосылки для эффективной реализации параллельных задач.
     ООП декларирует принцип взаимодействия объектов путем вызова методов одного объекта другим, однако объект функционирует только в пределах своей оболочки (локальной среды) и не может непосредственно взаимодействовать с другими объектами (он их не видит). На практике проблема решается либо объявлением глобальных объектов и доступом к ним по имени, что грубо нарушает принцип независимости модуля от внешней среды, либо взаимодействие объектов организуется через посредника, например, главную программу, либо путем связывания объектов по ссылке.
     ООП предоставляет следующие возможности по взаимодействию объектов:
     - наследование (обобщение) – два объекта фактически объединяются в один с доступом ко всем членам класса;
     - дружественные классы – функции одного класса имеют доступ к членам другого класса;
     - композиция объектов (агрегация по значению) – один объект является членом (свойством) другого объекта;
     - связь объектов (агрегация по ссылке) – один объект содержит ссылку на объект другого класса.
     Недостатками этих возможностей является то, что:
     - такое взаимодействие в большинстве случаев является статическим (наследование, дружба, композиция);
     - для транслятора необходимо точно указывать класс объекта, с которым требуется установить взаимодействие, что неприемлемо для динамического взаимодействия с объектами разных классов.
     В АДС-технологии под взаимодействием объектов будем понимать непосредственный доступ к членам одного объекта из другого объекта.
    
     5.1. Пассивное соединение объектов
    
     Одним из подходов к взаимодействию двух объектов является такая организация взаимодействия, при котором один объект использует свойства другого объекта, не указывая класс этого объекта, а указывая только тип данных свойства. Это потенциально позволяет одному объекту взаимодействовать с объектами любых классов, которые содержат свойства данного типа, т.к. собственно класс объекта не имеет значения.
     Рассмотрим схему взаимодействия двух объектов (рис.2).
     Будем называть объект А источником, а объект B – приемником данных.
     Дано:
     - объект А, имеющий два свойства (x, y), x – вход, y – выход;
     - объект B, имеющий два свойства (x, y), x – вход, y – выход;
     Требуется:
     - соединить объекты таким образом, чтобы выход объекта A использовался в качестве входа объекта B.
    
    
        Рисунок 2. Схема взаимодействия объектов.
    
     Решение:
     - свойство X объекта B должно быть ссылкой (адресом) на константу типа, соответствующего типу свойства X объекта A (вход приемника соответствует выходу источника);
     - для связи объектов достаточно в качестве значения свойства B.X указать адрес свойства A.X.
     Сформулируем основные правила пассивного соединения объектов:
     - объект-приемник не должен влиять на объект-источник данных;
     - соединять можно только вход приемника с выходом источника;
     - вход приемника, предназначенный для соединения, должен быть ссылкой на константу;
     - создание связи объектов заключается в записи адреса выходного свойства объекта- источника во входное свойство объекта-приемника.

     Следствия:
     - объект-приемник не может использоваться самостоятельно без связи с другими объектами;
     - объект-приемник не может быть переведен в адекватное состояние, если он не связан с объектом-источником;
     - объект-приемник автоматически получает значение от объекта-источника при его изменении, т.к. ссылается на значение свойства объекта-источника.
     Принцип пассивного взаимодействия объектов посредством соединений:
         объекты должны обеспечивать механизм динамического соединения объектов путем записи в свойства объекта-приемника ссылок на значения объектов-источников различных классов.
     Примечание: теоретически в качестве ссылки на значение можно использовать как ссылку на свойство, так и ссылку на метод (get()) получения значения свойства в объекте-источнике.
     Таким образом, получается определенный вид объектов-приемников, способных подключаться к любым подходящим по типу соединяемых свойств объектам. При этом приемники не оказывают никакого влияния на структуру и функционирование источников (источники ничего не знают о подключенных к ним приемниках и о том, как они используют значения их свойств).
     Единственный вопрос – как приемник получает адрес свойства источника? Здесь возможны два варианта:
     - получить адрес нужного свойства объекта-источника непосредственно, если доступ к нему открыт;
     - включить в состав объекта-источника метод получения адреса свойства.
     Второй вариант является более предпочтительным, т.к. позволяет управлять в объекте- источнике разрешением на подключение к его свойствам, причем как к открытым, так и закрытым.
     Будем называть свойство-ссылку объекта-приемника, предназначенное для соединения с другим объектом, коннектором.
     Объект-приемник может содержать не один, а несколько коннекторов. При этом в разные моменты времени коннекторы одного объекта могут быть соединены с разными объектами- источниками.
    
    
        Рисунок 3. Схождение соединений.
    
     С другой стороны, к одному источнику могут быть подключены несколько приемников.
    
    
        Рисунок 4. Разветвление соединений.
    
     Кроме того, к одному свойству источника могут быть подключены несколько приемников.
    
    
        Рисунок 5. Соединение типа «звезда».
    
     Любой объект-приемник, в свою очередь, может быть источником для других объектов- приемников.
    
    
        Рисунок 6. Цепочка соединений.
    
     Используя циклическое соединение, можно на вход источника передавать значения из приемника и возвращать полученный результат назад в приемник.
    
    
        Рисунок 7. Циклическое соединение.
    
     Формально, объект имеет право свой собственный выход соединить со своим входом, получая автосоединение.
    
    
        Рисунок 8. Автосоединение.
    
     Таким образом, объекты могут соединяться в сколь угодно сложные структуры, соответствующие решаемой задаче.
    
     Пример 3. Использование принципа пассивного соединения объектов.
     Условия задачи: выполнить расчеты над геометрическими фигурами:
     - прямоугольник;
     - круг;
     - призма с основанием - прямоугольник;
     - цилиндр.
     План решения:
     создадим два объекта-источника (прямоугольник и круг) и один объект- приемник (призма), причем основанием этой призмы будет сначала прямоугольник, а затем круг.
     class Trectangle
     {
     protected:
     int a, b, s;
     // методы вычисления выходных свойств
     void calc_s() {s= a*b;} // площадь
     public:
     // методы изменения входных свойств
     void set_a(int x) {a=x;}
     void set_b(int x) {b=x;}
     // методы чтения выходных свойств
     int get_s() {return s;}
     // адреса выходных свойств – для связи
     int *getadr_s() {return &s;}
     // функция ядра
     void act() {calc_s();}
     };
     class Tcircle
     {
     protected:
     int r, s;
     // методы вычисления выходных свойств
     void calc_s() {s= 3.14*r*r;} // площадь
     public:
     // методы изменения входных свойств
     void set_r(int x) {r=x;}
     // методы чтения выходных свойств
     int get_s() {return s;}
     // адреса выходных свойств – для связи
     int *getadr_s() {return &s;}
     // функция ядра
     void act() {calc_s();}
     };
     class Tprizma
     {
     protected:
     int h, v; // высота и объем
     const int *s; // связанное свойство – площадь основания
     // методы вычисления выходных свойств
     void calc_v() {v= *s*h;} // объем
     public:
     // методы изменения входных свойств
     void set_h(int x) {h=x;}
     // методы чтения выходных свойств
     int get_v() {return v;}
     // методы создания и разрыва соединения
     void connect_s(int *x) { if (x!=NULL) s=x;}
     void disconnect_s() {s=NULL;}
     Tprizma() {s=NULL;}
     // функция ядра
     int act() { if (s==NULL) return 0; calc_v();return 1;}
     };
     void main()
     {
     Trectangle rect;
     Tcircle circ;
     Tprizma priz;
     rect.set_a(5); rect.set_b(6); rect.act();
     cout<<"rect:s="<< rect.get_s();
     circ.set_r(7); circ.act();
     cout<<" circ:s="<< circ.get_s();
     priz.connect_s(rect.getadr_s()); // Соединяем с прямоугольником - получаем призму
     priz.set_h(10); priz.act();
     cout<<" prizma:v="<< priz.get_v();
     priz.disconnect_s();
     priz.connect_s(circ.getadr_s()); // Соединяем с кругом – получаем цилиндр той же высоты
     priz.act();
     cout<<" cilindr:v="<< priz.get_v();
     }
    
     Удобно создать универсальный шаблон класса пассивного коннектора для включения его в любые классы объектов-приемников.
    
     Пример шаблона пассивного коннектора:

     template < class T > // T должен соответствовать типу свойства объекта-источника
     class TConnectorPass
     {
     protected:
     const T *value; // ссылка на свойство объекта-источника
     public:
     // конструктор обнуляет ссылку при создании объекта
     TConnectorPass () {value=NULL;}
     // метод для соединения свойства value с нужным свойством объекта-источника
     int connect(T *in)
     { if (in!=NULL) {value=in; return 1;} else return 0;}
     // метод для разрыва соединения
     void disconnect() {value=NULL;}
     // получение значения связанного свойства из объекта-источника
     T get_value() { return *value;}
     };
    
     5.2. Активное соединение объектов
    
     Недостатком пассивного соединения объектов является невозможность контроля и управления адекватностью объекта-источника из объекта-приемника. Например, если в цепочке соединений A->B->C->D объект A перешел в неадекватное состояние, то объект C не может об этом узнать.
     Поэтому, задачу контроля адекватности в цепочке соединений объектов приходится возлагать на главную программу, что связано с определенными трудностями, т.к. объекты должны приводиться в адекватное состояние в соответствии с последовательностью их соединения. При динамических соединениях отследить порядок текущего соединения объектов из главной программы довольно сложно.
     Лучшим решением является возможность вызова методов ядра объектов-источников из объектов-приемников по цепочке соединений, если объекты-источники не находятся в адекватном состоянии. Это придаст свойство активности не только отдельным объектам, но и всем цепочкам соединений, сделает их еще более автономными.
     Принцип активного взаимодействия объектов посредством соединений:
         объекты-приемники должны обеспечивать механизм управления состоянием объекта- источника путем вызова его метода ядра из объекта-приемника и проверки возвращаемого значения в динамических соединениях объектов различных классов.
     На сегодняшний день проблема существующих реализаций трансляторов для ООП заключается в том, что нельзя вызвать метод объекта по ссылке без ссылки на сам объект (для выполнения метода класса в контексте конкретного объекта), а значит, требуется привязка к классу объекта на этапе трансляции, что противоречит принципу динамических соединений.
     Одним из способов реализации принципа активного соединения может быть хранение в объекте-приемнике следующих дополнительных свойств:
     - ссылка на метод ядра объекта-источника, по которой можно будет вызвать метод ядра и привести источник в адекватное состояние;
     - ссылка на сам объект-приемник для передачи ее в качестве параметра методу ядра класса источника, чтобы метод ядра выполнился для нужного объекта.
     Т.к. объект-приемник может содержать несколько коннекторов, связанных с разными объектами, то, очевидно, нужно хранить указанные данные для каждого ссылочного свойства. Поэтому целесообразно создать универсальный шаблон класса активного коннектора для включения его в любые классы объектов-приемников.

     Пример шаблона активного коннектора:

     template < class T > // T должен соответствовать типу свойства объекта-источника
     class TConnector
     {
     protected:
     const T *value;     // ссылка на свойство объекта-источника
     const void *obj;     // ссылка на сам объект-источник
     int (*act)(void*);     // ссылка на метод ядра объекта-источника
     public:
     // конструктор обнуляет все ссылки при создании объекта
     TConnector () {value=NULL; obj=NULL; act=NULL;}
     // метод для соединения свойства value с нужным свойством объекта-источника
     int connect(T *in, void *ob, int (*ac)(void*))
     {if ((in!=NULL)&&(ob!=NULL)&&(ac!=NULL))
     {value=in; obj=ob; act=ac; return 1;}
     else return 0;
     }
     // метод для разрыва соединения
     void disconnect() { value=NULL; obj=NULL; act=NULL; }
     // метод вызова метода ядра объекта-источника для данного свойства
     int call_act() {return (*act)(obj);}
     // получение значения связанного свойства из объекта-источника
     T get_value() { return *value; }
     };
    
     Использование активных соединений накладывает определенные ограничения на методы ядра объектов:
     - метод ядра объекта-источника должен быть статическим;
     - метод ядра объекта-источника должен в качестве параметра получать ссылку на объект своего класса;
     - метод ядра объекта-приемника должен управлять адекватностью всех непосредственно связанных с ним объектов-источников (должен вызвать соответствующие методы ядра источника для каждого коннектора перед использованием их значений);
     - метод ядра должен возвращать состояние объекта.

     Следствия:
     - при вызове метода ядра некоторого объекта в цепочке соединения будут вызваны все методы ядер связанных объектов-источников в порядке их соединения на всю глубину цепочки соединения вверх от текущего объекта;
     - метод ядра возвратит в качестве результата адекватное состояние, если все объекты в цепочке вызова переведены в адекватное состояние.
     Например, если есть цепочка соединения A->B->C->D и вызывается метод ядра объекта C, то будут вызваны методы ядра объектов B и A. Если метод ядра реализовать рационально таким образом, чтобы пересчет выходов выполнялся только при измененных входах (хранить старые значения входов), то это не приведет к излишним вычислениям.
     Создание и разрыв соединений инициирует внешняя по отношению к объектам программа путем вызова соответствующих методов коннекторов и передачи им требуемых параметров.

     Сформулируем основные правила активного соединения объектов:
     - объект-приемник для каждой связи должен содержать следующие данные об объекте-источнике:
     - ссылка на требуемое свойство (предмет связи);
     - ссылка на метод ядра (для перевода в адекватное состояние);
     - ссылка на объект-источник (для передачи в качестве параметра ядру);
     - объект-приемник должен содержать методы для установки и разрыва соединения с объектом-источником;
     - установка и разрыв соединений инициируется извне объекта;
     - метод ядра объекта-приемника должен вызывать методы ядра всех связанных объектов-источников перед использованием их свойств для перевода их в адекватное состояние;
     - метод ядра объекта-приемника может выполняться, если все связанные объекты находятся в адекватном состоянии.
    
     Пример 4. Использование принципа активного соединения объектов.
     За основу возьмем пример 3 и доработаем его.

     class Trectangle
     {protected:
     int a, b, s;
     int state; // введем свойство состояния объекта
     // методы вычисления выходных свойств
     void calc_s() {s= a*b;} // площадь
     public:
     // методы изменения входных свойств
     void set_a(int x) {a=x;state=-1;}
     void set_b(int x) {b=x;state=-1;}
     // методы чтения выходных свойств
     int get_s() {if (state==1) return s;}
     // адреса выходных свойств – для связи
     int *getadr_s() {return &s;}
     // конструктор
     Trectangle() {state=-1;}
     // функция ядра
     static int act(Trectangle *obj) {if (obj->state!=1) { obj->calc_s(); obj->state=1;} return obj->state;}
     };
     class Tcircle
     {
     protected:
     int r, s;
     int state; // введем свойство состояния объекта
     // методы вычисления выходных свойств
     void calc_s() {s= 3.14*r*r;} // площадь
     public:
     // методы изменения входных свойств
     void set_r(int x) {r=x; state=-1;}
     // методы чтения выходных свойств
     int get_s() {if (state==1) return s;}
     // адреса выходных свойств – для связи
     int *getadr_s() {return &s;}
     // конструктор
     Tcirle() {state=-1;}
     // функция ядра – интеллектуальная
     static int act(Tcircle *obj) {if (obj->state!=1) {obj->calc_s();obj->state=1;} return obj->state;}
     };
     class Tprizma
     {
     public:
     Tconnector < int > s; // связанное свойство – площадь основания
     protected:
     int h, v; // высота и объем
     int state; // введем свойство состояния объекта
     // методы вычисления выходных свойств с использованием связи
     void calc_v() {v= s.get_value()*h;} // объем
     public:
     // методы изменения входных свойств
     void set_h(int x) {h=x; state=-1;}
     // методы чтения выходных свойств
     int get_v() {return v;}
     Tprizma() {s=NULL; state=-1;}
     // функция ядра
     static int act()
     {
     if (s.value==NULL) {state=-1; return state;} // если нет источника – не вычисляем
     if (s.call_act()!=1) {state=-1; return state;} // вызов метода ядра источника - обязательно
     // выполняем свои рассчеты
     calc_v();state=1; return state;
     }
     };
     void main()
     {
     Trectangle rect; rect.set_a(5); rect.set_b(6);
     Tcircle circ; circ.set_r(7);
     Tprizma priz; priz.set_h(10);
     // Соединяем призму с прямоугольником - получаем параллелепипед
     if (priz.s.connect(rect.getadr_s(), &rect, (int (*)(void*))(&Trectangle::act))==1)
     {
     priz.act(); // будет вычислен прямоугольник и параллелепипед
     cout<<" prizma:v="<< priz.get_v();
     priz.s.disconnect();
     }
     // Соединяем призму с кругом – получаем цилиндр той же высоты
     if (priz .s.connect(circ.getadr_s(), & circ, (int (*)(void*))(& Tcircle::act))==1)
     {
     priz.act(); // будет вычислен круг и цилиндр
     cout<<" cilindr:v="<< priz.get_v();
     priz.s.disconnect();
     }
     }
    
     5.3. Функция синтеза соединения объектов
    
     При большом количестве объектов в соединении собственно процедура соединения объектов может быть достаточно трудоемкой и требующей отладки. Кроме того, при переносе соединения в другой проект было бы целесообразно каким-то образом оформить этот фрагмент программного кода.
     Хорошим решением является создание функции синтеза соединения, которая на вход будет получать указатели на объекты и в теле функции конструировать соединение в нужном порядке.
     Например, если требуется создать активное соединение A->B->C, то функция синтеза может быть такой:
    
     int Connection(A *a, B *b, C *c)
     {
     // соединяем B с A
     if (b->a.connect(a->getadr_a(), a, (int (*)(void*))(&A::act))!=1) return 0;
     // соединяем C с B
     if (c->b.connect(b->getadr_b(), b, (int (*)(void*))(&B::act))!=1) return 0;
     return 1;
     }
    
     Для корректности функция синтеза перед выходом должна разорвать все сделанные соединения, если хотя бы одно соединение не выполнено.
     Сигнатура функции синтеза соединения Connection(A *a, B *b, C *c) определяет состав и классы объектов в соединении, а тело функции {...} определяет структуру соединения. Таким образом, для задания соединения необходимо:
     - описания классов всех объектов соединения;
     - определение функции синтеза.
     В принципе, на функцию синтеза можно возложить и задачу создания объектов.
     Для одного и того же множества объектов можно создавать несколько функций синтеза, которые при последовательном вызове будут создавать разные соединения над одним и тем же множеством объектов.
     Для разных составов соединений функцию синтеза можно перегружать, т.е. использовать одно и то же имя функции для разных соединений, например:
    
     Connection(A *a, B *b);
     Connection(B *b, C *c);
     Connection(A *a, B *b, C *c);
    
     Для соединения двух уже созданных соединений достаточно соединить два любых объекта из этих соединений.
     Функция синтеза фактически является ключевым элементом соединения, т.к. она содержит «знания» о структуре соединения. Поэтому, можно использовать различные методы для защиты функции синтеза как от искажения, так и для защиты «ноу хау», например, закрывая ее исходный код и передавая только объектный модуль с заголовком функции. Защитить же структуру объекта в ООП практически довольно сложно.
    
     5.4. Виды объектов в соединениях
    
     В общем случае структура объекта может содержать следующие элементы:
     - свойства;
     - методы;
     - функцию ядра;
     - пассивные коннекторы приемников;
     - активные коннекторы приемников с поддержкой адекватности;
     - адресные функции источников.
     Объект, содержащий все эти элементы, может одновременно выступать в роли источника и приемника в соединениях.
     Чтобы быть источником, объект должен содержать адресную функцию для предоставления адреса свойства, к которому могут подключаться приемники. «Чистые» источники могут стоять только в начале цепочки соединений.
     Чтобы быть приемником, объект должен содержать коннектор для подключения к свойству источника. «Чистые» приемники могут стоять только в конце цепочки соединений.
     Приемники являются особым видом объектов, которые способны подключаться к источникам и не могут использоваться, пока их коннекторы не соединены со свойствами. Для того, чтобы все-таки использовать приемники автономно, можно рассмотреть два решения:
     - для каждого коннектора создать дублирующее обычное (не ссылочное) входное свойство в объекте и подключить все коннекторы к этим внутренним свойствам (сделать автосоединение с помощью, например, внутренней функции синтеза); это позволит использовать объект как автономно, так и в соединениях с другими объектами;
     - создать специальный объект - «чистый» источник, который будет содержать только свойства и адресные функции для всех коннекторов объекта-приемника, и соединить его с объектом-приемником; тогда, используя такую соединенную пару, можно использовать этот объект.
    
     5.5. Классы соединений
    
     Если определенный набор объектов-приемников и объектов источников вместе с функциями синтеза соединений оформить в виде класса объектов, то получим интересную разновидность композитных объектов, которые будем называть объектами-соединениями. Например, описав класс фигуры D (см. рис.9), которая включает один объект-приемник – объемную фигуру A без основания и два основания в виде объектов-источников – прямоугольник B и окружность C, а также две функции синтеза соединений, можно в разные моменты времени получить параллелепипед или цилиндр в зависимости от того, какое основание соединено с объемной фигурой (A->B или A->C).
    
    
        Рисунок 9. Пример объекта-соединения.
    
     Теперь уже не главная программа, а объект-соединение создает источники и приемники, а также управляет их соединением.
     Объект-соединение может содержать главный метод и функцию синтеза по умолчанию, которая вызывается конструктором и строит первичную структуру соединения.
     Классы объектов-соединений позволяют создавать несколько экземпляров однотипных соединений, которые к тому же действительно обладают свойством полиморфизма – при одинаковом составе элементов объект может иметь различные внутренние структуры их соединений.
     Естественно, что объекты-соединения сами могут соединяться с другими объектами- соединениями, создавая супер-соединения, а также быть членами других супер-классов соединений. Такие способы композиции соединений объектов позволяют строить программы со сложной и динамически изменяемой структурой.
    
     Заключение
    
     Несмотря на то, что АДС-технология не вносит революционных изменений в процесс программирования и базируется на традиционном ООП, она придает программе и самому процессу программирования новые полезные свойства.
     Т.к. основой АДС-технологии являются соединения объектов, то создание и накопление схем соединений может быть предметом автоматизации при использовании, например, средств визуального программирования. В таких средах программист может создавать классы как атомы будущих соединений, а также проектировать собственно программу в виде визуальных схем соединений.
     Аналогичный механизм визуального синтеза схем соединений из готовых классов объектов можно предоставлять пользователям для решения задач в их предметной области, реализуя, таким образом, принцип «программирования без программирования».
     АДС-технология позволяет довольно просто решить задачу взаимного отображения объектной модели программы и реляционной модели базы данных за счет сопоставления каждой таблице БД класса объектов и установки связей по ключевым полям между объектами.
     И, наконец, АДС-технология дает ответ на главный вопрос, не решенный в ООП, - как из диаграммы классов, описывающей структуру элементов системы, построить саму систему.
     Перспективными направлениями исследований по развитию АДС-технологии являются следующие:
     - разработка теоретических основ проектирования схем соединений объектов;
     - создание языка описания схем соединений объектов для их динамического синтеза;
     - разработка метода синтеза требуемой схемы соединения по заданной постановке задачи;
     - исследование путей создания программ с возможностью адаптации к условиям эксплуатации.
    
     Литература
    
     1. Гради Буч. Объектно-ориентированный анализ и проектирование с примерами приложений на С++. - М: Бином, 1998. - 560 с.
     2. Ирэ Пол. Объектно-ориентированное программирование с использованием C++: Пер. с англ. - Киев: НИИПФ ДиаСофт Лтд, 1995. – 480 с.
     3. Фаронов В.В. Программирование на языке С#. –СПб: Питер, 2007. – 240 с.
    

Ждем ваших отзывов
НАШИ ПРОДУКТЫ
      DentExpert
      MedExpert
      КРЕДОПОЛИС
      Сезам
      ICC-2000
      Реестр Предприятий
      Социометрия
      Информ-ФПУ
      Аладдин
      Back Track
      GPS диспетчер
      СтройЭксперт
      TVP Generator
      Менеджер Заказов
      AIT-SMU
      Mini-Content
      Интернет-сервис MyTaxi
      Интернет-магазин
      BUKET-EXPRESS.UA
      Интернет-магазин
      VUTKA.COM.UA
      Первая Украинская Федерация Спорта на Пилоне
      UFPOLESPORT.ORG.UA
      Спортивный клуб
      FITNES-LIGA.COM.UA
      Бесплатные программы
   Все продукты
ПУБЛИКАЦИИ
АДС-технология
    программирования

Подход к оценке сложности
   систем

Эволюция языков
   программирования

Свободу программам!
Декларация независимости
   программ

Поймать такси в интернете
ГЛАВНАЯ   |   РЕШЕНИЯ   |   ПРОДУКТЫ   |   УСЛУГИ   |   ИНФОРМАЦИЯ   |   НОВОСТИ   |   КАРТА САЙТА   |   БЛОГ   |   О НАС
Rambler's Top100
АИТ (с) 2000-2012. Все права защищены
Украина, 02660, Киев, ул. Евгения Сверстюка 11 оф.901/1
Tел: +38044 5865655, +38044 2091257, +38067 2091257,+7901 9034908
факс: +38044 5865655
E-mail: soft@ait.org.ua