Полиморфизм дизайн вопрос - PullRequest
8 голосов
/ 03 февраля 2011

Прежде всего, извините за длинный вопрос, но я не мог написать его короче:)

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

Например, наклейка с кодом XX0001 означает, что после нее должны быть только нормальные нумерованные коды (от NN0001 до NN9999), всегда один и тот же номер.Код QC0001 говорит нам, что следующие 10 кодов (от QC0001 до QC0010) должны перейти к контролю качества.

Я разработал приложение так, чтобы каждый тип наклейки был своего класса - NormalSticker, BadSticker, ControllSticker, QualitySticker, ... Все они наследуются от класса SticerBase, который содержит некоторые общие данные для всех из них (качество сканирования, дата и время сканирования, содержимое кода).Экземпляры этих классов создаются в статическом классе Parser, который проверяет код и возвращает нам соответствующий объект.

Все это работает нормально, но теперь я остановился.У меня также есть класс Roll, который имеет набор стикеров, реализованных как List<StickerBase>.. Этот класс имеет открытый метод AddSticker(StickerBase), с помощью которого мы добавляем стикеры в рулон.Но этот метод должен содержать некоторую логику, например, если мы получим код XX001, то следующие наклейки 9999 должны быть от NN0001 до NN9999.Единственный вариант, который я вижу здесь, - это принимать решения на основе типа наклейки, например:

public void AddSticker(StickerBase sticker)
{
    if (sticker.GetType().Equals(typeof(StickerNewRoll)))
    {
        // Next 9999 sticker should be in the form of NN0001 to NN9999
    }

    if (sticker.GetType().Equals(typeof(EnumeratedSticker)))
    {
        // Add 9999 stickers to the list, other business logic...
    }

    if (sticker.GetType().Equals(typeof(QualitySticker)))
    {
        // Stop the machine and notify the worker
    }
}

Я был бы очень удивлен, если это правильный подход.Есть идеи?

Правка - возможное решение: потому что для каждой наклейки я знаю, как должна выглядеть следующая наклейка, я могу добавить новый метод public Sticker NextStickerShouldLookLike() метод к каждому Sticker классу.В логике проверки (аналогично ответу Петера Торока ) я могу просто проверить, совпадает ли текущая наклейка с previousSticker.NextStickerShouldLookLike().Метод Validate будет иметь два входных параметра - текущий и предыдущий стикер.

Ответы [ 7 ]

6 голосов
/ 03 февраля 2011

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

В первом случае вы можете добавить полиморфный метод GetAssociatedStickers() к классам наклеек, который возвращает набор наклеек с NN0001 по NN9999 на наклейку с кодом XX001 и т. д.Затем вы можете добавить этот набор наклеек сразу после контрольной наклейки.

Обновление

Для проверки вы можете иметь новый интерфейс StickerValidator и метод GetValidator вваши стикерыСпециальные стикеры будут возвращать правильный объект валидатора (который может быть реализован как анонимный класс или внутренний класс), в то время как обычные стикеры будут возвращать null.Тогда AddSticker можно изменить, чтобы он выглядел примерно так:

public void AddSticker(StickerBase sticker)
{
    if (sticker.GetValidator() != null)
    {
        this.validator = sticker.GetValidator();
        // add the sticker to the list
    }
    else
    {
        if (this.validator == null || this.validator.validate(sticker))
        {
            // add the sticker to the list
        }
        else
        {
            // set error state
        }
    }
}
4 голосов
/ 03 февраля 2011

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

Если возможно, поместите свою логику в реализации Sticker.Иметь абстрактный метод в классе StickerBase и переопределять в подклассах.Таким образом, вам нужно только вызвать метод в методе AddSticker и не знать, какой тип стикера добавлен.

Если это невозможно, и вам действительно нужно иметьВ вашем коде, касающемся различных типов наклеек в вашем классе Roll, вы можете рассмотреть шаблон Visitor .Постарайтесь избежать этого и, если возможно, используйте первый подход.

2 голосов
/ 03 февраля 2011

Похоже, шаблон Visitor (возможно, с шаблоном Iterator) является идеальным кандидатом здесь.

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

Сначала вы должны создать интерфейс посетителя (или базовый класс) для иерархии наклеек.

public interface IStickerVisitor
{
    void Visit(NewRollSticker sticker);
    void Visit(EnumeratedSticker sticker);
    void Visit(QualitySticker sticker);
    //need a method for every kind of sticker here
}

Затем вам нужно добавить абстрактный класс Accept в ваш класс StickerBase, который принимаетa Visitor в качестве параметра, как показано ниже;

public abstract void Accept(IStickerVisitor visitor);

содержание этого метода в конкретных классах должно просто соответствовать приведенному ниже;

public abstract void Accept(IStickerVisitor visitor)
{
     visitor.Visit(this);
}

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

public class StickerRollerVisitor : IStickerVisitor
{
    private RollList rollList;
    public StickerRollerVisitor(RollList list)
    {
        this.rollList = list;
    }
    public void Visit(NewRollSticker sticker)
    {
        // Next 9999 sticker should be in the form of NN0001 to NN9999
    }
    public void Visit(EnumeratedSticker sticker)
    {
        // Add 9999 stickers to the list, other business logic...
    }
    public void Visit(QualitySticker sticker)
    {
        // Stop the machine and notify the worker
    }
}

В этой реализации посетителя вы можете принять Итератор вместо исходного списка для перехода (вперед, назад, пропустить и т. Д.) В списке с пользовательской стратегией.

После создания значения по умолчанию StickerRollerVisitor в вашем RollList или принятия посетителя в качестве параметра конструктора ваш код может выглядеть следующим образом:

private StickerRollerVisitor rollListStickerVisitor;
public void AddSticker(StickerBase sticker)
{
    sticker.Accept(rollListStickerVisitor)
}

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

2 голосов
/ 03 февраля 2011

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

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

Первый вопрос: учитывая, что какой-то класс отвечает за прикрепление наклеек к рулону (класс Parser?), Можете ли вы оставить рулон полностью амбивалентным, к каким наклейкам он относится?получать, и разместить свою логику в другом месте?Является ли логика того, что делать, когда наклейка типа X, действительно что-то, о чем нужно знать рулону, учитывая, что это не рулон, который выходит и получает наклейки?

Второе: насколько полиморфны вашинаклейки?У них есть разные методы?У них разные свойства?Или они настолько похожи, что вы можете просто нанести ярлык на класс StickerBase?

Третий: стоит ли наклейкам указывать рулону, что делать?То есть, если в броске вызывается метод sticker.TellMeWhatToDoNextPlease() (реализованный как виртуальный метод и ovveridden в производных классах наклеек) - тем более, что бросок не отвечает за то, что люди пытаются приклеить на него.Вы можете задать тот же вопрос классу, который отвечает за нанесение наклеек на рулон.Вы можете научить обезьяну делать это и позволить рулону понять это, или вы можете поместить свою логику туда (если это место, где делаются наклейки, оно должно уже знать, что она делает) и позволить рулону принять то, с чем он застрял..

В основном - что контролирует ваш процесс?Рулон бумаги, на котором наклеены наклейки, сами наклейки, или что еще делает и наклеивает наклейки?

2 голосов
/ 03 февраля 2011

Создать виртуальную функцию GetNextStickerLabel (); и реализовать его по-разному в любом типе наклейки. Как правило, если вам нужно спросить, какой тип является объектом, и принять решение (если) о нем, то в дизайне есть что-то неправильное .

0 голосов
/ 03 февраля 2011

У вас есть хороший способ решить эту проблему.

Вы можете реализовать перечисление видов меток и атрибутов.

public enum StickerKinds
{
     NewRoll, Enumerated, Quality
}

public class StickedAttribute : Attribute
{
      public StickedAttribute(StickerKinds kind)
      {
           _kind = kind;
      }

      private readonly StickerKinds _kind;

      public StickerKinds Kind 
      {
           get { return _kind; }
      }
}

Теперь у вас может быть класс с именем "StickerLabeler"который реализует метод с именем «NextLabel», принимающий входной параметр типа StickerKinds, и возвращает строку, представляющую всю метку в виде текста.

И, наконец, метод расширения для простого получения атрибутов:

public static class ObjectExtensions
{
    public static TAttribute GetAttribute<TAttribute>(this object source)
        where TAttribute : Attribute
    {
        if (source != null)
        {
            object[] attributeSearchResult = source.GetType().GetCustomAttributes(typeof(TAttribute), true);

            if (attributeSearchResult.Length > 0)
            {
                return (TAttribute)attributeSearchResult.Single();
            }
            else
            {
                return default(TAttribute);
            }
        }
        else
        {
            return default(TAttribute);
        }
    }
}

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

public void AddSticker(StickerBase sticker)
{
    sticker.Label = StickerLabeler.NextLabel(sticker.GetAttribute<StickedAttribute>().Kind);

    // TODO: Implement code to add the sticker to the store.
}

РЕДАКТИРОВАТЬ: я забыл упомянуть, что вы будете использовать StickedAttribute в ваших подклассах StickerBase:

[Sticked(StickerKinds.NewRoll)]
public class StickerNewRoll
{ 
...
}

ОБНОВЛЕНИЕ: Ну, на самом деле, нет никаких причин для подкласса больше.У вас может быть класс стикера, и этот атрибут будет определять тип стикера.

ОБНОВЛЕНИЕ 2: Вы можете сделать еще одно улучшение.Реализуйте свойство «только для чтения» в классе стикеров «Kind», который может читать атрибут, и он будет возвращать значение перечисления StickerKinds так называемого атрибута, поэтому теперь ваш код может быть еще чище:

public void AddSticker(StickerBase sticker)
{
    sticker.Label = StickerLabeler.NextLabel(sticker.Kind);

    // TODO: Implement code to add the sticker to the store.
}

ОБНОВЛЕНИЕ 3: Андреас - комментатор моего ответа - заставил меня подумать, что вам может потребоваться создание подклассов, потому что каждый вид стикера будет иметь свои собственные свойства и так называемый атрибут будет применяться к этим производным классам.

0 голосов
/ 03 февраля 2011

Я бы все равно переписал это так:

if (sticker is StickerNewRoll)
{
    // Next 9999 sticker should be in the form of NN0001 to NN9999
}
else if (sticker is EnumeratedSticker)
{
    // Add 9999 stickers to the list, other business logic...
}
else if (sticker is QualitySticker)
{
    // Stop the machine and notify the worker
}

...

...