Moduły

edytuj

Moduły w Rubim są podobne do klas, ale:

  • Moduł nie może mieć instancji.
  • Moduł nie może mieć podklas.
  • Moduł jest definiowany przez słowa kluczowe module i end.

Właściwie to... klasa Module modułu jest nadklasą klasy Class.[1]

Istnieją dwa typowe zastosowania modułów. Jedno to zebranie powiązanych metod i stałych w jednym centralnym miejscu. Moduł Math z standardowej biblioteki Rubiego odgrywa taką rolę:

irb(main):001:0> Math.sqrt(2)
=> 1.4142135623731
irb(main):002:0> Math::PI
=> 3.14159265358979
irb(main):003:0>

Operator :: mówi interpreterowi Rubiego, który moduł powinien on sprawdzić by pobrać wartość zmiennej (możliwe, że jakiś moduł oprócz Math może użyć PI do oznaczenia czegoś innego). Jeżeli chcemy odnieść się do metod lub stałych modułu bezpośrednio, bez używania ::, możemy "zawrzeć" ten moduł używając słowa kluczowego include:

irb(main):003:0> include Math
=> Object
irb(main):004:0> sqrt(2)
=> 1.4142135623731
irb(main):005:0> PI
=> 3.14159265358979
irb(main):006:0>

Domieszkowanie klas

edytuj

Inne użycie modułu nazywane jest domieszkowaniem klas (ang. mixin). Niektóre języki obiektowe, włączając C++ lub Eiffel, pozwalają na wielokrotne dziedziczenie, to znaczy, dziedziczenie po więcej niż jednej nadklasie. Przykładem wielokrotnego dziedziczenia z codziennej rzeczywistości jest budzik. Możesz zaliczyć budziki do klasy zegarków jak i do klasy przedmiotów z brzęczykami.

Ruby celowo nie implementuje prawdziwego wielokrotnego dziedziczenia, ale domieszkowanie klas jest dobrą alternatywą. Pamiętaj, że moduły nie mogą posiadać instancji ani podklas. Ale jeśli włączymy (include) moduł w definicję klasy, jego metody będą efektywnie dodane czy też "wmieszane" w klasę.

Domieszkowanie klas może być rozważane jako odpowiedź na pytanie o wszelkie partykularne własności, które chcemy mieć. Na przykład, jeżeli klasa ma działającą metodę each, zmieszanie jej ze standardowym modułem Enumerable da nam dodatkowo metody sort oraz find. Dzieje się tak, ponieważ metody z modułu Enumerable używają właśnie metody each.

Takie użycie modułów dostarcza podstawowej funkcjonalności wielokrotnego dziedziczenia, pozwalając jednocześnie, by relacje pomiędzy klasami były nadal reprezentowane za pomocą prostych struktur drzewiastych. W ten sposób upraszcza się znacząco implementacja języka (podobny punkt widzenia został przyjęty przez projektantów Javy). Domieszkowanie wydaje się poza tym dużo wygodniejsze niż wielokrotne dziedziczenie i dużo efektywniejsze niż np. stosowane w C# i Javie - interfejsy.

Przykładowy moduł

edytuj

Zrealizujmy nasz przykład z zegarkami.

module Brzeczyk
  def dzwon
    puts "BZZZZ!BZZZZ!BZZZZ!"
  end
end

class Czasomierz
  def podaj_czas
    puts Time.now
  end
end

class Budzik < Czasomierz
  include Brzeczyk
end

b = Budzik.new
b.podaj_czas #=> Sun Aug 05 17:24:08 +0200 2007
b.dzwon      #=> BZZZZ!BZZZZ!BZZZZ!

Jak widzimy, Budzik dziedziczy po klasie Czasomierz oraz zawiera metody (akurat tylko jedną) z modułu Brzeczyk. Dzieje się tak dzięki wspomnianej już metodzie include. A co, jeśli chcielibyśmy dodać brzęczyk do jednego tylko obiektu danej klasy? W tym celu możemy użyć metody extend(module) która dodaje metody modułu do konkretnego obiektu.

zegarek = Czasomierz.new
zegarek.extend(Brzeczyk)
zegarek.dzwon #=> BZZZZ!BZZZZ!BZZZZ!

Metoda extend może być również używana tak jak include, jednak jej działanie jest nieco inne. Otóż wszystkie metody niestatyczne z modułów przekazanych do extend zostaną włączone do klasy (lub modułu) jako metody klasowe (statyczne). O metodach klasowych powiemy jeszcze przy okazji omawiania zmiennych klasowych.

class Buczek
  extend Brzeczyk
end

Buczek.dzwon #=> BZZZZ!BZZZZ!BZZZZ!

Przypisy

  1. W Rubim obiektami są nawet klasy lub moduły i posiadają one swoje klasy (Module i Class).