Ruby - wprowadzenie, część 5 - programujemy inkrementalnie
środa, 1 marzec 2006, w kategoriach: Programowanie, Ruby
Efektem ostatniego odcinka jest ten podsumowujący listing. Zaimplementowaliśmy pewne podstawowe funkcje, poznaliśmy pewne “idiomy” Ruby’ego, ale wciąż nie bardzo jest co uruchamiać. Zaczniemy więc od wzięcia na tapetę klasy “Interfejs”:
class Interfejs def initialize @lista = ListaZadan.new @lista.wczytaj end def start puts "Lista zadan do zrobienia: " puts "--------------------------" @lista.zadania.each_index do |idx| print "#{idx} " @lista.zadania[idx].wyswietl end puts "--------------------------" puts "Co chcesz zrobic? [ Wpisz odp. polecenie ]" puts "dodaj - Dodaj nowe zadanie" puts "usun - Usun istniejace zadanie" puts "koniec - Koniec pracy" end end
W tej części będziemy chcieli przede wszystkim stworzyć główną pętle programu: wyświetlenie zadań do zrobienia i listy poleceń, pobranie polecenia od użytkownika, wykonanie go i rozpoczęcie całej pętli od początku (chyba że użytkownik wydał polecenie zakończenia pracy z programem).
Chcąc, uniknąć zbyt długich metod, zaczniemy od małego refactoringu - zmiany kodu w celu poprawienia jego czytelności, bez zmiany jego działania. Z metody start wydzielmy sobie dwie metody:
def wyswietl_zadania puts "Lista zadan do zrobienia: " puts "--------------------------" @lista.zadania.each_index do |idx| print "#{idx} " @lista.zadania[idx].wyswietl end puts "--------------------------" end def wyswietl_polecenia puts "Co chcesz zrobic? [ Wpisz odp. polecenie ]" puts "dodaj - Dodaj nowe zadanie" puts "usun - Usun istniejace zadanie" puts "koniec - Koniec pracy" end
Napiszemy sobie też na razie pustą metodę przetwarzającą zadania:
def przetworz_polecenie(polecenie) end
I możemy w metodzie start umieścić już szkielet głównej pętli:
def start while true wyswietl_zadania wyswietl_polecenia przetworz_polecenie(gets.chop) end end
Musimy teraz mieć jakiś sposób, żeby sprawdzić, czy użytkownik wpisał poprawne polecenie, czy nie i ewentualnie wyświetlić odpowiednie pouczenie, po czym ponownie pobrać standardowe wejście. Jako że pętle goto wyszły z mody kawałek czasu temu, użyjemy w tym celu wyjątków.
Wyjątki, są to specjalne klasy, które zostają zainicjalizowane i “wyrzucone” w “ciele” pewnej metody kiedy zaistnieje jakiś błąd, tak żeby użytkownik wywołujący tą metodę mógł taki wyjątek “złapać” i zorientować się że coś poszło nie tak. Nasza pusta klasa będzie sygnalizować wpisanie przez użytkownika nieprawidłowego polecenia:
class NieznanePolecenie RuntimeError end
Dziedziczymy z RuntimeError, czyli standardowej klasy wyjątków. Teraz zmieniamy odrobinę główną pętle:
def start while true wyswietl_zadania wyswietl_polecenia begin przetworz_polecenie(gets.chop) rescue NieznanePolecenie puts "Wpisales zle polecenie, co chcesz zrobic?" retry end end end
To co jest pomiędzy “begin” a “rescue”, w C++ czy Javie znalazłoby się w “try” i jest to blok kodu który może rzucić wyjątek. Następnie mamy “rescue NazwaKlasy” (odpowiednik catch), czyli obsłużenie takiego, a takiego wyjątku, w tym przypadku NieznanePolecenie. Specjalna instrukcja “retry” powtarza cały blok zawarty pomiędzy “begin” a “rescue”. Pozostaje nam tylko sprawić, żeby metoda przetworz_polecenie rzucała odpowiedni wyjątek, ale tym zajmiemy się chwile później.
Wracając do poleceń - rozbudowa programu będzie polegać głównie na dodawaniu właśnie nowych polecen, dlatego chcielibyśmy aby dało się to zrobić jak najłatwiej i jak najbardziej elegancko. Wykorzystamy w tym celu dwie rzeczy - wzorzec projektowy “Command” i dynamiczne właściwości Ruby’ego.
Wzorzec projektowy to pewne rozwiązanie umożliwiające łatwe rozwiązanie jakiejś szerokiej gamy problemów czy to programistycznych, czy architektonicznych , czy jeszcze innych. Więcej o wzorcach można poczytać np. w Wikipedii.
Wzorzec Command, o którym mowa, każe nam zbudować obiekt dla każdego polecenia w systemie. Dodatkowo zrobimy sobie hash, który przyporządkuje polecenia użytkownika, odpowiadającym klasom poleceń. Rozwiązanie które otrzymamy będzie dużo bardziej eleganckie niż to co mamy do tej pory. Mamy więc pare nowych klas:
class Polecenie attr_accessor :opis def to_s return @opis end end class PolecenieDodaj Polecenie attr_accessor :lista_zadan def initialize @opis = "Dodaj nowe zadanie" end def wykonaj @lista_zadan.dodaj(Zadanie.zbuduj) end end class PolecenieUsun Polecenie attr_accessor :lista_zadan def initialize @opis = "Usun istniejace zadanie" end def wykonaj puts "Podaj numer zadania: " @lista_zadan.usun(gets.to_i) end end class PolecenieKoniec Polecenie attr_accessor :lista_zadan def initialize @opis = "Koniec pracy" end def wykonaj @lista_zadan.zapisz puts "Dziekujemy za prace z programem!" exit end end
Potrzebujemy teraz listy wszystkich polecen w systemie, więc w klasie Interfejs wprowadzamy dodatkowy hasz - gdzie kluczem jest nazwa polecenie wydanego przez użytkownika, a wartością obiekt reprezentujący to polecenie.
def initialize @lista = ListaZadan.new @lista.wczytaj @polecenia = {} @polecenia["dodaj"] = PolecenieDodaj.new @polecenia["dodaj"].lista_zadan = @lista @polecenia["usun"] = PolecenieUsun.new @polecenia["usun"].lista_zadan = @lista @polecenia["koniec"] = PolecenieKoniec.new @polecenia["koniec"].lista_zadan = @lista end
No i zmienia nam się metoda wyświetlająca możliwe polecenia:
def wyswietl_polecenia puts "Co chcesz zrobic? [ Wpisz odp. polecenie ]" @polecenia.each_key do |klucz| puts "#{klucz} - #{@polecenia[klucz].opis}" end end
Zostało nam jeszcze tylko napisanie metody przetwarzającej podane polecenie i otrzymamy pierwszą jako tako działająco wersję programu:
def przetworz_polecenie(polecenie) do_wykonania = @polecenia[polecenie] if(do_wykonania != nil) do_wykonania.wykonaj else raise NieznanePolecenie end end
Linia “do_wykonania = @polecenia[polecenie]” wyszukuje w naszym haszu polecenie wydane przez użytkownika i jeśli zostanie ono znalezione, pobierany jest odpowiadający mu obiekt, poczym zostanie ono wykonane. Jeśli zaś takiego polecenia nie ma, metoda rzuca wyjątek NieznanePolecenie.
W tej chwili nasz kod powinien przedstawiać się tak, jak w listingu 2. Można to już uruchomić, pobawić się i dostrzec pewne dość oczywiste niedociągnięca i pomyłki w programie - spowodowane głównie tym, że nie da się czytelnie w tutorialu wprowadzać na raz zbyt wielu nowych koncepcji. Dlatego w następnym odcinku zajmiemy się m. in. naprawieniem tych usterek i poprawieniem ogólnego kształtu programu. Do zobaczenia :)
Dodaj do del.icio.us | Dodaj do wykop.pl
Komentarze ():
n-kamil, 6 lipiec 2008, 9:43 pm
Zamiast a+ powinno być odpowiednio r i w
Marek, 30 styczeń 2009, 12:43 am
Nie działają linki z lisingami :(