Rumah  >  Artikel  >  hujung hadapan web  >  Pemahaman mendalam tentang siri JavaScript (17): Pengenalan terperinci kepada pengaturcaraan berorientasikan objek_Pengetahuan asas

Pemahaman mendalam tentang siri JavaScript (17): Pengenalan terperinci kepada pengaturcaraan berorientasikan objek_Pengetahuan asas

WBOY
WBOYasal
2016-05-16 16:11:181081semak imbas

Pengenalan

Dalam artikel ini, kami mempertimbangkan pelbagai aspek pengaturcaraan berorientasikan objek dalam ECMAScript (walaupun topik ini telah dibincangkan dalam banyak artikel sebelum ini). Kami akan melihat isu-isu ini lebih daripada perspektif teori. Khususnya, kami akan mempertimbangkan algoritma penciptaan objek, bagaimana objek berkaitan (termasuk hubungan asas - warisan), yang juga boleh digunakan dalam perbincangan (yang saya harap akan menghilangkan beberapa kekaburan konsep sebelumnya tentang OOP dalam JavaScript).

Teks asal bahasa Inggeris:http://dmitrysoshnikov.com/ecmascript/chapter-7-1-oop-general-theory/

Pengenalan, paradigma dan idea

Sebelum menjalankan analisis teknikal OOP dalam ECMAScript, kami perlu menguasai beberapa ciri asas OOP dan menjelaskan konsep utama dalam pengenalan.

ECMAScript menyokong pelbagai kaedah pengaturcaraan termasuk berstruktur, berorientasikan objek, berfungsi, imperatif, dll. Dalam sesetengah kes, ia juga menyokong pengaturcaraan berorientasikan aspek tetapi artikel ini membincangkan pengaturcaraan berorientasikan objek, jadi di sini ialah objek-; pengaturcaraan berorientasikan dalam ECMAScript Definisi:

ECMAScript ialah bahasa pengaturcaraan berorientasikan objek berdasarkan pelaksanaan prototaip.
Terdapat banyak perbezaan antara OOP berasaskan prototaip dan pendekatan berasaskan kelas statik. Mari kita lihat perbezaan mereka secara terperinci.

Berdasarkan atribut kelas dan berdasarkan prototaip

Perhatikan bahawa satu perkara penting telah dinyatakan dalam ayat sebelumnya - sepenuhnya berdasarkan kelas statik. Dengan perkataan "statik" kami memahami objek statik dan kelas statik, ditaip dengan kuat (walaupun tidak diperlukan).

Mengenai situasi ini, banyak dokumen di forum telah menekankan bahawa ini adalah sebab utama mengapa mereka membantah membandingkan "kelas dengan prototaip" dalam JavaScript, walaupun pelaksanaannya berbeza (seperti berdasarkan kelas dinamik) Python dan Ruby) tidak terlalu menentang fokus (beberapa syarat ditulis, walaupun terdapat perbezaan tertentu dalam pemikiran, JavaScript tidak menjadi begitu alternatif), tetapi tumpuan penentangan mereka adalah kelas statik vs. prototaip dinamik ), tepatnya, mekanisme daripada kelas statik (contohnya: C, JAVA) dan subordinatnya serta takrifan kaedah membolehkan kita melihat perbezaan yang tepat antara kelas itu dan pelaksanaan berasaskan prototaip.

Tetapi mari kita senaraikan satu persatu. Mari kita pertimbangkan prinsip umum dan konsep utama paradigma ini.

Berdasarkan kelas statik

Dalam model berasaskan kelas, terdapat konsep kelas dan kejadian. Contoh kelas juga sering dinamakan objek atau kejadian.

Kelas dan Objek

Kelas mewakili abstraksi suatu contoh (iaitu, objek). Ia agak seperti matematik dalam hal ini, tetapi kami memanggilnya jenis atau klasifikasi.

Sebagai contoh (contoh di sini dan di bawah ialah pseudokod):

Salin kod Kod adalah seperti berikut:

C = Kelas {a, b, c} // Kelas C, termasuk ciri a, b, c

Ciri-ciri kejadian ialah: sifat (huraian objek) dan kaedah (aktiviti objek). Sifat itu sendiri juga boleh dianggap sebagai objek: iaitu, sama ada sifat boleh ditulis, boleh dikonfigurasikan, boleh ditetapkan (getter/setter), dsb. Oleh itu, keadaan menyimpan objek (iaitu, nilai khusus semua sifat yang diterangkan dalam kelas), dan kelas mentakrifkan struktur tidak berubah (sifat) dan tingkah laku (kaedah) yang tidak boleh diubah secara ketat untuk kejadiannya.
Salin kod Kod adalah seperti berikut:

C = Kelas {a, b, c, kaedah1, kaedah2}

c1 = {a: 10, b: 20, c: 30} // Kelas C ialah contoh: objek с1
c2 = {a: 50, b: 60, c: 70} // Kelas C ialah contoh: objek с2, yang mempunyai keadaan sendiri (iaitu, nilai atribut)

Warisan berhierarki

Untuk meningkatkan penggunaan semula kod, kelas boleh dilanjutkan dari satu ke satu sama lain, menambah maklumat tambahan. Mekanisme ini dipanggil warisan (hierarki).

Salin kod Kod adalah seperti berikut:

D = Kelas melanjutkan C = {d, e} // {a, b, c, d, e}
d1 = {a: 10, b: 20, c: 30, d: 40, e: 50}

Apabila memanggil kaedah pada contoh kelas, anda biasanya akan mencari kaedah dalam kelas asli Jika tidak ditemui, pergi ke kelas induk langsung untuk mencari Jika belum ditemui, pergi ke kelas induk kelas induk untuk mencari (Contohnya, dalam rantaian warisan yang ketat), jika bahagian atas warisan ditemui tetapi belum ditemui, hasilnya ialah: objek tidak mempunyai kelakuan yang serupa dan tiada cara untuk mendapatkan hasilnya.

Salin kod Kod adalah seperti berikut:

d1.method1() // D.method1 (no) -> C.method1 (ya)
d1.method5() // D.method5 (no) -> C.method5 (no) ->
Berbeza dengan warisan, di mana kaedah tidak disalin ke subkelas, sifat sentiasa disalin ke subkelas. Kita dapat melihat bahawa subkelas D mewarisi daripada kelas induk C: atribut a, b, c disalin, dan struktur D ialah {a, b, c, d, e}}. Walau bagaimanapun, kaedah {method1, method2} tidak disalin dari masa lalu, tetapi diwarisi dari masa lalu. Oleh itu, jika kelas dalam mempunyai beberapa atribut yang objek tidak perlukan sama sekali, maka subkelas juga mempunyai atribut ini.

Konsep utama berdasarkan kelas

Oleh itu, kami mempunyai konsep utama berikut:

1. Sebelum mencipta objek, kelas mesti diisytiharkan terlebih dahulu, adalah perlu untuk menentukan kelasnya

2. Oleh itu, objek akan dicipta daripada kelas yang diabstraksikan ke dalam "ikonogram dan persamaan" (struktur dan kelakuan)nya sendiri
3. Kaedah diproses melalui rantaian warisan yang ketat, langsung dan tidak berubah
4. Subkelas mengandungi semua atribut dalam rantaian warisan (walaupun beberapa atribut tidak diperlukan oleh subkelas itu); 5. Buat contoh kelas. Kelas tidak boleh (kerana model statik) mengubah ciri (sifat atau kaedah) contohnya; 6. Kejadian (kerana model statik yang ketat) tidak boleh mempunyai gelagat atau atribut tambahan selain daripada yang diisytiharkan dalam kelas yang sepadan dengan contoh.

Mari lihat cara menggantikan model OOP dalam JavaScript, yang kami cadangkan berdasarkan prototaip OOP.

Berdasarkan prototaip

Konsep asas di sini ialah objek boleh ubah dinamik. Transformasi (transformasi lengkap, termasuk bukan sahaja nilai tetapi juga atribut) berkaitan secara langsung dengan bahasa dinamik. Objek seperti berikut boleh menyimpan semua sifatnya (sifat, kaedah) secara bebas tanpa memerlukan kelas.



Salin kod Kod adalah seperti berikut: objek = {a: 10, b: 20, c: 30, kaedah: fn};
objek.a; // 10
objek.c; // 30
object.method();



Tambahan pula, kerana dinamik, mereka boleh menukar (menambah, memadam, mengubah suai) ciri mereka dengan mudah:


Salin kod Kod adalah seperti berikut: object.method5 = function () {...}; // Tambah kaedah baharu
object.d = 40; // Tambah atribut baharu "d"
delete object.c; // Padam atribut "с"
object.a = 100; // Ubah suai atribut "а"

// Hasilnya ialah: objek: {a: 100, b: 20, d: 40, kaedah: fn, kaedah5: fn};



Iaitu, pada masa tugasan, jika beberapa atribut tidak wujud, buatnya dan mulakan tugasan dengannya, dan jika wujud, kemas kini sahaja.
Dalam kes ini, penggunaan semula kod tidak dicapai dengan memanjangkan kelas, (sila ambil perhatian bahawa kami tidak mengatakan bahawa kelas tidak boleh diubah, kerana tiada konsep kelas di sini), tetapi dengan prototaip.

Prototaip ialah objek yang digunakan sebagai salinan primitif objek lain, atau jika sesetengah objek tidak mempunyai sifat yang diperlukan sendiri, prototaip boleh digunakan sebagai perwakilan untuk objek ini dan berfungsi sebagai objek tambahan .

Berasaskan perwakilan

Sebarang objek boleh digunakan sebagai objek prototaip untuk objek lain, kerana objek boleh menukar prototaipnya secara dinamik pada masa jalan dengan mudah.

Perhatikan bahawa kami sedang mempertimbangkan gambaran keseluruhan dan bukannya pelaksanaan khusus Apabila kami membincangkan pelaksanaan khusus dalam ECMAScript, kami akan melihat beberapa ciri mereka sendiri.

Contoh (pseudokod):


Salin kod Kod adalah seperti berikut: x = {a: 10, b: 20};
y = {a: 40, c: 50};
y.[[Prototaip]] = x; // x ialah prototaip y

y.a; // 40, ciri sendiri
y.c; // 50, juga ciri-cirinya sendiri
y.b; // 20 – diperolehi daripada prototaip: y.b (tidak) -> y.[[Prototaip]].b (ya): 20

padam y.a; // Padam "а"nya sendiri
y.a; // 10 – Dapatkan
daripada prototaip
z = {a: 100, e: 50}
y.[[Prototaip]] = z; // Ubah suai prototaip y kepada z
y.a; // 100 – Dapatkan
daripada prototaip z y.e // 50, juga diperoleh daripada prototaip z

z.q = 200 // Tambah sifat baharu pada prototaip
y.q // Pengubahsuaian juga digunakan pada y

Contoh ini menunjukkan fungsi dan mekanisme penting prototaip sebagai atribut objek tambahan, sama seperti meminta atributnya sendiri Berbanding dengan atributnya sendiri, atribut ini adalah atribut perwakilan. Mekanisme ini dipanggil perwakilan, dan model prototaip berdasarkannya ialah prototaip perwakilan (atau prototaip berasaskan perwakilan). Mekanisme rujukan di sini dipanggil menghantar mesej kepada objek Jika objek tidak mendapat respons, ia akan diwakilkan kepada prototaip untuk mencarinya (memerlukannya untuk cuba membalas mesej tersebut).

Penggunaan semula kod dalam kes ini dipanggil warisan berasaskan perwakilan atau warisan berasaskan prototaip. Memandangkan sebarang objek boleh digunakan sebagai prototaip, ini bermakna prototaip juga boleh mempunyai prototaip sendiri. Prototaip ini dihubungkan bersama untuk membentuk rantai prototaip yang dipanggil. Rantaian juga berhierarki seperti kelas statik, tetapi ia boleh disusun semula dengan mudah untuk menukar hierarki dan struktur.

Salin kod Kod adalah seperti berikut:

x = {a: 10}

y = {b: 20}
y.[[Prototaip]] = x

z = {c: 30}
z.[[Prototaip]] = y

z.a // 10

// z.a ditemui dalam rantaian prototaip:
// z.a (tidak) ->
// z.[[Prototaip]].a (tidak) ->
// z.[[Prototaip]].[[Prototaip]].a (ya): 10

Jika objek dan rantai prototaipnya tidak dapat bertindak balas kepada penghantaran mesej, objek boleh mengaktifkan isyarat sistem yang sepadan, mungkin dikendalikan oleh perwakilan lain pada rantai prototaip.

Isyarat sistem ini tersedia dalam banyak pelaksanaan, termasuk sistem berdasarkan kelas dinamik kurungan: #doesNotUnderstand dalam Smalltalk, method_missing dalam Ruby, __call dalam PHP dan __noSuchMethod__ pelaksanaan dalam ECMAScript, dsb.

Contoh (pelaksanaan ECMAScript SpiderMonkey):

Salin kod Kod adalah seperti berikut:

objek var = {

//Tangkap isyarat sistem yang tidak dapat membalas mesej
__noSuchMethod__: fungsi (nama, args) {
makluman([nama, args]);
Jika (nama == 'ujian') {
         pulangkan kaedah '.test() dikendalikan';
}
Kembalikan perwakilan[nama].apply(this, args);
}

};

var perwakilan = {
segi empat sama: fungsi (a) {
Kembalikan * a;
}
};

alert(object.square(10)); // 100
alert(object.test()); // kaedah .test() dikendalikan

Dalam erti kata lain, jika pelaksanaan berdasarkan kelas statik tidak dapat bertindak balas kepada mesej, kesimpulannya ialah objek semasa tidak mempunyai ciri yang diperlukan, tetapi jika anda cuba mendapatkannya daripada rantai prototaip, anda mungkin masih dapatkan hasilnya , atau objek mempunyai ciri ini selepas beberapa siri perubahan.

Mengenai ECMAScript, pelaksanaan khusus ialah: menggunakan prototaip berasaskan perwakilan. Walau bagaimanapun, seperti yang akan kita lihat dari spesifikasi dan pelaksanaan, mereka juga mempunyai ciri-ciri mereka sendiri.

Model Gabungan

Sejujurnya, adalah perlu untuk mengatakan sesuatu tentang situasi lain (sebaik sahaja ia tidak digunakan dalam ECMASCript): keadaan apabila prototaip menggantikan objek asli daripada objek lain. Penggunaan semula kod dalam kes ini ialah salinan sebenar (klon) objek semasa fasa penciptaan objek dan bukannya perwakilan. Prototaip jenis ini dipanggil prototaip concatenative. Menyalin semua sifat prototaip objek boleh mengubah sepenuhnya sifat dan kaedahnya, dan prototaip juga boleh mengubah dirinya sendiri (dalam model berasaskan perwakilan, perubahan ini tidak akan mengubah kelakuan objek sedia ada, tetapi mengubah sifat prototaipnya) . Kelebihan kaedah ini ialah ia dapat mengurangkan masa penjadualan dan delegasi, tetapi kelemahannya ialah penggunaan memori adalah tinggi.

Jenis Itik

Memulangkan objek yang menukar jenis lemah secara dinamik Berbanding dengan model berdasarkan kelas statik, menguji sama ada ia boleh melakukan perkara ini tiada kaitan dengan jenis (kelas) objek, tetapi sama ada ia boleh bertindak balas kepada mesej (yang. ialah, selepas menyemak sama ada Keupayaan untuk melakukannya adalah satu kemestian).

Contohnya:

Salin kod Kod adalah seperti berikut:

// Dalam model berasaskan statik
if (objek instanceof SomeClass) {
// Beberapa tindakan dijalankan
}

// Dalam pelaksanaan dinamik
// Tidak kira jenis objek pada ketika ini
// Kerana mutasi, jenis dan sifat boleh diubah secara bebas dan berulang kali.
// Sama ada objek penting boleh membalas mesej ujian
if (isFunction(object.test)) // ECMAScript

if object.respond_to?(:test) // Ruby

if hasattr(objek, 'test'): // Python

Inilah yang dipanggil jenis Dok. Iaitu, objek boleh dikenal pasti dengan ciri-ciri mereka sendiri apabila menyemak, bukannya kedudukan objek dalam hierarki atau kepunyaan mereka kepada mana-mana jenis tertentu.

Konsep utama berdasarkan prototaip

Mari kita lihat ciri utama pendekatan ini:

1. Konsep asas ialah objek
2. Objek benar-benar dinamik dan berubah-ubah (secara teorinya ia boleh ditukar dari satu jenis ke jenis yang lain)
3. Objek tidak mempunyai kelas ketat yang menerangkan struktur dan tingkah lakunya sendiri. Objek tidak memerlukan kelas
4. Objek tidak mempunyai kelas tetapi boleh mempunyai prototaip Jika mereka tidak dapat membalas mesej, mereka boleh diwakilkan kepada prototaip
5. Prototaip objek boleh ditukar pada bila-bila masa semasa runtime;
6. Dalam model berasaskan perwakilan, mengubah ciri prototaip akan menjejaskan semua objek yang berkaitan dengan prototaip;
7. Dalam model prototaip gabungan, prototaip ialah salinan asal yang diklon daripada objek lain, dan seterusnya menjadi salinan asal yang bebas sepenuhnya Perubahan ciri prototaip tidak akan menjejaskan objek yang diklon daripadanya
8. Jika mesej tidak dapat dibalas, pemanggilnya boleh mengambil langkah tambahan (cth., menukar penjadualan)
9. Kegagalan objek tidak boleh ditentukan oleh tahap mereka dan kelas mana mereka tergolong, tetapi oleh ciri semasa

Walau bagaimanapun, terdapat model lain yang juga harus kita pertimbangkan.

Berdasarkan kelas dinamik

Kami percaya bahawa perbezaan "prototaip kelas VS" yang ditunjukkan dalam contoh di atas tidak begitu penting dalam model ini berdasarkan kelas dinamik, (terutamanya jika rantaian prototaip tidak boleh diubah, untuk perbezaan yang lebih tepat, ia masih perlu pertimbangkan kelas statik). Sebagai contoh, ia juga boleh menggunakan Python atau Ruby (atau bahasa lain yang serupa). Bahasa-bahasa ini semuanya menggunakan paradigma berasaskan kelas dinamik. Walau bagaimanapun, dalam beberapa aspek kita boleh melihat beberapa fungsi dilaksanakan berdasarkan prototaip.

Dalam contoh berikut, kita dapat melihat bahawa hanya berdasarkan delegasi, kita boleh membesarkan kelas (prototaip), dengan itu mempengaruhi semua objek yang berkaitan dengan kelas ini Kita juga boleh menukar objek ini secara dinamik pada masa runtime objek untuk perwakilan) dan sebagainya.

Salin kod Kod adalah seperti berikut:

#Python

kelas A(objek):

Def __init__(self, a):
         diri.a = a

Def persegi(diri):
          kembalikan diri.a * diri.a

a = A(10) # Cipta contoh
cetak(a.a) # 10

A.b = 20 # Sediakan atribut baharu untuk kelas
print(a.b) # 20 –
boleh diakses dalam contoh "a"
a.b = 30 # Cipta atribut a sendiri
cetak(a.b) # 30

del a.b # Padamkan atributnya sendiri
print(a.b) # 20 - Dapatkan (prototaip) dari kelas sekali lagi

# Sama seperti model berasaskan prototaip
# Anda boleh menukar prototaip objek pada masa jalan

kelas B(objek): # Kosongkan kelas B
Lulus

b = B() # Contoh B

b.__class__ = A # Tukar kelas (prototaip) secara dinamik

b.a = 10 # Cipta atribut baharu
print(b.square()) # 100 - Kaedah Kelas A tersedia pada masa ini

# Anda boleh memaparkan rujukan kepada kelas yang dipadam
del A
del B

# Tetapi objek masih mempunyai rujukan tersirat, dan kaedah ini masih tersedia
print(b.square()) # 100

# Tetapi kelas tidak boleh ditukar pada masa ini
# Ini ialah ciri yang dilaksanakan
b.__class__ = dict # error

Pelaksanaan dalam Ruby adalah serupa: kelas dinamik sepenuhnya juga digunakan (dengan cara dalam versi Python semasa, berbeza dengan Ruby dan ECMAScript, pembesaran kelas (prototaip) tidak berfungsi), kita boleh menukar objek sepenuhnya (atau kelas) ciri (menambah kaedah/sifat pada kelas, dan perubahan ini akan menjejaskan objek sedia ada), namun, ia tidak boleh menukar kelas objek secara dinamik.

Walau bagaimanapun, artikel ini tidak khusus mengenai Python dan Ruby, jadi kami tidak akan bercakap lebih lanjut dan mari terus membincangkan ECMAScript itu sendiri.

Tetapi sebelum itu, kita perlu melihat sekali lagi tentang "gula sintaksis" yang terdapat dalam beberapa OOP, kerana banyak artikel terdahulu tentang JavaScript sering merangkumi isu ini.

Satu-satunya ayat yang salah untuk diperhatikan dalam bahagian ini ialah: "JavaScript bukan kelas, ia mempunyai prototaip, yang boleh menggantikan kelas." Adalah penting untuk mengetahui bahawa tidak semua pelaksanaan berasaskan kelas adalah sama sekali berbeza Walaupun kita mungkin mengatakan "JavaScript adalah berbeza", ia juga perlu mempertimbangkan bahawa (sebagai tambahan kepada konsep "kelas") terdapat ciri lain yang berkaitan. .

Ciri lain pelbagai pelaksanaan OOP

Dalam bahagian ini kami memperkenalkan secara ringkas ciri dan kaedah penggunaan semula kod lain dalam pelbagai pelaksanaan OOP, termasuk pelaksanaan OOP dalam ECMAScript. Sebabnya ialah terdapat beberapa sekatan pemikiran lazim pada pelaksanaan OOP dalam JavaScript Satu-satunya keperluan utama ialah ia harus dibuktikan secara teknikal dan ideologi. Ia tidak boleh dikatakan bahawa kami tidak menemui fungsi gula sintaksis dalam pelaksanaan OOP lain, dan kami dengan tergesa-gesa menganggap bahawa JavaScript bukan bahasa OOP tulen Ini adalah salah.

Polimorfik

Objek mempunyai beberapa makna polimorfisme dalam ECMAScript.

Sebagai contoh, fungsi boleh digunakan pada objek yang berbeza, sama seperti sifat objek asli (kerana nilai ditentukan apabila memasuki konteks pelaksanaan):

Salin kod Kod adalah seperti berikut:

ujian fungsi() {
makluman([ini.a, ini.b]);
}

test.call({a: 10, b: 20}); // 10, 20
test.call({a: 100, b: 200}); // 100, 200

var a = 1;
var b = 2;

ujian(); // 1, 2

Walau bagaimanapun, terdapat pengecualian: kaedah Date.prototype.getTime(), yang mengikut piawaian harus sentiasa mempunyai objek tarikh, jika tidak pengecualian akan dilemparkan.
Salin kod Kod adalah seperti berikut:

alert(Date.prototype.getTime.call(new Date())); // masa
alert(Date.prototype.getTime.call(new String(''))); // TypeError

Apa yang dipanggil polimorfisme parameter apabila mentakrifkan fungsi adalah setara dengan semua jenis data, kecuali ia menerima parameter polimorfik (seperti kaedah pengisihan .sort tatasusunan dan parameternya - fungsi pengisihan polimorfik). Dengan cara ini, contoh di atas juga boleh dianggap sejenis polimorfisme parametrik.

Kaedah dalam prototaip boleh ditakrifkan sebagai kosong dan semua objek yang dicipta harus mentakrifkan semula (melaksanakan) kaedah ini (iaitu "satu antara muka (tandatangan), berbilang pelaksanaan").

Polymorphism berkaitan dengan jenis Itik yang kami nyatakan di atas: iaitu jenis dan kedudukan objek dalam hierarki tidak begitu penting, tetapi jika ia mempunyai semua ciri yang diperlukan, ia boleh diterima dengan mudah (iaitu antara muka biasa adalah penting , pelaksanaan boleh menjadi pelbagai).

Ekapsulasi

Selalunya terdapat salah tanggapan tentang pengkapsulan. Dalam bahagian ini kita membincangkan beberapa gula sintaksis dalam pelaksanaan OOP - juga dikenali sebagai pengubah: Dalam kes ini, kita akan membincangkan beberapa kemudahan "gula" dalam pelaksanaan OOP - pengubah yang terkenal: peribadi, dilindungi dan awam ( Atau dikenali sebagai akses objek tahap atau pengubah suai akses).

Di sini saya ingin mengingatkan anda tentang tujuan utama enkapsulasi: enkapsulasi ialah penambahan abstrak, bukan "penggodam berniat jahat" tersembunyi yang menulis sesuatu terus ke dalam kelas anda.

Ini adalah kesilapan besar: gunakan sorok demi menyembunyikan.

Tahap akses (peribadi, dilindungi dan awam) telah dilaksanakan dalam banyak program berorientasikan objek untuk memudahkan pengaturcaraan (gula sintaks yang sangat mudah), menerangkan dan membina sistem dengan lebih abstrak.

Ini boleh dilihat dalam beberapa pelaksanaan (seperti Python dan Ruby yang telah disebutkan). Di satu pihak (dalam Python), atribut __private_protected ini (dinamakan melalui konvensyen garis bawah) tidak boleh diakses dari luar. Python, sebaliknya, boleh diakses dari luar dengan peraturan khas (_ClassName__field_name).

Salin kod Kod adalah seperti berikut:

kelas A(objek):

Def __init__(self):
        self.public = 10
        diri.__swasta = 20

Def get_private(self):
          pulangkan sendiri.__peribadi

# di luar:

a = A() # Contoh A

print(a.public) # OK, 30
print(a.get_private()) # OK, 20
print(a.__private) # Gagal kerana ia hanya boleh digunakan dalam A

# Tetapi dalam Python, ia boleh diakses melalui peraturan khas

print(a._A__private) # OK, 20

Dalam Ruby: Di satu pihak, ia mempunyai keupayaan untuk menentukan ciri peribadi dan dilindungi Sebaliknya, terdapat juga kaedah khas (seperti instance_variable_get, instance_variable_set, send, dll.) untuk mendapatkan data terkapsul.

Salin kod Kod adalah seperti berikut:

kelas A

def mulakan
@a = 10
tamat

def public_method
kaedah_peribadi(20)
tamat

persendirian

def private_method(b)
Kembalikan @a b
tamat

tamat

a = A.baru # Contoh baharu

a.kaedah_awam # OK, 30

a.a # Kegagalan, @a - ialah pembolehubah contoh peribadi

# "private_method" adalah peribadi dan hanya boleh diakses dalam kelas A

a.kaedah_pribadi # Ralat

# Tetapi ada nama kaedah metadata khas yang boleh mendapatkan data

a.send(:private_method, 20) # OK, 30
a.instance_variable_get(:@a) # OK, 10

Sebab utamanya ialah pengaturcara sendiri ingin mendapatkan data yang dikapsulkan (perhatikan bahawa saya secara khusus tidak menggunakan data "tersembunyi"). Jika data ini berubah secara tidak betul dalam beberapa cara atau mempunyai sebarang ralat, tanggungjawab penuh terletak pada pengaturcara, tetapi bukan hanya "ralat menaip" atau "hanya menukar beberapa medan". Tetapi jika ini berlaku dengan kerap, ia adalah tabiat dan gaya pengaturcaraan yang sangat buruk, kerana ia biasanya bernilai menggunakan API awam untuk "bercakap" dengan objek.

Untuk mengulangi, tujuan asas enkapsulasi adalah untuk mengabstrak pengguna data tambahan, bukan untuk menghalang penggodam daripada menyembunyikan data. Lebih serius, enkapsulasi tidak menggunakan persendirian untuk mengubah suai data untuk mencapai keselamatan perisian.

Mengenkapsulkan objek tambahan (sebahagian) Kami menggunakan kos minimum, penyetempatan dan perubahan ramalan untuk memberikan kebolehlaksanaan untuk perubahan tingkah laku dalam antara muka awam.

Selain itu, tujuan penting kaedah penetap adalah untuk mengabstraksi pengiraan kompleks. Sebagai contoh, penetap element.innerHTML - pernyataan abstrak - "HTML di dalam elemen ini sekarang ialah kandungan berikut", dan fungsi penetap dalam sifat innerHTML akan menjadi sukar untuk dikira dan diperiksa. Dalam kes ini, masalah kebanyakannya melibatkan abstraksi, tetapi enkapsulasi juga berlaku.

Konsep enkapsulasi bukan sahaja berkaitan dengan OOP. Sebagai contoh, ia boleh menjadi fungsi mudah yang hanya merangkum pelbagai pengiraan, menjadikannya abstrak (tidak perlu untuk pengguna mengetahui, sebagai contoh, bagaimana fungsi Math.round(...) dilaksanakan, pengguna hanya memanggil ia). Ia adalah sejenis enkapsulasi. Perhatikan bahawa saya tidak mengatakan ia adalah "peribadi, dilindungi dan awam".

Versi semasa spesifikasi ECMAScript tidak mentakrifkan pengubah suai peribadi, dilindungi dan awam.

Walau bagaimanapun, dalam amalan adalah mungkin untuk melihat sesuatu yang dinamakan "Mock JS Encapsulation". Secara amnya konteks ini bertujuan untuk digunakan (sebagai peraturan, pembina itu sendiri). Malangnya, "mimikri" ini sering dilaksanakan dan pengaturcara boleh menghasilkan tetapan entiti pseudo-benar-benar bukan abstrak "kaedah getter/setter" (saya ulangi, ia adalah salah):

Salin kod Kod adalah seperti berikut:

fungsi A() {

var _a; // "peribadi" a

this.getA = fungsi _getA() {
Kembali _a;
};

this.setA = fungsi _setA(a) {
_a = a;
};

}

var a = new A();

a.setA(10);
alert(a._a); // undefined, "pribadi"
makluman(a.getA()); // 10

Jadi, semua orang faham bahawa untuk setiap objek yang dicipta, kaedah getA/setA juga dicipta, yang juga merupakan sebab peningkatan dalam ingatan (berbanding dengan definisi prototaip). Walaupun, secara teori objek boleh dioptimumkan dalam kes pertama.

Selain itu, beberapa artikel JavaScript sering menyebut konsep "kaedah peribadi".

Walau bagaimanapun, dalam beberapa kes ia boleh dibuat dalam pembina, kerana JS ialah bahasa ideologi - objek boleh berubah sepenuhnya dan mempunyai ciri unik (di bawah keadaan tertentu dalam pembina, sesetengah objek boleh mendapatkan kaedah tambahan manakala yang lain tidak ).

Selain itu, dalam JavaScript, jika enkapsulasi masih disalahtafsirkan sebagai pemahaman yang menghalang penggodam berniat jahat daripada menulis nilai tertentu secara automatik dan bukannya menggunakan kaedah penetap, maka apa yang dipanggil "tersembunyi" dan " "peribadi" sebenarnya tidak terlalu "tersembunyi", sesetengah pelaksanaan boleh mendapatkan nilai pada rantai skop yang berkaitan (dan sepadan dengan semua objek pembolehubah) dengan memanggil konteks ke fungsi eval (boleh diuji pada SpiderMonkey1.7).

Salin kod Kod adalah seperti berikut:

eval('_a = 100', a.getA); // atau a.setA, kerana dua kaedah "_a" berada pada [[Skop]]
a.getA(); // 100

Sebagai alternatif, pelaksanaan membenarkan akses terus kepada objek aktif (seperti Rhino), dan nilai pembolehubah dalaman boleh diubah dengan mengakses sifat objek yang sepadan:

Salin kod Kod adalah seperti berikut:

// Badak
var foo = (fungsi () {
var x = 10; // "peribadi"
kembalikan fungsi () {
Cetak(x);
};
})();
foo(); // 10
foo.__ibu bapa__.x = 20;
foo(); // 20

Kadangkala data "peribadi" dan "dilindungi" dicapai dalam JavaScript dengan memberi awalan pembolehubah dengan garis bawah (tetapi berbanding Python, ini hanyalah konvensyen penamaan):

var _myPrivateData = 'testString';
Ia sering digunakan untuk menyertakan konteks pelaksanaan dalam kurungan, tetapi untuk data tambahan sebenar, ia tidak berkaitan secara langsung dengan objek, tetapi hanya mudah untuk mengabstrakkan daripada API luaran:

Salin kod Kod adalah seperti berikut:

(fungsi () {

// Mulakan konteks

})();

Warisan berbilang

Pewarisan berbilang ialah gula sintaksis yang sangat mudah untuk meningkatkan penggunaan semula kod (jika kita boleh mewarisi satu kelas pada satu masa, mengapa kita tidak boleh mewarisi 10 pada satu masa?). Walau bagaimanapun, disebabkan oleh beberapa kekurangan warisan berbilang, ia tidak menjadi popular dalam pelaksanaan.

ECMAScript tidak menyokong berbilang warisan (iaitu hanya satu objek boleh digunakan sebagai prototaip langsung), walaupun bahasa pengaturcaraan nenek moyangnya mempunyai keupayaan sedemikian. Tetapi dalam beberapa pelaksanaan (seperti SpiderMonkey) menggunakan __noSuchMethod__ boleh digunakan untuk mengurus penjadualan dan perwakilan dan bukannya rantai prototaip.

Bancuhan

Mixin ialah cara yang mudah untuk menggunakan semula kod. Mixin telah dicadangkan sebagai alternatif kepada pelbagai warisan. Setiap elemen individu ini boleh dicampur dengan mana-mana objek untuk melanjutkan fungsinya (jadi objek juga boleh dicampur dengan berbilang Mixin). Spesifikasi ECMA-262-3 tidak mentakrifkan konsep "Mixins", tetapi mengikut takrifan Mixins dan ECMAScript mempunyai objek boleh ubah secara dinamik, tiada halangan untuk hanya memanjangkan ciri menggunakan Mixins.

Contoh biasa:

Salin kod Kod adalah seperti berikut:

// pembantu untuk pembesaran
Object.extend = fungsi (destinasi, sumber) {
untuk (harta dalam sumber) jika (sumber.hasOwnProperty(harta)) {
destinasi[harta] = sumber[harta];
}
Destinasi pulang;
};

var X = {a: 10, b: 20};
var Y = {c: 30, d: 40};

Object.extend(X, Y); // campurkan Y menjadi X
makluman([X.a, X.b, X.c, X.d]); 10, 20, 30, 40

Sila ambil perhatian bahawa saya menggunakan takrifan ini ("mixin", "mix") dalam tanda petikan yang disebut dalam ECMA-262-3 Tiada konsep sedemikian dalam spesifikasi, dan ia bukan campuran tetapi biasa digunakan untuk memanjangkan objek dengan ciri-ciri baharu. (Konsep mixin dalam Ruby ditakrifkan secara rasmi. Mixins mencipta rujukan kepada modul yang mengandungi dan bukannya menyalin semua sifat modul ke modul lain - sebenarnya: mencipta objek tambahan (prototaip) untuk perwakilan. ).

Sifat

Sifat adalah serupa dalam konsep kepada campuran, tetapi ia mempunyai banyak fungsi (mengikut definisi, kerana campuran boleh digunakan, ia tidak boleh mengandungi keadaan, kerana ia boleh menyebabkan konflik penamaan). Menurut ECMAScript, Traits dan mixin mengikut prinsip yang sama, jadi spesifikasi tidak mentakrifkan konsep "Traits".

Antara Muka

Antara muka yang dilaksanakan dalam beberapa OOP adalah serupa dengan campuran dan sifat. Walau bagaimanapun, berbeza dengan campuran dan sifat, antara muka memaksa kelas pelaksana untuk melaksanakan gelagat tandatangan kaedah mereka.

Antara muka boleh dianggap sepenuhnya sebagai kelas abstrak. Walau bagaimanapun, berbanding dengan kelas abstrak (kaedah dalam kelas abstrak hanya boleh melaksanakan sebahagian daripada kaedah, dan bahagian lain masih ditakrifkan sebagai tandatangan), warisan hanya boleh mewarisi kelas asas tunggal, tetapi boleh mewarisi berbilang antara muka. antara muka (pelbagai Campuran) boleh dilihat sebagai alternatif kepada pewarisan berbilang.

Piawaian ECMA-262-3 tidak mentakrifkan konsep "antara muka" mahupun konsep "kelas abstrak". Walau bagaimanapun, sebagai tiruan, adalah mungkin untuk melaksanakan objek dengan kaedah "kosong" (atau pengecualian yang dilemparkan dalam kaedah kosong untuk memberitahu pembangun bahawa kaedah ini perlu dilaksanakan).

Kombinasi objek

Komposisi objek juga merupakan salah satu teknologi penggunaan semula kod dinamik. Komposisi objek berbeza daripada warisan yang sangat fleksibel kerana ia melaksanakan perwakilan yang boleh berubah secara dinamik. Dan ini juga berdasarkan asas prototaip yang ditugaskan. Selain prototaip boleh ubah secara dinamik, objek boleh mengagregat objek untuk perwakilan (mencipta gabungan sebagai hasil - pengagregatan) dan seterusnya menghantar mesej kepada objek yang mewakilkan kepada perwakilan. Ini boleh dilakukan dengan lebih daripada dua perwakilan, kerana sifat dinamiknya bermakna ia boleh berubah semasa masa jalan.

Contoh __noSuchMethod__ yang telah disebutkan melakukan ini, tetapi mari kita tunjukkan juga cara menggunakan perwakilan secara eksplisit:

Contohnya:

Salin kod Kod adalah seperti berikut:

var _delegate = {
foo: fungsi () {
alert('_delegate.foo');
}
};

var agregat = {

perwakilan: _delegate,

foo: fungsi () {
Kembalikan this.delegate.foo.call(this);
}

};

aggregate.foo(); // delegate.foo

aggregate.delegate = {
foo: fungsi () {
alert('foo daripada perwakilan baharu');
}
};

aggregate.foo(); // foo daripada wakil baharu

Hubungan objek ini dipanggil "mempunyai-a", dan penyepaduan ialah hubungan "adalah-a".

Disebabkan kekurangan komposisi eksplisit (fleksibiliti berbanding warisan), menambah kod perantaraan juga tidak mengapa.

Ciri AOP

Sebagai fungsi berorientasikan aspek, anda boleh menggunakan penghias fungsi. Spesifikasi ECMA-262-3 tidak mentakrifkan dengan jelas konsep "penghias fungsi" (berbanding dengan Python, di mana istilah ini ditakrifkan secara rasmi). Walau bagaimanapun, fungsi dengan parameter berfungsi boleh dihiasi dan diaktifkan dalam aspek tertentu (dengan menggunakan apa yang dipanggil cadangan):

Contoh penghias paling mudah:

Salin kod Kod adalah seperti berikut:

function checkDecorator(originalFunction) {
kembalikan fungsi () {
Jika (fooBar != 'ujian') {
alert('parameter salah');
Kembalikan palsu;
}
Kembalikan originalFunction();
};
}

ujian fungsi() {
alert('fungsi ujian');
}

var testWithCheck = checkDecorator(ujian);
var fooBar = palsu;

test(); // 'fungsi ujian'
testWithCheck(); // 'parameter salah'

fooBar = 'ujian';
test(); // 'fungsi ujian'
testWithCheck(); // 'fungsi ujian'

Kesimpulan

Dalam artikel ini, kami telah menjelaskan pengenalan OOP (saya harap maklumat ini berguna kepada anda), dan dalam bab seterusnya kami akan meneruskan pelaksanaan ECMAScript untuk pengaturcaraan berorientasikan objek.

Kenyataan:
Kandungan artikel ini disumbangkan secara sukarela oleh netizen, dan hak cipta adalah milik pengarang asal. Laman web ini tidak memikul tanggungjawab undang-undang yang sepadan. Jika anda menemui sebarang kandungan yang disyaki plagiarisme atau pelanggaran, sila hubungi admin@php.cn