Wątek przeniesiony 2022-06-21 17:19 z Kosz przez Adam Boduch.

Strategia w programowaniu obiektowym

0
jarekr000000 napisał(a):
Riddle napisał(a):
jarekr000000 napisał(a):

No to mamy wreszcie jakąś spójną definicję obiektowości i cieszy mnie, że mój ukochany BASIC z C64 się załapał (nie wiem czy każdy program w BASICU, ale z tego co widzę powyżej to dużo się załapie)

Od razu mówię, że według mnie nie ma czegośtakiego jak "język obiektowy", pisałem już o tym wyzej.

Mogę Ci powiedzieć czy kod który napisałeś jest obiektowy czy nie.

Oczywiście.
Aczkolwiek, zgodnie z tą definicją to w zasadzie nie da się napisać w tym BASICu kodu, który nie byłby obiektowy. A to mi wystarcza. Oznacza, że byłem programistą obiektowym zanim to było modne.

Da się, chociażby gdybyś napisał kod który na jednym stringu wyciąga extension a na innym mime-type; który już pokazałem wcześniej. Wtedy nie byłby obiektowy.

4

@Riddle: skąd wytrzasnąłeś taką definicję programowania OOP? Bo według Twojej definicji to mało co jest możliwe jako OOP, Twoja definicja wygląda mi na sprowadzenie wszystkiego do czysto funkcyjnego kodu z akceptacją z pewną dozą obrzydzenia, że muszą istnieć funkcję zmieniające stan globalny / lokalny jak zapis do pliku. Większość tej dyskusji jest sprowokowane tym, że zastosowałeś swoją definicję programowania OOP, która jest inna niż większość osób zna. No to tak, strategia nie będzie programownaiem obiektowym.

0
MuadibAtrides napisał(a):

@Riddle: skąd wytrzasnąłeś taką definicję programowania OOP? Bo według Twojej definicji to mało co jest możliwe jako OOP, Twoja definicja wygląda mi na sprowadzenie wszystkiego do czysto funkcyjnego kodu z akceptacją z pewną dozą obrzydzenia, że muszą istnieć funkcję zmieniające stan globalny / lokalny jak zapis do pliku. Większość tej dyskusji jest sprowokowane tym, że zastosowałeś swoją definicję programowania OOP, która jest inna niż większość osób zna. No to tak, strategia nie będzie programownaiem obiektowym.

Mało co jest możliwe w OOP?

  • Masz na myśli to że mało rzeczy można zaimplementować? Brednie, możesz co tylko Ci się podoba - tzn możesz w ten sposób napisać program, który robi co tylko chcesz.
  • Masz na myśli to, że wielu elementów języka programowania, albo wiele rzeczy które "się potocznie robi" nie można - oj tak! Podpisuje się dwoma rękami. Dlatego że większość elementów które "się potocznie używa", to jest źródło największego syfu i tech debtu.
0
Riddle napisał(a):

Mało co jest możliwe w OOP?

  • Masz na myśli to że mało rzeczy można zaimplementować? Brednie, możesz co tylko Ci się podoba - tzn możesz w ten sposób napisać program, który robi co tylko chcesz.

No to ja chciałbym tę kolejkę do synchronizacji watków o której mówiłem wcześniej

0

@Riddle: wiem, mogę pisać kod czysto funkcyjny bo w końcu mogę używać funkcji i kolega @omenomn2 pisze niestety na temat w końcu definiuje klasy

0
KamilAdam napisał(a):
Riddle napisał(a):

Mało co jest możliwe w OOP?

  • Masz na myśli to że mało rzeczy można zaimplementować? Brednie, możesz co tylko Ci się podoba - tzn możesz w ten sposób napisać program, który robi co tylko chcesz.

No to ja chciałbym tę kolejkę do synchronizacji watków o której mówiłem wcześniej

No ale to co, nie ma atomicznych operacji które nie łamią CQS?

1

@Riddle: Jak wchodzi ci wielowątkowość, to ciężko o CQS, jeżeli poważnie traktujesz "nie zmienia stanu obiektu". Jak bezpiecznie zrobić takie AtomicInteger.getAndIncrement()? mógłbyś to rozbić na 2 operacje, getAndLock() incrementAndRelease() ale zablokowanie to nadal zmiana stanu.

0
piotrpo napisał(a):

@Riddle: Jak wchodzi ci wielowątkowość, to ciężko o CQS, jeżeli poważnie traktujesz "nie zmienia stanu obiektu". Jak bezpiecznie zrobić takie AtomicInteger.getAndIncrement()? mógłbyś to rozbić na 2 operacje, getAndLock() incrementAndRelease() ale zablokowanie to nadal zmiana stanu.

Synchronized block?

synchronized (lock) {
  int.get();
  int.inc();
}
1
Riddle napisał(a):
piotrpo napisał(a):

@Riddle: Jak wchodzi ci wielowątkowość, to ciężko o CQS, jeżeli poważnie traktujesz "nie zmienia stanu obiektu". Jak bezpiecznie zrobić takie AtomicInteger.getAndIncrement()? mógłbyś to rozbić na 2 operacje, getAndLock() incrementAndRelease() ale zablokowanie to nadal zmiana stanu.

Synchronized block?

synchronized (lock) {
  int.get();
  int.inc();
}

I potem będziesz to powtarzać wielokrotnie w kodzie żeby nie złamać CQSa czy jednak wydzielisz do metody łamiąc CQSa?

0
KamilAdam napisał(a):

I potem będziesz to powtarzać wielokrotnie w kodzie żeby nie złamać CQSa czy jednak wydzielisz do metody łamiąc CQSa?

Nie umiem rozwiązać dobrze CQS vs. thread-safety. Można założyć osobny wątek na to.

Możliwe że CQS nie jest częścią OOP; (nie sądzę, ale możliwe).

1

Niby można robić te synchronizowane bloki, ale synchronizacja kosztuje duuuuużo więcej niż wspierany na poziomie CPU CAS. Ale nie jest to jedyny przykład, gdzie "ładny kod" jest przeciwstawny "wydajnemu kodowi".

1
piotrpo napisał(a):

Niby można robić te synchronizowane bloki, ale synchronizacja kosztuje duuuuużo więcej niż wspierany na poziomie CPU CAS. Ale nie jest to jedyny przykład, gdzie "ładny kod" jest przeciwstawny "wydajnemu kodowi".

Jeśli ładny kod jest przeciwstawny wydajnemu kodowi to tznaczy, że kompilator jest dupa.

Bywa. Ale po to się poprawia kompilatory, żeby nie bywało.

5

@Riddle

Metoda zmieniająca stan obiektu lub systemu ma return-type void.

serio?

Po tym jednym podpunkcie nie podoba mi się ta twoja interpretacja OOP, ani trochę.

1

J

1a2b3c4d5e napisał(a):

@Riddle

Metoda zmieniająca stan obiektu lub systemu ma return-type void.

serio?
Po tym jednym podpunkcie nie podoba mi się ta twoja interpretacja OOP, ani trochę.

A do tego jest to recepta na katastrofę po refaktoringu.

CQS mimo, że jest relatywnie elegancką koncepcją jest po prostu błędna (kontrproduktywna), bo nadużywanie void prowadzi do błędów przy refaktoringu.
Pochodzi z czasów (198x) kiedy jeszcze wiele rzeczy nie było przetestowanych na większą skalę, więc nie dziwne.

0
jarekr000000 napisał(a):

J

1a2b3c4d5e napisał(a):

@Riddle

Metoda zmieniająca stan obiektu lub systemu ma return-type void.

serio?
Po tym jednym podpunkcie nie podoba mi się ta twoja interpretacja OOP, ani trochę.

A do tego jest to recepta na katastrofę po refaktoringu.

CQS mimo, że jest relatywnie elegancką koncepcją jest po prostu błędna (kontrproduktywna), bo nadużywanie void prowadzi do błędów przy refaktoringu.
Pochodzi z czasów (198x) kiedy jeszcze wiele rzeczy nie było przetestowanych na większą skalę, więc nie dziwne.

Jak czytasz taki kod:

value1 = a.doStuff();
value2 = b.doStuff();

to wydaje się sensowne, że nic się nie stanie, jak zamienisz je miejscami. Albo wydzielisz zmienną, przekażesz coś przez parametry. Z callami do metod void() widać, że zmiana kolejności może coś zepsuć.

0
Riddle napisał(a):
jarekr000000 napisał(a):

J

1a2b3c4d5e napisał(a):

@Riddle

Metoda zmieniająca stan obiektu lub systemu ma return-type void.

serio?
Po tym jednym podpunkcie nie podoba mi się ta twoja interpretacja OOP, ani trochę.

A do tego jest to recepta na katastrofę po refaktoringu.

CQS mimo, że jest relatywnie elegancką koncepcją jest po prostu błędna (kontrproduktywna), bo nadużywanie void prowadzi do błędów przy refaktoringu.
Pochodzi z czasów (198x) kiedy jeszcze wiele rzeczy nie było przetestowanych na większą skalę, więc nie dziwne.

Jak czytasz taki kod:

value1 = a.doStuff();
value2 = b.doStuff();

to wydaje się sensowne, że nic się nie stanie, jak zamienisz je miejscami. Albo wydzielisz zmienną, przekażesz coś przez parametry. Z callami do metod void() widać, że zmiana kolejności może coś zepsuć.

Nie, ja tego nie widzę :(

0
KamilAdam napisał(a)

Jak czytasz taki kod:

value1 = a.doStuff();
value2 = b.doStuff();

to wydaje się sensowne, że nic się nie stanie, jak zamienisz je miejscami. Albo wydzielisz zmienną, przekażesz coś przez parametry. Z callami do metod void() widać, że zmiana kolejności może coś zepsuć.

Nie, ja tego nie widzę :(

Nie widzisz tego że jak zamienisz miejscami wywołania funkcji to program może działać inaczej?

0

Ostatniego zdania nie widzę

Z callami do metod void() widać, że zmiana kolejności może coś zepsuć.

Nie rozumiem czemu z void miało by być bardziej czytelne niż metoda zwracająca coś

2

@Riddle:

Chodzi o to, że void jest ubogi, powinien być jakiś Result<T>, a nie jakieś półśrodki, w końcu to OOP (tu akurat with @Riddle's flavour), czyli framework do modelowania.

1

Jeśli dwie funkcje muszą być wykonane w ściśle określonej kolejności. Jedna po drugiej, to najlepiej jak druga ma w sygnaturze argument zwracany przez pierwszą. (Druga może być metodą w klasie zwracanej przez pierwszą).
Jeśli te metody już istnieją to można dorobić jakieś wrappery.
To podstawa zdrowego api i nie rozstawiania sobie pola minowego w kodzie.

W innym przypadku (void), ktoś kiedyś zaleci zakomentowaniem za daleko, albo wstawi przypadkowo klamrę ifa pomiędzy i nieszczęście gotowe.

0

@jarekr000000: To chyba nie jest specjalnie przestrzegane w OOP, nie piszę, że to złe, tylko nie zetknąłem się z taką praktyką. Przykład kiedy kolejność wywołań ma znaczenie:

kibelek.klapa.otwórz()
kibelek.deska.otwórz()
człowiek.jedyneczkaExcecute()
kibelek.deska.opuść()
człowiek.usiądź(kibelek)
człowiek.dwójeczkaExecute()
2

@piotrpo:
Więc API powinno wyglądać do tego odpowiednio.

trait Kibelek {
   def otwórzKlape() : KibelekZOtwartąKlapą
}

trait KibelekZOtwartąKlapą {
  def podnieśDeskę() : KibelekZPodniesionąDeską
  def dwójeczkaExecute() : KibelekZOtwartąKlapąINieSpuszczonąKupą
  def jedyneczkaExecute() : KibelekZOszczanąDeską

}

trait KibelekZPodniesionąDeską {
    def jedyneczkaExecute() : KibelekZPodniesionąDeską
    def opuśćDeskę() : KibelekZOtwartąKlapą
}

trait KibelekZOszczanąDeską {
   def wytrzyjDeske() : KibelekZOtwartąKlapą
}
//itd.
0
jarekr000000 napisał(a):

@piotrpo:
Więc API powinno wyglądać do tego odpowiednio.

trait Kibelek {
   def otwórzKlape() : KibelekZOtwartąKlapą
}

trait KibelekZOtwartąKlapą {
  def podnieśDeskę() : KibelekZPodniesionąDeską
  def dwójeczkaExecute() : KibelekZOtwartąKlapąINieSpuszczonąKupą
  def jedyneczkaExecute() : KibelekZOszczanąDeską

}

trait KibelekZPodniesionąDeską {
    def jedyneczkaExecute() : KibelekZPodniesionąDeską
    def opuśćDeskę() : KibelekZOtwartąKlapą
}

trait KibelekZOszczanąDeską {
   def wytrzyjDeske() : KibelekZOtwartąKlapą
}
//itd.

Jasne. Uniknięcie metod void sprawia że nie musisz się przejmować kolejnością.

Ale @piotrpo ma rację. Jeśli już masz metody void, to bezpiecznie jest założyć że zmiana ich kolejności wywołać może coś sknocić.

0
Riddle napisał(a):
jarekr000000 napisał(a):

@piotrpo:
Więc API powinno wyglądać do tego odpowiednio.

trait Kibelek {
   def otwórzKlape() : KibelekZOtwartąKlapą
}

trait KibelekZOtwartąKlapą {
  def podnieśDeskę() : KibelekZPodniesionąDeską
  def dwójeczkaExecute() : KibelekZOtwartąKlapąINieSpuszczonąKupą
  def jedyneczkaExecute() : KibelekZOszczanąDeską

}

trait KibelekZPodniesionąDeską {
    def jedyneczkaExecute() : KibelekZPodniesionąDeską
    def opuśćDeskę() : KibelekZOtwartąKlapą
}

trait KibelekZOszczanąDeską {
   def wytrzyjDeske() : KibelekZOtwartąKlapą
}
//itd.

Jasne. Uniknięcie metod void sprawia że nie musisz się przejmować kolejnością.

Zauważ że tu masz fluent API i nie wywołasz jedyneczkaExecute jeśli wcześniej nie wywołasz otwórzKlape więc przykładowy kod wygląda:

  val kibelek: Kibelek = Kibelek.jakiśKonstruktor()
  kibelek
    .otwórzKlape()
    .podnieśDeskę()
    .jedyneczkaExecute()
    .opuśćDeskę()

zwyczajnie nie da się zrobić:

  val kibelek: Kibelek = Kibelek.jakiśKonstruktor()
  kibelek
    .jedyneczkaExecute()
    .opuśćDeskę()

Bo dostaniesz błąd kompilacji na tym że jedyneczkaExecute() nie jest metodą klasy/tritu/interfejsu Kibelek

1

@Riddle: To nie jest kwestia tego, że "bezpieczniej założyć". Tutaj jest proces orkiestrujący kilka obiektów, wiesz jaki proces chcesz zamodelować, więc wiesz o tym w jakiej kolejności należy wykonywać metody na różnych obiektach.
A kontynuując mój gimba level trolling, to faktycznie wydaje się sensowne, że jedyneczkaExecute i dwójeczkaExecute faktycznie nie powinny być void.

0

Jak już o kibelku mowa to wczoraj łażąć po Łazienkach Królewskich wpadłem na kolejną metodę/funkcję, której nie da się sensownie ogarnąć w CQSie czyli zapis do bazy z jednoczesnym zwróceniem klucza:

INSERT INTO table_name(column1, column2, …)
VALUES (value1, value2, …)
RETURNING id;

lub całego obiektu:

INSERT INTO table_name(column1, column2, …)
VALUES (value1, value2, …)
RETURNING *;

No nie da się zwrócić void, bo po

1

@KamilAdam:
W teorii mógłbyś zostawić to zwrócone ID w obiekcie jako pole.
A potem pobrać geterem :-)

Ale jest to rozwiązanie dobre jeśli już masz martwicę mózgu i jest Ci wszystko jedno.

0

I to jest bardzo dobry przykład, dlaczego enkapsulacja jest ważna ;)

1
jarekr000000 napisał(a):

@KamilAdam:
W teorii mógłbyś zostawić to zwrócone ID w obiekcie jako pole.
A potem pobrać geterem :-)

Czekaj czy ty właśnie zaproponowałeś stanowe repository/dao (zaraz przyjdzie sam wiesz kto i zacznie krzyczeć ze Repository != DAO, więc teraz będę używał słowa DAO) i kod powinien wyglądać np tak:

val dogStatefulDAO = dogStatelesDAO.createstatefulDAO
dogStatefulDAO.save(dog) //zwraca void (którego i tak nie ma w Scali, ale zasymulujemy)
val dogId = dogStatefulDAO.getIdForSaveDog

Pięknę, po prostu piękne

Dobrze ze w pracy nie pisze w CQS i mogę napisać:

val dogId = dogStatelesDAO.save(dog)
1
KamilAdam napisał(a):

Jak już o kibelku mowa to wczoraj łażąć po Łazienkach Królewskich wpadłem na kolejną metodę/funkcję, której nie da się sensownie ogarnąć w CQSie czyli zapis do bazy z jednoczesnym zwróceniem klucza:

Ten przykład jest akurat średni, bo dałoby się przerobić API w taki sposób, że id jest podawane przez klienta.
Z drugiej strony możemy mieć coś takiego

DELETE FROM table WHERE 1>x RETURNING id;

albo

INSERT INTO table (id, data) 
VALUES (:id, :data)
ON CONFLICT (id) DO NOTHING
RETURNING (id);

W obu przypadkach dzięki pomieszaniu zapisu i odczytu zyskujemy atomową operację, przez co nie jest konieczne używanie żadnych transakcji.

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