Prinsip terbuka-tertutup

Halo, Habr! Berikut ini adalah terjemahan dari artikel oleh Robert Martin dari Prinsip Terbuka-Tertutup , yang ia terbitkan pada Januari 1996. Artikel ini, secara sederhana, bukan yang terbaru. Tetapi di RuNet, artikel Paman Bob tentang SOLID hanya diceritakan kembali dalam bentuk terpotong, jadi saya pikir terjemahan penuh tidak akan berlebihan.



Saya memutuskan untuk memulai dengan huruf O, karena prinsip keterbukaan-penutupan, sebenarnya, adalah sentral. Di antara hal-hal lain, ada banyak seluk-beluk penting yang perlu diperhatikan:


  • Tidak ada program yang dapat "ditutup" 100%.
  • Pemrograman berorientasi objek (OOP) beroperasi tidak dengan objek fisik dari dunia nyata, tetapi dengan konsep - misalnya, konsep "pemesanan".

Ini adalah artikel pertama di kolom Catatan Teknisi untuk The C ++ Report . Artikel-artikel yang diterbitkan dalam kolom ini akan fokus pada penggunaan C ++ dan OOP dan menyentuh kesulitan dalam pengembangan perangkat lunak. Saya akan mencoba membuat bahan-bahan yang pragmatis dan berguna untuk melatih para insinyur. Untuk dokumentasi desain berorientasi objek dalam artikel ini saya akan menggunakan notasi Buch.


Ada banyak heuristik yang terkait dengan pemrograman berorientasi objek. Misalnya, "semua variabel anggota harus pribadi", atau "variabel global harus dihindari", atau "penentuan jenis saat runtime berbahaya". Apa alasan heuristik seperti itu? Mengapa itu benar? Apakah itu selalu benar? Kolom ini mengeksplorasi prinsip desain yang mendasari heuristik ini - prinsip keterbukaan-penutupan.
Ivar Jacobson berkata: “Semua sistem berubah selama siklus hidup. Ini harus diingat ketika merancang sistem yang memiliki lebih dari satu versi yang diharapkan. " Bagaimana kita bisa merancang suatu sistem sehingga stabil dalam menghadapi perubahan dan memiliki lebih dari satu versi yang diharapkan? Bertrand Meyer memberi tahu kami tentang hal ini pada tahun 1988, ketika prinsip keterbukaan-kedekatan yang terkenal sekarang dirumuskan:


Entitas program (kelas, modul, fungsi, dll.) Harus terbuka untuk ekspansi dan ditutup untuk perubahan.


Jika satu perubahan dalam program ini melibatkan perubahan dalam modul dependen, maka program menampilkan tanda-tanda yang tidak diinginkan dari desain "buruk".


Program menjadi rapuh, tidak fleksibel, tidak dapat diprediksi dan tidak digunakan. Prinsip keterbukaan-kedekatan menyelesaikan masalah-masalah ini dengan cara yang sangat mudah. Dia mengatakan bahwa perlu untuk merancang modul yang tidak pernah berubah . Ketika persyaratan berubah, Anda perlu memperluas perilaku modul tersebut dengan menambahkan kode baru, daripada mengubah kode lama yang sudah berfungsi.


Deskripsi


Modul yang memenuhi prinsip keterbukaan-kedekatan memiliki dua karakteristik utama:


  1. Terbuka untuk ekspansi. Ini berarti bahwa perilaku modul dapat diperluas. Artinya, kita dapat menambahkan perilaku baru ke modul sesuai dengan persyaratan perubahan untuk aplikasi atau untuk memenuhi kebutuhan aplikasi baru.
  2. Ditutup untuk perubahan. Kode sumber modul semacam itu tidak dapat disentuh. Tidak ada yang punya hak untuk mengubahnya.

Tampaknya kedua tanda ini tidak cocok satu sama lain. Cara standar untuk memperluas perilaku modul adalah dengan mengubahnya. Modul yang tidak dapat diubah biasanya dianggap sebagai modul dengan perilaku tetap. Bagaimana dua kondisi yang berlawanan ini dipenuhi?


Kunci dari solusinya adalah abstraksi.


Dalam C ++, menggunakan prinsip-prinsip desain berorientasi objek, dimungkinkan untuk membuat abstraksi tetap yang dapat mewakili serangkaian perilaku yang tidak terbatas.


Abstraksi adalah kelas dasar abstrak, dan serangkaian perilaku yang tidak terbatas diwakili oleh semua kelas penerus yang mungkin. Modul dapat memanipulasi abstraksi. Modul semacam itu ditutup untuk perubahan, karena itu tergantung pada abstraksi tetap. Juga, perilaku modul dapat diperluas dengan menciptakan keturunan abstraksi baru.


Diagram di bawah ini menunjukkan opsi desain sederhana yang tidak memenuhi prinsip keterbukaan-kedekatan. Kedua kelas, Client dan Server , tidak abstrak. Tidak ada jaminan bahwa fungsi yang menjadi anggota kelas Server adalah virtual. Kelas Client menggunakan kelas Server . Jika kami ingin objek kelas Client menggunakan objek server yang berbeda, kami harus mengubah kelas Client untuk merujuk ke kelas server baru.


gambar
Klien tertutup


Dan diagram berikut menunjukkan opsi desain yang sesuai, yang memenuhi prinsip keterbukaan-kedekatan. Dalam hal ini, kelas AbstractServer adalah kelas abstrak, semua fungsi anggota yang virtual. Kelas Client menggunakan abstraksi. Namun, objek dari kelas Client akan menggunakan objek dari kelas penerus Server . Jika kami ingin objek kelas Client menggunakan kelas server yang berbeda, kami akan memperkenalkan turunan baru dari kelas AbstractServer . Kelas Client akan tetap tidak berubah.


gambar
Buka klien


Shape Abstrak


Pertimbangkan aplikasi yang harus menggambar lingkaran dan bujur sangkar dalam GUI standar. Lingkaran dan kotak harus digambar dalam urutan tertentu. Dalam urutan yang sesuai, daftar lingkaran dan kotak akan dikompilasi, program harus melalui daftar ini dalam urutan dan menggambar setiap lingkaran atau kotak.


Dalam C, menggunakan teknik pemrograman prosedural yang tidak memenuhi prinsip buka-tutup, kita bisa menyelesaikan masalah ini seperti yang ditunjukkan pada Listing 1. Di sini kita melihat banyak struktur data dengan elemen pertama yang sama. Elemen ini adalah kode tipe yang mengidentifikasi struktur data sebagai lingkaran atau kuadrat. Fungsi DrawAllShapes melewati array pointer ke struktur data ini, mengenali kode jenis dan kemudian memanggil fungsi yang sesuai ( DrawCircle atau DrawSquare ).


 // 1 //  /    enum ShapeType {circle, square} struct Shape { ShapeType itsType; }; struct Circle { ShapeType itsType; double itsRadius; Point itsCenter; }; struct Square { ShapeType itsType; double itsSide; Point itsTopLeft; }; // //     // void DrawSquare(struct Square*) void DrawCircle(struct Circle*); typedef struct Shape *ShapePointer; void DrawAllShapes(ShapePointer list[], int n) { int i; for (i=0; i<n; i++) { struct Shape* s = list[i]; switch (s->itsType) { case square: DrawSquare((struct Square*)s); break; case circle: DrawCircle((struct Circle*)s); break; } } } 

Fungsi DrawAllShapes tidak memenuhi prinsip keterbukaan-penutupan, karena tidak dapat "ditutup" dari jenis bentuk baru. Jika saya ingin memperluas fungsi ini dengan kemampuan untuk menggambar bentuk dari daftar yang menyertakan segitiga, maka saya perlu mengubah fungsinya. Sebenarnya, saya harus mengubah fungsi untuk setiap jenis bentuk baru yang perlu saya gambar.


Tentu saja, program ini hanyalah sebuah contoh. Dalam kehidupan nyata, operator switch dari fungsi DrawAllShapes akan diulang berkali-kali di berbagai fungsi di seluruh aplikasi, dan masing-masing akan melakukan sesuatu yang berbeda. Menambahkan bentuk baru ke aplikasi seperti itu berarti menemukan semua tempat di mana switch (atau if/else rantai if/else ) digunakan, dan menambahkan bentuk baru ke masing-masing. Selain itu, sangat tidak mungkin bahwa semua switch dan if/else rantai if/else akan terstruktur sebaik di DrawAllShapes . Adalah jauh lebih mungkin bahwa predikat if akan digabungkan dengan operator logis, atau blok case dari switch akan digabungkan sedemikian rupa untuk “menyederhanakan” tempat tertentu dalam kode. Karena itu, masalah menemukan dan memahami semua tempat di mana Anda perlu menambahkan sosok baru bisa menjadi hal yang tidak sepele.


Dalam Listing 2, saya akan menunjukkan kode yang menunjukkan solusi persegi / lingkaran yang memenuhi prinsip keterbukaan-penutupan. Kelas Shape abstrak diperkenalkan. Kelas abstrak ini berisi satu fungsi Draw virtual murni. Kelas Circle dan Square adalah turunan dari kelas Shape .


 // 2 //  /  - class Shape { public: virtual void Draw() const = 0; }; class Square : public Shape { public: virtual void Draw() const; }; class Circle : public Shape { public: virtual void Draw() const; }; void DrawAllShapes(Set<Shape*>& list) { for (Iterator<Shape*>i(list); i; i++) (*i)->Draw(); } 

Perhatikan bahwa jika kita ingin memperluas perilaku fungsi DrawAllShapes di Listing 2 untuk menggambar bentuk baru, yang perlu kita lakukan adalah menambahkan turunan baru dari kelas Shape . Tidak perlu mengubah fungsi DrawAllShapes . Oleh karena itu, DrawAllShapes memenuhi prinsip keterbukaan-kedekatan. Perilakunya dapat diperluas tanpa mengubah fungsi itu sendiri.


Di dunia nyata, kelas Shape akan berisi banyak metode lain. Namun, menambahkan bentuk baru ke aplikasi masih sangat sederhana, karena yang perlu Anda lakukan adalah memasukkan pewaris baru dan mengimplementasikan fungsi-fungsi ini. Tidak perlu menjelajahi seluruh aplikasi untuk mencari tempat yang membutuhkan perubahan.


Oleh karena itu, program yang memenuhi prinsip keterbukaan-kedekatan diubah dengan menambahkan kode baru, dan bukan dengan mengubah kode yang sudah ada, mereka tidak mengubah perubahan karakteristik program yang tidak sesuai dengan prinsip ini.


Strategi Entri Tertutup


Jelas, tidak ada program yang dapat ditutup 100%. Misalnya, apa yang terjadi pada fungsi DrawAllShapes di Listing 2 jika kita memutuskan bahwa lingkaran dan kotak harus digambar terlebih dahulu? Fungsi DrawAllShapes tidak tertutup dari perubahan semacam ini. Secara umum, tidak masalah seberapa "tertutup" modul ini, selalu ada beberapa jenis perubahan yang tidak ditutup.


Karena penutupan tidak dapat lengkap, itu harus diperkenalkan secara strategis. Artinya, perancang harus memilih jenis perubahan dari mana program akan ditutup. Ini membutuhkan beberapa pengalaman. Pengembang berpengalaman mengetahui pengguna dan industri dengan cukup baik untuk menghitung kemungkinan berbagai perubahan. Dia kemudian memastikan bahwa prinsip keterbukaan-kedekatan dihormati untuk perubahan yang paling mungkin.


Gunakan abstraksi untuk mencapai kedekatan tambahan


Bagaimana kita bisa menutup fungsi DrawAllShapes dari perubahan dalam urutan gambar? Ingat bahwa penutupan didasarkan pada abstraksi. Oleh karena itu, untuk menutup DrawAllShapes dari pemesanan, kita perlu semacam "pemesanan abstraksi". Kasus khusus pemesanan, yang disajikan di atas, adalah menggambar tokoh dari satu jenis di depan tokoh dari jenis lain.


Kebijakan pemesanan menyiratkan bahwa dengan dua objek, Anda dapat menentukan mana yang harus ditarik terlebih dahulu. Oleh karena itu, kita dapat mendefinisikan metode untuk kelas Shape disebut Precedes , yang mengambil objek Shape lain sebagai argumen dan mengembalikan nilai Boolean true sebagai hasilnya jika objek kelas Shape yang menerima pesan ini perlu disortir sebelum objek kelas Shape yang sebelumnya disahkan sebagai argumen.


Dalam C ++, fungsi ini dapat direpresentasikan sebagai kelebihan dari operator "<". Listing 3 memperlihatkan kelas Shape dengan metode penyortiran.


Sekarang kita memiliki cara untuk menentukan urutan objek dari kelas Shape , kita dapat mengurutkannya dan kemudian menggambarnya. Kode 4 menunjukkan kode C ++ yang sesuai. Menggunakan kelas Set , OrderedSet dan Iterator dari kategori Components dikembangkan dalam buku saya (Merancang Aplikasi Berorientasi Objek C ++ menggunakan Metode Booch, Robert C. Martin, Prentice Hall, 1995).


Jadi, kami telah menerapkan pemesanan objek dari kelas Shape dan menggambarnya dalam urutan yang sesuai. Namun kami masih belum memiliki implementasi abstraksi pemesanan. Jelas, setiap objek Shape harus menimpa metode Precedes untuk menentukan urutan. Bagaimana ini bisa berhasil? Kode apa yang perlu ditulis dalam Circle::Precedes sehingga lingkaran ditarik ke kotak? Perhatikan daftar 5.


 // 3 //  Shape    . class Shape { public: virtual void Draw() const = 0; virtual bool Precedes(const Shape&) const = 0; bool operator<(const Shape& s) {return Precedes(s);} }; 

 // 4 // DrawAllShapes   void DrawAllShapes(Set<Shape*>& list) { //    OrderedSet  . OrderedSet<Shape*> orderedList = list; orderedList.Sort(); for (Iterator<Shape*> i(orderedList); i; i++) (*i)->Draw(); } 

 // 5 //    bool Circle::Precedes(const Shape& s) const { if (dynamic_cast<Square*>(s)) return true; else return false; } 

Jelas bahwa fungsi ini tidak memenuhi prinsip keterbukaan-kedekatan. Tidak ada cara untuk menutupnya dari keturunan baru dari kelas Shape . Setiap kali keturunan baru dari kelas Shape muncul, fungsi ini perlu diubah.


Menggunakan Pendekatan Data Driven untuk Mencapai Penutupan


Kedekatan pewaris dari kelas Shape dapat dicapai dengan menggunakan pendekatan tabular yang tidak memicu perubahan di setiap kelas yang diwariskan. Contoh dari pendekatan ini ditunjukkan pada Listing 6.


Dengan menggunakan pendekatan ini, kami berhasil menutup fungsi DrawAllShapes dari perubahan yang terkait dengan pemesanan, dan setiap keturunan dari kelas Shape - dari memperkenalkan turunan baru atau dari perubahan kebijakan pemesanan untuk objek-objek dari kelas Shape tergantung pada jenisnya (misalnya, sehingga objek dari kelas Squares harus ditarik terlebih dahulu).


 // 6 //     #include <typeinfo.h> #include <string.h> enum {false, true}; typedef int bool; class Shape { public: virtual void Draw() const = 0; virtual bool Precedes(const Shape&) const; bool operator<(const Shape& s) const {return Precedes(s);} private: static char* typeOrderTable[]; }; char* Shape::typeOrderTable[] = { "Circle", "Square", 0 }; //      . //   ,    //  . ,    , //      bool Shape::Precedes(const Shape& s) const { const char* thisType = typeid(*this).name(); const char* argType = typeid(s).name(); bool done = false; int thisOrd = -1; int argOrd = -1; for (int i=0; !done; i++) { const char* tableEntry = typeOrderTable[i]; if (tableEntry != 0) { if (strcmp(tableEntry, thisType) == 0) thisOrd = i; if (strcmp(tableEntry, argType) == 0) argOrd = i; if ((argOrd > 0) && (thisOrd > 0)) done = true; } else // table entry == 0 done = true; } return thisOrd < argOrd; } 

Satu-satunya elemen yang tidak tertutup dari mengubah urutan bentuk gambar adalah tabel. Tabel dapat ditempatkan dalam modul terpisah, terpisah dari semua modul lainnya, dan karenanya perubahannya tidak akan memengaruhi modul lainnya.


Penutupan lebih lanjut


Ini bukan akhir dari cerita. Kami menutup hierarki kelas Shape dan fungsi DrawAllShapes dari mengubah kebijakan pemesanan berdasarkan jenis bentuk. Namun, turunan dari kelas Shape tidak tertutup dari kebijakan pemesanan yang tidak terkait dengan tipe Shape . Tampaknya kita perlu mengatur gambar bentuk sesuai dengan struktur tingkat yang lebih tinggi. Sebuah studi lengkap tentang masalah-masalah semacam itu berada di luar cakupan artikel ini; namun, pembaca yang tertarik mungkin berpikir bagaimana menyelesaikan masalah ini menggunakan kelas OrderedObject abstrak yang terkandung dalam kelas OrderedShape , yang mewarisi dari kelas Shape dan OrderedObject .


Heuristik dan Konvensi


Seperti yang telah disebutkan di awal artikel, prinsip keterbukaan-kedekatan adalah motivasi utama di balik banyak heuristik dan konvensi yang telah muncul selama bertahun-tahun dalam pengembangan paradigma OOP. Berikut ini adalah yang paling penting.


Jadikan semua variabel anggota bersifat pribadi


Ini adalah salah satu konvensi PLO yang paling bertahan lama. Variabel anggota hanya boleh diketahui dengan metode kelas di mana mereka didefinisikan. Anggota variabel tidak boleh diketahui ke kelas lain, termasuk kelas turunan. Oleh karena itu, mereka harus dinyatakan dengan pengubah akses private , bukan public atau protected .
Dalam terang prinsip keterbukaan-kedekatan, alasan konvensi semacam itu dapat dipahami. Ketika variabel anggota kelas berubah, setiap fungsi yang bergantung padanya harus berubah. Artinya, fungsinya tidak tertutup dari perubahan variabel-variabel ini.


Dalam OOP, kami berharap bahwa metode kelas tidak tertutup terhadap perubahan variabel yang menjadi anggota kelas ini. Namun, kami berharap bahwa kelas lain, termasuk subclass, ditutup dari perubahan variabel-variabel ini. Ini disebut enkapsulasi.


Tetapi bagaimana jika Anda memiliki variabel yang Anda yakin tidak akan pernah berubah? Apakah masuk akal untuk menjadikannya private ? Misalnya, Listing 7 menunjukkan kelas Device yang berisi bool status anggota variabel. Ini menyimpan status operasi terakhir. Jika operasi berhasil, maka nilai variabel status akan true , jika tidak false .


 // 7 //   class Device { public: bool status; }; 

Kita tahu bahwa jenis atau makna variabel ini tidak akan pernah berubah. Jadi mengapa tidak membuatnya public dan memberikan klien akses langsung ke sana? Jika variabel benar-benar tidak pernah berubah, jika semua klien mengikuti aturan dan hanya membaca dari variabel ini, maka tidak ada yang salah dengan fakta bahwa variabel tersebut bersifat publik. Namun, pertimbangkan apa yang akan terjadi jika salah satu klien mengambil kesempatan untuk menulis ke variabel ini dan mengubah nilainya.


Tiba-tiba, klien ini dapat memengaruhi operasi klien lain apa pun dari kelas Device . Ini berarti bahwa tidak mungkin untuk menutup klien dari kelas Device dari perubahan ke modul yang salah ini. Ini terlalu banyak risiko.


Di sisi lain, anggaplah kita memiliki kelas Time , ditunjukkan pada Listing 8. Apa bahaya dari publisitas variabel yang menjadi anggota kelas ini? Sangat tidak mungkin mereka akan berubah. Selain itu, tidak masalah jika modul klien mengubah nilai-nilai variabel ini atau tidak, karena perubahan dalam variabel-variabel ini diasumsikan. Juga sangat tidak mungkin bahwa kelas yang diwarisi dapat bergantung pada nilai variabel anggota tertentu. Jadi, apakah ada masalah?


 // 8 class Time { public: int hours, minutes, seconds; Time& operator-=(int seconds); Time& operator+=(int seconds); bool operator< (const Time&); bool operator> (const Time&); bool operator==(const Time&); bool operator!=(const Time&); }; 

Satu-satunya keluhan yang bisa saya buat pada kode di Listing 8 adalah bahwa perubahan waktu tidak terjadi secara atom. Artinya, klien dapat mengubah nilai variabel minutes tanpa mengubah nilai variabel hours . Ini dapat menyebabkan objek dari kelas Time mengandung data yang tidak konsisten. Saya lebih suka memperkenalkan fungsi tunggal untuk mengatur waktu, yang akan membutuhkan tiga argumen, yang akan menjadikan pengaturan waktu sebagai operasi atom. Tapi ini argumen yang lemah.


Sangat mudah untuk menemukan kondisi lain di mana publisitas variabel-variabel ini dapat menyebabkan masalah. Namun, pada akhirnya, tidak ada alasan yang meyakinkan untuk menjadikannya private . Saya masih berpikir bahwa mempublikasikan variabel semacam itu adalah gaya yang buruk, tapi mungkin itu bukan desain yang buruk. Saya percaya bahwa ini adalah gaya yang buruk, karena hampir tidak ada biaya untuk memasuki fungsi yang sesuai untuk mengakses anggota ini, dan sudah pasti layak untuk melindungi diri Anda dari risiko kecil yang terkait dengan kemungkinan terjadinya masalah dengan penutupan.


Oleh karena itu, dalam kasus yang jarang terjadi, ketika prinsip keterbukaan-kedekatan tidak dilanggar, larangan variabel public - dan protected lebih tergantung pada gaya dan bukan pada konten.


Tidak ada variabel global ... sama sekali!


Argumen terhadap variabel global sama dengan argumen terhadap variabel anggota publik. Tidak ada modul yang bergantung pada variabel global yang dapat ditutup dari modul yang dapat menulisnya. Setiap modul yang menggunakan variabel ini dengan cara yang tidak dimaksudkan oleh modul lain akan merusak modul ini. Terlalu berisiko untuk memiliki banyak modul, tergantung pada keanehan dari satu modul jahat.
Di sisi lain, dalam kasus di mana variabel global memiliki sejumlah kecil modul bergantung padanya atau tidak dapat digunakan dengan cara yang salah, mereka tidak membahayakan. Perancang harus mengevaluasi seberapa banyak privasi dikorbankan dan menentukan apakah kenyamanan yang disediakan oleh variabel global sepadan.


Di sini lagi, masalah gaya ikut bermain. Alternatif untuk menggunakan variabel global biasanya tidak mahal. Dalam kasus seperti itu, penggunaan teknik yang memperkenalkan, meskipun kecil, tetapi risiko untuk penutupan, bukan teknik yang sepenuhnya menghilangkan risiko seperti itu, adalah tanda gaya buruk. Namun, terkadang menggunakan variabel global sangat nyaman. Contoh tipikal adalah variabel global cout dan cin. Dalam kasus seperti itu, jika prinsip keterbukaan-kedekatan tidak dilanggar, Anda dapat mengorbankan gaya demi kenyamanan.


RTTI berbahaya


Larangan umum lainnya adalah penggunaan dynamic_cast . Sangat sering, dynamic_cast atau bentuk lain dari penentuan jenis runtime (RTTI) dituduh sebagai teknik yang sangat berbahaya dan karenanya harus dihindari. Pada saat yang sama, mereka sering memberikan contoh dari Listing 9, yang jelas-jelas melanggar prinsip keterbukaan-kedekatan. Namun, Listing 10 menunjukkan contoh program serupa yang menggunakan dynamic_cast tanpa melanggar prinsip buka-tutup.


Perbedaan di antara mereka adalah bahwa dalam kasus pertama, ditunjukkan pada Listing 9, kode perlu diubah setiap kali keturunan baru dari kelas Shape muncul (belum lagi bahwa ini adalah solusi yang benar-benar konyol). Namun, dalam Listing 10, tidak diperlukan perubahan dalam kasus ini. Oleh karena itu, kode dalam Listing 10 tidak melanggar prinsip buka-tutup.
Dalam hal ini, aturan praktisnya adalah bahwa RTTI dapat digunakan jika prinsip keterbukaan-penutupan tidak dilanggar.


 // 9 //RTTI,   -. class Shape {}; class Square : public Shape { private: Point itsTopLeft; double itsSide; friend DrawSquare(Square*); }; class Circle : public Shape { private: Point itsCenter; double itsRadius; friend DrawCircle(Circle*); }; void DrawAllShapes(Set<Shape*>& ss) { for (Iterator<Shape*>i(ss); i; i++) { Circle* c = dynamic_cast<Circle*>(*i); Square* s = dynamic_cast<Square*>(*i); if (c) DrawCircle(c); else if (s) DrawSquare(s); } } 

 // 10 //RTTI,    -. class Shape { public: virtual void Draw() cont = 0; }; class Square : public Shape { // . }; void DrawSquaresOnly(Set<Shape*>& ss) { for (Iterator<Shape*>i(ss); i; i++) { Square* s = dynamic_cast<Square*>(*i); if (s) s->Draw(); } } 

Kesimpulan


Saya bisa berbicara lama tentang prinsip keterbukaan-kedekatan. Dalam banyak hal, prinsip ini paling penting untuk pemrograman berorientasi objek. Kepatuhan dengan prinsip khusus ini memberikan keuntungan utama dari teknologi berorientasi objek, yaitu penggunaan kembali dan dukungan.


, - -. , , , , , .

Source: https://habr.com/ru/post/id472186/


All Articles