Exposing functionality in C++ parent class to children classes through proxies

Hi Community,

Today’s post is about a common scenario we might face when building software. Sometimes, we have a parent class which contains a child member or property that requires to trigger or execute code that exist in the parent. It is a common practice to pass a reference to the container or parent, even when this solution works it requires us to store a reference to a complete object, even when we just need to execute something very specific. This also opens another problem, around circular references because both classes (Parent and child) require one another, as a good example: A toolbar in a Window, the toolbar must have a reference to the parent (Window) but also the parent must reference the child (The toolbar as a member of the Controls or Children collection, for instance). In either case, we might end up passing a reference to a complete object exposing functionality that should not be available from the child, in other words, we’re passing more than required. In that case, what can we do?

As the third article of this series on GTK+ and Linux development with C++ and SQL Server vNext, we aim to describe and explain another approach to accomplish what’s been mentioned above. In C++ we can do forward declaration and it works perfect, to the untrained eye this might feel or see “a bit weird” specially when an application comprises different components and they have been structured and stored in different files. In order to accomplish what we need that’s the “ability to run code in the parent, and passing just a reference to the minimum functionality required in parent to be called from child”, we must consider a proxy that points to the code in the parent, let’s take a look at the code snippet below

template <typename T>

class ParentStub {

public:

    std::function<void(void)> FetchData;

    std::function<void(bool)> NotifyParent;

    std::function<void(bool)> EnableFieldsinParent;

    std::function<void(UiFieldOperation op, T& contact)> ParentUiOperation;

 

    ParentStub() {    }

 

    ParentStub(std::function<void(bool)> notifyParent,

               std::function<void(bool)> enableFieldsinParent,

               std::function<void(UiFieldOperation op, T& contact)> parentUiOperation,

               std::function<void(void)> fetchData) {

        FetchData = fetchData;

        NotifyParent = notifyParent;

        ParentUiOperation = parentUiOperation;

        EnableFieldsinParent = enableFieldsinParent;

    }

};

It’s a class template that takes as argument the entity type (model in MVC) and it only has the methods that apply to it in the parent. The class’ name should have been “ParentProxy” instead of “ParentStub”, I chose the latter, however because its functionality is closer to a stub’s.  C++11 introduced std :: function which it’s the counterpart of Action and Func delegates in .NET. Hence, we can pass a reference to a method in the parent in the form of a struct that holds references to specific methods only. The child class takes and store this proxy to the parent in its constructor, as depicted next

 

/////////////////////////////////////////////////////////////////

// Child's constructor

/////////////////////////////////////////////////////////////////

 

DataContext::DataContext(GtkApplication& app, ParentStub<Contact> parent) {

    application = &app;

    Parent = parent;

    DataAccess.ConnectionString_set("DSN=Contacts;UID=sa;PWD=p@ssw0rd;DATABASE=GtkDemo;");

}

 

/////////////////////////////////////////////////////////////////

// Child's method calling method in parent through proxy

/////////////////////////////////////////////////////////////////

 

void DataContext::UpdateContextPostDbOperation(const Contact& contact, DatabaseOperation operation) {

    if (!contact.IsEmpty()) {

        auto index = 0;

        auto found = std::find_if(Rows_get().begin(), Rows_get().end(), [&](Contact &c) {

            index++;

            return (c.ContactId == contact.ContactId);

        });

 

        if (operation == DatabaseOperation::Create) {

            Navigate(NavigationDirection::Last);

        } else if (operation == DatabaseOperation::Delete) {

            if (found != Rows_get().end())

                Navigate(NavigationDirection::Previous, true, [&]{Rows_get().erase(found);});

        } else if (operation == DatabaseOperation::Update) {

            if (found != Rows_get().end()) {

                index--;

                Rows_get().at(index).Email = contact.Email;

                Rows_get().at(index).LastName = contact.LastName;

                Rows_get().at(index).FirstName = contact.FirstName;

                Rows_get().at(index).PhoneNumber = contact.PhoneNumber;

            }

            Parent.NotifyParent(true); //Let's notify parent of changes made to DataContext

         }

     }

}     

 

/////////////////////////////////////////////////////////////////////////////////////

// Method responsible for initializing app and passing parent's proxy to child object

////////////////////////////////////////////////////////////////////////////////////

 

void MainWindowController::StartUp(GtkApplication &app, gpointer user_data) {

    application = &app;

    GError *err = nullptr;

    self->builder = gtk_builder_new();

    gtk_builder_add_from_file(self->builder, "./../../ui/SqlTestHarness.glade", &err);

 

    if (err) {

        g_error(err->message);

        g_error_free(err);

        throw std::runtime_error("Unable to load UI. Please ensure glade file exists.");

    }

 

    self->SetUpUI();

    self->ConnectSignals();

    g_object_unref (G_OBJECT (self->builder));

    gtk_application_add_window(&app, (GtkWindow*) self->Controls.at("frmMain"));

    gtk_widget_show ((GtkWidget*) (GtkWindow*) self->Controls.at("frmMain"));

 

    // Here we pass the proxy to parent's methods to child object (in this case DataContext)

    self->Context = DataContext(app, ParentStub<Contact>([&](bool ignoreFields){self->Refresh(ignoreFields);},

                                                [&](bool controlState) {self->EnableOrDisableFieldsBasedOnMode(controlState);},

                                                [&](UiFieldOperation op, Contact& c){self->ReadOrWriteToFieldsOnUi(op, c);},

                                                [&] {self->FetchDataset();}));

 

    self->Context.CanEdit_set(false);

 

    self->FetchDataset();

 

    g_timeout_add_seconds(1, [&](gpointer data)->gboolean {self->UpdateStatusBar(data);}, self->Controls.at("sbrMain") );

}

 

 

In doing this, we’re passing the bare minimum functionality in the parent that’s required by the child, also we’re passing just the methods we’re interested in instead of passing the whole parent object.

Regards,

Angel

Leave a Reply

Your email address will not be published. Required fields are marked *