D/Moduły
Podstawy działania
edytujModuły są osobnymi plikami o rozszerzeniu .d, domyślnie wraz z kompilatorem D otrzymujemy duży zestaw modułów podzielonych na kategorie etc, std oraz core.
Importujemy je znanym już poleceniem import:
import nazwa_modulu;
D pozwala na tworzenie tylko jednego modułu na plik, oraz wymusza zmieszczenie modułu w jednym pliku, można więc (zgodnie z pierwszym akapitem) przyjąć że 1 moduł = 1 plik. Deklaracja modułu wygląda następująco:
module nazwa_modulu;
/* Kod modułu - klasy, metody itp. */
Przykładowym wykorzystaniem modułów może być oddzielenie implementacji stosu (niedostępnego domyślnie w D na dzień dzisiejszy, tj. 24 czerwiec 2016) od właściwego programu.
//stack.d
module stack;
class Stack(T)
{
private:
T[] tab;
public:
void push(T val)
{
tab ~= val;
}
void pop()
{
assert(!empty());
tab.length--;
}
T top()
{
assert(!empty());
return tab[tab.length - 1];
}
bool empty()
{
return tab.length == 0;
}
}
//main.d
module main;
import std.stdio;
import stack;
int main(string[] args)
{
auto s1 = new Stack!ulong;
long sum = 0;
for(int i = 0; i < 12; i++)
s1.push(i * i);
/* Inne operacje na stosie */
while(!s1.empty())
{
sum += s1.top();
s1.pop();
}
writeln("Suma elementów ze stosu: ", sum);
return 0;
}
Rozwiązywanie konfliktów
edytujJęzyk D nie udostępnia rozbudowanych przestrzeni nazw jak C++, co za tym idzie po zaimportowaniu 2 modułów definiujących tą samą metodę (itp.) wywołanie nie będzie jednoznaczne i kompilator poinformuje o konflikcie. By ujednoznacznić obiekt do którego się odwołujemy należy poprzedzić go nazwą modułu i kropką.
modul.funkcja();
Prosty przykład:
//add.d
module add;
int calculate(int a, int b)
{
return a + b;
}
//sub.d
module sub;
int calculate(int a, int b)
{
return a - b;
}
//main.d
module main;
import std.stdio;
import add;
import sub;
int main(string[] args)
{
int a = 5, b = 3;
writefln("a + b = %d\na - b = %d", add.calculate(a, b), sub.calculate(a, b));
return 0;
}
Pakiety
edytujDobrą praktyką jest utrzymywanie porządku w modułach z wykorzystaniem pakietów. Są to zwyczajne katalogi, nazwa folderu pełni funkcję nazwy pakietu a by dostać się do modułu należy poprzedzić go nazwą pakietu i kropką. Składnia:
import pakiet.modul;
Przykład zastosowania:
// matematyka/add.d
module add;
int calculate(int a, int b)
{
return a + b;
}
// matematyka/sub.d
module sub;
int calculate(int a, int b)
{
return a - b;
}
//main.d
module main;
import std.stdio;
import matematyka.add;
import matematyka.sub;
int main(string[] args)
{
/* Wskaźniki na funkcje skracające zapis */
auto add = &matematyka.add.calculate;
auto sub = &matematyka.sub.calculate;
int a = 5, b = 3;
writefln("a + b = %d\na - b = %d", add(a, b), sub(a, b));
return 0;
}
Importowanie pojedynczych klas / funkcji
edytujJak zapewne zauważyłeś mimo sporych nakładów pracy może dojść do konfliktu nazw a wykorzystywanie konkretniejszych wersji (poprzedzonych nazwą modułu i ew. pakietu) lub ich aliasów nie zawsze jest wygodne. Można temu zapobiec korzystając z operatora : w kontekście modułu – dzięki niemu można zaimportować jedynie najpotrzebniejsze funkcjonalności.
import pakiet.modul : cos_potrzebnego;
Dobre praktyki
edytujKilka rzeczy (niewymienionych wcześniej) umilających innym programistom korzystanie z naszych modułów i pakietów:
- Tworzenie modułu o nazwie pakietu, importującego wszystkie moduły z danego pakietu.
- Komentarze, nazwy klas, funkcji, metod itd. po angielsku.
- Wszystkie nazwy zwięzłe i jednoznaczne, lepiej nie wzorować się na calculate z przykładów.
- Zawieranie w jednym module jednej funkcjonalności.