在运行时更改对象的行为

时间:2009-04-08 13:06:51

标签: c++

如何在运行时更改对象的行为? (使用C ++)

我将举一个简单的例子。我有一个包含方法Operator的课程operate。我们假设它看起来像这样:

double operate(double a, double b){
  return 0.0;
}

用户将为ab提供一些输入值,并选择要执行的操作,假设他可以选择计算加法或乘法。鉴于它的输入,我被允许做的只是实例化Operator并调用operate(a, b),这正是我之前提到过的。

计算乘法或加法的方法将在某处实现(不知道在哪里)。

总之,我必须根据用户的输入更改Operator对象的行为。

11 个答案:

答案 0 :(得分:9)

这样做的标准模式是使外部类具有指向“实现”类的指针。

// derive multiple implementations from this:
class Implementation
{
    virtual ~Implementation() {} // probably essential!

    virtual void foo() = 0;
};

class Switcheroo
{
    Implementation *impl_;

public:
    // constructor, destructor, copy constructor, assignment 
    // must all be properly defined (any that you can't define, 
    // make private)

    void foo()
    {
        impl_->foo();
    }
};

通过将Switcheroo的所有成员函数转发给impl_成员,您可以在需要时切换到不同的实现。

此模式有多种名称:Pimpl(“私有实现”的缩写),智能参考(与智能指针相反,由于前导成员功能),它与代理和桥接模式有一些共同之处

答案 1 :(得分:6)

我只是把它作为琐事提及,不能再多说一点,但我们走了......

警告危险!!!

我认为,我所看到的一个愚蠢的技巧被称为抓紧,但这只是为了真正的愚蠢。基本上你将virtualtable指针交换到另一个类的指针,它可以工作,但它理论上可能破坏世界或导致一些其他未定义的行为:)

无论如何,只使用动态分类和kosher C ++,但作为一个实验,上面是有趣的...

答案 2 :(得分:3)

Coplien的信封/字母图案(在必须阅读书籍高级C ++编程风格和成语)是经典的方法。

简而言之,Envelope和Letter都是抽象基类/ interfcae的子类,它定义了所有子类的公共接口。

信封持有(并隐藏真实类型)一封信。

各种Letter类具有抽象类的公共接口的不同实现。

信封没有真正的实施;它只是对其信件的代表(代表)。它包含一个指向抽象基类的指针,并指向具体的Letter类实例。由于需要更改实现,因此更改了Letter子类指针的类型。

由于用户只有对Envelope的引用,因此除了Envelope的行为发生变化外,此更改对他们不可见。

Coplien的例子特别干净,因为它是字母,而不是引起变化的信封。

一个例子是Number类层次结构。抽象基数声明对所有数字的某些操作,例如,添加。 Integer和Complex是具体子类的示例。

添加整数和整数会产生一个整数,但添加一个Interget和一个Complex会产生一个Complex。

以下是Envelope的添加内容:

public class Number {
  Number* add( const Number* const n ) ; // abstract, deriveds override
}

public class Envelope : public Number {
  private Number* letter;

...

  Number* add( const Number& rhs) { // add a number to this
    // if letter and rhs are both Integers, letter->add returns an Integer
    // if letter is a a Complex, or rhs is, what comes back is a Complex
    //
    letter = letter->add( rhs ) ) ;
    return this;
  }
}

现在客户端的指针永远不会改变,他们永远不需要知道Envelop持有什么。这是客户端代码:

int main() {
  // makeInteger news up the Envelope, and returns a pointer to it
  Number* i = makeInteger( 1 ) ;  
  // makeComplex is similar, both return Envelopes.
  Number* c = makeComplex( 1, 1 ) ;

  // add c to i
  i->add(c) ;


  // to this code, i is now, for all intents and purposes, a Complex!
  // even though i still points to the same Envelope, because 
  // the envelope internally points to a Complex.
}

在他的书中,Coplien进一步深入 - 你会注意到add方法需要多次发送某种形式 - 并添加语法糖。但这是如何获得所谓的“运行时多态性”的 gist

答案 3 :(得分:2)

你可以通过动态绑定(多态)实现它......但这一切都取决于你实际想要实现的目标。

答案 4 :(得分:2)

除非对象打算通过某种技术(合成,回调等)使用“插件”行为,否则不能使用任何理智的方式更改任意对象的行为。

(疯狂的方式可能会覆盖功能代码所在的进程内存......)

但是,您可以通过覆盖vtable(An approach can be found in this article)来覆盖虚拟方法中的对象行为,而不会覆盖可执行页面中的内存。但这仍然不是一个非常理智的方式,它承担着多重安全风险。

最安全的做法是通过提供适当的钩子(回调,合成......)来改变设计为要改变的对象的行为。

答案 5 :(得分:1)

对象始终具有由其类定义的行为。

如果您需要不同的行为,则需要一个不同的类......

答案 6 :(得分:1)

你也可以考虑Role Pattern动态绑定......我正在努力解决你所做的事情。我读到了战略模式,但这个角色听起来也是一个很好的解决方案......

答案 7 :(得分:0)

有许多方法可以实现这种代理,pImpl习语,多态,所有这些都有利有弊。最适合您的解决方案将取决于您要解决的问题。

答案 8 :(得分:0)

有很多方法:

首先尝试if。您始终可以使用if语句更改行为。然后你可能会发现'多态“的方式更准确,但这取决于你的任务。

答案 9 :(得分:0)

创建一个抽象类,将哪些行为必须是可变的方法声明为虚拟。 创建将实现虚方法的具体类。使用设计模式有很多方法可以实现这一目标。

答案 10 :(得分:0)

您可以使用dynamic binding更改对象行为。像DecoratorStrategy这样的设计模式实际上可以帮助您实现相同的目标。