mardi 20 février 2018

Minimizing the amount of header files needed using the Builder/Fluent pattern

I am experimenting with the Builder/Fluent style of creating objects trying to extend some ideas presented in a course. One element I immediately didn't like with my test implementation was the large number of additional header files the client needs to include for the process to work, particularly when I wish to make use of public/private headers via the pImpl idiom for purposes of providing a library interface. I'm not entirely certain whether the problem lies with my implementation or I'm just missing an obvious 'last step' to achieve what I want.

The general gist is as follows (using the toy example of Pilots):

Firstly the client code itself:
(Note: for brevity, various boilerplate and irrelevant code has been omitted)

Pilot p = Pilot::create()
        .works().atAirline("Sun Air").withRank("Captain")
        .lives().atAddress("123 Street").inCity("London")

What's happening here is:

  • In Pilot.h, the Pilot class is defined with a static member method called create() that returns an instance of a PilotBuilder class defined in PilotBuilder.h and forward declared in Pilot.h
  • Essentially the PilotBuilder class is a convenience builder only used to present builders of the two different facets of a Pilot (.works() and .lives()), letting you switch from one builder to another.

Pilot.h:

class PilotBuilder;
class Pilot {
private:
    // Professional
    string airline_name_, rank_;    

    // Personal
    string street_address_, city_;

    Pilot(){}
public:
    Pilot(Pilot&& other) noexcept;

    static PilotBuilder create();

    friend class PilotBuilder;
    friend class PilotProfessionalBuilder;
    friend class PilotPersonalBuilder;
};

Pilot.cpp:

#include "PilotBuilder.h"

    PilotBuilder Pilot::create() {

        return PilotBuilder();
    }

    // Other definitions etc

PilotBuilder.h

#include "public/includes/path/Pilot.h"

class PilotProfessionalBuilder;
class PilotPersonalBuilder;

class PilotBuilder {    
private:
    Pilot p;
protected:
    Pilot& pilot_;

    explicit PilotBuilder(Pilot& pilot) : pilot_{pilot} {};

public:
    PilotBuilder() : pilot_{p} {}

    operator Pilot() {
        return std::move(pilot_);
    }

    PilotProfessionalBuilder works();
    PilotPersonalBuilder lives();
};

PilotBuilder.cpp

#include "PilotBuilder.h"
#include "PilotProfessionalBuilder.h"
#include "PilotPersonalBuilder.h"

PilotPersonalBuilder PilotBuilder::lives() {
   return PilotPersonalBuilder{pilot_};
}

PilotProfessionalBuilder PilotBuilder::works() {
    return PilotProfessionalBuilder{pilot_};
}

As you can imagine the PilotProfessionalBuilder class and the PilotPersonalBuilder class simply implement the methods relevant to that particular facet eg(.atAirline()) in the fluent style using the reference provided by the PilotBuilder class, and their implementation isn't relevant to my query.
Avoiding the slightly contentious issue of providing references to private members, my dilemma is that to make use of my pattern as it stands, the client has to look like this:

#include "public/includes/path/Pilot.h"
#include "private/includes/path/PilotBuilder.h"
#include "private/includes/path/PilotProfessionalBuilder.h"
#include "private/includes/path/PilotPersonalBuilder.h"

int main() {

    Pilot p = Pilot::create()
            .works().atAirline("Sun Air").withRank("Captain")
            .lives().atAddress("123 Street").inCity("London");
}

What I cannot figure out is:

  1. How do I reorder or reimplement the code so that I can simply use #include "public/includes/path/Pilot.h" in the client, imagining say, that I'm linking against a Pilots library where the rest of the implementation resides and still keep the same behaviour?
  2. Provided someone can enlighten me on point 1., is there any way it would be then possible to move the private members of Pilot into a unique_ptr<Impl> pImpl and still keep hold of the static create() method? - because the following is obviously not allowed:

:

PilotBuilder Pilot::create() {
    pImpl = make_unique(Impl); /* struct containing private members */
    return PilotBuilder();
}

Finally, I am by no means an expert at any of this so if any of my terminology is incorrect or coding practices really need fixing I will gladly receive any advice people have to give. Thank you!

Aucun commentaire:

Enregistrer un commentaire