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

Strategia w programowaniu obiektowym

0
somekind napisał(a):

Istotą strategii jest to, że klient może wybrać strategię.

I gdzie tu jest to moje poplątanie? Obiekt, który wybiera i przekazuje konkretną implementację algorytmu do użycia, to jest właśnie klient.

No i kłócisz się, że to nie jest HOF. To jest HOF (modulo interfejs(klasa abstrakcyjna)-funkcja). Przy czym
Strategia jest HOF,
Nie każde HOF jest strategią.

0
jarekr000000 napisał(a):

No i kłócisz się, że to nie jest HOF. To jest HOF (modulo interfejs(klasa abstrakcyjna)-funkcja). Przy czym
Strategia jest HOF,
Nie każde HOF jest strategią.

Co najwyżej strategia być może zawiera w sobie HOF.

Bo tak poza tym, to ani jej celem ani ideą nie jest zawieranie w sobie HOF (ani nawet interejsu), więc nie bardzo jest sens tak bardzo się na tym skupiać. :)

0

@Riddle:

Nie ma żadnego powodu zeby uważać że obiekty new BubbleSort() i new QuickSort() faktycznie są w paradygmacie obiektowym. Równie dobrze mogłyby być funkcjami.

Jak nie ma?

Jest - taka interpretacja jest spójna z definicją Object-oriented programming (OOP) is a programming paradigm based on the concept of "objects", which can contain data and code: data in the form of fields (often known as attributes or properties), and code, in the form of procedures (often known as methods).


W ogóle jak już w temacie enkapsulacji, to dlaczego ty tak zaglądasz do tych bebechów?

Przecież jeżeli uznasz że takie klasy bez danych faktycznie nie mają sensu i zaczniesz zmieniać np.

class SortA : ISortStrategy
{
	public sort(Collection c);
}

na funkcje (Równie dobrze mogłyby być funkcjami.)

to nagle tracisz elastyczność, bo za pół roku będzie Ci ciężej rozszerzyć/zmienić zachowanie na takie, aby w dodatku coś przechowywało pod spodem.

class SortA : ISortStrategy
{
    private _cache[Collection key, Collection value];
    private EnvironmentDetails _env; // cpu and environment detected on very first run
	public sort(Collection c);
    private internalEnvironmentAwareSort(Collection c);
}

Musiałbyś zmienić sygnaturę z funkcji na coś innego

lub potworka tego typu:

public class User 
{
    public bool Checked = false;
    public void DoStuff()
    {
        Checked = true;
    }
}

public static void Test(Action a)
{
    a();
}

public static void Main()
{
    var user = new User();
    Test(user.DoStuff);
    Console.WriteLine(user.Checked);
}

Czyli różne języki mają różne paradgymaty obiektowe?

Tak, różne języki mogą interpretować, a zatem implementować OOP inaczej.

Moim zdaniem przykładem tego jest chociażby friend z c++.

1

Ja tu widzę podstawowy problem. To, że istnieją bezstanowe strategie (typu BubbleSort, QuickSort), które przypominają lambdy nie oznacza jeszcze, że łamią one obiektowość. W takiej Javie rozwiązane jest to tak, że stworzona w ten sposób strategia jest obiektem klasy anonimowej implementująca jakiś tam interfejs. W tym przypadku paradygmat obiektowy byłby złamany gdyby z tak stworzoną strategią nie można było zrobić tego samego, co z dowolnym innym obiektem - tj. skopiować referencji, sprawdzić jakiej jest klasy itp. Na tym polega właśnie paradygmat obiektowy.

I teraz można się zastanawiać, czy np. w językach funkcyjno-obiektowych (tj. takich, który pozwala jednocześnie na tworzenie klas obiektów, ale także można przypisywać funkcje do zmiennych) zastosowanie wzorca strategii nie złamie paradygmatu obiektowego - ale to nie jest problem z samym wzorcem strategii, tylko od implementacji i języka.

0

@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:
screenshot-20220623083535.png
Źródło: https://refactoring.guru/design-patterns/strategy

Pytanie, czy jeżeli klasę Navigator zastąpimy HOF, RouteStrategy zniknie, a konkretne strategie staną się innymi funkcjami przekazywanymi do HOF jako jej parametry, to czy takie rozwiązanie, nadal pozostanie strategią? Czy statek Tezeusza staje się nowym statkiem po wymianie pierwszej części, wszystkich części, czy nigdy?

0
piotrpo napisał(a):

Źródło: https://refactoring.guru/design-patterns/strategy>
Pytanie, czy jeżeli klasę Navigator zastąpimy HOF, RouteStrategy zniknie, a konkretne strategie staną się innymi funkcjami przekazywanymi do HOF jako jej parametry, to czy takie rozwiązanie, nadal pozostanie strategią? Czy statek Tezeusza staje się nowym statkiem po wymianie pierwszej części, wszystkich części, czy nigdy?

Wreszcie jakiś konkret. Tam na dole jest też pseudo kod. W Scali z HOF ten pseudokod będzie wyglądać tak:

object Strategy extends App {
  type Strategy = (Int, Int) => Int //alias typu dpa picu

  // strategie można by też zdefiniowac jako lambdy, ale wolałem jako *statyczne* metody
  def concreteStrategyAdd(a: Int, b: Int): Int = a + b
  def concreteStrategySubtract(a: Int, b: Int): Int = a - b
  def concreteStrategyMultiply(a: Int, b: Int): Int = a * b

  // brzydki konteks przyjmujący nulla w konstruktorze
  class Context(var strategy: Strategy = null) {
    def executeStrategy(a: Int, b: Int): Int = strategy(a, b)
  }
  
  val context = new Context()
  val first = scala.io.StdIn.readInt
  val last = scala.io.StdIn.readInt
  val action = scala.io.StdIn.readChar

  val strategy = action match {
    case '+' => concreteStrategyAdd _
    case '-' => concreteStrategySubtract _
    case '*' => concreteStrategyMultiply _
  }

  // w produkcyjnym kodzie bym oczywiście czegoś takiego nie zrobił
  context.strategy = strategy
  //wywołanie strategii
  val result = context.executeStrategy(first, last)
  println(result)
}

WIem że to bardzo brzydki kod jak na Scalę (null parameter, nie pełny match), ale nie chciałem zmieniać za dużo

BTW interfejs Stretegii z przykładu Real World Analogy wygląda jak upośledzona funkcja :P

Real World Analodzy

UPDATE ponieważ nie mogłem się powstrzymać to jeszcze rozwiązanie z niemutowalnym kontekstem który też jest funkcją :P

object Strategy extends App {
  type Strategy = (Int, Int) => Int

  // strategie jako lambdy
  val concreteStrategyAdd: Strategy = (a, b) => a + b
  val concreteStrategySubtract: Strategy = (a, b) => a - b
  val concreteStrategyMultiply: Strategy = (a, b) => a * b

  // ładny kontekst, tylko że nic nie robi
  // mogłby też być metodą *statyczną*
  // def Context(strategy: Strategy)(a: Int, b: Int): Int = strategy(a, b)
  class Context(val strategy: Strategy) {
    def apply(a: Int, b: Int): Int = strategy(a, b)
  }

  val first = scala.io.StdIn.readInt
  val last = scala.io.StdIn.readInt
  val action = scala.io.StdIn.readChar

  val strategy = action match {
    case '+' => concreteStrategyAdd
    case '-' => concreteStrategySubtract
    case '*' => concreteStrategyMultiply
  }

  // jeśli kontekst byłby metodą statyczną to
  // val context = Context(strategy) _
  val context = new Context(strategy)
  val result = context(first, last)
  println(result)
}
0
somekind napisał(a):
Riddle napisał(a):

"Czy strategia pasuje do paradygmatu obiektowego?". Pytam, dlatego że uważam, że w paradygmacie obiektowym, obiekty powinny dostać coś przez parametr konstruktora. A uważam tak, dlatego że jeśli nie dostają czegoś, to można by zrobić z nich funkcje, po prostu, więc nie byłoby róznicy czy przekażę implementację interfejsu czy callbacka (a nie uważam żeby callbacki się wpisywały w paradygmat obiektowy). A po drugie dlatego, że wtedy nie byłoby znaczenia czy użyję tych samych instancji jakiejś implementacji czy różnych. Nie wydaje mi się że te dwie ostatnie cechy pasują do paradygmatu, a jednocześnie widzę że strategie je mają stąd pytanie - "Czy strategia pasuje do paradygmatu obiektowego?"

No dobra, ale czy jeśli mam aplikację, w której mam tysiąc klas, które dostały coś przez parametr konstruktora, i jedną, która nie, to to już nie będzie kod obiektowy? Czy może jeśli język pozwala na tworzenie bezparametrowych konstruktorów, nie jest językiem obiektowym?

Ja bym wtedy powiedział, że 99% aplikacji jest napisanej obiektowo, i ma jedną proceduralną klasę.

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.

Jeśliby poprawić tą klasę na obiektową, to wtedy powiedziałbym że aplikacja cała jest w pełni obiektowa. Podobnie, gdybym odpalił mutaion testing engine, i on by zapił 100% mutatnów, to powiedziałbym żę aplikacja jest w pełni przetestowana.

Co do języka: Już bardzo dawno przesatłem używać takich określeń jak "obiektowy język", "funkcyjny język", nie mają takie określenia sensu dla mnie. Można pisać obiektowo w nieobiektowym języku. Takie określenia dla mnie straciły już sens. Metafora, nawet jak znajdę wegetariańskie danie, to łatwo zrobić je niewegetariańskim, tak samo łatwo jak napisać nieobiektowy kod w rzekomo obiektowym języku.

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.

Czyli aplikacja posiadająca obiekty, ale która cześć logiki ma np w funkcjach statycznych jest obiektowa?

No to w takim razie żadna aplikacja nie jest obiektowa.

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.

Tylko własnie nie wiem co tutaj jest kluczem.

@somekind: Chodzi o to że strategia nie jest widoczna poza obiektem?

Obiekty są 3:

  1. Jeden główny, który chce coś zrobić, i wie jakim algorytmem chce to zrobić. On tworzy/wybiera obiekt konkretnej implementacji algorytmu, i przekazuje go dalej.
  2. Drugi, który trzyma referencję do konkretnej implementacji algorytmu, dostaje ją np. w konstruktorze i używa w swoim flow.
  3. No i wreszcie obiektami są konkretne implementacje algorytmu.
    Między nimi gdzieś tam poniewiera się interfejs, aby to wszystko połączyć, bo taką mamy składnię.

Tak więc, strategia nie jest widoczna poza obiektem 1, a używa jej obiekt 2. W Twoim kodzie brakuje któregoś z nich.

Czyli możnaby ją zrefaktorować do innego rozwiązania, np ifów? Albo innego dowolnego? I klienci tej klasy 1. by nie wiedzieli? Bo rozumiem że klasy 2. nikt nie używa bezpośrednio, a jedynie za pośrednictwej klasy 1.?

2

Zauważam podobne reakcje, jak tłumaczę komuś coś związanego z paradygmatem obiektowym, do innych dyscyplin.

Dam przykład może.

  • MVC - Powiedz komuś że w MVC chodzi o to, żeby usunąć wszystkie powiązania widoku z modelem. Co odpowie?

    • Ale to nie ma sensu! Jak w taki sposób coś wyświetlić? Byłby biały ekran! To nie ma sensu
  • Functional programming - Powiedz komuś, żę w FP nie można redefiniować raz ustawionych zmiennych. Co odpowie?

    • Ale to nie ma sensu! Jak wtdy zrobić fora? Wszystkie zmienne byłyby takie same, program byłby statyczny! To nie ma sensu!
  • TDD - Powiedz komuś, że nie może napisać żadnego kodu przed testem, co odpowiadają?

    • Ale musze napisać program, żeby móc go przetestować. Co mam niby przetestować? To nie ma sensu!
  • Na dokładkę może funkcje abstrakcyjne - Uczymy kogoś polimorfizmu (tego na klasach i interfejsach)

    • Ale po co mam deklarować funkcję bez ciała, która nic nie robi? To nie ma sensu.

Na pewno doświadczyliście takich odpowiedzi, kiedy przekazywanie komuś jakiejś dyscypliny, ale w taki wydestylowany sposób, jak np mówienie o TDD przez pryzmat trzech praw, albo o paradygmatach przez pryzmat dogmatów, to ktoś może dojść do wniosku że każda z tych dyscyplin nie ma sensu, jak się tak po prostu o niej słyszy.

Co nam to mówi?

Może to, że jeśli spotykamy się z jakąś dyscypliną, to może nie brać jej przez intuicję i próbować "się ją naumieć bez wysiłku", tylko może jednak przez pryzmat doświadczenia i wiedzy, i wiedzieć że żęby poznać nową dyscyplinę to to wymaga żeby włożyć w to jakiś minimum wysiłku żeby to zrozumieć, czasem całkiem sporo.

3

Bo uważam że w obiektowym świecie obiekty powinny przyjmować wartości przez konstruktor (coś co nazwałem enkapsulacją, co potem zrodziło gównoburzę)

Gdyby tak było, to każdy jeden język wymagałby tworzenia klasy z obowiązkowym konstruktorem z parametrami, ale tak nie jest. To wszystko jest opcjonalne i nadal są to obiekty, czyli dziedziczenie, wstrzykiwanie itd. stan obiektu można zmieniać/ustawiać nie tylko za pomocą konstruktora.
Uroiłeś sobie jakąś wizję programowania obiektowego i się jej kurczowo trzymasz, mimo, że jest nieprawdziwa.

0
omenomn2 napisał(a):

Bo uważam że w obiektowym świecie obiekty powinny przyjmować wartości przez konstruktor (coś co nazwałem enkapsulacją, co potem zrodziło gównoburzę)

Gdyby tak było, to każdy jeden język wymagałby tworzenia klasy z obowiązkowym konstruktorem z parametrami, ale tak nie jest. To wszystko jest opcjonalne i nadal są to obiekty, czyli dziedziczenie, wstrzykiwanie itd. stan obiektu można zmieniać/ustawiać nie tylko za pomocą konstruktora.
Uroiłeś sobie jakąś wizję programowania obiektowego i się jej kurczowo trzymasz, mimo, że jest nieprawdziwa.

A Ty znowu swoje.

Programowanie obiektowe to nie jest "po prostu używanie klas", wbij sobie to do głowy. To jest paradygmat. Możesz używać klas, obiektów i dziedziczenia na dziesiątą stronę, a i tak stworzyć kod który nie jest obiektowy.

Samo używanie funkcji, nie sprawia że piszesz funkcyjnie - samo używanie obiektów i klas nie sprawia że piszesz obiektowo.

Dziękuję za wzięcie udziału w rozmowie.

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