memperkenalkan
Dalam artikel ini, kami mempertimbangkan berbagai aspek pemrograman berorientasi objek dalam ecmascript (meskipun topik ini telah dibahas dalam banyak artikel sebelumnya). Kami akan melihat masalah ini lebih dari perspektif teoretis. Secara khusus, kami akan mempertimbangkan algoritma penciptaan objek, bagaimana hubungan antara objek (termasuk hubungan dasar - warisan) juga tersedia dalam diskusi (saya berharap bahwa beberapa ambiguitas konseptual OOP dalam JavaScript akan dihapus).
Teks Bahasa Inggris Asli: http://dmitrysoshnikov.com/ecmascript/chapter-7-1-oop-general-theory/
Pendahuluan, Paradigma dan Pikiran
Sebelum melakukan analisis teknis OOP dalam ecmascript, perlu untuk menguasai beberapa karakteristik dasar OOP dan mengklarifikasi konsep utama dalam pendahuluan.
Ecmascript mendukung berbagai metode pemrograman termasuk terstruktur, berorientasi objek, fungsional, imperatif, dll., Dan dalam beberapa kasus ini juga mendukung pemrograman yang berorientasi pada aspek; Tetapi artikel ini membahas pemrograman berorientasi objek, jadi kami akan memberikan definisi pemrograman yang berorientasi objek dalam ecmascript:
Ecmascript adalah bahasa pemrograman yang berorientasi objek berdasarkan implementasi prototipe.
Ada banyak perbedaan antara OOP berbasis prototipe dan metode berbasis kelas statis. Mari kita lihat perbedaan rinci langsung mereka.
Berbasis kelas dan berbasis prototipe
Perhatikan bahwa poin penting dalam kalimat sebelumnya telah menunjukkan - itu didasarkan sepenuhnya pada kelas statis. Dengan kata "statis", kami memahami objek statis dan kelas statis, sangat diketik (meskipun tidak diperlukan).
Mengenai situasi ini, banyak dokumen forum menekankan bahwa ini adalah alasan utama mengapa mereka menentang membandingkan "kelas dengan prototipe" dalam JavaScript. Meskipun perbedaan implementasinya (seperti Python dan Ruby berdasarkan kelas dinamis) tidak terlalu menentang poin (beberapa kondisi ditulis, meskipun ada beberapa perbedaan dalam ide, JavaScript tidak begitu alternatif), tetapi oposisi mereka adalah kelas statis dan prototipe dinamis (statika + kelas vs dinamika + prototipe). Lebih tepatnya, kelas statis (seperti C ++, Java) dan mekanisme definisi bawahan dan metode dapat memungkinkan kita melihat perbedaan yang tepat antara itu dan implementasi prototipe.
Tapi mari kita daftarkan mereka satu per satu. Mari kita pertimbangkan prinsip -prinsip umum dan konsep utama dari paradigma ini.
Berdasarkan kelas statis
Dalam model berbasis kelas, ada konsep tentang kelas dan instance. Contoh kelas juga sering bernama objek atau paradigma.
Kelas dan benda
Kelas mewakili abstraksi suatu instance (mis. Objek). Ini agak seperti matematika, tetapi kami menyebutnya jenis atau klasifikasi.
Misalnya (contoh di sini dan di bawah ini adalah pseudo-code):
Salinan kode adalah sebagai berikut:
C = class {a, b, c} // class c, termasuk fitur a, b, c
Karakteristik instance adalah: atribut (deskripsi objek) dan metode (aktivitas objek). Karakteristik itu sendiri juga dapat dianggap sebagai objek: yaitu, apakah atributnya dapat ditulisi, dapat dikonfigurasi, disesuaikan (pengambil/setter), dll. Oleh karena itu, objek menyimpan keadaan (mis., Nilai -nilai spesifik dari semua atribut yang dijelaskan dalam kelas), dan kelas -kelasnya mendefinisikan struktur yang tidak selaras (properti) dan perilaku invarian yang ketat.
Salinan kode adalah sebagai berikut:
C = class {a, b, c, method1, method2}
c1 = {a: 10, b: 20, c: 30} // class c adalah instance: objek с1
C2 = {A: 50, B: 60, C: 70} // Kelas C adalah sebuah instance: Object с2, yang memiliki keadaan sendiri (yaitu, nilai atribut)
Warisan hierarkis
Untuk meningkatkan penggunaan kembali kode, kelas dapat diperpanjang dari satu ke yang lain, menambahkan informasi tambahan. Mekanisme ini disebut warisan (hierarkis).
Salinan kode adalah sebagai berikut:
D = kelas memperluas c = {d, e} // {a, b, c, d, e}
d1 = {a: 10, b: 20, c: 30, d: 40, e: 50}
Saat memanggil partai pada contoh kelas, kelas asli biasanya akan mencari metode sekarang. Jika tidak ditemukan, buka kelas induk untuk mencari secara langsung. Jika tidak ditemukan, buka kelas induk dari kelas induk untuk mencari (misalnya, pada rantai warisan yang ketat). Jika ditemukan bahwa bagian atas warisan belum ditemukan, hasilnya adalah: Objek tidak memiliki perilaku yang sama dan tidak bisa mendapatkan hasilnya.
Salinan kode adalah sebagai berikut:
d1.method1 () // d.method1 (tidak) -> c.method1 (ya)
d1.method5 () // d.method5 (no) -> c.method5 (no) -> tidak ada hasil
Dibandingkan dengan metode warisan yang tidak disalin ke subclass, properti selalu rumit ke dalam subclass. Kita dapat melihat bahwa subclass D mewarisi dari kelas induk C: Atribut A, B, C disalin, dan struktur D adalah {a, b, c, d, e}}. Namun, metode {method1, method2} tidak menyalin masa lalu, tetapi mewarisi masa lalu. Oleh karena itu, yaitu, jika kelas yang sangat dalam memiliki beberapa properti yang tidak dibutuhkan objek sama sekali, maka subclass juga memiliki properti ini.
Konsep kunci berdasarkan kelas
Karena itu, kami memiliki konsep -konsep kunci berikut:
1. Sebelum membuat objek, kelas harus dinyatakan. Pertama, perlu untuk mendefinisikan kelasnya.
2. Oleh karena itu, objek akan dibuat dari kelas yang disusun menjadi "piktografi dan kesamaan" sendiri (struktur dan perilaku)
3. Metode ini ditangani melalui rantai warisan yang ketat, langsung, dan tidak berubah.
4. Subkelas berisi semua atribut dalam rantai warisan (bahkan jika beberapa atribut tidak diperlukan oleh subkelas);
5. Buat instance kelas. Kelas tidak dapat (karena model statis) untuk mengubah karakteristik (atribut atau metode) dari contohnya;
6. Contoh (karena model statis yang ketat) tidak dapat memiliki perilaku atau atribut tambahan kecuali untuk perilaku dan atribut yang dinyatakan dalam kelas yang sesuai dengan instance.
Mari kita lihat cara mengganti model OOP di JavaScript, yang merupakan OOP berbasis prototipe yang kami rekomendasikan.
Berdasarkan prototipe
Konsep dasar di sini adalah objek dinamis yang dapat berubah. Transformasi (konversi penuh, termasuk tidak hanya nilai, tetapi juga fitur) secara langsung terkait dengan bahasa dinamis. Objek seperti berikut ini dapat secara independen menyimpan semua sifat mereka (properti, metode) tanpa kelas.
Salinan kode adalah sebagai berikut:
Object = {A: 10, B: 20, C: 30, Metode: fn};
objek.a; // 10
objek.c; // 30
objek.method ();
Selain itu, karena dinamis, mereka dapat dengan mudah mengubah (menambah, menghapus, memodifikasi) fitur mereka sendiri:
Salinan kode adalah sebagai berikut:
object.method5 = function () {...}; // Tambahkan metode baru
objek.d = 40; // Tambahkan atribut baru "D"
hapus objek.c; // Hapus atribut "с"
objek.a = 100; // Ubah atribut "a"
// Hasilnya adalah: objek: {a: 100, b: 20, d: 40, metode: fn, metode5: fn};
Artinya, ketika menetapkan, jika beberapa fitur tidak ada, itu dibuat dan penugasan diinisialisasi dengannya, dan jika ada, itu baru saja diperbarui.
Dalam hal ini, penggunaan kembali kode tidak diimplementasikan dengan memperluas kelas (perhatikan bahwa kami tidak mengatakan bahwa kelas tidak dapat diubah karena tidak ada konsep kelas di sini), tetapi diimplementasikan oleh prototipe.
Prototipe adalah objek yang digunakan sebagai salinan primitif dari objek lain, atau jika beberapa objek tidak memiliki karakteristik yang diperlukan sendiri, prototipe dapat digunakan sebagai delegasi untuk objek ini dan bertindak sebagai objek tambahan.
Berdasarkan delegasi
Objek apa pun dapat digunakan sebagai objek prototipe untuk objek lain, karena objek dapat dengan mudah mengubah dinamika prototipe saat runtime.
Perhatikan bahwa kami sedang mempertimbangkan pengantar daripada implementasi konkret, dan ketika kami membahas implementasi konkret dalam ecmascript, kami akan melihat beberapa karakteristik mereka sendiri.
Contoh (pseudocode):
Salinan kode adalah sebagai berikut:
x = {a: 10, b: 20};
y = {A: 40, C: 50};
y. [[prototipe]] = x; // x adalah prototipe y
ya; // 40, karakteristiknya sendiri
YC; // 50, itu juga karakteristiknya sendiri
YB; // 20 Dapatkan dari prototipe: yb (tidak) -> y. [[Prototipe]]. B (ya): 20
hapus ya; // hapus sendiri "a"
ya; // 10 Dapatkan dari prototipe
z = {a: 100, e: 50}
y. [[prototipe]] = z; // Ubah prototipe y ke z
ya; // 100 Dapatkan dari Prototipe Z
kamu // 50, juga diperoleh dari prototipe z
ZQ = 200 // Tambahkan atribut baru ke prototipe
yq // modifikasi juga berlaku untuk y
Contoh ini menunjukkan fungsi dan mekanisme penting dari prototipe sebagai atribut objek tambahan, sama seperti Anda memerlukan atribut Anda sendiri. Dibandingkan dengan atribut Anda sendiri, atribut ini adalah atribut delegasi. Mekanisme ini disebut delegasi, dan model prototipe berdasarkan itu adalah prototipe delegasi (atau prototipe berbasis delegasi). Mekanisme referensi disebut mengirim informasi ke suatu objek. Jika objek tidak mendapat respons, itu akan didelegasikan ke prototipe untuk menemukannya (mengharuskannya untuk mencoba menanggapi pesan).
Penggunaan kembali kode dalam kasus ini disebut warisan berbasis delegasi atau warisan berbasis prototipe. Karena objek apa pun dapat dianggap sebagai prototipe, yaitu, prototipe juga dapat memiliki prototipe sendiri. Prototipe ini terhubung bersama untuk membentuk rantai prototipe yang disebut. Rantai juga hierarkis di kelas statis, tetapi dapat dengan mudah diatur ulang, mengubah hierarki dan struktur.
Salinan kode adalah sebagai berikut:
x = {a: 10}
y = {b: 20}
y. [[prototipe]] = x
z = {C: 30}
z. [[prototipe]] = y
za // 10
// za ditemukan dalam rantai prototipe:
// za (tidak) ->
// z. [[prototipe]]. a (tidak) ->
// z. [[prototipe]]. [[prototipe]]. a (ya): 10
Jika suatu objek dan rantai prototipe tidak dapat menanggapi pengiriman pesan, objek dapat mengaktifkan sinyal sistem yang sesuai, yang dapat diproses oleh delegasi lain pada rantai prototipe.
Sinyal sistem ini tersedia dalam banyak implementasi, termasuk sistem berdasarkan kelas dinamis: #doesnotunderstand di SmallTalk, Method_missing di Ruby; __getAttr__ dalam Python, __call dalam PHP; dan __nosuchmethod__ implementasi dalam ecmascript, dll.
Contoh (implementasi ecmascript spidermonkey):
Salinan kode adalah sebagai berikut:
var objek = {
// Tangkap sinyal sistem yang tidak dapat menanggapi pesan
__nosuchmethod__: function (name, args) {
peringatan ([nama, args]);
if (name == 'test') {
metode return '.test () ditangani';
}
return delegate [name] .Apply (ini, args);
}
};
var delegate = {
Square: function (a) {
mengembalikan a * a;
}
};
peringatan (objek.square (10)); // 100
alert (object.test ()); // .test () metode ditangani
Artinya, berdasarkan implementasi kelas statis, ketika pesan tidak dapat ditanggapi, kesimpulannya adalah bahwa objek saat ini tidak memiliki karakteristik yang diperlukan, tetapi jika Anda mencoba mendapatkannya dari rantai prototipe, Anda mungkin masih mendapatkan hasilnya, atau objek memiliki karakteristik setelah serangkaian perubahan.
Mengenai ecmascript, implementasi spesifik adalah: menggunakan prototipe berbasis delegasi. Namun, seperti yang akan kita lihat dari spesifikasi dan implementasi, mereka juga memiliki karakteristik sendiri.
Model concatenative
Jujur, perlu untuk mengatakan sesuatu tentang situasi lain (tidak digunakan dalam ecmascript sesegera mungkin): situasi ini ketika prototipe ini kompleks dari objek lain ke penggantian asli objek asli. Dalam hal ini kode penggunaan kembali adalah salinan nyata (klon) dari suatu objek daripada delegasi selama tahap pembuatan objek. Prototipe ini disebut prototipe concatenative. Menyalin sifat-sifat dari semua prototipe suatu objek dapat lebih lanjut mengubah sifat dan metodenya, dan sebagai prototipe, Anda juga dapat mengubah diri Anda sendiri (dalam model berbasis delegasi, perubahan ini tidak akan mengubah perilaku objek yang ada, tetapi akan mengubah karakteristik prototipe). Keuntungan dari metode ini adalah bahwa ia dapat mengurangi waktu penjadwalan dan delegasi, sementara kerugiannya adalah menggunakan memori.
Tipe bebek
Kembali ke objek perubahan tipe yang lemah secara dinamis, dibandingkan dengan model berbasis kelas statis, perlu untuk memeriksa apakah ia dapat melakukan hal-hal ini dan jenis apa (kelas) objek tersebut, tetapi apakah itu dapat terkait dengan pesan yang sesuai (mis. Apakah ia memiliki kemampuan untuk melakukannya setelah pemeriksaan diperlukan).
Misalnya:
Salinan kode adalah sebagai berikut:
// dalam model berbasis statis
if (objek instance dari someclass) {
// Beberapa perilaku sedang berjalan
}
// dalam implementasi dinamis
// Tidak masalah apa jenis objek saat ini
// Karena mutasi, jenis, dan karakteristik dapat diubah secara bebas.
// apakah objek penting dapat menanggapi pesan pengujian
if (isFunction (object.test)) // ecmascript
Jika objek.Respons_to? (: test) // ruby
jika hasattr (objek, 'tes'): // python
Ini disebut tipe Dock. Artinya, objek dapat diidentifikasi dengan karakteristik mereka sendiri saat memeriksa, daripada lokasi objek dalam hierarki atau mereka termasuk jenis spesifik apa pun.
Konsep kunci berdasarkan prototipe
Mari kita lihat fitur utama dari metode ini:
1. Konsep dasarnya adalah objeknya
2. Objek benar -benar dinamis dan dapat berubah (secara teori, itu dapat sepenuhnya ditransformasikan dari satu jenis ke jenis lainnya)
3. Objek tidak memiliki kelas ketat yang menggambarkan struktur dan perilaku mereka sendiri, dan objek tidak memerlukan kelas.
4. Objek tidak memiliki kelas kelas tetapi dapat memiliki prototipe. Jika mereka tidak dapat menanggapi pesan, mereka dapat mendelegasikan prototipe.
5. Prototipe objek dapat diubah kapan saja selama runtime;
6. Dalam model berbasis delegasi, mengubah karakteristik prototipe akan mempengaruhi semua objek yang terkait dengan prototipe;
7. Dalam model prototipe gabungan, prototipe adalah salinan asli yang dikloning dari objek lain dan selanjutnya menjadi salinan asli yang sepenuhnya independen. Transformasi karakteristik prototipe tidak akan mempengaruhi objek yang dikloning darinya.
8. Jika pesan tidak dapat ditanggapi, peneleponnya dapat mengambil tindakan tambahan (mis., Ubah penjadwalan)
9. Kegagalan objek dapat ditentukan bukan oleh level mereka dan kelas mana mereka berada, tetapi dengan karakteristik saat ini.
Namun, ada model lain yang harus kita pertimbangkan juga.
Berdasarkan kelas dinamis
Kami percaya bahwa perbedaan "kelas vs prototipe" yang ditunjukkan dalam contoh di atas tidak penting dalam model berbasis kelas dinamis ini (terutama jika rantai prototipe tidak berubah, masih perlu untuk mempertimbangkan kelas statis untuk perbedaan yang lebih akurat). Sebagai contoh, ini juga dapat digunakan dalam Python atau Ruby (atau bahasa serupa lainnya). Semua bahasa ini menggunakan paradigma berbasis kelas dinamis. Namun, dalam beberapa aspek, kita dapat melihat fungsi -fungsi tertentu yang diimplementasikan berdasarkan prototipe.
Dalam contoh berikut, kita dapat melihat bahwa hanya berdasarkan prototipe delegasi, kita dapat memperkuat kelas (prototipe), sehingga mempengaruhi semua objek yang terkait dengan kelas ini, kita juga dapat secara dinamis mengubah kelas objek ini saat runtime (menyediakan objek baru untuk delegasi), dll.
Salinan kode adalah sebagai berikut:
# Python
Kelas A (Objek):
def __init __ (self, a):
self.a = a
def square (self):
kembali self.a * self.a
a = a (10) # Buat sebuah instance
Cetak (AA) # 10
AB = 20 # Menyediakan properti baru untuk kelas
Cetak (AB) # 20 Anda dapat mengaksesnya di instance "A"
A = 30 # Buat properti sendiri
Cetak (AB) # 30
del ab # hapus atributnya sendiri
Print (AB) # 20 - Dapatkan dari kelas lagi (prototipe)
# Seperti model berbasis prototipe
# Dapat mengubah prototipe objek saat runtime
Kelas B (Objek): # Kelas Kosong B
lulus
b = b () # b instance
b .__ class__ = a # Dynamically Change Class (Prototipe)
BA = 10 # Buat atribut baru
Cetak (B.Square ()) # 100 - Metode Kelas A tersedia saat ini
# Dapat menampilkan referensi di kelas yang dihapus
del a
Del b
# Tapi objek masih memiliki referensi implisit, dan metode ini masih tersedia
Cetak (b.square ()) # 100
# Tapi Anda tidak dapat mengubah kelas saat ini
# Ini adalah fitur implementasi
b .__ class__ = kesalahan #
Implementasi di Ruby serupa: Ini juga menggunakan kelas yang sepenuhnya dinamis (omong -omong, dalam versi python saat ini, dibandingkan dengan Ruby dan Ecmascript, menguatkan kelas (prototipe) tidak dapat dilakukan), kita dapat sepenuhnya mengubah karakteristik objek (atau klasion) (tambahkan metode/atribut ke kelas, dan perubahan ini akan mempengaruhi objek yang ada), tetapi secara dinamis.
Namun, artikel ini tidak khusus untuk Python dan Ruby, jadi kami tidak akan banyak bicara, mari kita terus membahas ecmascript itu sendiri.
Tetapi sebelum ini, kita harus melihat "gula sintetis" yang ditemukan di beberapa oops, karena banyak artikel sebelumnya tentang JavaScript sering mencakup masalah ini.
Satu -satunya kalimat yang salah yang perlu dicatat di bagian ini adalah: "JavaScript bukan kelas, ia memiliki prototipe dan dapat menggantikan kelas." Sangat perlu untuk mengetahui bahwa tidak semua implementasi berbasis kelas sangat berbeda. Bahkan jika kita dapat mengatakan "JavaScript berbeda", juga perlu dipertimbangkan (selain konsep "kelas") bahwa ada fitur terkait lainnya.
Fitur lain dari berbagai implementasi OOP
Di bagian ini, kami secara singkat memperkenalkan fitur -fitur lain dan berbagai implementasi OOP tentang penggunaan kembali kode, termasuk implementasi OOP dalam ecmascript. Alasannya adalah bahwa penampilan implementasi OOP sebelumnya di JavaScript memiliki beberapa pembatasan pemikiran kebiasaan. Satu -satunya persyaratan utama adalah bahwa itu harus dibuktikan secara teknis dan ideologis. Tidak dapat dikatakan bahwa Anda belum menemukan fungsi gula sintaks dalam implementasi OOP lainnya, dan Anda tidak tahu bahwa JavaScript bukan bahasa OOP murni, yang salah.
Polimorfik
Dalam ecmascript, ada beberapa polimorfisme di mana objek berarti.
Misalnya, suatu fungsi dapat diterapkan pada objek yang berbeda, sama seperti sifat -sifat objek asli (karena nilai ini ditentukan saat memasuki konteks eksekusi):
Salinan kode adalah sebagai berikut:
function test () {
waspada ([this.a, this.b]);
}
test.call ({a: 10, b: 20}); // 10, 20
test.call ({a: 100, b: 200}); // 100, 200
var a = 1;
var b = 2;
tes(); // 1, 2
Namun, ada pengecualian: metode date.prototype.gettime (), sesuai dengan standar, harus selalu ada objek tanggal, jika tidak pengecualian akan dilemparkan.
Salinan kode adalah sebagai berikut:
alert (date.prototype.gettime.call (tanggal baru ())); // waktu
alert (date.prototype.gettime.call (string baru (''))); // typeError
Polimorfisme parameter yang disebut ketika mendefinisikan fungsi setara dengan semua tipe data, tetapi hanya menerima parameter polimorfik (seperti metode penyortiran .SORT dari array dan parameternya - fungsi penyortiran polimorfisme). Ngomong -ngomong, contoh di atas juga dapat dianggap sebagai polimorfisme parameter.
Metode dalam prototipe dapat didefinisikan sebagai kosong, dan semua objek yang dibuat harus didefinisikan ulang (diimplementasikan) metode (mis. "Satu antarmuka (tanda tangan), beberapa implementasi").
Polimorfisme terkait dengan tipe bebek yang kami sebutkan di atas: yaitu jenis objek dan posisinya dalam hierarki tidak begitu penting, tetapi dapat dengan mudah diterima jika memiliki semua fitur yang diperlukan (mis. Antarmuka umum penting, dan implementasinya dapat bervariasi).
Kemasan
Seringkali ada pandangan yang salah tentang enkapsulasi. Pada bagian ini, kita akan membahas beberapa gula sintaksis dalam implementasi OOP - yaitu, pengubah terkenal: dalam hal ini, kami akan membahas beberapa implementasi OOP yang nyaman "gula" - pengubah terkenal: pribadi, terlindungi dan publik (atau tingkat akses objek atau pengubah akses).
Di sini saya ingin mengingatkan Anda tentang tujuan utama enkapsulasi: enkapsulasi adalah tambahan abstrak, daripada memilih "peretas jahat" tersembunyi yang menulis sesuatu langsung ke kelas Anda.
Ini adalah kesalahan besar: Gunakan tersembunyi untuk bersembunyi.
Tingkat akses (pribadi, terlindungi, dan publik), untuk kenyamanan pemrograman, telah diimplementasikan di banyak objek yang berorientasi pada objek (sangat sangat nyaman gula sintaks), dan menggambarkan dan membangun sistem secara lebih abstrak.
Ini dapat dilihat dalam beberapa implementasi (seperti Python dan Ruby yang telah disebutkan). Di satu sisi (dalam Python), properti _private _Private ini (melalui spesifikasi nama garis bawah) tidak dapat diakses dari luar. Python, di sisi lain, dapat diakses dari luar melalui aturan khusus (_classname__field_name).
Salinan kode adalah sebagai berikut:
Kelas A (Objek):
def __init __ (self):
self.public = 10
Self .__ Private = 20
def get_private (self):
mengembalikan diri .__ pribadi
# di luar:
a = a () # contoh a
Cetak (A.Public) # OK, 30
print (a.get_private ()) # ok, 20
Cetak (A .__ Pribadi) # gagal karena hanya dapat tersedia di a
# Tapi di Python, aturan khusus dapat diakses
cetak (A._a__private) # ok, 20
Di Ruby: Di satu sisi, ia memiliki kemampuan untuk menentukan karakteristik pribadi dan terlindungi, dan di sisi lain, ada juga metode khusus (seperti instance_variable_get, instance_variable_set, kirim, dll.) Untuk mendapatkan data yang dienkapsulasi.
Salinan kode adalah sebagai berikut:
Kelas a
Def menginisialisasi
@A = 10
akhir
def public_method
private_method (20)
akhir
Pribadi
def private_method (b)
mengembalikan @A + b
akhir
akhir
a = a.new # instance baru
A.public_method # ok, 30
AA # Gagal, @A - adalah variabel instance pribadi
# "private_method" bersifat pribadi dan hanya dapat diakses di kelas a
a.private_method # error
# Tapi ada nama metode metadata khusus yang bisa mendapatkan data
a.send (: private_method, 20) # ok, 30
A.instance_variable_get (:@a) # ok, 10
Alasan utama adalah enkapsulasi (perhatikan bahwa saya tidak menggunakan "tersembunyi") yang ingin diperoleh oleh programmer. Jika data ini diubah secara tidak benar dalam beberapa cara atau ada kesalahan, maka seluruh tanggung jawabnya adalah programmer, tetapi tidak hanya "kukuh" atau "mengubah bidang tertentu dengan santai." Tetapi jika ini sering terjadi, itu adalah kebiasaan dan gaya pemrograman yang buruk, karena biasanya API umum untuk "berbicara" dengan benda -benda.
Ulangi, tujuan dasar enkapsulasi adalah untuk mengabstraksikannya dari pengguna data tambahan, daripada mencegah peretas menyembunyikan data. Lebih serius, enkapsulasi bukan untuk menggunakan pribadi untuk memodifikasi data untuk mencapai tujuan keamanan perangkat lunak.
Enkapsulasi objek helper (lokal), kami memberikan kelayakan untuk perubahan perilaku antarmuka publik dengan biaya minimal, lokalisasi dan perubahan prediktif, yang merupakan tujuan enkapsulasi.
Selain itu, tujuan penting dari metode setter adalah untuk abstrak perhitungan kompleks. Misalnya, element.innerhtml setter - pernyataan abstrak - "HTML di dalam elemen ini sekarang sebagai berikut", dan fungsi setter dalam atribut InnerHTML akan sulit untuk dihitung dan diperiksa. Dalam hal ini, masalah sebagian besar melibatkan abstraksi, tetapi enkapsulasi dapat terjadi.
Konsep enkapsulasi tidak hanya terkait dengan OOP. Misalnya, ini bisa menjadi fungsi sederhana yang merangkum semua jenis perhitungan sehingga abstrak (tidak perlu memberi tahu pengguna, misalnya bagaimana fungsi matematika. Ini adalah enkapsulasi, perhatikan bahwa saya tidak mengatakan itu "pribadi, terlindungi, dan publik".
Versi spesifikasi ecmascript saat ini tidak mendefinisikan pengubah pribadi, terlindungi, dan publik.
Namun, dalam praktiknya adalah mungkin untuk melihat sesuatu bernama "meniru enkapsulasi JS". Umumnya tujuan konteks ini adalah digunakan (sebagai aturan, konstruktor itu sendiri). Sayangnya, sering menerapkan "imitasi" ini dapat dilakukan, dan pemrogram dapat menghasilkan entitas pseudo-absolute non-abstrak untuk mengatur "metode pengambil/setter" (saya katakan lagi, itu salah):
Salinan kode adalah sebagai berikut:
fungsi a () {
var _a; // "Pribadi" a
this.geta = function _geta () {
mengembalikan _a;
};
this.seta = function _seta (a) {
_a = a;
};
}
var a = new a ();
a.seta (10);
peringatan (A._a); // tidak terdefinisi, "pribadi"
peringatan (a.geta ()); // 10
Oleh karena itu, semua orang memahami bahwa untuk setiap objek yang dibuat, metode GETA/Seta juga dibuat, yang juga merupakan alasan peningkatan memori (dibandingkan dengan definisi prototipe). Meskipun, secara teori, objek dapat dioptimalkan dalam kasus pertama.
Selain itu, beberapa artikel JavaScript sering menyebutkan konsep "metode pribadi". Catatan: Standar ECMA-262-3 tidak menentukan konsep "metode pribadi".
Namun, dalam beberapa kasus dapat dibuat dalam konstruktor karena JS adalah bahasa ideologis - objek sepenuhnya dapat berubah dan memiliki karakteristik unik (dalam kondisi tertentu dalam konstruktor, beberapa objek dapat memperoleh metode tambahan, sementara yang lain tidak bisa).
Selain itu, dalam JavaScript, jika enkapsulasi masih disalahartikan sebagai cara untuk mencegah peretas jahat menulis nilai-nilai tertentu secara otomatis sebagai pengganti menggunakan metode setter, yang disebut "tersembunyi" dan "pribadi" tidak "tersembunyi". Beberapa implementasi dapat memperoleh nilai pada rantai ruang lingkup yang relevan (dan semua objek variabel yang sesuai) dengan memanggil konteks ke fungsi eval (yang dapat diuji pada spidermonkey 1.7).
Salinan kode adalah sebagai berikut:
eval ('_ a = 100', a.geta); // atau a.seta, karena metode "_a" pada [[lingkup]]
a.geta (); // 100
Atau, dalam implementasi, memungkinkan akses langsung ke objek aktif (seperti badak), dengan mengakses properti yang sesuai dari objek, nilai variabel internal dapat diubah:
Salinan kode adalah sebagai berikut:
// Badak
var foo = (function () {
var x = 10; // "pribadi"
return function () {
cetak (x);
};
}) ();
foo (); // 10
foo .__ orang tua __. x = 20;
foo (); // 20
Kadang -kadang, dalam JavaScript, tujuan data "pribadi" dan "dilindungi" dicapai dengan menggarisbawahi variabel (tetapi dibandingkan dengan Python, ini hanya spesifikasi penamaan):
var _myprivatedata = 'testString';
Ini sering digunakan untuk melampirkan konteks eksekusi dengan tanda kurung, tetapi untuk data tambahan, tidak memiliki hubungan langsung dengan objek, dan lebih mudah untuk abstrak dari API eksternal:
Salinan kode adalah sebagai berikut:
(fungsi () {
// inisialisasi konteksnya
}) ();
Warisan berganda
Multi-warisan adalah gula sintaksis yang sangat nyaman untuk perbaikan penggunaan kembali kode (jika kita dapat mewarisi satu kelas sekaligus, mengapa kita tidak bisa mewarisi 10 sekaligus?). Namun, karena beberapa kekurangan dalam warisan berganda, itu belum menjadi populer dalam implementasi.
Ecmascript tidak mendukung warisan berganda (mis., Hanya satu objek yang dapat digunakan sebagai prototipe langsung), meskipun bahasa pemrograman mandiri leluhurnya memiliki kemampuan seperti itu. Tetapi dalam beberapa implementasi (seperti spidermonkey) menggunakan __nosuchmethod__ dapat digunakan untuk mengelola penjadwalan dan mendelegasikan untuk mengganti rantai prototipe.
Mixin
Mixins adalah cara yang nyaman untuk menggunakan kembali kode. Mixins telah direkomendasikan sebagai pengganti warisan berganda. Elemen independen ini semuanya dapat dicampur dengan objek apa pun untuk memperluas fungsionalitasnya (sehingga objek juga dapat mencampur banyak mixin). Spesifikasi ECMA-262-3 tidak menentukan konsep "mixin", tetapi menurut definisi mixin dan ecmascript memiliki objek yang dapat berubah dinamis, sehingga tidak ada hambatan untuk hanya memperluas fitur menggunakan mixin.
Contoh khas:
Salinan kode adalah sebagai berikut:
// penolong untuk augmentasi
Object.extend = function (tujuan, sumber) {
untuk (properti di sumber) if (source.hasownproperty (properti)) {
tujuan [properti] = sumber [properti];
}
tujuan pengembalian;
};
var x = {a: 10, b: 20};
var y = {c: 30, d: 40};
Object.extend (x, y); // campur y menjadi x
waspada ([xa, xb, xc, xD]); 10, 20, 30, 40
Harap dicatat bahwa saya mengambil definisi ini ("mixin", "mix") dalam kutipan yang disebutkan dalam ECMA-262-3. Tidak ada konsep seperti itu dalam spesifikasi, dan itu bukan campuran tetapi cara yang umum digunakan untuk memperluas objek melalui fitur baru. (Konsep mixin di Ruby didefinisikan secara resmi. Mixin membuat referensi ke modul alih -alih hanya menyalin semua atribut modul ke modul lain - pada kenyataannya: membuat objek tambahan (prototipe) untuk delegasi).
Sifat-sifat
Ciri -ciri dan mixin memiliki konsep yang sama, tetapi memiliki banyak fungsi (menurut definisi, karena mixin dapat diterapkan sehingga tidak dapat berisi status karena memiliki potensi untuk menyebabkan konflik penamaan). Menurut ecmascript, sifat dan mixin mengikuti prinsip yang sama, sehingga spesifikasinya tidak menentukan konsep "sifat".
antarmuka
Antarmuka yang diimplementasikan dalam beberapa oops mirip dengan mixin dan sifat. Namun, dibandingkan dengan mixin dan sifat, antarmuka menegakkan kelas implementasi untuk mengimplementasikan perilaku tanda tangan metodenya.
Antarmuka dapat dianggap sebagai kelas abstrak. Namun, dibandingkan dengan kelas abstrak (metode dalam kelas abstrak hanya dapat menerapkan bagian dari mereka, dan bagian lain masih didefinisikan sebagai tanda tangan), warisan hanya dapat berupa kelas dasar warisan tunggal, tetapi dapat mewarisi banyak antarmuka. Inilah sebabnya mengapa antarmuka (beberapa campuran) dapat dianggap sebagai alternatif dari warisan berganda.
Standar ECMA-262-3 tidak mendefinisikan konsep "antarmuka" atau konsep "kelas abstrak". Namun, sebagai imitasi, dapat diimplementasikan oleh objek yang metode "kosong" (atau pengecualian yang dilemparkan dalam metode kosong, memberi tahu pengembang bahwa metode ini perlu diimplementasikan).
Kombinasi objek
Kombinasi objek juga merupakan salah satu teknik penggunaan kembali kode dinamis. Kombinasi objek berbeda dari warisan fleksibilitas tinggi, yang mengimplementasikan delegasi dinamis dan variabel. Dan ini juga didasarkan pada prototipe utama. Selain prototipe yang dapat berubah dinamis, objek dapat berupa objek agregat delegasi (buat kombinasi sebagai hasilnya - agregasi), dan selanjutnya mengirim pesan ke objek dan mendelegasikan ke delegasi. Ini bisa lebih dari dua delegasi, karena sifat dinamisnya menentukan bahwa itu dapat diubah saat runtime.
Contoh __nosuchmethod__ yang disebutkan seperti ini, tetapi juga memungkinkan kami menunjukkan cara menggunakan delegasi secara eksplisit:
Misalnya:
Salinan kode adalah sebagai berikut:
var _delegate = {
foo: function () {
alert ('_ delegate.foo');
}
};
var agregat = {
Delegasi: _delegate,
foo: function () {
kembalikan this.delegate.foo.call (ini);
}
};
agregat.foo (); // delegate.foo
agregate.delegate = {
foo: function () {
waspada ('foo dari delegasi baru');
}
};
agregat.foo (); // foo dari delegasi baru
Hubungan objek ini disebut "has-a", sedangkan integrasi adalah hubungan "is-a".
Karena kurangnya kombinasi tampilan (fleksibilitas dibandingkan dengan warisan), juga tidak masalah untuk menambahkan kode perantara.
Fitur AOP
Sebagai fungsi yang berorientasi pada aspek, dekorator fungsi dapat digunakan. Spesifikasi ECMA-262-3 tidak memiliki konsep "dekorator fungsi" yang jelas (sebagai lawan dari Python, kata tersebut secara resmi didefinisikan dalam Python). Namun, fungsi dengan parameter fungsional bersifat dekoratif dan diaktifkan dalam beberapa hal (dengan menerapkan apa yang disebut saran):
Contoh paling sederhana dari dekorator:
Salinan kode adalah sebagai berikut:
function checkDecorator (original function) {
return function () {
if (foobar! = 'test') {
peringatan ('parameter salah');
mengembalikan false;
}
return original function ();
};
}
function test () {
peringatan ('fungsi uji');
}
var testWithCheck = checkDecorator (tes);
var foobar = false;
tes(); // 'fungsi tes'
testwithcheck (); // 'parameter yang salah'
foobar = 'tes';
tes(); // 'fungsi tes'
testwithcheck (); // 'fungsi tes'
sebagai kesimpulan
Dalam artikel ini, kami telah menyelesaikan pengantar OOP (saya harap informasi ini bermanfaat bagi Anda), dan di bab berikutnya kami akan terus mengimplementasikan ecmascript dalam pemrograman yang berorientasi objek.