Yang merupakan praktik yang lebih baik - metode pembantu sebagai contoh atau statis?

48

Pertanyaan ini subyektif tapi saya hanya ingin tahu bagaimana kebanyakan programmer mendekati ini. Sampel di bawah ini dalam pseudo-C # tetapi ini harus berlaku untuk Java, C ++, dan bahasa OOP lainnya juga.

Lagi pula, ketika menulis metode helper di kelas saya, saya cenderung menyatakannya sebagai statis dan hanya meneruskan kolom jika metode helper membutuhkannya. Misalnya, mengingat kode di bawah ini, saya lebih suka menggunakan Metode Panggilan # 2 .

class Foo
{
  Bar _bar;

  public void DoSomethingWithBar()
  {
    // Method Call #1.
    DoSomethingWithBarImpl();

    // Method Call #2.
    DoSomethingWithBarImpl(_bar);
  }

  private void DoSomethingWithBarImpl()
  {
    _bar.DoSomething();
  }

  private static void DoSomethingWithBarImpl(Bar bar)
  {
    bar.DoSomething();
  }
}

Alasan saya melakukan ini adalah karena hal itu menjelaskan (paling tidak di mata saya) bahwa metode helper memiliki efek samping yang mungkin terjadi pada objek lain - bahkan tanpa membaca implementasinya. Saya menemukan bahwa saya dapat dengan cepat grok metode yang menggunakan praktik ini dan dengan demikian membantu saya dalam debugging hal-hal.

Yang mana yang Anda sukai untuk dilakukan dalam kode Anda sendiri dan apa alasan Anda melakukannya?

Ilian
sumber
3
Saya akan menghapus C ++ dari pertanyaan ini: di C ++, jelas, Anda akan menjadikannya metode gratis, karena tidak ada gunanya disimpan secara sewenang-wenang ke objek: ia menonaktifkan ADL.
Matthieu M.
1
@ MatthieuM .: Metode gratis atau tidak, tidak masalah. Maksud dari pertanyaan saya adalah untuk menanyakan apakah ada keuntungan dalam menyatakan metode pembantu sebagai contoh. Jadi di C ++ pilihan bisa menjadi metode instan vs metode / fungsi gratis. Anda punya poin bagus tentang ADL. Jadi, apakah Anda mengatakan bahwa Anda lebih suka menggunakan metode gratis? Terima kasih!
Ilian
2
untuk C ++, metode gratis direkomendasikan. Mereka meningkatkan enkapsulasi (secara tidak langsung, karena hanya dapat mengakses antarmuka publik) dan masih bermain bagus karena ADL (terutama di templat). Namun saya tidak tahu apakah ini berlaku untuk Java / C #, C ++ sangat berbeda dari Java / C # jadi saya berharap jawabannya berbeda (dan dengan demikian saya tidak berpikir meminta 3 bahasa bersama-sama masuk akal).
Matthieu M.
1
Tanpa mencoba memperparah siapa pun, saya tidak pernah mendengar alasan bagus untuk tidak menggunakan metode statis. Saya menggunakannya di mana pun saya bisa karena mereka memperjelas apa yang dilakukan metode ini.
Sridhar Sarnobat

Jawaban:

23

Ini sangat tergantung. Jika nilai yang digunakan oleh pembantu Anda adalah primitif, maka metode statis adalah pilihan yang baik, seperti yang ditunjukkan Péter.

Jika mereka kompleks, maka SOLID berlaku, lebih khusus S , yang saya dan D .

Contoh:

class CookieJar {
      function takeCookies(count:Int):Array<Cookie> { ... }
      function countCookies():Int { ... }
      function ressuplyCookies(cookies:Array<Cookie>
      ... // lot of stuff we don't care about now
}

class CookieFan {
      function getHunger():Float;
      function eatCookies(cookies:Array<Cookie>):Smile { ... }
}

class OurHouse {
      var jake:CookieFan;
      var jane:CookieFan;
      var cookies:CookieJar;
      function makeEveryBodyAsHappyAsPossible():Void {
           //perform a lot of operations on jake, jane and the cookies
      }
      public function cookieTime():Void {
           makeEveryBodyAsHappyAsPossible();
      }
}

Ini tentang masalah Anda. Anda dapat membuat makeEveryBodyAsHappyAsPossiblemetode statis, yang akan mengambil parameter yang diperlukan. Pilihan lain adalah:

interface CookieDistributor {
    function distributeCookies(to:Array<CookieFan>):Array<Smile>;
}
class HappynessMaximizingDistributor implements CookieDistributor {
    var jar:CookieJar;
    function distributeCookies(to:Array<CookieFan>):Array<Smile> {
         //put the logic of makeEveryBodyAsHappyAsPossible here
    }
}
//and make a change here
class OurHouse {
      var jake:CookieFan;
      var jane:CookieFan;
      var cookies:CookieDistributor;

      public function cookieTime():Void {
           cookies.distributeCookies([jake, jane]);
      }
}

Sekarang OurHousetidak perlu tahu tentang seluk-beluk aturan distribusi cookie. Itu harus sekarang hanya objek, yang mengimplementasikan aturan. Implementasinya diabstraksi menjadi sebuah objek, siapa yang bertanggung jawab untuk menerapkan aturan tersebut. Objek ini dapat diuji secara terpisah. OurHousedapat diuji dengan menggunakan tiruan belaka CookieDistributor. Dan Anda dapat dengan mudah memutuskan untuk mengubah aturan distribusi cookie.

Namun, berhati-hatilah agar Anda tidak berlebihan. Sebagai contoh, memiliki sistem 30 kelas yang kompleks bertindak sebagai implementasi dari CookieDistributor, di mana setiap kelas hanya memenuhi tugas kecil, tidak terlalu masuk akal. Interpretasi saya terhadap SRP adalah bahwa hal itu tidak hanya menentukan bahwa setiap kelas mungkin hanya memiliki satu tanggung jawab, tetapi juga bahwa satu tanggung jawab tunggal harus dijalankan oleh satu kelas tunggal.

Dalam kasus primitif atau objek yang Anda gunakan seperti primitif (misalnya objek yang mewakili titik dalam ruang, matriks atau sesuatu), kelas helper statis sangat masuk akal. Jika Anda memiliki pilihan, dan itu benar-benar masuk akal, maka Anda mungkin benar-benar mempertimbangkan untuk menambahkan metode ke kelas yang mewakili data, misalnya masuk akal jika Pointmemiliki addmetode. Sekali lagi, jangan berlebihan.

Jadi tergantung pada masalah Anda, ada berbagai cara untuk menyelesaikannya.

back2dos
sumber
18

Merupakan idiom yang terkenal untuk mendeklarasikan metode kelas utilitas static, sehingga kelas seperti itu tidak perlu dipakai. Mengikuti idiom ini membuat kode Anda lebih mudah dipahami, yang merupakan hal yang baik.

Ada batasan serius untuk pendekatan ini: metode / kelas seperti itu tidak dapat dengan mudah diejek (meskipun AFAIK setidaknya untuk C # ada kerangka kerja mengejek yang dapat mencapai hal ini, tetapi mereka tidak biasa dan setidaknya beberapa dari mereka adalah komersial). Jadi, jika metode helper memiliki ketergantungan eksternal (misalnya DB) yang membuatnya - sehingga peneleponnya - sulit untuk diuji unit, lebih baik untuk menyatakannya non-statis . Ini memungkinkan injeksi ketergantungan, sehingga membuat penelepon metode lebih mudah untuk unit test.

Memperbarui

Klarifikasi: pembicaraan di atas tentang kelas utilitas, yang hanya berisi metode pembantu tingkat rendah dan (biasanya) tidak ada status. Metode pembantu di dalam kelas non-utilitas stateful adalah masalah yang berbeda; permintaan maaf karena salah mengartikan OP.

Dalam hal ini, saya merasakan bau kode: metode kelas A yang beroperasi terutama pada instance dari kelas B mungkin sebenarnya memiliki tempat yang lebih baik di kelas B. Tetapi jika saya memutuskan untuk menyimpannya di tempat itu, saya lebih suka opsi 1, karena lebih sederhana dan lebih mudah dibaca.

Péter Török
sumber
Tidak bisakah Anda meneruskan ketergantungan ke metode pembantu statis?
Ilian
1
@JackOfAllTrades, jika satu-satunya tujuan dari metode ini adalah untuk berurusan dengan sumber daya eksternal, hampir tidak ada titik menyuntikkan ketergantungan itu. Kalau tidak, itu bisa menjadi solusi (walaupun dalam kasus terakhir, metode ini mungkin melanggar Prinsip Tanggung Jawab Tunggal, dengan demikian merupakan kandidat untuk refactoring).
Péter Török
1
statika mudah 'diejek' - Microsoft Moles atau Fakes (tergantung, VS2010 atau VS2012 +) memungkinkan Anda untuk mengganti metode statis dengan implementasi Anda sendiri dalam pengujian Anda. Membuat kerangka kerja mengejek yang lama menjadi usang. (Itu juga melakukan ejekan tradisional juga, dan dapat mengolok-olok kelas beton juga). Ini gratis dari MS melalui manajer ekstensi.
gbjbaanb
4

Yang mana Anda lebih suka lakukan dalam kode Anda sendiri

Saya lebih suka # 1 ( this->DoSomethingWithBarImpl();), kecuali tentu saja metode helper tidak perlu akses ke data / implementasi instance static t_image OpenDefaultImage().

dan apa alasan Anda melakukannya?

antarmuka publik dan implementasi pribadi dan anggota terpisah. program akan jauh lebih sulit dibaca jika saya memilih # 2. dengan # 1, saya selalu memiliki anggota dan contoh tersedia. dibandingkan dengan banyak implementasi berbasis OO, saya cenderung menggunakan banyak encapsualtion, dan sangat sedikit anggota per kelas. sejauh efek samping - saya setuju dengan itu, sering ada validasi negara yang memperluas dependensi (misalnya argumen yang harus dilewati).

# 1 jauh lebih mudah untuk dibaca, dipelihara, dan memiliki sifat kinerja yang baik. tentu saja, akan ada kasus di mana Anda dapat membagikan implementasi:

static void ValidateImage(const t_image& image);
void validateImages() const {
    ValidateImage(this->innerImage());
    ValidateImage(this->outerImage());
}

Jika # 2 adalah solusi umum yang baik (misalnya default) dalam basis kode, saya akan khawatir tentang struktur desain: apakah kelas melakukan terlalu banyak? apakah tidak ada enkapsulasi yang cukup atau tidak cukup digunakan kembali? Apakah level abstraksinya cukup tinggi?

terakhir, # 2 tampaknya berlebihan jika Anda menggunakan bahasa OO di mana mutasi didukung (misalnya constmetode).

justin
sumber