Czy użyć klasy?

Mam pytanie czy w języku Ruby zawsze starać się używać klas?
Napisałem właśnie skrypt do obliczenia wyrażenia x/(x2-4) dla x >0;

#!/usr/bin/env ruby
require 'mathn'
puts "podaj liczbe"
x = gets.to_i
if x>0 then 
  y = x/(x**2 - 4)
  puts y
else
  puts "Podałeś złą liczbę"
end

I teraz moje pytanie czy dla tak prostego skryptu poprawniej byłoby użyć klasy?
Jeżeli tak to jak powinna wyglądać? Czy może mi ktoś wytłumaczyć jak poprawnie skonstruwać klasę i metody? Bo starałęm się napisać klase, która miałaby obliczać wyrażenie x/(x^2-4) jednak wychodziły mi same błędy :frowning: a bez klasy bez problemu napisałem i działa :slight_smile:

W tym jednym wypadku nie ma to sensu, ale jeśli chciałbyś generalny przypadek, to możesz napisać to w ten sposób:

Hint: użyłem gema https://github.com/rubysolo/dentaku

Oczywiście w wypadku jednego wyrażenia to mocne overengineering, ale da się :smile:

Ale gdyby skrypt był częścią większego systemu i chciałbyś dodatkowo napisać testy to bardziej abstrakcyjna wersja mogłaby wyglądać tak:

#calculator.rb

require 'mathn'

class Calculator
  def run
    @value = get_value
    
    if valid?
      render_response(calculate)
    else
      render_error
    end
  end
  
  private

  def calculate
    @value / (@value ** 2 - 4)
  end
  
  def valid?
    @value > 0
  end
  
  def get_value
    prompt
    
    gets.to_s.chomp.to_i
  end
  
  def prompt
    puts 'podaj liczbę'
  end
  
  def render_response(v)
    puts v
  end
  
  def render_error
    puts 'Podałeś złą liczbę'
  end
end

#main.rb

require_relative 'calculator'

c = Calculator.new

c.run

jeśli tworzysz zmienną instancji @value to nie musisz jej przekazywać jako parametr i lepiej by to wyglądało.

Fakt, poprawię zaraz.

Dziękuję za szybkie odpowiedzi. Mam tylko pytanie, co oznacza słowo private w skrypcie Arthwood’a i po co zostało użyte? Jakie jest jego zadanie skoro skrypt się wykonał mi gdy je pominąłem?
Nie wiem też dlaczego skrypt nie działa gdy jest require_relative ‘calculator’ gdy skasowałem to wyrażanie, skrypt się uruchomił. Wcześniej dostałe komunikat Cannot load such file … calculator.
Czy że powinienem pierwszą część skryptu z klasą zapisać gdzieś w pliku a w drugim gdy wpisze wyrażenie require_relative ‘calculator’ to odnosi się do klasy? Jeśli tak to gdzie powinienem zapisać plik z klasą Calculator?

  1. private oznacza że metody są ukryte i nie można użyć ich jako calculator.get_value. Zawsze staraj się ukrywać jak najwięcej :wink:
  2. plik main.rb i calculator.rb w tym przypadku powinny być “obok siebie”. Dzięki takiemu rozseparowaniu masz podział na definicję i użycie.

@edit (bo jest tu jakieś dziwne ograniczenie do 3 postów per wątek dla nowych użytkowników)

@wafcio Te dwie strony o których piszesz się nie wykluczają. Metody prywatne powinny być trywialne i należy ukrywać jak najwięcej.

@Arnvald Zawsze się znajdzie jakaś przeciwwaga ale tu nie chodzi o to że są złe jako takie, tylko domyślam się że często wpycha się do prywatnych metod rzeczy które powinny być w osobnych klasach.

@mark Przesadzasz :stuck_out_tongue: przykład miał pokazać jak z tego kodu zrobić klasę a nie jak napisać koszerny projekt :wink: Można przyjąć że ta klasa stanowi kalkulator jako całość razem z przyciskami i wyświetlaczem a nie jako procedurę obliczeniową.

Nie do końca się z tym zgodze, ukrywać powinno się z jednej strony jak najwięcej metod, ale z drugiej strony metody private powinny być na tyle trywialne żeby nie wymagały testów (jednostkowych), bo nie zaleca się pisania testów jednostkowych do private.

Zawsze staraj się ukrywać jak najwięcej

Dla przeciwwagi polecam wygooglać “private method code smell” i poczytać argumenty przeciwko używaniu prywatnych metod :smile:

@arthwood, @Arnvald każda moneta ma dwie strony, kiedyś wymyślono ActiveRecord i wszyscy byli happy że można tam wszystko umieszczać, a teraz idzie się w kierunku tego, żeby klasy posiadały jak najmniej logiki i rozbija się duże klasy na wiele mniejszych. Więc potrzebny jest punkt wyważenia.

Klasa Calculator ma inna zasadnicza wade - robi zbyt duzo. Czyli:

  • pobiera wartosc z konsoli
  • wylicza wynik

a powinna robic tylko to 2gie. Wtedy mozna jej uzyc do wykonania tego skomplikowanego obliczenia bez wzgledu na to skad wezma sie dane wejsciowe. Chyba ze przyjac ze jest to program unixowy ktory standardowo czyta dane ze standardowego wejscia. Ale wtedy takim ‘programem’ i tak powinien byc jakis calculator.rb ktory wczytuje dane i uruchamia instancje Calculator. Chyba :slight_smile:

Dzięki za wytłumaczenie. Teraz przynajmniej wiem co i jak.
Jutro spróbuje napisać własną klasę, która będzie tylko
obliczała wyrażenie, a reszta będzie poza klasą. I przedstawię wam do oceny.
Wiem, że po tych przykładach, które podaliście to już mam w zasadzie gotowca ale
przecież jedną z metod nauki jest czytanie kodu innych i modyfikowanie go :slight_smile:

Na podstawie podanych przykładów napisałem jeszcze raz skrypt.
A tak wygląda moja modyfikacja.

#!/usr/bin/env ruby
class Calculator
  def set_value(x)
    @value = x
  end
  def calculate
    @value / ( @value ** 2 - 4)
  end
end
puts 'Podaj liczbę'
x = gets.to_s.chomp.to_f
if x > 0 then
  c = Calculator.new
  c.set_value(x)
  print 'Dla podanej liczby wynik wynosi: '
  puts c.calculate
else
  puts 'Podałeś niepoprawną wartość.'
end

Czy tak napisany skrypt i wykorzystnie klasy jest poprawne? Wiem, że mój kod jest mało profesjonalny w porównaniu do waszych, ale chodzi mi czy zaliczylibyście tak rozwiązane zadanie?
I jeszcze pytanie czy dobrzez że użyłem typu zmiennopozycyjnego aby otrzymać także reszte przy dzieleniu a nie tylko samą liczbę całkjowitą z obcietą resztą?

Czy może użycie typu zmiennopozycyjnego powinno być użyte w ten sposób?

@value.to_f / ( @value.** to_f** ** 2 - 4)

x = gets.to_s.chomp.to_i

#!/usr/bin/env ruby
class Calculator
  def set_value(x)
    @value = x
  end
  def calculate
    @value.to_f / ( @value.to_f ** 2 - 4)
  end
end
puts 'Podaj liczbę'
x = gets.to_s.chomp.to_i
if x > 0 then
  c = Calculator.new
  c.set_value(x)
  print 'Dla x wynik wynosi: '
  puts c.calculate
else
  puts 'Podałeś niepoprawną wartość.'
end

Mam parę rad:

  1. wcięcia dla pierwszej metody w klasie sa inne niz wciezia dla drugiej metody

  2. nie get_value tylko set_value bo to ustawia wartosc w zmiennej instancji, ale ja byc usunac ta metode i w konstruktorze ja przekazał

    class Calculator
    def initialize(x)
    @value = x
    end

    def calculate
    @value / (@value ** 2 - 4)
    end
    end

    puts ‘Podaj liczbę’
    x = gets.to_s.chomp.to_f

    if x > 0 then
    c = Calculator.new(x)
    print 'Dla podanej liczby wynik wynosi: ’
    puts c.calculate
    else
    puts ‘Podałeś niepoprawną wartość.’
    end

@wafcio dzięki za szybką odpowiedź.

  1. wcięcia dla pierwszej metody w klasie sa inne niz wciezia dla drugiej metody

Racja to przez mój pośpiech na przyszłość będę bardziej dokładny.

  1. nie get_value tylko set_value bo to ustawia wartosc w zmiennej instancji

Na to nie mam usprawiedliwienia. Wstyd mi :frowning: Bo wiecie jak to jest z początkującymi, bardziej myślą żeby coś zaprogramować i żeby działało niż dbać o poprawność kodu. Ale na przyszłość będę się starał nie popełniać takich błędów.

Mam jeszcze pytanie. Bo jeśli zastosuję tutaj typ zmiennopozycyjny:
x = gets.to_s.chomp.to_f
a układ równań ma znajdować rozwiązanie dla x należącego do liczb rzeczywistych, to nie ma zabezpieczenia przed wpisanie liczby innej niż rzeczywista. W związku z tym czy można najpierw użyć

x = gets.to_s.chomp.to_i

a potem w metodzie calculate zrobić coś takiego @value.to_f / ( @value.to_f ** 2 - 4)

Czy taka praktyka byłaby poprawna? Czy jest na to inny bardziej poprawny sposób?