C #: инкапсуляция, например, коллекций
-
22-07-2019 - |
Вопрос
Мне интересно, какой из них будет считаться самым чистым или лучшим в использовании и почему.
Один из них предоставляет список пассажиров, который позволяет пользователю добавлять, удалять и т. д. Другой скрывает список и позволяет пользователю только перечислять их и добавлять, используя специальный метод.
Пример 1
class Bus
{
public IEnumerable<Person> Passengers { get { return passengers; } }
private List<Passengers> passengers;
public Bus()
{
passengers = new List<Passenger>();
}
public void AddPassenger(Passenger passenger)
{
passengers.Add(passenger);
}
}
var bus = new Bus1();
bus.AddPassenger(new Passenger());
foreach(var passenger in bus.Passengers)
Console.WriteLine(passenger);
Пример 2
class Bus
{
public List<Person> Passengers { get; private set; }
public Bus()
{
Passengers = new List<Passenger>();
}
}
var bus = new Bus();
bus.Passengers.Add(new Passenger());
foreach(var passenger in bus.Passengers)
Console.WriteLine(passenger);
Первый класс, который я бы сказал, лучше инкапсулирован. И в этом конкретном случае это может быть лучшим подходом (поскольку вы, вероятно, должны убедиться, что в автобусе осталось место и т. Д.). Но я предполагаю, что могут быть случаи, когда второй класс также может быть полезен? Например, если классу все равно, что будет с этим списком, пока он есть. Что ты думаешь?
Решение
В первом примере можно изменить вашу коллекцию.
Примите во внимание следующее:
var passengers = (List<Passenger>)bus.Passengers;
// Now I have control of the list!
passengers.Add(...);
passengers.Remove(...);
Чтобы это исправить, вы можете рассмотреть что-то вроде этого:
class Bus
{
private List<Passenger> passengers;
// Never expose the original collection
public IEnumerable<Passenger> Passengers
{
get { return passengers.Select(p => p); }
}
// Or expose the original collection as read only
public ReadOnlyCollection<Passenger> ReadOnlyPassengers
{
get { return passengers.AsReadOnly(); }
}
public void AddPassenger(Passenger passenger)
{
passengers.Add(passenger);
}
}
Другие советы
В большинстве случаев я бы посчитал пример 2 приемлемым при условии, что базовый тип был расширяемым и / или предоставлял некоторую форму событий onAdded / onRemoved, чтобы ваш внутренний класс мог реагировать на любые изменения в коллекции.
В этом случае List < T > не подходит, так как у класса нет возможности узнать, было ли что-то добавлено. Вместо этого вы должны использовать коллекцию, потому что коллекция & Lt; T & Gt; В классе есть несколько виртуальных членов («Вставить», «Удалить», «Установить», «Очистить»), которые можно переопределить и добавить триггеры событий для уведомления класса обтекания.
(Вы также должны знать, что пользователи класса могут изменять элементы в списке / коллекции без ведома родительского класса, поэтому убедитесь, что вы не полагаетесь на неизменность элементов - если только они очевидно, неизменны - или вы можете предоставить события стиля onChanged, если вам нужно.)
Проведите ваши соответствующие примеры через FxCop, и это должно дать вам подсказку о рисках разоблачения List<T>
Я бы сказал, что все сводится к вашей ситуации. Обычно я бы выбрал вариант 2, так как он самый простой, , если у вас нет бизнес-причин для добавления более жестких элементов управления.
Вариант 2 самый простой, но он позволяет другим классам добавлять / удалять элементы в коллекцию, что может быть опасно.
Я думаю, что хорошей эвристикой является рассмотрение того, что делают методы-оболочки. Если ваш метод AddPassenger (или Remove, или др.) Просто передает вызов коллекции, то я бы выбрал более простую версию. Если вам нужно проверить элементы до их вставки, то вариант 1 в основном неизбежен. Если вам нужно отслеживать вставленные / удаленные элементы, вы можете пойти любым путем. В варианте 2 вам нужно регистрировать события в коллекции для получения уведомлений, а в варианте 1 вы должны создавать оболочки для каждой операции в списке, которую вы хотите использовать (например, если вы хотите вставить, а также добавить), поэтому я думаю, это зависит.