Συμπεριφορές
Στο προηγούμενο μάθημα μάθαμε για τις προδιαγραφές, σε αυτό θα μάθουμε πως να απαιτούμε από μία ενότητα να υλοποιεί αυτές τις προδιαγραφές. Στην Elixir, αυτή η λειτουργία αναφέρεται ως συμπεριφορές.
Χρησιμότητα
Όταν θέλετε οι ενότητές σας να μοιράζονται ένα δημόσιο API, η λύση για αυτό στην Elixir είναι οι συμπεριφορές. Οι συμπεριφορές έχουν δύο κύριως ρόλους:
- Ορισμός ενός σετ συναρτήσεων που πρέπει να υλοποιηθούν
- Έλεγχος για την υλοποίηση αυτού του σετ
Η Elixir περιέχει ένα σύνολο συμπεριφορών όπως αυτή του GenServer, αλλά σε αυτό το μάθημα θα εστιάσουμε στη δημιουργία τους.
Ορίζοντας μια συμπεριφορά
Για να κατανοήσουμε καλύτερα τις συμπεριφορές, ας υλοποιήσουμε μια για μια ενότητα εργάτη. Αυτοί οι εργάτες θα πρέπει να υλοποιούν δύο συναρτήσεις: τις init/1
και perform/2
.
Για να καταφέρουμε αυτό, θα χρησιμοποιήσουμε την οδηγία @callback
με συντακτικό παρόμοιο της @spec
. Αυτό ορίζει μια αναγκαία μέθοδο - για τις μακροεντολές μπορούμε να χρησιμοποιήσουμε την @macrocallback
. Ας καθορίσουμε τις μεθόδους init/1
και perform/2
για τους εργάτες μας:
defmodule Example.Worker do
@callback init(state :: term) :: {:ok, new_state :: term} | {:error, reason :: term}
@callback perform(args :: term, state :: term) ::
{:ok, result :: term, new_state :: term}
| {:error, reason :: term, new_state :: term}
end
Εδώ ορίσαμε την init/1
σαν να αποδέχεται κάθε τιμή και να επιστρέφει μια τούπλα από τις {:ok, state}
ή {:error, reason}
, μια πολύ συνηθισμένη αρχικοποίηση. Η μέθοδος perform/2
θα λαμβάνει μερικά ορίσματα για τον εργάτη μαζί με την κατάσταση που αρχικοποιήσαμε, και θα περιμένουμε από την perform/2
να επιστρέψει {:ok, result, state}
ή {:error, reason, state}
όπως ακριβώς και οι GenServers.
Χρήση συμπεριφορών
Τώρα που έχουμε ορίσει την συμπεριφορά μας μπορούμε να την χρησιμοποιήσουμε σε ένα εύρος ενοτήτων που όλες μοιράζονται το ίδιο δημόσιο API. Η προσθήκη μιας συμπεριφοράς στην ενότητά μας είναι εύκολη με το χαρακτηριστικό @behaviour
.
Χρησιμοποιώντας τη νέα μας συμπεριφορά ας δημιουργήσουμε μια ενότητα που θα κατεβάζει ένα απομακρυσμένο αρχείο και θα το αποθηκεύει τοπικά:
defmodule Example.Downloader do
@behaviour Example.Worker
def init(opts), do: {:ok, opts}
def perform(url, opts) do
url
|> HTTPoison.get!()
|> Map.fetch(:body)
|> write_file(opts[:path])
|> respond(opts)
end
defp write_file(:error, _), do: {:error, :missing_body}
defp write_file({:ok, contents}, path) do
path
|> Path.expand()
|> File.write(contents)
end
defp respond(:ok, opts), do: {:ok, opts[:path], opts}
defp respond({:error, reason}, opts), do: {:error, reason, opts}
end
Ή τι θα λέγατε για έναν εργάτη που συμπιέζει ένα πίνακα αρχείων; Είναι εφικτό:
defmodule Example.Compressor do
@behaviour Example.Worker
def init(opts), do: {:ok, opts}
def perform(payload, opts) do
payload
|> compress
|> respond(opts)
end
defp compress({name, files}), do: :zip.create(name, files)
defp respond({:ok, path}, opts), do: {:ok, path, opts}
defp respond({:error, reason}, opts), do: {:error, reason, opts}
end
Παρόλο που η εργασία που γίνεται είναι διαφορετική, το δημόσιο API που βλέπουμε δεν είναι, και κάθε κώδικας που αξιοποιεί αυτές τις ενότητες μπορεί να αλληλεπιδρά μαζί τους γνωρίζοντας ότι θα ανταποκρίνονται όπως πρέπει. Αυτό μας δίνει τη δυνατότητα να δημιουργήσουμε όσους εργάτες θέλουμε, με τον καθένα να κάνει διαφορετικές εργασίες, αλλά όλοι να συμμορφώνονται στο ίδιο δημόσιο API.
Αν τύχει και προσθέσουμε μια συμπεριφορά αλλά αποτύχουμε στο να υλοποιήσουμε τις αναγκαίες συναρτήσεις, θα σηκωθεί μια προειδοποίηση κατά την σύνταξη. Για να το δούμε αυτό στην πράξη ας αλλάξουμε τον κώδικά μας στην ενότητα Example.Compressor
αφαιρώντας τη συνάρτηση init/1
:
defmodule Example.Compressor do
@behaviour Example.Worker
def perform(payload, opts) do
payload
|> compress
|> respond(opts)
end
defp compress({name, files}), do: :zip.create(name, files)
defp respond({:ok, path}, opts), do: {:ok, path, opts}
defp respond({:error, reason}, opts), do: {:error, reason, opts}
end
Τώρα θα πρέπει να δούμε μια προειδοποίηση κατά τη σύνταξη του κώδικά μας:
lib/example/compressor.ex:1: warning: undefined behaviour function init/1 (for behaviour Example.Worker)
Compiled lib/example/compressor.ex
Αυτό ήταν! Τώρα είμαστε έτοιμοι να συντάξουμε και μοιραστούμε συμπεριφορές με άλλους.
Έπιασες λάθος ή θέλεις να συνεισφέρεις στο μάθημα; Επεξεργαστείτε αυτό το μάθημα στο GitHub!