Search code examples
c++inheritancestrategy-pattern

method pointer and inheritance // kind of strategy pattern (C++)


In my design, there is a class which reads information from file. The read info represents a job (for simplicity, it's an integer, which is "job id"). The file reader class can accept objects which can handle such a job. Now my idea was, to make an Interface, e.g. "IJobHandler" which has a pure virtual function "DoJob()" and then you can call something like

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()

This would work fine. But what happens, if some class can handle two or more job ids? But there is only one method which this class can implement (HandleJobId()). Wouldn't the following be nice: fr.Register(c1, c1::Handle_1()) or something like that?

Maybe my intention is not very clear right now. But you will se it on the bigger code example below. Sorry for the big code block, but I don't know how to explain it that exactly...

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();
}

Well, maybe the design is my problem and someone can give me a hint how to make it better :)


Solution

  • Here's a complete example:

    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. To call a member function we need an object; so your maps should contain not simply method pointers, but something that can encapsulate a complete call: an object + a method pointer. That something is Delegate here.

    2. To make sure that the method is called correctly even if it's defined in a subclass, we need to store both the derived object and the method pointer type-correctly (no casting). So we make Delegate a template, with the derived class as its parameter.

    3. This means that delegates based on methods of different subclasses are incompatible, and cannot be put into a map. To work around this we introduce a common base class, DelegateBase, and the virtual function Call(). Call() can be called without knowing the exact type of stored object / method, and it will be dispatched to a type-correct implementation. Now we can store DelegateBase* pointers in the map.

    Also check out boost::function and boost::bind, they provide a generalization for the above, and I think they could also be used to your purposes.