c++ - 如何处理与派生类不兼容的基类方法?
问题描述
想象一下,您正在制作一个 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
直到它们被显式公开。
所以我的问题是:在创建与基类具有“是”关系但仅与其方法部分兼容的新类时,我应该使用哪种模式?
谢谢
解决方案
让我们退后一步,再看看你的设计:
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
.
推荐阅读
- css - 如何在 Vue.js 中添加条件样式?
- javascript - 'v-html' 在构建时是否会删除一些 css 属性?
- django - 如何在单元测试期间模拟另一个模块内的方法
- javascript - 如何查看分配给以太坊 Solidity 语言中变量的值?
- c - 怎么了?运行 C 代码时出现运行时错误
- ruby-on-rails - RSpec:主页的集成测试
- php - 缓慢的 x-cart 购物车页面
- angular - 如何通过get请求将mongodb的数据作为数组存储在本地存储中
- clang - 防止 ArmClang 添加对标准 C 库的调用
- angular - [Angular][Store] 将服务注入另一个服务的问题