Apakah menggunakan banyak metode statis adalah hal yang buruk?

97

Saya cenderung mendeklarasikan sebagai statis semua metode di kelas ketika kelas itu tidak perlu melacak status internal. Misalnya, jika saya perlu mengubah A menjadi B dan tidak bergantung pada beberapa status internal C yang mungkin bervariasi, saya membuat transformasi statis. Jika ada keadaan internal C yang ingin saya sesuaikan, maka saya menambahkan konstruktor untuk menyetel C dan tidak menggunakan transformasi statis.

Saya membaca berbagai rekomendasi (termasuk di StackOverflow) TIDAK untuk terlalu sering menggunakan metode statis tetapi saya masih gagal memahami apa yang salah dengan aturan praktis di atas.

Apakah itu pendekatan yang masuk akal atau tidak?

Lolo
sumber

Jawaban:

152

Ada dua jenis metode statis yang umum:

  • Metode statis "aman" akan selalu memberikan keluaran yang sama untuk masukan yang sama. Ini tidak memodifikasi global dan tidak memanggil metode statis "tidak aman" dari kelas mana pun. Pada dasarnya, Anda menggunakan jenis pemrograman fungsional terbatas - jangan takut dengan ini, mereka baik-baik saja.
  • Metode statis "tidak aman" mengubah status global, atau proksi ke objek global, atau perilaku lain yang tidak dapat diuji. Ini adalah kemunduran untuk pemrograman prosedural dan harus difaktorkan ulang jika memungkinkan.

Ada beberapa penggunaan umum dari statika "tidak aman" - misalnya, dalam pola Singleton - tetapi ketahuilah bahwa meskipun Anda memanggilnya dengan nama cantik, Anda hanya mengubah variabel global. Pikirkan baik-baik sebelum menggunakan statika yang tidak aman.

John Millikin
sumber
Inilah masalah yang harus saya selesaikan - penggunaan, atau lebih tepatnya penyalahgunaan, objek Singleton.
overslacked
Terima kasih atas jawaban yang sangat bagus itu. Pertanyaan saya adalah, jika lajang diteruskan sebagai parameter ke metode statis, apakah itu membuat metode statis tidak aman?
Tony D
1
Istilah "fungsi murni" dan "fungsi tidak murni" adalah nama yang diberikan dalam pemrograman fungsional untuk apa yang Anda sebut statika "aman" dan "tidak aman".
Omnimike
19

Objek tanpa keadaan internal adalah hal yang mencurigakan.

Biasanya, objek merangkum status dan perilaku. Objek yang hanya merangkum perilaku adalah ganjil. Terkadang itu adalah contoh Lightweight atau Flyweight .

Di lain waktu, itu desain prosedural dilakukan dalam bahasa objek.

S. Lott
sumber
6
Saya mendengar apa yang Anda katakan, tetapi bagaimana sesuatu seperti objek Matematika merangkum apa pun kecuali perilaku?
JonoW
10
Dia hanya mengatakan curiga, tidak salah, dan dia benar sekali.
Bill K
2
@JonoW: Matematika adalah kasus yang sangat spesial dimana ada banyak fungsi stateless. Tentu saja, jika Anda melakukan pemrograman Fungsional di Java, Anda akan memiliki banyak fungsi stateless.
S. Lott
13

Ini benar-benar hanya tindak lanjut dari jawaban hebat John Millikin.


Meskipun aman untuk membuat metode stateless (yang memiliki banyak fungsi) statis, terkadang dapat menyebabkan kopling yang sulit untuk dimodifikasi. Anggap Anda memiliki metode statis seperti:

public class StaticClassVersionOne {
    public static void doSomeFunkyThing(int arg);
}

Yang Anda sebut sebagai:

StaticClassVersionOne.doSomeFunkyThing(42);

Yang mana semuanya baik dan bagus, dan sangat nyaman, sampai Anda menemukan kasus di mana Anda harus mengubah perilaku metode statis, dan menemukan bahwa Anda terikat erat StaticClassVersionOne . Mungkin Anda dapat mengubah kode dan itu akan baik-baik saja, tetapi jika ada penelepon lain yang bergantung pada perilaku lama, mereka harus diperhitungkan dalam isi metode. Dalam beberapa kasus, metode tubuh bisa menjadi sangat jelek atau tidak dapat dipertahankan jika mencoba untuk menyeimbangkan semua perilaku ini. Jika Anda membagi metode, Anda mungkin harus mengubah kode di beberapa tempat untuk memperhitungkannya, atau membuat panggilan ke kelas baru.

Tetapi pertimbangkan jika Anda telah membuat antarmuka untuk menyediakan metode, dan memberikannya kepada pemanggil, sekarang ketika perilakunya harus diubah, kelas baru dapat dibuat untuk mengimplementasikan antarmuka, yang lebih bersih, lebih mudah diuji, dan lebih dapat dipelihara, dan itu diberikan kepada penelepon. Dalam skenario ini, kelas pemanggil tidak perlu diubah atau bahkan dikompilasi ulang, dan perubahan tersebut dilokalkan.

Ini mungkin atau mungkin bukan situasi yang mungkin terjadi, tetapi saya pikir itu layak dipertimbangkan.

Grundlefleck
sumber
5
Saya berpendapat bahwa ini bukan hanya skenario yang mungkin terjadi, ini membuat statika menjadi pilihan terakhir. Statika membuat TDD menjadi mimpi buruk juga. Di mana pun Anda menggunakan statis, Anda tidak dapat membuat tiruan, Anda harus tahu apa input dan outputnya untuk menguji kelas yang tidak terkait. Sekarang, jika Anda mengubah perilaku statis, pengujian Anda pada kelas yang tidak terkait yang menggunakan statis tersebut akan rusak. Selain itu, ini menjadi dependensi tersembunyi yang tidak dapat Anda teruskan pada konstruktor untuk memberi tahu developer tentang dependensi yang berpotensi penting.
DanCaveman
6

Opsi lainnya adalah menambahkannya sebagai metode non-statis pada objek asal:

yaitu, mengubah:

public class BarUtil {
    public static Foo transform(Bar toFoo) { ... }
}

ke

public class Bar {
    ...
    public Foo transform() { ...}
}

namun dalam banyak situasi hal ini tidak mungkin (misalnya, pembuatan kode kelas reguler dari XSD / WSDL / dll), atau akan membuat kelas menjadi sangat panjang, dan metode transformasi seringkali dapat menjadi masalah yang nyata untuk objek yang kompleks dan Anda hanya menginginkannya di kelas mereka sendiri yang terpisah. Jadi ya, saya memiliki metode statis di kelas utilitas.

JeeBee
sumber
5

Kelas statis tidak masalah asalkan digunakan di tempat yang tepat.

Yaitu: Metode yang merupakan metode 'daun' (mereka tidak mengubah status, mereka hanya mengubah input). Contoh bagusnya adalah seperti Path.Combine. Hal-hal semacam ini berguna dan membuat sintaks yang lebih pendek.

The masalah yang saya miliki dengan statika banyak:

Pertama, jika Anda memiliki kelas statis, dependensi disembunyikan. Pertimbangkan hal berikut:

public static class ResourceLoader
{
    public static void Init(string _rootPath) { ... etc. }
    public static void GetResource(string _resourceName)  { ... etc. }
    public static void Quit() { ... etc. }
}

public static class TextureManager
{
    private static Dictionary<string, Texture> m_textures;

    public static Init(IEnumerable<GraphicsFormat> _formats) 
    {
        m_textures = new Dictionary<string, Texture>();

        foreach(var graphicsFormat in _formats)
        {
              // do something to create loading classes for all 
              // supported formats or some other contrived example!
        }
    }

    public static Texture GetTexture(string _path) 
    {
        if(m_textures.ContainsKey(_path))
            return m_textures[_path];

        // How do we know that ResourceLoader is valid at this point?
        var texture = ResourceLoader.LoadResource(_path);
        m_textures.Add(_path, texture);
        return texture; 
    }

    public static Quit() { ... cleanup code }       
}

Melihat TextureManager, Anda tidak dapat mengetahui langkah-langkah inisialisasi apa yang harus dilakukan dengan melihat konstruktor. Anda harus mempelajari kelas untuk menemukan dependensinya dan menginisialisasi hal-hal dalam urutan yang benar. Dalam kasus ini, ResourceLoader harus diinisialisasi sebelum dijalankan. Sekarang tingkatkan mimpi buruk ketergantungan ini dan Anda mungkin bisa menebak apa yang akan terjadi. Bayangkan mencoba mempertahankan kode di mana tidak ada urutan inisialisasi yang eksplisit. Bandingkan ini dengan injeksi ketergantungan dengan instance - dalam hal ini kode bahkan tidak dapat dikompilasi jika dependensi tidak terpenuhi!

Lebih jauh lagi, jika Anda menggunakan statika yang mengubah status, ini seperti rumah kartu. Anda tidak pernah tahu siapa yang memiliki akses ke apa, dan desainnya cenderung menyerupai monster spaghetti.

Akhirnya, dan sama pentingnya, menggunakan statika mengikat program ke implementasi tertentu. Kode statis adalah antitesis dari perancangan untuk kemudahan pengujian. Menguji kode yang penuh dengan statika adalah mimpi buruk. Panggilan statis tidak pernah dapat ditukar dengan pengujian ganda (kecuali Anda menggunakan framework pengujian yang dirancang khusus untuk meniru jenis statis), sehingga sistem statis menyebabkan semua yang menggunakannya menjadi pengujian integrasi instan.

Singkatnya, statika baik-baik saja untuk beberapa hal dan untuk alat kecil atau kode sekali pakai, saya tidak akan melarang penggunaannya. Namun, di luar itu, mereka adalah mimpi buruk berdarah untuk perawatan, desain yang baik, dan kemudahan pengujian.

Berikut artikel bagus tentang masalah tersebut: http://gamearchitect.net/2008/09/13/an-anatomy-of-despair-managers-and-contexts/

Mark Simpson
sumber
4

Alasan Anda diperingatkan untuk menjauhi metode statis adalah karena menggunakannya akan menghilangkan salah satu keuntungan objek. Objek dimaksudkan untuk enkapsulasi data. Ini mencegah efek samping tak terduga terjadi yang menghindari bug. Metode statis tidak memiliki data yang dienkapsulasi * jadi jangan mengumpulkan manfaat ini.

Meskipun demikian, jika Anda tidak menggunakan data internal, data tersebut baik-baik saja untuk digunakan dan sedikit lebih cepat untuk dieksekusi. Pastikan Anda tidak menyentuh data global di dalamnya.

  • Beberapa bahasa juga memiliki variabel tingkat kelas yang memungkinkan untuk enkapsulasi data dan metode statis.
Steve Rowe
sumber
4

Tampaknya itu pendekatan yang masuk akal. Alasan Anda tidak ingin menggunakan terlalu banyak kelas / metode statis adalah karena Anda akhirnya beralih dari pemrograman berorientasi objek dan lebih banyak lagi ke bidang pemrograman terstruktur.

Dalam kasus Anda di mana Anda hanya mengubah A ke B, katakan semua yang kita lakukan adalah mengubah teks menjadi asal

"hello" =>(transform)=> "<b>Hello!</b>"

Maka metode statis akan masuk akal.

Namun, jika Anda sering memanggil metode statis ini pada suatu objek dan cenderung unik untuk banyak panggilan (misalnya cara Anda menggunakannya bergantung pada input), atau itu adalah bagian dari perilaku inheren objek, itu akan bijaksana untuk menjadikannya bagian dari objek dan mempertahankan keadaannya. Salah satu cara untuk melakukannya adalah dengan mengimplementasikannya sebagai antarmuka.

class Interface{
    method toHtml(){
        return transformed string (e.g. "<b>Hello!</b>")
    }

    method toConsole(){
        return transformed string (e.g. "printf Hello!")
    }
}


class Object implements Interface {
    mystring = "hello"

    //the implementations of the interface would yield the necessary 
    //functionality, and it is reusable across the board since it 
    //is an interface so... you can make it specific to the object

   method toHtml()
   method toConsole()
}

Sunting: Salah satu contoh bagus dari penggunaan metode statis yang hebat adalah metode helper html di Asp.Net MVC atau Ruby. Mereka membuat elemen html yang tidak terikat dengan perilaku objek, dan karena itu bersifat statis.

Sunting 2: Pemrograman fungsional berubah menjadi pemrograman terstruktur (untuk beberapa alasan saya bingung), props ke Torsten untuk menunjukkannya.

MunkiPhD
sumber
2
Saya tidak berpikir menggunakan metode statis memenuhi syarat sebagai pemrograman fungsional, jadi saya rasa yang Anda maksud adalah pemrograman terstruktur.
Torsten Marek
3

Saya baru-baru ini merefaktor aplikasi untuk menghapus / memodifikasi beberapa kelas yang awalnya diimplementasikan sebagai kelas statis. Seiring waktu, kelas-kelas ini memperoleh begitu banyak dan orang-orang terus menandai fungsi baru sebagai statis, karena tidak pernah ada instance yang mengambang.

Jadi, jawaban saya adalah bahwa kelas statis tidak buruk secara inheren, tetapi mungkin lebih mudah untuk mulai membuat instance sekarang, kemudian harus melakukan refaktorisasi nanti.

overslacked
sumber
3

Saya akan menganggapnya sebagai bau desain. Jika Anda menemukan diri Anda sebagian besar menggunakan metode statis, Anda mungkin tidak memiliki desain OO yang sangat baik. Itu tidak selalu buruk, tetapi seperti semua bau itu akan membuat saya berhenti dan mengevaluasi kembali. Ini mengisyaratkan bahwa Anda mungkin dapat membuat desain OO yang lebih baik, atau mungkin Anda harus pergi ke arah lain dan menghindari OO sepenuhnya untuk masalah ini.

patros
sumber
2

Saya biasa bolak-balik antara kelas dengan banyak metode statis dan tunggal. Keduanya memecahkan masalah, tetapi singleton dapat dengan mudah diganti dengan lebih dari satu. (Pemrogram selalu tampak begitu yakin bahwa hanya akan ada 1 dari sesuatu dan saya menemukan diri saya cukup sering salah untuk sepenuhnya menyerah pada metode statis kecuali dalam beberapa kasus yang sangat terbatas).

Bagaimanapun, singleton memberi Anda kemampuan untuk meneruskan sesuatu ke pabrik nanti untuk mendapatkan contoh yang berbeda dan yang mengubah perilaku seluruh program Anda tanpa refactoring. Mengubah kelas global metode statis menjadi sesuatu dengan data "dukungan" yang berbeda atau perilaku yang sedikit berbeda (kelas anak) adalah masalah besar.

Dan metode statis tidak memiliki keunggulan serupa.

Jadi ya, mereka jahat.

Bill K
sumber
1

Selama keadaan internal tidak ikut bermain, ini bagus. Perhatikan bahwa biasanya metode statis diharapkan aman untuk thread, jadi jika Anda menggunakan struktur data helper, gunakan metode tersebut dengan cara yang aman untuk thread.

Lucero
sumber
1

Jika Anda tahu Anda tidak perlu menggunakan kondisi internal C, tidak apa-apa. Namun, jika itu berubah di masa depan, Anda harus membuat metode ini non-statis. Jika non-statis untuk memulai, Anda bisa mengabaikan status internal jika Anda tidak membutuhkannya.

Adam Crume
sumber
1

Jika ini adalah metode utilitas, sebaiknya buat statis. Guava dan Apache Commons dibangun berdasarkan prinsip ini.

Pendapat saya tentang ini murni pragmatis. Jika ini adalah kode aplikasi Anda, metode statis umumnya bukan yang terbaik untuk dimiliki. Metode statis memiliki batasan pengujian unit yang serius - metode tersebut tidak dapat dengan mudah dipermainkan: Anda tidak dapat memasukkan fungsionalitas statis yang dibuat-buat ke beberapa pengujian lainnya. Anda juga biasanya tidak dapat memasukkan fungsionalitas ke dalam metode statis.

Jadi dalam logika aplikasi saya, saya biasanya memiliki panggilan metode kecil seperti utilitas statis. Yaitu

static cutNotNull(String s, int length){
  return s == null ? null : s.substring(0, length);
}

salah satu keuntungannya adalah saya tidak menguji metode seperti itu :-)

Andrey Chaschev
sumber
1

Ya, tidak ada peluru perak tentunya. Kelas statis baik untuk utilitas / pembantu kecil. Tetapi menggunakan metode statis untuk pemrograman logika bisnis tentu saja jahat. Perhatikan kode berikut

   public class BusinessService
   {

        public Guid CreateItem(Item newItem, Guid userID, Guid ownerID)
        {
            var newItemId = itemsRepository.Create(createItem, userID, ownerID);
            **var searchItem = ItemsProcessor.SplitItem(newItem);**
            searchRepository.Add(searchItem);
            return newItemId;
        }
    }

Anda melihat panggilan metode statis ke ItemsProcessor.SplitItem(newItem);Penyebab bau

  • Anda tidak memiliki dependensi eksplisit yang dideklarasikan dan jika Anda tidak menggali kode, Anda mungkin mengabaikan penggabungan antara class Anda dan container metode statis
  • Anda tidak dapat menguji BusinessServicemengisolasinya ItemsProcessor(sebagian besar alat pengujian tidak meniru kelas statis) dan pengujian unit tidak mungkin dilakukan. Tidak Ada Tes Unit == kualitas rendah
Konstantin Chernov
sumber
0

Metode statis umumnya merupakan pilihan yang buruk bahkan untuk kode tanpa negara. Alih-alih, buat kelas tunggal dengan metode ini yang dibuat instance-nya sekali dan dimasukkan ke dalam kelas yang ingin menggunakan metode tersebut. Kelas semacam itu lebih mudah untuk diejek dan diuji. Mereka jauh lebih berorientasi pada objek. Anda dapat membungkusnya dengan proxy saat diperlukan. Statika membuat OO lebih sulit dan saya tidak melihat alasan untuk menggunakannya di hampir semua kasus. Tidak 100% tapi hampir semuanya.

John
sumber