В книге Брюса Эккеля "Философия С++ часть 2" автор приводит пример использование множественного наследование в качестве средства для расширения абстрактного класса библиотеки, к которой нет доступа.
Суть задачи такова: есть интерфейс Vendor, редактировать код которого нельзя. Есть класс Pastel, с которым должны использоваться функции из библиотеки, в которой определен Vendor (принимают ссылку или указатель на Vendor), но функционала этого интерфейса недостаточно.
Автор приводит решение:
class MyBase{
public:
virtual void v()const=0;
virtual void g()const=0;
}
class Pastel: public MyBase, public Vendor{
/*...*/
}
Вопрос: в чем преимущество такого подхода к расширению над таким:
class MyBase: public Vendor{
public:
virtual void v()const=0;
virtual void g()const=0;
}
class Pastel: public MyBase{
/*...*/
}
Ответ
Представьте, что MyDeviceInterface - это интерфейсный класс, который определяет некоторые методы для интеграции классов в проект.
class MyDeviceInterface
{
virtual int device_id() const = 0;
virtual char device_name() const = 0;
}
А где-то в проекте есть общий метод опроса девайсов, типа
...
std::vector
for (MyDeviceInterface* device : my_devices)
{
std::cout << device->device_id() << std::endl;
std::cout << device->device_name() << std::endl;
}
...
Теперь представьте, что у вас есть 5 классов девайсов, которые нельзя редактировать: Device1...Device5, причем каждый из этих классов имеет свою неповторимую реализацию методов :)
В первом случае вам необходимо будет написать 5 классов для своего проекта, в которых вы просто опишите реализацию работы интерфейса
class MyDevice1 : public MyDeviceInterface, public Device1
{
int device_id() const { return Device1::get_device1_id(); }
char device_name() const { return Device1::get_device1_name(); }
}
...
class MyDevice5 : public MyDeviceInterface, public Device5
{
int device_id() const { return Device5::get_device5_id(); }
char device_name() const { return Device5::get_device5_name(); }
}
Получается чистый и красивый код, интерфейсный класс MyDeviceInterface собственно определяет интерфейс, а реализацию уже можно брать из классов Device1..5.
Также наличие класса интерфейса позволяет манипулировать всеми девайсами как некими абстрактными сущностями, которые подчиняются общим правилам поведения, а жесткое разделение наследования защищает наши классы друг от друга (класс MyDevice1 ничего не знает о детялях реализации классов MyDevice2 и т.п.)
Также довольно просто добавлять новые девайсы в такой код, мы просто штампуем наследные классы от MyDeviceInterface и DeviceN.
Во втором случае у вас такое тоже получится, но придется создать какой-то супер-интерфейс:
class MyDeviceInterface : public Device1, ... , public Device5
{
virtual int device_id() const = 0;
virtual char device_name() const = 0;
}
и классы реализации
class MyDevice1 : public MyDeviceInterface
{
int device_id() const { return Device1::get_device1_id(); }
char device_name() const { return Device1::get_device1_name(); }
}
...
class MyDevice5 : public MyDeviceInterface
{
int device_id() const { return Device5::get_device5_id(); }
char device_name() const { return Device5::get_device5_name(); }
}
Но, в этом случае все девайсы знают друг про друга, что небезопасно, методы классов Device1..5 могут иметь коллизии имен и тому подобное. Добавление каждого нового девайса будет раздувать MyDeviceInterface все больше и больше, и, в конечном итоге, это все станет невозможно сопровождать и проект умрет.
Можно создать просто 5 классов девайсов
class MyDevice1 : public Device1
{
int device_id() const { return Device1::get_device1_id(); }
char device_name() const { return Device1::get_device1_name(); }
}
Но тогда вы теряете абстрактность, т.к. у классов нету общего родителя. Эту потерю можно компенсировать шаблонами, но опять же при добавлении каждого нового девайса код будет неизбежно раздуваться за счет разворачивания шаблонов, что может быть критично для устройств с ограниченным объемом памяти. Кроме того шаблоны трудны в отладке. Однако такой способ тоже может иметь место на жизнь.
Комментариев нет:
Отправить комментарий