Mengapa C # tidak memberikan kata kunci 'teman' gaya C ++? [Tutup]


208

Kata kunci teman C ++ memungkinkan class Auntuk menunjuk class Bsebagai temannya. Ini memungkinkan Class Buntuk mengakses private/ protectedanggota class A.

Saya tidak pernah membaca apa pun mengapa ini ditinggalkan dari C # (dan VB.NET). Sebagian besar jawaban untuk pertanyaan StackOverflow sebelumnya tampaknya mengatakan itu adalah bagian yang berguna dari C ++ dan ada alasan bagus untuk menggunakannya. Dalam pengalaman saya, saya harus setuju.

Pertanyaan lain bagi saya tampaknya benar-benar bertanya bagaimana melakukan sesuatu yang mirip dengan friendaplikasi C #. Walaupun jawaban umumnya berkisar pada kelas bersarang, sepertinya tidak seelegan menggunakan friendkata kunci.

Buku Pola Desain asli menggunakannya secara teratur di seluruh contohnya.

Jadi secara ringkas, mengapa tidak friendada dari C #, dan apa cara "praktik terbaik" (atau cara) mensimulasikannya dalam C #?

(Omong-omong, internalkata kunci bukan hal yang sama, itu memungkinkan semua kelas dalam seluruh majelis untuk mengakses internalanggota, sementara friendmemungkinkan Anda untuk memberikan kelas tertentu akses lengkap ke tepat satu kelas lain)


5
Saya merasa dilindungi internal adalah kompromi yang baik ..
Gulzar Nazim

3
Tidak terlalu membingungkan kan? Seperti yang saya katakan di atas, buku GOF cukup sering menggunakannya dalam contoh. Bagi saya itu tidak lebih membingungkan daripada internal.
Ash

7
Saya pasti bisa melihat skenario di mana mereka bisa sangat berguna, seperti yang telah disebutkan Unit Testing. Tetapi apakah Anda benar-benar ingin teman Anda mengakses privasi Anda?
danswain

2
Sangat mudah untuk menjawab: C # menawarkan "internal" sebagai pengubah akses yang memberikan akses ke semua kode dalam modul / rakitan yang sama. Ini menghilangkan kebutuhan akan sesuatu seperti teman. Di Jawa, kata kunci "protected" berperilaku sama dengan akses wrt dari paket yang sama.
sellibitze

2
@sellibitze, saya menyebutkan perbedaan dengan internal dalam pertanyaan. Masalah dengan Interanl adalah bahwa semua kelas dalam majelis dapat mengakses anggota internal. Ini memecah enkapsulasi karena banyak dari kelas-kelas ini mungkin tidak memerlukan akses.
Ash

Jawaban:


67

Memiliki teman dalam pemrograman lebih atau kurang dianggap "kotor" dan mudah disalahgunakan. Ini memutus hubungan antara kelas dan merusak beberapa atribut mendasar dari bahasa OO.

Yang sedang berkata, ini adalah fitur yang bagus dan saya sudah sering menggunakannya di C ++; dan ingin menggunakannya dalam C # juga. Tapi saya bertaruh karena OOness "murni" C # (dibandingkan dengan pseudo OOness C ++) MS memutuskan bahwa karena Java tidak memiliki kata kunci teman C # tidak seharusnya (hanya bercanda;))

Pada catatan yang serius: internal tidak sebaik teman tetapi itu menyelesaikan pekerjaan. Ingatlah bahwa Anda jarang mendistribusikan kode Anda ke pengembang pihak ketiga tidak melalui DLL; jadi selama Anda dan tim Anda tahu tentang kelas internal dan penggunaannya Anda harus baik-baik saja.

EDIT Biarkan saya menjelaskan bagaimana kata kunci teman melemahkan OOP.

Variabel dan metode pribadi dan yang dilindungi mungkin merupakan salah satu bagian terpenting dari OOP. Gagasan bahwa objek dapat menyimpan data atau logika yang hanya dapat mereka gunakan memungkinkan Anda untuk menulis implementasi fungsionalitas Anda terlepas dari lingkungan Anda - dan bahwa lingkungan Anda tidak dapat mengubah informasi keadaan yang tidak cocok untuk ditangani. Dengan menggunakan teman Anda menggabungkan implementasi dua kelas bersama-sama - yang jauh lebih buruk jika Anda hanya menambahkan antarmuka mereka.


167
C # 'internal' sebenarnya lebih merusak enkapsulasi daripada 'teman' C ++. Dengan "persahabatan", pemilik secara eksplisit memberikan izin kepada siapa pun yang diinginkannya. "Internal" adalah konsep terbuka.
Nemanja Trifunovic

21
Anders harus dipuji karena fitur yang ditinggalkannya bukan fitur yang ia sertakan.
Mehrdad Afshari

21
Saya tidak setuju bahwa internal C # sakit inkapsulasi sekalipun. Sebaliknya menurut saya. Anda dapat menggunakannya untuk membuat ekosistem yang dienkapsulasi dalam sebuah majelis. Sejumlah objek dapat berbagi tipe internal dalam ekosistem ini yang tidak terkena sisa aplikasi. Saya menggunakan trik perakitan "teman" untuk membuat unit test rakitan untuk objek-objek ini.
Quibblesome

26
Ini tidak masuk akal. friendtidak membiarkan kelas atau fungsi sewenang-wenang mengakses anggota pribadi. Ini memungkinkan fungsi atau kelas tertentu yang ditandai sebagai teman. Kelas yang memiliki anggota pribadi masih mengontrol akses ke sana 100%. Anda mungkin juga berpendapat bahwa metode anggota publik. Keduanya memastikan bahwa metode yang secara khusus tercantum dalam kelas diberikan akses ke anggota kelas pribadi.
Jalf

8
Saya pikir justru sebaliknya. Jika suatu rakitan memiliki 50 kelas, jika 3 dari kelas tersebut menggunakan beberapa kelas utilitas, maka hari ini, satu-satunya pilihan Anda adalah menandai kelas utilitas itu sebagai "internal". Dengan melakukan itu, Anda tidak hanya membuatnya tersedia untuk digunakan ke 3 kelas, tetapi ke seluruh kelas dalam perakitan. Bagaimana jika yang ingin Anda lakukan adalah menyediakan akses yang lebih halus sehingga hanya tiga kelas yang bersangkutan yang memiliki akses ke kelas utilitas. Ini sebenarnya membuatnya lebih berorientasi objek karena meningkatkan enkapsulasi.
zumalifeguard

104

Di samping catatan. Menggunakan teman bukan tentang melanggar enkapsulasi, tetapi sebaliknya tentang menegakkannya. Seperti accessor + mutators, operator overloading, warisan publik, downcasting, dll , sering disalahgunakan, tetapi itu tidak berarti kata kunci tidak memiliki, atau lebih buruk, tujuan yang buruk.

Lihat Konrad Rudolph 's pesan di thread lain, atau jika Anda lebih suka melihat entri yang relevan dalam C ++ FAQ.


... dan entri yang sesuai dalam FQA: yosefk.com/c++fqa/friend.html#fqa-14.2
Josh Lee

28
+1 untuk " Menggunakan teman bukan tentang melanggar enkapsulasi, tetapi sebaliknya tentang menegakkannya "
Nawaz

2
Masalah pendapat semantik saya pikir; dan mungkin terkait dengan situasi yang tepat dalam pertanyaan. Membuat kelas a friendmemberikannya akses ke SEMUA anggota pribadi Anda, bahkan jika Anda hanya perlu membiarkannya mengatur salah satunya. Ada argumen kuat yang dibuat bahwa membuat bidang tersedia untuk kelas lain yang tidak memerlukannya dianggap sebagai "melanggar enkapsulasi." Ada tempat di C ++ di mana diperlukan (operator kelebihan beban), tetapi ada tradeoff enkapsulasi yang perlu dipertimbangkan dengan hati-hati. Tampaknya para desainer C # merasa bahwa tradeoff tidak sepadan.
GrandOpener

3
Enkapsulasi adalah tentang menegakkan invarian. Ketika kami mendefinisikan kelas sebagai teman bagi yang lain, kami mengatakan secara eksplisit bahwa kedua kelas sangat terkait dengan pengelolaan invarian dari yang pertama. Memiliki kelas membuat teman masih lebih baik daripada mengekspos semua atribut secara langsung (sebagai bidang publik) atau melalui setter.
Luc Hermitte

1
Persis. Ketika Anda ingin menggunakan teman tetapi tidak bisa, Anda terpaksa menggunakan internal atau publik, yang jauh lebih terbuka untuk dicobai oleh hal-hal yang seharusnya tidak. Terutama di lingkungan tim.
tukik

50

Untuk info, lain terkait-tapi-tidak-cukup-the-sama hal di NET adalah [InternalsVisibleTo], yang memungkinkan perakitan menunjuk lain perakitan (seperti tes unit perakitan) yang (efektif) memiliki "internal" akses ke jenis / anggota di perakitan asli.


3
petunjuk yang baik, karena mungkin sering orang mencari teman ingin menemukan cara untuk menguji unit dengan kemampuan untuk memiliki lebih banyak 'pengetahuan' internal.
SwissCoder

14

Anda harus dapat menyelesaikan hal-hal yang sama seperti yang digunakan "teman" di C ++ dengan menggunakan antarmuka di C #. Ini mengharuskan Anda untuk secara eksplisit menentukan anggota mana yang disahkan antara dua kelas, yang merupakan pekerjaan ekstra tetapi juga dapat membuat kode lebih mudah dimengerti.

Jika seseorang memiliki contoh penggunaan "teman" yang masuk akal yang tidak dapat disimulasikan menggunakan antarmuka, silakan bagikan! Saya ingin lebih memahami perbedaan antara C ++ dan C #.


Poin yang bagus. Apakah Anda memiliki kesempatan untuk melihat pertanyaan SO sebelumnya (ditanyakan oleh monoksida dan ditautkan di atas ?. Saya akan tertarik pada cara terbaik untuk menyelesaikannya dalam C #?
Ash

Saya tidak yakin bagaimana melakukannya dalam C #, tapi saya pikir jawaban Jon Skeet untuk pertanyaan monoksida mengarah ke arah yang benar. C # memungkinkan kelas bersarang. Saya belum benar-benar mencoba untuk kode keluar dan mengkompilasi solusi. :)
Parappa

Saya merasa bahwa pola mediator adalah contoh yang baik di mana teman akan bersikap baik. Saya refactor Formulir saya untuk memiliki mediator. Saya ingin formulir saya dapat memanggil metode "event" seperti dalam mediator, tetapi saya tidak benar-benar ingin kelas lain memanggil metode "event" tersebut. Fitur "Teman" akan memberikan formulir akses ke metode tanpa membukanya untuk segala hal lain di majelis.
Vaccano

3
Masalah dengan pendekatan antarmuka untuk mengganti 'Teman' adalah untuk objek untuk mengekspos antarmuka, itu berarti siapa pun dapat melemparkan objek ke antarmuka itu dan memiliki akses ke metode! Mencakup antarmuka untuk internal, dilindungi, atau pribadi tidak berfungsi baik jika itu berhasil, Anda bisa menggunakan metode ini! Pikirkan tentang seorang ibu dan anak di pusat perbelanjaan. Publik adalah semua orang di dunia. Internal hanyalah orang-orang di mal. Pribadi hanyalah ibu atau anak. 'Teman' adalah sesuatu antara ibu dan anak perempuan saja.
Mark A. Donohoe

1
Inilah satu: stackoverflow.com/questions/5921612/... Saat saya datang dari latar belakang C ++, kurangnya teman membuat saya gila hampir setiap hari. Dalam beberapa tahun saya dengan C #, saya telah membuat ratusan metode publik di mana mereka bisa menjadi pribadi dan lebih aman, semua karena kurangnya teman. Secara pribadi saya pikir teman adalah hal paling penting yang harus ditambahkan ke .NET
kaalus

14

Dengan friend C ++ desainer memiliki kontrol yang tepat atas siapa anggota * pribadi terkena. Tapi, dia terpaksa memaparkan setiap anggota pribadi.

Dengan internalC # desainer memiliki kontrol yang tepat atas set anggota pribadi yang dia ungkap. Jelas, dia dapat mengekspos hanya satu anggota pribadi. Tapi, itu akan terkena semua kelas di majelis.

Biasanya, seorang perancang ingin memaparkan hanya beberapa metode pribadi ke beberapa kelas lain yang dipilih. Sebagai contoh, dalam pola pabrik kelas mungkin diinginkan bahwa kelas C1 hanya dipakai oleh pabrik kelas CF1. Oleh karena itu kelas C1 dapat memiliki konstruktor yang dilindungi dan CF1 pabrik kelas teman.

Seperti yang Anda lihat, kami memiliki 2 dimensi di mana enkapsulasi dapat dilanggar. friendmelanggar itu sepanjang satu dimensi, internalapakah itu sepanjang yang lain. Mana yang merupakan pelanggaran yang lebih buruk dalam konsep enkapsulasi? Sulit untuk dikatakan. Tetapi akan menyenangkan untuk memiliki keduanya frienddan internaltersedia. Selain itu, tambahan yang bagus untuk keduanya adalah jenis kata kunci ke-3, yang akan digunakan berdasarkan anggota-oleh-anggota (suka internal) dan menentukan kelas target (seperti friend).

* Untuk singkatnya saya akan menggunakan "pribadi" bukan "pribadi dan / atau dilindungi".

- Nick


13

Bahkan, C # memberikan kemungkinan untuk mendapatkan perilaku yang sama dengan cara OOP murni tanpa kata-kata khusus - itu adalah antarmuka pribadi.

Sejauh pertanyaan. Apa yang dimaksud dengan C # yang setara dengan teman? ditandai sebagai duplikat untuk artikel ini dan tidak ada seorang pun di sana yang mengusulkan realisasi yang sangat bagus - saya akan menunjukkan jawaban pada kedua pertanyaan di sini.

Gagasan utama diambil dari sini: Apa itu antarmuka pribadi?

Katakanlah, kita memerlukan beberapa kelas yang dapat mengatur instance dari kelas lain dan memanggil beberapa metode khusus pada mereka. Kami tidak ingin memberikan kemungkinan untuk memanggil metode ini ke kelas lain. Ini persis sama dengan apa yang dilakukan teman c ++ kata kunci di dunia c ++.

Saya pikir contoh yang baik dalam praktek nyata bisa menjadi pola Full State Machine di mana beberapa kontroler memperbarui objek keadaan saat ini dan beralih ke objek keadaan lain bila perlu.

Anda bisa:

  • Cara termudah dan terburuk untuk membuat metode Pembaruan () publik - harap semua orang mengerti mengapa itu buruk.
  • Cara selanjutnya adalah menandainya sebagai internal. Ini cukup baik jika Anda menempatkan kelas Anda ke majelis lain tetapi bahkan kemudian setiap kelas di majelis itu dapat memanggil setiap metode internal.
  • Gunakan antarmuka pribadi / terlindungi - dan saya mengikuti cara ini.

Controller.cs

public class Controller
{
    private interface IState
    {
        void Update();
    }

    public class StateBase : IState
    {
        void IState.Update() {  }
    }

    public Controller()
    {
        //it's only way call Update is to cast obj to IState
        IState obj = new StateBase();
        obj.Update();
    }
}

Program.cs

class Program
{
    static void Main(string[] args)
    {
        //it's impossible to write Controller.IState p = new Controller.StateBase();
        //Controller.IState is hidden
        var p = new Controller.StateBase();
        //p.Update(); //is not accessible
    }
}

Nah, bagaimana dengan warisan?

Kita perlu menggunakan teknik yang dijelaskan dalam Karena implementasi anggota antarmuka yang eksplisit tidak dapat dideklarasikan secara virtual dan tandai IState sebagai yang dilindungi untuk memberikan kemungkinan untuk diturunkan dari Pengontrol juga.

Controller.cs

public class Controller
{
    protected interface IState
    {
        void Update();
    }

    public class StateBase : IState
    {
        void IState.Update() { OnUpdate(); }
        protected virtual void OnUpdate()
        {
            Console.WriteLine("StateBase.OnUpdate()");
        }
    }

    public Controller()
    {
        IState obj = new PlayerIdleState();
        obj.Update();
    }
}

PlayerIdleState.cs

public class PlayerIdleState: Controller.StateBase
{
    protected override void OnUpdate()
    {
        base.OnUpdate();
        Console.WriteLine("PlayerIdleState.OnUpdate()");
    }
}

Dan akhirnya contoh cara menguji class throw throw inheritance: ControllerTest.cs

class ControllerTest: Controller
{
    public ControllerTest()
    {
        IState testObj = new PlayerIdleState();
        testObj.Update();
    }
}

Semoga saya mencakup semua kasus dan jawaban saya bermanfaat.


Ini adalah jawaban yang luar biasa yang membutuhkan lebih banyak upvotes.
KthProg

@max_cn Ini adalah solusi yang sangat bagus, tapi saya tidak bisa melihat cara untuk mendapatkan ini bekerja dengan kerangka kerja mengejek untuk pengujian unit. Ada saran?
Steve Land

Saya sedikit bingung. Jika saya ingin kelas eksternal dapat memanggil Pembaruan dalam contoh pertama Anda (antarmuka pribadi), bagaimana saya memodifikasi Pengontrol untuk memungkinkan hal itu terjadi?
AnthonyMonterrosa

@Steve Land, Anda dapat menggunakan warisan seperti pada ControllerTest.cs ditunjukkan. Tambahkan metode publik yang diperlukan dalam kelas uji turunan dan Anda akan memiliki akses untuk semua staf yang dilindungi yang Anda miliki di kelas dasar.
max_cn

@AnthonyMonterrosa, kami menyembunyikan Pembaruan dari SEMUA sumber daya eksternal, jadi mengapa Anda ingin membaginya dengan seseorang;)? Tentu Anda dapat menambahkan beberapa metode publik untuk mengakses anggota pribadi tetapi itu akan menjadi seperti pintu belakang untuk kelas lain. Bagaimanapun, jika Anda memerlukannya untuk tujuan pengujian - gunakan pewarisan untuk membuat sesuatu seperti kelas ControllerTest untuk membuat kasus pengujian di sana.
max_cn

9

Teman sangat berguna saat menulis unit test.

Sementara itu datang dengan biaya mencemari deklarasi kelas Anda sedikit, itu juga merupakan pengingat yang dikompilasi oleh kompiler tentang tes apa yang mungkin benar-benar peduli dengan keadaan internal kelas.

Idiom yang sangat berguna dan bersih yang saya temukan adalah ketika saya memiliki kelas pabrik, menjadikan mereka teman dari barang yang mereka buat yang memiliki konstruktor yang dilindungi. Lebih khusus, ini adalah ketika saya memiliki satu pabrik yang bertanggung jawab untuk membuat objek rendering pencocokan untuk objek report report, rendering ke lingkungan tertentu. Dalam hal ini Anda memiliki satu titik pengetahuan tentang hubungan antara kelas penulis laporan (hal-hal seperti blok gambar, band tata letak, header halaman dll) dan objek rendering yang cocok.


Saya akan membuat komentar tentang "teman" yang berguna untuk kelas pabrik, tetapi saya senang melihat bahwa seseorang telah membuatnya.
Edward Robertson

9

C # tidak memiliki kata kunci "teman" karena alasan yang sama dengan kehancuran deterministik yang hilang. Mengubah konvensi membuat orang merasa pintar, seolah-olah cara baru mereka lebih unggul daripada cara lama orang lain. Ini semua tentang kebanggaan.

Mengatakan bahwa "kelas teman buruk" sama pendeknya dengan pernyataan tidak memenuhi syarat lainnya seperti "jangan gunakan gotos" atau "Linux lebih baik daripada Windows".

Kata kunci "teman" yang dikombinasikan dengan kelas proxy adalah cara yang bagus untuk hanya mengekspos bagian tertentu dari kelas ke kelas tertentu lainnya. Kelas proxy dapat bertindak sebagai penghalang tepercaya terhadap semua kelas lainnya. "publik" tidak mengizinkan penargetan seperti itu, dan menggunakan "dilindungi" untuk mendapatkan efek dengan warisan adalah canggung jika benar-benar tidak ada konseptual "adalah" hubungan.


C # hilang karena kerusakan deterministik karena lebih lambat dari pengumpulan sampah. Penghancuran deterministik membutuhkan waktu untuk setiap objek yang dibuat, sedangkan pengumpulan sampah hanya membutuhkan waktu untuk objek yang saat ini hidup. Dalam kebanyakan kasus, ini lebih cepat, dan lebih banyak objek berumur pendek ada dalam sistem, pengumpulan sampah relatif lebih cepat.
Edward Robertson

1
@ Edward Robertson Penghancuran deterministik tidak memerlukan waktu apa pun kecuali ada destruktor yang ditentukan. Tetapi dalam kasus ini, pengumpulan sampah jauh lebih buruk, karena Anda harus menggunakan finalizer, yang lebih lambat dan tidak deterministik.
kaalus

1
... dan memori bukan satu-satunya sumber daya. Orang sering bertindak seolah itu benar.
cubuspl42

8

Anda bisa mendekati C ++ "teman" dengan kata kunci C # "internal" .


3
Tutup, tapi tidak cukup di sana. Saya kira itu tergantung pada bagaimana Anda menyusun majelis / kelas Anda, tetapi akan lebih elegan untuk meminimalkan jumlah kelas yang diberikan akses menggunakan teman. Sebagai contoh dalam perakitan utilitas / penolong, tidak semua kelas harus memiliki akses ke anggota internal.
Ash

3
@ Ash: Anda perlu membiasakan diri, tetapi begitu Anda melihat majelis sebagai blok bangunan dasar aplikasi Anda, internalakan lebih masuk akal dan memberikan pertukaran yang sangat baik antara enkapsulasi dan utilitas. Akses standar Java bahkan lebih baik.
Konrad Rudolph

7

Ini sebenarnya bukan masalah dengan C #. Ini adalah batasan mendasar dalam IL. C # dibatasi oleh ini, seperti bahasa .Net lainnya yang berupaya diverifikasi. Batasan ini juga termasuk kelas terkelola yang didefinisikan dalam C ++ / CLI ( Spec bagian 20.5 ).

Yang sedang berkata saya pikir Nelson memiliki penjelasan yang baik tentang mengapa ini adalah hal yang buruk.


7
-1. friendbukan hal yang buruk; sebaliknya, itu jauh lebih baik daripada internal. Dan penjelasan Nelson buruk dan salah.
Nawaz

4

Berhentilah membuat alasan untuk pembatasan ini. teman buruk, tetapi internal baik? mereka adalah hal yang sama, hanya teman yang memberi Anda kontrol yang lebih tepat atas siapa yang diizinkan untuk mengakses dan siapa yang tidak.

Ini untuk menegakkan paradigma enkapsulasi? jadi Anda harus menulis metode accessor dan sekarang apa? bagaimana Anda seharusnya menghentikan semua orang (kecuali metode kelas B) dari memanggil metode ini? Anda tidak dapat, karena Anda tidak dapat mengontrol ini juga, karena kehilangan "teman".

Tidak ada bahasa pemrograman yang sempurna. C # adalah salah satu bahasa terbaik yang pernah saya lihat, tetapi membuat alasan konyol untuk fitur yang hilang tidak membantu siapa pun. Dalam C ++, saya melewatkan sistem event / delegate yang mudah, refleksi (+ otomatis de / serialisasi) dan foreach, tetapi dalam C # Saya kehilangan operator yang berlebihan (ya, terus mengatakan kepada saya bahwa Anda tidak memerlukannya), parameter default, konstanta yang tidak dapat dielakkan, pewarisan berganda (yeah, terus katakan padaku bahwa Anda tidak membutuhkannya dan antarmuka merupakan pengganti yang memadai) dan kemampuan untuk memutuskan untuk menghapus instance dari memori (tidak, ini tidak terlalu buruk kecuali Anda adalah seorang Tinkerer)


Dalam C # Anda dapat membebani sebagian besar operator. Anda tidak dapat membebani operator penugasan, tetapi di sisi lain Anda dapat mengesampingkan ||/ &&membuat hubungan arus pendek tetap. Parameter default ditambahkan di C # 4.
CodesInChaos

2

Ada InternalsVisibleToAttribute sejak. Net 3 tapi saya menduga mereka hanya menambahkannya untuk melayani rakitan pengujian setelah munculnya unit testing. Saya tidak dapat melihat banyak alasan lain untuk menggunakannya.

Ini bekerja di tingkat perakitan tetapi ia melakukan pekerjaan di mana internal tidak; yaitu, di mana Anda ingin mendistribusikan sebuah majelis tetapi menginginkan majelis yang tidak terdistribusi memiliki akses istimewa untuk itu.

Cukup benar mereka membutuhkan teman perakitan untuk memiliki kunci yang kuat untuk menghindari seseorang membuat teman pura-pura bersama dengan teman-teman Anda yang dilindungi.


2

Saya telah membaca banyak komentar cerdas tentang kata kunci "teman" & saya setuju apa itu hal yang berguna, tetapi saya pikir kata kunci "internal" kurang berguna, & mereka berdua masih buruk untuk pemrograman OO murni.

Apa yang kita miliki? (Mengatakan tentang "teman" Saya juga mengatakan tentang "internal")

  • menggunakan "teman" membuat kode kurang murni untuk oo?
  • Iya;

  • tidak menggunakan "teman" membuat kode lebih baik?

  • tidak, kita masih perlu membuat hubungan pribadi antar kelas, & kita bisa melakukannya hanya jika kita menghancurkan enkapsulasi kita yang indah, jadi itu juga tidak baik, aku bisa mengatakan apa yang lebih jahat daripada menggunakan "teman".

Menggunakan teman membuat beberapa masalah lokal, tidak menggunakannya membuat masalah bagi pengguna perpustakaan-kode.

solusi umum yang baik untuk bahasa pemrograman yang saya lihat seperti ini:

// c++ style
class Foo {
  public_for Bar:
    void addBar(Bar *bar) { }
  public:
  private:
  protected:
};

// c#
class Foo {
    public_for Bar void addBar(Bar bar) { }
}

Apa yang Anda pikirkan? Saya pikir itu solusi berorientasi objek yang paling umum & murni. Anda dapat membuka akses metode apa pun yang Anda pilih ke kelas yang Anda inginkan.


Saya bukan penjaga OOP, tetapi sepertinya itu mengatasi keluhan semua orang terhadap teman dan internal. Tetapi saya menyarankan menggunakan atribut untuk menghindari perluasan sintaks C #: [PublicFor Bar] batal addBar (bilah Bar) {} ... Bukan berarti saya yakin itu masuk akal; Saya tidak tahu bagaimana atribut bekerja atau apakah mereka bisa mengutak-atik aksesibilitas (mereka melakukan banyak hal "ajaib" lainnya).
Grault

2

Saya hanya akan menjawab pertanyaan "Bagaimana".

Ada begitu banyak jawaban di sini, namun saya ingin mengusulkan semacam "pola desain" untuk mencapai fitur itu. Saya akan menggunakan mekanisme bahasa sederhana, yang meliputi:

  • Antarmuka
  • Kelas bersarang

Sebagai contoh, kami memiliki 2 kelas utama: Mahasiswa dan Universitas. Siswa memiliki IPK yang hanya dapat diakses oleh universitas. Ini kodenya:

public interface IStudentFriend
{
    Student Stu { get; set; }
    double GetGPS();
}

public class Student
{
    // this is private member that I expose to friend only
    double GPS { get; set; }
    public string Name { get; set; }

    PrivateData privateData;

    public Student(string name, double gps) => (GPS, Name, privateData) = (gps, name, new PrivateData(this);

    // No one can instantiate this class, but Student
    // Calling it is possible via the IStudentFriend interface
    class PrivateData : IStudentFriend
    {
        public Student Stu { get; set; }

        public PrivateData(Student stu) => Stu = stu;
        public double GetGPS() => Stu.GPS;
    }

    // This is how I "mark" who is Students "friend"
    public void RegisterFriend(University friend) => friend.Register(privateData);
}

public class University
{
    var studentsFriends = new List<IStudentFriend>();

    public void Register(IStudentFriend friendMethod) => studentsFriends.Add(friendMethod);

    public void PrintAllStudentsGPS()
    {
        foreach (var stu in studentsFriends)
            Console.WriteLine($"{stu.Stu.Name}: stu.GetGPS()");
    }
}

public static void Main(string[] args)
{
    var Technion = new University();
    var Alex     = new Student("Alex", 98);
    var Jo       = new Student("Jo", 91);

    Alex.RegisterFriend(Technion);
    Jo.RegisterFriend(Technion);
    Technion.PrintAllStudentsGPS();

    Console.ReadLine();
}

1

Saya menduga itu ada hubungannya dengan model kompilasi C # - membangun IL kompilasi JIT yang saat runtime. yaitu: alasan yang sama bahwa obat generik C # pada dasarnya berbeda dengan obat generik C ++.


Saya pikir kompiler dapat mendukungnya dengan mudah setidaknya antara kelas dalam suatu perakitan. Hanya masalah akses santai memeriksa kelas teman di kelas target. Teman di antara kelas di majelis eksternal mungkin perlu perubahan yang lebih mendasar pada CLR.
Ash

1

Anda dapat merahasiakannya dan menggunakan fungsi refleksi untuk memanggil. Kerangka uji dapat melakukan ini jika Anda memintanya untuk menguji fungsi pribadi


Kecuali Anda bekerja pada aplikasi Silverlight.
kaalus

1

Saya biasa menggunakan teman, dan saya pikir itu bukan pelanggaran OOP atau tanda cacat desain apa pun. Ada beberapa tempat yang merupakan cara paling efisien untuk tujuan yang tepat dengan jumlah kode paling sedikit.

Salah satu contoh nyata adalah ketika membuat rakitan antarmuka yang menyediakan antarmuka komunikasi ke beberapa perangkat lunak lain. Secara umum ada beberapa kelas kelas berat yang menangani kompleksitas protokol dan kekhasan rekan, dan menyediakan model penghubung / baca / tulis / penerus / relatif yang relatif sederhana yang melibatkan pengiriman pesan dan pemberitahuan antara aplikasi klien dan perakitan. Pesan-pesan / notifikasi tersebut harus dibungkus dalam kelas. Atribut umumnya perlu dimanipulasi oleh perangkat lunak protokol karena pembuatnya, tetapi banyak hal yang harus tetap hanya-baca untuk dunia luar.

Sangat konyol untuk menyatakan bahwa ini merupakan pelanggaran OOP untuk kelas protokol / "pembuat" untuk memiliki akses intim ke semua kelas yang dibuat - kelas pembuat harus menggigit setiap bit data di jalan. Apa yang saya temukan paling penting adalah untuk meminimalkan semua baris BS tambahan kode yang biasanya mengarah ke model "OOP for Sake". Spaghetti ekstra hanya membuat lebih banyak bug.

Apakah orang tahu bahwa Anda dapat menerapkan kata kunci internal di tingkat atribut, properti, dan metode? Ini bukan hanya untuk deklarasi kelas tingkat atas (meskipun sebagian besar contoh tampaknya menunjukkan itu.)

Jika Anda memiliki kelas C ++ yang menggunakan kata kunci teman, dan ingin meniru itu dalam kelas C #: 1. mendeklarasikan kelas C # publik 2. mendeklarasikan semua atribut / properti / metode yang dilindungi dalam C ++ dan karenanya dapat diakses oleh teman-teman sebagai internal di C # 3. buat properti hanya baca untuk akses publik ke semua atribut dan properti internal

Saya setuju itu tidak 100% sama dengan teman, dan unit test adalah contoh yang sangat berharga dari kebutuhan akan sesuatu seperti teman (seperti juga kode logging log analyzer). Namun internal memberikan eksposur ke kelas yang ingin Anda ekspos, dan [InternalVisibleTo ()] menangani sisanya - sepertinya ia dilahirkan khusus untuk unit test.

Sejauh teman "menjadi lebih baik karena Anda dapat secara eksplisit mengontrol kelas mana yang memiliki akses" - apa gerangan yang dilakukan sekelompok tersangka kelas jahat di majelis yang sama? Partisi majelis Anda!


1

Persahabatan dapat disimulasikan dengan memisahkan antarmuka dan implementasi. Idenya adalah: " Membutuhkan contoh nyata tetapi membatasi akses konstruksi dari contoh itu ".

Sebagai contoh

interface IFriend { }

class Friend : IFriend
{
    public static IFriend New() { return new Friend(); }
    private Friend() { }

    private void CallTheBody() 
    {  
        var body = new Body();
        body.ItsMeYourFriend(this);
    }
}

class Body
{ 
    public void ItsMeYourFriend(Friend onlyAccess) { }
}

Terlepas dari kenyataan bahwa ItsMeYourFriend()hanya Friendkelas publik yang dapat mengaksesnya, karena tidak ada orang lain yang bisa mendapatkan contoh nyata dari Friendkelas tersebut. Ini memiliki konstruktor pribadi, sedangkan New()metode pabrik mengembalikan antarmuka.

Lihat artikel saya Teman dan anggota antarmuka internal tanpa biaya dengan pengkodean ke antarmuka untuk detail.


Sedihnya persahabatan tidak dapat disimulasikan dengan cara apa pun yang memungkinkan. Lihat pertanyaan ini: stackoverflow.com/questions/5921612/…
kaalus

1

Beberapa orang menyarankan bahwa hal-hal dapat di luar kendali dengan menggunakan teman. Saya setuju, tapi itu tidak mengurangi kegunaannya. Saya tidak yakin bahwa teman tentu saja menyakiti paradigma OO lebih daripada membuat semua anggota kelas Anda publik. Tentu saja bahasa akan memungkinkan Anda untuk membuat semua anggota Anda menjadi publik, tetapi itu adalah programmer yang disiplin yang menghindari jenis pola desain itu. Demikian juga programmer yang disiplin akan menggunakan teman untuk kasus-kasus tertentu yang masuk akal. Saya merasa internal terlalu banyak mengungkapkan dalam beberapa kasus. Mengapa mengekspos kelas atau metode untuk semua yang ada di majelis?

Saya memiliki halaman ASP.NET yang mewarisi halaman dasar saya sendiri, yang pada gilirannya mewarisi System.Web.UI.Page. Di halaman ini, saya memiliki beberapa kode yang menangani pelaporan kesalahan pengguna akhir untuk aplikasi dalam metode yang dilindungi

ReportError("Uh Oh!");

Sekarang, saya memiliki kontrol pengguna yang terdapat di halaman. Saya ingin kontrol pengguna dapat memanggil metode pelaporan kesalahan di halaman.

MyBasePage bp = Page as MyBasePage;
bp.ReportError("Uh Oh");

Tidak dapat melakukannya jika metode ReportError dilindungi. Saya bisa membuatnya internal, tetapi terkena kode apa pun di majelis. Saya hanya ingin itu terkena elemen UI yang merupakan bagian dari halaman saat ini (termasuk kontrol anak). Lebih khusus, saya ingin kelas kontrol basis saya untuk menentukan metode pelaporan kesalahan yang sama persis, dan cukup memanggil metode di halaman dasar.

protected void ReportError(string str) {
    MyBasePage bp = Page as MyBasePage;
    bp.ReportError(str);
}

Saya percaya bahwa sesuatu seperti teman dapat berguna dan diimplementasikan dalam bahasa tanpa membuat bahasa kurang "OO" seperti, mungkin sebagai atribut, sehingga Anda dapat memiliki kelas atau metode menjadi teman untuk kelas atau metode tertentu, yang memungkinkan pengembang untuk memberikan akses spesifik. Mungkin sesuatu seperti ... (kode semu)

[Friend(B)]
class A {

    AMethod() { }

    [Friend(C)]
    ACMethod() { }
}

class B {
    BMethod() { A.AMethod() }
}

class C {
    CMethod() { A.ACMethod() }
}

Dalam kasus contoh saya sebelumnya mungkin memiliki sesuatu seperti berikut (orang dapat berdebat semantik, tapi saya hanya mencoba untuk menyampaikan ide):

class BasePage {

    [Friend(BaseControl.ReportError(string)]
    protected void ReportError(string str) { }
}

class BaseControl {
    protected void ReportError(string str) {
        MyBasePage bp = Page as MyBasePage;
        bp.ReportError(str);
    }
}

Seperti yang saya lihat, konsep teman tidak memiliki risiko lebih dari itu daripada membuat sesuatu menjadi publik, atau menciptakan metode atau properti publik untuk mengakses anggota. Jika ada teman yang memungkinkan tingkat rincian aksesibilitas data yang lain dan memungkinkan Anda untuk mempersempit aksesibilitas itu daripada meluaskannya dengan internal atau publik.


0

Jika Anda bekerja dengan C ++ dan Anda menemukan diri Anda menggunakan kata kunci teman, itu adalah indikasi yang sangat kuat, bahwa Anda memiliki masalah desain, karena mengapa kelas perlu mengakses anggota pribadi dari kelas lain ??


Saya setuju. Saya tidak pernah membutuhkan kata kunci teman. Ini umumnya digunakan untuk memecahkan masalah perawatan yang kompleks.
Edouard A.

7
Overloading operator, ada yang ??
We Are All Monica

3
berapa kali Anda menemukan kasus yang baik untuk operator kelebihan beban secara serius?
bashmohandes

8
Saya akan memberi Anda kasus yang sangat sederhana yang sepenuhnya menghilangkan komentar 'masalah desain' Anda. Orangtua-Anak. Orang tua memiliki anak-anaknya. Seorang anak dalam koleksi 'Anak' orang tua dimiliki oleh orang tua itu. Penyetel properti Orang Tua pada Anak tidak boleh diselesaikan oleh sembarang orang. Itu perlu ditetapkan kapan dan hanya ketika anak ditambahkan ke koleksi Anak orang tua. Jika itu publik, siapa pun dapat mengubahnya. Internal: siapa pun di majelis itu. Pribadi? Tidak ada Menjadikan setter sebagai teman bagi orang tua menghilangkan kasus-kasus itu dan masih membuat kelas Anda terpisah, tetapi terkait erat. Sabas!!
Mark A. Donohoe

2
Ada banyak kasus seperti itu, seperti sebagian besar pola dasar manajer / dikelola. Ada pertanyaan lain tentang SO dan tidak ada yang tahu cara melakukannya tanpa teman. Tidak yakin apa yang membuat orang berpikir bahwa satu kelas "akan selalu mencukupi". Terkadang lebih dari satu kelas perlu bekerja bersama.
kaalus

0

Bsd

Dinyatakan bahwa, teman-teman menyakiti OOness murni. Yang saya setuju.

Juga dinyatakan bahwa teman-teman membantu enkapsulasi, yang juga saya setujui.

Saya pikir persahabatan harus ditambahkan ke metodologi OO, tetapi tidak cukup seperti di C ++. Saya ingin memiliki beberapa bidang / metode yang dapat diakses oleh kelas teman saya, tetapi saya TIDAK ingin mereka mengakses SEMUA bidang / metode saya. Seperti dalam kehidupan nyata, saya akan membiarkan teman-teman saya mengakses lemari es pribadi saya tetapi saya tidak akan membiarkan mereka mengakses rekening bank saya.

Seseorang dapat mengimplementasikan itu sebagai diikuti

    class C1
    {
        private void MyMethod(double x, int i)
        {
            // some code
        }
        // the friend class would be able to call myMethod
        public void MyMethod(FriendClass F, double x, int i)
        {
            this.MyMethod(x, i);
        }
        //my friend class wouldn't have access to this method 
        private void MyVeryPrivateMethod(string s)
        {
            // some code
        }
    }
    class FriendClass
    {
        public void SomeMethod()
        {
            C1 c = new C1();
            c.MyMethod(this, 5.5, 3);
        }
    }

Itu tentu saja akan menghasilkan peringatan kompiler, dan akan melukai intellisense. Tapi itu akan berhasil.

Sebagai catatan, saya berpikir bahwa seorang programmer yang percaya diri harus melakukan unit pengujian tanpa mengakses anggota pribadi. ini cukup di luar ruang lingkup, tetapi cobalah membaca tentang TDD. namun, jika Anda masih ingin melakukannya (memiliki c ++ seperti teman) coba sesuatu seperti

#if UNIT_TESTING
        public
#else
        private
#endif
            double x;

jadi Anda menulis semua kode Anda tanpa mendefinisikan UNIT_TESTING dan ketika Anda ingin melakukan pengujian unit Anda menambahkan #define UNIT_TESTING ke baris pertama file (dan menulis semua kode yang melakukan pengujian unit di bawah #jika UNIT_TESTING). Itu harus ditangani dengan hati-hati.

Karena saya pikir pengujian unit adalah contoh buruk untuk penggunaan teman, saya akan memberikan contoh mengapa saya pikir teman bisa baik. Misalkan Anda memiliki sistem melanggar (kelas). Dengan menggunakan, sistem melanggar usang dan perlu direnovasi. Sekarang, Anda ingin hanya mekanik berlisensi yang akan memperbaikinya. Untuk membuat contoh yang kurang sepele saya akan mengatakan bahwa montir akan menggunakan obeng pribadinya untuk memperbaikinya. Itu sebabnya kelas mekanik harus menjadi teman kelas breakingSystem.


2
Parameter FriendClass itu tidak berfungsi sebagai kontrol akses: Anda dapat menelepon c.MyMethod((FriendClass) null, 5.5, 3)dari kelas mana pun.
Jesse McGrew

0

Persahabatan juga dapat disimulasikan dengan menggunakan "agen" - beberapa kelas batin. Pertimbangkan contoh berikut:

public class A // Class that contains private members
{
  private class Accessor : B.BAgent // Implement accessor part of agent.
  {
    private A instance; // A instance for access to non-static members.
    static Accessor() 
    { // Init static accessors.
      B.BAgent.ABuilder = Builder;
      B.BAgent.PrivateStaticAccessor = StaticAccessor;
    }
    // Init non-static accessors.
    internal override void PrivateMethodAccessor() { instance.SomePrivateMethod(); }
    // Agent constructor for non-static members.
    internal Accessor(A instance) { this.instance = instance; }
    private static A Builder() { return new A(); }
    private static void StaticAccessor() { A.PrivateStatic(); }
  }
  public A(B friend) { B.Friendship(new A.Accessor(this)); }
  private A() { } // Private constructor that should be accessed only from B.
  private void SomePrivateMethod() { } // Private method that should be accessible from B.
  private static void PrivateStatic() { } // ... and static private method.
}
public class B
{
  // Agent for accessing A.
  internal abstract class BAgent
  {
    internal static Func<A> ABuilder; // Static members should be accessed only by delegates.
    internal static Action PrivateStaticAccessor;
    internal abstract void PrivateMethodAccessor(); // Non-static members may be accessed by delegates or by overrideable members.
  }
  internal static void Friendship(BAgent agent)
  {
    var a = BAgent.ABuilder(); // Access private constructor.
    BAgent.PrivateStaticAccessor(); // Access private static method.
    agent.PrivateMethodAccessor(); // Access private non-static member.
  }
}

Itu bisa jauh lebih sederhana ketika digunakan untuk akses hanya ke anggota statis. Manfaat untuk implementasi semacam itu adalah bahwa semua tipe dideklarasikan di dalam lingkup kelas pertemanan dan, tidak seperti antarmuka, itu memungkinkan anggota statis untuk diakses.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.