Это хорошая практика для создания ClassCollection другого класса?


35

Допустим, у меня есть Carкласс:

public class Car
{
    public string Engine { get; set; }
    public string Seat { get; set; }
    public string Tires { get; set; }
}

Допустим, мы создаем систему для парковки, я собираюсь использовать много Carклассов, поэтому мы создаем CarCollectionкласс, который может иметь несколько дополнительных методов, таких как FindCarByModel:

public class CarCollection
{
    public List<Car> Cars { get; set; }

    public Car FindCarByModel(string model)
    {
        // code here
        return new Car();
    }
}

Если я учусь на уроке ParkingLot, что лучше?

Опция 1:

public class ParkingLot
{
    public List<Car> Cars { get; set; }
    //some other properties
}

Вариант № 2:

public class ParkingLot
{
    public CarCollection Cars { get; set; }
    //some other properties
}

Это даже хорошая практика для создания ClassCollectionдругого Class?


Какую выгоду вы чувствуете при прохождении, CarCollectionа не List<Car>вокруг? Особенно учитывая, что CarCollection не расширяет класс Backing List и даже не реализует интерфейс Collection (я уверен, что в C # есть похожие вещи).

Список <T> уже реализует IList <T>, ICollection <T>, IList, ICollection, IReadOnlyList <T>, IReadOnlyCollection <T>, IEnumerable <T> и IEnumerable ... Кроме того, я мог бы использовать Linq ...
Луис

Но public class CarCollectionне реализует IList или ICollection и т. Д. ... поэтому вы не можете передать его в список, который подходит для списка. Он заявляет как часть своего имени, что это коллекция, но не реализует ни один из этих методов.

1
будучи 6-летним вопросом, я вижу, что никто не упомянул, что это обычная практика в DDD. Любая коллекция должна быть абстрагирована в пользовательскую коллекцию. Например, скажем, вы хотели рассчитать стоимость группы автомобилей. Где бы вы положили эту логику? в сервисе? Или DDD вы бы иметь CarColectionс TotalTradeValueсобственностью на него. DDD - не единственный способ проектирования систем, просто указав его в качестве опции.
Буря Мюллер

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

Ответы:


40

До появления дженериков в .NET была обычная практика создания «типизированных» коллекций, чтобы вы могли иметь и class CarCollectionт. Д. Для каждого типа, который нужно сгруппировать. В .NET 2.0 с введением Generics появился новый класс, List<T>который избавляет вас от необходимости создавать и CarCollectionт. Д., Которые вы можете создать List<Car>.

В большинстве случаев вы обнаружите, что этого List<T>достаточно для ваших целей, однако могут быть случаи, когда вы хотите иметь конкретное поведение в своей коллекции, если вы считаете, что это так, у вас есть несколько вариантов:

  • Создайте класс, который инкапсулирует, List<T>например,public class CarCollection { private List<Car> cars = new List<Car>(); public void Add(Car car) { this.cars.Add(car); }}
  • Создать собственную коллекцию public class CarCollection : CollectionBase<Car> {}

Если вы используете подход инкапсуляции, вы должны как минимум выставить перечислитель, чтобы объявить его следующим образом:

public class CarCollection : IEnumerable<Car>
{
    private List<Car> cars = new List<Car>();

    public IEnumerator<Car> GetEnumerator() { return this.cars.GetEnumerator(); }
}

Без этого вы не сможете перезаписать foreachколлекцию.

Некоторые причины, по которым вы можете захотеть создать собственную коллекцию:

  • Вы не хотите полностью раскрывать все методы IList<T>илиICollection<T>
  • Вы хотите выполнить дополнительные действия при добавлении или удалении элемента из коллекции

Это хорошая практика? хорошо, это зависит от того, почему вы это делаете, если это, например, одна из причин, которые я перечислил выше, тогда да.

Microsoft делает это довольно регулярно, вот несколько сравнительно недавних примеров:

Что касается ваших FindByметодов, я хотел бы поместить их в методы расширения, чтобы их можно было использовать для любой коллекции, содержащей автомобили:

public static class CarLookupQueries
{
    public static Car FindByLicencePlate(this IEnumerable<Car> source, string licencePlate)
    {
        return source.SingleOrDefault(c => c.LicencePlate == licencePlate);
    }

    ...
}

Это отделяет задачу запроса коллекции от класса, в котором хранятся автомобили.


Следуя этому подходу, я мог бы даже отклонить ClassCollectionдаже методы add, delete, update, добавив новый, CarCRUDкоторый будет инкапсулировать все эти методы ...
Luis

@ Luis Я бы не рекомендовал CarCRUDрасширение, так как принудительно использовать его было бы сложно, преимущество использования пользовательской логики crud в классе коллекции состоит в том, что ее невозможно обойти. Кроме того, вы можете на самом деле не заботиться о логике поиска в базовой сборке, где и Carт. Д. Объявляется, что может быть только пользовательским интерфейсом.
Тревор Пилли

Как примечание MSDN: «Мы не рекомендуем использовать класс CollectionBase для новой разработки. Вместо этого мы рекомендуем использовать универсальный класс Collection <T>». - docs.microsoft.com/en-us/dotnet/api/…
Райан

9

Нет. Создание XXXCollectionклассов в значительной степени вышло из моды с появлением обобщений в .NET 2.0. На самом деле, есть замечательное Cast<T>()расширение LINQ, которое люди используют в наши дни для извлечения материала из этих пользовательских форматов.


1
А как насчет пользовательских методов, которые мы можем иметь внутри ClassCollection? это хорошая практика, чтобы поставить их на главном Class?
Луис

3
Я считаю, что подпадает под мантру разработки программного обеспечения "это зависит". Если вы говорите, например, о вашем FindCarByModelметоде, это имеет смысл как метод в вашем репозитории, который немного сложнее, чем просто Carколлекция.
Джесси С. Слайсер

2

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

public static class CarExtensions
{
    public static IEnumerable<Car> ByModel(this IEnumerable<Car> cars, string model)
    {
        return cars.Where(car => car.Model == model);
    }
}

Вы добавляете так много фильтров или полезных методов к этому классу , как вам нравится, и вы можете использовать их везде, где есть IEnumerable<Car>, что включает в себя все ICollection<Car>, массивы Car, и IList<Car>т.д.

Поскольку у нашего решения для персистентности есть поставщик LINQ, я также часто создаю похожие методы фильтрации, которые работают и возвращают IQueryable<T>, поэтому мы можем применить эти операции и к хранилищу.

Идиома .NET (ну, C #) сильно изменилась с 1.1. Поддержание пользовательских классов коллекций - это боль, и вы мало выигрываете от наследования, CollectionBase<T>поскольку вы не получаете решения с помощью метода расширений, если все, что вам нужно, - это специфичные для домена методы фильтрации и селектора.


1

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

Например, в вашем случае добавление функции, которая будет возвращать списки автомобилей, припаркованных на четных / неровных участках ... И даже тогда ... возможно, только если это часто используется повторно, потому что если он занимает только одну строку с хорошим LinQ функция и используется только один раз, какой смысл? ПОЦЕЛУЙ !

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


Несмотря на это, я думаю, что я мог бы включить эти методы в основнойClass
Луис

Да, действительно ... вы можете
Jalayn

-1

Я предпочитаю использовать следующую опцию, чтобы вы могли добавить свой метод в коллекцию и использовать преимущество списка.

public class CarCollection:List<Car>
{
    public Car FindCarByModel(string model)
    {
        // code here
        return new Car();
    }
}

и тогда вы можете использовать его как C # 7.0

public class ParkingLot
{
    public CarCollection Cars { get; set; }=new CarCollection();
    //some other properties
}

Или вы можете использовать его как

public class ParkingLot
{
   public ParkingLot()
   {
      //initial set
      Cars =new CarCollection();
   }
    public CarCollection Cars { get; set; }
    //some other properties
}

- Общая версия благодаря комментарию @Bryan

   public class MyCollection<T>:List<T> where T:class,new()
    {
        public T FindOrNew(Predicate<T> predicate)
        {
            // code here
            return Find(predicate)?? new T();
        }
       //Other Common methods
     }

и тогда вы можете использовать его

public class ParkingLot
{
    public MyCollection<Car> Cars { get; set; }=new MyCollection<Car>();
    public MyCollection<Motor> Motors{ get; set; }=new MyCollection<Motor>();
    public MyCollection<Bike> Bikes{ get; set; }=new MyCollection<Bike>();
    //some other properties
}

не наследуй от List <T>
Брайан

@ Брайан, ты прав, вопрос не для универсальной коллекции. Я изменю свой ответ для универсальной коллекции.
Валид А.К.

1
@WaleedAK ты все еще это сделал - не наследуй от List <T>: stackoverflow.com/questions/21692193/why-not-inherit-from-listt
Брайан

@ Брайан: если ты читаешь свою ссылку Когда это приемлемо? Когда вы создаете механизм, который расширяет механизм List <T>. , Так что все будет хорошо до тех пор, пока нет дополнительных свойств
Waleed AK
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.