Подскажите, пожалуйста, как применить design patterns в следующем случае:
Имеется базовый (абстрактный) класс назовем его BaseMessage и несколько наследуемых от него классов. Эти классы предназначены для хранения сообщений, получаемых в текстовом виде и содержащих набор определенных информационных полей из этого текстового сообщения. Различные типы текстовых сообщений имеют различные поля, которые порою значительно отличаются друг от друга и могут иметь иерархическую структуру. Необходимо спроектировать эти классы таким образом, чтобы объекты-сообщения можно было сохранять в различных форматах: текст, xml, различные базы данных (firebird, mysql…).
Разработка осуществляется на C#. Пока я ввел в базовый класс объект (типа interface) ISaver, в котором присутствует метод Save(BaseMessage message), в котором в качестве аргумента указатель на сообщение, которое нужно сохранить. Виртуальный метод класса BaseMessage также содержит метод Save без параметров, который вызывает метод этого интерфейсного объекта и указывая в качестве аргумента this.
Но как быть дальше с классами реализующими этот интерфейс (ISaver)– ума не приложу. Не хочется создавать классы Saver-ы для каждого формата, в котором нужно сохранять сообщения, и для каждого типа сообщения.
Как быть?
09.10.06 19:32: Перенесено модератором из '.NET' — Хитрик Денис
Здравствуйте, <Аноним>, Вы писали:
А>Подскажите, пожалуйста, как применить design patterns в следующем случае:
А>Имеется базовый (абстрактный) класс назовем его BaseMessage и несколько наследуемых от него классов. Эти классы предназначены для хранения сообщений, получаемых в текстовом виде и содержащих набор определенных информационных полей из этого текстового сообщения. Различные типы текстовых сообщений имеют различные поля, которые порою значительно отличаются друг от друга и могут иметь иерархическую структуру. Необходимо спроектировать эти классы таким образом, чтобы объекты-сообщения можно было сохранять в различных форматах: текст, xml, различные базы данных (firebird, mysql…).
Для полей можно использовать паттерн компоновщик.
А>Разработка осуществляется на C#. Пока я ввел в базовый класс объект (типа interface) ISaver, в котором присутствует метод Save(BaseMessage message), в котором в качестве аргумента указатель на сообщение, которое нужно сохранить. Виртуальный метод класса BaseMessage также содержит метод Save без параметров, который вызывает метод этого интерфейсного объекта и указывая в качестве аргумента this.
Я так понимаю, метод Save у BaseMessage предоставляет информацию о сообщении, обработанную таким образом, чтобы её можно было сохранить. Т.е. он не сохраняет информацию физически в каком-либо формате. Тогда, чтобы Save не вызывал путанницы, предлагаю переименовать его в Export() или в GetMessageInfo() или ещё во что-нибудь. Для сохранения можно было бы использовать стратегию или строитель, т.е. соответствующим образом сделать ISaver.
А>Но как быть дальше с классами реализующими этот интерфейс (ISaver)– ума не приложу. Не хочется создавать классы Saver-ы для каждого формата, в котором нужно сохранять сообщения, и для каждого типа сообщения.
ИМХО, нужно писать реализацию для каждого формата. Сообщения должны возвращать какую-либо информацию о себе в Export(), а ISaver должен сохранять эту информацию.
А>Как быть?
Впредь обращаться в "Архитектуру ПО"
А ещё можно сделать так же, как и в стандартной сериализации. Только нужно будет понаписать кучу IFormatter'ов.
Здравствуйте, <Аноним>, Вы писали:
А>Не хочется создавать классы Saver-ы для каждого формата, в котором нужно сохранять сообщения, и для каждого типа сообщения.
Почему? Как минимум, для каждой пары сообщение-формат нужен отдельный метод. Правильно? Ведь "Различные типы текстовых сообщений имеют различные поля, которые порою значительно отличаются друг от друга" и в общем виде не имеют ничего общего. Если этот метод вынести в отдельный класс, то появится возможность добавлять типы сообщений и новые форматы независимо ни от чего.
А>Как быть?
ИМХО. Начнём с сообщения и классов, которые его сохраняют.
// Базовый тип для сообщений. Не обязательно интерфейс.public interface IMessage
{
}
// Базовый класс "сохранителей"
// Параметры сохранения (путь и имя файла или строка соединения)
// передаются конкретному экземпляру (не-абстрактному наследнику)
// в конструкторе.
// Наследники данного класса будут "добавлять" места сохранения (текс, хмл, база данных),
// а спецификация TMessage позволит разделить наследников по типам сообщений.public abstract class MessageSaver<TMessage> where TMessage : IMessage
{
protected MessageSaver() {
}
// Метод, сохраняющий сообщение.
// Он перелаёт управление защищённому виртуальному методу,
// так как открытые виртуальные методы, ИМХО, - зло :о)
// см. паттерн NVI [http://www.rsdn.ru/Forum/?mid=1873634]public void Save(TMessage message) {
SaveCore(message);
}
protected abstract void SaveCore(TMessage message);
}
// "Промежуточный", "вспомогательный" класс в иерархии.
// Нужен
// 1. Для хранения свойства имени файла
// 2. Для переноса логики сохранения сообщений из MessageSaver<>.SaveCore(TMessage)
// в Save(TMessage, TWriter)public abstract class StreamWriterMessageSaver<TMessage, TWriter> : MessageSaver<TMessage>
where TMessage : IMessage
where TWriter : IDisposable
{
private readonly string filePath;
protected StreamWriterMessageSaver(string filePath) {
this.filePath = filePath;
}
protected string FilePath {
get { return filePath ?? String.Empty; }
}
protected override void SaveCore(TMessage message) {
using(TWriter writer = CreateWriter(message)) {
Save(message, writer);
}//using
}
protected abstract TWriter CreateWriter(TMessage message);
protected abstract void Save(TMessage message, TWriter writer);
}
// Базовый клас, сохраняющий сообщения в поток и ничего не знающий о содержимом сообщения
// Специфицирует базовый класс StreamWriter-омpublic abstract class TextMessageSaver<TMessage> : StreamWriterMessageSaver<TMessage, StreamWriter> where TMessage : IMessage
{
protected TextMessageSaver(string filePath) : base(filePath) {
}
protected override StreamWriter CreateWriter(TMessage message) {
return new StreamWriter(FilePath);
}
}
// Базовый клас, сохраняющий сообщения в xml-файл и ничего не знающий о содержимом сообщения
// Специфицирует базовый класс XmlWriter-омpublic abstract class XmlMessageSaver<TMessage> : StreamWriterMessageSaver<TMessage, XmlWriter> where TMessage : IMessage
{
protected XmlMessageSaver(string filePath) : base(filePath) {
}
// Метод даёт возможность наслдедникам более тонко настроить writer, если это необходимоprotected virtual XmlWriterSettings CreateWriterSettings(TMessage message) {
XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true;
settings.OmitXmlDeclaration = true;
settings.NewLineOnAttributes = true;
return settings;
}
protected override XmlWriter CreateWriter(TMessage message) {
XmlWriterSettings settings = CreateWriterSettings(message);
return XmlWriter.Create(FilePath, settings);
}
}
// Базовый клас, сохраняющий сообщения в базу данных и ничего не знающий о содержимом сообщенияpublic abstract class DatabaseMessageSaver<TMessage> : MessageSaver<TMessage> where TMessage : IMessage
{
private readonly DbProviderFactory factory;
private readonly string connectionString;
protected DatabaseMessageSaver(string providerInvariantName, string connectionString) {
factory = DbProviderFactories.GetFactory(providerInvariantName);
this.connectionString = connectionString;
}
protected string ConnectionString {
get { return connectionString ?? String.Empty; }
}
protected DbProviderFactory Factory {
get { return factory; }
}
// Метод даёт возможность наслдедникам более тонко настроить соединение, если это необходимоprotected virtual void PrepareConnection(TMessage message, DbConnection connection) {
}
// Метод позволяет наследнику сформировать текст команды для сохранения сообщенияprotected abstract string GetCommandText(TMessage message);
// Метод даёт возможность наслдедникам более тонко настроить команду, если это необходимоprotected virtual void PrepareCommand(TMessage message, DbCommand command) {
}
protected override void SaveCore(TMessage message) {
using(DbConnection connection = Factory.CreateConnection()) {
connection.ConnectionString = ConnectionString;
PrepareConnection(message, connection);
using(DbCommand command = connection.CreateCommand()) {
command.CommandText = GetCommandText(message);
PrepareCommand(message, command);
connection.Open();
command.ExecuteNonQuery();
}//using
}//using
}
}
Теперь готовы механизмы для сохранения IMessage куда угодно (Text, Xml или база данных).
Далее займёмся, собственно, сохранением конкретных сообщений:
class Message1 : IMessage
{
public int ID;
public string Header;
public string Body;
public string Footer;
}
class Message1ToTextSaver : TextMessageSaver<Message1>
{
public Message1ToTextSaver(string filePath) : base(filePath) {
}
protected override void Save(Message1 message, StreamWriter writer) {
if(message == null) {
throw new ArgumentNullException("message");
} else if(writer == null) {
throw new ArgumentNullException("writer");
}//if
writer.WriteLine("Message ID: {0}", message.ID);
writer.WriteLine("Header is: {0}", message.Header);
writer.WriteLine(message.Body);
writer.WriteLine("Footer is: {0}", message.Footer);
}
}
class Message1ToXmlSaver : XmlMessageSaver<Message1>
{
public Message1ToXmlSaver(string filePath) : base(filePath) {
}
protected override void Save(Message1 message, XmlWriter writer) {
if(message == null) {
throw new ArgumentNullException("message");
} else if(writer == null) {
throw new ArgumentNullException("writer");
}//if
// Не уверен, что не наврал в порядке\смысле вызываемых мотодов,
// но, надеюсь, суть ясна :о))
writer.WriteStartElement("Message");
writer.WriteAttributeString("ID", message.ID.ToString());
writer.WriteAttributeString("Header", message.Header);
writer.WriteAttributeString("Footer", message.Footer);
writer.WriteString(message.Body);
writer.WriteEndElement();
}
}
class Message1ToSqlServerSaver : DatabaseMessageSaver<Message1>
{
public Message1ToSqlServerSaver(string connectionString) : base("System.Data.SqlClient", connectionString) {
}
protected override string GetCommandText(Message1 message) {
return"Update [Messages] Set [Header] = @Header, [Body] = @Body, [Footer] = @Footer Where [ID] = @ID";
}
protected override void PrepareCommand(Message1 message, DbCommand command) {
if(message == null) {
throw new ArgumentNullException("message");
} else if(command == null) {
throw new ArgumentNullException("command");
}//ifbase.PrepareCommand(message, command);
command.Parameters.Add(new SqlParameter("@ID", message.ID));
command.Parameters.Add(new SqlParameter("@Header", message.Header));
command.Parameters.Add(new SqlParameter("@Body", message.Body));
command.Parameters.Add(new SqlParameter("@Footer", message.Footer));
}
}
Как создавать Saver-ы — отдельный разговор. Это может быть какая-либо фабрика, например. Но возможно и прямое создание.
... << RSDN@Home 1.2.0 alpha rev. 652>>
Now playing: «Тихо в лесу…»
Help will always be given at Hogwarts to those who ask for it.
Re: design patterns
От:
Аноним
Дата:
09.10.06 06:52
Оценка:
А>Добрый день!
А>Подскажите, пожалуйста, как применить design patterns в следующем случае:
FactoryMethod в твоем случае очень даже подходит Его описаний и примеров использования куча в нете..
Re: design patterns
От:
Аноним
Дата:
09.10.06 14:48
Оценка:
А>Имеется базовый (абстрактный) класс назовем его BaseMessage и несколько наследуемых от него классов. Эти классы предназначены для хранения сообщений, получаемых в текстовом виде и содержащих набор определенных информационных полей из этого текстового сообщения. Различные типы текстовых сообщений имеют различные поля, которые порою значительно отличаются друг от друга и могут иметь иерархическую структуру. Необходимо спроектировать эти классы таким образом, чтобы объекты-сообщения можно было сохранять в различных форматах: текст, xml, различные базы данных (firebird, mysql…). А>Разработка осуществляется на C#. Пока я ввел в базовый класс объект (типа interface) ISaver, в котором присутствует метод Save(BaseMessage message), в котором в качестве аргумента указатель на сообщение, которое нужно сохранить. Виртуальный метод класса BaseMessage также содержит метод Save без параметров, который вызывает метод этого интерфейсного объекта и указывая в качестве аргумента this. А>Но как быть дальше с классами реализующими этот интерфейс (ISaver)– ума не приложу. Не хочется создавать классы Saver-ы для каждого формата, в котором нужно сохранять сообщения, и для каждого типа сообщения. А>Как быть?
отделить параллельную имплементацию нужно через Bridge (не забудь Plugin для каждой имплиментации)
иерархию через Composite.
по сохранению смотри в сторону Builder.
Здравствуйте, _FRED_, Вы писали:
_FR>Как создавать Saver-ы — отдельный разговор. Это может быть какая-либо фабрика, например. Но возможно и прямое создание.
А можно ли в этой фабрике избежать переключателей для выявления нужного Saver-a?
Скажем есть два типа сообщения:
class Message1 : IMessage
{
public int ID;
public string Header;
public string Body;
public string Footer;
}
class Message2 : IMessage
{
public int Age;
public string FirstName;
public string MiddleName;
public string LastName;
}
Внутри фабрики (не шарп):
if ( itIsMessage1(thisMessage) )
{
return new Message1ToTextSaver(thisMessage)
}
else if ( itIsMessage2(thisMessage) )
{
return new Message2ToTextSaver(thisMessage)
}
Нарочно оставил типы данных одинаковыми, в этом случае, имхо, невозможно опознать тип сообщения. Но это уже второй вопрос: Как?
Здравствуйте, Аноним, Вы писали:
А>Как быть?
Раз на дот нете, то может пометить специальным атрибутом данные которые надо сохранять и в Saver-е просто проходить по метаданным?