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 :(