这个 C++ 习语是否有一个名称,其中类型出售扩展其接口的包装器?

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

我有一个本质上是相互共享一些共同属性的类型族。我实际上可以用 C++ class 继承相当体面地模拟这种关系。但是,我还需要在我的代码周围传递和存储这些对象,并且将每个实例都保留为多态堆引用是一件很痛苦的事情。

这是初始情况:

具有所有值的枚举类型 "subclasses":

enum class PhoneType {
    landline,
    cell
}

存储和传递的类型 很多:

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 列举了不同可能的 Phone 类型,它们都有一个 PhoneNumber 并且可能有也可能没有 AppList.

问题是一旦调用者确定它正在处理 cell [=76=,如何让外界访问 phone 的 AppList ].请注意,我不想简单地提供可选类型,因为这会将大量错误检查代码推送到调用函数中,这不是我想要的(在大多数情况下,调用者知道 [= Phone 中的 16=] 甚至无需检查就通过了,因此出售 optional<> 只是不必要的痛苦)。

我可以将额外的访问器添加到 Phone class,并记录它们 throw/crash/etc。如果接收 Phone 不代表 cell phone。然而,在实际代码中还有更多这样的属性需要更多的访问器,而这些访问器中的每一个在调用站点读取时都不清楚其前提条件。

长话短说,经过一番考虑,我最终得出了这个成语:

定义前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
};

Phonepublic 部分:

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

Phoneprivate 部分:

friend LandlineCheckedPhoneRef;
friend CellCheckedPhoneRef;

现在很清楚在任何给定的调用站点做出的假设是什么:如果我说 phone.cell_ref() 那么我清楚地断言这个 phonecell phone,例如,

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

(愚蠢的例子,但你明白了。我知道我可以在这里使用访问模式,但真正的代码并不完全像这样。)

我喜欢我正在做的这个设计。问题是,我不太清楚如何命名出售的包装器类型。我目前正在使用 LandlinePhoneLensCellPhoneLens 等模式,但我知道 "lens" 在编程中已经具有其他含义。也许这不是什么大问题,但我想问一下以确保我没有错过更成熟的命名方案。

是否有此 pattern/idiom 的既定名称,其中类型出售扩展其接口的包装器?

很遗憾,我并不完全清楚您的意图。

起初,我以为您只是重新发明了 decorator pattern,您可以在其中向现有对象动态添加一些职责(访问器)。

但仔细观察,我认为这一切看起来都像是一个 反模式、一个依赖混乱和一个有缺陷的设计:每一次在基础 class 你需要成为 derived class 的朋友,你应该开始敲响警钟了。

与其寻找名称,不如设计一个更简洁的设计。忘记枚举,去寻找一个抽象的 Phone 基础 class,抽象函数对应于每个 phone 应该能够做的事情。然后创建两个派生的具体 classes:LandLineCellPhone,它们都继承自 Phone

现在您可以考虑获取应用程序列表是所有类型 phone 的通用功能,而 LandLine 只是 returns 一个空列表。然后,您所有的代码都将只使用内置多态性以适当的可扩展方式完成工作:

  • 如果明天有人发明一个TelepathyPhone,你只需要实现抽象接口需要的常用功能,所有的使用代码仍然可以正常工作。
  • 在最坏的情况下,如果您真的需要调用一个非常具体的 class 依赖函数,而该函数在公共接口(例如 TelepathyPhone::displayBrainWavelength())中是完全未知的,您可以使用if 使用 dynamic_cast。至少你会避免每次发明一个新的派生 class 时都创建一个新的枚举。