Haruskah saya meneruskan objek ke konstruktor, atau instantiate di kelas?

10

Perhatikan dua contoh ini:

Melewati objek ke konstruktor

class ExampleA
{
  private $config;
  public function __construct($config)
  {
     $this->config = $config;
  }
}
$config = new Config;
$exampleA = new ExampleA($config);

Instantiating kelas

class ExampleB
{
  private $config;
  public function __construct()
  {
     $this->config = new Config;
  }
}
$exampleA = new ExampleA();

Mana cara yang benar untuk menangani menambahkan objek sebagai properti? Kapan saya harus menggunakan yang satu? Apakah pengujian unit memengaruhi apa yang harus saya gunakan?

Tawanan
sumber
Mungkinkah ini lebih baik di codereview.stackexchange.com/questions ?
FrustratedWithFormsDesigner
9
@FrustratedWithFormsDesigner - ini tidak cocok untuk Tinjauan Kode.
ChrisF

Jawaban:

14

Saya pikir yang pertama akan memberi Anda kemampuan untuk membuat configobjek di tempat lain dan meneruskannya ExampleA. Jika Anda memerlukan Ketergantungan Injeksi, ini bisa menjadi hal yang baik karena Anda dapat memastikan bahwa semua niat berbagi objek yang sama.

Di sisi lain, mungkin Anda ExampleA memerlukan objek baru dan bersih configsehingga mungkin ada kasus di mana contoh kedua lebih tepat, seperti kasus di mana setiap contoh berpotensi memiliki konfigurasi yang berbeda.

FrustratedWithFormsDesigner
sumber
1
Jadi, A bagus untuk pengujian unit, B bagus untuk keadaan lain ... mengapa tidak memiliki keduanya? Saya sering memiliki dua konstruktor, satu untuk DI manual, satu untuk penggunaan normal (saya menggunakan Unity untuk DI, yang akan menghasilkan konstruktor untuk memenuhi persyaratan DI-nya).
Ed James
3
@ Edwoodcock ini bukan tentang pengujian unit vs keadaan lainnya. Objek membutuhkan ketergantungan dari luar atau mengaturnya di dalam. Tidak keduanya / keduanya.
MattDavey
9

Jangan lupa tentang testabilitas!

Biasanya jika perilaku Examplekelas tergantung pada konfigurasi yang Anda inginkan untuk dapat mengujinya tanpa menyimpan / mengubah keadaan internal kelas contoh (yaitu Anda ingin memiliki tes sederhana untuk jalur bahagia dan untuk konfigurasi yang salah tanpa memodifikasi properti / memeber Examplekelas).

Karena itu saya akan pergi dengan opsi pertama ExampleA

Paul
sumber
Itulah mengapa saya menyebutkan pengujian - terima kasih telah menambahkan itu :)
Tahanan
5

Jika objek memiliki tanggung jawab untuk mengelola masa pakai dependensi, maka boleh saja untuk membuat objek di konstruktor (dan membuangnya di destruktor). *

Jika objek tidak bertanggung jawab untuk mengelola masa ketergantungan, objek harus diteruskan ke konstruktor dan dikelola dari luar (misalnya oleh wadah IoC).

Dalam hal ini saya tidak berpikir ClassA Anda harus bertanggung jawab untuk membuat $ config, kecuali jika itu juga bertanggung jawab untuk membuangnya, atau jika konfigurasi tersebut unik untuk setiap instance ClassA.

* Untuk membantu dalam testabilitas, konstruktor dapat mengambil referensi ke kelas pabrik / metode untuk membangun ketergantungan pada konstruktornya, meningkatkan kohesi dan testabilitas.

//Object which manages the lifetime of its dependency (C#):
public class ClassA : IDisposable
{
    public Config Config { get; private set; }

    public ClassA()
    {
        this.Config = new Config(); // Tightly coupled to Config class...
    }

    public void Dispose()
    {
        this.Config.Dispose();
    }
}

// Object which does not manage its dependency:
public class ClassA
{
    public Config Config { get; set; }

    public ClassA(Config config) // dependency may be injected...
    {
        this.Config = config;
    }
}

// Object which manages its dependency in a testable way:
public class ClassA : IDisposable
{
    public Config Config { get; private set; }

    public ClassA(IConfigFactory configFactory) // dependency may be mocked...
    {
        this.Config = configFactory.BuildConfig();
    }

    public void Dispose()
    {
        this.Config.Dispose();
    }
}
MattDavey
sumber
2

Saya telah melakukan diskusi yang sama persis dengan tim arsitektur kami baru-baru ini dan ada beberapa alasan halus untuk melakukannya. Sebagian besar tergantung pada Injeksi Ketergantungan (seperti yang telah dicatat orang lain) dan apakah Anda benar-benar memiliki kendali atas penciptaan objek yang Anda baru di konstruktor.

Dalam contoh Anda, bagaimana jika kelas Config Anda:

a) memiliki alokasi non-sepele, seperti berasal dari kumpulan atau metode pabrik.

b) bisa gagal dialokasikan. Meneruskannya ke konstruktor dengan rapi menghindari masalah ini.

c) sebenarnya adalah subclass dari Config.

Melewati objek ke konstruktor memberikan fleksibilitas paling.

JBRWilkinson
sumber
1

Jawaban di bawah ini salah, tetapi saya akan menyimpannya agar orang lain dapat belajar darinya (lihat di bawah)

Di ExampleA, Anda dapat menggunakan Configcontoh yang sama di beberapa kelas. Namun, jika harus ada hanya satu Configinstance dalam seluruh aplikasi, pertimbangkan menerapkan pola Singleton pada Configuntuk menghindari memiliki beberapa instance Config. Dan jika Anda Configseorang Singleton, Anda bisa melakukan yang berikut:

class ExampleA
{
  private $config;
  public function __construct()
  {
     $this->config = Config->getInstance();
  }
}
$exampleA = new ExampleA();

Di ExampleBsisi lain, Anda akan selalu mendapatkan instance terpisah Configuntuk setiap instance ExampleB.

Versi mana yang harus Anda terapkan benar-benar tergantung pada bagaimana aplikasi akan menangani instance dari Config:

  • jika setiap instance ExampleXharus memiliki instance terpisah Config, ikuti ExampleB;
  • jika setiap instance ExampleXakan membagikan satu (dan hanya satu) instance dari Config, gunakan ExampleA with Config Singleton;
  • Jika contoh ExampleXdapat menggunakan contoh berbeda Config, tetap dengan ExampleA.

Mengapa Configmenjadi Singleton salah:

Saya harus mengakui bahwa saya baru belajar tentang pola Singleton kemarin (membaca buku Kepala Pertama pola desain). Secara naif saya pergi dan menerapkannya untuk contoh ini, tetapi seperti yang telah ditunjukkan banyak orang, satu cara yang lain (beberapa lebih samar dan hanya mengatakan "Anda salah!"), Ini bukan ide yang baik. Jadi, untuk mencegah orang lain melakukan kesalahan yang sama seperti yang baru saja saya lakukan, berikut ini ringkasan mengapa pola Singleton bisa berbahaya (berdasarkan komentar dan apa yang saya temukan di Google):

  1. Jika ExampleAmengambil referensi sendiri ke Configinstance, kelas akan digabungkan secara ketat. Tidak akan ada cara ExampleAuntuk menggunakan versi yang berbeda dari Config(misalnya beberapa subclass). Ini mengerikan jika Anda ingin menguji ExampleAmenggunakan contoh mock-up Configkarena tidak ada cara untuk menyediakannya ExampleA.

  2. Premis akan ada satu, dan hanya satu, contoh dari Configmungkin berlaku sekarang , tetapi Anda tidak selalu dapat memastikan bahwa hal yang sama akan berlaku di masa depan . Jika pada suatu saat nanti ternyata beberapa contoh Configakan diinginkan, tidak ada cara untuk mencapai ini tanpa menulis ulang kode.

  3. Meskipun instance satu-dan-hanya-satu Configmungkin benar untuk selamanya, itu bisa terjadi bahwa Anda ingin dapat menggunakan beberapa subclass dari Config(sementara masih hanya memiliki satu instance). Tetapi, karena kode secara langsung mendapatkan instance via getInstance()dari Config, yang merupakan staticmetode, tidak ada cara untuk mendapatkan subclass. Sekali lagi, kode harus ditulis ulang.

  4. Fakta bahwa ExampleApenggunaan Configakan disembunyikan, setidaknya ketika hanya melihat API ExampleA. Ini mungkin atau mungkin bukan hal yang buruk, tetapi secara pribadi saya merasa ini terasa tidak menguntungkan; misalnya, ketika mempertahankan, tidak ada cara sederhana untuk mengetahui kelas mana yang akan terpengaruh oleh perubahan Configtanpa melihat ke dalam implementasi setiap kelas lainnya.

  5. Bahkan jika fakta yang ExampleAmenggunakan Singleton Config bukanlah masalah, itu mungkin masih menjadi masalah dari sudut pandang pengujian. Benda-benda singleton akan membawa keadaan yang akan bertahan sampai penghentian aplikasi. Ini bisa menjadi masalah ketika menjalankan tes unit karena Anda ingin satu tes diisolasi dari yang lain (yaitu bahwa menjalankan satu tes seharusnya tidak mempengaruhi hasil yang lain). Untuk memperbaikinya, objek Singleton harus dihancurkan antara setiap uji coba (berpotensi harus me-restart seluruh aplikasi), yang mungkin memakan waktu (belum lagi membosankan dan menjengkelkan).

Setelah mengatakan ini, saya senang saya membuat kesalahan ini di sini dan tidak dalam penerapan aplikasi nyata. Bahkan, saya sebenarnya sedang mempertimbangkan untuk menulis ulang kode terbaru saya untuk menggunakan pola Singleton untuk beberapa kelas. Meskipun saya dapat dengan mudah mengembalikan perubahan (semuanya disimpan dalam SVN, tentu saja), saya masih akan membuang waktu untuk melakukannya.

Gablin
sumber
4
Saya tidak akan merekomendasikan melakukannya .. dengan cara ini Anda pasangan kelas ketat ExampleAdan Config- yang bukan hal yang baik.
Paul
@ Paul: Itu benar. Tangkapan yang bagus, tidak memikirkan hal itu.
gablin
3
Saya selalu merekomendasikan untuk tidak menggunakan lajang karena alasan testabilitas. Mereka pada dasarnya adalah variabel global dan tidak mungkin untuk mengejek ketergantungan.
MattDavey
4
Saya akan selalu merekomendasikan againts menggunakan Singletons untuk alasan Anda salah melakukannya.
Raynos
1

Hal paling sederhana untuk dilakukan adalah beberapa ExampleAuntuk Config. Anda harus melakukan hal yang paling sederhana, kecuali ada alasan kuat untuk melakukan sesuatu yang lebih kompleks.

Salah satu alasan untuk decoupling ExampleAdan Configakan meningkatkan testability dari ExampleA. Kopling langsung akan menurunkan testabilitas ExampleAjika Configmemiliki metode yang lambat, kompleks, atau berkembang dengan cepat. Untuk pengujian, metode lambat jika berjalan lebih dari beberapa mikrodetik. Jika semua metode Configyang sederhana dan cepat, maka saya akan mengambil pendekatan yang sederhana dan langsung beberapa ExampleAuntuk Config.

kevin cline
sumber
1

Contoh pertama Anda adalah contoh dari pola Injeksi Ketergantungan. Kelas dengan dependensi eksternal diberikan dependensi oleh konstruktor, setter, dll.

Pendekatan ini menghasilkan kode yang digabungkan secara longgar. Kebanyakan orang berpikir bahwa kopling longgar adalah hal yang baik, karena Anda dapat dengan mudah mengganti konfigurasi dalam kasus-kasus di mana satu contoh tertentu dari suatu objek perlu dikonfigurasi secara berbeda dari yang lain, Anda dapat mengirimkan objek konfigurasi tiruan untuk pengujian, dan sebagainya di.

Pendekatan kedua lebih dekat dengan pola pencipta GRASP. Dalam hal ini, objek membuat dependensinya sendiri. Ini menghasilkan kode yang digabungkan secara ketat, ini dapat membatasi fleksibilitas kelas dan membuatnya lebih sulit untuk diuji. Jika Anda memerlukan satu instance kelas untuk memiliki ketergantungan yang berbeda dari yang lain, satu-satunya pilihan Anda adalah dengan subkelasnya.

Namun, ini bisa menjadi pola yang sesuai, dalam kasus di mana masa hidup objek bergantung ditentukan oleh masa pakai objek dependen, dan di mana objek dependen tidak digunakan di mana pun di luar objek yang bergantung padanya. Biasanya saya menyarankan DI untuk posisi default, tetapi Anda tidak harus mengesampingkan pendekatan lain sepenuhnya selama Anda mengetahui konsekuensinya.

GordonM
sumber
0

Jika kelas Anda tidak mengekspos $configke kelas eksternal, maka saya akan membuatnya di dalam konstruktor. Dengan cara ini, Anda menjaga keadaan internal Anda tetap pribadi.

Jika persyaratan $configmemerlukan keadaan internal sendiri untuk ditetapkan dengan benar (misalnya, membutuhkan koneksi database atau beberapa bidang internal diinisialisasi) sebelum digunakan, maka masuk akal untuk menunda inisialisasi ke beberapa kode eksternal (mungkin kelas pabrik) dan menyuntikkannya ke dalam konstruktor. Atau, seperti yang telah ditunjukkan orang lain, jika perlu dibagikan di antara objek-objek lain.

TMN
sumber
0

Contoh A dipisahkan dari kelas beton Config yang baik , asalkan objek diterima jika bukan tipe Config tetapi jenis superclass abstrak Config.

Contoh B sangat digabungkan ke kelas beton Config yang buruk .

Instantiasi objek menciptakan kopling yang kuat antara kelas. Itu harus dilakukan di kelas Pabrik.

Tulains Córdova
sumber