В этой статье будет рассмотрен паттерн проектирования Фабричный метод C# — Factory Method C#, для чего он нужен и какие проблемы он решает, где можно применять данный шаблон и когда это будет излишним.
Идея паттерна Фабричный метод (Factory Method)
Паттерн проектирования — это продуманный способ построения исходного кода программы для решения часто возникающих в повседневном программировании проблем проектирования. Иными словами, это уже придуманное решения, для типичной задачи. При этом паттерн не готовое решение, а просто алгоритм действий, который должен привести к желаемому результату. Давайте рассмотрим один из наиболее часто используемых порождающих паттернов — Фабричный метод (Factory Method).
Как я уже писал ранее, существует три вида паттернов проектирования:
- Порождающие паттерны позволяют возможность выполнять инициализацию объектов наиболее удобным и оптимальным способом.
- Структурные паттерны описывают взаимоотношения между различными классами или объектами, позволяя им совместно реализовывать поставленную задачу.
- Поведенческие паттерны позволяют грамотно организовать связь между сущностями для оптимизации и упрощения их взаимодействия.
Фабричный метод (Factory Method) — это порождающий паттерн, который задает интерфейс создания экземпляра объекта, но при этом позволяет наследникам решать экземпляр какого типа создавать. То есть, базовый класс определяет интерфейс создания экземпляра, а реализацию процесса инстанцирования предоставляет наследникам.
Подпишись на группу Вконтакте и Телеграм-канал. Там еще больше полезного контента для программистов.
А на YouTube-канале ты найдешь обучающие видео по программированию. Подписывайся!
Архитектура шаблона Factory Method

- Creator — объявляет абстрактный или виртуальный метод создания продукта. Использует фабричный метод в своей реализации
- ConcreteCreator — реализует фабричный метод, который возвращает ConcreteProduct
- Product — определяет интерфейс продуктов, создаваемых фабричным методом
- ConcreteProduct — определяет конкретный вид продуктов.
Логика работы Фабричного метода
Рассмотрим основную логику работы паттерна Фабричный метод. На самом деле данный паттерн является частным случаем другого паттерна, рассмотренного нами ранее в статье Паттерн проектирования Шаблонный метод (Template method) на языке C#. В базовом классе создателе мы определяем метод создания нового экземпляра базового класса продукта. И в дальнейшем реализуем этот метод для конкретных создателей и продуктов. В качестве примера давайте рассмотрим устройство для изготовления денег. Мы можем создать различные валюты соответствующие устройства для их печати. Давайте рассмотрим пример кода.
Реализация паттерна проектирования Фабричный метод (Factory Method) на языке C#
Для начала создадим базовый класс для валюты.
using System; namespace FactoryMethod { /// <summary> /// Базовый класс для любой валюты. /// </summary> public abstract class MoneyBase { /// <summary> /// Название валюты. /// </summary> public string Name { get; protected set; } /// <summary> /// Символ валюты. /// </summary> public string Symbol { get; protected set; } /// <summary> /// Создать новый экземпляр валюты. /// </summary> /// <param name="name"> Название валюты. </param> /// <param name="symbol"> Символ валюты. </param> public MoneyBase(string name, string symbol) { // Проверяем входные данные на корректность. if(string.IsNullOrEmpty(name)) { throw new ArgumentNullException(nameof(name)); } if(string.IsNullOrEmpty(symbol)) { throw new ArgumentNullException(nameof(symbol)); } // Устанавливаем значения. Name = name; Symbol = symbol; } /// <summary> /// Приведение объекта к строке. /// </summary> /// <returns> Название валюты. </returns> public override string ToString() { return Name; } } }
Теперь определим базовый класс для устройств печатающих деньги. Именно здесь мы объявляем фабричный метод Create
using System; namespace FactoryMethod { /// <summary> /// Базовый класс для устройств печати денег. /// </summary> public abstract class CashMachineBase { /// <summary> /// Название устройства. /// </summary> public string Name { get; protected set; } /// <summary> /// Создать новый экземпляр устройства печати денег. /// </summary> /// <param name="name"> Название. </param> public CashMachineBase(string name) { // Проверяем входные данные на корректность. if(string.IsNullOrEmpty(name)) { throw new ArgumentNullException(nameof(name)); } // Устанавливаем значение. Name = name; } /// <summary> /// Напечатать новую партию денег. /// </summary> /// <param name="pageCount"> Количество листов бумаги для денег. </param> /// <returns> Напечатанные деньги. </returns> public abstract MoneyBase[] Create(int pageCount); /// <summary> /// Приведение объекта к строке. /// </summary> /// <returns> Название. </returns> public override string ToString() { return Name; } } }
Теперь переходим к реализации конкретных классов валюты. Я намеренно вношу небольшие отличия между классами, чтобы показать, что они могут отличаться между собой в реализации.
using System; namespace FactoryMethod { /// <summary> /// Российский рубль. /// </summary> public class Ruble : MoneyBase { /// <summary> /// Номер. /// </summary> public int Number { get; private set; } /// <summary> /// Номинал валюты. /// </summary> public int Denomination { get; private set; } /// <summary> /// Создать новый экземпляр рубля. /// </summary> /// <param name="denomination"> Номинал валюты. </param> public Ruble(int denomination) : base("Российский рубль", "₽") { // Проверяем входные данные на корректность. if(denomination <= 0) { throw new ArgumentException("Номинал валюты должен быть больше нуля.", nameof(denomination)); } // Создаем генератор случайных чисел. var rnd = new Random(); // Устанавливаем значения. Number = rnd.Next(1000000, 9999999); Denomination = denomination; } /// <summary> /// Приведение объекта к строке. /// </summary> /// <returns> Информация о купюре. </returns> public override string ToString() { return $"{Name} {Number} номиналом {Denomination}{Symbol}"; } } }
using System; namespace FactoryMethod { /// <summary> /// Американский доллар. /// </summary> public class Dollar : MoneyBase { /// <summary> /// Уникальный код. /// </summary> public Guid Number { get; private set; } /// <summary> /// Номинал валюты. /// </summary> public int Volume { get; private set; } /// <summary> /// Создать новый экземпляр американского доллара. /// </summary> /// <param name="volume"> Номинал. </param> public Dollar(int volume) : base("American dollar", "$") { // Проверяем входные данные на корректность. if (volume <= 0) { throw new ArgumentException("Номинал валюты должен быть больше нуля.", nameof(volume)); } Number = Guid.NewGuid(); Volume = volume; } /// <summary> /// Приведение объекта к строке. /// </summary> /// <returns> Информация о купюре. </returns> public override string ToString() { return $"{Name} {Number} номиналом {Volume}{Symbol}"; } } }
Ну и реализуем конкретные устройства печати для каждой из валют. Небольшие изменения так же добавлены специально для различия реализации.
using System; using System.Collections.Generic; using System.Linq; namespace FactoryMethod { /// <summary> /// Устройство для печати российских рублей. /// </summary> public class RubleCashMachine : CashMachineBase { /// <summary> /// Количество купюр на одном листе бумаги. /// </summary> private readonly int _countOnPage = 64; /// <summary> /// Номинал купюры. /// </summary> private int _denomination; /// <summary> /// Возможные номиналы валюты. /// </summary> private int[] _correntDenomination = { 50, 100, 200, 500, 1000, 2000, 5000 }; /// <summary> /// Создать новый экземпляр устройства для печати российских рублей. /// </summary> /// <param name="denomination"> Номинал. </param> public RubleCashMachine(int denomination = 1000) : base("Устройство для печати российских рублей") { // Проверяем входные данные на корректность. if(denomination <= 0) { throw new ArgumentException("Номинал валюты должен быть больше нуля.", nameof(denomination)); } if(!_correntDenomination.Contains(denomination)) { throw new ArgumentException($"Некорректный номинал валюты.", nameof(denomination)); } // Устанавливаем значение. _denomination = denomination; } /// <inheritdoc /> public override MoneyBase[] Create(int pageCount) { // Подсчитываем количество денег, которое должно быть напечатано. var count = _countOnPage * pageCount; // Создаем коллекцию для сохранения денег. var money = new List<MoneyBase>(); // Создаем деньги и добавляем в коллекцию. for(var i = 0; i < count; i++) { var ruble = new Ruble(_denomination); money.Add(ruble); } // Возвращаем готовые деньги. return money.ToArray(); } } }
using System; using System.Collections.Generic; using System.Linq; namespace FactoryMethod { public class DollarCashMachine : CashMachineBase { /// <summary> /// Номинал купюры. /// </summary> private int _denomination; /// <summary> /// Создать новый экземпляр устройства для печати американских долларов. /// </summary> /// <param name="denomination"> Номинал. </param> public DollarCashMachine(int denomination = 100) : base("Устройство для печати американских долларов") { // Проверяем входные данные на корректность. if (denomination <= 0) { throw new ArgumentException("Номинал валюты должен быть больше нуля.", nameof(denomination)); } // Возможные номиналы валюты. var correntDenomination = new int[] { 5, 10, 50, 100, 500, 1000 }; // Проверяем корректность номинала. if (!correntDenomination.Contains(denomination)) { throw new ArgumentException($"Некорректный номинал валюты.", nameof(denomination)); } // Устанавливаем значение. _denomination = denomination; } /// <inheritdoc /> public override MoneyBase[] Create(int pageCount) { // Количество купюр на одном листе бумаги. var countOnPage = 50; // Подсчитываем количество денег, которое должно быть напечатано. var count = countOnPage * pageCount; // Создаем коллекцию для сохранения денег. var money = new List<MoneyBase>(); // Создаем деньги и добавляем в коллекцию. for (var i = 0; i < count; i++) { var ruble = new Dollar(_denomination); money.Add(ruble); } // Возвращаем готовые деньги. return money.ToArray(); } } }
И теперь нам остается только воспользоваться нашими классами для производства денег.
using System; using System.Collections.Generic; using System.Text; namespace FactoryMethod { class Program { static void Main(string[] args) { // Устанавливаем кодировку чтобы корректно отобразился символ рубля. Console.OutputEncoding = Encoding.Unicode; // Создаем коллекцию устройств для печати денег. // Обратите внимание, что мы можем хранить их все в одной коллекции. var machines = new List<CashMachineBase> { new RubleCashMachine(), new DollarCashMachine() }; // Создаем коллекцию для хранения денег. // Опять таки здесь могут храниться любые классы, унаследованные от MoneyBase. var money = new List<MoneyBase>(); // Генератор случайных числе. var rnd = new Random(); // По очереди запускаем устройства для печати денег. foreach(var machine in machines) { // Случайное количество листов для разнообразия. var pageCount = rnd.Next(1, 3); // Вызываем фабричный метод для создания валюты. var newMoney = machine.Create(pageCount); // Добавляем созданную валюту в общую коллекцию. money.AddRange(newMoney); } // Выводим данные о деньгах на экран. foreach(var note in money) { Console.WriteLine(note); } Console.ReadLine(); } } }
Как можно увидеть, мы определили фабричный метод в базовом классе устройства, а реализовывали его уже у наследников. Но при этом данный паттерн гарантирует, что любой наследник будет предоставлять метод для создания экземпляра. Это позволяет легко масштабировать приложения, при необходимости добавляя новые реализации, но при этом не изменяя весь остальной код.
Результат работы приложения можно увидеть на изображении ниже

Исходный код приложения доступен в репозитории github.
Также рекомендую изучить статью Паттерн Шаблонный метод (Template method). А еще подписывайтесь на группу ВКонтакте, Telegram и YouTube-канал. Там еще больше полезного и интересного для программистов.