1

У меня есть иерархия классов, отвечающая за разбор или сопоставление одной модели (или сообщения) с другой. Он имеет нетривиальную логику.Добавление метода к середине иерархии классов по принципу O/C

internal interface IMessageParser<T, K> 
    where T : class 
    where K : class 
{ 
    K Serialize(T originalMessage); 
    T Deserialize(K concreteMessage); 
} 

internal abstract class OriginalToConcreteMessageParser : IMessageParser<OriginalMessage, ConcreteMessage> 
{ 
    // some private methods that do stuff and are called in the Serialize() method 

    public virtual ConcreteMessage Serialize(OriginalMessage originalMessage) 
    { 
     // some stuff 
    } 
} 

Есть 21 из этих конкретных анализаторов:

internal sealed class OriginalToConcreteMessageParserFooMessageParser : OriginalToConcreteMessageParser 
{ 
} 

internal sealed class OriginalToConcreteMessageParserBarMessageParser : OriginalToConcreteMessageParser 
{ 
} 

Я хочу, чтобы добавить новый частный метод OriginalToConcreteMessageParser и назвать его в Serialize(). Назовем этот метод Baz().

Я мог бы создать OriginalToConcreteBazMessageParser и сделать все 21 конкретные реализации наследовать от этого, но я бы предпочел не делать этого.

Функциональность, которую предоставляет Baz(), определенно находится на уровне абстракции OriginalToConcreteMessageParser.

В заключение, я хочу ввести метод в OriginalToConcreteMessageParser и позвонить ему в Serialize(), не касаясь OriginalToConcreteMessageParser.

+0

Вы просто ответить на свой вопрос в этом последнем пункте? Все, что отсутствует, это код. –

ответ

4

Я думаю, что вы могли бы попробовать реализацию decorator pattern, или, может быть, strategy pattern

Декоратор, имеет эту мотивацию, что более или менее то же самое, что у вас есть:

В качестве примера рассмотрим окно в системе окон. Чтобы разрешить прокрутку содержимого окна, мы можем добавить горизонтальные или вертикальные полосы прокрутки к нему, если это необходимо. Предположим, что окна представлены экземплярами класса Window, и предположим, что этот класс не имеет функций для добавления полос прокрутки. Мы могли бы создать подкласс ScrollingWindow, который их предоставляет, или мы могли бы создать ScrollingWindowDecorator, который добавляет эту функциональность к существующим объектам Window . На данный момент любое решение будет в порядке. Теперь предположим, что мы также желаем добавить границы в наши окна. Опять же, наш оригинальный класс Window не имеет поддержки. Подкласс ScrollingWindow представляет собой проблему, поскольку он имеет , эффективно созданный новый вид окна. Если мы хотим добавить границу для всех окон, мы должны создать подклассы WindowWithBorder и ScrollingWindowWithBorder. Очевидно, эта проблема ухудшается с каждой новой функцией, которую нужно добавить. Для решения декоратора просто создадим новый BorderedWindowDecorator - во время выполнения, мы можем украсить существующие окна с помощью ScrollingWindowDecorator или BorderedWindowDecorator или оба, как мы считаем нужным.

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

Со стратегией вы можете легко переключать определенное поведение класса. Хорошо, когда только думают, что изменения - это функция, и поведение обычно не складывается, а просто различается между различными реализациями.Допустим, что все классы имеют общее поведение, но в момент сериализации может выполняться несколько несколько разных операций. Как с этим справиться? вы сделаете свой IMessageParser способным получить стратегию синтаксического анализа (объект, реализующий интерфаз, возможно, только функцию, поэтому весь код, который вы планировали включить в BAZ(), будет находиться в объекте стратегии). И в каждом конкретном классе, если стратегия присутствует, функция serialize использует ее. Если стратегия равна null, конкретный класс просто использует поведение по умолчанию.

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

Я бы использовал стратегию. Вы создаете интерфейс SerializeStrategy с использованием метода execute. И затем один или несколько конкретных классов, реализующих этот интерфейс. Затем вы определяете метод setStrategy в интерфейсе IMessageParser и реализуете его в базовом классе OriginalToConcreteMessageParser или любым другим на этом уровне и сохраняете там объект стратегии. В дочерних классах просто проверьте, есть ли стратегия для использования.

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

, как мы можем прочитать в одной и той же ссылке выше:

Это позволяет лучше развязку между поведением и классом, использует поведение. Поведение может быть изменено без нарушения классов , которые его используют, и классы могут переключаться между поведением на , изменяя используемую реализацию, не требуя каких-либо значительных изменений кода . Поведение также может быть изменено во время выполнения как так же, как и во время разработки. Например, поведение тормоза автомобиля может быть изменено с BrakeWithABS() на Тормоз() путем изменения члена тормоза B12Vна: brakeBehavior = new Brake(); Это дает большую гибкость при проектировании и находится в гармонии с открыто/закрыто принцип (OCP)

0

Вы можете использовать делегат для этого, но вы, очевидно, придется изменить подпись метода:

internal abstract class OriginalToConcreteMessageParser : IMessageParser<OriginalMessage, ConcreteMessage> 
{ 
    public virtual ConcreteMessage Serialize(OriginalMessage originalMessage, Func<OriginalMessage, ConcreteMessage> baz) 
    { 
     return baz(originalMessage); 
    } 
} 

Можно по желанию добавить перегруженные Serialize ваши конкретные классы, инъекционных Baz метода:

OriginalToConcreteMessageParserFooMessageParser:

internal sealed class OriginalToConcreteMessageParserFooMessageParser : OriginalToConcreteMessageParser 
{ 
    public ConcreteMessage Serialize(OriginalMessage originalMessage) 
    { 
     Func<OriginalMessage, ConcreteMessage> baz = message => 
      { 
       ConcreteMessage foo = ToFoo(message); 

       return foo; 
      }; 

     return base.Serialize(originalMessage, baz); 
    } 
} 

OriginalToConcreteMessageParserBarMessageParser:

internal sealed class OriginalToConcreteMessageParserBarMessageParser : OriginalToConcreteMessageParser 
{ 
    public ConcreteMessage Serialize(OriginalMessage originalMessage) 
    { 
     Func<OriginalMessage, ConcreteMessage> baz = message => 
     { 
      ConcreteMessage bar = ToBar(message); 

      return bar; 
     }; 

     return base.Serialize(originalMessage, baz); 
    } 
}