Saya sedang belajar OOP di C ++ dan, meskipun saya sadar akan definisi dari ketiga konsep ini, saya tidak dapat benar-benar menyadari kapan atau bagaimana menggunakannya.
Mari kita gunakan kelas ini sebagai contoh:
class Person{
private:
string name;
int age;
public:
Person(string p1, int p2){this->name=p1; this->age=p2;}
~Person(){}
void set_name (string parameter){this->name=parameter;}
void set_age (int parameter){this->age=parameter;}
string get_name (){return this->name;}
int get_age (){return this->age;}
};
1. Singleton
BAGAIMANA pembatasan kelas untuk hanya memiliki satu objek bekerja?
BISAKAH Anda mendesain kelas yang HANYA memiliki 2 instance? Atau mungkin 3?
KAPAN SAJA menggunakan singleton direkomendasikan / diperlukan? Apakah ini praktik yang baik?
Sejauh yang saya tahu, jika hanya ada satu fungsi virtual murni, kelas menjadi abstrak. Jadi, tambahkan
virtual void print ()=0;
akan melakukannya, kan?
MENGAPA Anda membutuhkan kelas yang objeknya tidak diperlukan?
3. Kata Pengantar
Jika sebuah antarmuka adalah kelas abstrak di mana semua metode adalah fungsi virtual murni, maka
APA perbedaan utama antara keduanya?
Terima kasih sebelumnya!
Jawaban:
1. Singleton
Anda membatasi jumlah instance karena konstruktor akan bersifat pribadi yang berarti hanya metode statis yang dapat membuat instance dari kelas itu (ada trik kotor lainnya sebenarnya untuk mencapai itu tetapi jangan terbawa).
Membuat kelas yang hanya memiliki 2 atau 3 instance sangat layak. Anda harus menggunakan singleton setiap kali Anda merasa perlu memiliki hanya satu instance dari kelas itu di seluruh sistem. Itu biasanya terjadi pada kelas yang memiliki perilaku 'manajer'.
Jika Anda ingin mempelajari lebih lanjut tentang Lajang Anda dapat mulai di Wikipedia dan terutama untuk C ++ di posting ini .
Pasti ada beberapa hal baik dan buruk tentang pola ini, tetapi diskusi ini berada di tempat lain.
2. Kelas Abstrak
Ya itu betul. Hanya satu metode virtual yang akan menandai kelas sebagai abstrak.
Anda akan menggunakan kelas-kelas semacam itu ketika Anda memiliki hierarki kelas yang lebih besar di mana kelas-kelas top seharusnya tidak benar-benar dipakai.
Mari kita anggap Anda mendefinisikan kelas Mamalia dan kemudian mewarisinya ke Dog and Cat. Jika Anda memikirkannya, tidak ada gunanya memiliki contoh murni mamalia karena Anda pertama kali perlu tahu jenis mamalia apa itu sebenarnya.
Berpotensi ada metode yang disebut MakeSound () yang hanya akan masuk akal di kelas-kelas yang diwariskan tetapi tidak ada suara umum yang dapat dibuat oleh semua mamalia (Ini hanya contoh tidak mencoba membuat kasus untuk suara mamalia di sini).
Jadi itu berarti mamalia harus menjadi kelas abstrak karena itu akan memiliki beberapa perilaku umum diimplementasikan untuk semua mamalia tetapi itu tidak benar-benar seharusnya dipakai. Itulah konsep dasar di balik kelas abstrak tetapi pasti ada lebih banyak hal yang harus Anda pelajari.
3. Antarmuka
Tidak ada antarmuka murni di C ++ dalam arti yang sama yang Anda miliki di Java atau C #. Satu-satunya cara untuk membuatnya adalah dengan memiliki kelas abstrak murni yang meniru sebagian besar perilaku yang Anda inginkan dari sebuah antarmuka.
Pada dasarnya perilaku yang Anda cari adalah menentukan kontrak di mana objek lain dapat berinteraksi dengan tanpa peduli tentang implementasi yang mendasarinya. Ketika Anda membuat kelas murni abstrak itu berarti semua implementasi milik tempat lain sehingga tujuan kelas itu hanya tentang kontrak yang ditetapkannya. Ini adalah konsep yang sangat kuat dalam OO dan Anda harus melihat lebih dalam.
Anda dapat membaca tentang spesifikasi antarmuka untuk C # di MSDN untuk memiliki ide yang lebih baik:
http://msdn.microsoft.com/en-us/library/ms173156.aspx
C ++ akan memberikan jenis perilaku yang sama dengan memiliki kelas abstrak murni.
sumber
Kebanyakan orang sudah menjelaskan apa itu kelas lajang / abstrak. Semoga, saya akan memberikan perspektif yang sedikit berbeda dan memberikan beberapa contoh praktis.
Singletons - Ketika Anda ingin semua kode panggilan menggunakan satu instance variabel, untuk alasan apa pun, Anda memiliki opsi berikut:
Dari semua opsi jahat dan buruk di luar sana, jika Anda memang membutuhkan data global, singleton adalah pendekatan yang JAUH lebih baik daripada salah satu dari dua sebelumnya. Ini juga memungkinkan Anda membuka opsi jika besok Anda berubah pikiran dan memutuskan untuk menggunakan inversi kontrol alih-alih memiliki data global.
Jadi di mana Anda akan menggunakan singleton? Berikut beberapa contoh:
Logging - jika Anda ingin seluruh proses memiliki satu log, Anda bisa membuat objek log dan menyebarkannya ke mana-mana. Tetapi bagaimana jika Anda memiliki 100.000.000 baris kode aplikasi lama? modifikasi semuanya? Atau Anda bisa langsung memperkenalkan yang berikut dan mulai menggunakannya di mana saja sesuka Anda:
Tembolok koneksi server - Ini adalah sesuatu yang harus saya perkenalkan dalam aplikasi kami. Basis kode kami, dan ada banyak, digunakan untuk terhubung ke server kapan pun diinginkan. Sebagian besar waktu ini baik-baik saja, kecuali ada latensi dalam jaringan. Kami membutuhkan solusi dan mendesain ulang aplikasi berusia 10 tahun tidak benar-benar di atas meja. Saya menulis CServerConnectionManager singleton. Kemudian saya mencari-cari kode dan mengganti panggilan CoCreateInstanceWithAuth dengan panggilan tanda tangan identik yang memanggil kelas saya. Sekarang setelah koneksi upaya pertama di-cache dan sisa waktu "menghubungkan" upaya instan. Ada yang bilang lajang itu jahat. Saya katakan mereka menyelamatkan pantat saya.
Untuk debugging, kita sering menemukan tabel global running object sangat berguna. Kami memiliki beberapa kelas yang ingin kami ikuti. Mereka semua berasal dari kelas dasar yang sama. Selama instantiasi, mereka menyebut tabel objek singleton dan mendaftar sendiri. Ketika mereka dihancurkan, mereka tidak mendaftar. Saya bisa berjalan ke mesin apa pun, melampirkan ke proses dan membuat daftar objek yang berjalan. Sudah di produk selama lebih dari setengah dekade dan saya tidak pernah merasa bahwa kita pernah membutuhkan 2 tabel objek "global".
Kami memiliki beberapa kelas utilitas parser string yang relatif kompleks yang mengandalkan ekspresi reguler. Kelas ekspresi reguler perlu diinisialisasi sebelum dapat melakukan kecocokan. Inisialisasi agak mahal karena saat itulah FSM dihasilkan berdasarkan string parse. Namun setelah itu, kelas ekspresi reguler dapat diakses dengan aman oleh 100 utas karena sekali dibangun FSM tidak pernah berubah. Kelas parser ini secara internal menggunakan lajang untuk memastikan inisialisasi ini hanya terjadi sekali. Ini secara signifikan meningkatkan kinerja dan tidak pernah menyebabkan masalah karena "lajang jahat".
Setelah mengatakan semua ini, Anda perlu mengingat kapan dan di mana harus menggunakan lajang. 9 dari 10 kali ada solusi yang lebih baik dan tentu saja, Anda harus menggunakannya. Namun, ada kalanya singleton benar-benar pilihan desain yang tepat.
Topik selanjutnya ... antarmuka dan kelas abstrak. Pertama seperti yang disebutkan orang lain, antarmuka adalah kelas abstrak tetapi lebih dari itu dengan menegakkan bahwa itu benar-benar tidak ada implementasi. Dalam beberapa bahasa kata kunci antarmuka adalah bagian dari bahasa. Dalam C ++ kita cukup menggunakan kelas abstrak. Microsoft VC ++ mengambil satu langkah untuk mendefinisikan ini di suatu tempat secara internal:
... jadi Anda masih bisa menggunakan kata kunci antarmuka (itu bahkan akan disorot sebagai kata kunci 'nyata'), tetapi sejauh menyangkut kompiler yang sebenarnya, itu hanya sebuah struct.
Jadi, di mana Anda akan menggunakan ini? Mari kita kembali ke contoh saya dari tabel objek yang berjalan. Katakanlah kelas dasar memiliki ...
cetakan virtual void () = 0;
Ada kelas abstrak Anda. Kelas yang menggunakan tabel objek runtime semua akan berasal dari kelas dasar yang sama. Kelas dasar berisi kode umum untuk mendaftar / tidak mendaftar. Tapi itu tidak akan pernah dipakai dengan sendirinya. Sekarang saya dapat memiliki turunan kelas (mis. Permintaan, pendengar, objek koneksi klien ...), masing-masing akan menerapkan print () sehingga ketika melampirkan ke proses dan bertanya, apa yang sedang berjalan, setiap objek akan melaporkan keadaannya sendiri.
Contoh kelas abstrak / antarmuka tidak terhitung dan Anda pasti menggunakan (atau harus menggunakan) mereka jauh, lebih sering bahwa Anda akan menggunakan lajang. Singkatnya, mereka memungkinkan Anda untuk menulis kode yang bekerja dengan tipe dasar dan tidak terikat dengan implementasi aktual. Ini memungkinkan Anda untuk memodifikasi implementasi nanti tanpa harus mengubah terlalu banyak kode.
Ini contoh lain. Katakanlah saya memiliki kelas yang mengimplementasikan logger, CLog. Kelas ini menulis ke file di disk lokal. Saya mulai menggunakan kelas ini dalam 100.000 baris kode lama saya. Seluruh tempat. Hidup itu baik sampai seseorang berkata, hei mari kita menulis ke database, bukan file. Sekarang saya membuat kelas baru, sebut saja CDbLog dan menulis ke database. Bisakah Anda membayangkan kerumitan melewati 100.000 baris dan mengubah segalanya dari CLog ke CDbLog? Atau, saya dapat memiliki:
Jika semua kode menggunakan antarmuka ILogger, yang harus saya ubah adalah implementasi internal CLogFactory :: GetLog (). Sisa kode hanya akan bekerja secara otomatis tanpa saya harus mengangkat jari.
Untuk informasi lebih lanjut tentang antarmuka dan desain OO yang baik, saya sangat merekomendasikan Prinsip, Pola, dan Praktek Agile Paman Bob di C # . Buku ini dipenuhi dengan contoh-contoh yang menggunakan abstraksi dan memberikan penjelasan bahasa yang jelas tentang segalanya.
sumber
Tidak pernah. Lebih buruk dari itu, mereka benar-benar menyebalkan, sehingga membuat kesalahan ini sekali dapat menghantui Anda selama bertahun-tahun.
Perbedaan antara kelas abstrak dan antarmuka sama sekali tidak ada dalam C ++. Anda biasanya memiliki antarmuka untuk menentukan beberapa perilaku kelas turunan, tetapi tanpa harus menentukan semuanya. Ini membuat kode Anda lebih fleksibel, karena Anda dapat menukar kelas mana saja yang memenuhi spesifikasi yang lebih terbatas. Antarmuka run-time digunakan ketika Anda membutuhkan abstraksi run-time.
sumber
Singleton berguna ketika Anda tidak ingin banyak salinan dari objek tertentu, hanya boleh ada satu instance dari kelas itu - itu digunakan untuk objek yang mempertahankan keadaan global, harus berurusan dengan kode non-reentrant dalam beberapa cara dll.
Singleton yang memiliki jumlah tetap 2 atau lebih instance adalah multiton , pikirkan pooling koneksi database dll.
Antarmuka menentukan API yang didefinisikan dengan baik yang membantu memodelkan interaksi antara objek. Dalam beberapa kasus, Anda bisa memiliki sekelompok kelas yang memiliki beberapa fungsi umum - jika demikian, alih-alih menduplikasi dalam implementasi, Anda bisa menambahkan definisi metode ke antarmuka mengubahnya menjadi kelas abstrak .
Anda bahkan dapat memiliki kelas abstrak di mana semua metode diimplementasikan, tetapi Anda menandainya sebagai abstrak untuk menunjukkan itu tidak boleh digunakan apa adanya tanpa subklasifikasi.
Catatan: Antarmuka & kelas abstrak tidak jauh berbeda di dunia C ++ dengan banyak pewarisan dll, tetapi memiliki arti yang berbeda di Java et al.
sumber
Jika Anda berhenti untuk memikirkannya, itu semua tentang polimorfisme. Anda ingin dapat menulis sepotong kode sekali yang dapat melakukan lebih dari satu pemikiran tergantung pada apa yang Anda lewati.
Katakanlah kita memiliki fungsi seperti kode Python berikut:
Hal yang baik tentang fungsi ini adalah ia akan dapat menangani daftar objek apa saja, asalkan objek tersebut mengimplementasikan metode "printToScreen". Anda dapat melewatinya daftar widget senang, daftar widget sedih atau bahkan daftar yang memiliki campuran dari mereka dan fungsi foo masih akan dapat melakukan hal itu dengan benar.
Kami merujuk pada pembatasan jenis ini agar seperangkat metode diimplementasikan (dalam hal ini, printToScreen) sebagai antarmuka dan objek yang mengimplementasikan semua metode dikatakan mengimplementasikan antarmuka.
Jika kita berbicara tentang bahasa yang dinamis, seperti bebek seperti Python, pada dasarnya kita akan selesai sekarang. Namun, sistem tipe statis C ++ menuntut agar kita memberikan kelas pada objek dalam fungsi kita dan itu hanya akan dapat bekerja dengan subkelas dari kelas awal itu.
Dalam kasus kami, satu-satunya alasan kelas Printable ada adalah untuk memberi tempat bagi metode printToScreen. Karena tidak ada implementasi bersama antara kelas yang menerapkan metode printToScreen, masuk akal untuk membuat Printable menjadi kelas abstrak yang hanya digunakan sebagai cara untuk mengelompokkan kelas yang serupa dalam hierarki umum.
Dalam C ++ konsep kelas absctract dan antarmuka agak kabur. Jika Anda ingin mendefinisikannya dengan lebih baik, kelas abstrak adalah apa yang Anda pikirkan, sementara antarmuka biasanya berarti lebih umum, gagasan lintas-bahasa dari sekumpulan metode yang terlihat oleh suatu objek. (Meskipun beberapa bahasa, seperti Java, menggunakan istilah antarmuka untuk merujuk sesuatu yang lebih langsung seperti kelas dasar abstrak)
Pada dasarnya, kelas konkret menentukan bagaimana objek diimplementasikan, sedangkan kelas abstrak menentukan bagaimana mereka berinteraksi dengan sisa kode. Untuk membuat fungsi Anda lebih polymorphi, Anda harus mencoba menerima pointer ke superclass abstrak setiap kali masuk akal untuk melakukannya.
Adapun Singletons, mereka benar-benar sangat tidak berguna, karena mereka sering dapat digantikan oleh hanya sekelompok metode statis atau fungsi lama biasa. Namun, kadang-kadang Anda memiliki semacam batasan yang memaksa Anda untuk menggunakan objek, meskipun Anda tidak benar-benar ingin menggunakannya, jadi derai tunggal sesuai.
BTW, beberapa orang mungkin berkomentar bahwa kata "antarmuka" memiliki arti tertentu dalam bahasa Jawa. Saya pikir lebih baik tetap dengan definisi yang lebih umum untuk saat ini.
sumber
Antarmuka
Sulit untuk memahami tujuan dari alat yang memecahkan masalah yang belum pernah Anda miliki. Saya tidak mengerti antarmuka untuk sementara waktu setelah saya mulai pemrograman. Kami akan mengerti apa yang mereka lakukan, tetapi saya tidak tahu mengapa Anda ingin menggunakannya.
Inilah masalahnya - Anda tahu apa yang ingin Anda lakukan, tetapi Anda memiliki banyak cara untuk melakukannya, atau Anda dapat mengubah cara Anda melakukannya nanti. Alangkah baiknya jika Anda bisa memainkan peran sebagai manajer yang tidak mengerti - menggonggong beberapa pesanan dan mendapatkan hasil yang Anda inginkan tanpa peduli bagaimana hal itu dilakukan.
Katakanlah Anda memiliki situs web kecil mungil, dan Anda menyimpan semua informasi pengguna Anda dalam file csv. Bukan solusi yang paling canggih, tetapi berfungsi cukup baik untuk menyimpan detail pengguna ibumu. Kemudian, situs Anda lepas landas dan Anda memiliki 10.000 pengguna. Mungkin sudah waktunya untuk menggunakan database yang tepat.
Jika Anda pintar pada awalnya, Anda akan melihat ini datang dan tidak membuat panggilan untuk menyimpan ke csv secara langsung. Alih-alih, Anda akan memikirkan apa yang perlu Anda lakukan, tidak peduli bagaimana penerapannya. Katakanlah
store()
danretrieve()
. Anda membuatPersister
antarmuka dengan metode abstrak untukstore()
danretrieve()
dan membuatCsvPersister
subkelas yang benar-benar mengimplementasikan metode tersebut.Kemudian, Anda dapat membuat
DbPersister
yang mengimplementasikan penyimpanan aktual dan pengambilan data yang sama sekali berbeda dari bagaimana kelas csv Anda melakukannya.Yang hebat adalah, yang harus Anda lakukan sekarang adalah perubahan
untuk
dan kemudian kamu selesai. Panggilan Anda ke
prst.store()
danprst.retrieve()
semua masih akan berfungsi, mereka hanya ditangani secara berbeda "di belakang layar".Sekarang, Anda masih harus membuat implementasi cvs dan db, jadi Anda belum mengalami kemewahan menjadi bos. Manfaat nyata terlihat ketika Anda menggunakan antarmuka yang dibuat orang lain. Jika orang lain cukup baik untuk membuat
CsvPersister()
danDbPersister()
sudah, maka Anda hanya perlu memilih satu dan memanggil metode yang diperlukan. Jika Anda memutuskan untuk menggunakan yang lain nanti, atau dalam proyek lain, Anda sudah tahu cara kerjanya.Saya benar-benar berkarat pada C ++ saya, jadi saya hanya akan menggunakan beberapa contoh pemrograman generik. Kontainer adalah contoh yang bagus tentang bagaimana antarmuka membuat hidup Anda lebih mudah.
Anda dapat memiliki
Array
,LinkedList
,BinaryTree
, dll semua subclass dariContainer
yang memiliki metode sepertiinsert()
,find()
,delete()
.Sekarang ketika menambahkan sesuatu ke tengah daftar tertaut, Anda bahkan tidak perlu tahu apa itu daftar tertaut. Anda cukup menelepon
myLinkedList->insert(4)
dan secara ajaib beralih melalui daftar dan menempelkannya di sana. Sekalipun Anda tahu cara kerja daftar tertaut (yang memang harus Anda lakukan), Anda tidak harus melihat fungsinya yang spesifik, karena Anda mungkin sudah tahu apa yang mereka gunakan dengan menggunakan yang berbedaContainer
sebelumnya.Kelas Abstrak
Kelas abstrak sangat mirip dengan antarmuka (antarmuka baik secara teknis adalah kelas abstrak, tetapi di sini yang saya maksud adalah kelas dasar yang memiliki beberapa metode mereka disempurnakan.
Katakanlah Anda sedang membuat gim, dan Anda perlu mendeteksi kapan musuh berada dalam jarak pukul pemain. Anda bisa membuat kelas dasar
Enemy
yang memiliki metodeinRange()
. Meskipun ada banyak hal tentang musuh yang berbeda, metode yang digunakan untuk memeriksa jangkauan mereka konsisten. Oleh karena ituEnemy
kelas Anda akan memiliki metode menyempurnakan untuk memeriksa rentang, tetapi metode virtual murni untuk hal-hal lain yang tidak memiliki kesamaan di antara tipe musuh.Hal yang menyenangkan tentang ini adalah jika Anda mengacaukan kode deteksi jangkauan atau ingin mengubah, Anda hanya perlu mengubahnya di satu tempat.
Tentu saja ada banyak alasan lain untuk antarmuka dan kelas dasar abstrak, tetapi itulah beberapa alasan mengapa Anda mungkin menggunakannya.
Lajang
Saya menggunakannya sesekali, dan saya tidak pernah dibakar oleh mereka. Itu bukan untuk mengatakan bahwa mereka tidak akan menghancurkan hidup saya di beberapa titik, berdasarkan pengalaman orang lain.
Berikut ini adalah diskusi yang bagus tentang keadaan global dari beberapa orang yang lebih berpengalaman dan waspada: Mengapa Negara Global begitu Jahat?
sumber
Di dunia hewan ada berbagai hewan yang merupakan mamalia. Di sini mamalia adalah kelas dasar dan berbagai hewan berasal darinya.
Pernahkah Anda melihat mamalia lewat? Ya, berkali-kali saya yakin - namun mereka semua adalah jenis mamalia bukan?
Anda belum pernah melihat sesuatu yang secara harfiah hanya mamalia. Mereka semua adalah jenis mamalia.
Mamalia kelas diperlukan untuk mendefinisikan berbagai karakteristik dan kelompok tetapi tidak ada sebagai entitas fisik.
Oleh karena itu, ini adalah kelas dasar abstrak.
Bagaimana mamalia bergerak? Apakah mereka berjalan, berenang, terbang dll?
Tidak ada cara untuk mengetahui pada tingkat mamalia tetapi semua mamalia harus bergerak entah bagaimana (katakanlah ini adalah hukum biologis untuk menjadikan contoh lebih mudah).
Oleh karena itu MoveAround () adalah fungsi virtual karena setiap mamalia yang berasal dari kelas ini harus dapat mengimplementasikannya secara berbeda.
Namun, karena setiap mamalia HARUS mendefinisikan MoveAround karena semua mamalia harus bergerak dan tidak mungkin melakukannya di tingkat mamalia. Ini harus diimplementasikan oleh semua kelas anak tetapi tidak ada artinya di kelas dasar.
Karena itu MoveAround adalah fungsi virtual murni.
Jika Anda memiliki seluruh kelas yang memungkinkan aktivitas tetapi tidak dapat menentukan di tingkat atas bagaimana hal itu harus dilakukan maka semua fungsi adalah virtual murni dan ini adalah antarmuka.
Misalnya - jika kita memiliki permainan di mana Anda akan membuat kode robot dan mengirimkannya kepada saya untuk bertarung di medan pertempuran, saya perlu tahu nama fungsi dan prototipe yang harus dipanggil. Saya tidak peduli bagaimana Anda menerapkannya di sisi Anda selama 'antarmuka' jelas. Oleh karena itu saya dapat menyediakan Anda dengan kelas antarmuka yang Anda akan berasal dari untuk menulis robot pembunuh Anda.
sumber