CodeGuru Home VC++ / MFC / C++ .NET / C# Visual Basic VB Forums Developer.com
Results 1 to 1 of 1
  1. #1
    Join Date
    Apr 1999
    Location
    Altrincham, England
    Posts
    4,470

    C++ General: How to minimise mutual dependencies?

    Q: How to minimise mutual dependencies?

    A: A frequently raised problem on CodeGuru concerns the situation where two classes are mutually dependent: A needs to refer to B whilst, at the same time, B needs to refer to A. The simple resolution of this involves forward declarations:


    Code:
    //--------------------------------
    // A.h
    
    class B;
    
    class A
    {
    public:
    	A();
    	// ...
    private:
    	B* b;
    };
    
    //--------------------------------
    // B.h
    
    class A;
    
    class B
    {
    public:
    	B(A*);
    	// ...
    private:
    	A* a;
    };
    
    //--------------------------------
    // A.cpp
    
    #include "A.h"
    #include "B.h"
    
    A::A() : b(new B(this))
    {
    }
    
    // more definitions...
    
    //--------------------------------
    // B.cpp
    
    #include "B.h"
    #include "A.h"
    
    B::B(A* par) : a(par)
    {
    }
    // definitions...
    While this works, it does introduce some tight, and unwanted, coupling between the two classes. This means that changes are hard to make because of the explicit dependence between the two classes. Suppose, for example, that the needs of the program change and you now need a version that has to use class C in place of class A - but the original code must still be maintained. In one version, B needs an A* member, but in the other it needs a C*. This sort of thing usually ends up with code littered with conditional compilation sections to cater for the various builds.

    It's always a good idea to minimise the coupling between any two entities, so how can we go about improving the mutual dependency situation? The key, as is often the case, is abstraction. We take one of the two dependencies (say B -> A) and abstract it. That is, we look at what B wants to "say" to A, and make an abstract class that defines those functions. we supply this abstract class with the definition of B, and make B dependent on the abstract class:


    Code:
    // B.h
    
    class Bcallback
    {
    public:
    	// pure virtual functions
    };
    
    class B
    {
    public:
    	B(Bcallback*);
    	// ...
    private:
    	Bcallback* bc;
    };
    To complete the connection, A now derives from Bcallback and implements the functions that it defines, allowing B to "talk" to it.


    Code:
    // A.h
    
    #include "B.h"
    
    class A : public Bcallback
    {
    public:
    	A();
    	// implement Bcallback functions
    private:
    	B* b;
    };
    It's now a simple matter to replace A with C by deriving C from Bcallback as well (you might only need to link two different object files to create the two different versions), since there is no dependency from B to A to complicate matters.

    But now, suppose we need to replace B with D: there's still the A/C -> B dependency to cope with. The solution's the same. Abstract out of B the important functions and make an abstract class of them. We keep this abstraction together with the callback interface, and our original B now becomes an implementation of that interface (and is not visible to A). This does introduce the need for some some sort of factory function to create the correct concrete derivative of B, however. This can be either a free function or a static member of B, which is provided along with the implementation of the concrete derivative.


    Code:
    // B.h
    
    class Bcallback
    {
    public:
    	// Pure virtuals as before
    };
    
    class B
    {
    public:
    	static B* Create(Bcallback* par);
    	// Pure virtuals
    };
    
    //----------------------------------------------------------
    
    // BImpl.h
    #include "B.h"
    class BImpl : public B
    {
    public:
    	BImpl(Bcallback* par);
    	// Implementation
    	
    private:
    	Bcallback* b;
    };
    
    //----------------------------------------------------------
    
    //BImpl.cpp
    #include "BImpl.h"
    
    // Note: definition of static member defined in B.h
    B* B::Create(Bcallback par)
    {
    	return new BImpl(par);
    }
    
    BImpl::BImpl(Bcallback* par) : b(par)
    {
    }
    So, in the same way as we shielded A/C from B by hiding it between a layer of abstraction, we have now hidden B(Impl)/D from A/C. A/C can be freely mixed with BImpl/D without the need to introduce conditional compilation sections.

    Note: both halves of the abstracted two-way communication are supplied together. Most cases of this sort of dependency result from a client-server relationship between the classes: one class is a client - it uses a service provided by the other. The server class has a need to report progress back to the client at intervals that can't be represented by simple function returns. It's important that the server half of the relationship defines the two abstractions - it's the one providing the service, so it calls the shots.

    Note also that we could arrange for BImpl (and D) to accept multiple different instances of classes derived from Bcallback and to send the same message to all of them. And that's called the Observer pattern.



    Last edited by Andreas Masur; April 14th, 2006 at 06:26 AM.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  





Click Here to Expand Forum to Full Width

Featured