Autor Wątek: C++ Konwencje, oddzielenie modułów projektu  (Przeczytany 5724 razy)

Offline artur1

  • Użytkownik

# Maj 20, 2013, 21:35:18
Witam!

Przepisuję swoje stare GUI, chcę napisać je jako zewnętrzną bibliotekę więc wszystko umieszczam w przestrzeni nazw aby oddzielić od reszty kodu, tylko że moje stare GUI składa się też z pewnych modułów które też kiedyś napisałem i ich przeznaczenie jest ogólnikowe np. szablon klasy Singleton ( tą klasę dziedziczą inne ), paraser plików, timer itp i teraz pytanie czy dobrą rzeczą jest przydzielenie tego do całości GUI ? Czy może zostawić to jako zewnętrzny moduł tzn stworzyć kolejną przestrzeń nazw typu otherLibray i tutaj dać wszystkie te klasy a tą przestrzeń zagnieździć w przestrzeni GUI ? Jak to powinno wyglądać ?
Np mam napisane klasy odpowiadające za wczytywanie pixel vertex shaderów i one się nazywają CPixelShader, CVertexShader mogę ich użyć w każdym projekcie ale czy to oznacza że jak piszę jakiś projekt to powinienem te klasy wtopić w ten projekt czy może oddzielić od projektu w osobnej przestrzeni nazw ?
Kolejna sprawa to od jakiej literki powinienem zaczynać nazwy klas ? Jakoś sam się przyzwyczaiłem do tego aby nazwę klasy rozpoczynać dużą literką C, ale nie raz w projektach widziałem że np klasa singletona rozpoczyna się od literki I jak interface... w każdym projekcie zmienne znajdujące się w ciele klasy zaczynają się od m_ ale jakie są konwencje ? z tego co słyszałem to każdy robi po swojemu...

Offline Mr. Spam

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

Offline Xirdus

  • Redaktor

# Maj 20, 2013, 22:09:03
1. Po co w GUI singleton?

2. Klasy w bibliotece GUI które nie są od GUI są w porządku, o ile traktujesz je tak, jakby były osobnymi bibliotekami (tzn. nie zależą od żadnej innej klasy, której używasz - GUI za to może być od nich zależne, o ile jest taka potrzeba).

3. Każdy ma konwencję jaka mu pasuje. Musisz znaleźć swój styl. Ja np. mam taką: żadnej notacji węgierskiej, camelCase, klasy i przestrzenie nazw z dużej litery, cała reszta z małej, funkcje to czasowniki, klasy to rzeczowniki, zmienne jak w danej chwili wygodnie, klamra otwierająca od nowej linii, jednolinijkowe ify nie dostają klamer (ale po warunku zawsze jest enter, a następna linia ma wcięcie; to samo z forem, z tym że jak mam zagnieżdżone pętle jedna pod drugą to wcięcia nie robię).

Pamiętaj jednak, że w projektach zespołowych wszyscy powinni stosować jedną wspólną konwencję.

Offline artur1

  • Użytkownik

# Maj 20, 2013, 23:28:35
1. Klasa GUISystem, oraz GUIRender jako singleton. GUIRender będzie używany tylko i wyłącznie przez funkcje rysujące kontrolki, a jako singleton ponieważ chyba lepiej to niż przekazywać jako argument, no chyba że się mylę... I jeszcze jedna klasa pełniąca rolę metod obliczających różne rzeczy, taka klasa bardziej matematyczna, a co do klasy GUISystem to lepiej chyba lepiej będzie się używało singletona niż zwykłego obiektu globalnego.

2. GUI jest tylko zależne od tych klas, one wczytują ustawienia itp ale te klasy nie są zależne od GUI. Czyli jak ? Dać w osobnej przestrzeni czy wspólnej ?

Offline Xirdus

  • Redaktor

# Maj 20, 2013, 23:54:26
1. Klasa GUISystem, oraz GUIRender jako singleton. GUIRender będzie używany tylko i wyłącznie przez funkcje rysujące kontrolki, a jako singleton ponieważ chyba lepiej to niż przekazywać jako argument, no chyba że się mylę (...) a co do klasy GUISystem to lepiej chyba lepiej będzie się używało singletona niż zwykłego obiektu globalnego.
Z tym, że używając singletonów w bibliotece narzucasz jej użytkownikowi pewien styl pisania aplikacji. Gdzie singletony, tam niespodziewane wewnętrzne zależności które na pewno kiedyś komuś wyjdą bokiem. Używając zwykłych obiektów, dajesz użytkownikowi większą swobodę organizacji kodu. Zawsze też można przecież opakować to w singletony po stronie nie-bibliotecznej. Chyba że ten GUIRender jest strategicznym punktem designu twojej biblioteki.

I jeszcze jedna klasa pełniąca rolę metod obliczających różne rzeczy, taka klasa bardziej matematyczna
To coś w ogóle nie powinno być klasą.

2. GUI jest tylko zależne od tych klas, one wczytują ustawienia itp ale te klasy nie są zależne od GUI. Czyli jak ? Dać w osobnej przestrzeni czy wspólnej ?
Osobnej - jak masz przestrzeń GUI, to te shadery daj w GUIUtils na przykład. Względnie podprzestrzeń GUI::Utils.

Offline artur1

  • Użytkownik

# Maj 20, 2013, 23:58:47
Zakładam że to tylko ja będę z tego korzystał, ale to jednak cenna uwaga. W taki razie czym to powinno być ? Globalnymi funkcjami ?

Offline Xirdus

  • Redaktor

# Maj 21, 2013, 00:12:50
Nie globalnymi, tylko ujętymi w jakąś przestrzeń nazw. Generalnie chodzi o to, żeby funkcje "tylko liczące" były bezstanowe - dla tych samych parametrów dają zawsze ten sam wynik.

Offline artur1

  • Użytkownik

# Maj 21, 2013, 00:24:08
Tak, ale zamiast dawać je w przestrzeń żeby tam były luzem to nie lepiej już opakować w klasę i dać jako singleton ?

Kolejna sprawa, Klasa GUIRender potrzebuje adresu do kontekstu urządzenia jak i samego urządzenia, klasy do wczytywania pixel i vertex shadera też, tutaj nie ma problemu bo przekazuję to jako argument, ale czy jest to sensowne rozwiązanie ? może dać to jako statyczny obiekt klasy ?  Jest jeszcze klasa GUIBitmapFontRender, i pytanie gdzie trzymać adres do urządzenia i jego kontekstu ? przekazać go na stałe do GUIRender i GUIBitmapFontRender czy może przekazywać za każdym razem kiedy rysuje się element kontrolki przez metodę onDraw ? Czy może trzymać to w jakiejś innej klasie ? Bo nie ma sensu chyba trzymać w GUIRender i GUIBitmapFontRender jednocześnie ?

Offline Xirdus

  • Redaktor

# Maj 21, 2013, 00:31:58
Tak, ale zamiast dawać je w przestrzeń żeby tam były luzem to nie lepiej już opakować w klasę i dać jako singleton ?
Nie, nie lepiej. Co ci przeszkadza kilka funkcji z tej samej paki wrzucone do jednego worka, oddzielnego od reszty ekosystemu? To jest dokładnie to, po co wymyślono przestrzenie nazw! A klasa implikuje obiekt ze zmiennymi - a przecież nie chcesz mieć stanu.

Bo nie ma sensu chyba trzymać w GUIRender i GUIBitmapFontRender jednocześnie ?
Kilka bajtów cię nie zbawi. A minimalizacja wewnętrznych zależności - tak.

Offline Xion

  • Redaktor
    • xion.log

# Maj 21, 2013, 00:38:55
Cytuj
Tak, ale zamiast dawać je w przestrzeń żeby tam były luzem to nie lepiej już opakować w klasę i dać jako singleton ?
Nie, nie lepiej. W C++ masz normalne przestrzenie nazw więc korzystaj z nich. Funkcje w namespace'ie nie będą "luzem". W Javie zebrałbyś je w klasę w metodami statycznymi, ale tutaj nie musisz takich robić myków.

Cytuj
Kolejna sprawa, Klasa GUIRender potrzebuje (...)
...za to musisz robić inne, związane z zależnościami między obiektami. Bo skoro coś czegoś potrzebuje, to niech dostaje to po prostu w konstruktorze:
guiRender = new GUIRender(deviceContext, ...);To najprostsze i najlogiczniejsze rozwiązanie, tyle że przy większej liczbie zależności zaczyna się robić trochę niewygodne w użyciu. Niestety w C++ nie ma żadnego dobrego rozwiązania do Dependency Injection, więc trzeba z tym żyć.

Offline artur1

  • Użytkownik

# Maj 21, 2013, 11:59:24
W sumie to racja, teraz to trochę inaczej widzę. Klasę GUIRender i GUIBitmapFontRender umieścić w klasie GUISystem a nie jako singleton, i przy tworzeniu wszystko podać, i w tym momencie nie ma już tego problemu z singletonami. Jeżeli chodzi o te metody to takie rozwiązanie jednym słowem mnie przeraża, nie wiem dlaczego ale tak jest, bo jakoś tak mam że nie znoszę luźnych globali, te metody są od pobrania położenia myszy, sprawdzenia czy znajduje się nad danym obszarem itp. Ale jeżeli umieszczę je w osobnej przestrzeni to może wymagają one jakiejś specjalnej nazwy, końcówki, konwencji... ?
To mam już 3 przestrzenie:
namespace GUI
{
       namespace GUIUtils
       {
       }

       namespace GUIGlobals
       {
       }
}

Offline artur1

  • Użytkownik

# Maj 25, 2013, 16:13:00
Sam już sobie narobiłem masę dylematów więc jeżeli wypada klasy GUIRender i GUIBitmapFontRender umieścić w klasie GUISystem, to muszę je za każdym razem przekazywać jako argument każdej kontrolne w trakcie rysowania:

class GUIElement
{
public:

             friend GUISystem;

protected:

             void onDraw(GUIRender* render, GUIBitmapFontRender* fontRender);
};
To jest jedno rozwiązanie, drugie jest takie że może klasa GUIElement owinna mieć statyczny wskaźnik do tych dwóch obiektów, bo i tak każda kontrolka będzie dziedziczyć tą klasę.

Druga sprawa, GUIRender odpowiada za rysowanie kontrolek, kolorowy prostokąt czy prostokąt z teksturą, natomiast klasa GUIBitmapFontRender odpowiada tylko za rysowanie czcionek, ale czy nie powinno być tak że ta klasa korzysta z klasy GUIRender ? Bo to ona przecież rysuje figury a tekst to prostokąt z teksturą. Możliwe że tym rozwiązaniem dalej tylko namieszam. Jak nie namieszać a zrobić to mądrze ?


Offline siso

  • Użytkownik

# Maj 28, 2013, 01:09:45
Cytuj
Jak nie namieszać a zrobić to mądrze ?
Oddziel renderer GUI od logiki GUI.
Logika powinna zajmować się wszystkimi obliczeniami i przygotowaniem kontrolek do wyświetlenia. Renderer ma tylko wyświetlać przygotowane dane. Nie ma prawa modyfikować stanu przygotowanego przez klasy logiki.

Na styku tych dwóch światów masz tzw. "szew" (ang. "seam"). Możesz odciąć jeden od drugiego i przetestować renderer, każąc mu narysować w zadanym viewporcie to, co dostarczyłeś mu do testu jako "fake'owy" stan GUI. Z drugiej strony możesz przetestować sama logikę, każąc jej wyliczyć wszystko do osiągnięcia odpowiedniego stanu zdatnego do wrzucenia na wejście renderera, a następnie ten stan zweryfikować.

Trochę to wszystko nadmiarowe, ale z drugiej strony...
Teraz nie ma mowy o pomyłce. Do każdego typu kontrolki możesz napisać sobie testy jednostkowe. Piszesz test raz, a kiedy założenia testu są ujęte poprawnie, implementacja logiki nie ma innego wyjścia, jak tylko sprawić, by test był "zielony". Zielone testy opisują Ci wszystkie wymagania logiki GUI i wszelkiej maści utilsów, jakie tylko będą potrzebne. Żadne wymaganie się nie prześlizgnie, a jeśli któreś z nich oddziałuje na inne, od razu zobaczysz, że coś jest nie halo. Któreś testy zechcą się wyłożyć.

Unikaj stanu statycznego. Bardzo niewygodnie się takie coś testuje.

Mając świadomość, jakie to wszystko sprytne, postanawiasz pójść o krok dalej. Co by się stało, gdyby najpierw napisać test, a później dopiero implementację, która go zazieleni? W ten sposób otrzymasz implementację, która nie robi nic ponad to, co niezbędne.
Pisząc taki trzeci czy czwarty cykl z rzędu zaczynasz zakochiwać się w TDD...

Wszystkiego dobrego na nowej drodze życia! :)

Offline Xion

  • Redaktor
    • xion.log

  • +1
# Maj 30, 2013, 01:42:48
@up: Powiedziałbym coś, ale przypomniałem sobie że mamy ten paragraf odnośnie obrazy uczuć religijnych...

Offline raver

  • Użytkownik
    • Moja strona domowa.

# Maj 30, 2013, 11:12:14
@sisu: A mógłbyś napisać jak miałby wyglądać test jednostkowy do kontrolki? Jakoś nie mogę sobie tego wyobrazić (nie używam tdd).

Offline siso

  • Użytkownik

# Maj 30, 2013, 12:35:56
Zapodaj kod kontrolki, dam Ci do niej testy.