указатель метода и наследование // вид стратегии (C ++) - PullRequest
0 голосов
/ 05 августа 2011

В моем дизайне есть класс, который читает информацию из файла. Чтение информации представляет работу (для простоты это целое число, которое является «идентификатором работы»). Класс чтения файлов может принимать объекты, которые могут справиться с такой работой. Теперь моя идея заключалась в том, чтобы сделать интерфейс, например, «IJobHandler», который имеет чисто виртуальную функцию «DoJob ()», а затем вы можете вызвать что-то вроде

FileReader fr;
Class1 c1; // has a base class IAcceptor with virtual method HandleJobId()
Class2 c2; // has a base class IAcceptor with virtual method HandleJobId()
fr.Register(c1);
fr.Register(c2);
fr.doJob(1); // calls c1.HandleJobId()
fr.doJob(2); // class c2.HandleJobId()

Это будет работать нормально. Но что произойдет, если некоторый класс может обрабатывать два или более идентификаторов работы? Но есть только один метод, который этот класс может реализовать (HandleJobId ()). Не будет ли хорошо следующее: fr.Register(c1, c1::Handle_1()) или что-то подобное?

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

class IAcceptable
{
public:
    // interface; implementors should return map of job-ids (int)
    // and a kind of pointer to a method which should be called to
    // handle the job.
    virtual std::map<int, SOME_KIND_OF_FUNCTION_POINTER> GetJobIds() const = 0;
};

class Class12 : public IAcceptable
{
public:
    void Handle_1(){} // method to handle job id 1
    void Handle_2(){} // method to handle job id 2

    virtual std::map<int, SOME_KIND_OF_FUNCTION_POINTER> GetJobIds() const
    {
        std::map<int, SOME_KIND_OF_FUNCTION_POINTER> intToMethodMap;
        // return map, which says: "I can handle job id 1, by calling Handle_1(), so I give you c12 pointer to this method"
        // (same thing for job id 2 and Handle_2())
        intToMethodMap.insert(std::pair<int, SOME_KIND_OF_FUNCTION_POINTER>(1, POINTER_TO_Handle_1);
        intToMethodMap.insert(std::pair<int, SOME_KIND_OF_FUNCTION_POINTER>(2, POINTER_TO_Handle_2);
        return intToMethodMap;
    }
};

class Class34 : public IAcceptable
{
    void Handle_3(){} // method to handle job id 3
    void Handle_4(){} // method to handle job id 4
    virtual std::map<int, SOME_KIND_OF_FUNCTION_POINTER> GetJobIds() const
    {
        std::map<int, SOME_KIND_OF_FUNCTION_POINTER> intToMethodMap;
        // return map, which says: "I can handle job id 3, by calling Handle_3(), so I give you c12 pointer to this method"
        // (same thing for job id 4 and Handle_4())
        intToMethodMap.insert(std::pair<int, SOME_KIND_OF_FUNCTION_POINTER>(3, POINTER_TO_Handle_3);
        intToMethodMap.insert(std::pair<int, SOME_KIND_OF_FUNCTION_POINTER>(4, POINTER_TO_Handle_4);
        return intToMethodMap;
    }
};

class FileReader
{
public:
    // register an IAcceptable
    // and add its handlers to the local list
    void Register(const IAcceptable& acc)
    {
        m_handlers.insert(acc.GetJobIds());
    }

    // if some job is to do, search for the job id and call 
    // the found function
    void doSomeJob(int i)
    {
        std::map<int, SOMEFUNCTION>::iterator specificHandler = m_handlers.find(i);
        // call here (specificHandler->second)()
    }
private:
    std::map<int, SOMEFUNCTION> m_handlers;
};


int main()
{
    Class12 c12;   // can handle job id 1 and 2
    Class34 c34;   // can handle job id 3 and 4

    FileReader fr;
    fr.Register(c12);
    fr.Register(c34);

    fr.doSomeJob(1);  // should lead to this call: c12->Handle_1()
    fr.doSomeJob(2);  // c12->Handle_2();
    fr.doSomeJob(3);  // c34->Handle_3();
    fr.doSomeJob(4);  // c34->Handle_4();
}

Ну, может быть, дизайн - это моя проблема, и кто-то может подсказать, как его улучшить:)

Ответы [ 5 ]

1 голос
/ 05 августа 2011

Вот полный пример:

class IAcceptable; 

class DelegateBase
{
public: 
    virtual void Call() = 0; 
};

template <class Class> class Delegate: public DelegateBase
{
public: 
    typedef void (Class::*Function)(); 
    Delegate(Class* object, Function f): func(f) {}
    virtual void Call() { (object->*func)(); }

private: 
    Class* object; 
    Function func; 
}; 



class IAcceptable
{
public:
    // interface; implementors should return map of job-ids (int)
    // and a kind of pointer to a method which should be called to
    // handle the job.
    virtual std::map<int, DelegateBase*> GetJobIds() = 0;
};

class Class12 : public IAcceptable
{
public:
    void Handle_1(){} // method to handle job id 1
    void Handle_2(){} // method to handle job id 2

    virtual std::map<int, DelegateBase*> GetJobIds()
    {
        std::map<int, DelegateBase*> intToMethodMap;
        // return map, which says: "I can handle job id 1, by calling Handle_1(), so I give you c12 pointer to this method"
        // (same thing for job id 2 and Handle_2())
        intToMethodMap.insert(std::pair<int, DelegateBase*>(1, new Delegate<Class12>(this, &Class12::Handle_1)));
        intToMethodMap.insert(std::pair<int, DelegateBase*>(2, new Delegate<Class12>(this, &Class12::Handle_2)));
        return intToMethodMap;
    }
};

class Class34 : public IAcceptable
{
    void Handle_3(){} // method to handle job id 3
    void Handle_4(){} // method to handle job id 4
    virtual std::map<int, DelegateBase*> GetJobIds()
    {
        std::map<int, DelegateBase*> intToMethodMap;
        // return map, which says: "I can handle job id 3, by calling Handle_3(), so I give you c12 pointer to this method"
        // (same thing for job id 4 and Handle_4())
        intToMethodMap.insert(std::pair<int, DelegateBase*>(3, new Delegate<Class34>(this, &Class34::Handle_3)));
        intToMethodMap.insert(std::pair<int, DelegateBase*>(4, new Delegate<Class34>(this, &Class34::Handle_4)));
        return intToMethodMap;
    }
};

class FileReader
{
public:
    // register an IAcceptable
    // and add its handlers to the local list
    void Register(IAcceptable& acc)
    {
        std::map<int, DelegateBase*> jobIds = acc.GetJobIds(); 
        m_handlers.insert(jobIds.begin(), jobIds.end());
    }

    // if some job is to do, search for the job id and call 
    // the found function
    void doSomeJob(int i)
    {
        std::map<int, DelegateBase*>::iterator specificHandler = m_handlers.find(i);
        specificHandler->second->Call(); 
    }
private:
    std::map<int, DelegateBase*> m_handlers;
};


int _tmain(int argc, _TCHAR* argv[])
{
    Class12 c12;   // can handle job id 1 and 2
    Class34 c34;   // can handle job id 3 and 4

    FileReader fr;
    fr.Register(c12);
    fr.Register(c34);

    fr.doSomeJob(1);  // should lead to this call: c12->Handle_1()
    fr.doSomeJob(2);  // c12->Handle_2();
    fr.doSomeJob(3);  // c34->Handle_3();
    fr.doSomeJob(4);  // c34->Handle_4();

    return 0;
}
  1. Для вызова функции-члена нам нужен объект; поэтому ваши карты должны содержать не просто указатели на методы, а что-то, что может инкапсулировать полный вызов: объект + указатель на метод. Это что-то делегат здесь.

  2. Чтобы удостовериться, что метод вызывается правильно, даже если он определен в подклассе, нам нужно правильно хранить и производный объект, и указатель на метод (без приведения). Таким образом, мы делаем Делегат шаблон с производным классом в качестве его параметра.

  3. Это означает, что делегаты, основанные на методах разных подклассов, несовместимы и не могут быть помещены в карту. Чтобы обойти это, мы вводим общий базовый класс DelegateBase и виртуальную функцию Call (). Call () может быть вызван без знания точного типа хранимого объекта / метода, и он будет отправлен в реализацию с корректным типом. Теперь мы можем хранить указатели DelegateBase * на карте.

Также проверьте boost :: function и boost :: bind, они обеспечивают обобщение для вышеперечисленного, и я думаю, что они также могут быть использованы для ваших целей.

0 голосов
/ 05 августа 2011

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

Для этого пусть каждый обработчик реализует этот интерфейс:

struct Handler
{
  virtual bool canHandle(job_id_t id) const = 0;
  virtual void doJob(job_it_t id) = 0;
};

Чтобы зарегистрировать обработчик, просто сохраните указатель в контейнере:

std::vector<Handler*> handlers;

Затем, если вам нужновыполнить работу, выполнить итерацию контейнера и отправить:

handleJob(job_it_t id)
{
  for (std::vector<Handler*>::iterator it = handlers.begin(), end = handlers.end(); it != end; ++it)
  {
    if ((*it)->canHandle(id))
      (*it)->doJob(id);
  }
}
0 голосов
/ 05 августа 2011

Существует несколько способов решения этой проблемы.

Если у вас есть класс, который может обрабатывать несколько разных заданий в отдельных функциях, простейшим решением будет обернуть его несколькими типами, например:

class JobsOneAndTwo
{
public:
    void doJobOne();
    void doJobTwo();
};

class JobOne : public AbstractJob, JobsOneAndTwo
{
public:
    virtual void doJob() { doJobOne(); }
};

class JobTwo : public AbstractJob, JobOneAndTwo
{
public:
    virtual void doJob() { doJobTwo(); }
};

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

В качестве альтернативы вы можете отправлять начлен данных класса:

class JobOneAndTwo : public AbstractJob
{
    int myJob;
public:
    JobOneAndTwo(int id) : myJob( id ) {}
    void JobOne();
    void JobTwo();
    virtual void doJob()
    {
        switch ( myJob ) {
        case 1:
            JobOne();
            break;

        case 2:
            JobTwo();
            break;
    }
};

В этом случае вы создаете экземпляр класса дважды, каждый раз передавая конструктору разные аргументы.

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

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

0 голосов
/ 05 августа 2011
typedef void (IAccaptable::*SOME_KIND_OF_FUNCTION_POINTER)(); 
...
Register(1, (SOME_KIND_OF_FUNCTION_POINTER)(&Class12::Handle1)); 

Предупреждение: это приведение в стиле C будет работать только с одним наследованием.(Ну, на самом деле приведение будет прекрасно скомпилироваться и с множественным наследованием, но при вызове (derivedObject->*funcPtr)() с funcPtr, который указывает на функцию-член не первого базового класса, он будет вызван без правильно указанного указателя производного объектанастроен так, чтобы указывать на соответствующий подобъект, принадлежащий этой базе, что, скорее всего, приводит к сбою.)

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

class CallerBase
{
public: 
    virtual void Call(Base* object) = 0; 
}; 

template <class Derived>
struct Caller: public CallerBase
{
public: 
    typedef void (Derived::*Function)(); 
    Caller(Function f): func(f) {}
    virtual void Call(Base* object) 
    {
        Derived* derived = static_cast<Derived*>(object); 
        (derived->*func)(); 
    }

private: 
    Function func; 
}; 

Register(1, new Caller<Derived>(&Derived::F)); 

Тогда ваша карта будет содержать указатели CallerBase *, и как только вы найдете подходящего абонента, вы сделаете caller->Call(object).Если object в этом вызове является Derived *, то он будет неявно приведен к Base *, но виртуальная функция Caller<Derived>::Call() преобразует его обратно в Derived * перед фактическим вызовом метода.

0 голосов
/ 05 августа 2011

Указатели на методы могут быть очень забавными.

Я не хочу саморекламировать себя, но ознакомьтесь с моим руководством по ним, которое я написал в школе.

http://nicolong.com/code-examples/menu-object-tutorial

Может немного помочь.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...