Какова цель иметь такой сложный дизайн класса C ++? - PullRequest
0 голосов
/ 21 ноября 2018

Я наткнулся на код C ++ с открытым исходным кодом, и мне стало любопытно, почему люди проектируют классы таким образом?

Итак, обо всем по порядку, вот класс Abstract:

class BaseMapServer
    {
    public:
        virtual ~BaseMapServer(){}

        virtual void LoadMapInfoFromFile(const std::string &file_name) = 0;
        virtual void LoadMapFromFile(const std::string &map_name) = 0;
        virtual void PublishMap() = 0;
        virtual void SetMap() = 0;
        virtual void ConnectROS() = 0;
    };

Ничего особенного здесь и наличие абстрактного класса не может иметь несколько хорошо понятных причин.Таким образом, с этого момента я подумал, что, возможно, автор хочет поделиться общими чертами с другими классами.Итак, вот следующий класс, который является отдельным классом, но на самом деле содержит указатель упомянутого выше абстрактного класса типа (фактический файл cpp, два других класса являются заголовочными файлами):

class MapFactory
{
    BaseMapServer *CreateMap(
            const std::string &map_type,
            rclcpp::Node::SharedPtr node, const std::string &file_name)
        {
            if (map_type == "occupancy") return new OccGridServer(node, file_name);
            else
            {
                RCLCPP_ERROR(node->get_logger(), "map_factory.cpp 15: Cannot load map %s of type %s", file_name.c_str(), map_type.c_str());
                throw std::runtime_error("Map type not supported")
            }
        }
};

А теперь интересная вещьприходит, вот дочерний класс абстрактного класса:

class OccGridServer : public BaseMapServer
    {
    public:
        explicit OccGridServer(rclcpp::Node::SharedPtr node) : node_(node) {}
        OccGridServer(rclcpp::Node::SharedPtr node, std::string file_name);
        OccGridServer(){}
        ~OccGridServer(){}

        virtual void LoadMapInfoFromFile(const std::string &file_name);
        virtual void LoadMapFromFile(const std::string &map_name);
        virtual void PublishMap();
        virtual void SetMap();
        virtual void ConnectROS();

    protected:
        enum MapMode { TRINARY, SCALE, RAW };

        // Info got from the YAML file
        double origin_[3];
        int negate_;
        double occ_th_;
        double free_th_;
        double res_;
        MapMode mode_ = TRINARY;
        std::string frame_id_ = "map";
        std::string map_name_;

        // In order to do ROS2 stuff like creating a service we need a node:
        rclcpp::Node::SharedPtr node_;

        // A service to provide the occupancy grid map and the message with response:
        rclcpp::Service<nav_msgs::srv::GetMap>::SharedPtr occ_service_;
        nav_msgs::msg::OccupancyGrid map_msg_;

        // Publish map periodically for the ROS1 via bridge:
        rclcpp::TimerBase::SharedPtr timer_;
    };

Так, какова цель класса MapFactory?

Чтобы быть более конкретным - в чем преимущество создания класса, который содержит указатель типа Abstract class BaseMapServer, который является конструктором (я считаю), и этот странный конструктор создает память для нового объекта с именемOccGridServer и возвращает это?Я так запутался, только написав это.Я действительно хочу стать лучшим программистом C ++ и отчаянно пытаюсь узнать секрет этих конструкций кода.

Ответы [ 3 ]

0 голосов
/ 21 ноября 2018

Класс MapFactory используется для создания правильного экземпляра подкласса BaseMapServer на основе переданных ему параметров.

В этом конкретном случае есть только один экземпляр дочернего класса, но, возможно, есть планы добавить еще.Затем, когда добавляется больше, фабричный метод может выглядеть примерно так:

BaseMapServer *CreateMap(
        const std::string &map_type,
        rclcpp::Node::SharedPtr node, const std::string &file_name)
    {
        if (map_type == "occupancy") return new OccGridServer(node, file_name);
        // create Type2Server
        else if (map_type == "type2") return new Type2Server(node, file_name);   
        // create Type3Server
        else if (map_type == "type3") return new Type3Server(node, file_name);
        else
        {
            RCLCPP_ERROR(node->get_logger(), 
                         "map_factory.cpp 15: Cannot load map %s of type %s", 
                         file_name.c_str(), map_type.c_str());
            throw std::runtime_error("Map type not supported")
        }
    }

Это имеет то преимущество, что вызывающей стороне не нужно знать точный используемый подкласс, и фактически базовый подкласс может потенциальноизменить или даже заменить под капотом без необходимости изменения кода вызова.Фабричный метод усваивает эту логику для вас.

0 голосов
/ 21 ноября 2018

Это пример шаблона дизайна фабрики.Вариант использования такой: есть несколько типов очень похожих классов, которые будут использоваться в коде.В этом случае OccGridServer является единственным показанным на самом деле, но общее объяснение может ссылаться на гипотетические Dog, Cat, Otter и т. Д.Из-за их сходства желателен некоторый полиморфизм: если все они наследуются от базового класса Animal, они могут совместно использовать методы виртуального класса, такие как ::genus, ::species и т. Д., И на производные классы можно указывать или ссылаться на них.с указателями / ссылками базового класса.В вашем случае OccGridServer наследуется от BaseMapServer;предположительно есть и другие производные классы, и указатели / ссылки.

Если вы знаете, какой производный класс необходим во время компиляции, вы обычно просто вызываете его конструктор.Смысл шаблона проектирования фабрики состоит в том, чтобы упростить выбор производного класса, когда конкретный производный класс неизвестен до времени выполнения.Представьте, что пользователь выбирает свое любимое животное, выбирая кнопку или вводя имя.Как правило, это означает, что где-то есть большой блок if / else, который сопоставляется с определенным типом неоднозначности ввода / вывода (string, enum и т. Д.) С определенным типом производного класса, вызывая его конструктор.Полезно инкапсулировать это в шаблон фабрики, который может действовать как именованный конструктор, который принимает этот двусмысленный аргумент как параметр «конструктор» и находит правильный производный класс для конструирования.

Обычно, кстати, CreateMap будет статическим методом BaseMapServer.Я не понимаю, почему в этом случае нужен отдельный класс для функции фабрики.

0 голосов
/ 21 ноября 2018

Это фабричный шаблон.См. https://en.wikipedia.org/wiki/Factory_method_pattern. Похоже, что текущий код поддерживает только одну реализацию (OccGridServer), но в будущем могут быть добавлены другие.И наоборот, если когда-либо может быть только одна конкретная реализация, то это чрезмерный дизайн.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...