Композитный с интеллектуальными указателями без dynamic_pointer_cast

#c #design-patterns #c 11 #composite

#c #шаблоны проектирования #c 11 #композитный

Вопрос:

Я реализовал составной шаблон с использованием интеллектуальных указателей, он работает до определенной точки.

Проблема в том, что я просто могу использовать методы, реализованные в интерфейсе, и я не могу использовать методы, определенные в производном классе, без использования dynamic_pointer_cast , и я этого не хочу.

Я хочу знать, возможно ли это сделать без использования dynamic_pointer_cast .

Я слышал, что мне нужно реализовать шаблон visitor, но я действительно не знаю, как это сделать и подходит ли это для этой проблемы.

 #include <iostream>
#include <vector>
#include <memory>

class Fruit
{
public:
    virtual void getOld() = 0;
};

class Orange : Fruit
{
public:
    Orange() {}

    void add(std::shared_ptr<Fruit> f)
    {
        v.push_back(f);
    }

    std::shared_ptr<Fruit> get(int k)
    {
        return v[k];
    }

    void getOld()
    {
        std::cout << "Orange - I'm old." << std::endl;
    }
private:
    std::vector<std::shared_ptr<Fruit>> v;
};

class Bitter : public Fruit
{
public:
    Bitter() {}

    void getOld()
    {
        std::cout << "Bitter - I'm old." << std::endl;
    }

    void getNew()
    {
        std::cout << "Bitter - I'm new." << std::endl;
    }
};

int main(int argc, char ** argv)
{
    auto orange = new Orange;
    orange->add(std::make_shared<Bitter>());
    auto bitter = orange->get(0);
    bitter->getOld();

    return 0;
}
  

Он работает так, как вы можете видеть здесь на предварительном просмотре, но когда я пытаюсь использовать:

 int main(int argc, char ** argv)
{
    auto orange = new Orange;
    orange->add(std::make_shared<Bitter>());
    auto bitter = orange->get(0);
    bitter->getOld();
    bitter->getNew();

    return 0;
}
  

Я получил ошибки:

ошибка: у ‘class Fruit’ нет члена с именем ‘getNew’

Заранее спасибо.

Ответ №1:

Проблема здесь, я думаю, в том, что он будет работать с полиморфизмом, но метод ‘getNew’ не существует в материнском классе, поэтому вам нужно определить его и сделать виртуальным. Это единственный способ сделать это без использования приведения к объекту. С этой строкой это должно сработать.

 virtual void getNew() = 0;
  

Комментарии:

1. Я знаю это, но что произойдет, если у меня есть десятки классов с разными методами? Интерфейс будет беспорядочным из-за большого количества методов, вам не кажется? — Давайте подождем, чтобы посмотреть, есть ли у кого-нибудь решение или, по крайней мере, способ улучшить это. Спасибо.

2. Тогда проблема может быть в вашем дизайне. Если вы знаете, какой тип Orange вернет, вы можете создавать функции типа ‘getBitter’ вместо просто ‘get’

3. Опять та же проблема, что произойдет, если у меня есть десятки классов? Мне нужно будет создать методы для каждого из них. Если мы посмотрим на реализацию, мы можем это сделать, но как насчет API?

4. Это один из недостатков C и Java, и вам нужно сделать это с помощью приведения или использовать полиморфизм. Как только вы помещаете производный тип в общий, вы теряете информацию о самом типе. Единственный способ использовать определенную часть объекта — это привести его (что небезопасно, поэтому ваше решение, вероятно, является плохим дизайном)

5. Почему это небезопасно? И вы думаете, что лучший способ — определить методы в базовом классе или определить метод для получения каждого класса, который у меня есть?

Ответ №2:

Одним из возможных решений является наличие следующей функции Orange .

 template <typename T>
T* get(int k)
{
    return dynamic_cast<T*>(v[k].get());
}
  

А затем используйте:

 auto bitter = orange->get<Bitter>(0);
bitter->getOld();
bitter->getNew();
  

Это выполняет a dynamic_cast , но локализуется на Orange .

Комментарии:

1. однако вы просто скрываете приведение здесь. Вы просто перемещаете проблему.

2. @meneldal, никаких аргументов нет. Просто предоставление механизма для уменьшения распространения dynamic_cast на другие функции.

Ответ №3:

Следующую информацию о «композитном шаблоне» можно найти в книге GOF. Конечно, это было объяснено на основе класса graphics.

Ключом к составному шаблону является абстрактный класс, который представляет как примитивы, так и их контейнеры. Для графической системы этот класс является графическим. Graphic объявляет операции, такие как Draw, которые специфичны для графических объектов. Он также объявляет операции, которые совместно используют все составные объекты, такие как операции для доступа к дочерним объектам и управления ими.

Основываясь на приведенном выше объяснении, в идеале мы должны объявить все возможные интерфейсы конечного и не-листового (контейнерного) типа узла при использовании составного шаблона.Я думаю, что это необходимо для того, чтобы позволить клиенту единообразно обрабатывать отдельные объекты и композиции объектов. Поэтому в идеале вы должны объявлять свои классы следующим образом при использовании этого конкретного шаблона. Любая логика, написанная на основе точного типа объекта в клиентском коде, нарушает суть этого шаблона.

 //Abstract class which should have all the interface common to
// Composite and Leaf class. It may also provide the default 
// implementation wherever appropriate.
class Fruit {
public:
    virtual void getOld() = 0;
    virtual void getNew() = 0;
    virtual void add(std::shared_ptr<Fruit> f) { }
    virtual  std::shared_ptr<Fruit> get(int index ) {return nullptr; }
    virtual ~Fruit() { }
};


//Composite Node
class Orange : Fruit {
public:
    Orange() {}
    void add(std::shared_ptr<Fruit> f) { v.push_back(f); }
    std::shared_ptr<Fruit> get(int k) { return v[k]; }
    void getOld()  { std::cout << "Orange - I'm old." << std::endl; }
    void getNew() { std::cout << "Orange - I'm new." << std::endl; } 
private:
    std::vector<std::shared_ptr<Fruit>> v;
};


//Leaf node
class Bitter : public Fruit {
public:
    Bitter() {}
    void getOld() { std::cout << "Bitter - I'm old." << std::endl; }
    void getNew() { std::cout << "Bitter - I'm new." << std::endl; }
};