Вопрос по паттерну Адаптер
Имеется некий класс, назовем его Запрос, который может обращаться к разным типам источников данных, но используя при этом одни и те же методы читать() и писать(). Соответственно для этого создается некий абстрактный класс Адаптер в котором так же присутствуют методы читать() и писать(). От этого абстрактного класса создаются потомки в которых методы читать() и писать() уже имеют реализацию для конкретного источника данных.
По сути это паттерн Адаптер.
Возникает вот какой вопрос. В ходе выполнения программы, в зависимости от типа источника данных мы создаем нужного наследника класса Адаптер. Но каким образом это делать?
Создавать т.н. фабрику объектов и передавать ей в качестве параметра тип источника данных?
Размещать эту фабрику как отдельный объект или как часть объекта :Запрос?
При расширении числа потомков класса Адаптер каждый раз переписывать код фабрики?
Или можно каким то образом исключить объект фабрика и уйти от первоначальной зависимости от предопределенного числа потомков Адаптера?
Возникает вот какой вопрос. В ходе выполнения программы, в зависимости от типа источника данных мы создаем нужного наследника класса Адаптер. Но каким образом это делать?
Создавать т.н. фабрику объектов и передавать ей в качестве параметра тип источника данных?
Да.
Размещать эту фабрику как отдельный объект или как часть объекта :Запрос?
Пусть фабрика живет сама по себе.
При расширении числа потомков класса Адаптер каждый раз переписывать код фабрики? Или можно каким то образом исключить объект фабрика и уйти от первоначальной зависимости от предопределенного числа потомков Адаптера?
Не надо ничего переписывать.
Сделай регистрацию классов в фабрике. Каждый новый адаптер (потомок класса Адаптера) должен быть зарегистрирован автоматически или "вручную" (я бы предпочел "вручную", т.е. явно) в фабрике. Далее при запросе адаптера определенного типа фабрика отыскивает (перебором всех зарегистрированных или с помощью ассоциативного массива) необходимый класс адаптера и возвращает пользователю его экземпляр.
Регистрацию классов можно проделать с помощью введения в них статического метода create (фабричный метод), который будет создавать экземпляр данного класса, а в фабрике храни указатели на эти методы в ассоциативном контейнере в соответствии с типом. Регистрация в таком случае сводится к помещению указателя на метод create нового класса в этот контейнер.
Если адаптеров немного, то можно обойтись без ассоциативного контейнера, используя обычный (например, список). При этом соответствие требуемому типу можно производить в процессе перебора всех зарегистрированных адаптеров.
Имеется некий класс, назовем его Запрос, который может обращаться к разным типам источников данных, но используя при этом одни и те же методы читать() и писать(). Соответственно для этого создается некий абстрактный класс Адаптер в котором так же присутствуют методы читать() и писать(). От этого абстрактного класса создаются потомки в которых методы читать() и писать() уже имеют реализацию для конкретного источника данных.
По сути это паттерн Адаптер.
По-моему, автор темы неверно понимает суть паттерна Адаптер. Ведь его назначение - адаптировать интерфейс одного класса к другому в том случае, когда адаптируемый класс не обеспечивает нужной внутренней функциональности.
В данном же случае можно всего лишь сделать методы читать() и писать() виртуальными и переопределять их в потомках от класса Запрос для обеспечения доступа к различным типам данных.
Наследование запрашивающего класса как раз наоборот, является смешиванием реализации.
Мне совершенно не интересно плодить потомков класса инициирующего запрос, только для того, чтобы адаптировать его под новый тип источника данных. Как раз задача состоит в том, чтоб уйти от этого не затрагивая класс. И этим занимается адаптер.
Класс инициирующий запрос не обязан ничего знать о типе источника данных. Он просто должен выполнять свои функции, обращаясь к абстрактному источнику, который уже в свою очередь и реализует взаимодействие на необходимом уровне.
Проще говоря, часть системы реализующей логику не завязана на другую часть системы занимающуюся хранением и выборкой данных.
Наследование запрашивающего класса как раз наоборот, является смешиванием реализации.
Отнюдь, ваша система будет пользоваться интерфейсом класса Запрос, ничего не зная о конкретной реализации, которую возвращает фабрика (например, MSSQLЗапрос, MySQLЗапрос и т.д.)
Это будет называться, конечно уже не Адаптор, но смысла создание класса, который только и делает, что вызывает другой класс, я не вижу... Если класс Запрос нагружен какой-то иной деятельностью (которую я не могу даже представить) тогда другое дело :)
Мне совершенно не интересно плодить потомков класса инициирующего запрос, только для того, чтобы адаптировать его под новый тип источника данных. Как раз задача состоит в том, чтоб уйти от этого не затрагивая класс.
Хм... в итоге вы плодите потомков класса Адаптор... А класс Запрос в это время только вызывает этих потомков через интерфейс Адаптор...
Что мешает сразу вызывать методы Адаптор.Читать, Адаптор.Писать... Вместо того, чтобы вызывать Запрос.Читать, который в свою очередь вызовет Адаптор.Читать...
Класс инициирующий запрос не обязан ничего знать о типе источника данных. Он просто должен выполнять свои функции, обращаясь к абстрактному источнику, который уже в свою очередь и реализует взаимодействие на необходимом уровне.
Проще говоря, часть системы реализующей логику не завязана на другую часть системы занимающуюся хранением и выборкой данных.
Это конечно все правильно, но применение Адаптор + Фабрика здесь не оправданно... Можно использовать только фабрику, которая возвращает интерфейс для чтения/записи...
Адаптор нужен когда интерфейс у "внешней" системы не удовлетворяет требованиям... В Вашем случае интерфейсы совпадают и необходимости в Адаптере нет.
А зачем тогда в диаграмме наплодил, раз не интересно? Зачем тогда нужны потомки класса Запрос? Или в той диаграмме приведены 2 альтернативы?
Это будет называться, конечно уже не Адаптор, но смысла создание класса, который только и делает, что вызывает другой класс, я не вижу... Если класс Запрос нагружен какой-то иной деятельностью (которую я не могу даже представить) тогда другое дело :)
К примеру, запрос может сортировать набор данных на клиенте. Или скрывать/добавлять дополнительные столбцы. Или элементарно дополнительно фильтровать строки. А еще можно кэшировать. Вот вам и "фантастический" функционал.
Тем, что реализация класса Запрос у нас не зависит от источника данных - Адаптера, тем самым добавляет так нужный нам для реализации универсального Запроса уровень абстракции. Запрос работает с абстрактным предком Адаптер. И выполняет дополнительную логику (см выше)
Учитывая умозрительную специфику класса Запрос необходимость в Адаптере безусловно есть.
А вам не кажется, что этим должен не запрос заниматься, а некий DataSet или подобное... К тому же не тянет этот функционал на "универсальный интерфейс доступа к данным", это уже фильтрация, обработка и т.д.
Тем, что реализация класса Запрос у нас не зависит от источника данных - Адаптера, тем самым добавляет так нужный нам для реализации универсального Запроса уровень абстракции. Запрос работает с абстрактным предком Адаптер. И выполняет дополнительную логику (см выше)
Если запрос универсальный, то зачем нагружать его логикой? Логика запроса - осуществить команду SQL приминительно к абстрактному хранилищу...
P.S. Но даже если принять такую схему компоновки системы, все равно здесь даже не пахнет паттерном Адаптер - который есть лишь способ привести один интерфейс к другому... никакой иной функции у него нет.
Сдается мне, вы путаете Адаптер и Декоратор (фасад). Обратитесь к Википедии, к примеру, там это отличие продемонстрировано.
Фасад логически ближе к адаптеру, чем к декоратору (точней, с декоратором он вообще не связан).
Если адаптер позволяет клиенту рассматривать класс, не реализующий некоторый интерфейс, то фасад позволяют рассматривать группу произвольных классов как единый интерфейс (объект с заданным интерфейсов).
В википедии написано, что это близкие паттерны (+ замечание):
[QUOTE=Примечание в википедии на странице http://ru.wikipedia.org/wiki//Адаптер_(шаблон_проектирования)] Разница состоит в том, что шаблон Фасад предназначен для упрощения интерфейса, тогда как шаблон Адаптер предназначен для приведения различных существующих интерфейсов к единому требуемому виду.[/QUOTE]
Но оно же подчеркивает и сходство этих паттернов.
Кстати пока неизвестна область применения сей конструкции спорить особо бесплозно... Потому, что Запрос действительно может нести какие-то функции - и тогда схема имеет место быть!
Но я очень сомневаюсь, что класс Запрос чем-нибудь нагружен... простите мне мое упрямство :)
P.S. to Der Meister: Кстати, что значит "Табличное представение данных ущербно изначально"?
Я только "за" :)
Единственное хочу ответить Kogrom что на диаграмме я изобразил два варианта. Первый тот о чем я говорил, а второй тот, о котором говорил Babandr.
GreenRiver
Если класс Запрос нагружен какой-то иной деятельностью...
Само собой разумеется. Подача запроса отнюдь не единственная функция класса. Просто рассматривать остально в данном контексте я не вижу надобности. Мы ж об абстрации сейчас говорим.
В Вашем случае интерфейсы совпадают и необходимости в Адаптере нет.
Не совпадают. Парсинг DOM XML документа и выборка из БД - шибко разные интерфейсы... К тому же, с прицелом на будущее, добавление новых вариантов.
В принципе hardcase сабж в последствии хорошо "разжевал".
P.S. вопрос не в том, применять не применять и уместно не уместно, а про детали реализации был... Собственно весь вопрос был в добавлении (регистрации) нового потомка адаптера, так чтобы не переписывать в результате код фабрики.
P.P.S Фасад объединяет интерфейсы разнородных систем в один предоставляемый клиенту. В этом плане он хоть и похож на Адаптер, но при этом отличается тем, что реализует все в одном единственном классе.
Т.е. если Адаптер представляет модульность, используя полиморфизм и реализуя логику ИЛИ, то фасад объединяет интерфейсы сводя их к одному, используя логику И.
Само собой разумеется. Подача запроса отнюдь не единственная функция класса. Просто рассматривать остально в данном контексте я не вижу надобности. Мы ж об абстрации сейчас говорим.
А какие ещё функции у Запроса? Кстати какой язык? PHP?
P.S. вопрос не в том, применять не применять и уместно не уместно, а про детали реализации был... Собственно весь вопрос был в добавлении (регистрации) нового потомка адаптера, так чтобы не переписывать в результате код фабрики.
Все правильно. А потом Вы сказали, что если наследовать от Запроса, то произойдет смешивание реализаций... и понеслось :)
P.P.S Фасад объединяет интерфейсы разнородных систем в один предоставляемый клиенту. В этом плане он хоть и похож на Адаптер, но при этом отличается тем, что реализует все в одном единственном классе.
Т.е. если Адаптер представляет модульность, используя полиморфизм и реализуя логику ИЛИ, то фасад объединяет интерфейсы сводя их к одному, используя логику И.
Ёлки-палки... почитайте Википедию, или здесь, или прислушайтесь к тому, что говорит Zorkus.
Опять. Вы щас добиваетесь чего?
Запрос делегирует адаптеру, адаптер делегирует соответствующему API. Простейший пример:
{
string _Name;
class PersonQuery : Query<Person>
{
public PersonQuery(Adapter adapter) : base(adapter) { }
publicPerson FindById(long id) { ... }
}
// класс Query<T> имеет конструктор Query(Adapter adapter) и,
// помимо всего прочего, кеширует уже извлечённые из источника
// данных экземпляры класса T
}
class Person
{
class XMLPersonQuery : XMLQuery
{
Person FindByID(long id);
}
class SQLPersonQuery : SQLQuery
{
Person FindByID(long id);
}
}
object[] Read(string objClass, long id);
Мне просто непонятны мотивы продолжения вами дискуссии в этом русле.
Мне просто непонятны мотивы продолжения вами дискуссии в этом русле.
Спокойствие, только спокойствие. Я на счет схемы ничего не говорю ... и с самого начала говорил, что нормальная схема, если есть нагрузка на Запрос. То, что спросил какая именно нагрузка - это чистое любопытство...
Единственно, что мне непонятно зачем придумывать какие-то ужасы, типа паттернов, когда это обычная реализация интерфейса?!
Имеется класс/интерфейс Адаптер, имеется несколько наследников, работающих с разными источниками, но реализующими интерфейс Адаптер... в чем паттерн здесь? Это все, что я хочу сказать...