- mongodb - 在 MongoDB mapreduce 中,如何展平值对象?
- javascript - 对象传播与 Object.assign
- html - 输入类型 ="submit"Vs 按钮标签它们可以互换吗?
- sql - 使用 MongoDB 而不是 MS SQL Server 的优缺点
Sean Parent 的演讲,Inheritance is the base class of evil , 表示多态性不是类型的属性,而是如何使用它的属性。作为一个经验法则,不要使用继承来实现接口(interface)。这样做的许多好处之一是类的去虚拟化,这些类仅仅因为它们实现了一个接口(interface)而具有虚函数。这是一个例子:
class Drawable
{
public:
virtual void draw() = 0;
};
class DrawA : public Drawable
{
public:
void draw() override{//do something}
};
class UseDrawable
{
public:
void do(){mDraw->draw();}
Drawable* mDraw;
};
这里,而不是 UseDrawable
需要mDraw
成为 Drawable*
,你可以让它使用一个类型删除的类,它可以包裹任何实现名为 draw
的成员的类.所以,像 boost::type_erasure::any
具有适当的定义。那样,DrawA
不需要从 Drawable
继承- 多态性真的是UseDrawable
s 要求,而不是 DrawA
的真正属性.
我正在尝试按照这个原则重构一些代码。我有一个抽象类 ModelInterface
和两个具体的类ModelA
和 ModelB
继承自 ModelInterface
.按照 Sean 的建议,不强制 ModelA
是有意义的。和 ModelB
进入继承层次结构,并在需要满足 ModelInterface
建模的概念的类的位置简单地使用类型删除.
现在,我的问题是我的代码中的大多数地方当前使用 ModelInterface
也可以通过基于运行时配置文件构造适当的对象来做到这一点。目前,工厂将new
一个合适的对象并返回一个 ModelInterface*
.如果我重构代码以在代码中的这些位置使用类型删除的概念(比如 boost::type_erasure::any<implement ModelInterface>
),我如何在运行时构造这样的对象?将ModelA
和 ModelB
还需要启用 RTTI 的类(class)吗?或者我可以在没有 RTTI 信息的情况下以某种方式在工厂构建和使用它们吗?
(使用 RTTI,我可以拥有一个抽象类,例如 FactoryConstructible
,并使用 dynamic_cast<void*>
来获得最终类型。)
最佳答案
键入删除 101:
第 1 步:制作隐藏细节的常规(或半常规移动)类型。
struct exposed_type;
这个类公开了你想要支持的概念。复制、移动、销毁、等于、总顺序、哈希和/或您需要支持的任何自定义概念。
struct exposed_type {
exposed_type(exposed_type const&);
exposed_type(exposed_type&&);
friend bool operator<(exposed_type const&, exposed_type const&);
friend std::size_t hash(exposed_type const&);
// etc
};
其中许多概念可以从您当前基于继承的解决方案中的纯虚拟接口(interface)方法粗略映射。
在表达概念的 Regular 类型中创建非虚拟方法。复制/分配复制等。
第 2 步:编写类型删除助手。
struct internal_interface;
这里有纯虚拟接口(interface)。 clone()
用于复制等
struct internal_interface {
virtual ~internal_interface() {}
virtual internal_interface* clone() const = 0;
virtual int cmp( internal_interface const& o ) const = 0;
virtual std::size_t get_hash() const = 0;
// etc
virtual std::type_info const* my_type_info() const = 0;
};
在上面的常规类型中存储一个智能指针1。
struct exposed_type {
std::unique_ptr<internal_interface> upImpl;
将常规方法转发给助手。例如:
exposed_type::exposed_type( exposed_type const& o ):
upImpl( o.upImpl?o.upImpl->clone():nullptr )
{}
exposed_type::exposed_type( exposed_type&& o )=default;
第 3 步:编写类型删除实现。这是一个template
类,它存储一个T
并从helper 继承,并将接口(interface)转发给T
。如果没有找到 adl 自由函数,则使用在默认实现中使用方法的自由函数(有点像 std::begin
)。
// used if ADL does not find a hash:
template<class T>
std::size_t hash( T const& t ) {
return std::hash<T>{}(t);
}
template<class T>
struct internal_impl:internal_interface {
T t;
virtual ~internal_impl() {}
virtual internal_impl* clone() const {
return new internal_impl{t};
}
virtual int cmp( internal_interface const& o ) const {
if (auto* po = dynamic_cast<internal_interface const*>(&o))
{
if (t < *po) return -1;
if (*po < t) return 1;
return 0;
}
if (my_type_info()->before(*o.my_type_info()) return -1;
if (o.my_type_info()->before(*my_type_info()) return 1;
ASSERT(FALSE);
return 0;
}
virtual std::size_t get_hash() const {
return hash(t);
}
// etc
std::type_info const* my_type_info() const {
return std::addressof( typeid(T) ); // note, static type, not dynamic
}
};
第 4 步:向常规类型添加一个构造函数,该构造函数接受一个 T
并从中构造一个类型删除实现,并将其填充到其指向帮助程序的智能指针中。
template<class T,
// SFINAE block using this ctor as a copy/move ctor:
std::enable_if_t<!std::is_same<exposed_type, std::decay_t<T>>::value, int>* =nullptr
>
exposed_type( T&& t ):
upImpl( new internal_impl<std::decay_t<T>>{std::forward<T>(t)} )
{}
完成所有这些工作后,您现在拥有了具有常规(或半常规)值类型的非侵入式多态系统。
您的工厂函数返回常规类型。
查看 std::function
的示例实现以了解这是否已完全完成。
1唯一和共享都是不错的选择,具体取决于您是要存储不可变/写入数据时复制,还是手动克隆。
关于c++ - 澄清 Sean Parent 的谈话 "Inheritance is the base class of evil",我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26199126/
如果我不定义自己的构造函数,Base *b = new Base; 与 Base *b = new Base(); 之间有什么区别吗? 最佳答案 初始化是标准中要遵循的一种 PITA...然而,这两个
是否有现成的函数可以在 C# 中进行基本转换?我希望将以 26 为基数和以 27 为基数的数字转换为以 10 为基数。我可以在纸上完成,但我不是一个非常有经验的程序员,如果可能的话,我宁愿不要从头开始
JNA 中'base'是什么意思 Pointer.getPointerArray(long base) Pointer.getStringArray(long base) ? JNA Document
我正在做一个将数字从 10 进制转换为 2 进制的基本程序。我得到了这段代码: #include #include #include #include using namespace std;
“假设以下代码: public class MultiplasHerancas { static GrandFather grandFather = new GrandFather();
当我分析算法的时候,我突然问自己这个问题,如果我们有三元计算机时间复杂度会更便宜吗?还是有任何基础可以让我们构建计算机,这样时间复杂度分析就无关紧要了?我在互联网上找不到太多,但是基于三元的计算机在给
一个简化的场景。三个类,GrandParent,Parent 和 Child。我想要做的是利用 GrandParent 和 Parent 构造函数来初始化一个 Child 实例。 class Gran
我编写了一个简单的函数来将基数为 10 的数字转换为二进制数。我编写的函数是我使用我所知道的简单工具的最佳尝试。我已经在这个网站上查找了如何执行此操作的其他方法,但我还不太了解它。我确定我编写的函数非
我尝试了以下代码将数字从 base-10 转换为另一个 base。如果目标基地中没有零(0),它就会工作。检查 79 和 3 并正确打印正确的 2221。现在尝试数字 19 和 3,结果将是 21 而
这个问题在这里已经有了答案: Is Big O(logn) log base e? (7 个答案) 关闭 8 年前。 Intro 练习 4.4.6 的大多数解决方案。算法第三版说,n*log3(n)
如何判断基类(B)的指针是否(多态)重写了基类的某个虚函数? class B{ public: int aField=0; virtual void f(){}; }; class C
我测试了这样的代码: class A { public A() { } public virtual void Test () { Console.WriteL
两者都采用相同的概念:定义一些行和列并将内容添加到特定位置。但是 Grid 是最常见的 WPF 布局容器,而 html 中基于表格的布局是 very controversial .那么,为什么 WPF
我试图在 JS 中“获得”继承。我刚刚发现了一种基本上可以将所有属性从一个对象复制到另一个对象的简洁方法: function Person(name){ this.name="Mr or Miss
class A { public override int GetHashCode() { return 1; } } class B : A { pu
我有一个 Base32 信息哈希。例如IXE2K3JMCPUZWTW3YQZZOIB5XD6KZIEQ ,我需要将其转换为base16。 我怎样才能用 PHP 做到这一点? 我的代码如下所示: $ha
我已经使用其实验界面对 Google Analytics 进行了一些实验,一切似乎都运行良好,但我无法找到 Google Analytics 属性如何达到变体目标的答案,即归因 session - 基
if (state is NoteInitial || state is NewNote) return ListView.builder(
MSVC、Clang 和 GCC 不同意此代码: struct Base { int x; }; struct Der1 : public Base {}; struct Der2 : public
我已经尝试构建一个 Base 10 到 Base 2 转换器... var baseTen = window.prompt("Put a number from Base 10 to conver
我是一名优秀的程序员,十分优秀!