Czy dobrą praktyką jest utworzenie kolekcji innej klasy?

35

Powiedzmy, że mam Carklasę:

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

Powiedzmy, że tworzymy system o parkingu, zamierzam wykorzystać dużo Carklasy, więc tworzymy CarCollectionklasę, może mieć kilka dodatkowych metod, takich jak FindCarByModel:

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

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

Jeśli robię zajęcia ParkingLot, jaka jest najlepsza praktyka?

Opcja 1:

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

Opcja 2:

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

Czy to dobra praktyka, aby stworzyć ClassCollectioninną Class?

Luis
źródło
Jakie korzyści widzisz w przekazywaniu CarCollectionraczej niż List<Car>dookoła? Zwłaszcza, że ​​CarCollection nie rozszerza zaplecza klasy List, ani nawet nie implementuje interfejsu Collection (jestem pewien, że C # ma podobne rzeczy).
Lista <T> już implementuje IList <T>, ICollection <T>, IList, ICollection, IReadOnlyList <T>, IReadOnlyCollection <T>, IEnumerable <T> i IEnumerable ... Poza tym mógłbym użyć Linq ...
Luis
Ale public class CarCollectionnie implementuje IList ani ICollection itp., Dlatego nie można przekazać go do czegoś, co jest w porządku z listą. W ramach nazwy twierdzi, że jest kolekcją, ale nie implementuje żadnej z tych metod.
1
będąc 6-letnim pytaniem, nikt nie wspomniał, że jest to powszechna praktyka w DDD. Każda kolekcja powinna zostać wyodrębniona do kolekcji niestandardowej. Powiedzmy na przykład, że chcesz obliczyć wartość grupy samochodów. Gdzie położyłbyś tę logikę? w serwisie? Lub w DDD to masz CarColectionz TotalTradeValuenieruchomości na niego. DDD nie jest jedynym sposobem projektowania systemów, tylko wskazując go jako opcję.
Storm Muller,
1
Dokładnie, te rzeczy są w rzeczywistości zagregowanymi korzeniami, jak sugerowano w DDD, Jedyną rzeczą jest to, że DDD prawdopodobnie nie poleciłby ci nazywania tego zbiorem samochodów. Raczej użyj niektórych nazw, takich jak Parking, Obszar roboczy itp.
Nazwa kodowa Jack

Odpowiedzi:

40

Przed generycznymi w .NET powszechną praktyką było tworzenie kolekcji „na maszynie”, aby mieć class CarCollectionitd. Dla każdego typu, który trzeba pogrupować. W .NET 2.0 z wprowadzeniem Generics wprowadzono nową klasę, List<T>która oszczędza konieczności tworzenia CarCollectionitp. Podczas tworzenia List<Car>.

List<T>Przez większość czasu okaże się, że jest to wystarczające do realizacji twoich celów, jednak może się zdarzyć, że będziesz chciał mieć określone zachowanie w swojej kolekcji, jeśli uważasz, że tak jest, masz kilka opcji:

  • Utwórz List<T>na przykład klasę, która będzie hermetyzowaćpublic class CarCollection { private List<Car> cars = new List<Car>(); public void Add(Car car) { this.cars.Add(car); }}
  • Utwórz własną kolekcję public class CarCollection : CollectionBase<Car> {}

Jeśli wybierzesz metodę enkapsulacji, powinieneś przynajmniej odsłonić moduł wyliczający, aby zadeklarować go w następujący sposób:

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

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

Bez tego nie można przerobić foreachkolekcji.

Niektóre powody, dla których warto utworzyć kolekcję niestandardową, to:

  • Nie chcesz w pełni ujawniać wszystkich metod w IList<T>lubICollection<T>
  • Chcesz wykonać dodatkowe czynności po dodaniu lub usunięciu elementu z kolekcji

Czy to dobra praktyka? cóż, to zależy od tego , dlaczego to robisz, jeśli jest to na przykład jeden z powodów, które wymieniłem powyżej, to tak.

Microsoft robi to dość regularnie, oto kilka całkiem nowych przykładów:

Jeśli chodzi o twoje FindBymetody, pokusiłbym się o zastosowanie ich w metodach rozszerzenia, aby można było ich używać przeciwko dowolnej kolekcji zawierającej samochody:

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

    ...
}

Oddziela to kwestię zapytania kolekcji od klasy, która przechowuje samochody.

Trevor Pilley
źródło
Postępując zgodnie z tym podejściem, mógłbym nawet odrzucić metody ClassCollectionparzystości dla metod dodawania, usuwania i aktualizacji, dodając nowy, CarCRUDktóry będzie zawierał wszystkie te metody ...
Luis
@Luis Nie poleciłbym CarCRUDrozszerzenia, ponieważ wymuszenie jego użycia byłoby trudne, zaletą umieszczenia niestandardowej logiki w klasie kolekcji jest to, że nie ma możliwości obejścia tego. Ponadto może nie obchodzić logiki Znajdź w zespole podstawowym, w którym Cardeklarowane są itp., Może to być działanie tylko interfejsu użytkownika.
Trevor Pilley,
Podobnie jak notatka z MSDN: „Nie zalecamy używania klasy CollectionBase do nowego programowania. Zamiast tego zalecamy używanie ogólnej klasy Collection <T>”. - docs.microsoft.com/en-us/dotnet/api/…
Ryan
9

Nie. Tworzenie XXXCollectionklas praktycznie przestało być modne wraz z pojawieniem się generics w .NET 2.0. W rzeczywistości istnieje fajne Cast<T>()rozszerzenie LINQ, którego ludzie używają w dzisiejszych czasach, aby wydobyć rzeczy z tych niestandardowych formatów.

Jesse C. Slicer
źródło
1
Co z niestandardowymi metodami, które moglibyśmy w tym mieć ClassCollection? czy dobrą praktyką jest umieszczanie ich na głównej pozycji Class?
Luis
3
Wierzę, że mieści się w mantrze rozwoju oprogramowania „to zależy”. Jeśli mówisz na przykład o swojej FindCarByModelmetodzie, ma to sens jako metoda w repozytorium, która jest nieco bardziej złożona niż tylko Carkolekcja.
Jesse C. Slicer,
2

Często przydatne są metody zorientowane na domeny do znajdowania / wycinania kolekcji, jak w powyższym przykładzie FindByCarModel, ale nie ma potrzeby uciekania się do tworzenia klas kolekcji opakowań. W tej sytuacji zazwyczaj utworzę teraz zestaw metod rozszerzenia.

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

Dodać dowolną liczbę filtrów lub użyteczności metod do tej klasy, jak chcesz, i można z nich korzystać w dowolnym miejscu masz IEnumerable<Car>, który zawiera wszystko ICollection<Car>, tablice Car, IList<Car>etc.

Ponieważ nasze rozwiązanie utrwalające ma dostawcę LINQ, często tworzę również podobne metody filtrowania, które działają i zwracają IQueryable<T>, abyśmy mogli zastosować te operacje również w repozytorium.

Idiom .NET (cóż, C #) bardzo się zmienił od 1.1. Utrzymywanie niestandardowych klas kolekcji jest uciążliwe, a dziedziczenie CollectionBase<T>po nich nie przynosi korzyści , ponieważ nie dostajesz rozwiązania z metodą rozszerzenia, jeśli wszystko, czego potrzebujesz, to metody filtrowania i selekcji specyficzne dla domeny.

Neil Hewitt
źródło
1

Myślę, że jedynym powodem, aby stworzyć specjalną klasę do przechowywania kolekcji innych przedmiotów, powinno być dodanie czegoś wartościowego, czegoś więcej niż tylko hermetyzacja / odziedziczenie po instancji IListlub innym typie kolekcji.

Na przykład, w twoim przypadku, dodanie funkcji, która zwróciłaby podlisty samochodów zaparkowanych na parzystych / nierównych powierzchniach działki ... I nawet wtedy ... może tylko wtedy, gdy często jest ponownie używana, ponieważ jeśli zajmuje tylko jedną linię z ładnym LinQ funkcja i jest używana tylko raz, o co chodzi? KISS !

Teraz, jeśli planujesz zaoferować wiele metod sortowania / wyszukiwania, to tak, myślę, że może to być przydatne, ponieważ właśnie tam powinny one należeć, w tej specjalnej klasie kolekcji. Jest to również dobry sposób na „ukrycie” złożoności niektórych zapytań „znajdź” lub cokolwiek innego, co można zrobić w metodzie sortowania / wyszukiwania.

Jalayn
źródło
Nawet jeśli, myślę, że mógłbym włączyć te metody do głównejClass
Luis
Tak, rzeczywiście ... możesz
Jalayn,
-1

Wolę iść z następującą opcją, abyś mógł dodać swoją metodę do kolekcji i skorzystać z zalet listy.

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

a następnie możesz użyć go jak w C # 7.0

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

Lub możesz go użyć jak

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

- Ogólna wersja dzięki komentarzowi @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
     }

i wtedy możesz go użyć

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
}
Waleed AK
źródło
nie dziedziczymy z Listy <T>
Bryan Boettcher
@Bryan, masz rację, pytanie nie dotyczy ogólnej kolekcji. Zmodyfikuję swoją odpowiedź dla zbioru ogólnego.
Waleed AK
1
@WaleedAK nadal to robiłeś - nie dziedzicz z listy <T>: stackoverflow.com/questions/21692193/hyhy-not-inherit-from-listt
Bryan Boettcher
@Bryan: jeśli czytasz swój link Kiedy jest to dopuszczalne? Podczas budowania mechanizmu rozszerzającego mechanizm List <T>. , Więc będzie dobrze, dopóki nie będzie żadnych dodatkowych właściwości
Waleed AK