Фабричный класс знает слишком много - PullRequest
2 голосов
/ 30 марта 2009

ОБНОВЛЕНО Я обновил пример, чтобы лучше проиллюстрировать мою проблему. Я понял, что в нем отсутствует один конкретный момент, а именно тот факт, что метод CreateLabel() всегда принимает тип метки, чтобы фабрика могла решить, какой тип метки создать. Дело в том, что может потребоваться получить больше или меньше информации в зависимости от того, какой тип метки он хочет вернуть.

У меня есть фабричный класс, который возвращает объекты, представляющие этикетки, для отправки на принтер.

Фабричный класс выглядит так:

public class LargeLabel : ILabel
{
    public string TrackingReference { get; private set; }

    public LargeLabel(string trackingReference)
    {
        TrackingReference = trackingReference;
    }
}

public class SmallLabel : ILabel
{
    public string TrackingReference { get; private set; }

    public SmallLabel(string trackingReference)
    {
        TrackingReference = trackingReference;
    }
}

public class LabelFactory
{
    public ILabel CreateLabel(LabelType labelType, string trackingReference)
    {
        switch (labelType)
        {
            case LabelType.Small:
                return new SmallLabel(trackingReference);
            case LabelType.Large:
                return new LargeLabel(trackingReference);
        }
    }
}

Скажите, что я создаю новый тип метки, который называется CustomLabel. Я хочу вернуть это с завода, но для этого нужны дополнительные данные:

public class CustomLabel : ILabel
{
    public string TrackingReference { get; private set; }
    public string CustomText { get; private set; }

    public CustomLabel(string trackingReference, string customText)
    {
        TrackingReference = trackingReference;
        CustomText = customText;
    }
}

Это означает, что мой фабричный метод должен измениться:

public class LabelFactory
{
    public ILabel CreateLabel(LabelType labelType, string trackingReference, string customText)
    {
        switch (labelType)
        {
            case LabelType.Small:
                return new SmallLabel(trackingReference);
            case LabelType.Large:
                return new LargeLabel(trackingReference);
            case LabelType.Custom:
                return new CustomLabel(trackingReference, customText);
        }
    }
}

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

Как правильно реализовать этот сценарий?

Ответы [ 8 ]

5 голосов
/ 30 марта 2009

Ну, как вы хотите вызвать заводской метод?

Сконцентрируйтесь на том, как вы хотите иметь возможность использовать свой API, и реализация, как правило, будет достаточно ясной. Это станет еще проще, если вы напишите желаемые результаты вашего API в виде модульных тестов.

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

2 голосов
/ 30 марта 2009

Как насчет использования фабричного метода, чтобы решить, какой ярлык вам нужен?

public class LabelFactory {
    public ILabel CreateLabel(string trackingReference, string customText) {
        return new CustomLabel(trackingReference, customText);
    }

    public ILabel CreateLabel(String trackingReference) {
        return new BasicLabel(trackingReference);
    }
}

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

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

1 голос
/ 02 апреля 2009

ОТВЕТ ОБ ОБНОВЛЕНИИ ПОСЛЕ ОБНОВЛЕНИЯ ВОПРОСА - СМ. НИЖЕ

Я все еще думаю, что вы правы, используя шаблон Factory и исправляя перегрузку метода CreateLabel; но я думаю, что передавая LabelType методу CreateLabel, вы упускаете смысл использования шаблона Factory.

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

Логика в вашем коде в точке, где вы вызываете Factory, должна выглядеть примерно так: псевдокод ...

// Need to create a label now
ILabel label;
if(we need to create a small label)
{
   label = factory.CreateLabel(LabelType.SmallLabel, "ref1");
}
else if(we need to create a large label)
{
   label = factory.CreateLabel(LabelType.LargeLabel, "ref1");
}
else if(we need to create a custom label)
{
   label = factory.CreateLabel(LabelType.CustomLabel, "ref1", "Custom text")
}

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

  1. Изменить заводской код для работы с новым значением LabelType
  2. Идите и добавьте новое else-if везде, которое фабрика называет

Однако, если вы переместите логику, которая выбирает значение LabelType, на вашу фабрику, вы избежите этого. Логика заключена в фабрике вместе со всем остальным. Если в вашей системе добавлен новый тип метки, вам нужно всего лишь изменить Фабрику. Весь существующий код, вызывающий Factory, остается прежним, без каких-либо серьезных изменений.

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

Ваши классы Factory и label могут выглядеть следующим образом ...

// Unchanged
public class BasicLabel: ILabel
{
    public LabelSize Size {get; private set}
    public string TrackingReference { get; private set; }

    public SmallLabel(LabelSize size, string trackingReference)
    {
        Size = size;
        TrackingReference = trackingReference;
    }
}



// ADDED THE NULL OR EMPTY CHECK
public class CustomLabel : ILabel
{
    public string TrackingReference { get; private set; }
    public string CustomText { get; private set; }

    public CustomLabel(string trackingReference, string customText)
    {
        TrackingReference = trackingReference;
        if(customText.IsNullOrEmpty()){
           throw new SomeException();
        }
        CustomText = customText;
    }
}



public class LabelFactory
{
    public ILabel CreateLabel(string trackingReference, LabelSize labelSize)
    {
         return new BasicLabel(labelSize, trackingReference);
    }

    public ILabel CreateLabel(string trackingReference, string customText)
    {
         return new CustomLabel(trackingReference, customText);
    }
}

Надеюсь, это полезно.

1 голос
/ 30 марта 2009

Вы пытаетесь заставить шаблон в сценарий, в котором он не подходит. Я бы предложил отказаться от этого конкретного паттерна и сосредоточиться вместо того, чтобы делать самое простое возможное решение.

Я думаю, что в этом случае у меня был бы только один класс, Label, у которого есть текстовое поле для пользовательского текста, обычно нулевое / пустое, но которое можно установить, если метка должна быть пользовательской. Это просто, не требует объяснений и не доставит вашим программистам никаких кошмаров.

public class Label
{
    public Label(string trackingReference) : this(trackingReference, string.Empty)
    {
    }

    public Label(string trackingReference, string customText)
    {
        CustomText = customText;
    }

    public string CustomText ( get; private set; }

    public bool IsCustom
    {
        get
        {
            return !string.IsNullOrEmpty(CustomText);
        }
    }
}
1 голос
/ 30 марта 2009

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

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

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

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

1 голос
/ 30 марта 2009

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

Для приведенного вами примера я напишу BasicLabelConfiguration и производную от нее CustomLabelConfiguration. BasicLabelConfiguration захватывает ссылку отслеживания, в то время как CustomLabelConfiguration захватывает пользовательский текст.

Наконец, фабрика принимает решение на основе типа переданного объекта конфигурации.


Вот пример кода:

public class BasicLabelConfiguration
{
  public BasicLabelConfiguration()
  {
  }

  public string TrackingReference { get; set; }
}

public class CustomLabelConfiguration : BasicLabelConfiguration
{
  public CustomLabelConfiguration()
  {
  }

  public string CustomText { get; set; }
}

public class LabelFactory
{
  public ILabel CreateLabel(BasicLabelConfiguration configuration)
  {
    // Possibly make decision from configuration
    CustomLabelConfiguration clc = configuration as CustomLabelConfiguration;
    if (clc != null)
    {
      return new CustomLabel(clc.TrackingReference, clc.CustomText);
    }
    else
    {
      return new BasicLabel(configuration.TrackingReference);
    }
  }
}

Наконец, вы бы использовали фабрику так:

// Create basic label
ILabel label = factory.CreateLabel(new BasicLabelConfiguration 
{
  TrackingReference = "the reference"
});

или

// Create basic label
ILabel label = factory.CreateLabel(new CustomLabelConfiguration 
{
  TrackingReference = "the reference",
  CustomText = "The custom text"
});
1 голос
/ 30 марта 2009

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

0 голосов
/ 30 марта 2009

После прочтения вашего вопроса кажется, что ваш пользовательский интерфейс собирает информацию, а затем использует фабрику для создания соответствующей метки. Мы используем другой подход в приложении CAD / CAM, которое я разрабатываю.

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

В первом случае инициализация выполняется в заводском методе. Поэтому я создаю три экземпляра FlatPartLabel, передавая необходимые параметры.

Во втором случае интерфейс Label имеет опцию конфигурации. Это вызывается диалоговым окном принтера этикеток для заполнения панели настройки. В вашем случае именно здесь будет передана ссылка отслеживания и CustomText.

Мой интерфейс меток также возвращает уникальный идентификатор для каждого типа меток. Если бы у меня была определенная команда для работы с этим типом метки, я бы просмотрел список меток в моем приложении, чтобы найти, какая из них совпадает с идентификатором, привел ее к метке определенного типа и затем настроил ее. Мы делаем это, когда пользователь хочет напечатать одну этикетку только для определенной плоской детали.

Это означает, что вы можете быть произвольно сложными в параметрах, которые нужны вашим ярлыкам, и не обременять вашу фабрику необязательными параметрами.

...