#новичкам
Полиморфизм - это способность кода единообразно обрабатывать разные сущности. И хоть термин "полиморфизм" называют принципом ООП, это понятие в широком смысле выходит за границы этой парадигмы. Любая конструкция языка, которая позволяет единообразно управлять разными сущностями проявляет полиморфные свойства. В этом и следующих постах постараемся по верхам раскрыть сущности, реализующие полиморфизм в С++ в широком смысле.
Но раз уж заговорили об ООП, давайте для начала поговорим про полиморфизм в рамках ООП.
Если мы говорим про ООП, значит где-то рядом тусуются классы и их иерархии. Полиморфизм в объектно-ориентированном программировании - один из основных его принципов. Это свойство, позволяющее объектам разных классов обрабатываться одинаково, используя общий интерфейс. При этом поведение разное в зависимости от конкретного типа объекта. Реализации интерфейсов у всех классов разные. И решение о вызове того или иного конкретного метода принимается во время выполнения программы.
Для работы работы динамического полиморфизма нужен: базовый класс, пара наследников и виртуальные методы:
struct ITask {
virtual void Execute() = 0;
virtual ~ITask() = default;
};
struct FileDeleteTask : public ITask {
std::string path_;
FileDeleteTask(const std::string &path) : path_(path) {}
void Execute() override {
std::filesystem::remove(path_);
std::cout << "Deleted: " << path_ << std::endl;
}
};
struct S3FileUploadTask : public ITask {
std::string bucket_;
std::string path_;
std::shared_ptr<S3Client> client_;
S3FileUploadTask(const std::string &bucket, const std::string &path, const std::shared_ptr<S3Client> &client)
: bucket_{bucket}, path_{path}, client_{client} {}
void Execute() override {
client_->Upload(bucket_, path_);
std::cout << "Uploaded: " << bucket_ << ", pathL " << path_ << std::endl;
}
};У нас есть интерфейс ITask и виртуальный метод Execute. Два других класса наследуются от ITask и переопределяют метод Execute. В задаче FileDeleteTask удаляется файл по заданному пути из файловой системы. В задаче S3FileUploadTask файл загружается в удаленное хранилище S3.
Заметим, у этих задач общий интерфейс(их можно выполнить), но они совершают разные действия.
Теперь мы можем использовать эти задачи:
void Producer1(const std::string &bucket, const std::vector<std::string> &paths,
const std::shared_ptr<S3Client> &client, std::deque<std::unique_ptr<ITask>> &tasks) {
for (const auto &path : paths)
tasks.emplace_back(std::make_unique<S3FileUploadTask>(bucket, path, client));
}
void Producer2(const std::vector<std::string> &paths, std::deque<std::unique_ptr<ITask>> &tasks) {
for (const auto &path : paths)
tasks.emplace_back(std::make_unique<FileDeleteTask>(path));
}
void Worker(std::deque<std::unique_ptr<ITask>> &tasks) {
while (!tasks.empty()) {
auto task = std::move(tasks.front());
task.pop_front();
task->Execute();
}
}
У нас есть 2 продюсера, которые кладут задачи в очередь, и воркер, который выполнятся задачи из очереди.
В очереди хранятся уникальные указатели на базовый класс ITask. Это значит, что она может хранить объекты любых наследников интерфейса ITask.
Теперь самое важное: воркеру не нужно знать, какую конкретно задачу он сейчас достанет из очереди и какой конкретно продюсер ее туда положил. Единственное, что важно - общий интерфейс. Он позволяет единообразно выполнить задачи разных типов, даже не зная их исходный тип.
В этом и суть: абстрагироваться от конкретной реализации и верхнеуровнево определить, как себя должен вести объект.
Но динамический полиморфизм не ограничивается полиморфизмом подтипов. Для него вообще иерархия классов не нужна. И в следующих постах посмотрим, что еще в С++ позволяет реализовать полиморфное поведение.
Extract common traits. Stay cool.
#OOP #cppcore