Kwestie projektowe - kompozyt

0

Cześć, potrzebuję mieć coś jak kompozyt. Wiem, że nie jest to idealny wzorzec, ale nie udało mi się wymyślić niczego lepszego.
Mamy klasę Item. Klasa Item zawiera kolekcję innych Itemów - typowy kompozyt.

Załóżmy, że dziedziczenie wygląda tak:

class Item {}
class MyItem: Item {}
class SuperItem: MyItem {}
class ExtraItem: Item {}

I teraz chciałbym, żeby klasa MyItem mogła mieć kolekcję klas MyItem, a nie Item. To można by załatwić klasami generycznymi. Ale chciałbym też, żeby klasa SuperItem mogła mieć kolekcję jedynie klas ExtraItem.

I tu poległem. Więc zrobiłem coś bardzo prostego. Zaimplementowałem taki mechanizm, który sprawdza, czy można do obiektu dodać konkretny obiekt - coś w stylu:

protected override bool CanAddObject(Item item)
{
   return item.GetType() == typeof(ExtraItem);
}

(to dla klasy SuperItem)

Jeśli ta metoda zwraca false, aplikacja po prostu rzuca asserta i się wywala.
Czy takie rozwiązanie jest do przyjęcia? Jeśli nie, to co w zamian?

Od razu uprzedzam uwagi, które się pojawią: "Źle projektujesz system od początku". Potrzebuję, żeby wszystkie itemy dziedziczyły po jednej klasie: Item. I chcę mieć poukładaną taką strukturę:
Dokument (zawiera Itemy) -> Okres (może zawierać inne okresy i arkusze) -> Arkusz (dziedziczy po okresie, po prostu rozszerza jego funkcjonalność, może posiadać grupy) -> Grupy (mogą posiadać kategorie) -> Kategorie... itd :)

Jak widać bez sensu jest, żeby Grupy zawierały arkusze. Ale może to po prostu ograniczenie kompozytu i nie wymyślono niczego lepszego.

1

W moim odczuciu wystarczą generyki:

abstract class Item<TItem>
{
    public List<TItem> Items { get; set; }
}

class MyItem : Item<MyItem>
{
}

class SuperItem : Item<ExtraItem>
{
}

class ExtraItem : Item<object>
{
}

To, że chcesz zamodelować jakąś hierarchię, nie znaczy, że hierarchia klas musi wyglądać tak samo. Tworzenie tak rozbuchanych hierarchii klas z wieloma poziomami dziedziczenia często powoduje problemy z rozwojem aplikacji.

Czemu w ogóle chcesz użyć kompozytu? Jakie operacje będą wykonywane na komponentach?

0

No właśnie na początku też myślałem o rozwiązaniu, jakie dałeś. Ale okazało się (piszę o tym w poście), że SuperItem ma dziedziczyć po MyItem, a nie po Item. I tu jest problem :)

Z komponentów będą pobierane dane. Na przykład Item będzie miał metodę GetSum(). Ta metoda w różnych typach będzie wyglądała inaczej. Np. w jednym może to być:

items.Sum(item => item.GetSum()) + sum;

W innej może to być po prostu zsumowanie wszystkich dzieci, a w jeszcze innej zwrócenie jakiejś wartości.

2

Chciałbym, żeby klasa MyItem mogła mieć kolekcję klas MyItem, a nie Item.

Ale chciałbym też, żeby klasa SuperItem mogła mieć kolekcję jedynie klas ExtraItem.

Ale okazało się (piszę o tym w poście), że SuperItem ma dziedziczyć po MyItem, a nie po Item

Jeśli SuperItem ma dziedziczyć po MyItem, to musi zawierać elementy typu MyItem, bo takie było wymaganie dla MyItem. Nie możesz w klasie pochodnej znieść jakiejś własności gwarantowanej kontraktem klasy bazowej. Niestety ww trzy wymagania łamią zasadę Liskov:

https://pl.wikipedia.org/wiki/Zasada_podstawienia_Liskov
https://en.wikipedia.org/wiki/Liskov_substitution_principle

Zgadzam się z @somekind że takie rozbudowane hierarchie się później mszczą. W ogóle najlepiej unikać dziedziczenia na rzecz interfejsów i Type Class.

0

Hmm, znowu dziedziczenie ;-)

  1. Po co Ci struktura?
  2. Jakie operacje mają być na niej realizowane?

Pomijając motywacje, to 2 sugestie z mojej strony:
struktura: można ogarnąć na zasadzie drzewa: Korzeń + kolekcja węzłów, z których każdy może przechowywać kolekcję węzłów itd. W węźle możesz trzymać dowolny obiekt.
operacje: pewnie będą się różnić per obiekt. Może warto rozważyć wzorzec Visitor, wtedy można na strukturze realizować różne algorytmy, które będą uwzględniały typ obiektu.

0
yarel napisał(a):

Hmm, znowu dziedziczenie ;-)

  1. Po co Ci struktura?

Struktura jest mi potrzebna do tego, żeby odpowiednie kategorie były w odpowiednich grupach. Odpowiednie grupy w odpowiednich arkuszach itd. Każdy item ma w prawdzie jakiś unikalny handle i parentHandle.

  1. Jakie operacje mają być na niej realizowane?

Tak, jak napisałem wyżej. Np. sumowanie jakiś wartości.

Pomijając motywacje, to 2 sugestie z mojej strony:
struktura: można ogarnąć na zasadzie drzewa: Korzeń + kolekcja węzłów, z których każdy może przechowywać kolekcję węzłów itd. W węźle możesz trzymać dowolny obiekt.
operacje: pewnie będą się różnić per obiekt. Może warto rozważyć wzorzec Visitor, wtedy można na strukturze realizować różne algorytmy, które będą uwzględniały typ obiektu.

Nie do końca tu jest problem. Moim problemem jest to, że np. dziećmi grupy powinny być kategorie. Dziećmi arkusza powinny być grupy. Itd.

0

To moze coś takiego?>

    interface ICommonBehavior
    {
        int Sum();
    }
    class Item : ICommonBehavior, /*lub pole z kolekcją lub wizytor*/ IEnumerable<ICommonBehavior>
    {

        //common stuff
        public virtual int Sum();

        /* IEnumerable lub pole z kolekcją lub wizytor*/
    }
    class Dokument : Item
    {
        public void Add(Okres item) { }
    }

    class Okres : Item
    {
        public void Add(Grupa item) { }
    }

    class Grupa : Item
    {
        public void Add(Okresitem) { }
    }

Do możesz dodawać elementy tylko zgodnie z hierarchią, równocześnie miałbyś dostęp do wspólnych zachowań, ale nie wiem co konkretnie próbujesz zrobić. Mhh w sumie czemu generyki Ci nie działają?

1

A czym się różnią grupy od kategorii, poza tym że mają innych rodziców?

0

Z tego co zrozumiałem, to ta hierarchia wygląda tak jak w załączniku. To wszystkie elementy tej struktury ?

Czy nie używasz dziedziczenia tylko po to, żeby nie duplikować kodu/funkcjonalności ?

0
somekind napisał(a):

A czym się różnią grupy od kategorii, poza tym że mają innych rodziców?

W bazie danych lądują w osobnych tabelach i mają kilka osobnych właściwości. Rodzica - względem dziedziczenia - mają tego samego. Względem struktury rodzic jest inny.

yarel napisał(a):

Z tego co zrozumiałem, to ta hierarchia wygląda tak jak w załączniku. To wszystkie elementy tej struktury ?

Prawie. Dokument może zawierać tylko Okresy. Okresy mogą zawierać okresy lub Arkusze (przy czym Arkusz jest okresem).
Jednak w związku z przeszukiwaniem danych (FindItemByHandle) - wszystkie itemy w dokumencie siedzą w słowniku, gdzie kluczem jest handle, a wartością Item.

Czy nie używasz dziedziczenia tylko po to, żeby nie duplikować kodu/funkcjonalności ?

Zakładam użycie polimorfizmu, ale w tym momencie (na samym początku) jeszcze z niego nie korzystam.

0
abstract class Item {}
class ItemWithCollection : Item, IEnumerable<Item> {}
abstract class MyItem : Item{}
class MyItemWithCollection : MyItem, IEnumerable<MyItem> {}
class ExtraItem : Item{}
class SuperItem : MyItem, IEnumerable<ExtraItem> {}

Na oko pasowałby tutaj też visitor lub zastąpienie dziedziczenia mixinami. No i zostaje pytanie, jak chcesz dodawać elementy do kolecji, bo jeżeli będziesz musiał rzutować lub sprawdzać typy refleksją, to skomplikowanie dziedziczenia niewiele Ci da.

1 użytkowników online, w tym zalogowanych: 0, gości: 1