Принимаете ли вы интерфейсы в качестве параметров конструктора?


Применима ли рекомендацияКшиштофа к конструкторам? Если да, то как вы его правильно реализуете?

Мы рекомендуем использовать Collection, ReadOnlyCollection или KeyedCollection для выходных данных и свойств и интерфейсов IEnumerable, ICollection, IList для входных данных.

Например,

public ClassA
{
    private Collection<String> strings;
    public Collection<String> Strings { get { return strings; } }
    public ClassA(IEnumerable<String> strings)
    {
        this.strings = strings; // this does not compile
        this.strings = strings as Collection<String>; // compiles (and usually runs?)
    }
}
7 2

7 ответов:

Вы должны избегать понижающей передачи; в вашем примере я бы предложил, чтобы тип вашего параметра contructor соответствовал (должен быть таким же) типу ваших данных-членов (т. е. либо Collection<String> или IEnumerable<String>).

Или есть решение Марка, которое отличается: потому что решение Марка означает, что ваши данные-члены не только другой тип, но и другой экземпляр (т. е. если вы изменяете данные-члены, то вы изменяете копию исходной коллекции, а не редактирование самой исходной коллекции; аналогично, если исходная коллекция изменяется после создания копии, ваши локальные данные копии / члена не включают это последующее изменение в исходную коллекцию).

Вы не должны использовать версию as - вы должны либо принять и сохранить что-то повторяющееся, как IList<T>, либо создать новую локальную коллекцию данных:

this.strings = new Collection<string>();
foreach(string s in strings) { this.strings.Add(s); }

На самом деле, я бы использовал List<T> сам; тогда вы можете сделать (хотя это не причина для использования List<T>):

this.strings = new List<string>(strings);

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

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

С другой стороны, я бы сказал, что принятие ввода в качестве интерфейса лучше, чем конкретного класса, поскольку это позволяет гибкость подачи в макет объектов легче. Однако для этого потребуется, чтобы внутренние элементы класса зависели от интерфейса, а не от конкретного класса (таким образом, в примере переменная-член будет иметь значение IEnumerable вместо Collection).

Я согласен с яписканом, что решение марка фактически изменило использование, создав копию входных данных, и, таким образом, произошло изменение ответственности:

Before: входные данные всегда разделяются между потребителем и этим классом. Таким образом, изменение входных данных является "общим" между потребителем и этим классом

After: понимание этого класса входных данных отделяется от потребителя после вызова конструктора.

Если то, что вы храните, является коллекцией, поэтому я предпочитаю получать ICollection в качестве входных данных.

Почему бы не принять параметр типа IEnumerable<String>и не привести его к извлечению?

public ClassA
{
    private IEnumberable<String> strings;

    public Collection<String> StringCollection {
        get { return (Collection<String>) strings; }
    }

    public ClassA(IEnumerable<String> strings)
    {
        this.strings = strings; 
    }
}

Я бы согласился с Пейдж, в некотором роде.

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

Но то, что вы делаете выше со свойством StringCollection, для меня не имеет смысла - вы понижаете передачу, которая не будет работать.

- Matt

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

- - - - edit - - - -