最近、PLOへの別れのトピックに関する記事と議論、およびこの概念にアランケイが最初に入れた意味の検索がより頻繁になりました。
逃した人に対するケイのいくつかのことわざ「オブジェクト指向」という用語を作成しましたが、C ++を念頭に置いていなかったことがわかります。
私にとってのOOPとは、メッセージング、ローカルの保持と状態プロセスの保護と隠蔽、およびすべてのものの極端な遅延バインディングのみを意味します。
私がずっと前にこのトピックの「オブジェクト」という用語を作ったのは残念です。なぜなら、多くの人々がより小さなアイデアに集中するようになるからです。 大きなアイデアは「メッセージング」です。
優れた成長可能なシステムを作成するための鍵は、内部のプロパティや動作がどうあるべきかではなく、モジュールの通信方法を設計することです。
レイトバインディングにより、プロジェクト開発の後半で学習したアイデアを、従来のアーリーバインディングシステム(C、C ++、Javaなど)よりも指数関数的に少ない労力でプロジェクトに再定式化できます。
私は型には反対ではありませんが、完全な苦痛ではない型システムについては知りません。そのため、動的型付けがまだ好きです。
これらの議論に関連して、Erlang / Elixirがケイが「オブジェクト指向」の概念に設定した基準を非常によく満たすという考えがしばしば生じます。 しかし、誰もがこれらの言語に精通しているわけではないため、一般的なC ++、Java、C#よりも機能言語がオブジェクト指向になりやすいという誤解があります。
この記事では、
exercism.ioを使用した簡単な例で、OOPがElixirでどのように見えるかを示したいと思います。
タスクの説明学生の名前を保存する小さなプログラムを作成し、学習するクラス番号でグループ化します。
最終的に、次のことができるはずです。
- クラスに学生名を追加
- クラスの全生徒のリストを取得する
- すべての成績のすべての学生のソートされたリストを取得します。 クラスは昇順(1、2、3など)で並べ替える必要があり、生徒の名前はアルファベット順に並べ替える必要があります。
テストから始めて、関数を呼び出すコードがどのようになるかを確認します。
ExercismがRuby用に準備した
テストを見
てください。OOPでは、演算子でさえ他の人のメソッドである
ことがわかりました。
そして、このプログラムのElixirバージョンについても同様のテストを作成します。Code.load_file("school.exs")
ExUnit.start
defmodule SchoolTest do
use ExUnit.Case, async: true
import School, only: [add_student: 3, students_by_grade: 1, students_by_grade: 2]
test "get students in a non existant grade" do
school = School.new
assert [] == school |> students_by_grade(5)
end
test "add student" do
school = School.new
school |> add_student("Aimee", 2)
assert ["Aimee"] == school |> students_by_grade(2)
end
test "add students to different grades" do
school = School.new
school |> add_student("Aimee", 3)
school |> add_student("Beemee", 7)
assert ["Aimee"] == school |> students_by_grade(3)
assert ["Beemee"] == school |> students_by_grade(7)
end
test "grade with multiple students" do
school = School.new
grade = 6
students = ~w(Aimee Beemee Ceemee)
students |> Enum.each(fn(student) -> school |> add_student(student, grade) end)
assert students == school |> students_by_grade(grade)
end
test "grade with multiple students sorts correctly" do
school = School.new
grade = 6
students = ~w(Beemee Aimee Ceemee)
students |> Enum.each(fn(student) -> school |> add_student(student, grade) end)
assert Enum.sort(students) == school |> students_by_grade(grade)
end
test "empty students by grade" do
school = School.new
assert [] == school |> students_by_grade
end
test "students_by_grade with one grade" do
school = School.new
grade = 6
students = ~w(Beemee Aimee Ceemee)
students |> Enum.each(fn(student) -> school |> add_student(student, grade) end)
assert [[grade: 6, students: Enum.sort(students)]] == school |> students_by_grade
end
test "students_by_grade with different grades" do
school = School.new
everyone |> Enum.each(fn([grade: grade, students: students]) ->
students |> Enum.each(fn(student) -> school |> add_student(student, grade) end)
end)
assert everyone_sorted == school |> students_by_grade
end
defp everyone do
[
[ grade: 3, students: ~w(Deemee Eeemee) ],
[ grade: 1, students: ~w(Effmee Geemee) ],
[ grade: 2, students: ~w(Aimee Beemee Ceemee) ]
]
end
defp everyone_sorted do
[
[ grade: 1, students: ~w(Effmee Geemee) ],
[ grade: 2, students: ~w(Aimee Beemee Ceemee) ],
[ grade: 3, students: ~w(Deemee Eeemee) ]
]
end
end
, «» «» School:
school = School.new
school |> add_student("Aimee", 2) # => :ok
school |> students_by_grade(2) # => ["Aimee"]
school |> students_by_grade # => [[grade: 2, students: ["Aimee"]]]
, , , , . , . -> pipe- |>.
, , , pipe- , , . Elixir :
school = School.new
School.add_student(school, "Aimee", 2) # => :ok
School.students_by_grade(school, 2) # => ["Aimee"]
School.students_by_grade(school) # => [[grade: 2, students: ["Aimee"]]]
! «» . . , …
, , Erlang, Elixir, OTP, - , , Erlang. OTP ( ). , —
GenServer. ( Erlang).
- , , . , , race condition , . — GenServer, — .
, , handle_call (c ) handle_cast ( ). , . .. API-, , .
, , (.. ).
, . , :
defmodule School do
use GenServer
# API
@doc """
Start School process.
"""
def new do
{:ok, pid} = GenServer.start_link(__MODULE__, %{})
pid
end
@doc """
Add a student to a particular grade in school.
"""
def add_student(pid, name, grade) do
GenServer.cast(pid, {:add, name, grade})
end
@doc """
Return the names of the students in a particular grade.
"""
def students_by_grade(pid, grade) do
GenServer.call(pid, {:students_by_grade, grade})
end
@doc """
Return the names of the all students separated by grade.
"""
def students_by_grade(pid) do
GenServer.call(pid, :all_students)
end
# Callbacks
def handle_cast({:add, name, grade}, state) do
state = Map.update(state, grade, [name], &([name|&1]))
{:noreply, state}
end
def handle_call({:students_by_grade, grade}, _from, state) do
students = Map.get(state, grade, []) |> Enum.sort
{:reply, students, state}
end
def handle_call(:all_students, _from, state) do
all_students = state
|> Map.keys
|> Enum.map(fn(grade) ->
[grade: grade, students: get_students_by_grade(state, grade)]
end)
{:reply, all_students, state}
end
# Private functions
defp get_students_by_grade(state, grade) do
Map.get(state, grade, []) |> Enum.sort
end
end
, , GenServer, 3 :
- API — , GenServer , / ..
- Callbacks — , GenServer: ..
- Private functions — ,
— pid, API-. start_link, , , ( ) new.
- system-wide , , . pid API-, .. .
Elixir , .
P.S. , Elixir , , — . « ».