Использование шаблона Factory или Builder с неуниверсальным классом продукта?

Добрый день.

Я пытаюсь выяснить, какой шаблон я могу использовать для какой-то новой разработки, но я увязаю. Сначала я подумал о шаблоне Factory, но потом, возможно, о шаблоне Builder, но ни один из них не подходит идеально.

Моя проблема в том, что базовый класс Product в шаблонах не является универсальным. Кажется, что все в шаблонах Factory и Builder требует, чтобы базовые продукты были в точности одинаковыми.

Например (действительно тупой пример):

public class ProductBase
{
    public int x;
    public int y;
}

public class ProductA : ProductBase
{
    public int z;
}

public class ProductB : ProductBase
{
    public int a;
}

Я думал, что могу использовать фабричный метод для создания продукта и возврата его вызывающей стороне, а затем просто заставить вызывающую сторону установить уникальные данные о продукте, как только я его получу, но для этого требуется приведение класса продукта к определенному типу, чтобы установить конкретные данные типа. Однако мне все это кажется подозрительным, потому что, если я уже знаю, какой тип мне нужен, неужели я просто усложняю ситуацию шаблоном?

В любом случае, любые рекомендации относительно того, какой подход можно использовать здесь, будут приветствоваться. В конечном счете, я ищу некоторую модификацию этих шаблонов (или вообще другую), чтобы разрешить неуниверсальные продукты. Или, может быть, установка данных о типе продукта после создания с помощью шаблона не так уж и зла?

ОБНОВЛЕНИЕ: я должен был упомянуть, что мне нужно установить данные для конкретного типа (z или a) во время выполнения, поскольку они передаются через веб-форму. Таким образом, я не могу предоставить Factory или Builder создание полного экземпляра, мне все еще нужен какой-то способ установки данных постфактум (или во время создания экземпляра, но, похоже, это не имеет хорошего универсального решения).


person ChrisC    schedule 21.09.2011    source источник
comment
Нужен ли клиенту (завода) доступ к свойствам z и a или клиент может запрограммировать ProductBase?   -  person Steven    schedule 21.09.2011
comment
Я не понимаю, как вы можете использовать установщик для конкретного типа, не зная базового типа?   -  person Daniel    schedule 21.09.2011
comment
Действительно, клиенту нужно как-то задать свойства Z или a. Вверху у меня есть веб-форма, в которой мы устанавливаем информацию, а под ней я предусмотрел использование фабрики для получения объекта. Это прекрасно работало, пока я не понял, что хотя продукты имеют общую основу, они не обладают всеми свойствами.   -  person ChrisC    schedule 21.09.2011
comment
Дэниел, да, это моя проблема. Я должен знать базовый тип, так что разве это не убивает идею Фабрики или Строителя?   -  person ChrisC    schedule 21.09.2011
comment
Зачем вам нужен завод или строитель? Вы можете просто вызвать базовый c'tor, чтобы избежать дублирования c'tor, но если клиенту нужен конкретный экземпляр продукта - передайте его ему   -  person Amittai Shapira    schedule 21.09.2011
comment
Амиттай – Это моя мысль. Сначала казалось, что продукты будут общего класса, следовательно, это фабрика, но теперь, похоже, мне нужно знать конкретный тип. Я подумал, что задам вопрос о шаблонах, но в конечном итоге я не хочу впихивать фабричный шаблон там, где он неуместен.   -  person ChrisC    schedule 21.09.2011


Ответы (2)


Что плохого в наличии одного класса абстрактных фабричных методов и одного конкретного класса фабричных методов для каждого продукта?

public class ProductBase
{
    public int x;
    public int y;
}

public class ProductA : ProductBase
{
    public int z;
}

public class ProductB : ProductBase
{
    public int a;
}
public class Factory
{
    abstract Product Create();
}
public class FactoryA:Factory
{
    override Product Create()
    {
         return new ProductA();
    }
}
public class FactoryB:Factory
{
    override Product Create()
    {
       return new ProductB();
    }
}

Это для общего случая и внешнего параметра.

 internal static class Program
    {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        private static void Main()
        {
            var fact = new FactoryA();
            int passParam = 5;//fromGUI
            ProductA f = fact.Create(passParam);
        }
    }

    public class ProductBase
    {
        public int x;
        public int y;
    }

    public class ProductA : ProductBase
    {
        public int z;
    }

    public class ProductB : ProductBase
    {
        public int a;
    }

    public abstract class Factory<T> where T : ProductBase
    {
        public abstract T Create(int passedParam);
    }

    public class FactoryA : Factory<ProductA>
    {
        public override ProductA Create(int passedParam)
        {
            return new ProductA() { x = 1, y = 1, z = passedParam };
        }
    }

    public class FactoryB : Factory<ProductB>
    {
        public override ProductB Create(int passedParam)
        {
            return new ProductB() { x = 1, y = 1, a = passedParam };
        }
    }
person Yurii Hohan    schedule 21.09.2011
comment
Спасибо за ответ, Хоххи, но я не уверен, что это решает. он действительно переносит ответственность за создание в фабричный класс от клиента, но мы не используем фабрику для возврата какого-либо экземпляра универсального типа, мы должны указать тип используемой фабрики, который, я считаю, отбрасывает концепцию шаблона. Кроме того, мне все еще приходится отбрасывать продукт, когда я получаю его обратно, чтобы установить данные для конкретного продукта. Кроме того, см. мой комментарий к ответу Николы, поскольку я должен был упомянуть, что устанавливаемые данные передаются только во время выполнения. - person ChrisC; 21.09.2011
comment
Вы должны указать только динамическую часть, которая может быть изменена во время выполнения, что, насколько я знаю, является обычной практикой. - person Yurii Hohan; 21.09.2011

Если у вас есть контекст, в котором вам нужно использовать экземпляры ProductA и ProductB, как если бы они были одного типа, тогда имеет смысл предоставить эти экземпляры из одной функции, которая принимает в качестве аргументов то, что необходимо для решения, какой конкретный класс создавать и имеет средства для извлечения любого объекта, необходимого для правильного создания таких экземпляров. В этом случае все атрибуты должны быть установлены из этой функции, чтобы избежать включения конкретных типов для завершения построения.

Если вы чувствуете, что процесс построения слишком сложен, разделите его на модули:

class ProductFactoryBase {
  protected void Populate(ProductBase p) {
    p.x = some_x;
    p.y = some_y;
  }
}

class ProductAFactory : ProductFactoryBase {
  public ProductBase Create() {
    ProductA p = new ProductA();
    populate(p);
    p.z = some_z;
    return p;
  }
}

class ProductBFactory : ProductFactoryBase {
  public ProductBase Create() {
    ProductB p = new ProductB();
    populate(p);
    p.a = some_a;
    return p;
  }
}

class ProductFactory {
  private ProductAFactory paf = new ProductAFactory();
  private ProductBFactory pbf = new ProductBFactory();

  ProductBase Create(bool create_A) {
    if ( create_A )
      return paf.Create();
    else
      return pbf.Create();
  }
}

Однако, если вам действительно не нужно использовать ProductAs и ProductBs, как если бы они были одним и тем же, не обобщайте только ради этого; просто создавайте и используйте экземпляры каждого типа, когда они вам нужны.

Между этими двумя крайностями вы могли бы подумать о том, чтобы сделать ProductFactoryBase универсальным, чтобы учесть общность в процессе построения, даже если ваши классы на самом деле не нуждаются в общей базе. В этом случае у вас не будет общей фабрики, такой как ProductFactory выше.

Надеюсь, что это ответ на ваш вопрос.

person Nicola Musatti    schedule 21.09.2011
comment
Никола, спасибо за это. Концепция имеет смысл, но моя проблема заключается в том, что значения some_a или some_z необходимо передавать во время выполнения, поскольку они являются значениями, передаваемыми из веб-формы (я должен был упомянуть об этом в первую очередь). Я согласен с вашим утверждением о том, что не следует принуждать к обобщению, если оно не подходит. Мне придется оглянуться на продукты, чтобы увидеть, можно ли передать все свойства в класс продуктов bae. Если нет, возможно, мне просто придется работать с каждым конкретным типом. - person ChrisC; 21.09.2011
comment
Я не вижу ничего плохого в том, чтобы some_a и some_z передавались в качестве аргументов в ProductFactory.Create() или были доступны для него другими способами, например. другими конкретными фабриками, которые являются частными для ProductFactory. - person Nicola Musatti; 21.09.2011
comment
Для меня суть в том, чтобы отделить строительство сложных объектов от их использования: автомобиль не автомобильный завод! - person Nicola Musatti; 21.09.2011
comment
Как упоминалось в моем ответе Хоххи, мне следовало использовать более сложный пример. Если бы это была строка a и int b, действительно ли мне теперь нужны две отдельные версии ProductFactory.Create()? Кроме того, когда я добавляю новые продукты, я думаю, что постоянное добавление новых версий Create() для учета каждого другого продукта не кажется очень гибким. - person ChrisC; 21.09.2011
comment
Фабрики имеют смысл, если создание объекта представляет собой многоэтапный процесс, который, возможно, включает в себя получение данных из других источников, например. СУБД. Если все, что вы делаете, это new SomeClass(), вам не нужны фабрики. Мой подход — одна фабрика на класс, возможно, с использованием других фабрик для сбора объектов, необходимых во время строительства. Если вы планируете переключать фабрики, рассмотрите возможность использования некоторой инверсии библиотеки управления, в противном случае не будьте более гибкими, чем вы ожидаете. - person Nicola Musatti; 21.09.2011
comment
Честная оценка. Как я упоминал в начале, я волновался, что пытаюсь впихнуть это в какой-то элегантный шаблон дизайна, но по мере роста требований мне показалось, что имеет смысл просто создать его самому. Это действительно будет довольно простой класс, и я буду явно устанавливать все значения, поэтому я думаю, что фабричный шаблон здесь излишен. Спасибо за помощь! - person ChrisC; 21.09.2011