Модераторы: Partizan, gambit

Поиск:

Ответ в темуСоздание новой темы Создание опроса
> Фабрика классов, C++.NET 
:(
    Опции темы
Vyacheslav
Дата 16.3.2005, 10:36 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Экс. модератор
Сообщений: 2124
Регистрация: 25.3.2002
Где: Москва

Репутация: 2
Всего: 59



Возможно. То что я реализовал - это первое, что пришло мне в голову при поверхностном обзоре возможностей .NET. А я в нем только с начала марта. А я по опыту знаю, что первое пришедшее решение далеко не всегда эффективно. С другой стороны иммитировать виртуальный конструктор средствами pure C++ при наличии такового( CreateInstance) в среде выглядит довольно глупо. Тем более, IMHO то что написанное на чистом С++ и STL выглядит изящнее, чем его эквивалент на managed C++. Отсюда и возник первоначальный вопрос
Добавлено @ 10:44
Вот чертов managed C++
Чтобы в pure С++ закешировать список типов мне достаточно было указать static
Вместо
Код

System::Type* CExecutorHelper::FindType(CMessage* Message )
{
    Type* types[] = Assembly::GetExecutingAssembly()->GetTypes();



использовать
Код

System::Type* CExecutorHelper::FindType(CMessage* Message )
{
    static Type* types[] = Assembly::GetExecutingAssembly()->GetTypes();


но в managed C++ это не проходит. Ругается компилятор. Теперь придется что то изобретать дополнительно.


--------------------
С уважением, Вячеслав Ермолаев
PM MAIL WWW ICQ   Вверх
Vyacheslav
Дата 16.3.2005, 11:07 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Экс. модератор
Сообщений: 2124
Регистрация: 25.3.2002
Где: Москва

Репутация: 2
Всего: 59



Во как получилось. В полном противоречии со стандартом С++ smile
Код

__gc class CExecutorHelper
{
public:
    static IExecutor* ExecutorCreate(CMessage*, CConnection*);
private:
    static System::Type* FindType(CMessage*);
    static System::Type* m_types[] = System::Reflection::Assembly::GetExecutingAssembly()->GetTypes();
};



--------------------
С уважением, Вячеслав Ермолаев
PM MAIL WWW ICQ   Вверх
arilou
Дата 16.3.2005, 20:29 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Великий МунаБудвин
****


Профиль
Группа: Экс. модератор
Сообщений: 2646
Регистрация: 15.7.2004
Где: город-герой Минск

Репутация: 21
Всего: 61



Честно говоря, просмотрев эту ветку, у меня возник немного другой вариант. Напишу на C#, т.к. мне это ближе. Для моей реализации необходимо 2 допущения:

1) Каждому классу, наследующемуся из MyApp.MyNamespace.MyMessage соответствует класс MyMessageDescendantExecutor, где MyMessageDescendant - это имя класса-потомка MyMessage. Класс MyMessageDescendantExecutor должен находиться в том же namespace, что и MyMessageDescendant.

2) Все Executor'ы соответствуют интерфейсу IExecutor

Код

public interface IExecutor
{
  void Populate(MyMessage message);
  void Execute();
}

public abstract class MyMessage
{
  // ...... реализация не имеет значения
}

public class TestMessage : MyMessage
{
  // ...
}

public class TestMessageExecutor : IExecutor
{
  // ...
}

class Connection 
{
  private const string executorToken = "Executor";
  private static Hashtable _typeMap = new Hashtable();

  private static IExecutor lookupExecutor(MyMessage msg)
  {
     Type execKey = msg.GetType();
     string execName = execKey.FullName + executorToken;
     
     
     if(_typeMap.ContainsKey(execKey))
       return Activator.CreateInstance(_typeMap[execKey] as Type) as IExecutor;
   
    // для простоты считаем, что все в одном assembly
    Type execType = Assembly.GetExecutingAssembly().GetType(execName);
    _typeMap.Add(execKey, execType);

    // можно кэшировать не типы, а сами инстансы, если задача того требует
    return Activator.CreateInstance(_typeMap[execKey] as Type) as IExecutor;
  }

  void ProcessMessages()
  {
    // проверки на null убраны в целях экономии :)
    while (MyMessage message = PeekIncoming()) 
    {
       IExecutor executor = lookupExecutor(message);
       executor->Populate(message);
       executor->Execute();
     }
   }
}


В результате велосипед не изобретен, пользуемся стандартными средствами. Как написано в комментарии, можно кэшировать не типы executor'ов, а инстансы.

Надеюсь, что это имеет смысл. Таким же образом решаются задачи этого уровня.

P.S. 1) Не заметил, писал ли кто-нить, что Activator.CreateInstance в сочетании с информацией о типах реализует абстракную фабрику классов, т.е. такую, которая умеет создавать инстансы любых типов.
2) Добавление других сообщений и executor'ов происходит с минимальными издержками - добавляешь 2 класса, и вуаля - все работает smile

Это сообщение отредактировал(а) arilou - 16.3.2005, 20:35


--------------------
user posted imageuser posted image
PM WWW ICQ   Вверх
Vyacheslav
Дата 17.3.2005, 10:29 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Экс. модератор
Сообщений: 2124
Регистрация: 25.3.2002
Где: Москва

Репутация: 2
Всего: 59



Тут оказалась скрыта другая часть задачи. Дело в том, что при таком подходе проблема просто перетечет в PeekIncoming(). От клиента приходит массив байтов, с помощью которого заполняется CMessage. Если использовать Ваш подход, то мне все равно на основании данных, содержащихся в массиве необходимо создать соответсвующий этим данным класс XXXMessage, а затем используя имя класса создать экземпляр класса XXXMessageExecutor. При этом все равно вопрос остается открытым : нужно приходящим данным сопоставить соответствущий класс( теперь не Executor, а Мessage) и создать его экземпляр.
Здесь есть один неприятный момент: клиентская часть уже исторически унаследована, может работать на различных платформах и переделать ее таким образом, что бы она вместо линейного массива данных передавала непосредственно объект, нельзя. Интерфейс и протокол обмена определен и зафиксирован и моя программа дожна обрабатывать сообщения, поступающие от него. Проблема в том, что их в общем случае очень много, а я должен обрабатывать ограниченную часть, но никто не гарантирует, что после некоторого времени, потребуется зайдействовать дополнительные команды из этого набора. Также не исключается возможность, что добавлять обработки новых сообщения буду не я и даже не моя фирма. Поэтому и хочется по максимому облегчить процесс добавления
В моем случае при этом добавляется один класс и, я пока не силен в .NET, мне кажется при внесении небольших изменений, можно дело поставить таким образом, что добавление новых классов будет производится просто посредством подмены некоторой загружаемой библиотеки (сборки?)


--------------------
С уважением, Вячеслав Ермолаев
PM MAIL WWW ICQ   Вверх
arilou
Дата 17.3.2005, 13:00 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Великий МунаБудвин
****


Профиль
Группа: Экс. модератор
Сообщений: 2646
Регистрация: 15.7.2004
Где: город-герой Минск

Репутация: 21
Всего: 61



Цитата(Vyacheslav @ 17.3.2005, 10:29)
Если использовать Ваш подход, то мне все равно на основании данных, содержащихся в массиве необходимо создать соответсвующий этим данным класс XXXMessage, а затем используя имя класса создать экземпляр класса XXXMessageExecutor. При этом все равно вопрос остается открытым : нужно приходящим данным сопоставить соответствущий класс( теперь не Executor, а Мessage) и создать его экземпляр.


Наверняка массив байт содержит в начале некий заголовок, который указывает на тип сообщения. Я прав? Если так, то можно сделать следующее (в целях упрощения заголовок будет типа byte, т.е. имеем 255 различных типов сообщений):

Код

using System;
using System.Reflection;
using System.Collections;

namespace ConsoleApplication1
{
    public class Message
    {
        // ...
    }

    // атрибут, при помощи которого будем отпределять, какому заголовку
    // соответствует определенный класс сообщения
    public class MessageTypeAttribute : System.Attribute
    {
        private byte _id = 0;

        public MessageTypeAttribute(byte id) : base()
        {
            _id = id;
        }

        public byte ID { get { return _id; } }
    }


    // добавляем метаданные, описывающие данный класс сообщения
    // в данном случае это атрибут, связывающий класс сообщения
    // с определенным заголовком
    // комилятор C# достаточно умён, чтобы определить, что на самом деле
    // имеется ввиду MessageTypeAttribute, а не просто MessageType :)
    [MessageType(1)]
    public class TestMessage : Message
    {
        // ...
    }

    // данный класс является хранилищем всех типов сообщений,
    // сопоставленных с соотв. заголовками
    public class MessageRepository
    {
        // здесь храним типы сообщений
        private static Hashtable _msgMap = new Hashtable();

        // возвращаем тип сообщения по заголовку
        public static Type GetMessageType(byte msgID)
        {
            if(_msgMap.ContainsKey(msgID))
                return _msgMap[msgID] as Type;
            else
                throw new ApplicationException(String.Format("Message type for {0} is not registered", msgID));
        }

        public static void Init()
        {
            // из текущей сборки считаем все типы, которые наследуются
            // из Message
            Type[] exportedTypes = Assembly.GetExecutingAssembly().GetExportedTypes();

            foreach(Type typ in exportedTypes)
            {
                if(typ.IsSubclassOf(typeof(Message)))
                {
                    // если наш тип - наследник Message, считываем атрибут MessageType
                    // и записываем данный тип в Hashtable. Ключ - значение свойства
                    // ID, указанное в атрибуте
                    object[] attrs = typ.GetCustomAttributes(typeof(MessageTypeAttribute), false);

                    // мы ожидаем что атрибует будет, и что он будет один
                    // не будем заморачиваться :)
                    _msgMap.Add((attrs[0] as MessageTypeAttribute).ID, typ);
                }
            }
        }
    }

    /// <summary>
    /// Summary description for Class1.
    /// </summary>
    class App
    {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main(string[] args)
        {
            MessageRepository.Init();

            // выводим найденный тип
            Console.WriteLine(MessageRepository.GetMessageType(1).ToString());
            Console.ReadLine();
        }
    }
}


Цитата(Vyacheslav @ 17.3.2005, 10:29)
В моем случае при этом добавляется один класс и, я пока не силен в .NET, мне кажется при внесении небольших изменений, можно дело поставить таким образом, что добавление новых классов будет производится просто посредством подмены некоторой загружаемой библиотеки (сборки?)


Мой приведенный выше пример можно расширять следующим образом:
1) Можно редактировать исходный код - добавлять новые сообщения и executor'ы - это при условии, что есть доступ к исходникам

2) Выносим Message и IExecutor в отдельную общую сборку. В MessageRepository.Init заружаем все сборки из определенного каталога (например, components), перебираем все загруженные сборки, выполняем ту же процедуру (см. метод Init) для каждого типа, удовлетворяющего условию.

Разработка дополнительных типов сообщений потребует от других фирм поставить референс на Вашу общую сборку (там, где определен класс Message и интерфейс IExecutor), реализовать их и поместить в каталог components, откуда ваша программа благополучно считает их в repository.

Наедюсь, что это Вам поможет. smile

P.S. Кстати, таким же образом можно реализовать и загрузку и сопоставление всех Executor'ов.

P.P.S Таким образом, функция PeekIncoming может выглядеть следующим образом:
Код

Message PeekIncoming()
{
    // псевдокод
    byte id = getMessageHeader().ID;

    Message msg = Activator.CreateInstance(MessageRepository.GetMessageType(id)) as Message;
    msg.ReadFromStream(getMessageStream());

    return msg;
}


Это сообщение отредактировал(а) arilou - 17.3.2005, 13:05


--------------------
user posted imageuser posted image
PM WWW ICQ   Вверх
Vyacheslav
Дата 17.3.2005, 14:08 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Экс. модератор
Сообщений: 2124
Регистрация: 25.3.2002
Где: Москва

Репутация: 2
Всего: 59



Насчет типа сообщения Вы правы. Только сначала идет длина, потом тип.
Насчет же всего прочего, я примерно так и делал за некоторым исключением.
Во первых я таким образом создавал не Message, а Executor

Код

__gc class MessageType : public System::Attribute
{
public:
    MessageType(Byte MsgType)
        :m_value(MsgType){}
    __property  get_Value() { return m_value; }
private:
    Byte m_value; 
};

//..

[MessageType(MSG_TEST)]
__gc class CTestExecutor : public IExecutor
{
public:    
    CTestExecutor(CMessage* Message, CConnection* Connection)
        :IExecutor(Message, Connection) {}
    void Execute(void); 
};


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


А теперь вопрос. Можно ли заменить в принципе это десериализацией при условии при фиксированном и неизменном формате присылаемых данных?
Ведь приведенный вами код в принципе этим и является
Код

    byte id = getMessageHeader().ID;
    Message msg = Activator.CreateInstance(MessageRepository.GetMessageType(id)) as Message;
    msg.ReadFromStream(getMessageStream());



--------------------
С уважением, Вячеслав Ермолаев
PM MAIL WWW ICQ   Вверх
arilou
Дата 17.3.2005, 14:17 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Великий МунаБудвин
****


Профиль
Группа: Экс. модератор
Сообщений: 2646
Регистрация: 15.7.2004
Где: город-герой Минск

Репутация: 21
Всего: 61



В принципе
Цитата(Vyacheslav @ 17.3.2005, 14:08)
Можно ли заменить в принципе это десериализацией при условии при фиксированном и неизменном формате присылаемых данных?


Не могу придумать, чем тут поможет десериализция в том понятии, в котором она используется в .NET'e, ведь приходит некий поток байтов, который же не является бинарным отображением объекта? Если бы это были какие-нить символьные данные, то можно было бы через XSL превращать их в XML-сериализованное представление объекта, и десериализовать сам объект. На выходе получался бы экземпляр Message.

Скорее, я бы определил этот метод (ReadFromStream) в базовом классе, а в потомках бы из байтового массива на входе доставал бы данные и записывал в поля и свойства. Не зная специфики принимаемых данных ничего более умного предложить не могу smile


--------------------
user posted imageuser posted image
PM WWW ICQ   Вверх
Vyacheslav
Дата 17.3.2005, 14:53 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Экс. модератор
Сообщений: 2124
Регистрация: 25.3.2002
Где: Москва

Репутация: 2
Всего: 59



Я не стал использовать метод. Просто в конструктор тупо передаю массив и от хранится как поле класса. Для общих свойств(тип, длина итд) определил свойства, которые читают-записывают прямо из массива.


--------------------
С уважением, Вячеслав Ермолаев
PM MAIL WWW ICQ   Вверх
arilou
Дата 17.3.2005, 14:59 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Великий МунаБудвин
****


Профиль
Группа: Экс. модератор
Сообщений: 2646
Регистрация: 15.7.2004
Где: город-герой Минск

Репутация: 21
Всего: 61



Цитата(Vyacheslav @ 17.3.2005, 14:53)
Для общих свойств(тип, длина итд) определил свойства, которые читают-записывают прямо из массива.


Логично smile Кстати, через Activator.CreateInstance можно тоже вызывать параметризованные конструкторы.


--------------------
user posted imageuser posted image
PM WWW ICQ   Вверх
Vyacheslav
Дата 17.3.2005, 16:10 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Экс. модератор
Сообщений: 2124
Регистрация: 25.3.2002
Где: Москва

Репутация: 2
Всего: 59



Я это как раз и делаю smile. И уже внедрил Hashtable
Код

IExecutor* CExecutorHelper::ExecutorCreate(CMessage* Message, CConnection* Connection)
{
    Type* type = __try_cast<Type*>(m_typesMap->Item[__box(Message->MsgType)]);
    Object* params[] = {Message, Connection };
    return __try_cast<IExecutor*>(type?Activator::CreateInstance(type,params ):NULL);
}


Меня радует, что после обсуждения мы пришли почти к единому знаменателю. А значит можно сказать, что решение с большой долей вероятности работоспособно. В принципе моя цель в этом и заключалась: убедится в применимомти или непригодности решения


--------------------
С уважением, Вячеслав Ермолаев
PM MAIL WWW ICQ   Вверх
arilou
Дата 17.3.2005, 16:18 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Великий МунаБудвин
****


Профиль
Группа: Экс. модератор
Сообщений: 2646
Регистрация: 15.7.2004
Где: город-герой Минск

Репутация: 21
Всего: 61



Ну тогда топик действительно можно закрывать smile


--------------------
user posted imageuser posted image
PM WWW ICQ   Вверх
Ответ в темуСоздание новой темы Создание опроса
Прежде чем создать тему, посмотрите сюда:
mr.DUDA
THandle

Используйте теги [code=csharp][/code] для подсветки кода. Используйтe чекбокс "транслит" если у Вас нет русских шрифтов.
Что делать если Вам помогли, но отблагодарить помощника плюсом в репутацию Вы не можете(не хватает сообщений)? Пишите сюда, или отправляйте репорт. Поставим :)
Так же не забывайте отмечать свой вопрос решенным, если он таковым является :)


Если Вам понравилась атмосфера форума, заходите к нам чаще! С уважением, mr.DUDA, THandle.

 
1 Пользователей читают эту тему (1 Гостей и 0 Скрытых Пользователей)
0 Пользователей:
« Предыдущая тема | Общие вопросы по .NET и C# | Следующая тема »


 




[ Время генерации скрипта: 0.0886 ]   [ Использовано запросов: 21 ]   [ GZIP включён ]


Реклама на сайте     Информационное спонсорство

 
По вопросам размещения рекламы пишите на vladimir(sobaka)vingrad.ru
Отказ от ответственности     Powered by Invision Power Board(R) 1.3 © 2003  IPS, Inc.