Bagaimana Anda menghindari getter dan setter?


85

Saya mengalami sesuatu yang sulit dengan merancang kelas dengan cara oo. Saya pernah membaca bahwa objek memperlihatkan perilaku mereka, bukan data mereka; Oleh karena itu, daripada menggunakan pengambil / setter untuk memodifikasi data, metode kelas yang diberikan harus "kata kerja" atau tindakan yang beroperasi pada objek. Misalnya, dalam objek 'Akun', kita akan memiliki metode Withdraw()dan Deposit()bukannya setAmount()dll. Lihat: Mengapa metode pengambil dan penyetel adalah jahat .

Jadi misalnya, diberikan kelas Pelanggan yang menyimpan banyak informasi tentang pelanggan, misalnya Nama, DOB, Telp, Alamat dll., Bagaimana seseorang menghindari pengambil / penentu untuk mendapatkan dan mengatur semua atribut itu? Metode tipe 'Perilaku' apa yang dapat ditulis untuk mengisi semua data itu?




8
Saya akan menunjukkan bahwa jika Anda harus berurusan dengan spesifikasi Java Beans , Anda akan mendapatkan getter dan setter. Banyak hal yang menggunakan Java Beans (Bahasa Ekspresi dalam jsps) dan mencoba menghindarinya kemungkinan akan ... menantang.

4
... dan, dari perspektif yang berlawanan dari MichaelT: jika Anda tidak menggunakan spesifikasi JavaBeans (dan saya pribadi berpikir Anda harus menghindarinya kecuali jika Anda menggunakan objek Anda dalam konteks di mana diperlukan), maka tidak perlu untuk kutil "get" pada getter, terutama untuk properti yang tidak memiliki setter yang sesuai. Saya pikir metode yang disebut name()pada Customeradalah sebagai jelas, atau lebih jelas, dari metode yang disebut getName().
Daniel Pryden

2
@Daniel Pryden: name () dapat berarti mengatur atau mendapatkan? ...
IntelliData

Jawaban:


55

Sebagaimana dinyatakan dalam beberapa jawaban dan komentar, DTOs yang tepat dan berguna dalam beberapa situasi, terutama dalam mentransfer data melintasi batas-batas (misalnya serialisasi ke JSON untuk mengirim melalui layanan web). Untuk sisa jawaban ini, saya akan sedikit banyak mengabaikan itu dan berbicara tentang kelas domain, dan bagaimana mereka dapat dirancang untuk meminimalkan (jika tidak menghilangkan) getter dan setter, dan masih berguna dalam proyek besar. Saya juga tidak akan berbicara tentang mengapa menghapus getter atau setter, atau kapan melakukannya, karena itu adalah pertanyaan mereka sendiri.

Sebagai contoh, bayangkan bahwa proyek Anda adalah permainan papan seperti Catur atau Battleship. Anda mungkin memiliki berbagai cara untuk mewakili ini dalam lapisan presentasi (aplikasi konsol, layanan web, GUI, dll.), Tetapi Anda juga memiliki domain inti. Satu kelas yang mungkin Anda miliki adalah Coordinate, mewakili posisi di papan tulis. Cara "jahat" untuk menulisnya adalah:

public class Coordinate
{
    public int X {get; set;}
    public int Y {get; set;}
}

(Saya akan menulis contoh kode dalam C # daripada Java, untuk singkatnya dan karena saya lebih akrab dengannya. Mudah-mudahan itu bukan masalah. Konsepnya sama dan terjemahannya harus sederhana.)

Menghapus Setters: Immutability

Sementara getter dan setter publik keduanya berpotensi bermasalah, setter adalah yang jauh lebih "jahat" dari keduanya. Mereka juga biasanya lebih mudah dihilangkan. Prosesnya sederhana, atur nilai dari dalam konstruktor. Metode apa pun yang sebelumnya bermutasi objek seharusnya mengembalikan hasil baru. Begitu:

public class Coordinate
{
    public int X {get; private set;}
    public int Y {get; private set;}

    public Coordinate(int x, int y)
    {
        X = x;
        Y = y;
    }
}

Perhatikan bahwa ini tidak melindungi terhadap metode lain di kelas yang bermutasi X dan Y. Agar lebih tidak dapat diubah, Anda bisa menggunakan readonly( finaldi Jawa). Tetapi bagaimanapun juga - apakah Anda membuat properti Anda benar-benar tidak dapat diubah atau hanya mencegah mutasi publik langsung melalui setter - itu memang menghilangkan trik setter publik Anda. Dalam sebagian besar situasi, ini berfungsi dengan baik.

Menghapus Getters, Bagian 1: Merancang untuk Perilaku

Di atas semua baik dan bagus untuk setter, tetapi dalam hal getter, kami benar-benar menembak diri sendiri sebelum memulai. Proses kami adalah memikirkan apa yang dimaksud dengan koordinat - data yang diwakilinya - dan membuat kelas di sekitarnya. Sebagai gantinya, kita harus mulai dengan perilaku apa yang kita butuhkan dari seorang koordinator. Omong-omong, proses ini dibantu oleh TDD, di mana kita hanya mengekstrak kelas-kelas seperti ini begitu kita membutuhkannya, jadi kita mulai dengan perilaku yang diinginkan dan bekerja dari sana.

Jadi katakanlah tempat pertama yang Anda perlukan Coordinateadalah untuk deteksi tabrakan: Anda ingin memeriksa apakah dua potong menempati ruang yang sama di papan tulis. Inilah cara "jahat" (konstruktor dihilangkan karena singkatnya):

public class Piece
{
    public Coordinate Position {get; private set;}
}

public class Coordinate
{
    public int X {get; private set;}
    public int Y {get; private set;}
}

    //...And then, inside some class
    public bool DoPiecesCollide(Piece one, Piece two)
    {
        return one.X == two.X && one.Y == two.Y;
    }

Dan inilah cara yang baik:

public class Piece
{
    private Coordinate _position;
    public bool CollidesWith(Piece other)
    {
        return _position.Equals(other._position);
    }
}

public class Coordinate
{
    private readonly int _x;
    private readonly int _y;
    public bool Equals(Coordinate other)
    {
        return _x == other._x && _y == other._y;
    }
}

( IEquatableimplementasi disingkat karena kesederhanaan). Dengan mendesain untuk perilaku daripada memodelkan data, kami telah berhasil menghapus getter kami.

Perhatikan ini juga relevan dengan contoh Anda. Anda mungkin menggunakan ORM, atau menampilkan informasi pelanggan di situs web atau sesuatu, dalam hal ini semacam CustomerDTO mungkin masuk akal. Tetapi hanya karena sistem Anda mencakup pelanggan dan mereka terwakili dalam model data tidak secara otomatis berarti Anda harus memiliki Customerkelas di domain Anda. Mungkin saat Anda mendesain untuk perilaku, seseorang akan muncul, tetapi jika Anda ingin menghindari getter, jangan membuatnya terlebih dahulu.

Menghapus Getters, Bagian 2: Perilaku Eksternal

Jadi di atas adalah awal yang baik, tetapi cepat atau lambat Anda mungkin akan mengalami situasi di mana Anda memiliki perilaku yang berhubungan dengan kelas, yang dalam beberapa cara tergantung pada negara kelas, tetapi yang bukan milik atas kelas. Perilaku semacam ini adalah yang biasanya hidup di lapisan layanan aplikasi Anda.

Mengambil Coordinatecontoh kami , pada akhirnya Anda akan ingin mewakili permainan Anda kepada pengguna, dan itu mungkin berarti menggambar ke layar. Anda mungkin, misalnya, memiliki proyek UI yang digunakan Vector2untuk mewakili suatu titik di layar. Tetapi tidak pantas bagi Coordinatekelas untuk mengambil alih konversi dari koordinat ke titik di layar - yang akan membawa segala macam masalah presentasi ke dalam domain inti Anda. Sayangnya situasi semacam ini melekat dalam desain OO.

Pilihan pertama , yang sangat umum dipilih, adalah hanya mengekspos para pengambil sialan dan mengatakan persetan dengan itu. Ini memiliki keunggulan kesederhanaan. Tetapi karena kita berbicara tentang menghindari getter, katakanlah demi argumen, kita menolak yang ini dan melihat opsi apa yang ada.

Opsi kedua adalah menambahkan semacam .ToDTO()metode di kelas Anda. Ini - atau yang serupa - mungkin diperlukan juga, misalnya ketika Anda ingin menyimpan permainan, Anda perlu menangkap hampir semua negara Anda. Tetapi perbedaan antara melakukan ini untuk layanan Anda dan hanya mengakses pengambil secara langsung lebih atau kurang estetika. Itu masih memiliki "kejahatan" sebanyak itu.

Opsi ketiga - yang saya lihat didukung oleh Zoran Horvat dalam beberapa video Pluralsight- adalah menggunakan versi modifikasi dari pola pengunjung. Ini adalah penggunaan yang cukup tidak biasa dan variasi dari pola dan saya pikir jarak tempuh orang akan sangat bervariasi pada apakah itu menambah kompleksitas tanpa keuntungan nyata atau apakah itu kompromi yang bagus untuk situasi tersebut. Idenya pada dasarnya adalah untuk menggunakan pola pengunjung standar, tetapi minta Visitmetode mengambil status yang mereka butuhkan sebagai parameter, bukan kelas yang mereka kunjungi. Contohnya dapat ditemukan di sini .

Untuk masalah kami, solusi menggunakan pola ini adalah:

public class Coordinate
{
    private readonly int _x;
    private readonly int _y;

    public T Transform<T>(IPositionTransformer<T> transformer)
    {
        return transformer.Transform(_x,_y);
    }
}

public interface IPositionTransformer<T>
{
    T Transform(int x, int y);
}

//This one lives in the presentation layer
public class CoordinateToVectorTransformer : IPositionTransformer<Vector2>
{
    private readonly float _tileWidth;
    private readonly float _tileHeight;
    private readonly Vector2 _topLeft;

    Vector2 Transform(int x, int y)
    {
        return _topLeft + new Vector2(_tileWidth*x + _tileHeight*y);
    }
}

Seperti yang Anda mungkin tahu, _xdan _ytidak benar - benar dienkapsulasi lagi. Kita dapat mengekstraknya dengan membuat IPositionTransformer<Tuple<int,int>>yang baru mengembalikannya secara langsung. Tergantung pada rasanya, Anda mungkin merasa ini membuat seluruh latihan menjadi sia-sia.

Namun, dengan getter publik, sangat mudah untuk melakukan hal-hal dengan cara yang salah, hanya menarik data secara langsung dan menggunakannya melanggar Tell, Don't Ask . Sedangkan menggunakan pola ini sebenarnya lebih mudah untuk melakukannya dengan cara yang benar: ketika Anda ingin membuat perilaku, Anda akan secara otomatis mulai dengan membuat tipe yang terkait dengannya. Pelanggaran terhadap TDA akan sangat berbau dan mungkin membutuhkan solusi yang lebih sederhana dan lebih baik. Dalam praktiknya, poin-poin ini membuatnya jauh lebih mudah untuk melakukannya dengan cara yang benar, OO, daripada cara "jahat" yang didorong oleh para pengecut.

Akhirnya , bahkan jika itu awalnya tidak jelas, mungkin sebenarnya ada cara untuk mengekspos cukup dari apa yang Anda butuhkan sebagai perilaku untuk menghindari keharusan untuk mengekspos keadaan. Misalnya, menggunakan versi kami sebelumnya Coordinateyang hanya anggota publik Equals()(dalam praktiknya akan membutuhkan IEquatableimplementasi penuh ), Anda dapat menulis kelas berikut di lapisan presentasi Anda:

public class CoordinateToVectorTransformer
{
    private Dictionary<Coordinate,Vector2> _coordinatePositions;

    public CoordinateToVectorTransformer(int boardWidth, int boardHeight)
    {
        for(int x=0; x<boardWidth; x++)
        {
            for(int y=0; y<boardWidth; y++)
            {
                _coordinatePositions[new Coordinate(x,y)] = GetPosition(x,y);
            }
        }
    }

    private static Vector2 GetPosition(int x, int y)
    {
        //Some implementation goes here...
    }

    public Vector2 Transform(Coordinate coordinate)
    {
        return _coordinatePositions[coordinate];
    }
}

Ternyata, mungkin secara mengejutkan, bahwa semua perilaku yang benar - benar kita butuhkan dari koordinat untuk mencapai tujuan kita adalah pengecekan kesetaraan! Tentu saja, solusi ini dirancang untuk masalah ini, dan membuat asumsi tentang penggunaan / kinerja memori yang dapat diterima. Itu hanya contoh yang sesuai dengan domain masalah khusus ini, bukan cetak biru untuk solusi umum.

Dan lagi, pendapat akan bervariasi tergantung pada apakah dalam praktiknya ini adalah kompleksitas yang tidak perlu. Dalam beberapa kasus, tidak ada solusi seperti ini yang mungkin ada, atau mungkin sangat aneh atau kompleks, dalam hal ini Anda dapat kembali ke tiga di atas.


Dijawab dengan indah! Saya ingin menerima, tetapi pertama-tama beberapa komentar: 1. Saya pikir toDTO () bagus, karena Anda tidak mengakses get / set, yang memungkinkan Anda untuk mengubah bidang yang diberikan kepada DTO tanpa melanggar kode yang ada. 2. Katakanlah Pelanggan memiliki perilaku yang cukup untuk membenarkan menjadikannya entitas, bagaimana Anda mengakses alat peraga untuk memodifikasinya, mis. Perubahan alamat / tel dll.
IntelliData

@IntelliData 1. Ketika Anda mengatakan "ubah bidang", maksud Anda mengubah definisi kelas atau memutasi data? Yang terakhir hanya bisa dihindari dengan menghapus setter publik tetapi meninggalkan getter, sehingga aspek dto tidak relevan. Yang pertama tidak benar-benar alasan (seluruh) bahwa getter publik adalah "jahat". Lihat programmers.stackexchange.com/questions/157526/… misalnya.
Ben Aaronson

@IntelliData 2. Ini sulit dijawab tanpa mengetahui perilakunya. Tetapi mungkin, jawabannya adalah: Anda tidak akan melakukannya. Perilaku apa yang Customerdimiliki suatu kelas yang mengharuskan untuk dapat mengubah nomor teleponnya? Mungkin nomor telepon pelanggan berubah dan saya perlu bertahan bahwa perubahan dalam database, tetapi tidak ada yang menjadi tanggung jawab objek domain yang menyediakan perilaku. Itu adalah masalah akses-data, dan mungkin akan ditangani dengan DTO dan, katakanlah, repositori.
Ben Aaronson

@IntelliData Menjaga Customerdata objek domain relatif segar (selaras dengan db) adalah masalah mengelola daur hidupnya, yang juga bukan tanggung jawabnya sendiri, dan mungkin sekali lagi akan berakhir di repositori atau pabrik atau wadah IOC atau instantiate apapun Customers.
Ben Aaronson

2
Saya sangat suka konsep Desain untuk Perilaku . Ini menginformasikan "struktur data" yang mendasarinya dan membantu menghindari kelas anemia yang terlalu umum dan sulit digunakan. tambah satu.
radarbob

71

Cara paling sederhana untuk menghindari setter adalah menyerahkan nilai ke metode konstruktor saat Anda newmenaikkan objek. Ini juga pola yang biasa ketika Anda ingin membuat objek tidak berubah. Yang mengatakan, hal-hal tidak selalu jelas di dunia nyata.

Memang benar bahwa metode harus tentang perilaku. Namun, beberapa objek, seperti Pelanggan, ada terutama untuk menyimpan informasi. Itu adalah jenis objek yang paling diuntungkan dari getter dan setter; jika tidak perlu sama sekali untuk metode seperti itu, kami hanya akan menghilangkannya sama sekali.

Bacaan Lebih Lanjut
Kapan Getters dan Setters Dibenarkan


3
jadi mengapa semua hype tentang 'Getter / Setters' itu jahat?
IntelliData

46
Hanya pemurnian biasa oleh pengembang perangkat lunak yang seharusnya tahu lebih baik. Untuk artikel yang Anda tautkan, penulis menggunakan frasa "getter and setters is evil" untuk mendapatkan perhatian Anda, tetapi itu tidak berarti pernyataan tersebut benar.
Robert Harvey

12
@IntelliData beberapa orang akan memberi tahu Anda bahwa java itu sendiri jahat.
null

18
@MetaFightsetEvil(null);

4
Pastinya eval adalah Evarn Incarnate. Atau sepupu dekat.
Uskup

58

Sangat baik untuk memiliki objek yang mengekspos data daripada perilaku. Kami hanya menyebutnya "objek data". Pola ada di bawah nama-nama seperti Objek Transfer Data atau Nilai Objek. Jika tujuan objek adalah untuk menyimpan data, maka getter dan setter berlaku untuk mengakses data.

Jadi mengapa seseorang mengatakan "metode pengambil dan penyetel itu jahat"? Anda akan sering melihat ini - seseorang mengambil panduan yang benar-benar valid dalam konteks tertentu dan kemudian menghapus konteksnya untuk mendapatkan informasi utama yang lebih keras. Misalnya, " mengunggulkan komposisi daripada warisan " adalah prinsip yang bagus, tetapi tak lama kemudian seseorang akan menghapus konteks dan menulis " Mengapa meluas itu jahat " (hai, penulis yang sama, kebetulan sekali!) Atau " warisan itu jahat dan harus hancur ".

Jika Anda melihat konten artikel yang sebenarnya memiliki beberapa poin valid, itu hanya memperpanjang poin untuk membuat headline klik-baity. Sebagai contoh, artikel tersebut menyatakan bahwa detail implementasi tidak boleh diekspos. Ini adalah prinsip enkapsulasi dan penyembunyian data yang mendasar dalam OO. Namun, metode pengambil tidak secara definisi mengekspos detail implementasi. Dalam kasus objek data Pelanggan , properti Nama , Alamat dll. Bukan detail implementasi melainkan seluruh tujuan objek dan harus menjadi bagian dari antarmuka publik.

Baca kelanjutan dari artikel yang Anda tautkan, untuk melihat bagaimana ia menyarankan sebenarnya pengaturan properti seperti 'nama' dan 'gaji' pada objek 'Karyawan'-tanpa menggunakan setter jahat. Ternyata ia menggunakan pola dengan 'Eksportir' yang diisi dengan metode yang disebut add Name, tambahkan Gaji yang pada gilirannya menetapkan bidang dengan nama yang sama ... Jadi pada akhirnya ia akhirnya menggunakan persis pola penyetel, hanya dengan konvensi penamaan yang berbeda.

Ini seperti berpikir Anda menghindari jebakan para lajang dengan mengganti nama mereka menjadi satu-satunya yang bisa bertahan sambil menjaga implementasi yang sama.


Dalam oop, para pakar yang disebut tampaknya mengatakan bahwa ini tidak terjadi.
IntelliData

8
Kemudian lagi, beberapa orang mengatakan 'Jangan pernah menggunakan OOP': dangerous.cat-v.org/software/OO_programming
JacquesB

7
FTR, saya pikir lebih banyak "ahli" masih mengajar untuk membuat getter / setter yang tidak berarti untuk semuanya , daripada tidak pernah membuat sama sekali. IMO, yang terakhir adalah saran yang kurang menyesatkan.
leftaroundabout

4
@leftaroundabout: OK, tapi saya menyarankan jalan tengah antara 'selalu' dan 'tidak pernah' yang merupakan 'digunakan saat yang tepat'.
JacquesB

3
Saya pikir masalahnya adalah bahwa banyak programmer mengubah setiap objek (atau terlalu banyak objek) menjadi DTO. Kadang-kadang diperlukan, tetapi hindari sebanyak mungkin karena mereka memisahkan data dari perilaku. (Anggap saja Anda adalah seorang OOPer yang taat)
user949300

11

Untuk mengubah Customer-class dari objek data, kita dapat bertanya pada diri sendiri pertanyaan berikut tentang bidang data:

Bagaimana kita ingin menggunakan {data field}? Di mana {data field} digunakan? Bisakah dan haruskah penggunaan {data field} dipindahkan ke kelas?

Misalnya:

  • Apa tujuannya Customer.Name?

    Kemungkinan jawaban, tampilkan nama di halaman web login, gunakan nama dalam surat kepada pelanggan.

    Yang mengarah ke metode:

    • Customer.FillInTemplate (...)
    • Pelanggan. Berlaku untuk Berlayar (...)
  • Apa tujuannya Customer.DOB?

    Memvalidasi usia pelanggan. Diskon pada hari ulang tahun pelanggan. Surat

    • Pelanggan. Dapat Berlaku untuk Produk ()
    • Pelanggan.GetPersonalDiscount ()
    • Pelanggan. Berlaku untuk Berlangsung ()

Mengingat komentar, objek contoh Customer- baik sebagai objek data dan sebagai objek "nyata" dengan tanggung jawabnya sendiri - terlalu luas; yaitu memiliki terlalu banyak properti / tanggung jawab. Yang mengarah ke banyak komponen tergantung pada Customer(dengan membaca propertinya) atau Customerbergantung pada banyak komponen. Mungkin ada pandangan pelanggan yang berbeda, mungkin masing-masing harus memiliki kelas 1 yang berbeda :

  • Pelanggan dalam konteks Accountdan transaksi moneter mungkin hanya digunakan untuk:

    • membantu manusia mengidentifikasi bahwa pengiriman uang mereka ke orang yang tepat; dan
    • grup Accounts.

    Pelanggan ini tidak perlu bidang seperti DOB, FavouriteColour, Tel, dan mungkin bahkan tidak Address.

  • Pelanggan dalam konteks pengguna yang masuk ke situs web perbankan.

    Bidang yang relevan adalah:

    • FavouriteColour, yang mungkin datang dalam bentuk tema pribadi;
    • LanguagePreferences, dan
    • GreetingName

    Alih-alih properti dengan getter dan setter, ini mungkin ditangkap dalam satu metode:

    • PersonaliseWebPage (halaman Templat);
  • Pelanggan dalam konteks pemasaran dan pengiriman surat pribadi.

    Di sini tidak mengandalkan properti dari suatu objek data, melainkan mulai dari tanggung jawab objek; misalnya:

    • IsCustomerInterestedInAction (); dan
    • GetPersonalDiscounts ().

    Fakta bahwa objek pelanggan ini memiliki FavouriteColourproperti, dan / atau Addressproperti menjadi tidak relevan: mungkin implementasinya menggunakan properti ini; tetapi mungkin juga menggunakan beberapa teknik pembelajaran mesin dan menggunakan interaksi sebelumnya dengan pelanggan untuk menemukan produk mana yang mungkin diminati pelanggan.


1. Tentu saja, Customerdan Accountkelas adalah contoh, dan untuk contoh sederhana atau latihan pekerjaan rumah, memisahkan pelanggan ini mungkin berlebihan, tetapi dengan contoh pemisahan, saya berharap untuk menunjukkan bahwa metode mengubah objek data menjadi objek dengan tanggung jawab akan berhasil.


4
Suara positif karena Anda benar-benar menjawab pertanyaan :) Namun juga jelas bahwa solusi yang diusulkan jauh lebih buruk daripada hanya memiliki gettes / setter - misalnya FillInTemplate jelas mematahkan prinsip pemisahan kekhawatiran. Yang hanya menunjukkan bahwa premis pertanyaan itu cacat.
JacquesB

@ Kasper van den Berg: Dan ketika Anda memiliki banyak atribut di Pelanggan seperti biasanya, bagaimana Anda awalnya mengaturnya?
IntelliData

2
@IntelliData, nilai Anda kemungkinan berasal dari database, XML, dll. Pelanggan, atau CustomerBuilder, membacanya, lalu menetapkan menggunakan (mis. Di Jawa) akses pribadi / paket / kelas dalam, atau, refleksi (ick). Tidak sempurna, tetapi Anda biasanya dapat menghindari setter publik. (Lihat jawaban saya untuk lebih jelasnya)
user949300

6
Saya tidak berpikir kelas pelanggan harus tahu tentang hal-hal ini.
CodesInChaos

Bagaimana dengan sesuatu seperti itu Customer.FavoriteColor?
Gabe

8

TL; DR

  • Pemodelan untuk perilaku itu baik.

  • Memodelkan abstraksi untuk kebaikan (!) Lebih baik.

  • Terkadang objek data diperlukan.


Perilaku dan Abstraksi

Ada beberapa alasan untuk menghindari getter dan setter. Salah satunya adalah, seperti yang Anda perhatikan, untuk menghindari pemodelan data. Ini sebenarnya adalah alasan kecil. Alasan yang lebih besar adalah untuk memberikan abstraksi.

Dalam contoh Anda dengan rekening bank yang jelas: Suatu setBalance()metode akan menjadi sangat buruk karena pengaturan saldo bukanlah tujuan penggunaan akun. Perilaku akun harus abstrak dari saldo saat ini sebanyak mungkin. Mungkin mempertimbangkan saldo ketika memutuskan apakah akan gagal melakukan penarikan, itu dapat memberikan akses ke saldo saat ini, tetapi memodifikasi interaksi dengan rekening bank seharusnya tidak mengharuskan pengguna untuk menghitung saldo baru. Itulah yang seharusnya dilakukan oleh akun itu sendiri.

Bahkan sepasang deposit()dan withdraw()metode tidak ideal untuk memodelkan rekening bank. Cara yang lebih baik adalah dengan menyediakan hanya satu transfer()metode yang menggunakan akun lain dan jumlah sebagai argumen. Ini akan memungkinkan kelas akun untuk secara sepele memastikan bahwa Anda tidak secara tidak sengaja membuat / menghancurkan uang di sistem Anda, itu akan memberikan abstraksi yang sangat bermanfaat, dan itu benar-benar akan memberikan pengguna dengan lebih banyak wawasan karena akan memaksa penggunaan akun khusus untuk uang yang didapat / diinvestasikan / hilang (lihat akuntansi ganda ). Tentu saja, tidak setiap penggunaan akun membutuhkan tingkat abstraksi ini, tetapi jelas layak untuk mempertimbangkan berapa banyak abstraksi yang dapat diberikan oleh kelas Anda.

Perhatikan bahwa menyediakan abstraksi dan menyembunyikan data internal tidak selalu sama. Hampir semua aplikasi berisi kelas yang hanya data secara efektif. Tuple, kamus, dan array adalah contoh yang sering. Anda tidak ingin menyembunyikan koordinat x suatu titik dari pengguna. Ada sangat sedikit abstraksi yang bisa / harus Anda lakukan dengan sebuah poin.


Kelas Pelanggan

Pelanggan jelas merupakan entitas dalam sistem Anda yang harus mencoba memberikan abstraksi yang bermanfaat. Sebagai contoh, itu kemungkinan harus dikaitkan dengan keranjang belanja, dan kombinasi dari keranjang dan pelanggan harus memungkinkan melakukan pembelian, yang mungkin memulai tindakan seperti mengirimnya produk yang diminta, membebankan biaya kepadanya (dengan mempertimbangkan pembayaran yang dipilihnya) metode), dll.

Tangkapannya adalah, bahwa semua data yang Anda sebutkan tidak hanya terkait dengan pelanggan, semua data itu juga bisa berubah. Pelanggan bisa pindah. Mereka dapat mengubah perusahaan kartu kredit mereka. Mereka dapat mengubah alamat email dan nomor telepon mereka. Heck, mereka bahkan dapat mengubah nama dan / atau jenis kelamin mereka! Jadi, kelas pelanggan berfitur lengkap memang harus menyediakan akses modifikasi penuh ke semua item data ini.

Namun, setter dapat / harus menyediakan layanan non-sepele: Mereka dapat memastikan format alamat email yang benar, verifikasi alamat pos, dll. Demikian juga, "pengambil" dapat menyediakan layanan tingkat tinggi seperti menyediakan alamat email dalam Name <user@server.com>format menggunakan bidang nama dan alamat email yang disetor, atau memberikan alamat pos yang diformat dengan benar, dll. Tentu saja, fungsi fungsional tingkat tinggi apa ini sangat tergantung pada kasus penggunaan Anda. Itu mungkin benar-benar berlebihan, atau mungkin memanggil kelas lain untuk melakukannya dengan benar. Pemilihan tingkat abstraksi tidak mudah.


kedengarannya benar, meskipun saya tidak setuju pada bagian seks ...;)
IntelliData

6

Mencoba mengembangkan jawaban Kasper, paling mudah untuk menentang dan menghilangkan setter. Dalam argumen yang agak kabur, handwaving (dan semoga lucu):

Kapan Pelanggan. Nama akan berubah?

Jarang. Mungkin mereka menikah. Atau pergi ke perlindungan saksi. Tetapi dalam hal ini Anda juga ingin memeriksa dan mungkin mengubah tempat tinggal mereka, keluarga terdekat, dan informasi lainnya.

Kapan DOB akan berubah?

Hanya saat pembuatan awal, atau pada entri entri data. Atau jika mereka adalah pemain baseball Domincan. :-)

Bidang-bidang ini tidak boleh diakses dengan setter rutin dan normal. Mungkin Anda memiliki Customer.initialEntry()metode, atau Customer.screwedUpHaveToChange()metode yang memerlukan izin khusus. Tetapi tidak memiliki Customer.setDOB()metode publik .

Biasanya Pelanggan dibaca dari database, API REST, beberapa XML, apa pun. Memiliki metode Customer.readFromDB(), atau, jika Anda lebih tegas tentang SRP / pemisahan masalah, Anda akan memiliki pembangun yang terpisah, misalnya CustomerPersisterobjek dengan read()metode. Secara internal, mereka entah bagaimana mengatur bidang (Saya lebih suka menggunakan akses paket atau kelas dalam, YMMV). Tapi sekali lagi, hindari setter publik.

(Tambahan sebagai Pertanyaan telah agak berubah ...)

Katakanlah aplikasi Anda banyak menggunakan basis data relasional. Akan bodoh memiliki Customer.saveToMYSQL()atau Customer.readFromMYSQL()metode. Itu menciptakan kopling yang tidak diinginkan ke entitas konkret, non-standar, dan cenderung berubah . Misalnya, ketika Anda mengubah skema, atau mengubah ke Postgress atau Oracle.

Namun, IMO, itu bisa diterima untuk beberapa pelanggan ke standar abstrak , ResultSet. Objek pembantu yang terpisah (saya akan menyebutnya CustomerDBHelper, yang mungkin merupakan subkelas dari AbstractMySQLHelper) tahu tentang semua koneksi kompleks ke DB Anda, mengetahui detail optimasi rumit, mengetahui tabel, kueri, bergabung, dll ... (atau menggunakan ORM suka Hibernate) untuk menghasilkan ResultSet. Objek Anda berbicara ke ResultSet, yang merupakan standar abstrak , tidak mungkin berubah. Saat Anda mengubah database yang mendasarinya, atau mengubah skema, Pelanggan tidak berubah , tetapi CustomerDBHelper berubah . Jika Anda beruntung, itu hanya AbstractMySQLHelper yang berubah yang secara otomatis membuat perubahan untuk Pelanggan, Merchant, Pengiriman dll ...

Dengan cara ini Anda dapat (mungkin) menghindari atau mengurangi kebutuhan getter dan setter.

Dan, poin utama dari artikel Holub, membandingkan dan membedakan di atas dengan bagaimana jadinya jika Anda menggunakan getter dan setter untuk semuanya dan mengubah database.

Demikian pula, katakanlah Anda menggunakan banyak XML. IMO, tidak apa-apa untuk memasangkan Pelanggan Anda dengan standar abstrak, seperti Python xml.etree.ElementTree atau Java org.w3c.dom.Element . Pelanggan mendapatkan dan menetapkan sendiri dari itu. Sekali lagi, Anda dapat (mungkin) mengurangi kebutuhan getter dan setter.


apakah menurut Anda disarankan untuk menggunakan Pola Builder?
IntelliData

Builder berguna untuk membuat konstruksi objek lebih mudah dan lebih kuat, dan, jika Anda mau, memungkinkan objek menjadi tidak berubah. Namun, itu masih (sebagian) memperlihatkan fakta bahwa objek yang mendasarinya memiliki bidang DOB, jadi itu bukan lebah semuanya.
user949300

1

Masalah memiliki getter dan setter dapat menjadi masalah fakta bahwa suatu kelas dapat digunakan dalam logika bisnis dalam satu cara tetapi Anda mungkin juga memiliki kelas pembantu untuk membuat serial / deserialisasi data dari database atau file atau penyimpanan persisten lainnya.

Karena faktanya ada banyak cara untuk menyimpan / mengambil data Anda dan Anda ingin memisahkan objek data dari cara mereka disimpan, enkapsulasi dapat "dikompromikan" dengan membuat anggota ini dipublikasikan, atau membuatnya dapat diakses melalui getter dan setter yang hampir sama buruknya dengan membuat mereka publik.

Ada berbagai cara untuk mengatasi hal ini. Salah satu caranya adalah membuat data tersedia untuk "teman". Meskipun pertemanan tidak diwariskan, ini dapat diatasi dengan serializer apa pun yang meminta informasi dari teman, yaitu serializer dasar "meneruskan" informasi tersebut.

Kelas Anda bisa menggunakan metode generik "fromMetadata" atau "toMetadata". From-metadata membangun objek jadi mungkin juga merupakan konstruktor. Jika itu adalah bahasa yang diketik secara dinamis, metadata cukup standar untuk bahasa seperti itu dan mungkin merupakan cara utama untuk membangun objek seperti itu.

Jika bahasa Anda khusus C ++, salah satu cara untuk mengatasi hal ini adalah dengan memiliki "struct" data publik dan kemudian untuk kelas Anda memiliki instance "struct" ini sebagai anggota dan pada kenyataannya semua data yang akan Anda simpan / ambil untuk disimpan di dalamnya. Anda kemudian dapat dengan mudah menulis "pembungkus" untuk membaca / menulis data Anda dalam berbagai format.

Jika bahasa Anda adalah C # atau Java yang tidak memiliki "struct" maka Anda dapat melakukan hal yang sama tetapi struct Anda sekarang menjadi kelas sekunder. Tidak ada konsep nyata "kepemilikan" dari data atau keteguhan jadi jika Anda memberikan contoh kelas yang berisi data Anda dan semuanya publik, apa pun yang ditahan dapat memodifikasinya. Anda bisa "mengkloningnya" walaupun ini bisa mahal. Atau Anda bisa membuat kelas ini memiliki data pribadi tetapi menggunakan accessor. Itu memberi pengguna kelas Anda cara bundaran untuk mendapatkan data tetapi itu bukan antarmuka langsung dengan kelas Anda dan benar-benar detail dalam menyimpan data kelas yang juga merupakan kasus penggunaan.


0

OOP adalah tentang merangkum dan menyembunyikan perilaku di dalam objek. Objek adalah kotak hitam. Ini adalah cara untuk mendesain sesuatu. Aset dalam banyak kasus seseorang tidak perlu mengetahui keadaan internal komponen lain dan lebih baik tidak harus mengetahuinya. Anda dapat menerapkan gagasan itu dengan antarmuka utama atau di dalam objek dengan visibilitas dan dengan hati-hati hanya verba / tindakan yang diizinkan yang tersedia untuk pemanggil.

Ini bekerja dengan baik untuk beberapa jenis masalah. Misalnya dalam antarmuka pengguna untuk memodelkan komponen UI individu. Ketika Anda berinteraksi dengan kotak teks, Anda hanya terputus dalam pengaturan teks, mendapatkannya atau mendengarkan acara perubahan teks. Anda biasanya tidak tertarik ke mana kursor berada, font yang digunakan untuk menggambar teks atau bagaimana keyboard digunakan. Enkapsulasi memberikan banyak hal di sini.

Sebaliknya ketika Anda memanggil layanan jaringan, Anda memberikan input eksplisit. Biasanya ada tata bahasa (seperti di JSON atau XML) dan semua opsi untuk memanggil layanan tidak memiliki alasan untuk disembunyikan. Idenya adalah bahwa Anda dapat memanggil layanan seperti yang Anda inginkan dan format data bersifat publik dan dipublikasikan.

Dalam hal ini, atau banyak lainnya (seperti akses ke database) Anda benar-benar bekerja dengan data bersama. Karena itu tidak ada alasan untuk menyembunyikannya, sebaliknya Anda ingin membuatnya tersedia. Mungkin ada kekhawatiran akses baca / tulis atau konsistensi datacheck tetapi pada inti ini, konsep inti jika ini adalah publik.

Untuk persyaratan desain seperti itu di mana Anda ingin menghindari enkapsulasi dan membuat hal-hal publik dan jelas, Anda ingin menghindari objek. Yang Anda butuhkan adalah tuple, struct C atau yang setara, bukan objek.

Tetapi itu juga terjadi dalam bahasa seperti Java, satu-satunya hal yang dapat Anda modelkan adalah objek atau array objek. Objek sendiri dapat menampung beberapa jenis asli (int, float ...) tapi itu saja. Tapi objek juga bisa berperilaku seperti struct sederhana hanya dengan bidang publik dan itu semua.

Jadi, jika Anda memodelkan data, Anda bisa melakukannya hanya dengan bidang publik di dalam objek karena Anda tidak perlu lagi. Anda tidak menggunakan enkapsulasi karena Anda tidak membutuhkannya. Ini dilakukan dengan cara ini dalam banyak bahasa. Dalam java, secara historis, standar naik di mana dengan pengambil / penyetel Anda setidaknya bisa memiliki kontrol baca / tulis (dengan tidak menambahkan penyetel misalnya) dan bahwa perkakas dan kerangka kerja dengan menggunakan instrospeksi API akan mencari metode pengambil / penyetel dan menggunakannya untuk Isi otomatis konten atau tampilkan tesis sebagai bidang yang dapat dimodifikasi di antarmuka pengguna yang dibuat secara otomatis.

Ada juga argumen Anda bisa menambahkan beberapa logika / memeriksa metode setter.

Pada kenyataannya hampir tidak ada pembenaran untuk pengambil / penentu karena mereka paling sering digunakan untuk memodelkan data murni. Kerangka kerja dan pengembang menggunakan objek Anda berharap pengambil / penyetel tidak melakukan apa-apa selain mengatur / mendapatkan bidang. Anda secara efektif tidak melakukan lebih banyak dengan pengambil / penyetel daripada apa yang bisa dilakukan dengan bidang publik.

Tapi itu kebiasaan lama dan kebiasaan lama sulit dihilangkan ... Anda bahkan bisa diancam oleh kolega atau guru Anda jika Anda tidak menempatkan getter / setter secara membabi buta di mana-mana jika mereka tidak memiliki latar belakang untuk lebih memahami apa mereka dan apa mereka tidak.

Anda mungkin perlu mengubah bahasa untuk mendapatkan semua kode getter / setter boilerplate ini. (Seperti C # atau lisp). Bagi saya pengambil / penyetel hanyalah kesalahan satu miliar dolar ...


6
Properti C # tidak benar-benar mengimplementasikan enkapsulasi seperti halnya getter dan setter ...
IntelliData

1
Keuntungan dari [gs] etters adalah Anda dapat melakukan hal-hal yang biasanya tidak Anda lakukan (memeriksa nilai, memberi tahu pengamat), mensimulasikan bidang yang tidak ada dll, dan terutama bahwa Anda dapat mengubahnya nanti . Dengan Lombok , boilerplate hilang: @Getter @Setter class MutablePoint3D {private int x, y, z;}.
maaartinus

1
@maaartinus Tentu saja [gs] etter dapat melakukan apa saja seperti metode lainnya. Ini berarti bahwa setiap kode penelepon harus menyadari nilai apa pun yang mereka setel dapat menimbulkan pengecualian, diubah atau berpotensi mengirim pemberitahuan perubahan ... atau apa pun yang lainnya. Kurang lebih [gs] etter tidak menyediakan akses ke suatu bidang tetapi melakukan kode arbitrer.
Nicolas Bousquet

@IntelliData C # properties memungkinkan untuk tidak menulis 1 karakter tunggal boilerplate yang tidak berguna dan tidak mempedulikan semua hal pengambil / penyetel ini ... Ini sudah merupakan pencapaian yang lebih baik daripada proyek lombok. Juga bagi saya POJO dengan hanya getter / setter tidak ada di sini untuk menyediakan enkapsulasi melainkan untuk menerbitkan format data yang bebas untuk dibaca atau ditulis sebagai pertukaran dengan layanan. Enkapsulasi adalah kebalikan dari persyaratan desain.
Nicolas Bousquet

Saya tidak berpikir properti itu sangat bagus. Tentu, Anda menyimpan awalan dan tanda kurung, yaitu, 5 karakter per panggilan, tetapi 1. mereka terlihat seperti bidang, yang membingungkan. 2. mereka adalah hal tambahan yang Anda perlu dukungan dalam refleksi. Bukan masalah besar, tetapi juga tidak ada keuntungan besar (bila dibandingkan dengan Jawa + Lombok; Jawa murni jelas-jelas hilang).
maaartinus

0

Jadi misalnya, diberikan kelas Pelanggan yang terus banyak jika informasi tentang pelanggan, misalnya Nama, DOB, Telp, Alamat dll., Bagaimana seseorang menghindari pengambil / setter untuk mendapatkan dan mengatur semua atribut itu? Metode tipe 'Perilaku' apa yang dapat ditulis untuk mengisi semua data itu?

Saya pikir pertanyaan ini berduri karena Anda khawatir tentang metode perilaku untuk mengisi data tetapi saya tidak melihat indikasi perilaku apa yang Customerkelas objek dimaksudkan untuk merangkum.

Jangan bingung Customersebagai kelas objek dengan 'Pelanggan' sebagai pengguna / aktor yang melakukan tugas berbeda menggunakan perangkat lunak Anda.

Ketika Anda mengatakan diberi kelas Pelanggan yang membuat banyak informasi tentang pelanggan maka sejauh perilaku kelihatannya kelas Pelanggan Anda memiliki sedikit membedakannya dari batu. A Rockdapat memiliki warna, Anda bisa memberinya nama, Anda bisa memiliki bidang untuk menyimpan alamatnya saat ini tetapi kami tidak mengharapkan segala macam perilaku cerdas dari batu.

Dari artikel yang ditautkan tentang getter / setter menjadi jahat:

Proses desain OO berpusat pada use case: pengguna melakukan tugas mandiri yang memiliki hasil yang bermanfaat. (Log on bukan merupakan use case karena tidak memiliki hasil yang berguna dalam domain masalah. Menggambar gaji adalah use case.) Sistem OO, kemudian, mengimplementasikan kegiatan yang diperlukan untuk memainkan berbagai skenario yang terdiri dari use case.

Tanpa perilaku yang didefinisikan, merujuk pada batu sebagai Customertidak mengubah fakta bahwa itu hanya objek dengan beberapa properti yang ingin Anda lacak dan tidak masalah trik apa yang ingin Anda mainkan untuk menjauh dari getter dan setter. Batuan tidak peduli jika itu memiliki nama yang valid dan batu tidak akan diharapkan untuk mengetahui apakah alamat itu valid atau tidak.

Sistem pesanan Anda dapat mengaitkan Rockdengan pesanan pembelian dan selama itu Rockmemiliki alamat yang ditentukan maka beberapa bagian dari sistem dapat memastikan barang dikirim ke batu.

Dalam semua kasus ini, Rockini hanyalah Obyek Data dan akan terus menjadi objek sampai kita mendefinisikan perilaku spesifik dengan hasil yang bermanfaat alih-alih hipotesis.


Coba ini:

Ketika Anda menghindari membebani kata 'Pelanggan' dengan 2 arti yang berpotensi berbeda, itu akan membuat segalanya lebih mudah untuk dikonsep.

Apakah Rockobjek memesan atau apakah itu sesuatu yang dilakukan manusia dengan mengeklik elemen UI untuk memicu tindakan di sistem Anda?


Pelanggan adalah aktor yang melakukan banyak hal, tetapi juga memiliki banyak informasi yang terkait dengannya; apakah itu membenarkan membuat 2 kelas terpisah, 1 sebagai aktor dan 1 sebagai objek data?
IntelliData

@IntelliData lewat benda kaya melintasi lapisan adalah tempat segalanya menjadi rumit. Jika Anda mengirim objek dari controller ke tampilan, tampilan perlu memahami kontrak umum objek (standar JavaBeans misalnya). Jika Anda mengirim objek melalui kabel, JaxB atau sejenisnya harus dapat mengembalikannya ke objek bodoh (karena Anda tidak memberi mereka objek kaya penuh). Objek kaya sangat bagus untuk memanipulasi data - mereka miskin untuk mentransfer negara. Sebaliknya, objek bodoh buruk untuk memanipulasi data dan ok untuk mentransfer status.

0

Saya menambahkan 2 sen saya di sini menyebutkan pendekatan objek yang berbicara SQL .

Pendekatan ini didasarkan pada gagasan objek mandiri. Ia memiliki semua sumber daya yang diperlukan untuk menerapkan perilakunya. Tidak perlu diberitahu bagaimana melakukan tugasnya - permintaan deklaratif sudah cukup. Dan suatu objek pasti tidak harus memegang semua datanya sebagai properti kelas. Ini benar-benar tidak - dan tidak seharusnya - masalah dari mana mereka berasal.

Berbicara tentang agregat , ketidakberubahan juga tidak menjadi masalah. Katakanlah, Anda memiliki urutan status yang dapat disimpan oleh agregat: Agregat root sebagai saga Tidak apa-apa untuk menerapkan setiap status sebagai objek mandiri. Mungkin Anda bisa melangkah lebih jauh: ngobrol dengan pakar domain Anda. Kemungkinannya adalah bahwa dia tidak melihat agregat ini sebagai entitas yang disatukan. Mungkin setiap negara memiliki maknanya sendiri, layak mendapatkan objeknya sendiri.

Akhirnya, saya ingin mencatat bahwa proses pencarian objek sangat mirip dengan dekomposisi sistem menjadi subsistem . Keduanya didasarkan pada perilaku, bukan hal lain.

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.