Паттерны проектирования «Шаблонный метод» и «Мост» на современном C++
Является ли самым важным в разработке программного обеспечения только хорошее (или отличное) знание языка программирования? Конечно, нет. Есть еще много всего, но одно из самого важного – это знание шаблонов (или паттернов) проектирования. Это может быть неочевидно, когда вы разрабатываете элементарные приложения в начале своего пути, но становится необходимым при разработке пакетов ПО на профессиональном уровне. Многие книги могут помочь вам понять различные шаблоны, независимо от языка программирования (погуглите). Один очень удобный ресурс, который я недавно нашел, – это видеоуроки Арьяна Эггеса под названием ArjanCodes. Он делится очень хорошими уроками, основанными на python. Одно очень хорошее упражнение, которое вы можете выполнить, это применить эти шаблоны в своих проектах или… применить эти шаблоны, используя другие языки программирования. Последнее – это то, что я попытался сделать. Я использовал C++, чтобы переписать пример Арьяна и посмотреть, работает ли он. Поэтому я предлагаю сначала посмотреть видео Арьяна, а затем прочитать пост, чтобы проверить версию на C++.
Пример основан на разработке торгового бота. Бот подключается к криптобирже и получает рыночные данные. На этом этапе появляется шаблон «мост», который поможет подключиться к различным биржевым рынкам без изменения бота. Затем он проверяет цены и советует, нужно ли нам покупать или продавать. Совет может быть основан на различных алгоритмах, таких как среднее значение или минимум-максимум. Опять же, на помощь приходит паттерн «шаблонный метод», который помогает нам использовать разные торговые стратегии, не меняя торгового бота. Интересно?
Паттерн шаблонный метод подходит для стандартной процедуры, но шаги этой процедуры могут различаться в зависимости от ситуации. В нашем случае эта процедура заключается в принятии решения о покупке или продаже криптовалюты, но торговые стратегии могут быть разными. Как мы собираемся это реализовать? Основная идея состоит в том, чтобы реализовать торгового бота как абстрактный класс, создать подклассы и реализовать определенные стратегии, которые нам нравится применять. Как это выглядит?
#ifndef TEMPLATEPATTERN__TRADINGBOT_H_
#define TEMPLATEPATTERN__TRADINGBOT_H_
#include <vector>
#include <string>
#include <iostream>
#include "Exchange.h"
class TradingBot {
public:
explicit TradingBot(Exchange& exchange) : exchange_{exchange} {}
void CheckPrices(const std::string& coin);
protected:
[[nodiscard]] virtual bool ShouldBuy(const std::vector<int>& prices) const = 0;
[[nodiscard]] virtual bool ShouldSell(const std::vector<int>& prices) const = 0;
Exchange& exchange_;
};
#endif//TEMPLATEPATTERN__TRADINGBOT_H_
Да, мы видим, что ShouldBuy
и ShouldSell
являются абстрактными методами класса, а метод CheckPrices
, который дает нам рекомендации, использует эти абстрактные методы, например:
#include "TradingBot.h"
void TradingBot::CheckPrices(const std::string& coin) {
exchange_.Connect();
std::vector<int> prices = exchange_.GetMarketData(coin);
if (this->ShouldBuy(prices)) {
std::cout << "You should buy " << coin << std::endl;
} else if (this->ShouldSell(prices)) {
std::cout << "You should sell " << coin << std::endl;
} else {
std::cout << "No action needed for " << coin << std::endl;
}
}
Хорошо, это начинает обретать смысл. Давайте теперь посмотрим, как выглядит подкласс:
#ifndef TEMPLATEPATTERN__MINMAXTRADER_H_
#define TEMPLATEPATTERN__MINMAXTRADER_H_
#include "TradingBot.h"
#include <algorithm>
class MinMaxTrader : public TradingBot {
public:
explicit MinMaxTrader(Exchange& exchange) : TradingBot(exchange) {}
protected:
[[nodiscard]] bool ShouldBuy(const std::vector<int> &prices) const override {
return (prices.back() == *std::min_element(std::begin(prices), std::end(prices)));
}
[[nodiscard]] bool ShouldSell(const std::vector<int> &prices) const override {
return (prices.back() == *std::max_element(std::begin(prices), std::end(prices)));
}
};
#endif//TEMPLATEPATTERN__MINMAXTRADER_H_
Что ж, неудивительно, как мы и ожидали, подкласс реализует абстрактные методы, и с этого момента мы используем этот подкласс для создания торгового бота. Если нам нужна другая торговая стратегия, мы создаем еще один подкласс торгового бота и разрабатываем подход, который нам нужен.
Паттерн мост, очевидно, соединяет разные классы. В нашем случае биржевая часть и торговый бот – это две разные вещи, которые могут различаться. У нас могут быть разные биржи криптовалюты, например, Coinbase и Binance, а также различные торговые боты, например, среднее и min-max. Таким образом, этот паттерн позволит нам использовать любую криптобиржу, не меняя торгового бота.
С чего начать? Как обычно, я бы сказал, создав для биржи абстрактный класс.
#ifndef TEMPLATEPATTERN__EXCHANGE_H_
#define TEMPLATEPATTERN__EXCHANGE_H_
#include <vector>
#include <string>
class Exchange {
public:
virtual void Connect() = 0;
virtual std::vector<int> GetMarketData(const std::string& coin) = 0;
};
#endif//TEMPLATEPATTERN__EXCHANGE_H_
Затем мы можем создать различные подклассы для поддержки различных бирж, таких как Binance:
#ifndef TEMPLATEPATTERN__BINANCE_H_
#define TEMPLATEPATTERN__BINANCE_H_
#include "Exchange.h"
#include <vector>
#include <string>
#include <iostream>
class Binance : public Exchange {
public:
void Connect() override {
std::cout << "Connecting to Binance exchange..." << std::endl;
}
std::vector<int> GetMarketData(const std::string& coin) override {
return {10, 11, 12, 13};
}
};
#endif//TEMPLATEPATTERN__BINANCE_H_
Как это связано с нашим торговым ботом? Ну, вы это уже видели. Мы определяем ссылку на абстрактный класс Exchange
и инициализируем ее в конструкторе. Всё просто?
Я знаю, что мое объяснение минимально, но идея состоит в том, чтобы сначала посмотреть видеоурок Арьяна по ссылке, а затем увидеть реализацию C++ как вишенку на вершине торта. Надеюсь, что всё было понятно.