Apakah saya menderita terlalu sering menggunakan enkapsulasi?

11

Saya telah memperhatikan sesuatu dalam kode saya di berbagai proyek yang sepertinya bau kode bagi saya dan sesuatu yang buruk untuk dilakukan, tetapi saya tidak bisa menghadapinya.

Saat mencoba menulis "kode bersih" Saya cenderung menggunakan metode pribadi untuk membuat kode saya lebih mudah dibaca. Masalahnya adalah bahwa kode tersebut memang lebih bersih tetapi juga lebih sulit untuk diuji (ya saya tahu saya bisa menguji metode pribadi ...) dan secara umum sepertinya kebiasaan buruk bagi saya.

Berikut adalah contoh kelas yang membaca beberapa data dari file .csv dan mengembalikan sekelompok pelanggan (objek lain dengan berbagai bidang dan atribut).

public class GroupOfCustomersImporter {
    //... Call fields ....
    public GroupOfCustomersImporter(String filePath) {
        this.filePath = filePath;
        customers = new HashSet<Customer>();
        createCSVReader();
        read();
        constructTTRP_Instance();
    }

    private void createCSVReader() {
        //....
    }

    private void read() {
        //.... Reades the file and initializes the class attributes
    }

    private void readFirstLine(String[] inputLine) {
        //.... Method used by the read() method
    }

    private void readSecondLine(String[] inputLine) {
        //.... Method used by the read() method
    }

    private void readCustomerLine(String[] inputLine) { 
        //.... Method used by the read() method
    }

    private void constructGroupOfCustomers() {
        //this.groupOfCustomers = new GroupOfCustomers(**attributes of the class**);
    }

    public GroupOfCustomers getConstructedGroupOfCustomers() {
        return this.GroupOfCustomers;
    }
}

Seperti yang Anda lihat kelas hanya memiliki konstruktor yang memanggil beberapa metode pribadi untuk menyelesaikan pekerjaan, saya tahu itu bukan praktik yang baik secara umum, tetapi saya lebih memilih untuk merangkum semua fungsionalitas di kelas daripada membuat metode publik dalam hal ini klien harus bekerja dengan cara ini:

GroupOfCustomersImporter importer = new GroupOfCustomersImporter(filepath)
importer.createCSVReader();
read();
GroupOfCustomer group = constructGoupOfCustomerInstance();

Saya lebih suka ini karena saya tidak ingin meletakkan baris kode yang tidak berguna dalam kode sisi klien yang mengganggu kelas klien dengan detail implementasi.

Jadi, apakah ini sebenarnya kebiasaan buruk? Jika ya, bagaimana saya bisa menghindarinya? Harap dicatat bahwa contoh di atas hanyalah contoh sederhana. Bayangkan situasi yang sama terjadi pada sesuatu yang sedikit lebih kompleks.

Florents Tselai
sumber

Jawaban:

17

Saya pikir Anda sebenarnya berada di jalur yang benar sejauh ingin menyembunyikan detail implementasi dari klien. Anda ingin merancang kelas Anda sehingga antarmuka yang dilihat klien adalah API paling sederhana dan paling ringkas yang dapat Anda buat. Tidak hanya klien tidak akan "terganggu" dengan detail implementasi, tetapi Anda juga akan bebas untuk memperbaiki kode yang mendasarinya tanpa khawatir penelepon kode itu harus dimodifikasi. Mengurangi sambungan antara modul yang berbeda memiliki manfaat nyata dan Anda harus berusaha keras untuk itu.

Jadi, jika Anda mengikuti saran yang baru saja saya berikan, Anda akan berakhir dengan sesuatu yang sudah Anda perhatikan dalam kode Anda, sejumlah besar logika yang tetap tersembunyi di balik antarmuka publik dan tidak mudah diakses.

Idealnya, Anda harus dapat menguji unit Anda terhadap antarmuka publik saja dan jika memiliki dependensi eksternal, Anda mungkin perlu memperkenalkan objek palsu / tiruan / rintisan untuk mengisolasi kode yang sedang diuji.

Namun, jika Anda melakukan ini dan Anda masih merasa bahwa Anda tidak dapat dengan mudah menguji setiap bagian dari kelas, maka sangat mungkin bahwa satu kelas Anda melakukan terlalu banyak. Dalam semangat prinsip SRP , Anda dapat mengikuti apa yang disebut Michael Feathers "Pola Kelas Sprout" dan mengekstrak sepotong kelas asli Anda ke dalam kelas baru.

Dalam contoh Anda, Anda memiliki kelas importir yang juga bertanggung jawab untuk membaca file teks. Salah satu opsi Anda adalah mengekstrak sepotong kode pembacaan file ke kelas InputFileParser yang terpisah. Sekarang semua fungsi pribadi menjadi publik dan karenanya mudah diuji. Pada saat yang sama kelas parser bukan sesuatu yang terlihat oleh klien eksternal (tandai "internal", jangan publikasikan file header atau hanya tidak mengiklankannya sebagai bagian dari API Anda) karena mereka akan terus menggunakan yang asli importir yang antarmuka-nya akan tetap pendek dan manis.

DXM
sumber
1

Sebagai alternatif untuk menempatkan banyak pemanggilan metode ke dalam konstruktor kelas Anda - yang saya setuju adalah kebiasaan yang biasanya dihindari - Anda bisa membuat metode pabrik untuk menangani semua hal inisialisasi tambahan yang Anda tidak ingin mengganggu klien berpihak. Ini berarti Anda akan mengekspos metode agar terlihat seperti constructGroupOfCustomers()metode Anda sebagai publik, dan menggunakannya sebagai metode statis kelas Anda:

GroupOfCustomersImporter importer = 
    new GroupOfCustomersImporter.CreateInstance();

atau sebagai metode kelas pabrik yang terpisah mungkin:

ImporterFactory factory = new ImporterFactory();
GroupOfCustomersImporter importer = factory.CreateGroupOfCustomersImporter();

Itulah beberapa pilihan pertama yang dipikirkan langsung dari atas kepala saya.

Anda juga perlu mempertimbangkan bahwa naluri Anda sedang mencoba memberi tahu Anda sesuatu. Ketika usus Anda memberi tahu Anda bahwa kodenya mulai berbau, mungkin baunya, dan lebih baik melakukan sesuatu tentangnya sebelum berbau! Tentu saja di permukaan mungkin secara intrinsik tidak ada yang salah dengan kode Anda, namun periksa ulang cepat dan refactor akan membantu menyelesaikan pikiran Anda tentang masalah ini sehingga Anda dapat beralih ke hal-hal lain dengan percaya diri tanpa khawatir jika Anda sedang membangun rumah kartu potensial. Dalam kasus khusus ini, mendelegasikan beberapa tanggung jawab konstruktor untuk - atau dipanggil oleh - sebuah pabrik memastikan Anda dapat melakukan kontrol yang lebih besar atas instantiasi kelas Anda dan tentang potensi turunannya di masa depan.

S.Robins
sumber
1

Menambahkan jawaban saya sendiri, karena terlalu panjang untuk dikomentari.

Anda sepenuhnya benar bahwa enkapsulasi dan pengujian sangat bertolak belakang - jika Anda menyembunyikan hampir semuanya, sulit untuk menguji.

Anda bisa, bagaimanapun, membuat contoh lebih dapat diuji dengan memberikan aliran daripada nama file - dengan cara itu Anda hanya menyediakan csv yang dikenal dari memori, atau file jika Anda mau. Ini juga akan membuat kelas Anda lebih kuat ketika Anda perlu menambahkan dukungan http;)

Juga, perhatikan menggunakan lazy-init:

public class Csv
{
  private CustomerList list;

  public CustomerList get()
  {
    if(list == null)
        load();
     return list;
  }
}
Maks
sumber
1

Konstruktor tidak boleh melakukan terlalu banyak kecuali menginisialisasi beberapa variabel atau keadaan objek. Melakukan terlalu banyak dalam konstruktor dapat meningkatkan pengecualian runtime. Saya akan mencoba menghindari klien melakukan sesuatu seperti ini:

MyClass a;
try {
   a = new MyClass();
} catch (MyException e) {
   //do something
}

Alih-alih melakukan:

MyClass a = new MyClass(); // Or a factory method
try {
   a.doSomething();
} catch (MyException e) {
   //do something
}
trotuar
sumber