Как написать ясный и элегантный код для приложения на основе шаблона стратегии? - PullRequest
3 голосов
/ 09 февраля 2011

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

Interface IGuidoFormattable
Class Note : IGuidoFormattable, ICloneable
Class Pause : IGuidoFormattable, ICloneable
Class Chord : List<Note>, IGuidoFormattable, ICloneable
Class Key : IGuidoFormattable, ICloneable
Class Tempo : IGuidoFormattable, ICloneable
Class Meter : IGuidoFormattable, ICloneable
Class FretPiece : List<IGuidoFormattable>, ICloneable
Class Fret : List<FretPiece>

FretPiece представляет собой музыкальную фразу, часть полного фриза. Он выставляет в качестве свойств Key, Tempo и Meter, которые являются одноименными с их типами. Больше фраз, взятых вместе, создают хаос, представленный классом Fret. Каждый элемент в одной фразе должен быть форматируемым в соответствии со стандартной нотацией GUIDO, поэтому он должен реализовывать интерфейс IGuidoFormattable. В другом пространстве имен определены некоторые классы мутаций, и все они наследуются от одного из двух абстрактных классов:

Class FretMutation
Class LambdaFretMutation : FretMutation

Наконец, существует класс с именем FretMutationGenerator, который имеет единственную задачу - применять выбранные мутации к музыкальной теме и выводить весь фриз в качестве экземпляра класса Fret.

FretPiece должен содержать несколько различных элементов (в данном случае ноты, паузы и аккорды), которые, тем не менее, должны удовлетворять двум ограничениям: они должны быть форматируемыми с помощью записи GUIDO и, следовательно, преобразовываться в значащие строки; они должны быть клонируемыми. В текущем коде каждый класс реализует ICloneable, но синтаксис и семантика текущего кода не гарантируют, что все члены коллекции являются клонируемыми. Мне нужно найти способ выразить оба ограничения без применения наследования к IGuidoFormattable и предпочтительно без определения метода Clone в интерфейсе IGuidoFormattable.

Вторая и самая важная проблема. FretMutation определяет абстрактный метод «Apply», который должен быть переопределен в каждом производном классе. Поэтому любой класс мутаций определяет свою собственную версию этого метода, которая имеет следующую сигнатуру:

FretPiece Apply(FretPiece originalTheme)

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

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

Ответы [ 2 ]

3 голосов
/ 10 февраля 2011

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

А как насчет третьего варианта?

public interface ICloneableAndGuidoFormattable : IGuidoFormattable, ICloneable { }

Тогда ваш FretPiece - это список ICloneableAGuidoFormattable

Если бы не это, вы можете попробовать такую ​​конструкцию:

public interface ICloneable<T>
{
  T Clone();
}

public class FretPiece : IEnumerable<IFormattable>, ICloneable<FretPiece>
{
    private List<IFormattable> items = new List<IFormattable>();

    public void Add<T>(T value) where T : IFormattable, ICloneable<IFormattable>
    {
        items.Add(value);
    }

    public IEnumerator<IFormattable> GetEnumerator()
    {
        items.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }

    public FretPiece Clone()
    {
        return new FretPiece { items = new List<IFormattable>(
            items.Cast<ICloneable<IFormattable>>().Select(c=>c.Clone()))
        };
    }
}

И где-то еще, например. на вашем мутаторе:

public T Apply<T>(T fretPiece) where T : IEnumerable<IFormattable>, ICloneable<T> ( ...)

Это обеспечит возможность добавления только элементов, реализующих оба интерфейса. перечисление только предполагает, что возвращаются IFormattables. Это позволило бы вам внутри приведения безопасно привести к ICloneable, так как он должен был пройти ограничение типа на «Добавить». Вы можете увидеть реализацию клона. Даже если у вас есть актерский состав, это безопасно, если кто-то не возится с items на основе отражения;)

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

Я бы сделал несколько из них неизменными. Таким образом, вам больше не нужно их клонировать. Заметка - это всегда одна и та же заметка, независимо от того, где она появляется. Поэтому вполне естественно сделать его неизменным.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...