Посоветуйте паттерн
От: _Sphinx_ Россия http://www.rogov.su
Дата: 09.05.08 18:27
Оценка:
Полагаю, что задача довольно типичная и для ее решения должен подходить какой-то из паттернов.

Итак, исходные условия
1. Маленькая библиотека, пишется на C#, предполагается использование только в среде .NET, соответственно использование того, что получится, должно быть максимально удобным в контексте языка C#
2. Пишется библиотека для разбора и обработки некоторых данных, получаемых по сети. Данные представляют собой пакеты некоторого протокола (в данном случае речь идет про протокол NetFlow хотя это и не принципиально). Протокол имеет несколько версий, что сказывается на структуре и содержимом пакетов. Достоверно известно, что в любой версии первые 2 байта пакета означают номер версии протокола. Необходимо сделать обертку для этих данных — т.е. прилетает пакет, мы его разбираем на части, и засовываем в какой-то класс.

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

Я пока думаю сделать так: завести отдельные классы для каждой версии, базовый класс (Foo) для них и в нем статический метод. Этот метод будет возвращать объект с типом Foo принимая входной массив байт. Соответственно, будет определять версию пакета и создавать объект нужного типа, инициализируя его всеми необходимыми данными. Если это как то вписывается в существующие паттерны, то как это можно назвать? Мне думалось, что это фабрика, но как-то не очень похоже на фабрику...

Соответственно для конечной программы обработка пакета представляет собой что-то типа:

byte [] packetData;
// Здесь получаем содержимое пакета по сети
PacketVersion1 PV1 = null;
PacketVersion2 PV2 = null;

PacketBase Packet = PacketBase.Parse(packetData);
if(typeof(Packet) == typeof(PacketVersion1))
{
    PV1 = (PacketVersion1)Packet;
}

if(typeof(Packet) == typeof(PacketVersion2))
{
    PV2 = (PacketVersion2)Packet;
}


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

Нужен совет.

P.S. как напишу это хозяйство — выложу исходники для всех желающих на условиях Absolutely Free, мож кому пригодится....
ICQ: 203-009-172
Re: Посоветуйте паттерн
От: SergH Россия  
Дата: 09.05.08 19:47
Оценка:
Здравствуйте, _Sphinx_, Вы писали:

_S_>Соответственно для конечной программы обработка пакета представляет собой что-то типа:


_S_>
_S_>byte [] packetData;
_S_>// Здесь получаем содержимое пакета по сети
_S_>PacketVersion1 PV1 = null;
_S_>PacketVersion2 PV2 = null;

_S_>PacketBase Packet = PacketBase.Parse(packetData);
_S_>if(typeof(Packet) == typeof(PacketVersion1))
_S_>{
_S_>    PV1 = (PacketVersion1)Packet;
_S_>}

_S_>if(typeof(Packet) == typeof(PacketVersion2))
_S_>{
_S_>    PV2 = (PacketVersion2)Packet;
_S_>}
_S_>


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


Я бы сказал, что это уже порнуха

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

А если это всё равно невозможно, то нафиг тебе фабрика? Просто по первым байтам узнавай версию, и в зависимости от них выполняй ту или иную ветку. Типа:
if (getPacketVersion(packetData) == 1)
{
...
}
else if (getPacketVersion(packetData) == 2)
{
...
}
else
{
// unsupported
}


В принципе, это можно сделать даже расширяемо: завести карту, задающую соответствие номер версии --> функция-обработчик.
Делай что должно, и будь что будет
Re[2]: Посоветуйте паттерн
От: _Sphinx_ Россия http://www.rogov.su
Дата: 09.05.08 20:32
Оценка:
Здравствуйте, SergH, Вы писали:

SH>Я бы сказал, что это уже порнуха


Согласен

SH>Ну смотри. Зачем вводится фабрика и за счёт чего достигается расширяемость? Для того/за счёт того, что весь остальной код может практически игнорировать разницу между типами пакетов, обрабатывать их всех более-менее одинаково. И только в критичных случаях спрашивать что-то типа "версия такая-то?" или, лучше "версия не ниже такой-то?" Ну или "такая-то возможность поддерживается?" Но если такие штуки встречаются часто, ни расширяемости не получится, ни фабрика не нужна. Для этого пакеты должны иметь более-менее общий интерфейс. Возможно, на уровне пакетов это не удастся, значит нужно оперировать более высоким уровнем абстракции.


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

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

PacketBase Packet = PacketBase.Parse(Data); // typeof(Packet) == PacketV2, где class PacketV2 : PacketBase {...}
// че-то тут делаем
if(Packet.Version == 2)
{
    PacketV2 Packet2 = (PacketV2)Packet;
    // Используем данные версии 2
}


И совсем бредовая идея — собрать поля с пакетов всех версий в одном классе, сделать свойства с геттерами и сеттерами и при обращении к свойству проверять версию, и если в этой версии свойство недоступно — как то об этом сообщать, эксепшн специальный бросать например? Это как? Тоже неприлично? Чего то мне не нравится самому такая мысль...
ICQ: 203-009-172
Re[3]: Посоветуйте паттерн
От: SergH Россия  
Дата: 09.05.08 21:11
Оценка:
Здравствуйте, _Sphinx_, Вы писали:

_S_>Подтверждаешь мои худшие опасения... Надежда, как говорится, умирает последней...


Ну, какие опасения.. Надо просто понимать, что паттерны — не самоцель. Паттерн — не ради паттерна. Паттерн ради расширяемости, ясности кода, изменяемости. И оценивать результат нужно с этой точки зрения.

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


_S_>
_S_>PacketBase Packet = PacketBase.Parse(Data); // typeof(Packet) == PacketV2, где class PacketV2 : PacketBase {...}
_S_>// че-то тут делаем
_S_>if(Packet.Version == 2)
_S_>{
_S_>    PacketV2 Packet2 = (PacketV2)Packet;
_S_>    // Используем данные версии 2
_S_>}
_S_>


Можно так. Минус — если появляется пакет новой версии, нужно будет переделывать общий код обработки. Если пакетов новой версии в ближайшие N лет не планируется... Или если модификации незначительны...

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

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

_S_>И совсем бредовая идея — собрать поля с пакетов всех версий в одном классе, сделать свойства с геттерами и сеттерами и при обращении к свойству проверять версию, и если в этой версии свойство недоступно — как то об этом сообщать, эксепшн специальный бросать например? Это как? Тоже неприлично? Чего то мне не нравится самому такая мысль...


Ну, ты же не знаешь заранее, какие поля будут в будущих версиях. Но можно сделать свойство, возвращающее поле по имени. И, скажем, итератор по всем свойствам. Это может быть плохой идеей, а может быть нормальным подходом — зависит от ситуации, от того, что ты с этими пакетами делаешь. Например, если их надо просто дампить в файл/на экран в текстовом виде — вполне вариант.
Делай что должно, и будь что будет
Re: Посоветуйте паттерн
От: gandjustas Россия http://blog.gandjustas.ru/
Дата: 09.05.08 21:14
Оценка:
Здравствуйте, _Sphinx_, Вы писали:

_S_>Полагаю, что задача довольно типичная и для ее решения должен подходить какой-то из паттернов.


_S_>Итак, исходные условия

_S_>1. Маленькая библиотека, пишется на C#, предполагается использование только в среде .NET, соответственно использование того, что получится, должно быть максимально удобным в контексте языка C#
_S_>2. Пишется библиотека для разбора и обработки некоторых данных, получаемых по сети. Данные представляют собой пакеты некоторого протокола (в данном случае речь идет про протокол NetFlow хотя это и не принципиально). Протокол имеет несколько версий, что сказывается на структуре и содержимом пакетов. Достоверно известно, что в любой версии первые 2 байта пакета означают номер версии протокола. Необходимо сделать обертку для этих данных — т.е. прилетает пакет, мы его разбираем на части, и засовываем в какой-то класс.

_S_>Вот как собственно сделать это наиболее удобным образом? чтобы и выглядело прилично, и было расширяемо нормально (в случае появления новых версий).

Наиболее подходящий паттерн — "стратегия", но....


_S_>Я пока думаю сделать так: завести отдельные классы для каждой версии, базовый класс (Foo) для них и в нем статический метод. Этот метод будет возвращать объект с типом Foo принимая входной массив байт. Соответственно, будет определять версию пакета и создавать объект нужного типа, инициализируя его всеми необходимыми данными. Если это как то вписывается в существующие паттерны, то как это можно назвать? Мне думалось, что это фабрика, но как-то не очень похоже на фабрику...

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

_S_>Нужен совет.

1) Занимайтесь проектированием "сверху вниз", исходя из задач, которые должна выполнять система.
2) Не проектируйте иерархии, которые не соответсвуюют LSP (принципу подстановки лисков).
Re: Посоветуйте паттерн
От: Vyacheslav Benedichuk Интернет  
Дата: 11.05.08 14:07
Оценка:
Здравствуйте, _Sphinx_, Вы писали:


_S_>2. Пишется библиотека для разбора и обработки некоторых данных, получаемых по сети. Данные представляют собой пакеты некоторого протокола (в данном случае речь идет про протокол NetFlow хотя это и не принципиально). Протокол имеет несколько версий, что сказывается на структуре и содержимом пакетов. Достоверно известно, что в любой версии первые 2 байта пакета означают номер версии протокола. Необходимо сделать обертку для этих данных — т.е. прилетает пакет, мы его разбираем на части, и засовываем в какой-то класс.


_S_>Соответственно для конечной программы обработка пакета представляет собой что-то типа:


_S_>PacketBase Packet = PacketBase.Parse(packetData);

_S_>if(typeof(Packet) == typeof(PacketVersion1))
_S_>{
_S_> PV1 = (PacketVersion1)Packet;
_S_>}

_S_>if(typeof(Packet) == typeof(PacketVersion2))

_S_>{
_S_> PV2 = (PacketVersion2)Packet;
_S_>}
_S_>[/c#]

А что ты собираешься дальше делать с этими PV1 и PV2?
Насколько у PacketVersion1 и PacketVersion2 совпадают интерфесы?
От этого зависит как делать.

Вообще реализация с If-ами будет плохо-расширяемой в будущем.

Как вариант можно предложить посмотреть на классический шаблон Chain Of Responsibility. возможно он тебе подойдет.
Либо сделать класс обработчик для каждой версии протокола. и загнать их экземпляры в Dictionary<String, IPacketProcessor>

тогда твой код будет выглядеть примерно так.

public interface IPacketProcessor
{
void ProcessPacket(byte[] packet); //В этом методе реализуются действия по обработке пакета
}

public class PacketWorker
{

private static Dictionary<String, IPacketProcessor> processors = LoadProcessors(); //загружаем сисок обработчиков пакетов.
//Можно грузить с помощью reflection читая список из конфига.
//Тогда для добавления нового обработчика не потребуется перекомпиляция.
//Достаточно будет добавить библиотеку с обработчиком и поправить конфиг.

...

String packetVersion = GetVersion(packetData);

processors[packetVersion].ProcessPacket(packetData);


...

}
--
http://www.slideshare.net/vyacheslavbenedichuk
https://www.linkedin.com/in/vbenedichuk
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.