Чи є гарною практикою створення 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 не розширює клас списку резервних копій або навіть реалізує інтерфейс Collection (я впевнений, що у C # є подібні речі).

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

Але 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навіть для методів додавання, видалення та оновлення, додавши новий, CarCRUDякий буде інкапсулювати всі ці методи ...
Луїс,

@Luis Я не рекомендував би CarCRUDрозширення, оскільки примусово використовувати його використання буде важко, перевага перед тим, як застосувати власну логічну логіку до класу колекції, полягає в тому, що немає способу її обійти. Крім того, ви можете насправді не піклуватися про логіку Find в основній збірці, де оголошено 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
Луїс

Так, справді ... можна
Джалайн

-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
}

не успадковуйте зі Списку <T>
Брайан Боттчер

@Bryan, ви праві, питання не для загальної колекції. Я зміню свою відповідь на загальну колекцію.
Waleed AK

1
@WaleedAK ви все-таки це зробили - не успадковуйте зі Списку <T>: stackoverflow.com/questions/21692193/why-not-inherit-from-listt
Брайан Боттчер

@Bryan: якщо ти читаєш посилання, коли це прийнятно? Коли ви будуєте механізм, який розширює механізм Список <T>. , Тож буде добре, поки не буде додаткових властивостей
Waleed AK
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.