Apakah itu melanggar prinsip OOP jika fungsi anggota tidak menggunakan properti kelas / variabel anggota?

8

Saya memiliki kelas yang sudah ada yang berinteraksi yang dapat membuka, membaca atau menulis ke file. Saya perlu mengambil modifikasi file untuk tujuan itu saya harus menambahkan metode baru

Misalkan berikut ini adalah definisi kelas saya di mana saya ingin menambahkan metode baru.

class IO_file
{
 std::string m_file_name;
 public:
 IO();
 IO(std::string file_name);

+ time_t get_mtime(file_name);
+       OR
+ time_t get_mtime();
};

Saya punya dua opsi -

  1. Membuat objek kosong dan kemudian meneruskan namafile dalam argumen metode yang saya ingin mengambil waktu modifikasi file.

  2. Lulus nama file pada konstruksi objek dan cukup panggil fungsi anggota yang akan beroperasi pada variabel anggota.

Kedua opsi akan melayani tujuan. Saya juga percaya bahwa pendekatan kedua lebih baik daripada yang pertama. Tapi yang tidak saya mengerti adalah Bagaimana ? Apakah pendekatan pertama desain yang buruk karena tidak menggunakan variabel anggota? Prinsip desain berorientasi objek mana yang dilanggar? Jika fungsi anggota tidak menggunakan variabel anggota, haruskah fungsi anggota itu selalu dibuat statis?

irsis
sumber
OOP adalah tentang memisahkan masalah, misalnya memisahkan klien dari implementasi melalui antarmuka - tetapi juga memisahkan pilihan implementasi dari penggunaan antarmuka, sementara juga memungkinkan beberapa implementasi yang berpotensi hidup berdampingan secara dinamis. Menggunakan contoh strategi @ CandiedOrange, pemilih strategi dan konsumen strategi dapat dipisahkan; sedangkan dengan metode statis, meskipun klien dan masalah implementasi terpisah, ada hubungan erat antara klien dengan pilihan implementasi (tunggal).
Erik Eidt
2
Ini desain buruk yang bisa saya lakukan IO("somefile.txt").get_mtime("someotherfile.txt"). Apa artinya itu?
user253751

Jawaban:

8

Apakah itu melanggar prinsip OOP jika fungsi anggota tidak menggunakan variabel properti / variabel anggota?

Tidak.

OOP tidak peduli jika fungsi anggota Anda menggunakan, atau tidak menggunakan, properti kelas atau variabel anggota. OOP peduli tentang polimorfisme dan bukan implementasi hard coding. Fungsi statis memiliki kegunaannya tetapi fungsi tidak boleh statis hanya karena tidak bergantung pada keadaan objek. Jika itu pemikiran Anda baik-baik saja, tetapi jangan menyalahkan OOP karena ide itu tidak berasal dari OOP.

Apakah desain yang buruk tidak memanfaatkan variabel anggota?

Jika Anda tidak perlu mengingat status dari panggilan ke panggilan, tidak ada alasan untuk menggunakan status tersebut.

Prinsip desain berorientasi objek mana yang dilanggar?

Tidak ada

Jika fungsi anggota tidak menggunakan variabel anggota maka fungsi anggota itu harus selalu dibuat statis?

Tidak. Pemikiran ini memiliki implikasi panah menuju ke arah yang salah.

  • Fungsi statis tidak dapat mengakses keadaan instance

  • Jika fungsi tidak perlu mengakses keadaan instance maka fungsi tersebut bisa statis atau non-statis

Menjadikan fungsi ini statis di sini sepenuhnya terserah Anda. Tapi itu akan membuatnya lebih seperti global jika Anda melakukannya. Sebelum menjadi statis, pertimbangkan untuk menjalankan fungsi di kelas tanpa kewarganegaraan. Ini lebih fleksibel.


Di sini saya memiliki contoh OOP dari fungsi anggota yang tidak menggunakan properti kelas atau variabel anggota.

Fungsi anggota (dan ini adalah kelas tanpa kewarganegaraan) :

#include <iostream>

class Strategy
{
public:
     virtual int execute (int a, int b) = 0; // execute() is a so-called pure virtual 
                                             // function. As a consequence, Strategy 
                                             // is a so-called abstract class.
};

 
Tiga implementasi yang berbeda:

class ConcreteStrategyAdd:public Strategy
{
public:
    int execute(int a, int b)
    {
        std::cout << "Called ConcreteStrategyAdd's execute()\n";
        return a + b;
    }
};

class ConcreteStrategySubstract:public Strategy
{
public:
    int execute(int a, int b)
    {
        std::cout << "Called ConcreteStrategySubstract's execute()\n";
        return a - b;
    }
};

class ConcreteStrategyMultiply:public Strategy
{
public:
    int execute(int a, int b)
    {
        std::cout << "Called ConcreteStrategyMultiply's execute()\n";
        return a * b;
    }
};

 
Tempat menyimpan pilihan implementasi:

class Context
{
private:
    Strategy* pStrategy;

public:

    Context (Strategy& strategy)
        : pStrategy(&strategy)
    {
    }

    void SetStrategy(Strategy& strategy)
    {
        pStrategy = &strategy;
    }

    int executeStrategy(int a, int b)
    {
        return pStrategy->execute(a,b);
    }
};

 
Contoh penggunaan

int main()
{
    ConcreteStrategyAdd       concreteStrategyAdd;
    ConcreteStrategySubstract concreteStrategySubstract;
    ConcreteStrategyMultiply  concreteStrategyMultiply;

    Context context(concreteStrategyAdd);
    int resultA = context.executeStrategy(3,4);

    context.SetStrategy(concreteStrategySubstract);
    int resultB = context.executeStrategy(3,4);

    context.SetStrategy(concreteStrategyMultiply);
    int resultC = context.executeStrategy(3,4);

    std::cout << "\nresultA: " << resultA 
              << "\nresultB: " << resultB 
              << "\nresultC: " << resultC 
              << "\n";
}

Output:

Called ConcreteStrategyAdd's execute()
Called ConcreteStrategySubstract's execute()
Called ConcreteStrategyMultiply's execute()

resultA: 7       
resultB: -1       
resultC: 12

Dan semua tanpa execute()mempedulikan keadaan objek apa pun. The Strategykelas sebenarnya stateless. Hanya status dalam Context. Objek stateless sangat baik di OOP.

Ditemukan kode ini di sini .

candied_orange
sumber
1
+1 Sehubungan dengan statika "Pemikiran ini memiliki panah implikasi menuju arah yang salah." sempurna. Saya dulu bekerja di sebuah perusahaan di mana jika suatu fungsi tidak perlu menyatakannya akan selalu statis, yang berarti sekitar 60% atau lebih (jika tidak lebih) dari aplikasi itu ternyata statis. Bicara tentang mimpi buruk.
Carson
Betulkah? Kami melakukan yang sebaliknya di perusahaan saya saat ini (dan saya menganjurkan untuk itu). Metode apa pun yang dapat dibuat statis adalah.
Gardenhead
@gardenhead Jika Anda ingin membuat argumen balasan, silakan buat. Memberitahu saya bahwa seluruh perusahaan melakukannya bukan berarti itu hal yang baik.
candied_orange
1
@candiedorange Saya setuju itu bukan bukti yang tepat, tapi saya tidak ingin terlibat dalam perang api. Tapi karena Anda bersikeras, inilah argumen balasan saya: OOP mengerikan dan semakin sedikit digunakan semakin baik. Senang?
Gardenhead
@gardenhead OOP mengerikan jadi statis itu bagus? Saya dapat menulis kode fungsional murni tanpa pernah menggunakan statika. Apakah Anda yakin tidak mencari nyala api?
candied_orange
5

get_mtimeakan lebih masuk akal di sini sebagai fungsi mandiri atau sebagai staticfungsi, daripada seperti yang Anda tunjukkan di sini.

The mtimefile dibaca, pada kebanyakan sistem, dari panggilan ke lstatatau serupa, dan tidak memerlukan file descriptor terbuka, sehingga tidak masuk akal untuk memilikinya sebagai fungsi anggota dari kelas instantiated.

greyfade
sumber
Karena mtime tidak perlu membuka deskriptor file, inilah alasan saya ingin membuatnya independen dari variabel anggota m_file_name. Oleh karena itu, ini membuatnya lebih statis di kelas yang saya mengerti bagian itu. Tapi saya ingin mengerti dari sudut akademis / teori dimana konsep OPP saya melanggar dengan opsi pertama.
irsis
2
@Rahul Tidak ada jawaban yang bagus untuk itu. Maksud saya, saya kira saya bisa mengatakan bahwa itu melanggar abstraksi dengan menyiratkan ketergantungan pada data instan, tapi bukan itu yang dimaksud abstraksi. Jujur, saya pikir Anda terlalu khawatir tentang mengapa. Anggap saja sebagai aturan umum bahwa jika fungsi anggota tidak perlu mengakses sebuah instance, maka itu seharusnya bukan non- staticanggota.
greyfade
1
+1 tetapi ya praktis saya tahu itu bukan ide yang baik tetapi saya ingin menjelaskan pengertian saya "mengapa"? Jawaban CandidOrange menjelaskannya.
irsis
2

Alasan bahwa opsi kedua tampaknya secara naluriah lebih baik (dan, IMO, IS lebih baik) adalah karena opsi pertama Anda memberi Anda objek yang sebenarnya tidak mewakili apa pun.

Dengan tidak menentukan nama file, kelas IO_file Anda benar-benar hanya sebuah objek abstrak yang kebetulan menyerupai file konkret. Jika Anda memasukkan nama file ketika Anda memanggil metode, Anda mungkin juga hanya refactor seluruh metode menjadi fungsi murni mengambang bebas sebagai gantinya; tidak ada manfaat sebenarnya untuk menjaganya terikat dengan objek yang Anda perlukan untuk instantiate hanya untuk menggunakannya. Itu hanya fungsi; pelat objek objek hanya langkah tambahan yang tidak nyaman.

Di sisi lain, setelah nama file diberikan metode apa pun yang Anda panggil pada objek yang sedang seperti pertanyaan tentang contoh tertentu dari suatu hal. Itu lebih baik OO karena objek Anda memiliki makna aktual dan, karenanya, utilitas.

moberemk
sumber
2

Mari kita terjemahkan ini ke C. Pertama kita kelas kita - sekarang ini sebuah struct:

struct IO_file {
    char* m_file_name;
};

Mari, untuk kesederhanaan cuplikan, tentukan fungsi konstruktor (mengabaikan kebocoran memori untuk saat ini):

struct IO_file* IO_file(char* file_name) {
    struct IO_file* obj = malloc(sizeof(struct IO_file));
    obj.m_file_name = file_name;
    return obj;
}

Opsi # 2 terlihat seperti ini:

time_t get_mtime(struct IO_file*);

dan digunakan seperti ini:

time_t mtime = get_mtime(IO_file("some-file"));

Bagaimana dengan opsi # 1? Yah, kelihatannya seperti ini:

time_t get_mtime(struct IO_file* this, char* file_name);

Dan bagaimana ini digunakan? Pada dasarnya, Anda meminta untuk membuang sampah sebagai parameter pertama:

time_t mtime = get_mtime((struct IO_file*)1245151325, "some-file");

Tidak masuk akal, bukan?

Sistem objek C ++ menyembunyikannya, tetapi objek tersebut juga merupakan argumen dari metode - argumen pointer implisit bernama this. Ini adalah pilihan # 1 yang buruk - ia memiliki argumen yang tidak digunakan oleh definisi (argumen yang kebetulan tidak digunakan, tetapi dapat digunakan dalam penggantian OK). Argumen semacam itu tidak memberikan kontribusi apa pun saat menyulitkan tanda tangan, definisi, dan penggunaan fungsi serta membingungkan pembaca kode yang dibiarkan merenungkan apa yang seharusnya dilakukan oleh argumen, mengapa boleh saja membuang sampah ke dalamnya, dan terlepas dari kenyataan bahwa Anda atau tidak. meneruskan objek sampah / NULL/ kosong ke sana adalah penyebab bug yang mereka coba selesaikan. Spoiler - tidak, tetapi mereka masih akan membuang waktu mengeksplorasi kemungkinan itu.

Fakta bahwa argumen sampah implisit dapat mengurangi efeknya, tetapi masih ada, dan dan mudah dihindari dengan membuat metode static.

Idan Arye
sumber