Autor Wątek: Zależności klas.  (Przeczytany 1907 razy)

Offline waz0n

  • Użytkownik

# Kwiecień 17, 2010, 17:06:53
Witam,
mam taki oto problem i nie wiem, w jaki sposób elegancko go rozwiązać. Może dałoby się użyć jakiegoś wzorca projektowego? Otóż do rzeczy:

1. Mam klasę:

class CMyClass1 {
public:
void MyMethod1();

private:
int m_iParameter1;
};

2. I jakieś dwie inne klasy:

class CMyClass2 {
public:
void MyMethod2();

private:
int m_iParameter2;
};


class CMyClass3 {
public:
void MyMethod3();

private:
int m_iParameter3;
};

Powiedzmy, że klasa CMyClass1 służy do obsługi bazy danych - jest pośrednikiem między bazą danych i innymi klasami. Klasy CMyClass2 i CMyClass3 korzystają z bazy danych za pośrednictwem klasy CMyClass1. Jak w najlepszy sposób odwzorować tę zależność w kodzie?

Jako pierwsze rozwiązanie przyszedł mi do głowy Singleton, którym byłaby klasa CMyClass1. Jako drugie to rozwiązanie typu:

CMyClass2 i CMyClass3 miałyby w swoich składowych referencję na CMyClass1 przekazywaną np. w konstruktorze.

Jakie jest najbardziej eleganckie rozwiązanie odwzorowania w kodzie takich zależności i jak to się robi w 'profesjonalnych projektach'? Czy można pod tą sytuację podciągnąć jakiś wzorzec projektowy? Z góry dziękuje za odpowiedzi.

Offline Mr. Spam

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

Offline Stig

  • Użytkownik

# Kwiecień 17, 2010, 17:14:45
Wydaję mi się że możesz użyć factory, niech mnie ktoś poprawi jeśli się mylę

Offline maciek_slon

  • Użytkownik

# Kwiecień 17, 2010, 17:34:10
Wydaję mi się że możesz użyć factory, niech mnie ktoś poprawi jeśli się mylę

Mylisz się ;P

Wzorce pasujące jakoś do tego problemu to np. Proxy, ew. Fasada

Offline Lipek Samo Zło

  • Użytkownik

# Kwiecień 19, 2010, 11:33:56
Zdecydowanie fasada :)

Offline waz0n

  • Użytkownik

# Kwiecień 19, 2010, 23:19:55
Witam,
dziękuje za wcześniejsze wypowiedzi. Chodzi mi ogólnie o coś takiego:

class CDatabase {
public:
void Query(std::string &sQuery);
//....
private:
//....
};

No i teraz powiedzmy mam dwie klasy, które pobierają jakieś informacje z bazy danych. Zastanawiam się jak to rozwiązać. Np tak:

class CPlayer {
public:
explicit CPlayer(CDatabase &rDbInstance): m_rDbInstance(rDbInstance) {
this->dbGetLifePoints();
}

void Attack(CPlayer &rEnemy);

private:
void dbGetLifePoints() {
this->m_iLifePoints = m_rDbInstance.Query("SELECT life_points FROM players WHERE id = "someone");
}

CDatabase &m_rDbInstance;
int m_iLifePoints;
};

Oczywiście powyższy kod jest pisany z palca, także proszę się nie sugerować poprawnością ani kompletnością. Chodzi mi bardziej o wytypowanie bardziej eleganckiego rozwiązania. Czy lepiej przekazywać np. referencję/wskaźnik na klasę obsługującą bazę danych za każdym razem, do obiektu korzystającego z bazy, czy może po prostu dać klasę CDatabase jako Singleton i mieć problem z głowy? Mi do głowy przyszły tylko takie dwa rozwiązania. Być może są inne koncepcje rozwiązania tego problemu, lub całkiem inne podejście doń, o których bardzo chętnie się dowiem :). Pozdrawiam.
« Ostatnia zmiana: Kwiecień 19, 2010, 23:25:14 wysłana przez waz0n »

Offline rm-f

  • Użytkownik
    • Tu trolluje

# Kwiecień 20, 2010, 00:01:09
Nie myślałeś by "wyprodukować" "gotowe" zapytania przy tworzeniu klasy, i trzymać je u siebie. Każde zapytanie posiadało by sobie swoją referencje do bazy i basta.  ;)

Kod: (cpp) [Zaznacz]
class CPlayer
{
private:
     CQuery LifePoints;
     CID ID;
public:
     CPlayer(CDataBase& $):ID(),LifePoints($,"SELECT life_points FROM players WHERE id=%i",ID.Val)
     {
       
     }

     void SomeMethod()
     {
         [...]
         LifePoints = foo((int)LifePoints);
     }
};


Offline waz0n

  • Użytkownik

# Kwiecień 20, 2010, 00:44:03
A powiedzmy, że mam w aplikacji około 7000 zapytań do bazy. Jedne używane częściej, drugie rzadziej. Musiałbym stworzyć te 7000 obiektów z zapytaniami, ustawić im referencję na obiekt bazy danych, ustawić string z zapytaniem, itp. Czy to nie będzie zbyt wielki narzut?

Offline Dab

  • Redaktor
    • blog

# Kwiecień 20, 2010, 00:50:19
Zamiast

Engine::Render
{
foreach (player)
{
player->Render();
}
}


gdzie każdy player ma swoje odwołanie od DB można rozważyć taki projekt:

Engine::RenderAllPlayers
{
foreach (player)
{
// rob cos z player
}
}


przy czym player to bardziej pojemnik na dane niż obiekt który sam coś robi (obiekt DB posiada tylko Engine).

Offline siso

  • Użytkownik

# Kwiecień 20, 2010, 01:21:55
A powiedzmy, że mam w aplikacji około 7000 zapytań do bazy. Jedne używane częściej, drugie rzadziej. Musiałbym stworzyć te 7000 obiektów z zapytaniami, ustawić im referencję na obiekt bazy danych, ustawić string z zapytaniem, itp. Czy to nie będzie zbyt wielki narzut?

Będzie.

Nie potrzebujesz trzymać w każdym obiekcie referencji do bazy czy wręcz całego zapytania czy nawet kilku, bo domyślam się, że jedno to zdecydowanie za mało. Nie potrzebujesz ich tam nawet trzymać statycznie. Obiekt danych powinien przechowywać tylko swój stan i ew. informacje o relacjach, w jakich się znajduje. Zapytaniami zajmuje się inny byt, który do tego celu został zaprojektowany. I do żadnego innego.
 
Potrzebujesz fasady, jak już Koledzy napisali. Fasada taka martwi się o zarządzanie połączeniami z bazą. Oprócz tego wykonuje wszystkie zapytania, jakie tylko potrzebujesz:
- tworzenie obiektów w bazie,
- update'owanie ich tamże,
- wyszukiwanie z zadanymi kryteriami.
Jedyne, co musisz zrobić, to skonstruować zapytania, ustawić obiekt(y) danych jako ich parametr(y) i wepchnąć to do fasady :).
 
W zależności od użytego typu bazy możesz nawet wykonywać po jej stronie jakieś procedury na danych - oszczędzasz aplikację i pasmo (przy założeniu, że korzystasz z serwera bazy, nie z jakiejś wersji embedded).

Offline TeMPOraL

  • Użytkownik
    • devBlog

# Kwiecień 20, 2010, 03:43:55
Zamiast

Engine::Render
{
foreach (player)
{
player->Render();
}
}


gdzie każdy player ma swoje odwołanie od DB można rozważyć taki projekt:

Engine::RenderAllPlayers
{
foreach (player)
{
// rob cos z player
}
}


przy czym player to bardziej pojemnik na dane niż obiekt który sam coś robi (obiekt DB posiada tylko Engine).
A jakby to zrobić tak:
Engine::RenderAllPlayers
{
foreach (player)
{
   player->RenderSelf(renderingContext);
}
}

? ;)
Gdzie obiekt renderingContext zawiera metody potrzebne playerowi do wyrysowania, korzystania z bazy danych, whatever - w ten sposób player nie jest już "zwykłym pojemnikiem na dane", ale do poprawnego zachowania potrzebuje kontekstu, w którym może wykonywać operacje. Abstrakcja zachowana :).