Паттерн проектирования "Фабричный метод" на примере работы в ZennoPoster

Dmitriy Ka

Client
Регистрация
03.05.2016
Сообщения
912
Благодарностей
650
Баллы
93
Всем привет, с вами Дмитрий, и моя статья:

Фабричный метод в C# простыми словами (но это не точно)

Предыстория
Наконец-то я добрался до изучения паттернов проектирования. С «Фабричным методом» я был знаком уже давно, но честно признаюсь — до конца не понимал, когда и зачем его применять.
Сейчас, когда картинка сложилась в моей голове, как будто бы я стал его понимать, но возможно я ошибаюсь :-)
Ключевым моментом в понимание для меня стало более глубокое погружение в работу с интерфейсами в C#.

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

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

Если сказать проще:
  • у нас есть общая логика работы (например, интерфейс или абстрактный класс);
  • а вот конкретные «детали сборки» мы доверяем фабрике.
Как это выглядит на практике:
  • клиентский код работает только с интерфейсами (то есть использует общую логику, не зная деталей реализации);
  • создание конкретных экземпляров «спрятано» за фабричными методами (они подсовывают нужные объекты в общую логику).
Главное преимущество
Мы один раз пишем общую логику работы, а для каждого конкретного объекта интерфейса можем подключать свою реализацию через фабрики. Причем таких объекты мы можем подключить сколько угодна и когда угодно, при этом не меняя общей логики.

Практика
Ну что ж, с теорией мы разобрались, очень интересно, но ничего не понятно.
Теперь переходим к практике. Представим, что мы пишем парсер для маркетплейсов.

Этап первый — общий интерфейс
Маркетплейсов у нас много, а логика парсинга у всех будет одна. Поэтому мы создадим общий интерйфес IParser.
Он у нас пока что будет простенький и будет парсить только заголовки и цены товаров на странице и выводить это в лог.

Сейчас он содержит только свойства:
Name - имя парсера;
BaseUrl - урл главной маркетплейса;
TitelXpath - xPath для заголовка товара;
PriceXpath - xPath для цены товара);
Для примера этого хватит, но в дальнейшем по ходу необходимости новых задач, мы сможем его расширить и добавить новые свойства и методы

C#:
public interface IParser
{
    string Name { get; }
    string BaseUrl { get; }
    string TitelXpath { get; }
    string PriceXpath { get; }
}
Теперь реализуем интерфейс для конкретных Маркетплейсов.

Ozon:
C#:
public class ParserOzon : IParser
{
    public string Name => "Ozon";
    public string BaseUrl => "https://www.ozon.ru/";
    public string TitelXpath => "//div[contains(@class, 'root')]//a//span";
    public string PriceXpath => "//div[contains(@class, 'root')]//span[contains(@class, 'Headline')]";
}
WB:
C#:
public class ParserWB : IParser
{
    public string Name => "Wildberries";
    public string BaseUrl => "https://www.wildberries.ru/";
    public string TitelXpath => "//span[@class='product-card__name']";
    public string PriceXpath => "//ins[contains(@class, 'price__lower-price')]";

}
Отлично — у нас есть общий интерфейс и конкретные реализации.

Этап второй — фабрика
Теперь создадим абстрактную фабрику MainFactory. Она будет отвечать только за одно — создание объекта парсера (IParser).

C#:
public abstract class MainFactory
{
    /// <summary>
    /// Создатель фабрики.
    /// </summary>
    /// <returns></returns>
    public abstract IParser Create();
}
А для каждого маркетплейса сделаем свою фабрику, через MainFactory.
Пояснительная: Мы создаем объекты new ParserOzon() или new ParserWB(), но у обоих возвращаем IParser.
Ozon
C#:
public class FactoryOzon : MainFactory
{
    public override IParser Create()
    {
        return new ParserOzon();
    }
}
WB
C#:
public class FactoryWB : MainFactory
{
    public override IParser Create()
    {
        return new ParserWB();
    }
}
Этап третий — общая логика парсинга
Теперь напишем общую логику, которая будет использовать фабрики.
Сделаем словарь фабрик Dictionary<string, Func<IParser>>, чтобы по имени выбрать нужный маркетплейс.

После набросаем общую логику на примере ZennoPoster:
1) Открываем главную сайта Маркетплейса
2) Собираем список текстов и цен на странице
3) Выводим в лог

C#:
// Работа с Фабрикой
var parserName = project.Variables["parserName"].Value;

var factories = new Dictionary<string, Func<IParser>>()
{
{ "WB", () => new FactoryWB().Create() },
{ "Ozon", () => new FactoryOzon().Create() },
};

if (!factories.TryGetValue(parserName, out var factory))
throw new ArgumentException("Неизвестный Маркетплейс для парсинга: " + parserName);

var parser = factory();

// Логика парсинга

// Открываем главную сайта Маркетплейса
var tab = instance.ActiveTab;
project.SendInfoToLog("Начинаем парсить: " + parser.Name);
Thread.Sleep(500);
project.SendInfoToLog("Заходим на сайт: " + parser.BaseUrl);
tab.Navigate(parser.BaseUrl);
tab.WaitDownloading();

// Собираем список текстов и цен на странице
project.SendInfoToLog("Собираем данные!");

var titels = tab.FindElementsByXPath(parser.TitelXpath).ToArray();
var prices = tab.FindElementsByXPath(parser.PriceXpath).ToArray();

project.SendInfoToLog("Собрали данных: " + titels.Length);
project.SendInfoToLog("Выводим данные:");

// Выводим в лог
for (var i = 0; i < titels.Length; i++)
{
var titel = titels[i].InnerText;
var price = prices[i].InnerText;
project.SendInfoToLog($"{i + 1} | {titel}. Цена: {price}");
}

project.SendInfoToLog("Закончили парсинг!");
На этом у нас все, подведем итоги.

Итоги.
Собственно, а зачем мы это все делали? Можно же было сделать все гораздо проще!
Да, можно сделать проще, но мы заложили хороший фундамент для будущего:
  1. Захотели добавить новый маркетплейс (например, Яндекс.Маркет) → пишем новый класс, создаём фабрику — и ничего не ломаем в общей логике.
  2. Появилась задача с авторизацией или другой сложной логикой → переносим её в фабрику, не нагружая IParser и общую логику.
Главный итог: мы сделали код расширяемым и независимым от конкретных классов. Теперь мы можем легко добавлять новые объекты и задачи, не переписывая старую логику.

Еще раз, но с моими комментариями на видео:


Git:
 

Вложения

Последнее редактирование:
  • Спасибо
Реакции: usboff

Кто просматривает тему: (Всего: 2, Пользователи: 0, Гости: 2)