ParamsBean - альтернативный механизм вызова методов
или как реализовать системный подход при построении архитектуры ИС
Первая редакция
1. Каждый может принять решение, располагая достаточной информацией
2. Хороший руководитель принимает решение и при её нехватке
3. Идеальный - действует в абсолютном неведении
Законы исходных данных Спенсера
из "Законов Мёрфи"
ПредисловиеВ этой статье я скромно представляю на суд общественности своё решение (небольшое ноу-хау) в архитектуре приложений, написанных на Java, которое я назвал ParamsBean. Оно позволяет программировать логику, более приближенную к действительности, чем при классическом подходе, за счёт необязательных параметров методов, приближая, таким образом, вызов метода к постановке задачи. Подход уже опробован и в реальной (хотя и довольно редко возникающей) ситуации себя оправдал. Надеюсь, вам он так же сможет пригодиться.
После того, как мне понадобился этот приём, я задался вопросом о том, как наиболее полно использовать его потенциал и в итоге пришёл к полному пересмотру сложившейся архитектуры построения программ на основе ООП-модели и в перспективе - пересмотр вообще сложившейся архитектуры Информационных Систем - поэтому запаситесь терпением, начну издалека.
Архитектура: почему классический ООП-подход не эффективен при описании реальных ситуаций?Считается, что ООП гораздо ближе к реальному миру, к модели его восприятия человеком, нежели предшествующие подходы. Действительно, мы можем создать объект "собака" и реализовать методы "голос", "сидеть", "лежать" и "фас" - и их вызов будет по смыслу отдалённо напоминать поведение человека по отношению к собаке на прогулке. Всё вроде бы логично, да не совсем - чем на самом деле отличается окружающий мир от программы, так это обилием различных факторов. Собака получает команду "фас" не в отрыве от всей остальной реальности, а в условиях окружения множеством различных второстепенных факторов окружающей действительности и в совокупности эти факторы имеют порой гораздо большее влияние на реакцию собаки на нашу команду, чем сама эта команда. Очевидно, с методом объекта Dog этого не будет - он обработает лишь те параметры, которые ему передадут, коими может стать, пожалуй, лишь громкость голоса, измеренная в децибелах. Он может так же учесть параметры внутреннего состояния, инкапсулированные в нём, но не данные окружающей его среды.
Рассмотрим другой пример. Специалист в какой-то области архитектурно предстаёт перед нами как ресурс, способный справиться с каким-то классом задач (соответствующих данной предметной области). Когда возникает задача, специалист сначала выясняет более узкий тип задачи, затем входит в курс дела, погружаясь в специфику задачи, затем более узко - в специфику конкретной ситуации, в которой возникла проблема, затем ищет похожие случаи в своей практике и решает задачу либо на основе опыта (архитектурно - кэша уже выполненных задач или не выполненных, про которые сделаны выводы) либо, в отсутствии такового - пытается решить творчески.
Среда, в которой действуют объекты реальной действительности, обычно имеет фоновый характер и лишь косвенно влияет на процесс. Она состоит из множества разнообразных факторов. Соответственно, было бы логично передать её некоторым количеством переменных, редко изменяемых и имеющих значения по умолчанию для типичных ситуаций. Однако небольшой, ограниченный ряд свойств среды в контексте данного конкретного действия (работы метода) имеет первостепенную важность - очевидно, что эти-то свойства в меру возможностей мы и должны в первую очередь определять, передавая методу, остальные же, менее существенные можем считать равными значениям поумолчанию, если у нас нет достаточных ресурсов, что бы их уточнять.
Брюс Эккель писал в "Философии Java" о том, что разработчиков удобно разделять на создателей и пользователей библиотек. Архитектурно задача программиста-пользователя - как можно точнее определить внешнюю среду (одним из ключевых факторов которой является само посылаемое объекту сообщение, т.е. вызываемый метод) объекта, которую и передать объекту, а задача программиста-разработчика библиотек - определить внутреннюю структуру объекта, что бы он как можно более адекватно работал в условиях, которые показывает ему программист-пользователь.
Возвращаясь к примеру со специалистом, можно говорить о том, что если сопоставить специалиста классу, то у него есть:
- данные (энциклопедического характера и накопленные с опытом),
- методы (типы задач, с которыми он способен справиться).
Методы описываются предметной областью и квалификацией, что программно соответствует интерфейсу класса (класс может реализовывать несколько таких интерфейсов, как человек может быть специалистом в разных областях). Соответственно, у этих методов есть параметры, количество и качество которых определяется с одной стороны - спецификой задачи, с другой - бонально тем, можем ли мы их предоставить в данный конкретный момент или нет (качество решения проблемы, естественно, зависит как от эффективности организации алгоритма, так и от полноты данных, которых в реальной ситуации часто может не хватать - отсюда возможность обоюдного тюненья программы - составителю библиотеки можно вылизывать алгоритм реализации, а программисту-клиенту - пытаться передать более точные данные объекту, позволяя ему лучше "войти в курс дела").
Т.е. для моделирования более реальных ситуаций, нам необходимо иметь возможность писать методы, которые имеют не обязательные параметры. Позволяют ли стандартные принятые подходы такое делать? Да, но очень не удобно, не эффективно и не гибко.
Прикладные аспекты описания сигнатур методов или чем нам может помочь подход ParamsBean?Обычно когда мы планируем архитектуру приложения, мы закладываем в сигнатуры методов объектов в библиотеках какие-то параметры, которые играют для выполняемой задачи ключевую роль. Им необходимо их обработать и выдать приемлемый результат. Эти параметры имеют свои типы, имена и жёстко определённый порядок перечисления при вызове метода. Для того, что бы корректно работать с определённым таким образом методом, программист, который будет использовать нашу библиотеку, обязан считаться с этим.
Порой возникают ситуации, когда один и тот же функционал удобнее сделать доступным с использованием разного количества параметров, некоторые удобнее задать по умолчанию и дать возможность пользователю библиотеки передать лишь те, которые важны для него. Для таких ситуаций принято пользоваться механизмом перегрузки - действительно, очень удобно написать метод с тем же именем, но с меньшим количеством параметров, из которого вызвать исходный, добавив к параметрам другие со значениями по умолчанию.
Однако у этого метода есть ряд существенных недостатков, главным образом - он, очевидно, обладает очень низкой масштабируемостью. Он привносит элементы т.н. жесткого связывания обоих этих программистов по Эккелю (автора библиотек и клиента), а хорошо бы их разнести, что бы они не были вынуждены плотно общаться - особенно это может оказаться важно в больших проектах. Делать же перегруженные методы на все случаи жизни, очевидно, не разумно - количество возможных комбинаций растёт в геометрической прогрессии пропорционально количеству параметров.
Как-то была у меня такая задача - надо было вытащить данные из базы и передать их в функцию, в которой очень-очень много параметров (около 30-ти). Реально нужны из них только 5, остальные - значения по умолчанию, но было известно, что этот метод понадобится ещё для реализации большого числа других функций и так же понадобится модифицировать некоторые из параметров вызова, какие - заранее сказать было сложно. Создавать конструктор с 30ю параметрами было не просто очень неудобно - было нужно иметь возможность задать некоторые параметры, а остальные что бы остались такими, какими должны быть по умолчанию.
Создавать конструкторы для всех возможных комбинаций, естественно, я не стал

Я создал JavaBean (точнее именно POJO - обрезанный лишь getter`ами и settter`ами JavaBean), в котором инкапсулировались все параметры по умолчанию, и его через промежуточную функцию стал передавать той самой с огромным числом параметров.
Вроде бы всё хорошо, но не очень красиво выглядело наполнение параметрами этого бина, к тому же хотелось все параметры всё-таки аккуратно вложить в вызов функции - так оно как-то логичней было бы.
Есть множество аспектов языков, о которых не все знают, поскольку на них не принято акцентировать внимание. Так, недавно я открыл для себя, что оператор вызова метода - "." - не обязательно должен сразу следовать за тем объектом, за которым он вызывается, как это обычно принято делать, а может вызываться и на другой строке - главное, что бы метод был рядом, т.е., кто не знает, конструкция:
Код |
Obj1 theObj1 = new Obj1();
theObj1 .setProp1(1) .setProp2(2); |
- вполне валидная. Такая конструкция навешивания изменений свойств была бы идеальна для моей задачи и очень лаконична - если бы я при вызове метода создавал объект и передавал ему те параметры, которые отличаются от параметров по умолчанию, все же остальные оставались бы со значениями по умолчанию.

И вот я подумал - а почему принято setter`ы делать без возвращаемого значения, т.е. "void"? А может быть, стоит заставить их выдавать ссылку на объект своего класса, в конце прописывая выражение "return this;"? Тогда приведённая выше конструкция будет вполне реальной!
Сделал и всё отлично получилось! :-))) Выполнил задачу и остался очень собой доволен

Код |
class A implements Serializable {
private int forumID = 1; public int getForumID(){ return forumID; } public A setForumID(int forumID){ this.forumID = forumID; return this; }
private int memberID = 8; public int getMemberID(){ return memberID; } public A setMemberID(int memberID){ this.memberID = memberID; return this; }
private String postBody = ""; public String getPostBody(){ return postBody; } public A setPostBody(String postBody){ this.postBody = postBody; return this; }
// и т.д. }
public class B {
public static void main(String[] args){
ClassToGo.go( new A()); //Можем вызывать со всеми параметрами по-умолчанию
// Можем задавать все параметры ClassToGo.go( new A() .setForumID(5) .setMemberID(2) .setPostBody("ляляля") );
// Можем выборочно ClassToGo.go( new A() .setPostBody("боди поста") ); } } |
Пока назвал я это ParamsBean - по-моему, очень удобная штука.
Явные преимуществаТеперь я могу каждый раз при вызове менять значения только тех параметров, которые мне нужно изменить по сравнению с параметрами по умолчанию. Кроме того, я всегда знаю их имена и не путаю их, ведь я вызываю специальные методы, содержащие их имена. Так же теперь я жестко не привязан к порядку их следования при вызове метода - порядок отныне произволен. Могу теперь и пользоваться преимуществами модели JavaBeans для параметров метода, т.е. вводить ограничения на данные и преобразовывать их для хранения в другой формат, вообще, реализуя причудливую логику, инкапсулировать в бине часть функций по обработке данных - например, можно задать методу, которому нужна в качестве параметра длина, ParamsBean, в разных setter`ах которого можно задавать длину в метрах, милях, футах или дюймах, а он уж с методом сам как-нибудь разберётся - это уже вешается на составителя библиотеки, а не на клиента.
Кроме того, в сеттерах-геттерах можно инкапсулировать логику прямого или косвенного влияния одних факторов на другие, таким образом, пользователь такого бина получает возможность задавать одним методом несколько факторов.
Это возможность распараллелить работу программистов, выполняющих разные роли по Эккелю. Например, они договорились о каком-то методе, которому в качестве параметров передаётся объект "pBean", метод реализует какую-то функциональность. Потом понадобилось изменить метод, передавая ему дополнительные параметры, который в зависимости от них будет менять поведение - для того, что бы старый код работал, просто в этот pBean добавляется новое поле и оно передаётся методу - не надо перегружать метод. Зато программист-пользователь сможет гибче использовать метод и не заморачиваться с теми параметрами, которые ему не известны, при этом не должен будет клянчить каждый раз у разработчика библиотеки специальный перегруженный метод для его ситуации. Т.е. фактически данный подход - это как бы конструктор всех возможных индивидуальных перегрузок метода.
По сути, это просто альтернативный привычному нам механизм вызова методов объектов, гораздо более гибкий и более масштабируемый, чем стандартный механизм с перечислимыми параметрами. Если раньше для таких приёмов применялся механизм перегрузки, то c таким подходом он нам уже не нужен - мы сами решаем, какие параметры передавать изменёнными, а какие - оставить со значениями по умолчанию

Для использования такого подхода нужно, что бы параметры по умолчанию обеспечивали хоть какую-то функциональность и служили как бы "заглушками". В ситуации, когда ряд параметров методу быть передан
обязан, можно вызывать специальный Exception, но лучше не использовать данный подход вовсе. Он был оправдан именно в задачах с передачей большого количества параметров.
Послесловие: архитектурные перспективы внедренияЭтот подход позволяет архитектурно приблизить объектную модель к реальной действительности, делая вызов метода более похожим на постановку задачи человеку. Множество факторов, которые влияют на поведение системы, определяют, как система конкретно будет действовать, однако приоритет у этих факторов разный и, соответственно, если мы не знаем их все, но знаем лишь основные, мы можем передать объект состояния окружающей среды, который содержит усреднённые значения по умолчанию для второстепенных факторов и чётко замеренные для приоритетных. В дальнейшем, при тюнинге, для получения более качественной отдачи от метода, мы можем уточнять состояние среды при вызове метода, задавая более точно все параметры и делая систему тем самым более адекватной, видящей вокруг себя то, на что она может реагировать.
В документации на библиотеки можно было бы обозначать приоритетную важность точности тех параметров, которые играют для данного конкретного выполняемого метода ключевые и побочные роли, тем самым позволяя лучше распределять ресурсы по уточнению параметров при вызове методов.
Для разных состояний окружающей среды можно было бы передавать наследников от принимаемого объекта, которые бы содержали другой набор значений для тех же параметров - разработчик библиотеки был бы от этого дистанцирован.
Данный подход позволяет ещё больше стереть разницу между человеком и программой, приближая управление компонентами программы к управлению людьми. Это ставит перед нами вопрос - а почему машина должна подчиняться пользователю, если они оба (и со временем всё большую роль играет компьютер) совместно выполняют одну задачу, которую ставит перед ними тот или иной управляющий?
Современные архитектуры, основанные на UML, начинаются с диаграмм "Use Case" (анг. "Вариант использования"), на которых рисуется роль, которую выполняет человек и та функция, которую для него выполняет данная ИС, т.е. машина изначально воспринимается как инструмент.
Подход, к которому может привести внедрение ParamsBean`ов будет обратным - у системы будет большое количество распределённых объектов со своими интерфейсами на основе ParamsBean, и за некоторыми объектами будут скрываться терминалы, за которыми сидят пользователи, выполняя творчески какую-то часть бизнес-логики (с которой машина справиться не в состоянии), а всё остальное, автоматизированное, будет выполнять терминал сам, в целом представляясь в виде web-сервиса или CORBA-объекта - на уровне архитектуры будет без разницы - человек за терминалом или голая программа.
Реализация такого подхода уже даст возможность каждому отдельному сотруднику автоматизировать свою деятельность, если он не дурак в программировании, а программа, с другой стороны, сможет анализировать действия человека и пытаться учиться, т.е. самоавтоматизироваться - в итоге мы придём к системам уже не автоматизации деятельности персонала, а автоматизации автоматизации их деятельности, т.е. технологий, которые будут сами выискивать рутинные компоненты деятельности людей и от них эффективно людей избавлять, что откроет перед нами радужные перспективы, сделав весь наш труд - творческим.
Это сообщение отредактировал(а) Се ля ви - 25.1.2006, 16:46