lundi 28 janvier 2019

Is there a name for this C++ idiom in which a type vends a wrapper that expands its interface?

I have what is essentially a family of types that share a few common properties with each other. I could actually model this relationship fairly decently with C++ class inheritance. However, I also need to pass and store these objects all around my code, and keeping every instance as a polymorphic heap reference is a pain.

Here's the initial situation:

Enumeration type with values for all "subclasses":

enum class PhoneType {
    landline,
    cell
}

Type that's stored and passed around a lot:

class Phone {
public:
    static Phone landline(PhoneNumber number) {
        return Phone(PhoneType::landline, number);
    }
    static Phone cell(PhoneNumber number, optional<AppList> apps) {
        return Phone(PhoneType::cell, number, apps)
    }

    PhoneType type() { return _type; }
    PhoneNumber number() { return _number; }

private:
    PhoneType _type;
    PhoneNumber _number;
    optional<AppList> _apps;

    Phone(PhoneType type, PhoneNumber number) :
        _type(type), _number(number)
    {}
    Phone(PhoneType type, PhoneNumber number, optional<AppList> apps) :
        _type(type), _number(number), _apps(apps)
    {}
};

PhoneType enumerates different possible types of Phones, which all have a PhoneNumber and may or may not have an AppList.

The issue is how to go about giving the outside world access to a phone's AppList once the caller is sure that it's dealing with a cell phone. Note that I don't want to simply vend the optional type, as this pushes a lot of error checking code into the calling function(s), which is not what I want (in the majority of cases, the caller knows the PhoneType of the Phone it's being passed without even having to check, so vending an optional<> is just unnecessary pain).

I could just add the extra accessors to the Phone class, and document that they throw/crash/etc. if the receiving Phone doesn't represent a cell phone. However, in the real code there are many more such attributes that would require more accessors, and each of these accessors is not at all clear about its preconditions when read at a call site.

Long story short, after a bit of consideration I ended up with this idiom:

Before the definition of Phone:

class CheckedPhoneRef {
public:
    CheckedPhoneRef() = delete;

    Phone& phone() const { return * _phone; }

protected:
    Phone* _phone;
    CheckedPhoneRef(Phone* phone) : _phone(phone) {}

private:
    friend class Phone;
};
class LandlineCheckedPhoneRef : public CheckedPhoneRef {
public:
    using CheckedPhoneRef::CheckedPhoneRef;
};
class CellCheckedPhoneRef : public CheckedPhoneRef {
public:
    using CheckedPhoneRef::CheckedPhoneRef;
    AppList apps() const; // accesses private member of referenced Phone
};

In Phone's public section:

// (Comment above declarations in header):
// These assert that this Phone is of the correct PhoneType.
LandlineCheckedPhoneRef landline_ref() {
    assert(_type == PhoneType::landline);
    return LandlineCheckedPhoneRef(this);
}
CellCheckedPhoneRef cell_ref() {
    assert(_type == PhoneType::cell);
    return CellCheckedPhoneRef(this);
}
// (Plus const versions)

In Phone's private section:

friend LandlineCheckedPhoneRef;
friend CellCheckedPhoneRef;

Now it is rather clear what assumptions are being made at any given call site: if I say phone.cell_ref() then I'm clearly asserting that this phone is a cell phone, e.g.,

void call(Phone& phone) {
    if (phone.type() == PhoneType::cell) {
        if (has_facetime(phone.cell_ref())) ...
    } else {
        ...
    }
}

bool has_facetime(CellCheckedPhoneRef cell_phone) {
    return cell_phone.apps() ...
}

(Dumb example, but you get the point. I know I could use a visitation pattern here, but the real code isn't quite like this.)

I like this design for what I'm doing. Problem is, I don't quite know what to name the vended wrapper types. I'm currently using the pattern of LandlinePhoneLens, CellPhoneLens, etc., but I know that "lens" already has other meaning in programming. Perhaps this isn't a big issue, but I wanted to ask to be sure I'm not missing a more established naming scheme.

Is there an established name for this pattern/idiom in which a type vends a wrapper that expands its interface?

Aucun commentaire:

Enregistrer un commentaire