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

Strategia w programowaniu obiektowym

0
piotrpo napisał(a):

tak, to jest zmiana strategii, w zależności od tego zawartość pliku będzie inna, załóżmy, że ten "encoder" coś tam jeszcze robi

No ale możnaby to osiągnąć bez zmiany stanu, po prostu przekazując parametr.

Np tak:

class PictureWriter(val pictureEncoder:PictureEncoder){
  fun savePicture(File pictureFile){
    val imageFormat = when(pirtureFile.extension){
      when "png" -> PngEncoder()
      when "jpg" -> JpegEncoder(quality=80)
    } 
    writeFile(pictureFile, pictureEncoder.encode(imageFormat, pictureFile.bytes))
  }
  private fun writeFile(....)
}

Stąd było moje pytanie, czy ta zmiana stanu była tam konieczna? Bo widzisz, ja przerobiłem kod i zmiany stanu nie ma. Więc czy ten kod który tutaj pokazałem, nadal pasuje do Twojego pytania? czy pozbawiłem go tym jego istoty?

0

@Riddle: Nie pasuje, bo wtedy, to już nie będzie wzorcowa strategia, a tego (niby) dotyczy temat. W znaczeniu, tak wiem, ze da się tak zrobić, ale trzymajmy się blisko wzorca.

0
piotrpo napisał(a):

@Riddle: Nie pasuje, bo wtedy, to już nie będzie wzorcowa strategia, a tego (niby) dotyczy temat.

Riddle napisał(a):

Czyli strategia jest tylko wtedy kiedy zmieniasz stan?

Właśnie też się kurde zastanawiam. W orginalnych papierach jest o ustawianiu strategi przez setter. Czy to musi byc setter czy może być przez konstruktor?
@somekind: co ty o tym myślisz?

0
piotrpo napisał(a):

@jarekr000000, @somekind
Nie jestem pewien, czy Strategia może być HOF. To już czysta sofistyka, ale...
Wzorzec projektowy strategii, to sposób na rozwiązanie określonej klasy problemów, czyli zmiany działania fragmentu programu, w trakcie jego wykonywania, sterowana przez coś z zewnątrz. Oczywiście taki problem występuje niezależnie od języka.
Jednak wzorzec, to nie tylko definicja problemu, ale również zalecane rozwiązanie, które wygląda tak:

Oczywiście, że tak, przecież piszę o tym od początku.

KamilAdam napisał(a):

Wreszcie jakiś konkret.

Padłem xD

Konkretnie wyjaśniam o tym, czym jest strategia w chyba już czwartym ostatnio powstałym wątku na ten temat. Niestety niektórzy ciągle nie są w stanie zajarzyć, ze podróż nad morze to nie jest skrzynia biegów - nawet jeśli gdzieś tam w trakcie tej podróży ta skrzynia biegów jest używana.

Riddle napisał(a):

Podobnie jak masz 99% coverage'a, to nikt nie powie że kod niej est przetestowany, ale nie można też powiedzieć żę jest w pełni przetestowany.

To taki offtop, ale jak widzę 99% coverage, to znaczy, że ktoś skupiał się na procentach, a nie testach, więc zakładam, że w ogóle nic nie jest przetestowane.

Ja teraz nie posługuję się w ogóle określeniami "obiektowy język", i mówię tylko "obiektowy kod" albo "nie obiektowy kod". Nie ma języka który by Cię zmusił żebyś uniezależnił dane od implementacji, zawsze jak się uprzesz, to złamiesz gdzieś paradygmat.

Masz trochę racji, ale podobnie nie ma noża, który by Cię zmusił do krojenia sera. I nożem do sera możesz pokroić mięso. Czy przestanie on być nożem do sera?
Można pisać nieobiektowy kod w obiektowym języku, ale język nadal będzie obiektowy. Bo język obiektowy oznacza, że można w nim programować obiektowo.

A to, że gdzieś tam teoretycznie możesz mieć algorytm, który nie przyjmuje żadnych parametrów (aczkolwiek trudno im sobie to wyobrazić w nieakademickim przykładzie) nie zmienia faktu, że strategia to wzorzec programowania obiektowego.

Niektóre są. Mogę sobie łatwo wyobrazić aplikacje która nie ma absolutnie żądnej logiki w funkcjach statycznych, i spełniająca pozostałe założenia.

Bardzo wątpię. Gdzieś tam na pewno będzie wywołanie jakiegoś statycznego kodu, chociażby z bibliotek frameworka albo systemowych.

1
somekind napisał(a):

Bardzo wątpię. Gdzieś tam na pewno będzie wywołanie jakiegoś statycznego kodu, chociażby z bibliotek frameworka albo systemowych.

To akurat tylko obecnie typowy design w językach javopodobnych, ale nie jest to ograniczenie języka.
Można sobie wyobrazić, że funkcja main() przyjmuje argument typu System, który jest fasadą na kolejne moduły (getIO(), .getThreading() ... etc) i łatwo można się obejśc bez staticów.

Z drugiej strony powstaje pytanie - czym jest konstruktor, jeśli nie pewną formą statica... i koniec.

1

@Riddle:

No moim zdaniem to co pisze @piotrpo to brednie. Czemu niby ustawienie stanu miałoby być kluczowym elementem strategii, albo czemu w niemutowalnych klasach strategia miałby nie mieć racji bytu? Chodzi przecież o dostarczenie implementacji algorytmu.

Jeżeli mówimy o wzorcu, składającym się z problemu i implementacji, to strategia nie jest wyłącznie "wstrzyknięciem algorytmu". Gdyby tak było, to czym różni się od np. obserwatora? A jeżeli olejemy kompletnie wzorzec, to przecież da się taki problem rozwiązać w dowolnym języku Turing complete, chociażby za pomocą drabinki warunków i skoków.

Dla przypomnienia jak wygląda wzorzec:
screenshot-20220623130949.png

0
jarekr000000 napisał(a):

Po prostu zredaguj tą definicję, bo coś miałeś na myśli, ale logicznie wyszło bez sensu. (BTW. robienie takich definicji jest trudne).

No dobra. To spróbjmy kryteria

Moim zdaniem. Kod obiektowy to kod w którym spełniony jest każdy z warunków:

  • Nie występuje deklaracja żadnych funkcji statycznych (ale można ich użyć jako szczegół implementacyjny jakiegoś obiektu, w taki sposób żeby klienci obiektu o nim nie wiedzieli)
  • Dziedziczenie jest używane tylko jako szczegół implementacyjny, nie jako współdzielenie interfejsu
  • Nie występują obiekty, które można bez strat w logice sprowadzić do postaci funkcji (np obiektów które nie przyjmują nic przez parametr konstruktora).
  • Operacje na danych nie powinny być przeprowadzone na więcej niż jednym poziomie abstrakcji (np nie powinno się używać tego samego obiektu do okreslenia extensionu jak i mime'type'u).
    • Dla przykładu, jeśli dostajemy jedną daną (np Pesel) i z niego chcemy odczytać w programie datę urodzenia, wiek oraz płeć, to musimy stworzyć trzy obiekty które dostają ten pesel, jeden pozwala nam odczytać datę urodzenia, jeden wiek, a jeden płeć, i są przekazywane tam, gdzie każde z tych miejsc wymaga tych danych. Jeśli inny obiekt wymaga ich trzech, to powinny zostać skomponowane w czwarty obiekt, z którego można odczytać każdą z tych danych, ale który nie wie nic o peselu.
  • null nie jest używany jako wartość sama w sobie (tylko jest przekazywany jako parametry obiektu, który prezentuje zachowanie sugerujące "brak wartości")
  • Wartości nie są przekazywane bez obiektów (np publiczne stałe).
  • Metoda obiektu zwracająca wartość nie ma side-effectów (zmiany stanu obiektu lub stanu systemu).
  • Metoda zmieniająca stan obiektu lub systemu ma return-type void.
  • Nie zachodzi metaprograming (refleksje, kontenery zależnosci, switch na .class/::class/get_class(), Mickito)
  • Nie występuje rzutowanie typów
  • Nie występuje instanceof (chyba że jako szczegół implementacyjny pojedynczego obiektu, użyte na wartości zwróconej przez biblioteki, bez możliwości ominięcia go. Wtedy taką klasę na której się robi instanceof traktuje się jako gołą daną, i nie jest obiektem w rozumieniu OOP)
  • Klasy są albo final/sealed/nie open, albo abstract/interface.

Możliwe że dopiszę coś więcej, jak o tym pomyślę.

1
Riddle napisał(a):
  • Nie występują obiekty, które można bez strat w logice sprowadzić do postaci funkcji (np obiektów które nie przyjmują nic przez parametr konstruktora).

Ten warunek nie będzie spełniony dla dowolnego obiektu, bo wszystko możesz zamienić na funkcję (parametryKonstruktora, parametry metody) -> (kontekst, rezultat)

5

@Riddle: twoje kryteria są bardzo dziwne. Bardzo mocno mylisz "łamie OOP" z "łamie praktyki, które ja uznaję za dobre praktyki OOP".

Z twoich kryteriów wynika, że nie można zaimplementować stosu w OOP, bo operacja pop() zmienia stan obiektu i zwraca wartość, a według ciebie:

Metoda obiektu zwracająca wartość nie ma side-effectów (zmiany stanu obiektu lub stanu systemu).

0
piotrpo napisał(a):
Riddle napisał(a):
  • Nie występują obiekty, które można bez strat w logice sprowadzić do postaci funkcji (np obiektów które nie przyjmują nic przez parametr konstruktora).

Ten warunek nie będzie spełniony dla dowolnego obiektu, bo wszystko możesz zamienić na funkcję (parametryKonstruktora, parametry metody) -> (kontekst, rezultat)

Nie, np tego nie można:

class FullName {
  FullName(string fullname) {
    this.fullname = fullname;
  }

  firstName() {
     return fullname.split(" ")[0];
  }
  
  surname() {
     return fullname.split(" ")[1];
  }
}

I jesli użyjesz go gdzieś tak:

void jakaśFunkcja(FullName name) {
  WriteLine("Moje imie to " + name.firstName() + ", a nazwisko to " + name.surname());
}

to nie możesz.

wartek01 napisał(a):

@Riddle: twoje kryteria są bardzo dziwne. Bardzo mocno mylisz "łamie OOP" z "łamie praktyki, które ja uznaję za dobre praktyki OOP".

Z twoich kryteriów wynika, że nie można zaimplementować stosu w OOP, bo operacja pop() zmienia stan obiektu i zwraca wartość, a według ciebie:

Metoda obiektu zwracająca wartość nie ma side-effectów (zmiany stanu obiektu lub stanu systemu).

Dokładnie. pop() w stosie jest nieobiektowy. Łamie też CQS.

Żeby był obiektowy powinno się zrobić last = stack.last(); stack.removeLast(); (gdzie removeLast()) zwraca void. Jeśli pop() coś usuwa i zwraca, to nie jest napisany w paradygmacie obiektowym, po prostu.

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