Factory Method дает возможность подклассам создавать некоторые классы с помощью общего интерфейса, причем именно наследники определяют, какой родительский объект следует реализовать. То есть, нужен какой то, общий интерфейс. Этим интерфейсом в языке программирования C# может быть абстрактный
класс либо интерфейс.
Представьте
себе такой абстрактный класс:
abstract class Product
{
public abstract decimal PurchasePrice {get; set;}
public abstract decimal Price {get; set;}
public abstract string Description {get; set;}
}
Если мы унаследуем этот класс, то обязаны придерживаться принципа полиморфизма. То есть, переопределить все свойства этого класса, используя слово override. Давайте так и сделаем. Создаем класс, унаследованный от класса Product, который будет инкапсулировать логику конкретного продукта:
class Computer : Product
{
private decimal _purchase_price;
private decimal _price;
private string _description;
public Computer() : this(null) { }
public Computer(string _description)
: this(_description, 0) { }
public Computer(string _description, decimal _purchase_price)
: this (_description, _purchase_price, 0) { }
public Computer(string _description, decimal _purchase_price,
decimal _price)
{
this._description = _description;
this._purchase_price = _purchase_price;
this._price = _price;
}
public override string Description
{
get { return _description; }
set { _description = value; }
}
public override decimal Price
{
get { return _price; }
set { _price = value; }
}
public override decimal PurchasePrice
{
get { return _purchase_price; }
set { _purchase_price = value; }
}
}
Класс
Product — предназначен для определения интерфейса объектов, создаваемых фабричным методом. Это как бы базовая оболочка для продуктов. Продукт имеет цену и т.д. Если хотите, допишите в класс Product еще пару свойств, методов, и переопределите их в наследуемом классе. Надеюсь что пока все ясно. Прежде чем я продолжу, скажу пару слов о самом паттерне. Обычно мы используем конкретный класс и пишем вот так:
Computer computer = new Computer();
Но, данный шаблон гласит о том, что в тексте программы можно оперировать не какими-то конкретными классами, а их абстрактными представлениями. Вот наиболее популярные случаи, в которых имеет смысл применять обсуждаемый здесь шаблон:
- класс, создающий подклассы, заранее не знает, какими они будут;
- класс был спроектирован так, что создаваемые им объекты специфицируются подклассами;
- класс делегирует свои обязанности одному из вспомогательных подклассов, после чего планируется локализовать знание о том, какой класс принимает эти обязанности на себя;
Вот что имеется ввиду:

На диаграмме добавилось еще два класса. Следуя шаблону, должен быть еще один абстрактный класс, в котором и будет фабричный метод. Это как бы фабрика, которая создает продукты конкретного вида. Когда определен абстрактный класс
Creator с фабричным методом, мы можем создать свой унаследованный класс для конкретного продукта. Чтоб легче
было понять, упростим диаграмму:

Вот и все пироги. Есть абстрактный класс продукта и создателя, в котором есть фабричный метод. Создаем класс конкретного продукта (унаследован от Product) рас, создаем конкретный класс создатель конкретного
продукта (унаследован от Creator) два.
Вот какой вид имеет абстрактный класс Creator:
abstract class Creator
{
public abstract Product FactoryMethod();
public abstract Product FactoryMethod(string _description);
public abstract Product FactoryMethod(string _description,
decimal _purchase_price);
public abstract Product FactoryMethod(string _description,
decimal _purchase_price, decimal _price);
}
В этом классе, я определил методы для всех видов конструкторов класса Computer.
А вот создатель-класс для класса Computer:
class ComputerCreator : Creator
{
public override Product FactoryMethod()
{
return new Computer();
}
public override Product FactoryMethod(string _description)
{
return new Computer(_description);
}
public override Product FactoryMethod(string _description,
decimal _purchase_price)
{
return new Computer(_description, _purchase_price);
}
public override Product FactoryMethod(string _description,
decimal _purchase_price, decimal _price)
{
return new Computer(_description,_purchase_price,_price);
}
}
Все, теперь у нас все как на рис. 1. Видим, что фабричные методы перегружены, для того чтоб была возможность вызвать нужный конструктор класса Computer.
Создадим еще один класс CDPlayer и класс создатель для него аналогичным образом:
Класс CDPlayer:
class CDPlayer : Product
{
private decimal _purchase_price; // цена закупки
private decimal _price; // цена продажи
// масив форматов, которые поддерживет сд плеер
private string _description;
public CDPlayer() : this(null) { }
public CDPlayer(string _description)
: this(_description, 0) { }
public CDPlayer(string _description, decimal _purchase_price)
: this(_description, _purchase_price, 0) { }
public CDPlayer(string _description,
decimal _purchase_price, decimal _price)
{
this._description = _description;
this._purchase_price = _purchase_price;
this._price = _price;
}
public override string Description
{
get { return _description; }
set { _description = value; }
}
public override decimal Price
{
get { return _price; }
set{ _price = value;}
}
public override decimal PurchasePrice
{
get { return _purchase_price; }
set { _purchase_price = value;}
}
}
Создатель-класс для класса CDPlayer:
class CDPlayerCreator : Creator
{
public override Product FactoryMethod()
{
return new CDPlayer();
}
public override Product FactoryMethod(string _description)
{
return new CDPlayer(_description);
}
public override Product FactoryMethod(string _description,
decimal _purchase_price)
{
return new CDPlayer(_description, _purchase_price);
}
public override Product FactoryMethod(string _description,
decimal _purchase_price, decimal _price)
{
return new CDPlayer(_description, _purchase_price, _price);
}
}
Все что нам осталось, это написать клиентский код, что бы полюбоваться нашей работой.
static void Main(string[] args)
{
List<Product> productList = new List<Product>();
Creator[] creators = new Creator[2];
creators[0] = new ComputerCreator();
creators[1] = new CDPlayerCreator();
foreach (Creator cr in creators)
{
if (cr is ComputerCreator)
productList.Add(cr.FactoryMethod("Ноут бук", 600, 800));
if (cr is CDPlayerCreator)
productList.Add(cr.FactoryMethod("audio,mp3,mp4",250,360));
}
foreach (Product pr in productList)
{
Console.WriteLine("Обьект класса {0};\n" +
"Описание: {1};\n" +
"Закупочная цена: {2};\n" +
"Цена продажы: {3};\n",
pr.GetType().Name,
pr.Description,
pr.PurchasePrice,
pr.Price);
}
Console.ReadLine();
}
Вот результат программы:

Как по мне, то тут интересный факт. Экземпляр абстрактного класса создать нельзя. Но никто не говорил, что он не может служить как базовый. Если написать следующим образом, то все будет в порядке:
Product pr = new Computer();
Здесь ссылка pr (абстрактного класса) ссылается на объект класса Computer. По этой причине я спокойно могу создать специализированную коллекцию, как это и было сделано. Когда я увидел этот шаблон, я сразу заметил такие стадии разработки:
- Абстрактный класс для объектов –> напротив класс, который его реализует для своих целей.
- Абстрактный класс для создания объектов –> напротив класс, который его реализует для создания своих обьектов.
Иными словами:
Product — собственно продукт. Предназначен для определения интерфейса объектов, создаваемых фабричным методом;
ConcreteProduct(Computer, CDPlayer
) — конкретные продукты, которые участвуют в схеме, и отвечают за реализацию абстрактного класса (интерфейса) Product.
Creator — создатель, и его название говорит само за себя. Данный объект предназначен для объявления фабричного метода, возвращающего объект типа Product.
ConcreteCreator — конкретный создатель. Здесь все очевидно: конкретная реализация создателя занимается тем, что возвращает конкретный продукт. В нашем примере две конкретные реализации создателя — ComputerCreator и CDPlayerCreator.
Создатель доверяет своим подклассам реализацию подходящего онкретного продукта. В этом и заключается суть Factory Method.
Теперь отметим плюсы и минусы данного паттерна:
Самый очевидный недостаток Factory Method — необходимость создавать наследника Creator всегда, когда планируется получить новый тип продукта (т.е. новый ConcreteProduct). И этого, увы, не избежать. Но подобная проблема присутствует во многих порождающих шаблонах. К достоинствам же следует отнести возможность создавать объекты более универсально, не ориентируясь на конкретные классы и оперируя общим интерфейсом.