首页 > 解决方案 > 如何处理与派生类不兼容的基类方法?

问题描述

想象一下,您正在制作一个 GUI,并且有一个DataViewList类是一个显示数据行的小部件(例如,像这样)。你有方法AddRow(std::vector<std::string> row),DeleteRow(std::vector<std::string> row)AddColumn(std::string name), DeleteColumn(std::string name)

现在假设您要创建一个显示音乐播放列表的新类。它具有预先确定的列(标题、专辑、年份),您不想添加任何新列或删除现有列。此外,您希望能够Song在单个方法调用中将对象添加到列表中,因此您需要一个可以做到这一点的方法。你如何实现这样一个类?

最基本的想法是只创建一个MusicPlaylsit继承公共形式的新类,DataViewList并在构造函数中添加预定的列。然后重载AddRow方法,使其接受Song对象作为参数。这种方法有一个很大的问题:有人可能调用MusicPlaylist::AddColumn或其他与类的逻辑不兼容的方法MusicPlaylist。由于 MusicPlaylist 应该只有三个预定义的列,因此不应该有添加或删除列的方法(或访问任何其他不兼容的基类方法,例如基类非重载 AddRow 方法)。

为了解决这个问题,我可以使用组合而不是继承,并重新实现我可能想要使用的任何方法。在我看来,这是一个坏主意,因为如果我将来想更改某些内容,这比继承更困难,因为我无法覆盖基类方法。

另一种选择是继承为protected. 这可以防止使用不兼容的基类方法,并允许在未来继承的情况下进行覆盖。问题是,现在我必须明确声明我想使用的每个方法都作为 public with using,这似乎违背了面向对象编程的全部要点(能够在深层基类中更改某些内容并且仍然有任何继承的类是能够使用它)因为新添加的公共方法在其中或任何继承自它的类中DataViewList都不可见,MusicPlaylist直到它们被显式公开。

所以我的问题是:在创建与基类具有“是”关系但仅与其方法部分兼容的新类时,我应该使用哪种模式?

谢谢

标签: c++oopinheritancedesign-patterns

解决方案


让我们退后一步,再看看你的设计:

class DataViewList {
    using Col = std::string;
    using Row = std::vector<std::string>;
    virtual void addRow(Row) = 0;
    virtual void deleteRow(Row) = 0;
    virtual void addColumn(Col) = 0;
    virtual void deleteColumn(Col) = 0;
    virtual void draw(Context) = 0;   // not in your question, but inferred
};

你想做的是:

class MusicPlaylist : public DataViewList { /* ... */ }

你发现这行不通,因为MusicPlaylist不履行DataViewList. 让我们回顾一下。的职责是DataViewList什么?

  • 它绘制。
  • 它提供了一个项目列表。
  • 它会修改该项目列表。

这是3个责任。这违反了 2 条SOLID 原则

  • 一个类必须只有一个职责(Single-responsibility)。
  • 不得强迫消费者依赖他们不使用的东西(接口隔离)。

这就是你有问题的原因:MusicPlaylist只关心其中的两个职责,绘制和维护一个项目列表。它维护字段列表,因此不能继承DataViewList

怎么修?

分担责任。

// An interface for a data grid. Only provides a read-only view. No drawing.
struct DataList {
    virtual const std::vector<Column>& getColumns() const = 0;
    virtual const std::vector<Row>& getRows() const = 0;
protected:
    ~DataList(); // or make it public virtual to be able to delete a DataList*
};

// The widget that draws data.
class DataViewList : public Widget {
public:
    virtual void setData(const DataList&); //could be a pointer, YMMV
    virtual void draw(Context);
};

注意如何:

  • DataViewList不再包含任何数据,而是引用其他包含数据的对象。
  • 由于它只显示数据,它依赖于一个只包含读取功能的简单界面。

此时你可以简单地做一个:

// Contains music data - does not draw it
class MusicPlaylist : public DataList {
    void addSong(Song);
    void deleteSong(Song);
    const std::vector<Column>& getColumns() const override;
    const std::vector<Row>& getRows() const override;
};

// Contains more complex data with configurable columns - does not draw it
class SomeMoreComplexList : public DataList {
    void addColumn(Col);
    void deleteColumn(Col);
    void addRow(Row);
    void deleteRow(Row);
    const std::vector<Column>& getColumns() const override;
    const std::vector<Row>& getRows() const override;
};

也就是说,您的列表实现了小部件显示它们所需的界面,以及它们需要的任何特定功能。然后,您可以将它们提供给小部件的setData.


推荐阅读