Apakah enkapsulasi masih menjadi salah satu gajah OOP?


97

Enkapsulasi memberitahu saya untuk membuat semua atau hampir semua bidang menjadi pribadi dan mengeksposnya dengan getter / setter. Tapi sekarang perpustakaan seperti Lombok muncul yang memungkinkan kita untuk mengekspos semua bidang pribadi dengan satu penjelasan singkat @Data. Ini akan membuat getter, setter dan pengaturan konstruktor untuk semua bidang pribadi.

Bisakah seseorang menjelaskan kepada saya apa arti menyembunyikan semua bidang sebagai pribadi dan setelah itu mengekspos semuanya dengan teknologi ekstra? Mengapa kita tidak hanya menggunakan bidang publik saja? Saya merasa kami telah berjalan jauh dan sulit hanya untuk kembali ke titik awal.

Ya, ada teknologi lain yang bekerja melalui getter dan setter. Dan kita tidak dapat menggunakannya melalui bidang publik sederhana. Tetapi teknologi ini muncul hanya karena kami memiliki banyak properti - bidang pribadi di belakang pengambil / penetap publik. Jika kita tidak memiliki properti, teknologi ini akan mengembangkan cara lain dan akan mendukung bidang publik. Dan semuanya akan sederhana dan kita tidak akan membutuhkan Lombok sekarang.

Apa arti dari keseluruhan siklus ini? Dan apakah enkapsulasi benar - benar masuk akal sekarang dalam pemrograman kehidupan nyata?


19
"It will create getters, setters and setting constructors for all private fields."- Cara Anda menjelaskan alat ini, kedengarannya seperti itu adalah mempertahankan enkapsulasi. (Setidaknya dalam pengertian model yang longgar, otomatis, dan agak anemia.) Jadi, apa sebenarnya masalahnya?
David

78
Enkapsulasi menyembunyikan implementasi internal objek di belakang kontrak publiknya (biasanya, sebuah antarmuka). Getters dan setters melakukan hal yang sebaliknya - mereka mengekspos internal objek, jadi masalahnya ada di getter / setters, bukan enkapsulasi.

22
@VinceEmigh kelas data tidak memiliki enkapsulasi . Contoh dari mereka adalah nilai - nilai dalam arti persis seperti primitif
Caleth

10
@VinceEmigh menggunakan JavaBeans bukan OO , itu Prosedural . Bahwa literatur menyebut mereka "Objek" adalah kesalahan sejarah.
Caleth

28
Saya telah banyak memikirkan hal ini selama bertahun-tahun; Saya pikir ini adalah kasus di mana maksud OOP berbeda dari implementasi. Setelah mempelajari SmallTalk, jelas apa enkapsulasi OOP dimaksudkan untuk menjadi (yaitu, setiap kelas menjadi seperti komputer independen, dengan metode sebagai protokol bersama), meskipun untuk alasan yang sejauh ini tidak diketahui oleh saya, sudah pasti populer untuk membuat ini objek pengambil / penyetel yang tidak menawarkan enkapsulasi konseptual (tidak menyembunyikan apa pun, tidak mengelola apa pun, dan tidak memiliki tanggung jawab selain data), namun tetap menggunakan properti.
jrh

Jawaban:


82

Jika Anda mengekspos semua atribut Anda dengan getter / setter Anda mendapatkan hanya struktur data yang masih digunakan dalam C atau bahasa prosedural lainnya. Ini bukan enkapsulasi dan Lombok hanya membuat untuk bekerja dengan kode prosedural kurang menyakitkan. Getters / setter seburuk bidang publik biasa. Tidak ada perbedaan sebenarnya.

Dan struktur data bukan objek. Jika Anda akan mulai membuat objek dari menulis antarmuka, Anda tidak akan pernah menambahkan getter / setter ke antarmuka. Mengekspos atribut Anda mengarah ke kode prosedural spaghetti di mana manipulasi dengan data berada di luar objek dan menyebar ke seluruh basis kode. Sekarang Anda berurusan dengan data dan manipulasi dengan data alih-alih berbicara dengan objek. Dengan getter / setters, Anda akan memiliki pemrograman prosedural berbasis data di mana manipulasi dengan yang dilakukan dengan cara imperatif langsung. Dapatkan data - lakukan sesuatu - setel data.

Dalam OOP enkapsulasi adalah gajah jika dilakukan dengan cara yang benar. Anda harus merangkum rincian implementasi dan negara sehingga objek memiliki kontrol penuh untuk itu. Logika akan difokuskan di dalam objek dan tidak akan tersebar di seluruh basis kode. Dan ya - enkapsulasi masih penting dalam pemrograman karena kode akan lebih dapat dipertahankan.

EDIT

Setelah melihat diskusi yang terjadi, saya ingin menambahkan beberapa hal:

  • Tidak masalah berapa banyak atribut Anda yang diekspos melalui getter / setter dan seberapa hati-hati Anda melakukannya. Menjadi lebih selektif tidak akan membuat kode Anda OOP dengan enkapsulasi. Setiap atribut yang Anda paparkan akan mengarah pada beberapa prosedur yang bekerja dengan data telanjang dengan cara prosedural imperatif. Anda hanya akan menyebarkan kode Anda lebih lambat dengan menjadi lebih selektif. Itu tidak mengubah inti.
  • Ya, dalam batas-batas dalam sistem Anda mendapatkan data telanjang dari sistem atau database lain. Tetapi data ini hanyalah titik enkapsulasi lainnya.
  • Objek harus dapat diandalkan . Seluruh gagasan objek bertanggung jawab sehingga Anda tidak perlu memberikan perintah yang lurus dan penting. Sebaliknya Anda meminta objek untuk melakukan apa yang dilakukannya dengan baik melalui kontrak. Anda dengan aman mendelegasikan bagian akting ke objek. Objek merangkum rincian implementasi dan negara.

Jadi jika kita kembali ke pertanyaan mengapa kita harus melakukan ini. Pertimbangkan contoh sederhana ini:

public class Document {
    private String title;

    public String getTitle() {
        return title;
    }
}

public class SomeDocumentServiceOrHandler {

    public void printDocument(Document document) {
        System.out.println("Title is " + document.getTitle());
    }
}

Di sini kita memiliki Dokumen yang mengekspos detail internal oleh pengambil dan memiliki kode prosedural eksternal dalam printDocumentfungsi yang bekerja dengan yang di luar objek. Mengapa ini buruk? Karena sekarang Anda hanya memiliki kode gaya C. Ya itu terstruktur tapi apa bedanya? Anda dapat menyusun fungsi C dalam file dan nama yang berbeda. Dan mereka yang disebut layer melakukan hal itu. Kelas layanan hanyalah sekelompok prosedur yang bekerja dengan data. Kode itu kurang dapat dipelihara dan memiliki banyak kelemahan.

public interface Printable {
    void print();
}

public final class PrintableDocument implements Printable {
    private final String title;

    public PrintableDocument(String title) {
        this.title = title;
    }

    @Override
    public void print() {
        System.out.println("Title is " + title);
    }
}

Bandingkan dengan yang ini. Sekarang kami memiliki kontrak dan rincian implemantasi dari kontrak ini disembunyikan di dalam objek. Sekarang Anda dapat benar-benar menguji kelas itu dan kelas itu merangkum beberapa data. Cara kerjanya dengan data itu adalah objek perhatian. Untuk berbicara dengan objek sekarang, Anda perlu memintanya untuk mencetak sendiri. Itu enkapsulasi dan itu adalah sebuah objek. Anda akan mendapatkan kekuatan penuh injeksi ketergantungan, mengejek, pengujian, tanggung jawab tunggal dan banyak manfaat dengan OOP.


Komentar bukan untuk diskusi panjang; percakapan ini telah dipindahkan ke obrolan .
maple_shaft

1
"Getters / seters seburuk bidang publik biasa" - itu tidak benar, setidaknya dalam banyak bahasa. Anda biasanya tidak dapat mengesampingkan akses bidang biasa, tetapi dapat mengesampingkan getter / setter. Itu memberikan fleksibilitas untuk kelas anak. Ini juga memudahkan untuk mengubah perilaku kelas. misalnya, Anda mungkin mulai dengan pengambil / penyetel yang didukung oleh bidang pribadi dan kemudian pindah ke bidang yang berbeda atau menghitung nilai dari nilai lain. Tidak ada yang bisa dilakukan dengan bidang polos. Beberapa bahasa memang memungkinkan bidang untuk memiliki apa yang pada dasarnya getter dan setter otomatis, tetapi Java bukan bahasa seperti itu.
Kat

Eh, masih belum yakin. Contoh Anda baik-baik saja tetapi hanya menunjukkan gaya pengkodean yang berbeda, bukan "yang benar benar" - yang tidak ada. Ingatlah bahwa sebagian besar bahasa adalah multi-paradigma saat ini dan jarang yang murni prosedur murni. Menempel diri Anda begitu keras untuk "konsep OO murni". Sebagai contoh - Anda tidak dapat menggunakan DI pada contoh Anda karena Anda menambahkan logika pencetakan ke subkelas. Pencetakan tidak boleh menjadi bagian dari dokumen - dokumen yang dapat dicetak akan memaparkan bagian yang dapat dicetak (melalui pengambil) ke PrinterService.
T. Sar - Pasang kembali Monica

Pendekatan OO yang tepat untuk ini, jika kita akan menggunakan pendekatan "OO Murni", akan menggunakan sesuatu yang mengimplementasikan kelas PrinterService abstrak yang meminta, melalui pesan, apa yang harus dicetak - menggunakan semacam GetPrintablePart. Hal yang mengimplementasikan PrinterService bisa berupa printer apa saja - cetak ke PDF, ke layar, ke TXT ... Solusi Anda membuat tidak mungkin untuk menukar logika pencetakan dengan sesuatu yang lain, berakhir dengan lebih berpasangan dan kurang dapat dirawat. dari contoh "salah" Anda.
T. Sar - Reinstate Monica

Intinya: Getters dan Setters tidak jahat atau melanggar OOP - Orang yang tidak mengerti bagaimana menggunakannya. Contoh kasus Anda adalah contoh buku teks tentang orang yang kehilangan seluruh poin bagaimana DI bekerja. Contoh yang Anda "koreksi" sudah DI-diaktifkan, dipisahkan, dapat dengan mudah diejek ... Sungguh - ingat seluruh hal "Memilih Komposisi Lebih dari Warisan"? Salah satu pilar OO? Anda baru saja melemparkannya melalui jendela dengan cara Anda refactored kode. Ini tidak akan terbang dalam ulasan kode serius apa pun.
T. Sar - Reinstate Monica

76

Bisakah seseorang menjelaskan kepada saya, apa arti menyembunyikan semua bidang sebagai pribadi dan setelah itu mengekspos semuanya dengan teknologi ekstra? Mengapa kita tidak hanya menggunakan bidang publik saja?

Maksudnya adalah Anda tidak seharusnya melakukan itu .

Enkapsulasi berarti Anda hanya mengekspos bidang-bidang yang benar-benar Anda butuhkan untuk diakses kelas lain, dan Anda sangat selektif dan berhati-hati.

Jangan tidak hanya memberikan semua bidang getter dan setter per default!

Itu benar-benar bertentangan dengan semangat spesifikasi JavaBeans yang ironisnya darimana konsep pengambil dan pendatang publik berasal.

Tetapi jika Anda melihat speknya, Anda akan melihat bahwa itu dimaksudkan untuk membuat getter dan setter sangat selektif, dan itu berbicara tentang properti "read-only" (no setter) dan "write-only" properti (no pengambil).

Faktor lain adalah bahwa getter dan setter belum tentu akses mudah ke bidang pribadi . Seorang pengambil dapat menghitung nilai yang dikembalikannya dengan cara yang rumit, mungkin menyimpannya. Setter dapat memvalidasi nilai atau memberi tahu pendengar.

Jadi itulah Anda: enkapsulasi berarti Anda hanya mengekspos fungsionalitas yang sebenarnya perlu Anda paparkan. Tetapi jika Anda tidak berpikir tentang apa yang perlu Anda ungkapkan dan mau tidak mau memaparkan semuanya dengan mengikuti beberapa transformasi sintaksis maka tentu saja itu tidak benar-benar enkapsulasi.


20
"[G] etters dan setter belum tentu akses mudah" - Saya pikir itu adalah titik kunci. Jika kode Anda mengakses bidang dengan nama, Anda tidak dapat mengubah perilaku nanti. Jika Anda menggunakan obj.get (), Anda bisa.
Dan Ambrogio

4
@ jrh: Saya tidak pernah mendengarnya disebut praktik buruk di Jawa, dan ini cukup umum.
Michael Borgwardt

2
@MichaelBorgwardt Menarik; sedikit keluar dari topik tetapi saya selalu bertanya-tanya mengapa Microsoft merekomendasikan untuk tidak melakukan itu untuk C #. Saya kira pedoman itu menyiratkan bahwa dalam C # satu-satunya hal yang dapat digunakan oleh setter adalah mengonversi nilai yang diberikan ke beberapa nilai lain yang digunakan secara internal, dengan cara yang tidak dapat gagal atau memiliki nilai yang tidak valid (misalnya, memiliki properti PositionInches dan properti PositionCentimeters di mana secara otomatis dikonversi secara internal ke mm)? Bukan contoh yang bagus tapi itu yang terbaik yang bisa saya pikirkan saat ini.
jrh

2
@jrh sumber itu mengatakan untuk tidak melakukannya untuk getter, sebagai lawan setter.
Kapten Man

3
@ Gangnus dan Anda benar-benar melihat seseorang menyembunyikan beberapa logika di dalam pengambil / penyetel? Kami sudah terbiasa dengan itu yang getFieldName()menjadi kontrak otomatis bagi kami. Kami tidak akan mengharapkan beberapa perilaku rumit di balik itu. Dalam kebanyakan kasus itu hanya pemecahan langsung enkapsulasi.
Izbassar Tolegen

17

Saya pikir inti masalahnya dijelaskan oleh komentar Anda:

Saya sangat setuju dengan pemikiran Anda. Tetapi di suatu tempat kita harus memuat objek dengan data. Misalnya, dari XML. Dan platform saat ini mendukungnya melakukannya melalui pengambil / setter, sehingga menurunkan kualitas kode kita dan cara berpikir kita. Lombok sebenarnya tidak buruk dengan sendirinya, tetapi keberadaannya menunjukkan bahwa kita memiliki sesuatu yang buruk.

Masalah yang Anda miliki adalah bahwa Anda mencampur datamodel kegigihan dengan datamodel aktif .

Suatu aplikasi umumnya akan memiliki beberapa datamodels:

  • datamodel untuk berbicara dengan database,
  • datamodel untuk membaca file konfigurasi,
  • datamodel untuk berbicara dengan aplikasi lain,
  • ...

di atas datamodel yang sebenarnya digunakan untuk melakukan komputasinya.

Secara umum, datamodels yang digunakan untuk komunikasi dengan luar harus diisolasi dan independen dari datamodel dalam (model objek bisnis, BOM) di mana perhitungan dilakukan:

  • independen : sehingga Anda dapat menambah / menghapus properti di BOM sesuai dengan kebutuhan Anda tanpa harus mengubah semua klien / server, ...
  • terisolasi : sehingga semua perhitungan dilakukan pada BOM, tempat invarian tinggal, dan bahwa perubahan dari satu layanan ke layanan lain, atau meningkatkan satu layanan, tidak menyebabkan riak di seluruh basis kode.

Dalam skenario ini, sangat baik untuk objek yang digunakan dalam lapisan komunikasi untuk memiliki semua item publik atau diekspos oleh getter / setter. Itu adalah benda biasa tanpa invarian apa pun.

Di sisi lain, BOM Anda harus memiliki invarian, yang umumnya menghalangi memiliki banyak setter (getter tidak mempengaruhi invarian, meskipun mereka mengurangi enkapsulasi ke tingkat tertentu).


3
Saya tidak akan membuat getter dan setter untuk objek komunikasi. Saya hanya akan membuat ladang terbuka untuk umum. Mengapa membuat lebih banyak pekerjaan daripada berguna?
user253751

3
@immibis: Saya setuju secara umum, namun sebagaimana dicatat oleh OP beberapa kerangka kerja membutuhkan pengambil / setter, dalam hal ini Anda hanya harus mematuhi. Perhatikan bahwa OP menggunakan pustaka yang membuatnya secara otomatis dengan aplikasi atribut tunggal, jadi itu pekerjaan kecil baginya.
Matthieu M.

1
@ Gangnus: Idenya di sini adalah bahwa getter / setter diisolasi ke lapisan batas (I / O), di mana itu normal untuk menjadi kotor, tetapi sisa aplikasi (kode murni) tidak terkontaminasi dan tetap elegan.
Matthieu M.

2
@ kubanczyk: definisi resmi adalah Model Obyek Bisnis sejauh yang saya tahu. Ini sesuai di sini dengan apa yang saya sebut "datamodel batin", datamodel tempat Anda menjalankan logika dalam inti "murni" dari aplikasi Anda.
Matthieu M.

1
Ini adalah pendekatan pragmatis. Kita hidup di dunia hibrida. Jika perpustakaan eksternal dapat mengetahui data apa yang harus diteruskan ke konstruktor BOM, maka kami hanya akan memiliki BOM.
Adrian Iftode

12

Pertimbangkan yang berikut ini ..

Anda memiliki Userkelas dengan properti int age:

class User {
    int age;
}

Anda ingin meningkatkan ini sehingga Usermemiliki tanggal lahir, berlawanan dengan usia. Menggunakan pengambil:

class User {
    private int age;

    public int getAge() {
        return age;
    }
}

Kami dapat menukar int agebidang dengan yang lebih kompleks LocalDate dateOfBirth:

class User {
    private LocalDate dateOfBirth;

    public int getAge() {
        LocalDate now = LocalDate.now();
        int year = ...; // calculate using dateOfBirth and now
        return year;
    }

    // other behaviors can now make use of dateOfBirth
}

Tidak ada pelanggaran kontrak, tidak ada kerusakan kode .. Tidak lebih dari meningkatkan perwakilan internal dalam persiapan untuk perilaku yang lebih kompleks.

Bidang itu sendiri dienkapsulasi.


Sekarang, untuk menghapus kekhawatiran ..

@DataAnotasi Lombok mirip dengan kelas data Kotlin .

Tidak semua kelas mewakili objek perilaku. Adapun melanggar enkapsulasi, itu tergantung pada penggunaan fitur ini oleh Anda. Anda seharusnya tidak mengekspos semua bidang Anda melalui getter.

Enkapsulasi digunakan untuk menyembunyikan nilai atau keadaan objek data terstruktur di dalam kelas

Dalam arti yang lebih umum, enkapsulasi adalah tindakan menyembunyikan informasi. Jika Anda menyalahgunakan @Data, maka mudah untuk menganggap Anda mungkin melanggar enkapsulasi. Tetapi itu tidak berarti tidak memiliki tujuan. JavaBeans, misalnya, disukai oleh beberapa orang. Namun itu digunakan secara luas dalam pengembangan usaha.

Akankah Anda menyimpulkan bahwa pengembangan usaha itu buruk, karena penggunaan kacang? Tentu saja tidak! Persyaratan berbeda dari pengembangan standar. Bisakah kacang disalahgunakan? Tentu saja! Mereka disalahgunakan sepanjang waktu!

Lombok juga mendukung @Getterdan @Settermandiri - gunakan apa yang diminta oleh persyaratan Anda.


2
Ini tidak terkait dengan menampar @Dataanotasi pada suatu jenis, yang dengan desain tidak merangkum semua bidang
Caleth

1
Tidak, bidang tidak disembunyikan . Karena apa pun bisa datang dansetAge(xyz)
Caleth

9
@Caleth Bidang yang tersembunyi. Anda bertindak seolah-olah setter tidak dapat memiliki kondisi sebelum dan sesudah, yang merupakan kasus penggunaan yang sangat umum untuk menggunakan setter. Anda bertindak seolah-olah bidang == properti, yang bahkan dalam bahasa seperti C #, tidak benar, karena keduanya cenderung didukung (seperti dalam C #). Lapangan dapat dipindahkan ke kelas lain, dapat ditukar dengan representasi yang lebih kompleks ...
Vince Emigh

1
Dan apa yang saya katakan adalah itu tidak penting
Caleth

2
@Caleth Bagaimana itu tidak penting? Itu tidak masuk akal. Anda mengatakan enkapsulasi tidak berlaku karena Anda merasa itu tidak penting dalam situasi ini, meskipun itu berlaku menurut definisi dan sudahkah menggunakan kasus?
Vince Emigh

11

Enkapsulasi memberitahu saya untuk membuat semua atau hampir semua bidang menjadi pribadi dan mengeksposnya dengan getter / setter.

Itu bukan bagaimana enkapsulasi didefinisikan dalam pemrograman berorientasi objek. Enkapsulasi berarti bahwa setiap objek harus seperti kapsul, yang kulit luarnya (api publik) melindungi dan mengatur akses ke interiornya (metode dan bidang pribadi), dan menyembunyikannya dari pandangan. Dengan menyembunyikan internal, penelepon tidak bergantung pada internal, memungkinkan internal diubah tanpa mengubah (atau bahkan mengkompilasi ulang) penelepon. Juga, enkapsulasi memungkinkan setiap objek untuk menegakkan invariannya sendiri, dengan hanya membuat operasi yang aman tersedia untuk penelepon.

Oleh karena itu enkapsulasi adalah kasus khusus dari penyembunyian informasi, di mana setiap objek menyembunyikan internalnya dan menegakkan invariannya.

Menghasilkan getter dan setter untuk semua bidang adalah bentuk enkapsulasi yang cukup lemah, karena struktur data internal tidak disembunyikan, dan invarian tidak dapat ditegakkan. Itu memang memiliki keuntungan bahwa Anda dapat mengubah cara data disimpan secara internal (selama Anda dapat mengkonversi ke dan dari struktur lama) tanpa harus mengubah (atau bahkan mengkompilasi ulang) penelepon, namun.

Bisakah seseorang menjelaskan kepada saya apa arti menyembunyikan semua bidang sebagai pribadi dan setelah itu mengekspos semuanya dengan teknologi ekstra? Mengapa kita tidak hanya menggunakan bidang publik saja? Saya merasa kami telah berjalan jauh dan sulit hanya untuk kembali ke titik awal.

Sebagian, ini karena kecelakaan bersejarah. Strike satu adalah bahwa, di Jawa, ekspresi pemanggilan metode dan ekspresi akses bidang secara sintaksis berbeda di situs panggilan, yaitu mengganti akses bidang dengan pengambil atau penyetel memecah API kelas. Karenanya, jika Anda mungkin memerlukan pengakses, Anda harus menulisnya sekarang, atau dapat memecahkan API. Tidak adanya dukungan properti tingkat bahasa ini sangat kontras dengan bahasa modern lainnya, terutama C # dan EcmaScript .

Strike 2 adalah bahwa spesifikasi JavaBeans mendefinisikan properti sebagai getter / setter, bidang bukan properti. Akibatnya, sebagian besar kerangka kerja perusahaan awal mendukung pengambil / penentu, tetapi bukan bidang. Itu sudah lama di masa lalu sekarang ( Java Persistence API (JPA) , Validasi Bean , Arsitektur Java untuk XML Binding (JAXB) , Jacksonsemua bidang dukungan baik-baik saja sekarang), tetapi tutorial dan buku-buku lama terus berlama-lama, dan tidak semua orang sadar bahwa semuanya telah berubah. Tidak adanya dukungan properti tingkat bahasa masih bisa menjadi masalah dalam beberapa kasus (misalnya karena pemuatan malas JPA entitas tunggal tidak memicu ketika bidang publik dibaca), tetapi sebagian besar bidang publik berfungsi dengan baik. Intinya, perusahaan saya menulis semua DTO untuk API REST mereka dengan bidang publik (bagaimanapun, tidak mendapatkan lebih banyak publik yang ditransmisikan melalui internet, setelah semua :-).

Yang mengatakan, Lombok @Datatidak lebih dari menghasilkan getter / setter: Ini juga menghasilkan toString(), hashCode()dan equals(Object), yang bisa sangat berharga.

Dan apakah enkapsulasi benar-benar masuk akal sekarang dalam pemrograman kehidupan nyata?

Enkapsulasi bisa sangat berharga atau sama sekali tidak berguna, tergantung pada objek yang dienkapsulasi. Secara umum, semakin kompleks logika di dalam kelas, semakin besar manfaat enkapsulasi.

Getter dan setter yang dihasilkan secara otomatis untuk setiap bidang umumnya digunakan secara berlebihan, tetapi dapat berguna untuk bekerja dengan kerangka kerja lama, atau untuk menggunakan fitur kerangka kerja sesekali yang tidak didukung untuk bidang.

Enkapsulasi dapat dicapai dengan getter dan metode perintah. Setter biasanya tidak sesuai, karena mereka diharapkan hanya mengubah satu bidang, sementara mempertahankan invarian mungkin memerlukan beberapa bidang untuk diubah sekaligus.

Ringkasan

getter / setter menawarkan enkapsulasi yang agak buruk.

Prevalensi getter / pemukim di Jawa berasal dari kurangnya dukungan tingkat bahasa untuk properti, dan pilihan desain yang dipertanyakan dalam model komponen historisnya yang telah diabadikan dalam banyak bahan pengajaran dan programmer yang diajarkan oleh mereka.

Bahasa berorientasi objek lainnya, seperti EcmaScript, melakukan properti dukungan di tingkat bahasa, sehingga getter dapat diperkenalkan tanpa melanggar API. Dalam bahasa seperti itu, getter dapat diperkenalkan ketika Anda benar-benar membutuhkannya, daripada sebelumnya, hanya dalam kasus-Anda-mungkin-membutuhkan-itu-satu-hari, yang membuat pengalaman pemrograman yang jauh lebih menyenangkan.


1
memberlakukan invariannya? Apakah ini bahasa Inggris? Bisakah Anda menjelaskannya kepada orang yang bukan orang Inggris? Jangan terlalu rumit - beberapa orang di sini tidak cukup mengerti bahasa Inggris.
Gangnus

Saya suka basis Anda, saya suka pemikiran Anda (+1), tetapi bukan hasil praktisnya. Saya lebih suka menggunakan bidang pribadi dan beberapa perpustakaan khusus untuk memuat data untuk mereka dengan refleksi. Saya hanya tidak mengerti mengapa Anda menetapkan jawaban Anda sebagai argumen terhadap saya?
Gangnus

@ Gangnus Invarian adalah kondisi logis yang tidak berubah (artinya tidak dan bervariasi berarti / berubah). Banyak fungsi memiliki aturan yang harus benar sebelum dan sesudah dijalankan (disebut pra-kondisi dan pasca-kondisi) jika tidak ada kesalahan dalam kode. Misalnya fungsi mungkin mengharuskan argumennya tidak nol (pra-kondisi) dan mungkin melemparkan pengecualian jika argumennya nol karena kasus itu akan menjadi kesalahan dalam kode (yaitu dalam ilmu komputer berbicara, kode telah memecahkan invarian).
Pharap

@ Gangus: Tidak yakin di mana refleksi masuk ke dalam diskusi ini; Lombok adalah prosesor anotasi, yaitu plugin untuk Java compiler yang mengeluarkan kode tambahan saat kompilasi. Namun yang pasti, Anda bisa menggunakan lombok. Saya hanya mengatakan bahwa dalam banyak kasus, bidang publik berfungsi dengan baik, dan lebih mudah diatur (tidak semua kompiler mendeteksi prosesor anotasi secara otomatis ...).
meriton - mogok kerja

1
@ Gangnus: Invarian adalah sama dalam matematika dan CS, tetapi dalam CS ada perspektif tambahan: Dari perspektif objek, invarian harus ditegakkan dan ditetapkan. Dari perspektif penelepon objek itu, invarian selalu benar.
meriton - saat mogok

7

Saya sudah bertanya pada diri sendiri pertanyaan itu.

Tapi itu tidak sepenuhnya benar. Prevalensi pengambil / penyetel IMO disebabkan oleh spesifikasi Java Bean, yang mengharuskannya; jadi itu cukup banyak fitur bukan pemrograman Berorientasi Objek tetapi pemrograman berorientasi Bean, jika Anda mau. Perbedaan antara keduanya adalah lapisan abstraksi yang ada di dalamnya; Kacang lebih merupakan antarmuka sistem, yaitu pada lapisan yang lebih tinggi. Mereka abstrak dari landasan OO, atau setidaknya dimaksudkan - seperti biasa, hal-hal yang didorong terlalu jauh terlalu sering.

Saya akan mengatakan itu agak disayangkan bahwa hal Bean yang ada di mana-mana dalam pemrograman Java tidak disertai dengan penambahan fitur bahasa Jawa yang sesuai - Saya sedang memikirkan sesuatu seperti konsep Properties di C #. Bagi mereka yang tidak menyadarinya, ini adalah konstruksi bahasa yang terlihat seperti ini:

class MyClass {
    string MyProperty { get; set; }
}

Bagaimanapun, seluk beluk implementasi aktual masih sangat banyak manfaat dari enkapsulasi.


Python memiliki fitur serupa di mana properti dapat memiliki getter / setter transparan. Saya yakin ada lebih banyak bahasa yang memiliki fitur serupa juga.
JAB

1
"Prevalensi pengambil / penyetel IMO disebabkan oleh spesifikasi Java Bean, yang mengharuskannya" Ya, Anda benar. Dan siapa bilang itu harus diminta? Alasannya, tolong?
Gangnus

2
Cara C # benar-benar sama dengan Lombok itu. Saya tidak melihat perbedaan nyata. Kenyamanan lebih praktis, tetapi jelas buruk dalam konsepsi.
Gangnus

1
@ Gangnis Spesifikasi memerlukannya. Mengapa? Lihatlah jawaban saya untuk contoh konkret mengapa getter tidak sama dengan mengekspos bidang - cobalah mencapai yang sama tanpa rajin.
Vince Emigh

@VinceEmigh gangnUs, tolong :-). (gang-walking, nus - nut). Dan bagaimana dengan menggunakan get / set hanya di mana kita secara spesifik membutuhkannya, dan memuat secara massal ke bidang pribadi dengan refleksi dalam kasus lain?
Gangnus

6

Bisakah seseorang menjelaskan kepada saya, apa arti menyembunyikan semua bidang sebagai pribadi dan setelah itu mengekspos semuanya dengan teknologi ekstra? Mengapa kita tidak hanya menggunakan bidang publik saja? Saya merasa kita telah menempuh jalan yang panjang dan sulit hanya untuk kembali ke titik awal.

Jawaban sederhana di sini adalah: Anda memang benar. Getters dan setter menghilangkan sebagian besar (tetapi tidak semua) nilai enkapsulasi. Ini bukan untuk mengatakan bahwa setiap kali Anda memiliki metode get dan / atau set yang Anda hancurkan enkapsulasi tetapi jika Anda secara buta menambahkan accessors ke semua anggota pribadi kelas Anda, Anda melakukan kesalahan.

Ya, ada teknologi lain yang bekerja melalui getter dan setter. Dan kita tidak dapat menggunakannya melalui bidang publik sederhana. Tetapi teknologi ini muncul hanya karena kami memiliki banyak properti - bidang pribadi di belakang pengambil / penetap publik. Jika kita tidak memiliki properti, teknologi ini akan mengembangkan cara lain dan akan mendukung bidang publik. Dan semuanya akan sederhana dan kita tidak akan membutuhkan Lombok sekarang. Apa arti dari keseluruhan siklus ini? Dan apakah enkapsulasi benar-benar masuk akal sekarang dalam pemrograman kehidupan nyata?

Getters adalah setter yang ada di mana-mana dalam pemrograman Java karena konsep JavaBean didorong sebagai cara untuk secara dinamis mengikat fungsionalitas ke kode pra-dibangun. Misalnya, Anda dapat memiliki formulir dalam applet (ada yang ingat itu?) Yang akan memeriksa objek Anda, menemukan semua properti dan menampilkan bidang sebagai. Kemudian UI dapat memodifikasi properti tersebut berdasarkan input pengguna. Anda sebagai pengembang maka hanya khawatir tentang menulis kelas dan meletakkan validasi atau logika bisnis di sana dll.

masukkan deskripsi gambar di sini

Menggunakan Kacang Contoh

Ini bukan ide yang buruk, tetapi saya belum pernah menjadi penggemar berat pendekatan di Jawa. Itu hanya bertentangan dengan keinginan. Gunakan Python, Groovy dll. Sesuatu yang mendukung pendekatan semacam ini lebih alami.

Hal JavaBean semacam berputar di luar kendali karena ia menciptakan JOBOL yaitu pengembang yang menulis Java yang tidak mengerti OO. Pada dasarnya, objek menjadi tidak lebih dari sekumpulan data dan semua logika ditulis di luar dengan metode panjang. Karena dianggap normal, orang-orang seperti Anda dan saya yang mempertanyakan ini dianggap kook. Akhir-akhir ini saya melihat perubahan dan ini bukan posisi orang luar

Hal yang mengikat XML adalah kacang yang sulit. Ini mungkin bukan medan perang yang baik untuk berdiri melawan JavaBeans. Jika Anda harus membuat JavaBeans ini, coba jauhkan dari kode asli. Perlakukan mereka sebagai bagian dari lapisan serialisasi.


2
Pikiran yang paling konkret dan bijaksana. +1. Tetapi mengapa tidak menulis kelompok kelas, yang setiap orang akan secara besar-besaran memuat / menyimpan bidang pribadi ke / dari beberapa struktur eksternal? JSON, XML, objek lainnya. Ini bisa bekerja dengan POJO atau, mungkin, hanya dengan bidang, ditandai dengan anotasi untuk itu. Sekarang saya sedang memikirkan alternatif itu.
Gangnus

@ Gangnus Dugaan karena itu berarti banyak penulisan kode tambahan. Jika refleksi digunakan maka hanya satu gumpalan kode serialisasi yang harus ditulis dan dapat membuat serialisasi kelas apa pun yang mengikuti pola tertentu. C # mendukung ini melalui [Serializable]atribut dan kerabatnya. Mengapa menulis 101 metode serialisasi spesifik / kelas ketika Anda bisa menulis satu dengan meningkatkan refleksi?
Pharap

@Pharap Saya sangat menyesal, tetapi komentar Anda terlalu pendek untuk saya. "meningkatkan refleksi"? Dan apa arti menyembunyikan berbagai hal menjadi satu kelas? Bisakah Anda menjelaskan maksud Anda?
Gangnus

@ Gangnus maksud saya refleksi , kemampuan kode untuk memeriksa strukturnya sendiri. Dalam Java (dan bahasa lainnya) Anda bisa mendapatkan daftar semua fungsi yang diekspos kelas dan kemudian menggunakannya sebagai cara mengekstraksi data dari kelas yang diperlukan untuk membuat serial menjadi eg XML / JSON. Menggunakan refleksi yang akan menjadi pekerjaan satu kali alih-alih terus-menerus membuat metode baru untuk menghemat struktur berdasarkan per kelas. Ini contoh Java sederhana .
Pharap

@Pharap Itulah yang saya bicarakan. Tetapi mengapa Anda menyebutnya "pengungkit"? Dan alternatif yang saya sebutkan di komentar pertama di sini tetap - haruskah (tidak) bidang dikemas memiliki penjelasan khusus atau tidak?
Gangnus

5

Berapa banyak yang bisa kita lakukan tanpa getter? Apakah mungkin untuk menghapusnya sepenuhnya? Masalah apa yang terjadi? Bisakah kita bahkan mencekal returnkata kunci?

Ternyata Anda bisa melakukan banyak hal jika Anda bersedia melakukan pekerjaan itu. Lalu bagaimana informasi bisa keluar dari objek yang sepenuhnya dienkapsulasi ini? Melalui kolaborator.

Daripada membiarkan kode mengajukan pertanyaan, Anda mengatakan sesuatu untuk melakukan sesuatu. Jika barang-barang itu juga tidak mengembalikan barang-barang Anda tidak perlu melakukan apa-apa tentang apa yang mereka kembalikan. Jadi ketika Anda berpikir untuk meraih returnbukan mencoba untuk mencapai beberapa kolaborator port output yang akan melakukan apa pun yang tersisa untuk dilakukan.

Melakukan hal-hal seperti ini memiliki manfaat dan konsekuensi. Anda harus memikirkan lebih dari sekadar apa yang akan Anda kembalikan. Anda harus memikirkan bagaimana Anda akan mengirimkannya sebagai pesan ke objek yang tidak memintanya. Mungkin Anda membagikan objek yang sama dengan yang Anda kembalikan, atau mungkin hanya memanggil metode saja sudah cukup. Melakukan hal itu membutuhkan biaya.

Manfaatnya adalah sekarang Anda melakukan pembicaraan dengan antarmuka. Ini berarti Anda mendapatkan manfaat penuh dari abstraksi.

Ini juga memberi Anda pengiriman polimorfik, karena saat Anda tahu apa yang Anda katakan, Anda tidak harus tahu persis apa yang Anda katakan.

Anda mungkin berpikir ini berarti Anda harus pergi hanya satu cara melalui setumpuk lapisan, tetapi ternyata Anda dapat menggunakan polimorfisme untuk mundur tanpa membuat ketergantungan siklik yang gila.

Mungkin terlihat seperti ini:

Masukkan deskripsi gambar di sini

class Interactor implements InputPort {
    OutputPort out;
    int y = 0;

    Interactor(OutputPort out){
        this.out = out;
    }

    void accumulate(int x) {
        y = y + x;
        out.showAsImage(y);
    }
}

Jika Anda dapat kode seperti itu maka menggunakan getter adalah pilihan. Bukan kejahatan yang perlu.


Ya, getter / setter seperti itu memiliki rasa yang besar. Tapi apakah Anda mengerti ini tentang sesuatu yang lain? :-) tetap +1.
Gangnus

1
@ Gangnus Terima kasih. Ada banyak hal yang bisa Anda bicarakan. Jika Anda setidaknya mau menyebutkan namanya, saya bisa membahasnya.
candied_orange

5

Ini adalah masalah yang kontroversial (seperti yang Anda lihat) karena banyak dogma dan kesalahpahaman bercampur dengan keprihatinan yang masuk akal mengenai masalah getter dan setter. Namun singkatnya, tidak ada yang salah dengan @Dataitu dan tidak merusak enkapsulasi.

Mengapa menggunakan getter dan setter daripada bidang publik?

Karena getter / setter menyediakan enkapsulasi. Jika Anda mengekspos nilai sebagai bidang publik dan kemudian berubah untuk menghitung nilai dengan cepat, maka Anda perlu memodifikasi semua klien yang mengakses bidang tersebut. Jelas ini buruk. Ini adalah detail implementasi apakah beberapa properti dari suatu objek disimpan dalam suatu bidang, dihasilkan dengan cepat atau diambil dari tempat lain, sehingga perbedaannya tidak boleh diekspos kepada klien. Setter pengambil / setter menyelesaikan ini, karena menyembunyikan implementasi.

Tetapi jika pengambil / penyetel hanya mencerminkan bidang pribadi yang mendasarinya, bukankah itu sama buruknya?

Tidak! Intinya adalah enkapsulasi memungkinkan Anda untuk mengubah implementasi tanpa mempengaruhi klien. Bidang mungkin masih merupakan cara yang sangat baik untuk menyimpan nilai, selama klien tidak harus tahu atau peduli.

Tetapi bukankah autogenerating getter / setter dari bidang melanggar enkapsulasi?

Tidak ada enkapsulasi yang masih ada! @Dataanotasi hanyalah cara mudah untuk menulis pasangan pengambil / penyetel yang menggunakan bidang yang mendasarinya. Untuk sudut pandang klien, ini seperti sepasang pengambil / penyetel biasa. Jika Anda memutuskan untuk menulis ulang implementasi Anda masih bisa melakukannya tanpa mempengaruhi klien. Jadi Anda mendapatkan yang terbaik dari kedua dunia: enkapsulasi dan sintaksis ringkas.

Tetapi beberapa mengatakan rajin / rajin selalu buruk!

Ada kontroversi terpisah, di mana beberapa percaya bahwa pola pengambil / penyetel selalu buruk, terlepas dari implementasi yang mendasarinya. Idenya adalah bahwa Anda tidak harus menetapkan atau mendapatkan nilai dari objek, melainkan interaksi antara objek harus dimodelkan sebagai pesan di mana satu objek meminta objek lain untuk melakukan sesuatu. Ini sebagian besar adalah sepotong dogma dari hari-hari awal pemikiran berorientasi objek. Pemikiran sekarang adalah bahwa untuk pola tertentu (misalnya objek nilai, objek transfer data) getter / setter mungkin sangat tepat.


Enkapsulasi harus memungkinkan kita polimorfisme dan keamanan. Gets / set memungkinkan pertama, tetapi sangat menentang yang kedua.
Gangnus

Jika Anda mengatakan saya menggunakan dogma di suatu tempat, tolong, katakan, apa teks saya adalah dogma. Dan jangan lupa, bahwa beberapa pemikiran yang saya gunakan tidak saya sukai atau setujui. Saya menggunakan mereka untuk menunjukkan kontradiksi.
Gangnus

1
"GangNus" :-). Seminggu yang lalu saya telah mengerjakan proyek yang benar-benar penuh dengan panggilan getter dan setter di beberapa lapisan. Ini benar-benar tidak aman, dan bahkan lebih buruk lagi, mendukung orang dalam pengkodean yang tidak aman. Saya senang saya telah berganti pekerjaan cukup cepat, karena orang menjadi terbiasa dengan hal itu. Jadi, saya tidak berpikir, saya melihatnya .
Gangnus

2
@ jrh: Saya tidak tahu apakah ada penjelasan formal seperti itu, tetapi C # memiliki dukungan bawaan untuk properti (getter / setter dengan sintaks yang lebih bagus) dan untuk jenis anonim yang merupakan tipe dengan hanya properti dan tanpa perilaku. Jadi para perancang C # telah sengaja menyimpang dari metafora perpesanan dan prinsip "katakan, jangan tanya". Perkembangan C # sejak versi 2.0 tampaknya lebih terinspirasi oleh bahasa fungsional daripada oleh kemurnian OO tradisional.
JacquesB

1
@ jrh: Saya menduga sekarang, tapi saya pikir C # cukup terinspirasi oleh bahasa fungsional di mana data dan operasi lebih terpisah. Dalam arsitektur terdistribusi, perspektifnya juga telah bergeser dari berorientasi pesan (RPC, SOAP) ke berorientasi data (REST). Dan basis data yang mengekspos dan beroperasi pada data (bukan objek "kotak hitam") telah menang. Singkatnya saya pikir fokus telah bergeser dari pesan antara kotak hitam ke model data yang terbuka
JacquesB

3

Enkapsulasi memang memiliki tujuan, tetapi juga dapat disalahgunakan atau disalahgunakan.

Pertimbangkan sesuatu seperti Android API yang memiliki kelas dengan lusinan (jika tidak ratusan) bidang. Mengekspos bidang-bidang yang konsumen API membuatnya sulit untuk dinavigasi dan menggunakan, juga memberikan pengguna gagasan palsu bahwa ia dapat melakukan apa pun yang ia inginkan dengan bidang-bidang yang mungkin bertentangan dengan bagaimana mereka seharusnya digunakan. Jadi enkapsulasi sangat bagus untuk pemeliharaan, kegunaan, keterbacaan, dan menghindari bug gila.

Di sisi lain, POD atau tipe data lama biasa, seperti struct dari C / C ++ di mana semua bidang bersifat publik dapat berguna juga. Memiliki getter / setter yang tidak berguna seperti yang dihasilkan oleh anotasi @ data di Lombok hanyalah cara untuk menjaga "pola enkapsulasi". Salah satu dari sedikit alasan kami melakukan pengambil / setter "tidak berguna" di Jawa adalah bahwa metode tersebut memberikan kontrak .

Di Jawa, Anda tidak bisa memiliki bidang dalam antarmuka, oleh karena itu Anda menggunakan getter dan setter untuk menentukan properti umum yang dimiliki semua pelaksana antarmuka itu. Dalam bahasa yang lebih baru seperti Kotlin atau C # kita melihat konsep properti sebagai bidang yang dapat Anda deklarasikan sebagai setter dan pengambil. Pada akhirnya, getter / setters yang tidak berguna lebih merupakan warisan yang harus dijalani oleh Java, kecuali Oracle menambahkan properti padanya. Kotlin, misalnya, yang merupakan bahasa JVM lain yang dikembangkan oleh JetBrains, memiliki kelas data yang pada dasarnya melakukan apa yang dilakukan oleh anotasi @ data di Lombok.

Juga ada beberapa contoh:

class DataClass 
{
    private int data;

    public int getData() { return data; }
    public void setData(int data) { this.data = data; } 
}

Ini adalah kasus enkapsulasi yang buruk. Para pengambil dan penyetel secara efektif tidak berguna. Enkapsulasi banyak digunakan karena ini adalah standar dalam bahasa seperti Java. Tidak benar-benar membantu, selain menjaga konsistensi di seluruh basis kode.

class DataClass implements IDataInterface
{
    private int data;

    @Override public int getData() { return data; }
    @Override public void setData(int data) { this.data = data; }
}

Ini adalah contoh enkapsulasi yang bagus. Enkapsulasi digunakan untuk menegakkan kontrak, dalam hal ini IDataInterface. Tujuan enkapsulasi dalam contoh ini adalah untuk membuat konsumen kelas ini menggunakan metode yang disediakan oleh antarmuka. Meskipun pengambil dan penyetel tidak melakukan apa pun yang mewah, kami sekarang telah mendefinisikan sifat umum antara DataClass dan pelaksana lainnya dari IDataInterface. Dengan demikian saya dapat memiliki metode seperti ini:

void doSomethingWithData(IDataInterface data) { data.setData(...); }

Sekarang, ketika berbicara tentang enkapsulasi saya pikir penting untuk juga mengatasi masalah sintaksis. Saya sering melihat orang mengeluh tentang sintaksis yang diperlukan untuk menegakkan enkapsulasi daripada enkapsulasi itu sendiri. Salah satu contoh yang muncul dalam pikiran adalah dari Casey Muratori (Anda dapat melihat kata-katanya di sini ).

Misalkan Anda memiliki kelas pemain yang menggunakan enkapsulasi dan ingin memindahkan posisinya dengan 1 unit. Kode akan terlihat seperti ini:

player.setPosX(player.getPosX() + 1);

Tanpa enkapsulasi akan terlihat seperti ini:

player.posX++;

Di sini ia berpendapat bahwa enkapsulasi mengarah ke lebih banyak mengetik tanpa manfaat tambahan dan ini dalam banyak kasus bisa benar, tetapi perhatikan sesuatu. Argumen itu menentang sintaksis, bukan enkapsulasi itu sendiri. Bahkan dalam bahasa seperti C yang tidak memiliki konsep enkapsulasi, Anda akan sering melihat variabel dalam struct yang dipraxif atau sufixed dengan '_' atau 'my' atau apa pun untuk menandakan bahwa mereka tidak boleh digunakan oleh pengguna API, seolah-olah mereka adalah pribadi.

Faktanya adalah enkapsulasi dapat membantu membuat kode lebih mudah dirawat dan mudah digunakan. Pertimbangkan kelas ini:

class VerticalList implements ...
{
    private int posX;
    private int posY;
    ... //other members

    public void setPosition(int posX, int posY)
    {
        //change position and move all the objects in the list as well
    }
}

Jika variabel bersifat publik dalam contoh ini, maka konsumen API ini akan bingung kapan harus menggunakan posX dan posY dan kapan harus menggunakan setPosition (). Dengan menyembunyikan detail itu, Anda membantu konsumen lebih baik menggunakan API Anda dengan cara yang intuitif.

Sintaksnya adalah batasan dalam banyak bahasa. Namun bahasa yang lebih baru menawarkan properti yang memberi kita sintaksis yang bagus dari anggota publice dan manfaat enkapsulasi. Anda akan menemukan properti di C #, Kotlin, bahkan di C ++ jika Anda menggunakan MSVC. di sini adalah contoh di Kotlin.

kelas VerticalList: ... {var posX: Int set (x) {field = x; ...} var posY: Int set (y) {field = y; ...}}

Di sini kita mencapai hal yang sama seperti pada contoh Java, tetapi kita dapat menggunakan posX dan posY seolah-olah mereka variabel publik. Ketika saya mencoba mengubah nilainya, isi set settter () akan dieksekusi.

Di Kotlin misalnya, ini akan menjadi setara dengan Java Bean dengan getter, setter, kode hash, sama dengan dan toString diimplementasikan:

data class DataClass(var data: Int)

Perhatikan bagaimana sintaks ini memungkinkan kita melakukan Java Bean dalam satu baris. Anda dengan benar memperhatikan masalah yang dimiliki bahasa seperti Java dalam mengimplementasikan enkapsulasi, tetapi itu adalah kesalahan Java bukan enkapsulasi itu sendiri.

Anda mengatakan bahwa Anda menggunakan @Data Lombok untuk menghasilkan getter dan setter. Perhatikan namanya, @ Data. Ini sebagian besar dimaksudkan untuk digunakan pada kelas data yang hanya menyimpan data dan dimaksudkan untuk serial dan deserialized. Pikirkan sesuatu seperti menyimpan file dari gim. Tetapi dalam skenario lain, seperti dengan elemen UI, Anda paling pasti menginginkan setter karena hanya mengubah nilai variabel mungkin tidak cukup untuk mendapatkan perilaku yang diharapkan.


Bisakah Anda memberi contoh tentang penyalahgunaan enkapsulasi di sini? Itu bisa menarik. Contoh Anda agak menentang kurangnya enkapsulasi.
Gangnus

1
@ Gangnus saya menambahkan beberapa contoh. Enkapsulasi yang tidak berguna umumnya disalahgunakan dan juga dari pengalaman saya, pengembang API berusaha terlalu keras untuk memaksa Anda menggunakan API dengan cara tertentu. Saya tidak memberikan contoh untuk itu karena saya tidak punya yang mudah untuk disajikan. Jika saya menemukan satu, saya pasti akan menambahkannya. Saya pikir sebagian besar komentar terhadap enkapsulasi sebenarnya menentang sintaks enkapsulasi daripada enkapsulasi itu sendiri.
BananyaDev

Terima kasih atas hasil editnya. Saya menemukan kesalahan ketik kecil: "publice", bukan "publik".
jrh

2

Enkapsulasi memberi Anda fleksibilitas . Dengan memisahkan struktur dan antarmuka, ini memungkinkan Anda untuk mengubah struktur tanpa mengubah antarmuka.

Misalnya jika Anda menemukan bahwa Anda perlu menghitung properti berdasarkan bidang lain alih-alih menginisialisasi bidang yang mendasari konstruksi, Anda cukup mengubah pengambil. Jika Anda telah mengekspos bidang secara langsung, Anda harus mengubah antarmuka dan membuat perubahan di setiap situs penggunaan.


Meskipun ide bagus secara teori, ingatlah bahwa menyebabkan versi 2 dari API untuk membuang pengecualian bahwa versi 1 tidak akan merusak pengguna perpustakaan atau kelas Anda dengan sangat (dan mungkin secara diam-diam!); Saya agak skeptis bahwa perubahan besar dapat dibuat "di belakang layar" pada sebagian besar kelas data ini.
jrh

Maaf, ini bukan penjelasan. Fakta bahwa menggunakan bidang dilarang di antarmuka berasal dari ide enkapsulasi. Dan sekarang kita membicarakannya. Anda mendasarkan pada fakta yang sedang dibahas. (perhatikan, saya tidak berbicara pro atau kontra pemikiran Anda, hanya tentang mereka tidak dapat digunakan sebagai argumen di sini)
Gangnus

@ Gangnus Kata "antarmuka" memiliki makna di luar hanya kata kunci Java: dictionary.com/browse/interface
glennsl

@ jrh Itu masalah yang sama sekali berbeda. Anda dapat menggunakannya untuk membantah enkapsulasi dalam konteks tertentu , tetapi tidak dengan cara apa pun membantah argumen untuk itu.
glennsl

1
Enkapsulasi lama, tanpa massa / memberi kita tidak hanya Polimorfisme, tetapi juga keamanan. Dan set massa / mengajarkan kita menuju praktik buruk.
Gangnus

2

Saya akan mencoba mengilustrasikan ruang masalah enkapsulasi dan desain kelas, dan menjawab pertanyaan Anda di akhir.

Seperti disebutkan dalam jawaban lain, tujuan enkapsulasi adalah untuk menyembunyikan detail internal suatu objek di balik API publik, yang berfungsi sebagai kontrak. Objek aman untuk mengubah internalnya karena tahu itu hanya dipanggil melalui API publik.

Apakah masuk akal untuk memiliki bidang publik, atau getter / setter, atau metode transaksi tingkat tinggi atau pesan yang lewat tergantung pada sifat domain yang sedang dimodelkan. Dalam buku Akka Concurrency (yang dapat saya rekomendasikan meskipun agak ketinggalan zaman) Anda menemukan contoh yang menggambarkan hal ini, yang akan saya ringkas di sini.

Pertimbangkan kelas Pengguna:

public class User {
  private String first = "";
  private String last = "";

  public String getFirstName() {
    return this.first;
  }
  public void setFirstName(String s) {
    this.first = s;
  }

  public String getLastName() {
    return this.last;
  }
  public void setLastName(String s) {
    this.last = s;
  }
}

Ini berfungsi baik dalam konteks single-threaded. Domain yang dimodelkan adalah nama seseorang, dan mekanisme bagaimana nama itu disimpan dapat dienkapsulasi dengan sempurna oleh para setter.

Namun, bayangkan ini harus disediakan dalam konteks multi-utas. Misalkan satu utas secara berkala membaca nama:

System.out.println(user.getFirstName() + " " + user.getLastName());

Dan dua utas lainnya berjuang tarik ulur, mengaturnya untuk Hillary Clinton dan Donald Trump pada gilirannya. Mereka masing-masing perlu memanggil dua metode. Sebagian besar ini berfungsi dengan baik, tetapi sesekali Anda akan melihat Hillary Trump atau Donald Clinton lewat.

Anda tidak dapat menyelesaikan masalah ini dengan menambahkan kunci di dalam setter, karena kunci hanya ditahan selama pengaturan baik nama depan atau nama belakang. Satu-satunya solusi melalui penguncian adalah dengan menambahkan kunci di sekitar seluruh objek, tetapi itu memecah enkapsulasi karena kode panggilan harus mengelola kunci (dan dapat menyebabkan kebuntuan).

Ternyata, tidak ada solusi bersih melalui penguncian. Solusi bersih adalah merangkum ulang internal dengan membuatnya lebih kasar:

public class UserName {
   public final String first;
   public final String last;
   public UserName(String first, String last) { ... }
}

public class User
   private UserName name;
   public UserName getName() { return this.name; }
   public setName(UserName n) { this.name = n; }
}

Nama itu sendiri telah menjadi tidak berubah, dan Anda melihat bahwa anggotanya dapat menjadi publik, karena sekarang menjadi objek data murni tanpa kemampuan untuk memodifikasinya setelah dibuat. Pada gilirannya, API publik kelas Pengguna menjadi lebih kasar, dengan hanya satu setter tersisa, sehingga namanya hanya dapat diubah secara keseluruhan. Itu merangkum lebih banyak keadaan internal di belakang API.

Apa arti dari keseluruhan siklus ini? Dan apakah enkapsulasi benar-benar masuk akal sekarang dalam pemrograman kehidupan nyata?

Apa yang Anda lihat dalam siklus ini adalah upaya untuk menerapkan solusi yang baik untuk keadaan tertentu terlalu luas. Tingkat enkapsulasi yang sesuai membutuhkan pemahaman tentang domain yang dimodelkan dan menerapkan tingkat enkapsulasi yang tepat. Terkadang ini berarti semua bidang bersifat publik, kadang-kadang (seperti dalam aplikasi Akka) itu berarti Anda tidak memiliki API publik sama sekali kecuali metode tunggal untuk menerima pesan. Namun, konsep enkapsulasi itu sendiri, yang berarti menyembunyikan internal di belakang API yang stabil, adalah kunci untuk pemrograman perangkat lunak pada skala, terutama dalam sistem multi-threaded.


Fantastis. Saya akan mengatakan bahwa Anda menaikkan diskusi ke tingkat yang lebih tinggi. Mungkin dua. Sungguh, saya berpikir bahwa saya memahami enkapsulasi dan kadang-kadang lebih baik daripada buku-buku standar, dan benar-benar agak takut bahwa kita praktis kehilangannya karena beberapa kebiasaan dan teknologi yang diterima, dan ITULAH subjek sesungguhnya dari pertanyaan saya (mungkin, tidak dirumuskan dalam cara yang baik), tetapi Anda telah menunjukkan kepada saya sisi baru dari enkapsulasi. Saya jelas tidak pandai multitasking dan Anda telah menunjukkan kepada saya betapa berbahayanya memahami prinsip-prinsip dasar lainnya.
Gangnus

Tapi aku tidak dapat menandai posting Anda sebagai yang jawabannya, untuk itu bukan tentang pemuatan data massa ke / dari objek, masalah karena yang platform seperti kacang-kacangan dan Lombok muncul. Mungkin, Anda bisa menguraikan pemikiran Anda ke arah itu, tolong, jika Anda bisa mendapatkan waktu? Saya sendiri belum memikirkan kembali konsekuensi pemikiran Anda terhadap masalah itu. Dan saya tidak yakin saya cukup sehat untuk itu (ingat, latar belakang multithreading yang buruk: - [)
Gangnus

Saya belum pernah menggunakan lombok, tetapi seperti yang saya pahami, ini adalah cara menerapkan tingkat enkapsulasi tertentu (getter / setter pada setiap bidang) dengan lebih sedikit mengetik. Itu tidak mengubah bentuk ideal API untuk domain masalah Anda, itu hanya alat untuk lebih cepat menulisnya.
Joeri Sebrechts

1

Saya bisa memikirkan satu kasus penggunaan di mana ini masuk akal. Anda mungkin memiliki kelas yang semula Anda akses melalui API pengambil / penyetel sederhana. Anda nanti memperluas atau memodifikasi sehingga tidak lagi menggunakan bidang yang sama, tetapi masih mendukung API yang sama .

Contoh yang agak dibuat-buat: Titik yang dimulai sebagai pasangan Cartesian dengan p.x()dan p.y(). Anda nanti membuat implementasi atau subkelas baru yang menggunakan koordinat kutub, jadi Anda juga bisa memanggil p.r()dan p.theta(), tetapi kode klien Anda yang memanggil p.x()dan p.y()tetap valid. Kelas itu sendiri secara transparan mengkonversi dari bentuk kutub internal, yaitu y()sekarang return r * sin(theta);. (Dalam contoh ini, pengaturan saja x()atau y()tidak masuk akal, tetapi masih memungkinkan.)

Dalam hal ini, Anda mungkin mendapati diri Anda berkata, "Senang saya repot-repot secara otomatis mendeklarasikan getter dan setter daripada membuat bidang publik, atau saya harus menghancurkan API saya di sana."


2
Ini tidak begitu baik seperti yang Anda lihat. Mengubah representasi fisik batin benar-benar membuat objek berbeda. Dan buruknya mereka terlihat sama, karena mereka memiliki kualitas yang berbeda. Sistem Descartes tidak memiliki poin khusus, sistem kutub memiliki satu.
Gangnus

1
Jika Anda tidak menyukai contoh khusus itu, tentunya Anda dapat memikirkan orang lain yang benar-benar memiliki banyak representasi yang setara, atau representasi yang lebih baru yang dapat dikonversi ke dan dari yang lebih lama.
Davislor

Ya, Anda dapat menggunakannya secara sangat produktif untuk presentasi. Di UI dapat digunakan secara luas. Tetapi bukankah Anda membuat saya menjawab pertanyaan saya sendiri? :-)
Gangnus

Lebih efisien dengan cara itu, bukan? :)
Davislor

0

Bisakah seseorang menjelaskan kepada saya, apa arti menyembunyikan semua bidang sebagai pribadi dan setelah itu mengekspos semuanya dengan teknologi ekstra?

Sama sekali tidak ada gunanya. Namun, fakta Anda mengajukan pertanyaan itu menunjukkan bahwa Anda belum mengerti apa yang Lombok lakukan, dan bahwa Anda tidak mengerti bagaimana menulis kode OO dengan enkapsulasi. Mari kita mundur sedikit ...

Beberapa data untuk instance kelas akan selalu internal, dan tidak boleh diekspos. Beberapa data untuk instance kelas perlu diatur secara eksternal, dan beberapa data mungkin harus dikeluarkan kembali dari instance kelas. Kami mungkin ingin mengubah bagaimana kelas melakukan hal-hal di bawah permukaan, jadi kami menggunakan fungsi untuk memungkinkan kami mendapatkan dan mengatur data.

Beberapa program ingin menyimpan keadaan untuk instance kelas, jadi ini mungkin memiliki beberapa antarmuka serialisasi. Kami menambahkan lebih banyak fungsi yang memungkinkan instance kelas menyimpan statusnya ke penyimpanan dan mengambil statusnya dari penyimpanan. Ini mempertahankan enkapsulasi karena instance kelas masih mengendalikan datanya sendiri. Kami mungkin membuat serialisasi data pribadi, tetapi program lainnya tidak memiliki akses ke sana (atau lebih tepatnya, kami memelihara Tembok Cina dengan memilih untuk tidak dengan sengaja merusak data pribadi itu), dan instance kelas dapat (dan harus) melakukan pemeriksaan integritas deserialisation untuk memastikan datanya kembali OK.

Terkadang data membutuhkan pemeriksaan jangkauan, pemeriksaan integritas, atau hal-hal seperti itu. Menulis fungsi-fungsi ini sendiri memungkinkan kita melakukan semua itu. Dalam hal ini kami tidak ingin atau membutuhkan Lombok, karena kami melakukan semua itu sendiri.

Namun, sering kali Anda menemukan bahwa parameter yang diatur secara eksternal disimpan dalam variabel tunggal. Dalam hal ini Anda akan membutuhkan empat fungsi untuk mendapatkan / mengatur / membuat serial / deserialise isi dari variabel itu. Menulis keempat fungsi ini sendiri setiap kali memperlambat Anda dan rentan terhadap kesalahan. Mengotomatiskan proses dengan Lombok mempercepat pengembangan Anda dan menghilangkan kemungkinan kesalahan.

Ya, itu mungkin untuk membuat variabel itu publik. Dalam versi kode khusus ini, ini akan identik secara fungsional. Tetapi kembalilah ke mengapa kita menggunakan fungsi: "Kami mungkin ingin mengubah cara kelas melakukan hal-hal di bawah permukaan ..." Jika Anda membuat variabel Anda publik, Anda membatasi kode Anda sekarang dan selamanya untuk menjadikan variabel publik ini sebagai antarmuka. Jika Anda menggunakan fungsi, atau jika Anda menggunakan Lombok untuk secara otomatis menghasilkan fungsi-fungsi itu untuk Anda, Anda bebas untuk mengubah data yang mendasarinya dan implementasi yang mendasarinya setiap saat di masa depan.

Apakah ini membuatnya lebih jelas?


Anda berbicara tentang pertanyaan-pertanyaan dari siswa SW tahun pertama. Kami sudah berada di tempat lain. Saya tidak akan pernah mengatakan ini dan bahkan akan memberi Anda nilai tambah untuk jawaban yang cerdas, jika Anda tidak sopan dalam bentuk jawaban Anda.
Gangnus

0

Saya sebenarnya bukan pengembang Java; tetapi berikut ini cukup banyak platform agnostik.

Hampir semua yang kami tulis menggunakan getter dan setter publik yang mengakses variabel pribadi. Sebagian besar getter dan setter sepele. Tetapi ketika kita memutuskan bahwa setter perlu menghitung ulang sesuatu atau setter melakukan validasi atau kita perlu meneruskan properti ke properti variabel anggota kelas ini, ini sama sekali tidak melanggar seluruh kode dan kompatibel dengan biner sehingga kita dapat menukar satu modul keluar.

Ketika kami memutuskan properti ini benar-benar harus dihitung dengan cepat, semua kode yang melihatnya tidak perlu diubah dan hanya kode yang menulisnya yang perlu diubah dan IDE dapat menemukannya untuk kami. Ketika kami memutuskan bahwa ini adalah bidang komputasi yang dapat ditulisi (hanya pernah harus melakukan itu beberapa kali) kami juga dapat melakukannya. Yang menyenangkan adalah beberapa perubahan ini kompatibel biner (berubah menjadi bidang yang dihitung hanya baca tidak secara teori tetapi mungkin dalam praktiknya tetap).

Kami telah berakhir dengan banyak dan banyak getter sepele dengan setter rumit. Kami juga berakhir dengan beberapa caching getter. Hasil akhirnya adalah Anda diperbolehkan berasumsi bahwa getter cukup murah tetapi seter mungkin tidak. Di sisi lain, kami cukup bijak untuk memutuskan bahwa setingan tidak bertahan ke disk.

Tapi saya harus melacak orang yang secara buta mengubah semua variabel anggota menjadi properti. Dia tidak tahu apa yang ditambahkan dengan atom sehingga dia mengubah hal yang benar-benar perlu menjadi variabel publik ke properti dan memecahkan kode dengan cara yang halus.


Selamat datang di dunia Java. (C #, juga). *mendesah*. Tetapi untuk menggunakan get / set untuk menyembunyikan representasi internal, berhati-hatilah. Ini tentang format eksternal saja, tidak apa-apa. Tetapi jika itu tentang matematika, atau, lebih buruk, fisika atau hal nyata lainnya, representasi batin yang berbeda memiliki kualitas yang berbeda. yaitu komentar saya ke softwareengineering.stackexchange.com/a/358662/44104
Gangnus

@ Gangnus: Contoh poin itu sangat disayangkan. Kami sudah memiliki beberapa di antaranya tetapi perhitungannya selalu tepat.
Yosua

-1

Getters dan setter adalah ukuran "berjaga-jaga" yang ditambahkan demi menghindari refactoring di masa depan jika struktur internal atau persyaratan akses berubah selama proses pengembangan.

Katakanlah, beberapa bulan setelah rilis, klien Anda memberi tahu Anda bahwa salah satu bidang kelas kadang-kadang disetel ke nilai negatif, meskipun itu harus menjepit ke 0 paling banyak dalam kasus itu. Menggunakan bidang publik, Anda harus mencari setiap penugasan bidang ini di seluruh basis kode Anda untuk menerapkan fungsi penjepitan ke nilai yang akan Anda tetapkan, dan perlu diingat bahwa Anda akan selalu harus melakukannya ketika memodifikasi bidang ini, tapi itu menyebalkan. Sebaliknya, jika Anda sudah menggunakan getter dan setter, Anda bisa memodifikasi metode setField () Anda untuk memastikan penjepitan ini selalu diterapkan.

Sekarang, masalah dengan bahasa "kuno" seperti Java adalah bahwa mereka mendorong penggunaan metode untuk tujuan ini, yang hanya membuat kode Anda jauh lebih verbose. Sangat sulit untuk menulis, dan sulit dibaca, itulah sebabnya kami telah menggunakan IDE yang mengurangi masalah ini dengan satu atau lain cara. Sebagian besar IDE akan secara otomatis menghasilkan getter dan setter untuk Anda, dan juga menyembunyikannya kecuali jika diberitahukan sebaliknya. Lombok melangkah lebih jauh dan hanya membuatnya secara prosedural selama waktu kompilasi untuk menjaga kode Anda lebih ramping. Namun, bahasa lain yang lebih modern telah memecahkan masalah ini dengan satu atau lain cara. Bahasa Scala atau .NET, misalnya,

Misalnya, dalam VB .NET atau C #, Anda dapat dengan mudah membuat semua bidang yang dimaksudkan untuk memiliki - tidak ada efek samping - pengatur dan pengaturan hanya bidang publik, dan kemudian menjadikannya pribadi, mengubah nama mereka dan mengekspos Properti dengan nama sebelumnya dari bidang, tempat Anda dapat menyempurnakan perilaku akses bidang, jika Anda memerlukannya. Dengan Lombok, jika Anda perlu menyesuaikan perilaku pengambil atau penyetel, Anda bisa menghapus tag itu saat dibutuhkan, dan memberi kode Anda sendiri dengan persyaratan baru, dengan mengetahui bahwa Anda harus memperbaiki apa pun di file lain.

Pada dasarnya, cara metode Anda mengakses bidang harus transparan dan seragam. Bahasa modern memungkinkan Anda mendefinisikan "metode" dengan sintaks akses / panggilan yang sama pada suatu bidang, sehingga modifikasi ini dapat dilakukan sesuai permintaan tanpa banyak memikirkannya selama pengembangan awal, tetapi Java memaksa Anda untuk melakukan pekerjaan ini sebelumnya karena memang tidak memiliki fitur ini. Semua yang dilakukan Lombok adalah menghemat waktu Anda, karena bahasa yang Anda gunakan tidak ingin Anda menghemat waktu untuk metode "berjaga-jaga".


Dalam bahasa-bahasa "modern" itu, API terlihat seperti akses bidang foo.bartetapi dapat ditangani oleh pemanggilan metode. Anda mengklaim ini lebih unggul daripada cara "kuno" untuk membuat API terlihat seperti pemanggilan metode foo.getBar(). Kami tampaknya setuju bahwa bidang publik bermasalah, tetapi saya mengklaim bahwa alternatif "kuno" lebih unggul daripada yang "modern", karena seluruh API kami simetris (itu semua panggilan metode). Dalam pendekatan "modern", kita harus memutuskan hal-hal mana yang harus menjadi properti dan mana yang harus menjadi metode, yang terlalu rumit segala sesuatu (terutama jika kita menggunakan refleksi!).
Warbo

1
1. Menyembunyikan fisika tidak selalu begitu baik. lihat komentar saya di softwareengineering.stackexchange.com/a/358662/44104 . 2. Saya tidak berbicara tentang seberapa sulit atau sederhana kreasi pengambil / penyetel. C # memiliki masalah yang sama dengan yang kita bicarakan. Kurangnya inkapsulasi karena solusi buruk dari pemuatan info massa. 3. Ya, solusinya dapat didasarkan pada sintaksis, tetapi, tolong, dalam beberapa cara berbeda dari yang Anda sebutkan. Itu buruk, di sini kita punya halaman besar yang diisi dengan penjelasan. 4. Solusinya tidak perlu didasarkan pada sintaksis. Itu bisa dilakukan di batas Jawa.
Gangnus

-1

Bisakah seseorang menjelaskan kepada saya apa arti menyembunyikan semua bidang sebagai pribadi dan setelah itu mengekspos semuanya dengan teknologi ekstra? Mengapa kita tidak hanya menggunakan bidang publik saja? Saya merasa kami telah berjalan jauh dan sulit hanya untuk kembali ke titik awal.

Ya, itu kontradiktif. Saya pertama kali berlari ke properti di Visual Basic. Sampai saat itu, dalam bahasa lain, pengalaman saya, tidak ada pembungkus properti di sekitar bidang. Hanya bidang publik, pribadi, dan terlindungi.

Properti adalah semacam enkapsulasi. Saya memahami properti Visual Basic sebagai cara untuk mengontrol dan memanipulasi output dari satu atau beberapa bidang sambil menyembunyikan bidang eksplisit dan bahkan tipe datanya, misalnya memancarkan tanggal sebagai string dalam format tertentu. Tetapi meskipun demikian seseorang tidak "menyembunyikan status dan mengekspos fungsionalitas" dari perspektif objek yang lebih besar.

Tetapi properti membenarkan diri mereka sendiri karena pengambil dan penentu properti yang terpisah. Mengekspos bidang tanpa properti adalah semuanya atau tidak sama sekali - jika Anda bisa membacanya, Anda bisa mengubahnya. Jadi sekarang orang dapat membenarkan desain kelas lemah dengan setter terlindungi.

Jadi mengapa kita / mereka tidak menggunakan metode aktual? Karena itu Visual Basic (dan VBScript ) (oooh! Aaah!), Pengkodean untuk massa (!), Dan itu semua sangat digemari. Dan dengan demikian idiotokrasi akhirnya mendominasi.


Ya, properti untuk UI memiliki kepekaan khusus, properti tersebut merupakan representasi publik dan cantik dari bidang tersebut. Poin bagus.
Gangnus

"Jadi mengapa kita / mereka tidak menggunakan metode yang sebenarnya?" Secara teknis mereka melakukannya dalam versi .Net. Properti adalah gula sintaksis untuk mendapatkan dan mengatur fungsi.
Pharap

"idiotokrasi" ? Bukankah maksud Anda " keanehan "?
Peter Mortensen

Maksud saya idiocracy
radarbob
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.