Zmienne instancji vs argumenty metody – kiedy używać których?

Podejrzewam, że odpowiedź na moje pytanie leży w jakimś POODR, ale nigdy nie miałem okazji go przeczytać.

Przypuśćmy, że mamy coś, co ludzie lubią nazywać use casem (czyli tak naprawdę normalną klasę wykonującą jakieś zadanie). Możemy przekazać do owej klasy potrzebne rzeczy na dwa sposoby:

1. Przekazać wszystko podczas inicjalizacji.

GenerateReport = Struct.new(:orders, :invoices, :formatter) do
  def call
    # do something
  end
end

2. Przekazać część rzeczy podczas inicjalizacji, część przy wywoływaniu call.

GenerateReport = Struct.new(:formatter) do
  def call(orders, invoices)
    # do something
  end
end

Wszystkie use case’y w obecnym projekcie pisałem wg pierwszego sposobu, ale ostatnio coraz częściej zastanawiam się, czy nie poprawniej jest przekazywać przy inicjalizacji rzeczy, które należą do instancji i najpewniej się nie zmienią, a przy wywołaniu jakieś bardziej ruchome części.

W zasadzie nie wiem nawet, jak to dobrze uargumentować. Pierwszy sposób z pewnością słabo wypadłby przy operowaniu na danej instancji w wielu wątkach, ale powiedzmy sobie szczerze: czy gdybym musiał wykonać te akcje w wielu wątkach, to czy operowałbym na tym samym obiekcie? Najpewniej stworzyłbym nowe instancje (co mogłoby odbić się na pamięci, ale nie mam doświadczenia w pracy z takimi systemami).

Kiedy już zdecyduję się na drugi sposób, to w przypadku jakichkolwiek działań na zamówieniach lub fakturach, muszę nimi żonglować między metodami, ale czy nie o to w tym wszystkim chodzi? Dzięki temu przynajmniej unikam jakiegoś ukrytego stanu i wyraźnie widzę, które metody dotykają poszczególnych obiektów.

Interesujące opinie, linki do artykułów i tytuły książek bardzo mile widziane.

Ksiazka:Refactoring. Ruby Edition - sa opisane oba przypadki - argumenty wywalane do accessorow i accessory do argumentow.

Historyjka: Mialem raz taka sytuacje, ze w zadaniu rekrutacyjnym stworzylem mala fajna klase, jak to w rubym. Jako ze byla to czesc handlera http, rekruter wytknal mi, ze interpreter bedzie mial problem z duuuza iloscia instancji do uprzatniecia (firma owa ma naprawde duzy traffic) - potem klase zamienilem na prosta metode. Obiekty majace mala ilosc zmiennych instancji beda tez lzejsze. Ma to oczywiscie sens w przypadku duzego ruchu.

Mógłbyś pokazać na konkretnym przykładzie?

Przede wszystkim nie oczekiwałbym konkretnej odpowiedzi na to pytanie. Zahaczasz o kwestie tworzenia klas i ich odpowiedzialności, co jest tematem dosyć elastycznym. Gdy tworzysz nową klasę musisz się zastanowić jaka jest jej rola i odpowiedzialność i jakie dane (obiekty) dostaje na wejściu. Tak, one będą budować stan, ale dokładnie o to chodzi w tworzeniu obiektów (oczywiście pamiętając, że chcemy by ten stan był jak najmniejszy i by klasa mogła realizować swoje zadanie).

Twój przypadek jest na tyle prosty, że oba rozwiązania są w porządku. Pod postacią klasy jest tak na prawdę procedura generowania raportu na podstawie 3 parametrów i przeniesienie ich z konstruktora do metody (lub odwrotnie) niewiele zmienia.

Gdybyś jednak chciał w jakimś miejscu tworzyć obiekt generatora raportu i w zupełnie innym miejscu go generować to już Ty musisz odpowiedzieć sobie na pytanie: kto powinien odpowiadać za przekazywanie tych argumentów i gdzie. Zauważ także, że jeśli przekażesz je już na poziomie konstruktora to możesz przekazać ten obiekt dalej i on będzie zawierać w sobie już ten stan. To tez jest często istotne.

Kolejna sprawa to wydajność. W idealnym świecie nie przejmujemy się ilością obiektów i tworzymy sobie je do woli. Niestety w praktyce jeśli masz stworzyć N obiektów klasy A przekazując do konstruktora kolejne obiekty lub stworzyć 1 obiekt klasy A i przekazywać obiekty do konkretnej metody to ta ostatnia wersja będzie wydajniejsza.

Ja to widzę dokładnie na odwrót. Lepiej jest ustalić stan raz i potem żonglować nim na poziomie metod (+ mieszać z tym co jest ‘dynamiczne’). Jeśli przekazujesz parametry do metod publicznych i przekazujesz je do kolejnych metod (najczęściej prywatnych), które operują tylko na tych obiektach, to jest duża szansa, że ten parametr powinien być albo przekazywany do konstruktora, albo być może potrzebujesz dodatkowej klasy.

Zakładam oczywiście, że tworzysz małe klasy.

1 Like