替换一个类而不删除旧类,避免访问重复。

huangapple go评论78阅读模式
英文:

Replacing a class without deleting the old one, avoiding access duplication

问题

在重构任务中,我有一个名为 OldClass 的旧类,它将被更好的新类 NewClass 替代。

OldClassNewClass 有很多共同之处,例如共享具有相同名称的数据成员,如 memberAmemberB... 但虽然它们具有相同的名称,这些成员属于 OldClassNewClass,并且不通过继承共享(我也不希望它们共享)。

它们确实有一个共同的祖先,看起来像这样:

          ┌──────────────┐
          │CommonAncestor│
          └───────▲──────┘
          ┌───────┴──────┐
          │         ┌────┴────┐
          │         │NewParent│
          │         └────▲────┘
    ┌─────┴─────┐  ┌─────┴──────┐
    │ OldClass  │  │ NewClass   │
    ├───────────┤  ├────────────┤
    │int memberA│  │int memberA │
    │int memberB│  │bool memberB│
    └───────────┘  └────────────┘

我的问题是,在完全过渡之前,这两者都会在代码中存在一段时间,开始时会启用旧模式和新模式之间的切换。

这意味着在许多地方,我将会有以下情况:

     CommonAncestor* c = ...;
     if(newMode) {
         NewClass* n = dynamic_cast<NewClass*>(c);
         n->setMemberA(...);
         n->setMemberB(...);
     }
     else {
         OldClass* o = dynamic_cast<NewClass*>(c);
         o->setMemberA(...);
         o->setMemberB(...);
     }

这很糟糕(代码重复和类型转换)。当然,不仅有两个成员...

我如何避免这种情况?有没有一种不用复制每个成员访问和赋值就能让它们共存的好方法?

想法是尽可能轻松地删除 OldClass。___
附注:在实例化的情况下,我通过能够从 NewClass 中构建一个 OldClass 成员来避免复制,所以:

     CommonAncestor* c;
     NewClass* n;
     n->setMemberA(...);
     n->setMemberB(...);
     if(newMode) {
        c = n;
     }
     else {
         OldClass* o(n); //我们可以通过复制一个新对象来创建一个旧对象
         c = o;
     }
英文:

In a refactoring task, I have an old class, called OldClass, that will be replaced with the newer better NewClass.

OldClass and NewClass share have a lot in common, and for example share data members with the same name like memberA, memberB... But while they have the same name, these are members of OldClass and NewClass and are not shared through inheritance (nor do I want them to be shared).

They do have a common ancestor, so it would look like this:

      ┌──────────────┐
      │CommonAncestor│
      └───────▲──────┘
      ┌───────┴──────┐
      │         ┌────┴────┐
      │         │NewParent│
      │         └────▲────┘
┌─────┴─────┐  ┌─────┴──────┐
│ OldClass  │  │ NewClass   │
├───────────┤  ├────────────┤
│int memberA│  │int memberA │
│int memberB│  │bool memberB│
└───────────┘  └────────────┘

My problem is that both will exist within the code for a while before the transition is fully over, with a setting at start enabling the switch between old and new mode.

That means that in a lot of place, I will have

 CommonAncestor* c = ...;
 if(newMode) {
     NewClass* n = dynamic_cast&lt;NewClass*&gt;(c);
     n-&gt;setMemberA(...);
     n-&gt;setMemberB(...);
 }
 else {
     OldClass* o = dynamic_cast&lt;NewClass*&gt;(c);
     o-&gt;setMemberA(...);
     o-&gt;setMemberB(...);
 }

Which is awful (code duplication, and cast). (Of course, there is a lot more than two members...)

How can I avoid that? What would be a nice way for them to coexist without duplicating every member access and assignation?

The idea is that deleting OldClass, later, should be as easy as possible.


Side note: In the case of instanciation, I avoided duplication by being able to construct an OldClass member from a NewClass one, so:

 CommonAncestor* c;
 NewClass* n;
 n-&gt;setMemberA(...);
 n-&gt;setMemberB(...);
 if(newMode) {
    c = n;
 }
 else {
     OldClass* o(n); //we can make an Old by copying a New
     c = o;
 }

答案1

得分: 2

当您使用dynamic_cast进行向下转型时,通常意味着在设计中出现了问题:

CommonAncestor* c = ...;
if(newMode) {
    NewClass* n = dynamic_cast<NewClass*>(c);
    n->setMemberA(...);
    n->setMemberB(...);
}

上述示例可以改写为:

CommonAncestor* c = ...;
c->setMemberA(...);
c->setMemberB(...);

这需要使setMemberAsetMemberB成为虚成员函数,以便调用基类函数会选择派生类的实现。即使您无法修改CommonAncestor,您仍然可以:

  • 创建一个新的OldOrNewClass类,其中包含virtual void setMemberA(...)等成员函数。
  • 使OldClass继承自OldOrNewClass
  • OldOrNewClass可以继承自CommonAncestor,以使OldClass只有一个父类。
  • 通过将CommonInterface*向下转型为OldOrNewClass*,然后调用虚成员函数:
CommonAncestor* c = ...;
OldOrNewClass* n = dynamic_cast<OldOrNewClass*>(c);
n->setMemberA(...);
n->setMemberB(...);

在C++中允许多重继承,因此您可以保留旧类层次结构并将OldOrNewClass添加到其中。但是,如果允许修改CommonInterface,则可以避免这种麻烦。

还请参阅C++中为什么需要虚函数?

英文:

When you find yourself down-casting with dynamic_cast, something has usually gone wrong in your design:

CommonAncestor* c = ...;
if(newMode) {
    NewClass* n = dynamic_cast&lt;NewClass*&gt;(c);
    n-&gt;setMemberA(...);
    n-&gt;setMemberB(...);
}

This above example could instead be written as:

CommonAncestor* c = ...;
c-&gt;setMemberA(...);
c-&gt;setMemberB(...);

This would require setMemberA and setMemberB to be virtual member functions, so calling the base class functions chooses an implementation from the derived class. Even if you weren't allowed to modify CommonAncestor, you could:

  • create a new class OldOrNewClass containing virtual void setMemberA(...), et al.
  • make OldClass inherit from OldOrNewClass
    • OldOrNewClass can inherit from CommonAncestor, so that OldClass has only one parent
  • make NewClass or NewParent inherit from OldOrNewClass
  • downcast your CommonInterface* to OldOrNewClass* and then call the virtual member functions
CommonAncestor* c = ...;
OldOrNewClass* n = dynamic_cast&lt;OldOrNewClass&gt;(c);
n-&gt;setMemberA(...);
n-&gt;setMemberB(...);

Multiple inheritance is allowed in C++, so you can keep your old class hierarchy and add OldOrNewClass to that. However, if you are allowed to modify CommonInterface, you can save yourself this trouble.

Also see Why do we need virtual functions in C++?

答案2

得分: 1

以下是翻译好的部分:

模板用于代码去重:

template <typename T, typename F>
void manipulateClass(CommonAncestor* c, F&& functor) { // 请给我一个更好的名字
  if (T* n = dynamic_cast<T*>(c)) {
    std::forward<Functor>(functor)(n);
  } else {
    throw std::invalid_argument("Class has incorrect type for selected mode");
  }
}

...

auto const setter = [&](auto n) { // 所有变量都被捕获
  n->setMember(...);
  n->setMember(...);
  // ...
};
if (newMode)
  manipulateClass<NewClass>(c, setter);
else
  manipulateClass<OldClass>(c, setter);

类似的方法也可以用于构造。此外,我建议使用std::unique_ptr<Class>std::shared_ptr<Class>而不是Class*

英文:

Templates are for code deduplication:

template &lt;typename T, typename F&gt;
void manipulateClass(CommonAncestor* c, F&amp;&amp; functor) { // please give me a better name
  if (T* n = dynamic_cast&lt;T*&gt;(c)) {
    std::forward&lt;Functor&gt;(functor)(n);
  } else {
    throw std::invalid_argument(&quot;Class has incorrect type for selected mode&quot;);
  }
}

...

auto const setter = [&amp;](auto n) { // all variables captured
  n-&gt;setMember(...);
  n-&gt;setMember(...);
  // ...
};
if (newMode)
  manipulateClass&lt;NewClass&gt;(c,setter);
else
  manipulateClass&lt;OldClass&gt;(c,setter);

A similar thing can be done for construction. Also, I would recommend using either std::unique_ptr&lt;Class&gt; or std::shared_ptr&lt;Class&gt; instead of Class*.

huangapple
  • 本文由 发表于 2023年6月12日 17:48:10
  • 转载请务必保留本文链接:https://go.coder-hub.com/76455439.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定