我开发了一个“犬舍”应用程序来照顾各种狗。我的客户需要允许他们的狗加入我的应用程序以使用这些服务。
因此,我定义了一个通用的“Dog”接口(interface)。客户端需要实现接口(interface)来创建具体的狗类型(比如拉布拉多犬、 Poodle 等),实例化它们并将它们接纳到我的犬舍应用程序(使用 say,kennel::admitDog(dog *dog
).
这是 Dog 抽象基类:
class Dog {
public:
Dog()
{
}
virtual ~Dog()
{
}
virtual void eatFood() = 0;
virtual void takeBath() = 0;
virtual void play() = 0;
virtual void sleep() = 0;
};
我发布了这个接口(interface),我的客户已经开始使用它来创建他们自己的具体 Dog 类型。在下一版本的应用程序中,我计划在狗窝中支持机器狗。
问题来了。机器狗需要上述抽象基类中的Dog::rechargeBattery()
。而且,它不需要现有的 Dog::eatFood()
函数。将 Dog::rechargeBattery()
添加到上述抽象基类会影响所有已经在使用此接口(interface)的现有客户端。他们将被迫执行 Dog::rechargeBattery()
并重新编译。这可能是不可取的。
- 此时的解决方案是什么?
- 在初始设计中我应该怎么做才能避免这个问题?
最佳答案
这个:
class Dog {
virtual void eatFood() = 0;
virtual void takeBath() = 0;
virtual void play() = 0;
virtual void sleep() = 0;
};
class Kennel {
void admit (Dog*);
}
变成这样:
class DogLike {
// virtual void eatFood() = 0; <-- removed
virtual void takeBath() = 0;
virtual void play() = 0;
virtual void sleep() = 0;
};
class Dog : public DogLike {
virtual void eatFood() = 0; // <-- added
};
class RoboticDog : public DogLike {
virtual void rechargeBatteries() = 0;
};
class Kennel {
void admit (DogLike*);
};
现有客户端应该与您修改后的库源代码兼容(但几乎肯定不是二进制兼容)。实现二进制兼容性可能可行也可能不可行,但对已发布的 Dog
类进行任何更改都是如此。
既然客户应该知道他们交给狗舍的是哪种狗,就可以修改狗舍界面以将活狗与机器狗分开:
class Kennel {
public:
void admit (Dog*);
void admit (RoboticDog*);
};
其他种类的犬类不得入内。
下一步是什么?据推测,犬舍有设施可以为所有回答 DogLike 界面的人提供服务,并为活狗和机器狗提供单独的设施。
void Kennel::admit (Dog* dog) {
commonFacilities.admit(dog);
messHall.admit(dog);
}
void Kennel::admit (RoboticDog* dog) {
commonFacilities.admit(dog);
chargingStation.admit(dog);
}
仍然没有类型转换。如果不希望有两个公共(public) admit
方法,可以将它们隐藏在一个在幕后检查动态类型的外观后面。
class Kennel {
void admit (Dog*);
void admit (RoboticDog*);
public:
void admit (DogLike* dog) {
if (auto d = dynamic_cast<Dog*>(dog))
admit (d);
else if (auto d = dynamic_cast<RoboticDog*>(dog))
admit (d);
else
throw UnknownDogTypeError;
}
};
关于c++ - 如何处理已发布的抽象基类的变化?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47379250/