Agak terlalu esoteris untuk mengatakan setidaknya ketika Anda sudah mengenali yang mungkin membuat saya menggaruk-garuk kepala sejenak ketika saya pertama kali mulai menemukan kode Anda bertanya-tanya apa yang Anda lakukan dan di mana kelas-kelas pembantu ini diterapkan sampai saya mulai mengambil gaya Anda / Kebiasaan (pada titik mana saya mungkin akan benar-benar terbiasa).
Saya suka Anda mengurangi jumlah informasi di header. Terutama dalam basis kode yang sangat besar, yang dapat memiliki efek praktis untuk mengurangi dependensi waktu kompilasi dan akhirnya membangun waktu.
Reaksi usus saya adalah bahwa jika Anda merasa perlu untuk menyembunyikan detail implementasi dengan cara ini, untuk mendukung parameter lewat ke fungsi yang berdiri sendiri dengan tautan internal dalam file sumber. Biasanya Anda dapat mengimplementasikan fungsi utilitas (atau seluruh kelas) yang berguna untuk mengimplementasikan kelas tertentu tanpa memiliki akses ke semua internal kelas dan alih-alih hanya meneruskan yang relevan dari penerapan metode ke fungsi (atau konstruktor). Dan tentu saja itu memiliki bonus mengurangi kopling antara kelas Anda dan "pembantu". Ini juga memiliki kecenderungan untuk menggeneralisasi apa yang seharusnya menjadi "pembantu" lebih lanjut jika Anda menemukan bahwa mereka mulai melayani tujuan yang lebih umum yang berlaku untuk implementasi lebih dari satu kelas.
Saya juga terkadang merasa ngeri sedikit ketika saya melihat banyak "pembantu" dalam kode. Itu tidak selalu benar tetapi kadang-kadang mereka dapat merupakan gejala dari pengembang yang hanya membusuk fungsi mau tak mau untuk menghilangkan duplikasi kode dengan gumpalan besar data yang diteruskan ke fungsi dengan nama / tujuan yang hampir tidak dapat dipahami di luar fakta bahwa mereka mengurangi jumlah diperlukan kode untuk mengimplementasikan beberapa fungsi lainnya. Hanya sedikit lebih banyak berpikir di muka kadang-kadang dapat menyebabkan kejelasan yang lebih besar dalam hal bagaimana implementasi kelas didekomposisi menjadi fungsi lebih lanjut, dan lebih suka melewati parameter tertentu untuk melewati seluruh contoh objek Anda dengan akses penuh ke internal dapat membantu mempromosikan gaya pemikiran desain itu. Saya tidak menyarankan Anda melakukan itu, tentu saja (saya tidak tahu),
Jika itu menjadi sulit, saya akan mempertimbangkan solusi kedua, lebih idiomatis yang merupakan jerawat (saya menyadari Anda menyebutkan masalah dengan itu tetapi saya pikir Anda dapat menggeneralisasi solusi untuk menghindari mereka dengan usaha minimal). Itu bisa memindahkan seluruh banyak informasi yang perlu diimplementasikan oleh kelas Anda termasuk data privatnya dari grosir header. Masalah kinerja pimpl sebagian besar dapat dikurangi dengan pengalokasi waktu-murah konstan * seperti daftar gratis sambil menjaga semantik nilai tanpa harus menerapkan ctor copy yang ditentukan pengguna full-blown.
- Untuk aspek kinerja, mucikari memperkenalkan overhead pointer setidaknya, tapi saya pikir kasus harus cukup serius di mana pose menjadi perhatian praktis. Jika lokalitas spasial tidak terdegradasi secara signifikan melalui pengalokasi, maka loop ketat Anda berulang di atas objek (yang umumnya harus homogen jika kinerjanya sangat memprihatinkan) masih akan cenderung meminimalkan cache yang hilang dalam praktik asalkan Anda menggunakan sesuatu seperti daftar gratis untuk mengalokasikan jerawat, menempatkan bidang kelas ke dalam blok memori yang berdekatan.
Secara pribadi hanya setelah menguras kemungkinan itu saya akan mempertimbangkan sesuatu seperti ini. Saya pikir itu ide yang baik jika alternatifnya seperti metode yang lebih pribadi yang diekspos ke header dengan mungkin hanya sifat esoterik yang menjadi perhatian praktis.
Sebuah alternatif
Salah satu alternatif yang muncul di kepala saya sekarang yang sebagian besar mencapai tujuan yang sama Anda tidak ada teman adalah seperti ini:
struct PredicateListData
{
int somePrivateField;
};
class PredicateList
{
PredicateListData data;
public:
bool match() const;
};
// In source file:
static bool fullMatch(const PredicateListData& p)
{
// Can access p.somePrivateField here.
}
bool PredicateList::match() const
{
return fullMatch(data);
}
Nah, itu mungkin tampak seperti perbedaan yang sangat bisa diperdebatkan dan saya masih menyebutnya "penolong" (dalam arti yang mungkin merendahkan karena kita masih menyerahkan seluruh keadaan internal kelas ke fungsi apakah perlu semuanya atau tidak) kecuali itu menghindari faktor "kejutan" dari pertemuan friend
. Secara umum friend
terlihat agak menakutkan untuk melihat sering tidak ada inspeksi lebih lanjut, karena ia mengatakan bahwa internal kelas Anda dapat diakses di tempat lain (yang membawa implikasi bahwa ia mungkin tidak mampu mempertahankan invariannya sendiri). Dengan cara Anda menggunakannya friend
menjadi agak diperdebatkan jika orang menyadari praktik sejak saat itufriend
hanya berada di file sumber yang sama yang membantu mengimplementasikan fungsionalitas pribadi kelas, tetapi hal di atas menghasilkan banyak efek yang sama setidaknya dengan manfaat yang mungkin diperdebatkan bahwa itu tidak melibatkan teman yang menghindari semua jenis itu ("Oh tembak, kelas ini punya teman. Di mana lagi privatnya bisa diakses / dimutasi? "). Sedangkan versi di atas segera mengkomunikasikan bahwa tidak ada cara bagi privat untuk diakses / dimutasi di luar apa pun yang dilakukan dalam implementasi PredicateList
.
Itu mungkin bergerak menuju wilayah yang agak dogmatis dengan tingkat nuansa ini karena siapa pun dapat dengan cepat mengetahui apakah Anda secara seragam menyebutkan berbagai hal *Helper*
dan menempatkan semuanya dalam file sumber yang sama yang semuanya dibundel bersama sebagai bagian dari implementasi privat sebuah kelas. Tetapi jika kita menjadi rewel, mungkin gaya yang tepat di atas tidak akan menyebabkan reaksi spontan sekilas tidak ada friend
kata kunci yang cenderung terlihat sedikit menakutkan.
Untuk pertanyaan lain:
Seorang konsumen dapat mendefinisikan PredicateList_HelperFunctions kelas mereka sendiri, dan membiarkan mereka mengakses bidang pribadi. Meskipun saya tidak melihat ini sebagai masalah besar (jika Anda benar-benar ingin di bidang-bidang pribadi itu Anda bisa melakukan casting), mungkin itu akan mendorong konsumen untuk menggunakannya seperti itu?
Itu mungkin merupakan kemungkinan lintas batas API di mana klien dapat menentukan kelas kedua dengan nama yang sama dan mendapatkan akses ke internal seperti itu tanpa kesalahan tautan. Kemudian lagi saya sebagian besar C coder bekerja di grafik di mana masalah keamanan pada tingkat "bagaimana jika" ini sangat rendah pada daftar prioritas, jadi kekhawatiran seperti ini hanya yang saya cenderung melambaikan tangan dan menari dan cobalah untuk berpura-pura seolah mereka tidak ada. :-D Jika Anda bekerja di domain di mana masalah seperti ini agak serius, saya pikir itu pertimbangan yang layak untuk dibuat.
Usulan alternatif di atas juga menghindari masalah ini. Jika Anda masih ingin tetap menggunakan friend
, Anda juga dapat menghindari masalah itu dengan menjadikan helper sebagai kelas bersarang pribadi.
class PredicateList
{
...
// Declare nested class.
class Helper;
// Make it a friend.
friend class Helper;
public:
...
};
// In source file:
class PredicateList::Helper
{
...
};
Apakah ini pola desain terkenal yang ada namanya?
Tidak ada yang sepengetahuan saya. Saya agak ragu akan ada satu karena itu benar-benar masuk ke detail detail dan gaya implementasi.
"Helper Hell"
Saya mendapat permintaan klarifikasi lebih lanjut tentang bagaimana saya terkadang merasa ngeri ketika saya melihat implementasi dengan banyak kode "pembantu", dan itu mungkin sedikit kontroversial dengan beberapa tetapi sebenarnya faktual karena saya benar-benar merasa ngeri ketika saya debug beberapa implementasi kolega saya di kelas hanya untuk menemukan banyak "pembantu". :-D Dan aku bukan satu-satunya di tim yang menggaruk-garuk kepalaku mencoba untuk mencari tahu apa yang seharusnya dilakukan semua penolong ini. Saya juga tidak ingin lepas dari dogmatis seperti "Jangan menggunakan pembantu," tetapi saya akan membuat saran kecil bahwa mungkin membantu untuk memikirkan bagaimana menerapkan hal-hal yang tidak ada pada mereka ketika praktis.
Tidak semua fungsi anggota pribadi membantu fungsi dengan definisi?
Dan ya, saya termasuk metode pribadi. Jika saya melihat kelas dengan antarmuka publik yang langsung tetapi seperti serangkaian metode pribadi yang tak terbatas yang agak tidak jelas tujuannya seperti find_impl
atau find_detail
atau find_helper
, maka saya juga merasa ngeri dengan cara yang sama.
Apa yang saya sarankan sebagai alternatif adalah fungsi non-teman nonanggota dengan tautan internal (dideklarasikan static
atau di dalam namespace anonim) untuk membantu mengimplementasikan kelas Anda dengan setidaknya tujuan yang lebih umum daripada "fungsi yang membantu mengimplementasikan orang lain". Dan saya dapat mengutip Herb Sutter dari C ++ 'Coding Standards' di sini untuk alasan mengapa itu lebih disukai dari sudut pandang SE umum:
Hindari biaya keanggotaan: Jika memungkinkan, lebih suka membuat fungsi yang bukan anggota bukan teman. [...] Fungsi non-teman non-anggota meningkatkan enkapsulasi dengan meminimalkan dependensi: Tubuh fungsi tidak dapat bergantung pada anggota kelas yang bukan publik dari kelas (lihat Item 11). Mereka juga memecah kelas monolitik untuk membebaskan fungsi yang dapat dipisahkan, lebih lanjut mengurangi kopling (lihat Item 33).
Anda juga dapat memahami "biaya keanggotaan" yang dibicarakannya sampai batas tertentu dalam hal prinsip dasar penyempitan ruang lingkup variabel. Jika Anda membayangkan, sebagai contoh paling ekstrem, objek Dewa yang memiliki semua kode yang diperlukan untuk menjalankan seluruh program Anda, maka pilih "pembantu" jenis ini (fungsi, apakah fungsi anggota atau teman) yang dapat mengakses semua internal ( privat) dari suatu kelas pada dasarnya membuat variabel-variabel itu tidak kurang bermasalah daripada variabel global. Anda memiliki semua kesulitan mengelola keadaan dengan benar dan menjaga keamanan serta mempertahankan invarian yang akan Anda dapatkan dengan variabel global dalam contoh paling ekstrem ini. Dan tentu saja sebagian besar contoh nyata mudah-mudahan tidak mendekati ekstrem ini, tetapi menyembunyikan informasi hanya berguna karena membatasi ruang lingkup informasi yang diakses.
Sekarang Sutter sudah memberikan penjelasan yang bagus di sini, tetapi saya juga menambahkan bahwa decoupling cenderung mempromosikan seperti peningkatan psikologis (setidaknya jika otak Anda bekerja seperti milik saya) dalam hal bagaimana Anda merancang fungsi. Ketika Anda mulai mendesain fungsi yang tidak dapat mengakses semua yang ada di kelas kecuali hanya parameter yang relevan yang Anda lewati atau, jika Anda lulus instance dari kelas sebagai parameter, hanya anggota publiknya, ia cenderung mempromosikan pola pikir desain yang mendukung fungsi yang memiliki tujuan yang lebih jelas, di atas decoupling dan mempromosikan enkapsulasi yang lebih baik, daripada apa yang Anda mungkin tergoda untuk merancang jika Anda bisa mengakses semuanya.
Jika kita kembali ke ekstremitas maka basis kode yang penuh dengan variabel global tidak benar-benar menggoda pengembang untuk merancang fungsi dengan cara yang jelas dan digeneralisasi untuk tujuan tertentu. Sangat cepat, semakin banyak informasi yang dapat Anda akses dalam suatu fungsi, semakin banyak dari kita manusia menghadapi godaan untuk merosotkannya dan mengurangi kejernihannya dalam hal mengakses semua informasi tambahan yang kita miliki daripada menerima parameter yang lebih spesifik dan relevan dengan fungsi itu. untuk mempersempit aksesnya ke negara dan memperluas penerapannya dan meningkatkan kejelasan niatnya. Itu berlaku (meskipun umumnya pada tingkat yang lebih rendah) dengan fungsi anggota atau teman.