【问题标题】:Design Pattern in Lippman's C++ PrimerLippman C++ Primer 中的设计模式
【发布时间】:2016-07-21 23:37:22
【问题描述】:

我对编程还很陌生,所以如果答案很明显,请耐心等待我的问题。

在 Lippman、Lajoie 和 Moo 撰写的 C++ Primer 第 5 版的第 15.9 节中,该部分涉及对继承层次结构的编程。它基本上是一个抽象类Query_base,其子类是WordQueryNotQueryBinaryQuery,其中最后一个进一步分支为AndQueryOrQueryQuery_base 层次结构由名为Query 的接口类通过指针访问。此设置背后的基本思想是使用如下复合查询:

Query q = Query("fiery") & Query("bird") | Query("wind")

(这些运算符生成指向Query_base 类型对象的Query 对象,例如,“&”生成一个AndQuery,“~”生成一个NotQuery

然后可以在文本文档中搜索符合查询规则的行。)

Query_base 类如下:

class Query_base {
    friend class Query;
protected:
    virtual ~Query_base() = default;
private:
    virtual QueryResult eval(const TextQuery&) const = 0;
    virtual std::string rep() const = 0;
}

所有 Query_base 子级都实现了 eval() 和 rep(),因此是可实例化的。

Query类设计如下:

class Query {
    friend Query operator~(const Query&);
    friend Query operator|(const Query&, const Query&);
    friend Query operator&(const Query&, const Query&);
public:
    Query(const std::string&);
    QueryResult eval(const TextQuery &t) const { return q->eval(t); }
    std::string rep() const { return q->rep(); }
private:
    Query(std::shared_ptr<Query_base> query) : q(query) { }
    std::shared_ptr<Query_base> q;

所以我的理解是设计有一个继承层次结构,然后是一个故意相互分离的接口。后者从侧面访问前者,而不是仅仅将基类用作接口。

我有两个问题:

  1. 没有单独的Query 类有什么问题吗?我们可以只让Query_base 类作为层次结构和接口的基础,而去掉Query 类吗?我承认正如Query_base 所代表的那样,它不能,因为它具有纯虚函数,但这些可以更改。例如,我不能将eval()rep()Query 函数实现移到Query_base 中吗?这样做有什么问题吗? Query 中的 shared_ptr&lt;Query_base&gt; 是否使事情复杂化并且是分离的基础,如果是,我们可以解决这个问题吗?
  2. 这样的层次结构-界面分离有多普遍(如果这是正确的思考方式)?

提前致谢!

【问题讨论】:

  • 无继承,Query_base不是Query的基类。尽管有(糟糕的)名称,但这些类之间根本没有关系。否则,friend 关键字是必要的原因。在书中查找“封装”。
  • 明确一点:我知道Query 根本不是Query_base 继承层次结构的一部分。这就是为什么,正如您所说,friend QueryQuery_base 类中是必需的。事实上,QueryQuery_base 之间的这种分离正是我的问题所在。

标签: c++ inheritance interface


【解决方案1】:

除了上面的cmets,我觉得这个设计也和c++中的多态有关,只支持引用和指针,对象本身不支持。

通过使用一个没有层次结构的“Query”对象和一个指向具有层次结构和多态性的Query_Base的嵌入式智能指针,该类的用户可以使用Query对象来实现多态性的效果。

【讨论】:

    【解决方案2】:

    如果我们继续实现所有这些运算符和其他子查询类型,分离的必要性就会变得清晰。例如,让我们想象一下 & 运算符的样子。

    &amp; 运算符应构造一个查询,该查询又应包含另外两个查询。 所以,AndQuery 类可以这样定义:

    class AndQuery: public BinaryQuery {
    public:
         AndQuery(const Query_base& lhs, const Query_base& rhs);
         AndQuery(Query_base&& lhs, Query_base&& rhs);
         ...
         QueryResult eval(const TextQuery& t) override;
    private:
         // required members and private methods...
         // two principal members, lhsQuery and rhsQuery for
         // left hand side subquery and light hand side subquery
         // correspondingly are defined in `BinaryQuery`
    };
    

    并且,在一个简单的未优化案例中,AndQuery 应该以某种方式执行搜索 (eval()):

    QueryResult AndQuery::eval(const TextQuery& t) {
        QueryResult lhsResult = lhsQuery.eval(t);
        QueryResult rhsResult = rhsQeury.eval(t);
        // the resulting ResultSet should include only entries
        // from both result sets
        for(auto it = lhsResult.begin(); it != lhsResult.end(); ++it)
            if (rhsResult.find(*it) == rhsResult::cend())
                it = lhsResult.erase(it);
        // now we can return lhsResult because we have erased all elements from it 
        // which weren't in rhsResult
        return lhsResult;
    }
    

    其他查询组件子类有自己特定的实现。反过来,Query 有自己的一组特定方法,例如从字符串构造查询。如果没有纯虚拟Query_base,我们必须与所有组件子类共享Query 的实现细节。我们还必须让这些类对外部用户可见。

    通过使用单独的“接口”Query 类,我们为子系统的用户提供了一个清晰易懂的精益接口。

    【讨论】:

    • 感谢您的回复。至于我的第二个问题,使用侧接口类让子系统的用户访问类层次结构是否常见?
    • 好吧,如果有一个不暴露于外部世界的大型“后院”机器,那么使用覆盖类或覆盖函数(如果可能的话)是很常见的。当然,这可能会延长项目的设计阶段,因为您需要更仔细地规划您的类层次结构。但是,它有明显的好处。例如。它将您的类的可能用途减少到仅“有效”的预期情况,这简化了对您的实现的进一步修改。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-07-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-09-15
    • 2011-01-07
    • 1970-01-01
    相关资源
    最近更新 更多