Kelas dengan metode tunggal - pendekatan terbaik?


173

Katakanlah saya memiliki kelas yang dimaksudkan untuk melakukan fungsi tunggal. Setelah melakukan fungsi, itu bisa dihancurkan. Adakah alasan untuk memilih salah satu dari pendekatan ini?

// Initialize arguments in constructor
MyClass myObject = new MyClass(arg1, arg2, arg3);
myObject.myMethod();

// Pass arguments to method
MyClass myObject = new MyClass();
myObject.myMethod(arg1, arg2, arg3);

// Pass arguments to static method
MyClass.myMethod(arg1, arg2, arg3);

Saya sengaja tidak jelas tentang perinciannya, untuk mencoba mendapatkan panduan untuk situasi yang berbeda. Tapi aku tidak benar-benar ada dalam pikiran fungsi perpustakaan sederhana seperti Math.random (). Saya memikirkan lebih banyak kelas yang melakukan beberapa tugas spesifik dan kompleks, tetapi hanya memerlukan satu metode (publik) untuk melakukannya.

Jawaban:


264

Saya dulu suka kelas utilitas diisi dengan metode statis. Mereka melakukan konsolidasi besar metode penolong yang kalau tidak akan berbohong menyebabkan redundansi dan pemeliharaan neraka. Mereka sangat mudah digunakan, tidak ada instantiation, tidak ada pembuangan, hanya api 'jangan lupa. Saya kira ini adalah upaya tanpa disadari pertama saya dalam menciptakan arsitektur berorientasi layanan - banyak layanan tanpa kewarganegaraan yang baru saja melakukan pekerjaan mereka dan tidak ada yang lain. Namun ketika sistem tumbuh, naga akan datang.

Polimorfisme
Katakanlah kita memiliki metode UtilityClass.SomeMethod yang dengan senang hati berdengung. Tiba-tiba kita perlu sedikit mengubah fungsinya. Sebagian besar fungsinya sama, tetapi kami harus mengubah beberapa bagian. Seandainya itu bukan metode statis, kita bisa membuat kelas turunan dan mengubah isi metode sesuai kebutuhan. Karena ini adalah metode statis, kita tidak bisa. Tentu, jika kita hanya perlu menambahkan fungsionalitas sebelum atau setelah metode yang lama, kita dapat membuat kelas baru dan memanggil yang lama di dalamnya - tetapi itu hanya kotor.

Kesengsaraan antarmuka
Metode statis tidak dapat didefinisikan melalui antarmuka karena alasan logis. Dan karena kita tidak bisa mengesampingkan metode statis, kelas statis tidak berguna ketika kita perlu menyebarkannya melalui antarmuka mereka. Ini membuat kami tidak dapat menggunakan kelas statis sebagai bagian dari pola strategi. Kami mungkin memperbaiki beberapa masalah dengan mengirimkan delegasi alih-alih antarmuka .

Pengujian
Ini pada dasarnya berjalan seiring dengan kesengsaraan antarmuka yang disebutkan di atas. Karena kemampuan kami untuk menukarkan implementasi sangat terbatas, kami juga akan kesulitan mengganti kode produksi dengan kode uji. Sekali lagi, kita dapat membungkusnya tetapi itu akan mengharuskan kita untuk mengubah sebagian besar kode kita hanya untuk dapat menerima pembungkus daripada objek yang sebenarnya.

Menumbuhkan gumpalan
Karena metode statis biasanya digunakan sebagai metode utilitas dan metode utilitas biasanya akan memiliki tujuan yang berbeda, kita akan dengan cepat berakhir dengan kelas besar yang diisi dengan fungsionalitas yang tidak koheren - idealnya, setiap kelas harus memiliki satu tujuan dalam sistem. . Saya lebih suka memiliki lima kali kelas selama tujuan mereka didefinisikan dengan baik.

Parameter merayap
Pertama-tama, metode statis kecil yang lucu dan polos itu mungkin mengambil satu parameter. Saat fungsionalitas tumbuh, beberapa parameter baru ditambahkan. Segera parameter selanjutnya ditambahkan yang bersifat opsional, jadi kami membuat kelebihan metode (atau hanya menambahkan nilai default, dalam bahasa yang mendukungnya). Tak lama, kami memiliki metode yang mengambil 10 parameter. Hanya tiga yang pertama yang benar-benar diperlukan, parameter 4-7 adalah opsional. Tetapi jika parameter 6 ditentukan, 7-9 harus diisi juga ... Seandainya kita membuat kelas dengan tujuan tunggal melakukan apa yang dilakukan metode statis ini, kita bisa menyelesaikannya dengan mengambil parameter yang diperlukan di konstruktor, dan memungkinkan pengguna untuk menetapkan nilai opsional melalui properti, atau metode untuk mengatur beberapa nilai yang saling tergantung pada saat yang sama. Juga, jika suatu metode telah berkembang ke tingkat kompleksitas ini,

Menuntut konsumen untuk membuat instance kelas tanpa alasan
Salah satu argumen yang paling umum adalah, mengapa menuntut agar konsumen kelas kami membuat contoh untuk menggunakan metode tunggal ini, sementara setelah itu tidak menggunakan contoh tersebut? Membuat instance kelas adalah operasi yang sangat sangat murah di sebagian besar bahasa, jadi kecepatan bukanlah masalah. Menambahkan baris kode tambahan ke konsumen adalah biaya rendah untuk meletakkan fondasi solusi yang jauh lebih dapat dipertahankan di masa depan. Dan akhirnya, jika Anda ingin menghindari membuat instance, cukup buat pembungkus tunggal kelas Anda yang memungkinkan untuk digunakan kembali dengan mudah - meskipun ini membuat persyaratan bahwa kelas Anda adalah stateless. Jika tidak stateless, Anda masih bisa membuat metode pembungkus statis yang menangani semuanya, sambil tetap memberi Anda semua manfaat dalam jangka panjang. Akhirnya,

Hanya Sith yang membahas absolut.
Tentu saja, ada pengecualian untuk ketidaksukaan saya terhadap metode statis. Kelas utilitas sejati yang tidak menimbulkan risiko kembung adalah kasus yang sangat baik untuk metode statis - System.Convert sebagai contoh. Jika proyek Anda hanya sekali saja tanpa persyaratan untuk pemeliharaan di masa depan, arsitektur keseluruhan benar-benar tidak terlalu penting - statis atau non statis, tidak terlalu penting - kecepatan pengembangan memang demikian.

Standar, standar, standar!
Menggunakan metode instan tidak menghambat Anda untuk juga menggunakan metode statis, dan sebaliknya. Selama ada alasan di balik diferensiasi dan itu standar. Tidak ada yang lebih buruk daripada melihat lapisan bisnis yang luas dengan metode implementasi yang berbeda.


Seperti yang dikatakan Mark, polimorfisme, mampu meneruskan metode sebagai parameter 'strategi', dan mampu mengkonfigurasi / mengatur opsi adalah semua alasan untuk menggunakan instance . Sebagai contoh: ListDelimiter atau DelimiterParser dapat dikonfigurasikan untuk pembatas apa yang akan digunakan / menerima, apakah akan memangkas spasi putih dari token yang diuraikan, apakah akan mengurutkan daftar, bagaimana memperlakukan daftar kosong / nol .. dll.
Thomas W

4
"Ketika sebuah sistem tumbuh ..." Anda refactor?
Rodney Gitzel

3
@ user3667089 Argumen umum yang diperlukan agar kelas ada secara logis, yang akan saya sampaikan di konstruktor. Ini biasanya akan digunakan di sebagian besar / semua metode. Metode argumen spesifik yang saya sampaikan dalam metode khusus itu.
Mark S. Rasmussen

1
Benar-benar apa yang Anda lakukan dengan kelas statis akan kembali ke C mentah, tidak berorientasi objek (walaupun dikelola memori) - semua fungsi berada dalam ruang global, tidak ada this, Anda harus mengelola keadaan global dll. Jadi jika Anda suka pemrograman prosedural, lakukan ini. Tetapi hargai bahwa Anda kemudian kehilangan banyak manfaat struktural dari OO.
Insinyur

1
Sebagian besar alasan ini ada hubungannya dengan manajemen kode yang buruk, bukan dari menggunakan metode statis. Dalam F # dan bahasa fungsional lainnya kami menggunakan apa yang pada dasarnya adalah metode statis (fungsi tanpa instance-state) di mana-mana dan tidak memiliki masalah ini. Yang mengatakan, F # memberikan paradigma yang lebih baik untuk menggunakan fungsi (fungsi adalah kelas satu, fungsi dapat dikerjakan dan diterapkan sebagian) yang membuatnya lebih layak daripada di C #. Alasan yang lebih besar untuk menggunakan kelas adalah karena untuk itulah C # dibangun. Semua konstruksi Dependency Injection dalam .NET Core berputar di sekitar kelas instance dengan deps.
Justin J Stark

89

Saya lebih suka cara statis. Karena Kelas tidak mewakili suatu objek, tidak masuk akal untuk membuat instance darinya.

Kelas yang hanya ada untuk metode mereka harus dibiarkan statis.


19
-1 "Kelas yang hanya ada untuk metodenya harus dibiarkan statis."
Rookian

8
@Rookian kenapa kamu tidak setuju dengan itu?
jjnguy

6
contohnya adalah pola repositori. Kelas hanya berisi metode. Mengikuti pendekatan Anda tidak memungkinkan untuk menggunakan antarmuka. => tambah sambungan
Rookian

1
@Rook, secara umum orang tidak boleh membuat kelas yang hanya digunakan untuk metode. Dalam contoh yang Anda berikan metode statis bukan ide yang baik. Tetapi untuk metode utilitas sederhana cara statis adalah yang terbaik.
jjnguy

9
Benar sekali :) Dengan kondisi Anda "untuk metode utilitas sederhana" Saya setuju dengan Anda sepenuhnya, tetapi Anda memang membuat aturan umum dalam jawaban Anda yang imo salah.
Rookian

18

Jika tidak ada alasan untuk membuat instance kelas yang dibuat untuk menjalankan fungsi kemudian gunakan implementasi statis. Mengapa membuat konsumen kelas ini membuat contoh ketika seseorang tidak diperlukan.


16

Jika Anda tidak perlu menyelamatkan negara objek, maka tidak perlu membuat instantiate terlebih dahulu. Saya akan menggunakan metode statis tunggal yang Anda berikan parameternya.

Saya juga akan memperingatkan terhadap kelas Utils raksasa yang memiliki banyak metode statis yang tidak terkait. Ini bisa menjadi tidak teratur dan sulit dilakukan dengan terburu-buru. Lebih baik memiliki banyak kelas, masing-masing dengan sedikit, metode terkait.


6

Saya akan mengatakan format Metode Statis akan menjadi pilihan yang lebih baik. Dan saya akan membuat kelas menjadi statis juga, dengan begitu Anda tidak perlu khawatir tentang membuat instance kelas secara tidak sengaja.


5

Saya benar-benar tidak tahu apa situasinya di sini, tetapi saya akan melihat menempatkannya sebagai metode di salah satu kelas yang termasuk dalam arg1, arg2 atau arg3 - Jika Anda secara semantik dapat mengatakan bahwa salah satu dari kelas-kelas itu akan memiliki metode.


4

Saya menyarankan itu sulit dijawab berdasarkan informasi yang diberikan.

Perasaan saya adalah bahwa jika Anda hanya akan memiliki satu metode, dan bahwa Anda akan segera membuang kelas, maka buatlah kelas statis yang mengambil semua parameter.

Tentu saja, sulit untuk mengatakan dengan tepat mengapa Anda perlu membuat kelas tunggal hanya untuk metode yang satu ini. Apakah ini situasi "kelas Utilitas" seperti yang diasumsikan kebanyakan orang? Atau apakah Anda menerapkan semacam kelas aturan, yang mungkin ada lebih banyak di masa depan.

Sebagai contoh, minta kelas itu plugable. Kemudian Anda ingin membuat Interface untuk satu metode Anda, dan kemudian Anda ingin semua parameter dikirimkan ke antarmuka, daripada ke konstruktor, tetapi Anda tidak ingin itu menjadi statis.


3

Bisakah kelas Anda dibuat statis?

Jika demikian, maka saya akan menjadikannya kelas 'Utilities' dan saya akan memasukkan semua kelas satu-fungsi saya.


2
Saya agak tidak setuju. Dalam proyek yang saya terlibat, kelas Utilities Anda dapat memiliki ratusan metode yang tidak terkait di dalamnya.
Paul Tomblin

3
@ Paul: Secara umum disetujui. Saya benci melihat kelas dengan puluhan (atau ya, ratusan) metode yang sama sekali tidak terkait. Jika seseorang harus mengambil pendekatan ini, setidaknya membaginya menjadi set utilitas yang lebih kecil dan terkait (EG, FooUtilities, BarUtilities, dll).
John Rudy

9
satu kelas- satu tanggung jawab.
Andreas Petersson

3

Jika metode ini tidak memiliki kewarganegaraan dan Anda tidak perlu menyebarkannya, maka masuk akal untuk mendefinisikannya sebagai statis. Jika Anda perlu melewati metode ini, Anda dapat mempertimbangkan untuk menggunakan delegasi daripada salah satu pendekatan lain yang diusulkan.


3

Untuk aplikasi dan internalpembantu sederhana , saya akan menggunakan metode statis. Untuk aplikasi dengan komponen, saya menyukai Framework Ekstensibilitas Terkelola . Berikut adalah kutipan dari dokumen yang saya tulis untuk menjelaskan pola yang akan Anda temukan di API saya.

  • Jasa
    • Didefinisikan oleh I[ServiceName]Serviceantarmuka.
    • Diekspor dan diimpor oleh jenis antarmuka.
    • Implementasi tunggal disediakan oleh aplikasi host dan dikonsumsi secara internal dan / atau oleh ekstensi.
    • Metode pada antarmuka layanan aman-utas.

Sebagai contoh buat-buat:

public interface ISettingsService
{
    string ReadSetting(string name);

    void WriteSetting(string name, string value);
}

[Export]
public class ObjectRequiringSettings
{
    [Import]
    private ISettingsService SettingsService
    {
        get;
        set;
    }

    private void Foo()
    {
        if (SettingsService.ReadSetting("PerformFooAction") == bool.TrueString)
        {
            // whatever
        }
    }
}

2

Saya hanya akan melakukan segalanya di konstruktor. seperti itu:

new MyClass(arg1, arg2, arg3);// the constructor does everything.

atau

MyClass my_object(arg1, arg2, arg3);

Bagaimana jika Anda perlu mengembalikan apa pun selain objek bertipe MyClass?
Bill the Lizard

13
Saya menganggap itu praktik buruk untuk menempatkan logika eksekusi di konstruktor. Perkembangan yang baik adalah tentang semantik. Semantik, sebuah konstruktor ada untuk membangun objek, bukan untuk melakukan tugas-tugas lain.
mstrobl

tagihan, jika Anda membutuhkan nilai balik, berikan referensi ke nilai ret: MyClass myObject (arg1, arg2, arg3, retvalue); mstrobl, jika objek tidak diperlukan, mengapa membuatnya? dan trik ini sebenarnya dapat membantu Anda dalam beberapa kasus.

Jika suatu objek tidak memiliki keadaan, yaitu tidak ada vars, maka instancing itu tidak akan menghasilkan alokasi apa pun - baik pada heap maupun stack. Anda dapat menganggap objek di C ++ hanya sebagai panggilan fungsi C dengan parameter pertama tersembunyi yang menunjuk ke sebuah struct.
mstrobl

mstrobl, ini seperti fungsi ac pada steroid karena Anda dapat mendefinisikan fungsi pribadi yang dapat digunakan oleh ctor. Anda juga dapat menggunakan variabel anggota kelas untuk membantu meneruskan data ke fungsi pribadi.

0

Satu masalah yang lebih penting untuk dipertimbangkan adalah apakah sistem akan berjalan pada lingkungan multithreaded, dan apakah akan aman untuk memiliki metode atau variabel statis ...

Anda harus memperhatikan status sistem.


0

Anda mungkin dapat menghindari situasi ini bersama-sama. Cobalah refactor sehingga Anda dapatarg1.myMethod1(arg2, arg3) . Tukar arg1 dengan arg2 atau arg3 jika lebih masuk akal.

Jika Anda tidak memiliki kendali atas kelas arg1, maka hiaslah:

class Arg1Decorator
    private final T1 arg1;
    public Arg1Decorator(T1 arg1) {
        this.arg1 = arg1;
    }
    public T myMethod(T2 arg2, T3 arg3) {
        ...
    }
 }

 arg1d = new Arg1Decorator(arg1)
 arg1d.myMethod(arg2, arg3)

Alasannya adalah bahwa, dalam OOP, data dan metode memproses data milik bersama. Plus Anda mendapatkan semua keuntungan yang disebutkan Mark.


0

saya pikir, jika properti kelas Anda atau turunan kelas tidak akan digunakan dalam konstruktor atau metode Anda, metode tidak disarankan untuk dirancang seperti pola 'statis'. Metode statis harus selalu dipikirkan dengan cara 'bantuan'.


0

Bergantung pada apakah Anda ingin hanya melakukan sesuatu atau melakukan dan mengembalikan sesuatu yang dapat Anda lakukan ini:

public abstract class DoSomethingClass<T>
{
    protected abstract void doSomething(T arg1, T arg2, T arg3);
}

public abstract class ReturnSomethingClass<T, V>
{
    public T value;
    protected abstract void returnSomething(V arg1, V arg2, V arg3);
}

public class DoSomethingInt extends DoSomethingClass<Integer>
{
    public DoSomethingInt(int arg1, int arg2, int arg3)
    {
        doSomething(arg1, arg2, arg3);
    }

    @Override
    protected void doSomething(Integer arg1, Integer arg2, Integer arg3)
    {
        // ...
    }
}

public class ReturnSomethingString extends ReturnSomethingClass<String, Integer>
{
    public ReturnSomethingString(int arg1, int arg2, int arg3)
    {
        returnSomething(arg1, arg2, arg3);
    }

    @Override
    protected void returnSomething(Integer arg1, Integer arg2, Integer arg3)
    {
        String retValue;
        // ...
        value = retValue;
    }
}

public class MainClass
{
    static void main(String[] args)
    {
        int a = 3, b = 4, c = 5;

        Object dummy = new DoSomethingInt(a,b,c);  // doSomething was called, dummy is still around though
        String myReturn = (new ReturnSomethingString(a,b,c)).value; // returnSomething was called and immediately destroyed
    }
}
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.