it-swarm-id.com

Apakah ada alasan untuk menggunakan kelas "data lama biasa"?

Dalam kode lama saya kadang-kadang melihat kelas yang tidak lain adalah pembungkus untuk data. sesuatu seperti:

class Bottle {
   int height;
   int diameter;
   Cap capType;

   getters/setters, maybe a constructor
}

Pemahaman saya tentang OO adalah bahwa kelas adalah struktur untuk data dan metode operasi pada data itu. Ini tampaknya menghalangi objek dari jenis ini. Bagi saya mereka bukan apa-apa lebih dari structs dan semacam mengalahkan tujuan OO. Saya tidak berpikir itu pasti jahat, meskipun mungkin bau kode.

Apakah ada kasus di mana benda seperti itu diperlukan? Jika ini sering digunakan, apakah itu membuat desain curiga?

44
Michael K

Jelas bukan kejahatan dan bukan bau kode di pikiranku. Wadah data adalah warga negara yang valid OO. Terkadang Anda ingin merangkum informasi yang terkait bersama. Jauh lebih baik memiliki metode seperti

public void DoStuffWithBottle(Bottle b)
{
    // do something that doesn't modify Bottle, so the method doesn't belong
    // on that class
}

dari

public void DoStuffWithBottle(int bottleHeight, int bottleDiameter, Cap capType)
{
}

Menggunakan kelas juga memungkinkan Anda untuk menambahkan parameter tambahan ke Botol tanpa mengubah setiap pemanggil dari DoStuffWithBottle. Dan Anda dapat mensubklasifikasikan Botol dan selanjutnya meningkatkan keterbacaan dan pengaturan kode Anda, jika perlu.

Ada juga objek data biasa yang dapat dikembalikan sebagai hasil dari kueri basis data, misalnya. Saya percaya istilah untuk mereka dalam hal ini adalah "Objek Transfer Data".

Dalam beberapa bahasa ada pertimbangan lain juga. Misalnya, dalam C # kelas dan struct berperilaku berbeda, karena struct adalah tipe nilai dan kelas adalah tipe referensi.

68
Adam Lear

Kelas data valid dalam beberapa kasus. DTO adalah salah satu contoh bagus yang disebutkan oleh Anna Lear. Namun secara umum, Anda harus menganggap mereka sebagai benih kelas yang metodenya belum tumbuh. Dan jika Anda menemukan banyak dari mereka dalam kode lama, perlakukan mereka sebagai bau kode yang kuat. Mereka sering digunakan oleh programmer C/C++ lama yang tidak pernah membuat transisi ke OO pemrograman dan merupakan tanda pemrograman prosedural. Mengandalkan getter dan setter sepanjang waktu (atau lebih buruk lagi, pada akses langsung anggota non-pribadi) dapat membuat Anda mendapat masalah sebelum Anda menyadarinya. Pertimbangkan sebuah contoh metode eksternal yang membutuhkan informasi dari Bottle.

Di sini Bottle adalah kelas data):

void selectShippingContainer(Bottle bottle) {
    if (bottle.getDiameter() > MAX_DIMENSION || bottle.getHeight() > MAX_DIMENSION ||
            bottle.getCapType() == Cap.FANCY_CAP ) {
        shippingContainer = WOODEN_CRATE;
    } else {
        shippingContainer = CARDBOARD_BOX;
    }
}

Di sini kami telah memberikan Bottle beberapa perilaku):

void selectShippingContainer(Bottle bottle) {
    if (bottle.isBiggerThan(MAX_DIMENSION) || bottle.isFragile()) {
        shippingContainer = WOODEN_CRATE;
    } else {
        shippingContainer = CARDBOARD_BOX;
    }
}

Metode pertama melanggar prinsip Tell-Don't-Ask, dan dengan menjaga Bottle bisu kami telah membiarkan pengetahuan implisit tentang botol, seperti apa yang membuat satu rapuh (Cap), dimasukkan ke dalam logika yang berada di luar kelas Bottle. Anda harus waspada untuk mencegah 'kebocoran' semacam ini ketika Anda terbiasa mengandalkan getter.

Metode kedua meminta Bottle hanya untuk apa yang diperlukan untuk melakukan tugasnya, dan membiarkan Bottle untuk memutuskan apakah itu rapuh, atau lebih besar dari ukuran yang diberikan. Hasilnya adalah kopling yang jauh lebih longgar antara metode dan implementasi Botol. Efek samping yang menyenangkan adalah metode ini lebih bersih dan lebih ekspresif.

Anda jarang akan menggunakan banyak bidang objek ini tanpa menulis beberapa logika yang harus berada di kelas dengan bidang tersebut. Cari tahu apa logika itu dan kemudian pindahkan ke tempat yang seharusnya.

25
Mike E

Jika ini adalah jenis hal yang Anda butuhkan, tidak apa-apa, tapi tolong, tolong, tolong lakukan

public class Bottle {
    public int height;
    public int diameter;
    public Cap capType;

    public Bottle(int height, int diameter, Cap capType) {
        this.height = height;
        this.diameter = diameter;
        this.capType = capType;
    }
}

bukannya sesuatu seperti

public class Bottle {
    private int height;
    private int diameter;
    private Cap capType;

    public Bottle(int height, int diameter, Cap capType) {
        this.height = height;
        this.diameter = diameter;
        this.capType = capType;
    }

    public int getHeight() {
        return height;
    }

    public void setHeight(int height) {
        this.height = height;
    }

    public int getDiameter() {
        return diameter;
    }

    public void setDiameter(int diameter) {
        this.diameter = diameter;
    }

    public Cap getCapType() {
        return capType;
    }

    public void setCapType(Cap capType) {
        this.capType = capType;
    }
}

Silahkan.

7
compman

Seperti yang dikatakan @Anna, jelas tidak jahat. Tentu Anda dapat menempatkan operasi (metode) ke dalam kelas, tetapi hanya jika Anda ingin ke. Anda tidak memiliki ke.

Izinkan saya sedikit keluhan tentang gagasan bahwa Anda harus memasukkan operasi ke dalam kelas, bersama dengan gagasan bahwa kelas adalah abstraksi. Dalam praktiknya, ini mendorong programmer untuk

  1. Buat lebih banyak kelas dari yang mereka butuhkan (struktur data yang berlebihan). Ketika suatu struktur data mengandung lebih banyak komponen daripada yang diperlukan seminimal mungkin, itu tidak dinormalisasi, dan karena itu mengandung keadaan tidak konsisten. Dengan kata lain, ketika diubah, perlu diubah di lebih dari satu tempat agar tetap konsisten. Kegagalan untuk melakukan semua perubahan terkoordinasi membuatnya tidak konsisten, dan merupakan bug.

  2. Selesaikan masalah 1 dengan memasukkan pemberitahuan metode, sehingga jika bagian A dimodifikasi, ia mencoba untuk menyebarkan perubahan yang diperlukan ke bagian B dan C. Ini adalah alasan utama mengapa disarankan untuk mendapatkan-dan -set metode accessor. Karena ini adalah praktik yang disarankan, tampaknya memaafkan masalah 1, menyebabkan lebih banyak masalah 1 dan lebih banyak solusi 2. Hasil ini tidak hanya pada bug karena mengimplementasikan notifikasi yang tidak lengkap, tetapi juga pada masalah yang melemahkan kinerja notifikasi runaway. Ini bukan perhitungan yang tak terbatas, hanya yang sangat panjang.

Konsep-konsep ini diajarkan sebagai hal yang baik, umumnya oleh guru yang tidak harus bekerja dalam jutaan aplikasi monster yang penuh dengan masalah ini.

Inilah yang saya coba lakukan:

  1. Pertahankan data dinormalisasi mungkin, sehingga ketika perubahan dilakukan pada data, hal itu dilakukan pada titik kode sesedikit mungkin, untuk meminimalkan kemungkinan memasuki keadaan yang tidak konsisten.

  2. Ketika data harus dinormalisasi, dan redundansi tidak dapat dihindarkan, jangan gunakan pemberitahuan dalam upaya untuk membuatnya konsisten. Sebaliknya, toleransilah terhadap inkonsistensi sementara. Atasi ketidakkonsistenan dengan sapuan berkala melalui data dengan proses yang hanya melakukan itu. Ini memusatkan tanggung jawab untuk menjaga konsistensi, sambil menghindari masalah kinerja dan kebenaran yang rentan terhadap pemberitahuan. Ini menghasilkan kode yang jauh lebih kecil, bebas kesalahan, dan efisien.

6
Mike Dunlavey

Dengan desain game, overhead 1000 fungsi panggilan, dan pendengar acara kadang-kadang membuatnya layak untuk memiliki kelas yang hanya menyimpan data, dan memiliki kelas lain yang mengulang semua data hanya kelas untuk melakukan logika.

3
AttackingHobo

Setuju dengan Anna Lear,

Jelas bukan kejahatan dan bukan bau kode di pikiranku. Wadah data adalah warga negara yang valid OO. Terkadang Anda ingin merangkum informasi terkait secara bersama. Jauh lebih baik memiliki metode seperti ...

Terkadang orang lupa membaca 1999 Java Konvensi Pengkodean yang membuatnya sangat jelas bahwa pemrograman semacam ini baik-baik saja. Bahkan jika Anda menghindarinya, maka kode Anda akan tercium!) (Terlalu banyak pengambil/setter)

Dari Java Konvensi Kode 1999: Salah satu contoh variabel instance publik yang sesuai adalah kasus di mana kelas pada dasarnya adalah struktur data, tanpa perilaku. Dengan kata lain , jika Anda akan menggunakan struct alih-alih kelas (jika Java struct yang didukung), maka pantas untuk membuat variabel instance kelas menjadi publik. - http://www.Oracle.com/technetwork/Java/javase/documentation/codeconventions-137265.html#177

Ketika digunakan dengan benar, POD (struktur data lama biasa) lebih baik daripada POJO sama seperti POJO sering lebih baik daripada EJB.
http://en.wikipedia.org/wiki/Plain_Old_Data_Structures

3
Richard Faber

Kelas semacam ini sangat berguna ketika Anda berurusan dengan aplikasi ukuran menengah/besar, untuk beberapa alasan:

  • cukup mudah untuk membuat beberapa test case dan memastikan bahwa datanya konsisten.
  • itu memegang semua jenis perilaku yang melibatkan informasi itu, sehingga waktu pelacakan bug data berkurang
  • Menggunakannya harus menjaga metode args tetap ringan.
  • Saat menggunakan ORM, kelas ini memberikan fleksibilitas dan konsistensi. Menambahkan atribut kompleks yang dihitung berdasarkan informasi sederhana yang sudah ada di kelas, resutl secara tertulis satu metode sederhana. Ini cukup lincah dan produktif sehingga harus memeriksa database Anda dan memastikan semua basis data ditambal dengan modifikasi baru.

Singkatnya, menurut pengalaman saya, mereka biasanya lebih bermanfaat daripada menyebalkan.

3
guiman

Bangunan memiliki tempat mereka, bahkan di Jawa. Anda hanya boleh menggunakannya jika dua hal berikut ini benar:

  • Anda hanya perlu menggabungkan data yang tidak memiliki perilaku, mis. untuk lulus sebagai parameter
  • Tidak masalah sedikit pun seperti apa nilai yang dimiliki data agregat

Jika ini masalahnya, maka Anda harus membuat bidang publik dan melewati getter/setter. Lagipula Getters dan setters kikuk, dan Java konyol untuk tidak memiliki properti seperti bahasa yang berguna. Karena objek struct-like Anda seharusnya tidak memiliki metode apa pun, bidang publik paling masuk akal.

Namun, jika salah satu dari itu tidak berlaku, Anda sedang berhadapan dengan kelas nyata. Itu berarti semua bidang harus pribadi. (Jika Anda benar-benar membutuhkan bidang pada cakupan yang lebih mudah diakses, gunakan pengambil/penyetel.)

Untuk memeriksa apakah seharusnya struktur Anda memiliki perilaku, lihatlah ketika bidang digunakan. Jika tampaknya melanggar katakan, jangan tanya , maka Anda perlu memindahkan perilaku itu ke kelas Anda.

Jika beberapa data Anda tidak boleh berubah, maka Anda harus membuat semua bidang itu final. Anda mungkin mempertimbangkan membuat kelas Anda tidak berubah . Jika Anda perlu memvalidasi data Anda, maka berikan validasi di setter dan konstruktor. (Trik yang berguna adalah mendefinisikan setter pribadi dan memodifikasi bidang Anda di dalam kelas Anda hanya menggunakan setter itu.)

Contoh Botol Anda kemungkinan besar akan gagal dalam kedua pengujian. Anda dapat memiliki (buat) kode yang terlihat seperti ini:

public double calculateVolumeAsCylinder(Bottle bottle) {
    return bottle.height * (bottle.diameter / 2.0) * Math.PI);
}

Sebaliknya seharusnya

double volume = bottle.calculateVolumeAsCylinder();

Jika Anda mengubah tinggi dan diameter, apakah itu botol yang sama? Mungkin tidak. Itu harus final. Apakah nilai negatif ok untuk diameter? Haruskah Botol Anda lebih tinggi dari lebar? Bisakah Cap menjadi nol? Tidak? Bagaimana Anda memvalidasi ini? Anggaplah klien itu bodoh atau jahat. ( Tidak mungkin membedakannya. ) Anda perlu memeriksa nilai-nilai ini.

Seperti inilah bentuk kelas Botol Anda yang baru:

public class Bottle {

    private final int height, diameter;

    private Cap capType;

    public Bottle(final int height, final int diameter, final Cap capType) {
        if (diameter < 1) throw new IllegalArgumentException("diameter must be positive");
        if (height < diameter) throw new IllegalArgumentException("bottle must be taller than its diameter");

        setCapType(capType);
        this.height = height;
        this.diameter = diameter;
    }

    public double getVolumeAsCylinder() {
        return height * (diameter / 2.0) * Math.PI;
    }

    public void setCapType(final Cap capType) {
        if (capType == null) throw new NullPointerException("capType cannot be null");
        this.capType = capType;
    }

    // potentially more methods...

}
3
Eva

IMHO sering tidak ada cukup kelas-kelas seperti ini dalam sistem berorientasi objek. Saya perlu hati-hati untuk memenuhi syarat itu.

Tentu saja jika bidang data memiliki cakupan dan jarak pandang yang luas, itu bisa sangat tidak diinginkan jika ada ratusan atau ribuan tempat di basis kode Anda yang merusak data tersebut. Itu meminta masalah dan kesulitan mempertahankan invarian. Namun pada saat yang sama, itu tidak berarti bahwa setiap kelas di seluruh basis kode mendapat manfaat dari penyembunyian informasi.

Tetapi ada banyak kasus di mana bidang data seperti itu akan memiliki cakupan yang sangat sempit. Contoh yang sangat mudah adalah kelas privat Node dari struktur data. Sering dapat menyederhanakan kode banyak dengan mengurangi jumlah interaksi objek yang terjadi jika kata Node bisa hanya terdiri dari data mentah. Itu berfungsi sebagai mekanisme decoupling karena versi alternatif mungkin memerlukan kopling dua arah dari, katakanlah, Tree->Node dan Node->Tree bukan hanya Tree->Node Data.

Contoh yang lebih kompleks adalah sistem entitas-komponen seperti yang sering digunakan dalam mesin game. Dalam kasus tersebut, komponen seringkali hanya berupa data mentah dan kelas seperti yang Anda tunjukkan. Namun, ruang lingkup/visibilitas mereka cenderung terbatas karena biasanya hanya ada satu atau dua sistem yang dapat mengakses jenis komponen tertentu. Akibatnya, Anda masih cenderung merasa mudah mempertahankan invarian dalam sistem tersebut dan, apalagi, sistem seperti itu memiliki sangat sedikit object->object interaksi, membuatnya sangat mudah untuk memahami apa yang terjadi pada level mata burung.

Dalam kasus seperti itu, Anda dapat berakhir dengan sesuatu yang lebih seperti ini sejauh interaksi berjalan (diagram ini menunjukkan interaksi, bukan kopling, karena diagram kopling mungkin menyertakan antarmuka abstrak untuk gambar kedua di bawah):

enter image description here

... berbeda dengan ini:

enter image description here

... dan tipe sistem yang sebelumnya cenderung lebih mudah dipelihara dan beralasan dalam hal kebenaran terlepas dari kenyataan bahwa dependensi sebenarnya mengalir ke data. Anda mendapatkan lebih sedikit kopling terutama karena banyak hal dapat diubah menjadi data mentah, bukan objek yang berinteraksi satu sama lain membentuk grafik interaksi yang sangat kompleks.

0
user204677