Bagaimana cara menjaga argumen tetap rendah dan masih membuat dependensi pihak ketiga terpisah?

13

Saya menggunakan perpustakaan pihak ketiga. Mereka memberi saya POJO yang, untuk maksud dan tujuan kami, mungkin dilaksanakan seperti ini:

public class OurData {
  private String foo;
  private String bar;
  private String baz;
  private String quux;
  // A lot more than this

  // IMPORTANT: NOTE THAT THIS IS A PACKAGE PRIVATE CONSTRUCTOR
  OurData(/* I don't know what they do */) {
    // some stuff
  }

  public String getFoo() {
    return foo;
  }

  // etc.
}

Untuk banyak alasan, termasuk tetapi tidak terbatas pada merangkum API mereka dan memfasilitasi pengujian unit, saya ingin membungkus data mereka. Tetapi saya tidak ingin kelas inti saya bergantung pada data mereka (sekali lagi, untuk alasan pengujian)! Jadi sekarang saya punya sesuatu seperti ini:

public class DataTypeOne implements DataInterface {
  private String foo;
  private int bar;
  private double baz;

  public DataTypeOne(String foo, int bar, double baz) {
    this.foo = foo;
    this.bar = bar;
    this.baz = baz;
  }
}

public class DataTypeTwo implements DataInterface {
  private String foo;
  private int bar;
  private double baz;

  public DataTypeOne(String foo, int bar, double baz, String quux) {
    this.foo = foo;
    this.bar = bar;
    this.baz = baz;
    this.quux = quux;
  }
}

Dan kemudian ini:

public class ThirdPartyAdapter {
  public static makeMyData(OurData data) {
    if(data.getQuux() == null) {
      return new DataTypeOne(
        data.getFoo(),
        Integer.parseInt(data.getBar()),
        Double.parseDouble(data.getBaz()),
      );
    } else {
      return new DataTypeTwo(
        data.getFoo(),
        Integer.parseInt(data.getBar()),
        Double.parseDouble(data.getBaz()),
        data.getQuux();
      );
  }
}

Kelas adaptor ini digabungkan dengan beberapa kelas lain yang HARUS tahu tentang API pihak ketiga, membatasi pervasiveness melalui seluruh sistem saya. Namun ... solusi ini adalah KOTOR! Dalam Kode Bersih, halaman 40:

Lebih dari tiga argumen (polyadic) membutuhkan pembenaran yang sangat khusus - dan karenanya tidak seharusnya digunakan.

Hal-hal yang saya pertimbangkan:

  • Membuat objek pabrik daripada metode pembantu statis
    • Tidak memecahkan masalah memiliki argumen bajillion
  • Membuat subkelas DataTypeOne dan DataTypeTwo yang memiliki konstruktor dependen
    • Masih memiliki konstruktor yang dilindungi polyadic
  • Buat implementasi yang sepenuhnya terpisah yang sesuai dengan antarmuka yang sama
  • Banyak dari ide-ide di atas secara bersamaan

Bagaimana seharusnya situasi ini ditangani?


Perhatikan ini bukan situasi lapisan anti korupsi . Tidak ada yang salah dengan API mereka. Masalahnya adalah:

  • Saya tidak ingin memiliki struktur data MY import com.third.party.library.SomeDataStructure;
  • Saya tidak bisa membuat struktur data mereka dalam kasus pengujian saya
  • Solusi saya saat ini menghasilkan jumlah argumen yang sangat tinggi. Saya ingin menjaga agar jumlah argumen tetap rendah, TANPA meneruskan struktur data mereka.
  • Pertanyaan itu adalah " apa itu lapisan anti korupsi?". Pertanyaan saya adalah " bagaimana saya bisa menggunakan pola, pola apa saja, untuk menyelesaikan skenario ini?"

Saya juga tidak meminta kode (jika tidak pertanyaan ini ada di SO), hanya cukup meminta jawaban untuk memungkinkan saya menulis kode secara efektif (yang tidak disediakan pertanyaan itu).

durron597
sumber
Jika ada beberapa POJO pihak ke-3 tersebut, mungkin perlu upaya untuk menulis kode pengujian khusus yang menggunakan Peta dengan beberapa konvensi (mis. Beri nama kunci int_bar) sebagai input pengujian Anda. Atau gunakan JSON atau XML dengan beberapa kode perantara khusus. Akibatnya, semacam DSL untuk menguji com.thirdparty.
user949300
Kutipan lengkap dari Clean Code:The ideal number of arguments for a function is zero (niladic). Next comes one (monadic), followed closely by two (dyadic). Three arguments (triadic) should be avoided where possible. More than three (polyadic) requires very special justification — and then shouldn’t be used anyway.
Lilienthal
11
Ketaatan buta terhadap suatu pola atau pedoman pemrograman adalah anti-polanya sendiri .
Lilienthal
2
"merangkum API mereka dan memfasilitasi pengujian unit" Kedengarannya seperti ini mungkin kasus pengujian berlebihan dan / atau Kerusakan Desain yang diinduksi oleh Tes kepada saya (atau indikasi bahwa Anda dapat merancang ini secara berbeda untuk memulai). Tanyakan kepada diri Anda sendiri: apakah ini benar - benar membuat kode Anda lebih mudah dipahami dan diubah serta digunakan kembali? Saya akan menaruh uang saya pada "tidak." Seberapa realistis kemungkinan Anda akan pernah menukar perpustakaan ini? Mungkin tidak terlalu. Jika Anda menukarnya, apakah ini benar - benar membuatnya lebih mudah untuk menjatuhkan yang benar-benar berbeda di tempat? Sekali lagi, saya bertaruh "tidak".
jpmc26
1
@JamesAnderson Saya baru saja mereproduksi kutipan lengkap karena saya menemukan itu menarik tetapi tidak jelas bagi saya dari potongan apakah merujuk pada fungsi secara umum atau konstruktor secara khusus. Saya tidak bermaksud mendukung klaim tersebut dan, seperti yang dikatakan jpmc26, komentar saya berikutnya harus memberi Anda beberapa indikasi bahwa saya tidak melakukannya. Saya tidak yakin mengapa Anda merasa perlu untuk menyerang akademisi tetapi menggunakan polysyllables tidak membuat seseorang menjadi elitis akademik bertengger di menara gading di atas awan.
Lilienthal

Jawaban:

10

Strategi yang saya gunakan ketika ada beberapa parameter inisialisasi adalah membuat tipe yang hanya berisi parameter untuk inisialisasi

public class DataTypeTwoParameters {
    public String foo;  // use getters/setters instead if it's appropriate
    public int bar;
    public double baz;
    public String quuz;
}

Kemudian konstruktor untuk DataTypeTwo mengambil objek DataTypeTwoParameters, dan DataTypeTwo dibangun melalui:

DataTypeTwoParameters p = new DataTypeTwoParameters();
p.foo = "Hello";
p.bar = 4;
p.baz = 3;
p.quuz = "World";

DataTypeTwo dtt = new DataTypeTwo(p);

Ini memberi banyak peluang untuk menjelaskan semua parameter yang masuk ke DataTypeTwo dan apa artinya. Anda juga dapat memberikan default yang masuk akal dalam konstruktor DataTypeTwoParameters sehingga hanya nilai yang perlu diatur yang dapat dilakukan dengan urutan apa pun yang disukai konsumen API.

Erik
sumber
Pendekatan yang menarik. Di mana Anda akan meletakkan yang relevan Integer.parseInt? Di setter, atau di luar kelas parameter?
durron597
5
Di luar kelas parameter. Kelas parameter harus berupa objek "bisu", dan tidak boleh mencoba melakukan apa pun selain mengekspresikan apa input yang diperlukan dan tipenya. Parsing harus dilakukan di tempat lain, seperti: p.bar = Integer.parseInt("4").
Erik
7
ini terdengar seperti pola Objek Parameter
nyamuk
9
... atau anti-pola.
Telastyn
1
... atau Anda bisa mengganti nama DataTypeTwoParametersmenjadi DataTypeTwo.
user253751
14

Anda benar-benar memiliki dua masalah terpisah di sini: membungkus API dan menjaga agar argumen tetap rendah.

Saat membungkus API, idenya adalah mendesain antarmuka seolah-olah dari awal, tidak tahu apa-apa selain persyaratan. Anda mengatakan tidak ada yang salah dengan API mereka, maka dalam daftar nafas yang sama sejumlah hal yang salah dengan API mereka: testability, constructability, terlalu banyak parameter dalam satu objek, dll. Tuliskan API yang Anda inginkan . Jika itu membutuhkan beberapa objek, bukan satu, lakukan itu. Jika memerlukan pembungkus satu tingkat lebih tinggi, ke objek yang membuat POJO, lakukan itu.

Kemudian setelah Anda memiliki API yang Anda inginkan, jumlah parameter mungkin tidak lagi menjadi masalah. Jika ya, ada sejumlah pola umum untuk dipertimbangkan:

  • Objek parameter, seperti dalam jawaban Erik .
  • The pola pembangun , di mana Anda membuat objek pembangun terpisah, kemudian memanggil sejumlah setter untuk mengatur parameter individual, kemudian membuat objek akhir Anda.
  • The pola prototipe , di mana Anda mengkloning subclass dari objek yang Anda inginkan dengan bidang yang sudah ditetapkan secara internal.
  • Sebuah pabrik, yang sudah Anda kenal.
  • Beberapa kombinasi di atas.

Perhatikan bahwa pola kreasi ini sering kali berakhir memanggil konstruktor poladik, yang harus Anda pertimbangkan baik-baik saja ketika dienkapsulasi. Masalah dengan konstruktor polyadic tidak memanggilnya sekali, itu adalah ketika Anda dipaksa untuk memanggil mereka setiap kali Anda perlu membangun suatu objek.

Perhatikan bahwa biasanya lebih mudah dan lebih mudah dikelola untuk melewati API yang mendasarinya dengan menyimpan referensi ke OurDataobjek dan meneruskan panggilan metode, daripada mencoba menerapkan kembali internal-nya. Sebagai contoh:

public class DataTypeTwo implements DataInterface {
  private OurData data;

  public DataTypeOne(OurData data) {
    this.data = data;
  }

   public String getFoo() {
    return data.getFoo();
  }

  public int getBar() {
    return Integer.parseInt(data.getBar());
  }
  ...
}
Karl Bielefeldt
sumber
Bagian pertama dari jawaban ini: bagus, sangat membantu, +1. Bagian kedua dari jawaban ini: "lewat ke API yang mendasarinya dengan menyimpan referensi ke OurDataObjek" - inilah yang saya coba hindari, setidaknya di kelas dasar, untuk memastikan tidak ada ketergantungan.
durron597
1
Itu sebabnya Anda hanya melakukannya di salah satu implementasi dari DataInterface. Anda membuat implementasi lain untuk objek tiruan Anda.
Karl Bielefeldt
@ durron597: ya, tapi Anda sudah tahu cara mengatasi masalah itu jika itu benar-benar mengganggu Anda.
Doc Brown
1

Saya pikir Anda mungkin menafsirkan rekomendasi Paman Bob terlalu ketat. Untuk kelas normal, dengan logika dan metode dan konstruktor dan semacamnya, konstruktor poladik memang terasa sangat mirip dengan kode bau. Tetapi untuk sesuatu yang benar-benar wadah data yang mengekspos bidang, dan dihasilkan oleh apa yang pada dasarnya adalah objek Pabrik, saya tidak berpikir itu terlalu buruk.

Anda dapat menggunakan pola Objek Parameter, seperti yang disarankan dalam komentar, dapat membungkus parameter konstruktor ini untuk Anda, apa pembungkus tipe data lokal Anda sudah , pada dasarnya, objek Parameter. Semua objek Parameter Anda akan lakukan adalah mengemas parameter (Bagaimana Anda akan membuatnya? Dengan konstruktor poladik?) Dan kemudian membongkar mereka sedetik kemudian menjadi objek yang hampir identik.

Jika Anda tidak ingin mengekspos seter untuk bidang Anda dan memanggilnya, saya pikir menempel pada konstruktor poladik di dalam pabrik yang didefinisikan dengan baik dan dienkapsulasi baik-baik saja.

Avner Shahar-Kashtan
sumber
Masalahnya adalah, jumlah bidang dalam struktur data saya telah berubah beberapa kali dan mungkin akan berubah lagi. Yang berarti saya harus memperbaiki konstruktor dalam semua kasus pengujian saya. Pola parameter dengan default yang masuk akal terdengar seperti cara yang lebih baik; memiliki versi yang bisa berubah yang diselamatkan ke dalam bentuk yang tidak berubah dapat membuat hidup saya lebih mudah dalam banyak hal.
durron597