Autor Wątek: dostep do zmiennych klasy równorzędnej  (Przeczytany 4481 razy)

Offline MDW

  • Użytkownik
    • www.encore-games.com

# Lipiec 23, 2007, 19:57:04
Hi!

Właśnie się zastanawiam nad pewną sprawą i bardzo ciekawi mnie jak Wy sobie z czymś takim radzicie. Nie chodzi o to, że ja sobie nie radzę tylko zastanawiam się jakie są możliwości rozwiązania tej sprawy i jaki sposób jest najbardziej elegancki. Spróbuję to jakoś opisać. Mam nadzieję, że zrobię to w sposób zrozumiały. :) Sprawa dotyczy oczywiście C# chociaż problem nie dotyczy tylko tego języka.

Jest sobie jakaś główna klasa (KlasaA), która zawiera obiekty różnych innych klas (obiektB, obiektC, obiektD). Coś takiego (pseudokod diabelnie uproszczony):

KlasaA
{
  KlasaB obiektB;
  KlasaC obiektC;
  KlasaD obiektD;
}

Co zrobić żeby w metodach tych obiektów mieć dostęp do zmiennych innych obiektów? Na przykład jeżeli w jakiejś metodzie należącej do obiektC zmienić wartość zmiennej w obiektB. Jak Waszym zdaniem zrobić to najbardziej elegancko? :)

Niby nie jest to skomplikowany problem ale bardzo ciekawy jestem Waszych pomysłów. Ja jakoś nie widzę eleganckich sposobów na zorganizowanie tego. Wiem, że nie powinno się robić takich powiązań między obiektami ale... ale... ale kurcze czasem trzeba. :) Załóżmy po prostu, że chcemy coś takiego zrobić. Problem tylko jak to zrobić w sposób najbardziej elegancki.
Pomożecie? :)

Offline Mr. Spam

  • Miłośnik przetworów mięsnych

Offline nilphilus

  • Użytkownik
    • wordpress

# Lipiec 23, 2007, 20:08:38
może w każdym z obiektów dać wskaźnik na KlaseA i przy konstruktorze ustawiać go na daną klasę wskaźnik=this;
hmm... w sumie to wskaźnik na klasę będzie trzeba dać chyba [a sam rodzic powinien być czysto wirtualny]

Offline MDW

  • Użytkownik
    • www.encore-games.com

# Lipiec 23, 2007, 20:16:55
może w każdym z obiektów dać wskaźnik na KlaseA i przy konstruktorze ustawiać go na daną klasę wskaźnik=this;
hmm... w sumie to wskaźnik na klasę będzie trzeba dać chyba [a sam rodzic powinien być czysto wirtualny]

I to właśnie chciałem usłyszeć/przeczytać. :) Miałem to na myśli ale wydało mi się to jakieś takie mało eleganckie. :) No ale skoro ktoś inny też tak myśli to musi to być stosowana praktyka i wcale nie jest taka nieelegancka jak mi się wydaje. Ja tak robiłem ale w C++. I to oczywiście działało. Ciekawy jestem czy ktoś ma jeszcze jakiś pomysł. Ja innego nie wyczaiłem. Tylko to przechowywanie wskaźnika na klasę rodzica w każdym obiekcie.

Offline Xion

  • Moderator
    • xion.log

# Lipiec 23, 2007, 20:39:30
Tu wszystko będzie 'mało eleganckie', bo takie jest założenie projektowe stojące za takim powiązaniem klas. Po prostu za dużo jest to powiązań między klasami, których mogłoby nie być. Można się ich pozbyć, stosując szczątkową wersję wzorca Fasada.
A mówiąc po ludzku, po prostu przenieś operacje klasy KlasaB wymagające obiektu KlasaC na wyższy poziom (czyli do KlasaA). Wtedy nie będzie już żadnych skrupułów w rodzaju "kto powinien mieć wskaźnik na kogo?" lub co gorsza "kto jest czyim właścicielem?".

Offline MDW

  • Użytkownik
    • www.encore-games.com

# Lipiec 23, 2007, 20:52:44
Tu wszystko będzie 'mało eleganckie', bo takie jest założenie projektowe stojące za takim powiązaniem klas. Po prostu za dużo jest to powiązań między klasami, których mogłoby nie być. Można się ich pozbyć, stosując szczątkową wersję wzorca Fasada.

Masz rację. Sama konstrukcja projektu jest nieładna. Tylko tak się zastanawiam czy da się tak wszystko zorganizować żeby uniknąć tego przekazywania wskaźnika. Hmmm... chyba się da. :) Zerknąłem na definicję tego przytoczony przez ciebie wzorca. O to właśnie chodzi. Chyba postaram się to tak zrobić...



A mówiąc po ludzku, po prostu przenieś operacje klasy KlasaB wymagające obiektu KlasaC na wyższy poziom (czyli do KlasaA). Wtedy nie będzie już żadnych skrupułów w rodzaju "kto powinien mieć wskaźnik na kogo?" lub co gorsza "kto jest czyim właścicielem?".

Boję się, że to będzie trochę głupio wyglądało. :) Metody, które bardzo pasowałyby do KlasaB będą w KlasaA. Powstaje tutaj pytanie o sens robienia klas do obiektów, które będą w jednym egzemplarzu. Wszystko wrąbać do KlasaA i z głowy. :) W klasy zamknąć tylko to co będzie miało więcej obiektów.
« Ostatnia zmiana: Czerwiec 02, 2008, 10:28:43 wysłana przez Złośliwiec »

Offline zarius

  • Użytkownik

# Lipiec 31, 2007, 10:57:54
Tak jak napisal nilphilus, nie widze nic zlego w takim rozwiazaniu ;d

Przekazujesz w konstruktorze wlasciciela obiektu i juz (w tym wypadku konkretny obiekt klasy A). Sam mam w kodzie, moze w 2 miejscach takie cos i jakos mi to bardzo nie przeszkadza ;d

Offline skowronkow

  • Użytkownik
    • skowronkow devsite

# Czerwiec 02, 2008, 02:27:20
Hi! Dawno tu nie zaglądałem, ale jak widze natknąłem się na dość powszechny problem dotyczący samego projektowania obiektowego. Mimo, że cięzko w tym wypadku uogólniać pozwole sobie napisać kilka drobnych uwag:)

Mianowicie:

Po pierwsze takie zaleznoci (ale nie w tej formie, o czym zaraz )powinny jedynie istniec na poziomie implementacji szczegółowej, czyli na najniższym poziomie hierarchi (gdyz sa najbardziej konkretne). Zakladajac to jednak z góry skazani jesteśmy na kolejny błąd projektowy. Otóż jeżeli od klasy A zalezy jakas inna klasa to w tym wypadku zmieniając jakąkolwiek częsć klas zawieranych przez A musmy zmieniac lawinowo wszystkie klasy zalezne. Chodzi o to aby kierunek abstrakcyjności klasy był zgodny z kierunkiem niezależności (klasa A w tym wypadku, jesli jest na najwyzszym poziomie, powinna byc jak najbardziej konkretna zas klasy zawierane jak najmniej). To moja pierwsza uwaga wynikająca ze zbyt ogolnego potraktowania tematu. Najbardziej eleganckim rozwiazaniem (jednak dalej w oderwaniu od konkretow, gdyz tu ich brak) bedzie po pierwsze odwrocenie zaleznosci dla klasy A i klas zawieranych oraz wstrzykiwanie zaleznosci między klasami zawieranymi. Odwrocenie zaleznosci mozna latwo osiagnac stosujac interfejsy (po to wlasciwie zostaly wymyslone) w miejscu klas zawieranych (od razu napisze ze zamiast interfejsu moze byc oczywiscie klasa abstrakcyjna). Klasy zawierane aby mogly byc wykorzystane musza implementowac te interfejsy wiec to one w tym wypadku zaczynaja zalezec od klasy klienckiej (zawierajacej i wykorzystujacej wspomniane referencje do interfrjsow czyli nasza klasa A). W ten sposob rozwizalismy pierwszy, niewspomniany tutaj problem.

Przejdzmy wiec do omawianego. Jesli juz doszlismy do takiego stadium, ktorego schemat przedstawil MDW i nie da sie/nie chcemy/nie umiemy zastosowac odpowiedniego wzorca projektowego (sam nic nie podpowiem, gdyz wszystko zalezy od kokretnego przypdaku) najbardziej eleganckie bedzie wspomniane wstrzykiwanie zaelznosci. Chcemy bowiem aby instancja jednej klasy miala mozliwosc uzyskania dostepu do instancji innej klasy rownorzednej. Zasada jest prosta. Poslugując się przykładem podanym przez MDW, chcemy aby obiekt klasy B mial dostep do obiektu klasy C.

Class C
{
  //...
}

Class B
{
  C obiektC;
  public B(C obiektC)
  {
    this.obiektC = obiektC;
  }

  public void MetodaOperujacaNaObiektC()
  {
    obiektC.wlasciwosc // oczywiscie wlasciwosc lub skladowa musi byc publiczna
  }
}

Jest to jeden ze sposobow wstrzykiwania zaleznosci. Istnieje rowniez inny:


Interface clientC  // nazwy interfejsow powinny zalezec od klas klienckich, nie od implementujacych ten interfejs;)
{
  MetodaDzialajacaNaSkladowejKlasyC();
}

Class B : clientC
{
  MetodaDzialajacaNaSkladowejKlasyC(typ skladowa);
}

Class C
{
  typ jakasSkladowa;

  ClientC ModyfikatorSkladowej  // co za niefortunna nazwa:P
  public C(clientC ModyfikatorSkladowej)
  {
    this.ModyfikatorSkladowej = ModyfikatorSkladowej;
  }

  public void jakasMetoda()
  {
    ModyfikatorSkladowej.MetodaDzialajacaNaSkladowejKlasyC(jakasSkladowa)
  }
}


Jak widać, to którego sposobu uzyjemy scisle zalezy od konkretnej implementacji naszego przypadku. Sa to tylka 2 ze sposobow na OGOLNE rozwiazanie tego problemu.
Oczywiscie zawsze pozostaje rozwiazane podane wyzej, jednak najczesciej da sie go zupelnie uniknąć. Mimo wszystko sa to jedynie propozycje (wymyslone przez ludzi madrzejszych ode mnie;)) i czasem rozwiazanie najprostrze mimo naruszania zasad dobrego projektowania jest po porstu najlepsze.
« Ostatnia zmiana: Czerwiec 02, 2008, 02:30:01 wysłana przez Time »

poopa

  • Gość
# Czerwiec 02, 2008, 03:12:31
Ehh... nabazgrałeś, dużo tekstu z którego mało wynika (a nowego nic nie wniosłeś). ;)

Zamiast wpuszczać rodzica czy interface na niego, w konstruktorze dzieci, można rozważyć (w przypadku singletona) prywatną zmienną statyczną This w parencie. Czyli:
class SingletonA
{
  private static SingletonA This; //this jest zarezerwowane of kors ;)

  B b;
  class B
  {
    public JakasMetoda()
    {
      B beee = SingletonA.This.b; // wskaźnik na to samo co this w tym kontekście
    }
  }   
  public SingletonA()
  {
    SingletonA.This = this;
    b = new B();
  }
}
Singleton w nazwie tylko dla zaznaczenia żeby nie mnożyć tego na potęgę gdyż użyta jest zmienna statyczna. Nie do końca jestem pewien jak działają w c# statyczne... czy normalnie jak w c i asmie rezerwują blok na starcie, czy przy pierwszym tworzeniu, więc nie wiem do końca, czy po wyczyszczeniu tego obiektu ten wskaźnik statyczny będzie wolny. Ale to inna sprawa. Wiem natomiast na pewno że przechowywanie wskaźników na parent'y będzie droższe i zapewne wolniejsze. ;)
Sam unikam czegoś takiego...

Jeśli muszę mieszać zależności, to staram się to robić tylko w poszczególnych metodach...np. w class B ustawiam C jako argument do jakiejś tam metody.

O boshe ale to przedawnione.... lol. Akcja - wykop dinozaura.
« Ostatnia zmiana: Czerwiec 02, 2008, 03:14:33 wysłana przez poopa »

Offline skowronkow

  • Użytkownik
    • skowronkow devsite

# Czerwiec 02, 2008, 08:50:20
Cytuj
Ehh... nabazgrałeś, dużo tekstu z którego mało wynika (a nowego nic nie wniosłeś).

hmm.. sadzac po tym co napisales pozniej, jednak wnioslem...

Cytuj
Zamiast wpuszczać rodzica czy interface na niego, w konstruktorze dzieci, można rozważyć (w przypadku singletona) prywatną zmienną statyczną This w parencie.

Widzisz, i tu pies pogrzebany. Otoz nigdzie nic takiego nie robie. Pokazalem jak uniknac cyklicznych zaleznosci (ktore u ciebie z powrotem istnieja) miedzy klasa zawierającą, a klasami zawieranymi (a nie zadnym rodzicem jesli juz;)).

Cytuj
w class B ustawiam C jako argument do jakiejś tam metody.

Jedyne sensowne co napisales (sic). Pod warunkiem ze klasa B ma jakas referencje do C (tylko po co jej wtedy C jako argument). To jest dobre jesli to A wywoluje metode klasy B a nie sama klasa B. Oczywiscie nidzgdzie nie jest napisane, że klasa A nie wywoluje metod klas zawieranych, ale gdyby tak bylo to problemu MDW rowniez by nie bylo.

Nastepna kwestia to taka, że jezeli juz kogos osądzasz o pisanie głupot, troszke lepiej sie przygotuj, a przynajmniej przeczytaj te "glupoty" od poczatku do konca.

PS. no offense, ale co ty pokazales oprocz wzorca singletona (ktory nie jest singletonem). Nigdzie nie widze komunikacji miedzy klasami rownorzednymi, chyba ze stosujesz inne nazewnictwo niz MDW i ja. Wybacz ale nie znasz sie w ogole na projektowaniu obiektowym, nie mowiac juz o tym, że nawet nie sprawdzasz jak napisany przez ciebie kod dziala..

Pozdrawiam:)
« Ostatnia zmiana: Czerwiec 02, 2008, 10:29:28 wysłana przez Złośliwiec »

Offline Firkraag

  • Użytkownik

# Czerwiec 02, 2008, 10:35:44
Class C { }

Class B
{
  public void MetodaOperujacaNaObiektC(C obiektC)
  {
    obiektC.wlasciwosc // oczywiscie wlasciwosc lub skladowa musi byc publiczna
  }
}
A co powiesz na takie rozwiązanie?

poopa

  • Gość
# Czerwiec 02, 2008, 15:02:22
Time: Mocne słowa jak na 12-latka.   ;D
O jedno zdanie żeś się obraził - zdecydowanie mam talent do prowokowania. ;)

Przepraszam za parenta i dzieci. Oczywiście miały być składowe i zawierane. ;)

TO nie jest wzorzec singletona. Raczej chodzi oto żeby klasa była używana jak singleton... ale taką oczywiście nie jest.
Nie wiem gdzie ty widzisz w tym "cykliczne" zależności. Wywołujesz wskaźnik na klasę zawieraną(A?) za każdym razem jak chcesz się odwołać do innych obiektów w niej zawartych... i tyle. B bee oczywiście miał tylko być przykładem - broń boshe nie chodziło o przetrzymywanie w tej  klasie referencji B. W razie potrzeby odwołują się do tych obiektów przez składową This klasy A.

Przepraszam, pomyliłem się... ty robisz coś gorszego niźli wpuszczanie do klas podrzędnych klasy nadrzędnej - ty wpuszczasz klasy równorzędne. Znaczy to tyle że jeżeli nasza klasa B używa C, D, E, F, G to B będzie wyglądać tak:
class B
{
  C c; D d; E e; F f; G g;
  public B(C c, D d, E e, F f, G g)
  {
    this.c = c;
    this.d = d;
    this.e = e;
    this.f = f;
    this.g = g;
  }
}

I masz sieczkę... nie daj boshe żeby C, D, E, F, G wszystkie miały wskaźniki na resztę. W drugim przykładzie zrobiłeś to samo, tyle że interface-ami. Lepiej by było to zrobić w ten sposób (btw. coś jak usługi):
interface IB
{
  B B
  {
    get;
    set;
  }
}
class A : IB, IC, ID, IE, IG
{
  B b;
  IB.B
  {
    get { return b; }
    set { b = value; }
}
class C
{
  A a;
  public C(A a) //albo któraś usługa ale sprowadza się to częściowo do sytuacji przedstawionej przez ciebie
  {
    this.a = a;
  }
}
Oczywiście muszę ci przyznać rację w punkcie: Czy klient implementuje interface czy jest to jakby usługa jak w przytoczonym przykładzie, to kwestia smaku i potrzeb.
Osobiście wolę mojego pseudo singletona czy metody biorące konkretne obiekty za arguemnty niż rozsyłać referencje.

Firkraag: no właśnie to jest najlepsze dopóki nie ma bajzlu, jak z resztą napisałem w poprzednim poście.
« Ostatnia zmiana: Czerwiec 02, 2008, 15:07:17 wysłana przez poopa »

Offline skowronkow

  • Użytkownik
    • skowronkow devsite

# Czerwiec 02, 2008, 15:18:36
Konczac dyskusje, napisalem mozliwe rozwiazania ale w oderwaniu o konkretow wiec mozna zalozyc takze ze wspomniana sieczka nie nastapi. Tak naprawde rozchodzi sie o to ze jesli 2 klasy rownorzedne musze sie ze soba komunikowac to albo sa zarzadzane przez klase nadrzedna albo stosujemy jakis wzorzec w celu zapewnienia tej komunikacji (ciezko powiedziec, ktory w tym wypadku). Jesli zadne rozwiazanie nie pasuje (byloby za duzo zaleznosci itd) to znaczy ze calosc jest calkowicie zle zaprojketkowana.

PS. to pierwsze co napisalem piszac klase C rowniez mialem na mysli raczej jakas abstrakcje niz konretna klase. Co do wstrzykiwania klasy przez interfejs jest to o niebo lepsze niz trzymanie jakichs globalnych klas. Wszystko zalezy od zaprojketowania dobrego systemu w ktorym takich przypadkow bedzie jak najmniej (najlepiej w ogole).

Pozdrawiam:)