Haruskah kita menghindari objek khusus sebagai parameter?

49

Misalkan saya memiliki objek khusus, Siswa :

public class Student{
    public int _id;
    public String name;
    public int age;
    public float score;
}

Dan kelas, Window , yang digunakan untuk menampilkan informasi seorang Siswa :

public class Window{
    public void showInfo(Student student);
}

Kelihatannya cukup normal, tetapi saya mendapati Window tidak cukup mudah untuk diuji secara individual, karena dibutuhkan objek Student yang sebenarnya untuk memanggil fungsi tersebut. Jadi saya mencoba untuk memodifikasi showInfo sehingga tidak menerima objek Siswa secara langsung:

public void showInfo(int _id, String name, int age, float score);

sehingga lebih mudah untuk menguji Window secara individual:

showInfo(123, "abc", 45, 6.7);

Tetapi saya menemukan versi yang dimodifikasi memiliki masalah lain:

  1. Ubah Siswa (misalnya: tambahkan properti baru) memerlukan modifikasi metode-tanda tangan showInfo

  2. Jika Siswa memiliki banyak properti, metode-tanda tangan Siswa akan sangat panjang.

Jadi, menggunakan objek khusus sebagai parameter atau menerima setiap properti dalam objek sebagai parameter, mana yang lebih bisa dipertahankan?

ggrr
sumber
40
Dan 'peningkatan' Anda showInfomembutuhkan String nyata, float nyata dan dua int nyata. Bagaimana menyediakan Stringobjek nyata lebih baik daripada menyediakan Studentobjek nyata ?
Bart van Ingen Schenau
28
Satu masalah besar dengan mengirimkan parameter secara langsung: Anda sekarang memiliki dua intparameter. Dari situs panggilan, tidak ada verifikasi bahwa Anda benar-benar melewati mereka dalam urutan yang benar. Bagaimana jika Anda bertukar iddan age, atau firstNamedan lastName? Anda memperkenalkan titik kegagalan potensial yang bisa sangat sulit dideteksi hingga meledak di wajah Anda, dan Anda menambahkannya di setiap situs panggilan .
Chris Hayes
38
@ ChrisHayes ah, showForm(bool, bool, bool, bool, int)metode lama - Saya suka itu ...
Boris the Spider
3
@ ChrisHayes setidaknya bukan JS ...
Jens Schauder
2
properti tes yang kurang dihargai: jika sulit untuk membuat / menggunakan objek Anda sendiri dalam pengujian, API Anda mungkin dapat menggunakan beberapa pekerjaan :)
Eevee

Jawaban:

131

Menggunakan objek khusus untuk mengelompokkan parameter terkait sebenarnya merupakan pola yang disarankan. Sebagai refactoring, itu disebut Pengantar Parameter Objek .

Masalahmu ada di tempat lain. Pertama, generik Windowseharusnya tidak tahu apa-apa tentang Siswa. Sebagai gantinya, Anda harus memiliki beberapa jenis StudentWindowyang tahu tentang hanya menampilkan Students. Kedua, sama sekali tidak ada masalah tentang membuat Studentinstance untuk diuji StudentWindowselama Studenttidak mengandung logika kompleks yang secara drastis akan mempersulit pengujian StudentWindow. Jika memang memiliki logika itu maka membuat Studentantarmuka dan mengejek itu harus lebih disukai.

Euforia
sumber
14
Layak peringatan bahwa Anda bisa mendapatkan masalah jika objek baru itu bukan pengelompokan yang logis. Jangan mencoba menyemir setiap parameter yang ditetapkan menjadi objek tunggal; memutuskan berdasarkan kasus per kasus. Contoh dalam pertanyaan itu tampaknya cukup jelas untuk menjadi kandidat yang baik untuk itu. The Studentpengelompokan akal dan kemungkinan akan muncul di daerah lain dari aplikasi.
jpmc26
Berbicara dengan pedih jika Anda sudah memiliki objek, mis. Student, Itu akan menjadi Preserve Whole Object
abuzittin gillifirca
4
Juga ingat Hukum Demeter . Ada keseimbangan yang harus dicapai tetapi tldr tidak dilakukan a.b.cjika metode Anda mengambil a. Jika metode Anda sampai pada titik di mana Anda perlu memiliki lebih dari 4 parameter atau 2 level dalam aksesi properti, mungkin perlu diperhitungkan. Perhatikan juga bahwa ini adalah pedoman - seperti semua pedoman lainnya, pedoman ini memerlukan kebijaksanaan pengguna. Jangan mengikutinya secara membabi buta.
Dan Pantry
7
Saya menemukan kalimat pertama dari jawaban ini sangat sulit diurai.
helrich
5
@ Tanya saya, saya akan sangat tidak setuju. Siswa BENAR-BENAR terdengar seperti daun dalam grafik objek (kecuali objek sepele lainnya seperti mungkin Nama, DateOfBirth dll), hanya sebuah wadah untuk keadaan siswa. Tidak ada alasan apapun bahwa seorang siswa harus sulit dibangun, karena itu harus menjadi tipe catatan. Membuat tes ganda untuk siswa terdengar seperti resep untuk ujian yang sulit dipertahankan dan / atau ketergantungan yang berat pada beberapa kerangka isolasi mewah.
sara
26

Anda mengatakan itu

tidak cukup mudah untuk menguji secara individual, karena membutuhkan objek Siswa nyata untuk memanggil fungsi

Tetapi Anda bisa membuat objek siswa untuk diteruskan ke jendela Anda:

showInfo(new Student(123,"abc",45,6.7));

Tampaknya tidak terlalu rumit untuk dihubungi.

Tom.Bowen89
sumber
7
Masalahnya muncul ketika Studentmerujuk ke a University, yang mengacu pada banyak Facultys dan Campuss, dengan Professors dan Buildings, tidak ada yang showInfobenar-benar menggunakan, tetapi Anda belum mendefinisikan antarmuka apa pun yang memungkinkan tes untuk "mengetahui" itu dan hanya memasok siswa yang relevan data, tanpa membangun seluruh organisasi. Contohnya Studentadalah objek data biasa dan, seperti yang Anda katakan, pengujian harus dilakukan dengan senang hati.
Steve Jessop
4
Masalahnya muncul ketika Mahasiswa merujuk ke Universitas, yang merujuk pada banyak Fakultas dan Kampus, dengan Profesor dan Bangunan, Tidak ada istirahat bagi orang jahat.
abuzittin gillifirca
1
@abuzittingillifirca, "Object Mother" adalah salah satu solusi, juga objek siswa Anda mungkin terlalu kompleks. Mungkin lebih baik hanya memiliki UniversityId, dan layanan (menggunakan injeksi dependensi) yang akan memberikan objek Universitas dari UniversityId.
Ian
12
Jika Siswa sangat kompleks atau sulit untuk menginisialisasi cukup mengejeknya. Pengujian jauh lebih kuat dengan kerangka kerja seperti Mockito atau padanan bahasa lainnya.
Borjab
4
Jika showInfo tidak peduli tentang Universitas, maka sederhananya setel ke nol. Nulls mengerikan dalam produksi dan tes pengiriman dewa. Mampu menetapkan parameter sebagai nol dalam tes mengkomunikasikan maksud dan mengatakan bahwa "hal ini tidak diperlukan di sini". Meskipun saya juga akan mempertimbangkan membuat semacam model tampilan untuk siswa yang hanya berisi data yang relevan, mengingat showInfo terdengar seperti metode di kelas UI.
sara
22

Dalam istilah awam:

  • Apa yang Anda sebut "objek khusus" biasanya hanya disebut objek.
  • Anda tidak dapat menghindari melewatkan objek sebagai parameter saat merancang program atau API non-sepele, atau menggunakan API atau pustaka non-sepele.
  • Sangat oke untuk meneruskan objek sebagai parameter. Lihatlah Java API dan Anda akan melihat banyak antarmuka yang menerima objek sebagai parameter.
  • Kelas-kelas di perpustakaan yang Anda gunakan di mana ditulis oleh manusia biasa seperti Anda dan saya, jadi yang kami tulis bukan "kebiasaan" , hanya saja.

Sunting:

Sebagai @ Tom.Bowen89 menyatakan tidak jauh lebih kompleks untuk menguji metode showinfo:

showInfo(new Student(8812372,"Peter Parker",16,8.9));
Tulains Córdova
sumber
3
  1. Dalam contoh siswa Anda, saya menganggap itu sepele untuk memanggil konstruktor Siswa untuk membuat siswa lulus untuk menunjukkan informasi. Jadi tidak ada masalah.
  2. Dengan asumsi contoh Siswa sengaja diremehkan untuk pertanyaan ini dan lebih sulit untuk membangun, maka Anda dapat menggunakan tes ganda . Ada sejumlah opsi untuk tes ganda, cemoohan, stub, dll. Yang dibahas dalam artikel Martin Fowler untuk dipilih.
  3. Jika Anda ingin menjadikan fungsi showInfo lebih umum, Anda bisa menggunakannya lebih dari variabel publik, atau mungkin pengakses publik objek yang disahkan dan melakukan logika pertunjukan untuk semuanya. Kemudian Anda bisa melewati objek apa pun yang sesuai dengan kontrak itu dan itu akan berfungsi seperti yang diharapkan. Ini akan menjadi tempat yang baik untuk menggunakan antarmuka. Misalnya meneruskan objek Showable atau ShowInfoable ke fungsi showInfo yang tidak hanya menampilkan info siswa tetapi info objek apa pun yang mengimplementasikan antarmuka (tentu saja antarmuka tersebut memerlukan nama yang lebih baik tergantung pada seberapa spesifik atau generik Anda menginginkan objek yang dapat Anda lewati) menjadi dan apa Siswa adalah sub kelas dari).
  4. Seringkali lebih mudah untuk melewati primitif, dan kadang - kadang diperlukan untuk kinerja, tetapi semakin Anda dapat mengelompokkan konsep yang sama bersama-sama, semakin mudah dipahami kode Anda pada umumnya. Satu-satunya hal yang harus diperhatikan adalah mencoba untuk tidak melakukannya dan berakhir dengan fizzbuzz perusahaan .
Encaitar
sumber
3

Steve McConnell dalam Code Complete membahas masalah ini, mendiskusikan manfaat dan kelemahan dari melewatkan objek ke dalam metode alih-alih menggunakan properti.

Maafkan saya jika saya salah memahami detailnya, saya bekerja karena ingatan sudah lebih dari setahun sejak saya memiliki akses ke buku:

Dia sampai pada kesimpulan bahwa Anda lebih baik tidak menggunakan objek, alih-alih hanya mengirim properti yang benar-benar diperlukan untuk metode ini. Metode ini seharusnya tidak perlu tahu apa pun tentang objek di luar properti yang akan digunakan sebagai bagian dari operasinya. Selain itu, seiring waktu, jika objek berubah, ini bisa memiliki konsekuensi yang tidak diinginkan pada metode menggunakan objek.

Dia juga mengatakan bahwa jika Anda berakhir dengan metode yang menerima banyak argumen yang berbeda, maka itu mungkin merupakan pertanda bahwa metode tersebut melakukan terlalu banyak dan harus dipecah menjadi lebih banyak, metode yang lebih kecil.

Namun, terkadang, kadang-kadang, Anda benar-benar membutuhkan banyak parameter. Contoh yang dia berikan akan berupa metode yang membangun alamat lengkap, menggunakan banyak properti alamat yang berbeda (meskipun ini bisa didapat dengan menggunakan array string ketika Anda memikirkannya).

pengguna1666620
sumber
7
Saya memiliki Kode Lengkap 2. Ada satu halaman di dalamnya yang didedikasikan untuk masalah ini. Kesimpulannya adalah bahwa parameter harus berada pada level abstraksi yang benar. Kadang-kadang itu membutuhkan melewati seluruh objek, kadang-kadang hanya atribut individual.
DATANG DARI
UV. Kode Referensi Lengkap Lengkap dua kali lipat lebih baik. Pertimbangan yang bagus untuk desain dibandingkan pengujian kemanfaatan. Lebih suka desain, saya pikir McConnell akan mengatakan dalam konteks kita. Jadi kesimpulan yang bagus adalah "mengintegrasikan objek parameter ke dalam desain" ( Studentdalam hal ini). Dan inilah cara pengujian menginformasikan desain , sepenuhnya merangkul jawaban terbanyak suara sambil mempertahankan integritas desain.
radarbob
2

Jauh lebih mudah untuk menulis dan membaca tes jika Anda lulus seluruh objek:

public class AStudentView {
    @Test 
    public void displays_failing_grade_warning_when_a_student_with_a_failing_grade_is_shown() {
        StudentView view = aStudentView();
        view.show(aStudent().withAFailingGrade().build());
        Assert.that(view, displaysFailingGradeWarning());
    }

    private Matcher<StudentView> displaysFailingGradeWarning() {
        ...
    }
}

Untuk perbandingan,

view.show(aStudent().withAFailingGrade().build());

baris dapat ditulis, jika Anda memberikan nilai secara terpisah, seperti:

showAStudentWithAFailingGrade(view);

dimana panggilan metode aktual dikuburkan di suatu tempat seperti

private showAStudentWithAFailingGrade(StudentView view) {
    int someId = .....
    String someName = .....
    int someAge = .....
    // why have been I peeking and poking values I don't care about
    decimal aFailingGrade = .....
    view.show(someId, someName, someAge, aFailingGrade);
}

Untuk menjadi to the point, bahwa Anda tidak dapat memasukkan panggilan metode yang sebenarnya dalam tes adalah tanda bahwa API Anda buruk.

abuzittin gillifirca
sumber
1

Anda harus melewati apa yang masuk akal, beberapa ide:

Lebih mudah untuk diuji. Jika objek perlu diedit, apa yang paling membutuhkan refactoring? Apakah menggunakan kembali fungsi ini untuk tujuan lain bermanfaat? Berapa jumlah paling sedikit informasi yang saya butuhkan untuk memberikan fungsi ini untuk melakukan tujuannya? (Dengan memecahnya - itu memungkinkan Anda menggunakan kembali kode ini - waspada jatuh ke lubang desain pembuatan fungsi ini dan kemudian bottlenecking semuanya untuk secara eksklusif menggunakan objek ini.)

Semua aturan pemrograman ini hanyalah panduan untuk membuat Anda berpikir ke arah yang benar. Hanya saja, jangan membangun binatang kode - jika Anda tidak yakin dan hanya perlu melanjutkan, pilih arah / Anda sendiri atau saran di sini, dan jika Anda mencapai titik di mana Anda berpikir 'oh, saya harus membuat ini cara '- Anda mungkin dapat kembali dan refactor dengan mudah. (Misalnya jika Anda memiliki kelas Guru - hanya perlu properti yang sama ditetapkan sebagai Siswa, dan Anda mengubah fungsi Anda untuk menerima objek apa pun dari formulir Orang)

Saya akan paling cenderung untuk menjaga objek utama yang lewat - karena bagaimana saya kode itu akan lebih mudah menjelaskan apa fungsi ini lakukan.

Lilly
sumber
1

Salah satu rute yang umum di sekitar ini adalah memasukkan antarmuka antara dua proses.

public class Student {

    public int id;
    public String name;
    public int age;
    public float score;
}

interface HasInfo {
    public String getInfo();
}

public class StudentInfo implements HasInfo {
    final Student student;

    public StudentInfo(Student student) {
        this.student = student;
    }

    @Override
    public String getInfo() {
        return student.name;
    }

}

public class Window {

    public void showInfo(HasInfo info) {

    }
}

Terkadang hal ini sedikit berantakan tetapi beberapa hal menjadi sedikit lebih rapi di Jawa jika Anda menggunakan kelas dalam.

interface HasInfo {
    public String getInfo();
}

public class Student {

    public int id;
    public String name;
    public int age;
    public float score;

    public HasInfo getInfo() {
        return new HasInfo () {
            @Override
            public String getInfo() {
                return name;
            }

        };
    }
}

Anda kemudian dapat menguji Windowkelas dengan hanya memberikannya HasInfoobjek palsu .

Saya menduga ini adalah contoh Pola Penghias .

Ditambahkan

Tampaknya ada beberapa kebingungan yang disebabkan oleh kesederhanaan kode. Berikut ini contoh lain yang dapat menunjukkan teknik yang lebih baik.

interface Drawable {

    public void Draw(Pane pane);
}

/**
 * Student knows nothing about Window or Drawable.
 */
public class Student {

    public int id;
    public String name;
    public int age;
    public float score;
}

/**
 * DrawsStudents knows about both Students and Drawable (but not Window)
 */
public class DrawsStudents implements Drawable {

    private final Student subject;

    public DrawsStudents(Student subject) {
        this.subject = subject;
    }

    @Override
    public void Draw(Pane pane) {
        // Draw a Student on a Pane
    }

}

/**
 * Window only knows about Drawables.
 */
public class Window {

    public void showInfo(Drawable info) {

    }
}
OldCurmudgeon
sumber
Jika showInfo hanya ingin menampilkan nama siswa, mengapa tidak memberikan nama saja? membungkus bidang bernama semantik bermakna dalam antarmuka abstrak yang berisi string tanpa petunjuk tentang apa yang direpresentasikan string terasa seperti downgrade BESAR, baik dalam hal rawatan dan pemahaman.
sara
@ Kai - Penggunaan Studentdan di Stringsini untuk tipe pengembalian murni untuk demonstrasi. Kemungkinan akan ada parameter tambahan untuk getInfoseperti Paneuntuk menggambar jika menggambar. Konsep di sini adalah untuk melewatkan komponen fungsional sebagai dekorator dari objek asli .
OldCurmudgeon
dalam hal ini Anda akan menggabungkan entitas siswa dengan erat ke kerangka UI Anda, yang kedengarannya lebih buruk ...
sara
1
@ Kai - Justru sebaliknya. UI saya hanya tahu tentang HasInfoobjek. Studenttahu bagaimana menjadi satu.
OldCurmudgeon
Jika Anda getInfomengembalikan batal lulus Paneuntuk menggambar, maka implementasi (di Studentkelas) tiba-tiba digabungkan ke ayunan atau apa pun yang Anda gunakan. Jika Anda membuatnya mengembalikan beberapa string dan mengambil 0 parameter, maka UI Anda tidak akan tahu apa yang harus dilakukan dengan string tanpa asumsi ajaib dan kopling implisit. Jika Anda getInfobenar - benar mengembalikan beberapa model tampilan dengan properti yang relevan, maka Studentkelas Anda digabungkan lagi dengan logika presentasi. Saya tidak berpikir salah satu dari alternatif ini diinginkan
sara
1

Anda sudah memiliki banyak jawaban bagus, tetapi berikut adalah beberapa saran yang memungkinkan Anda melihat solusi alternatif:

  • Contoh Anda menunjukkan Siswa (jelas objek model) diteruskan ke Window (tampaknya objek tingkat tampilan). Pengontrol perantara atau objek Presenter mungkin bermanfaat jika Anda belum memilikinya, memungkinkan Anda mengisolasi antarmuka pengguna dari model Anda. Pengontrol / penyaji harus menyediakan antarmuka yang dapat digunakan untuk menggantinya untuk pengujian UI, dan harus menggunakan antarmuka untuk merujuk ke objek model dan melihat objek agar dapat mengisolasinya dari keduanya untuk pengujian. Anda mungkin perlu memberikan beberapa cara abstrak untuk membuat atau memuat ini (misalnya objek Pabrik, objek Repositori, atau yang serupa).

  • Mentransfer bagian yang relevan dari objek model Anda ke Obyek Transfer Data adalah pendekatan yang berguna untuk berinteraksi ketika model Anda menjadi terlalu kompleks.

  • Mungkin Siswa Anda melanggar Prinsip Segregasi Antarmuka. Jika demikian, bisa bermanfaat untuk membaginya menjadi beberapa antarmuka yang lebih mudah untuk dikerjakan.

  • Pemuatan Malas dapat membuat konstruksi grafik objek besar lebih mudah.

Jules
sumber
0

Ini sebenarnya pertanyaan yang layak. Masalah sebenarnya di sini adalah penggunaan istilah generik "objek", yang bisa sedikit ambigu.

Secara umum, dalam bahasa OOP klasik, istilah "objek" telah berarti "contoh kelas". Contoh kelas bisa sangat berat - properti publik dan pribadi (dan yang di antaranya), metode, pewarisan, dependensi, dll. Anda tidak akan benar-benar ingin menggunakan sesuatu seperti itu untuk sekadar memasukkan beberapa properti.

Dalam hal ini, Anda menggunakan objek sebagai wadah yang hanya menyimpan beberapa primitif. Dalam C ++, objek seperti ini dikenal sebagai structs(dan mereka masih ada dalam bahasa seperti C #). Structs, pada kenyataannya, dirancang persis untuk penggunaan yang Anda bicarakan - mereka mengelompokkan objek terkait dan primitif bersama ketika mereka memiliki hubungan logis.

Namun, dalam bahasa modern, benar-benar tidak ada perbedaan antara struct dan kelas ketika Anda menulis kode , jadi Anda boleh menggunakan objek. (Namun di belakang layar, ada beberapa perbedaan yang harus Anda perhatikan - misalnya, struct adalah tipe nilai, bukan tipe referensi.) Pada dasarnya, selama Anda menjaga objek Anda sederhana, itu akan mudah untuk menguji secara manual. Bahasa dan alat-alat modern memungkinkan Anda untuk mengurangi ini sedikit, (melalui antarmuka, kerangka kerja mengejek, injeksi ketergantungan, dll.)

makan siangme317
sumber
1
Melewati referensi tidak mahal, bahkan jika objek itu satu miliar terabyte besar, karena referensi masih hanya seukuran int di sebagian besar bahasa. Anda harus lebih khawatir tentang apakah metode penerimaan terkena api terlalu besar dan jika Anda memasangkan sesuatu dengan cara yang tidak diinginkan. Saya akan mempertimbangkan membuat lapisan pemetaan yang menerjemahkan objek bisnis ( Student) ke dalam model tampilan ( StudentInfoatau StudentInfoViewModeldll.), Tetapi mungkin tidak perlu.
sara
Kelas dan struktur sangat berbeda. Satu dilewatkan dengan nilai (berarti metode yang menerimanya mendapat salinan ) dan yang lainnya dilewatkan dengan referensi (penerima hanya mendapat pointer ke aslinya). Tidak memahami perbedaan ini berbahaya.
RubberDuck
@ Kai Saya mengerti bahwa memberikan referensi tidak mahal. Apa yang saya katakan adalah bahwa membuat fungsi yang membutuhkan instance kelas penuh mungkin lebih sulit untuk diuji tergantung pada dependensi kelas itu, metode-metodenya, dll - karena Anda harus mengejek kelas itu entah bagaimana.
lunchmeat317
Saya pribadi menentang mengejek apa saja kecuali kelas batas yang mengakses sistem eksternal (file / jaringan IO) atau yang tidak deterministik (misalnya acak, berbasis waktu sistem, dll). Jika kelas yang saya uji memiliki dependensi yang tidak relevan dengan fitur saat ini yang diuji, saya lebih suka melewati nol jika memungkinkan. Tetapi jika Anda menguji metode yang mengambil objek parameter, jika objek itu memiliki banyak dependensi saya akan khawatir tentang desain keseluruhan. Benda-benda seperti itu harus ringan.
Sara