design patterns
От: Аноним  
Дата: 07.10.06 12:20
Оценка:
Добрый день!

Подскажите, пожалуйста, как применить design patterns в следующем случае:

Имеется базовый (абстрактный) класс назовем его BaseMessage и несколько наследуемых от него классов. Эти классы предназначены для хранения сообщений, получаемых в текстовом виде и содержащих набор определенных информационных полей из этого текстового сообщения. Различные типы текстовых сообщений имеют различные поля, которые порою значительно отличаются друг от друга и могут иметь иерархическую структуру. Необходимо спроектировать эти классы таким образом, чтобы объекты-сообщения можно было сохранять в различных форматах: текст, xml, различные базы данных (firebird, mysql…).
Разработка осуществляется на C#. Пока я ввел в базовый класс объект (типа interface) ISaver, в котором присутствует метод Save(BaseMessage message), в котором в качестве аргумента указатель на сообщение, которое нужно сохранить. Виртуальный метод класса BaseMessage также содержит метод Save без параметров, который вызывает метод этого интерфейсного объекта и указывая в качестве аргумента this.
Но как быть дальше с классами реализующими этот интерфейс (ISaver)– ума не приложу. Не хочется создавать классы Saver-ы для каждого формата, в котором нужно сохранять сообщения, и для каждого типа сообщения.
Как быть?




09.10.06 19:32: Перенесено модератором из '.NET' — Хитрик Денис
Re: design patterns
От: konsoletyper Россия https://github.com/konsoletyper
Дата: 07.10.06 14:13
Оценка:
Здравствуйте, <Аноним>, Вы писали:

А>Подскажите, пожалуйста, как применить 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'ов.
... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
Re: design patterns
От: _FRED_ Черногория
Дата: 07.10.06 15:06
Оценка: 14 (3)
Здравствуйте, <Аноним>, Вы писали:

А>Не хочется создавать классы 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");
      }//if

      base.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.
Re[2]: design patterns
От: gok Россия  
Дата: 10.10.06 18:20
Оценка:
Здравствуйте, _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)
}


Нарочно оставил типы данных одинаковыми, в этом случае, имхо, невозможно опознать тип сообщения. Но это уже второй вопрос: Как?
gok
Re[3]: design patterns
От: _FRED_ Черногория
Дата: 11.10.06 08:25
Оценка:
Здравствуйте, gok, Вы писали:

gok>А можно ли в этой фабрике избежать переключателей для выявления нужного Saver-a?


Да, достаточно разобрать различные реализации паттерна AbstractFactory, например Паттерн разработки Abstract Factory
Автор(ы): Михаил Новиков
Дата: 03.03.2006
Паттерн Abstract Factory предоставляет интерфейс для создания семейств связанных или зависимых объектов (далее — продукты), позволяя не указывать их конкретные классы.
.
... << 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
От: SP_ Украина  
Дата: 13.10.06 08:17
Оценка:
Здравствуйте, Аноним, Вы писали:

А>Как быть?

Раз на дот нете, то может пометить специальным атрибутом данные которые надо сохранять и в Saver-е просто проходить по метаданным?
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.