Haruskah kemampuan objek diidentifikasi secara eksklusif oleh antarmuka yang diimplementasikan?

36

Operator C # isdan operator Java instanceofmemungkinkan Anda untuk bercabang pada antarmuka (atau, lebih kasarnya, kelas dasarnya) yang telah diterapkan oleh instance objek.

Apakah pantas untuk menggunakan fitur ini untuk percabangan tingkat tinggi berdasarkan kemampuan yang disediakan oleh antarmuka?

Atau haruskah kelas dasar menyediakan variabel boolean untuk menyediakan antarmuka untuk menggambarkan kemampuan yang dimiliki objek?

Contoh:

if (account is IResetsPassword)
       ((IResetsPassword)account).ResetPassword();
else
       Print("Not allowed to reset password with this account type!");

vs.

if (account.CanResetPassword)
       ((IResetsPassword)account).ResetPassword();
else
       Print("Not allowed to reset password with this account type!");

Apakah ada kesulitan untuk menggunakan implementasi antarmuka untuk identifikasi kemampuan?


Contoh ini hanya itu, contoh. Saya bertanya-tanya tentang aplikasi yang lebih umum.

TheCatWhisperer
sumber
10
Lihatlah dua contoh kode Anda. Mana yang lebih mudah dibaca?
Robert Harvey
39
Hanya pendapat saya, tapi saya akan memilih yang pertama hanya karena itu memastikan Anda dapat dengan aman menggunakan tipe yang sesuai. Yang kedua mengasumsikan bahwa CanResetPasswordhanya akan benar ketika sesuatu diterapkan IResetsPassword. Sekarang Anda membuat properti menentukan sesuatu yang harus dikontrol oleh sistem tipe (dan pada akhirnya akan ketika Anda membentuk sebelumnya para pemain).
Becuzz
10
@nocomprende: xkcd.com/927
Robert Harvey
14
Judul pertanyaan dan badan pertanyaan Anda menanyakan hal-hal yang berbeda. Untuk menjawab judul: Idealnya, ya. Untuk mengatasi tubuh: Jika Anda menemukan diri Anda memeriksa tipe dan melakukan casting secara manual, Anda mungkin tidak memanfaatkan sistem tipe Anda dengan benar. Bisakah Anda memberi tahu kami secara lebih spesifik bagaimana Anda menemukan diri Anda dalam situasi ini?
MetaFight
9
Pertanyaan ini mungkin tampak sederhana, tetapi tumbuh cukup luas ketika Anda mulai memikirkannya. Pertanyaan yang muncul di benak saya: Apakah Anda berharap untuk menambahkan perilaku baru ke akun yang ada atau membuat jenis akun dengan perilaku yang berbeda? Apakah semua tipe akun memiliki perilaku yang sama atau ada perbedaan besar di antara mereka? Apakah kode contoh tahu tentang semua jenis akun atau hanya antarmuka IAccount dasar?
Euforia

Jawaban:

7

Dengan peringatan bahwa yang terbaik adalah menghindari downcasting jika memungkinkan, maka bentuk pertama lebih disukai karena dua alasan:

  1. Anda membiarkan sistem tipe berfungsi untuk Anda. Pada contoh pertama, jika iskebakaran maka downcast pasti akan berfungsi. Di formulir kedua, Anda perlu memastikan secara manual bahwa hanya objek yang mengimplementasikan IResetsPasswordreturn true untuk properti itu
  2. kelas dasar yang rapuh. Anda perlu menambahkan properti ke kelas dasar / antarmuka untuk setiap antarmuka yang ingin Anda tambahkan. Ini rumit dan rentan kesalahan.

Itu bukan untuk mengatakan Anda tidak bisa memperbaiki masalah ini. Anda bisa, misalnya, memiliki kelas dasar memiliki satu set antarmuka yang diterbitkan yang dapat Anda periksa inklusi. Tapi Anda benar-benar hanya secara manual menerapkan sebagian dari sistem tipe yang ada yang berlebihan.

BTW preferensi alami saya adalah komposisi daripada warisan tetapi sebagian besar berkaitan dengan negara. Warisan antarmuka tidak seburuk biasanya. Juga, jika Anda menemukan diri Anda menerapkan hierarki tipe orang miskin, Anda lebih baik menggunakan yang sudah ada karena kompiler akan membantu Anda.

Alex
sumber
Saya juga sangat suka mengetik komposisi. +1 untuk membuat saya melihat bahwa contoh khusus ini tidak memanfaatkannya dengan benar. Meskipun, saya akan mengatakan mengetik komputasi (seperti warisan tradisional) lebih terbatas ketika kemampuan sangat bervariasi (sebagai lawan dari bagaimana mereka diimplementasikan). Oleh karena itu pendekatan interfacing, yang memecahkan masalah yang berbeda dari pengetikan komposisi atau pewarisan standar.
TheCatWhisperer
Anda dapat menyederhanakan pemeriksaan seperti: (account as IResettable)?.ResetPassword();atauvar resettable = account as IResettable; if(resettable != null) {resettable.ResetPassword()} else {....}
Berin Loritsch
89

Haruskah kemampuan objek diidentifikasi secara eksklusif oleh antarmuka yang diimplementasikan?

Kemampuan suatu objek tidak boleh diidentifikasi sama sekali.

Klien yang menggunakan objek tidak harus diminta untuk mengetahui apa pun tentang cara kerjanya. Klien seharusnya hanya mengetahui hal-hal yang bisa dilakukan objek. Apa yang dilakukan objek, setelah diberitahu, bukanlah masalah klien.

Jadi, bukannya

if (account is IResetsPassword)
    ((IResetsPassword)account).ResetPassword();
else
    Print("Not allowed to reset password with this account type!");

atau

if (account.CanResetPassword)
    ((IResetsPassword)account).ResetPassword();
else
    Print("Not allowed to reset password with this account type!");

mempertimbangkan

account.ResetPassword();

atau

account.ResetPassword(authority);

karena accountsudah tahu kalau ini akan berhasil. Kenapa bertanya itu? Katakan saja apa yang Anda inginkan dan biarkan ia melakukan apa pun yang akan dilakukan.

Bayangkan ini dilakukan sedemikian rupa sehingga klien tidak peduli apakah itu berhasil atau tidak karena itu adalah masalah lain. Pekerjaan klien hanya untuk melakukan upaya. Sudah dilakukan sekarang dan ada hal-hal lain yang harus dihadapi. Gaya ini memiliki banyak nama tetapi nama yang paling saya sukai adalah kirim, jangan tanya .

Sangat menggoda ketika menulis klien untuk berpikir Anda harus melacak semuanya dan menarik semua yang Anda pikir perlu Anda ketahui. Ketika Anda melakukan itu, Anda mengubah objek ke dalam. Hargai ketidaktahuan Anda. Dorong detailnya dan biarkan benda itu menanganinya. Semakin sedikit Anda tahu yang lebih baik.

candied_orange
sumber
54
Polimorfisme hanya berfungsi ketika saya tidak tahu atau tidak peduli dengan apa yang saya bicarakan. Jadi cara saya menghadapi situasi itu adalah dengan hati-hati menghindarinya.
candied_orange
7
Jika klien benar-benar perlu tahu apakah upaya itu berhasil, metode ini dapat mengembalikan boolean. if (!account.AttemptPasswordReset()) /* attempt failed */;Dengan cara ini klien mengetahui hasilnya tetapi menghindari beberapa masalah dengan logika keputusan dalam pertanyaan. Jika upaya ini tidak sinkron, Anda akan menerima panggilan balik.
Radiodef
5
@ DavidZ Kami berdua berbicara bahasa Inggris. Jadi saya dapat memberitahu Anda untuk mengunggah komentar ini dua kali. Apakah itu berarti Anda mampu melakukannya? Apakah saya perlu tahu jika Anda bisa sebelum saya dapat memberitahu Anda untuk melakukannya?
candied_orange
5
@QPaysTaxes untuk semua yang Anda tahu penangan kesalahan dilewatkan accountketika dibangun. Popup, logging, printouts, dan peringatan audio dapat terjadi pada saat ini. Tugas klien adalah mengatakan "lakukan sekarang". Tidak harus melakukan lebih dari itu. Anda tidak harus melakukan semuanya di satu tempat.
candied_orange
6
@QPaysTaxes Itu bisa ditangani di tempat lain dan dalam beberapa cara berbeda, itu berlebihan untuk percakapan ini dan hanya akan memperkeruh air.
Tom.Bowen89
17

Latar Belakang

Warisan adalah alat yang ampuh yang melayani tujuan dalam pemrograman berorientasi objek. Namun, itu tidak menyelesaikan setiap masalah dengan elegan: kadang-kadang, solusi lain lebih baik.

Jika Anda berpikir kembali ke kelas awal ilmu komputer Anda (dengan asumsi Anda memiliki gelar CS), Anda mungkin ingat seorang profesor memberi Anda paragraf yang menyatakan apa yang pelanggan inginkan untuk dilakukan oleh perangkat lunak. Tugas Anda adalah membaca paragraf, mengidentifikasi aktor dan tindakan, dan keluar dengan garis besar kasar tentang apa kelas dan metode itu. Akan ada beberapa lead gelandangan di sana yang terlihat seperti mereka penting, tetapi tidak. Ada kemungkinan yang sangat nyata untuk salah menafsirkan persyaratan.

Ini adalah keterampilan penting yang bahkan salah satu dari kita yang paling berpengalaman pun salah: mengidentifikasi persyaratan dengan tepat dan menerjemahkannya ke dalam bahasa mesin.

Pertanyaanmu

Berdasarkan apa yang telah Anda tulis, saya pikir Anda mungkin salah memahami kasus umum tindakan opsional yang dapat dilakukan oleh kelas. Ya, saya tahu kode Anda hanyalah contoh dan Anda tertarik dengan kasus umum. Namun, sepertinya Anda ingin tahu cara menangani situasi di mana subtipe tertentu dari suatu objek dapat melakukan suatu tindakan, tetapi subtipe lainnya tidak bisa.

Hanya karena objek seperti accountmemiliki jenis akun tidak berarti yang diterjemahkan ke dalam jenis dalam bahasa OO. "Ketik" dalam bahasa manusia tidak selalu berarti "kelas." Dalam konteks akun, "ketik" mungkin lebih dekat berkorelasi dengan "set izin." Anda ingin menggunakan akun pengguna untuk melakukan suatu tindakan, tetapi tindakan itu mungkin atau mungkin tidak dapat dilakukan oleh akun itu. Daripada menggunakan warisan, saya akan menggunakan token delegasi atau keamanan.

Solusi saya

Pertimbangkan kelas akun yang memiliki beberapa tindakan opsional yang dapat dilakukan. Alih-alih mendefinisikan "dapat melakukan tindakan X" melalui warisan, mengapa akun tidak mengembalikan objek yang didelegasikan (penyetel ulang kata sandi, submitter formulir, dll) atau token akses?

account.getPasswordResetter().doAction();
account.getFormSubmitter().doAction(view.getContents());

AccountManager.resetPassword(account, account.getAccessToken());

Manfaat untuk opsi terakhir ada bagaimana jika saya ingin menggunakan kredensial akun saya untuk mengatur ulang kata sandi orang lain ?

AccountManager.resetPassword(otherAccount, adminAccount.getAccessToken());

Tidak hanya sistem yang lebih fleksibel, saya tidak hanya menghapus gips, tetapi desainnya lebih ekspresif . Saya bisa membaca ini dan dengan mudah memahami apa yang dilakukannya dan bagaimana itu dapat digunakan.


TL; DR: ini berbunyi seperti masalah XY . Umumnya ketika dihadapkan dengan dua pilihan yang suboptimal, perlu mengambil kembali langkah dan pemikiran "apa aku benar-benar capai di sini? Haruskah saya benar-benar berpikir tentang bagaimana membuat typecasting kurang jelek, atau saya harus mencari cara untuk hapus typecast sepenuhnya? "


sumber
1
+1 untuk baunya desain, meskipun Anda tidak menggunakan frasa.
Jared Smith
bagaimana dengan kasus-kasus dimana reset password memiliki argumen yang sangat berbeda tergantung pada jenisnya? ResetPassword (pswd) vs ResetPasswordToRandom ()
TheCatWhisperer
1
@TheCatWhisperer Contoh pertama adalah "ubah kata sandi" yang merupakan tindakan berbeda. Contoh kedua adalah apa yang saya uraikan dalam jawaban ini.
Mengapa tidak account.getPasswordResetter().resetPassword();.
AnoE
1
The benefit to the last option there is what if I want to use my account credentials to reset someone else's password?.. mudah. Anda membuat objek domain Akun untuk akun lain dan menggunakannya. Kelas statis bermasalah untuk pengujian unit (metode yang menurut definisi memerlukan akses ke beberapa penyimpanan, jadi sekarang Anda tidak dapat dengan mudah menguji unit kode apa pun yang menyentuh kelas itu lagi) dan sebaiknya dihindari. Pilihan untuk mengembalikan beberapa antarmuka lain seringkali merupakan ide yang bagus tetapi tidak benar-benar berurusan dengan masalah mendasar (Anda baru saja memindahkan masalah ke antarmuka lain).
Voo
1

Bill Venner mengatakan bahwa pendekatan Anda baik - baik saja ; lompat ke bagian berjudul 'When to Use instanceof'. Anda downcasting ke tipe / antarmuka tertentu yang hanya mengimplementasikan perilaku spesifik itu sehingga Anda benar untuk selektif tentang hal itu.

Namun, jika Anda ingin menggunakan pendekatan lain, ada banyak cara untuk mencukur yak.

Ada pendekatan polimorfisme; Anda dapat berargumen bahwa semua akun memiliki kata sandi, oleh karena itu semua akun setidaknya dapat mencoba mengatur ulang kata sandi. Ini berarti, dalam kelas akun dasar, kita harus memiliki resetPassword()metode yang diterapkan semua akun tetapi dengan caranya sendiri. Pertanyaannya adalah bagaimana metode itu harus berperilaku ketika kemampuannya tidak ada.

Itu bisa mengembalikan kekosongan dan diam-diam menyelesaikannya apakah mereset kata sandi atau tidak, mungkin mengambil tanggung jawab untuk mencetak pesan secara internal jika tidak mengatur ulang kata sandi. Bukan ide terbaik.

Itu bisa mengembalikan boolean yang menunjukkan apakah reset berhasil. Mengaktifkan boolean itu, kita dapat menghubungkan bahwa pengaturan ulang kata sandi gagal.

Itu bisa mengembalikan sebuah String yang menunjukkan hasil dari upaya pengaturan ulang kata sandi. String dapat memberikan detail lebih lanjut tentang mengapa reset gagal dan bisa menjadi output.

Itu bisa mengembalikan objek ResetResult yang menyampaikan lebih detail dan menggabungkan semua elemen pengembalian sebelumnya.

Ini bisa mengembalikan kekosongan dan malah membuang pengecualian jika Anda mencoba mengatur ulang akun yang tidak memiliki kemampuan itu (jangan lakukan ini karena menggunakan penanganan pengecualian untuk kontrol aliran normal adalah praktik yang buruk karena berbagai alasan).

Memiliki canResetPassword()metode yang cocok mungkin tidak tampak seperti hal terburuk di dunia karena secara teori itu adalah kemampuan statis bawaan kelas ketika ditulis. Ini adalah petunjuk mengapa pendekatan metode adalah ide yang buruk, karena itu menunjukkan bahwa kemampuan itu dinamis dan yang canResetPassword()mungkin berubah, yang juga menciptakan kemungkinan tambahan bahwa itu mungkin berubah antara meminta izin dan membuat panggilan. Seperti disebutkan di tempat lain, beri tahu daripada meminta izin.

Komposisi atas warisan bisa menjadi pilihan: Anda bisa memiliki passwordResetterbidang terakhir (atau pengambil yang setara) dan kelas yang setara yang dapat Anda periksa untuk null sebelum memanggilnya. Sementara itu bertindak sedikit seperti meminta izin, finalitas mungkin menghindari sifat dinamis yang disimpulkan.

Anda mungkin berpikir untuk mengeksternalkan fungsionalitas ke dalam kelasnya sendiri yang dapat mengambil akun sebagai parameter dan menindaklanjutinya (misalnya resetter.reset(account)), meskipun ini juga sering merupakan praktik buruk (analogi yang umum adalah penjaga toko merogoh dompet Anda untuk mendapatkan uang tunai).

Namun, dalam bahasa dengan kapabilitas, Anda mungkin menggunakan mixin atau ciri, Anda berakhir di posisi di mana Anda mulai di mana Anda mungkin memeriksa keberadaan kapabilitas tersebut.

Danikov
sumber
Tidak semua akun memiliki kata sandi (bagaimanapun, dari perspektif sistem khusus ini). Misalnya akun saya menjadi pihak ke-3 ... google, facebook, dll. Bukan untuk mengatakan bahwa ini bukan jawaban yang bagus!
TheCatWhisperer
0

Untuk contoh khusus " dapat mengatur ulang kata sandi " ini, saya sarankan menggunakan komposisi di atas warisan (dalam hal ini, warisan antarmuka / kontrak). Karena, dengan melakukan ini:

class Foo : IResetsPassword {
    //...
}

Anda segera menentukan (pada waktu kompilasi) bahwa kelas Anda ' dapat mengatur ulang kata sandi '. Tetapi, jika dalam skenario Anda, keberadaan kapabilitas bersyarat dan tergantung pada hal-hal lain, maka Anda tidak dapat menentukan hal-hal pada waktu kompilasi lagi. Kemudian, saya sarankan melakukan ini:

class Foo {

    PasswordResetter passwordResetter;

}

Sekarang, saat runtime, Anda dapat memeriksa apakah myFoo.passwordResetter != nullsebelum melakukan operasi ini. Jika Anda ingin memisahkan barang lebih banyak (dan Anda berencana untuk menambahkan lebih banyak kemampuan), Anda dapat:

class Foo {
    //... foo stuff
}

class PasswordResetOperation {
    bool Execute(Foo foo) { ... }
}

class SendMailOperation {
    bool Execute(Foo foo) { ... }
}

//...and you follow this pattern for each new capability...

MEMPERBARUI

Setelah saya membaca beberapa jawaban dan komentar dari OP saya mengerti pertanyaannya bukan tentang solusi komposisional. Jadi saya pikir pertanyaannya adalah tentang bagaimana mengidentifikasi kemampuan objek secara lebih baik, dalam skenario seperti di bawah ini:

class BaseAccount {
    //...
}
class GuestAccount : BaseAccount {
    //...
}
class UserAccount : BaseAccount, IMyPasswordReset, IEditPosts {
    //...
}
class AdminAccount : BaseAccount, IPasswordReset, IEditPosts, ISendMail {
    //...
}

//Capabilities

interface IMyPasswordReset {
    bool ResetPassword();
}

interface IPasswordReset {
    bool ResetPassword(UserAccount userAcc);
}

interface IEditPosts {
    bool EditPost(long postId, ...);
}

interface ISendMail {
    bool SendMail(string from, string to, ...);
}

Sekarang, saya akan mencoba menganalisis semua opsi yang disebutkan:

OP contoh kedua:

if (account.CanResetPassword)
       ((IResetsPassword)account).ResetPassword();
else
       Print("Not allowed to reset password with this account type!");

Katakanlah kode ini menerima beberapa kelas akun dasar (misalnya: BaseAccountdalam contoh saya); ini buruk karena memasukkan boolean di kelas dasar, mencemarinya dengan kode yang tidak masuk akal untuk berada di sana.

OP contoh pertama:

if (account is IResetsPassword)
       ((IResetsPassword)account).ResetPassword();
else
       Print("Not allowed to reset password with this account type!");

Untuk menjawab pertanyaan, ini lebih tepat daripada opsi sebelumnya, tetapi tergantung pada implementasinya akan melanggar prinsip L solid, dan mungkin memeriksa seperti ini akan menyebar melalui kode dan membuat pemeliharaan lebih lanjut lebih sulit.

Pelamar CandiedOrange:

account.ResetPassword(authority);

Jika ResetPasswordmetode ini dimasukkan dalam BaseAccountkelas, maka itu juga mencemari kelas dasar dengan kode yang tidak sesuai, seperti pada contoh kedua OP

Jawaban Snowman:

AccountManager.resetPassword(otherAccount, adminAccount.getAccessToken());

Ini adalah solusi yang baik, tetapi mempertimbangkan bahwa kemampuannya dinamis (dan mungkin berubah seiring waktu). Namun, setelah saya membaca beberapa komentar dari OP, saya kira pembicaraan di sini adalah mengenai polimorfisme dan kelas-kelas yang ditentukan secara statis (walaupun contoh akun secara intuitif menunjukkan skenario dinamis). EG: dalam AccountManagercontoh ini pemeriksaan untuk izin adalah permintaan ke DB; dalam pertanyaan OP pemeriksaan adalah upaya casting benda.

Saran lain dari saya:

Gunakan pola Metode Templat untuk percabangan tingkat tinggi. Hirarki kelas yang disebutkan tetap seperti itu; kami hanya membuat penangan yang lebih tepat untuk objek, untuk menghindari gips dan properti / metode yang tidak sesuai, membuat polusi pada kelas dasar.

//Template method
class BaseAccountOperation {

    BaseAccount account;

    void Execute() {

        //... some processing

        TryResetPassword();

        //... some processing

        TrySendMail();

        //... some processing
    }

    void TryResetPassword() {
        Print("Not allowed to reset password with this account type!");
    }

    void TrySendMail() {
        Print("Not allowed to reset password with this account type!");
    }
}

class UserAccountOperation : BaseAccountOperation {

    UserAccount userAccount;

    void TryResetPassword() {
        account.ResetPassword(...);
    }

}

class AdminAccountOperation : BaseAccountOperation {

    AdminAccount adminAccount;

    override void TryResetPassword() {
        account.ResetPassword(...);
    }

    void TrySendMail() {
        account.SendMail(...);
    }
}

Anda dapat mengikat operasi ke kelas akun yang sesuai dengan menggunakan kamus / hashtable, atau melakukan operasi run-time menggunakan metode ekstensi, menggunakan dynamickata kunci, atau sebagai opsi terakhir gunakan hanya satu pemain untuk meneruskan objek akun ke operasi (di kasus ini jumlah gips hanya satu, di awal operasi).

Emerson Cardoso
sumber
1
Apakah ini berfungsi untuk selalu menerapkan metode "Execute ()" tetapi terkadang tidak melakukan apa-apa? Ini mengembalikan bool, jadi saya menduga itu berarti berhasil atau tidak. Itu melemparkannya kembali ke klien: "Saya mencoba mengatur ulang kata sandi, tetapi itu tidak terjadi (untuk alasan apa pun), jadi sekarang saya harus ..."
4
Memiliki metode "canX ()" dan "doX ()" adalah antipattern: jika sebuah antarmuka mengekspos metode "doX ()", itu lebih baik untuk dapat melakukan X bahkan jika melakukan X berarti no-op (misalnya pola objek nol) ). Seharusnya tidak pernah menjadi kewajiban pengguna antarmuka untuk terlibat dalam penggabungan sementara (memanggil metode A sebelum metode B) karena itu terlalu mudah untuk mendapatkan kesalahan dan memecahkan hal-hal.
1
Memiliki antarmuka tidak ada kaitannya dengan menggunakan komposisi lebih dari warisan. Mereka hanya mewakili fungsi yang tersedia. Bagaimana fungsionalitas itu terjadi, terlepas dari penggunaan antarmuka. Apa yang telah Anda lakukan di sini adalah menggunakan implementasi antarmuka secara ad-hoc tanpa ada manfaatnya.
Miyamoto Akira
Menarik: jawaban terpilih pada dasarnya mengatakan apa yang komentar saya katakan di atas. Beberapa hari Anda beruntung.
@nocomprende - ya, saya memperbarui jawaban saya berdasarkan beberapa komentar. Terima kasih atas umpan baliknya :)
Emerson Cardoso
0

Tak satu pun dari opsi yang Anda sajikan adalah OO yang baik. Jika Anda menulis pernyataan if di sekitar jenis objek, Anda kemungkinan besar melakukan kesalahan OO (ada pengecualian, ini bukan salah.) Berikut ini jawaban OO sederhana untuk pertanyaan Anda (mungkin tidak valid C #):

interface IAccount {
  bool CanResetPassword();

  void ResetPassword();

  // Other Account operations as needed
}

public class Resetable : IAccount {
  public bool CanResetPassword() {
    return true;
  }

  public void ResetPassword() {
    /* RESET PASSWORD */
  }
}

public class NotResetable : IAccount {
  public bool CanResetPassword() {
    return false;
  }

  public void ResetPassword() {
    Print("Not allowed to reset password with this account type!");}
  }

Saya telah memodifikasi contoh ini agar sesuai dengan apa yang kode asli lakukan. Berdasarkan beberapa komentar, tampaknya orang-orang terpaku pada apakah ini kode khusus 'benar' di sini. Itu bukan poin dari contoh ini. Seluruh overloading Polymorphic pada dasarnya untuk secara kondisional melaksanakan implementasi logika yang berbeda berdasarkan pada jenis objek. Apa yang Anda lakukan dalam kedua contoh tersebut adalah mengacaukan apa yang diberikan oleh bahasa Anda sebagai fitur. Singkatnya, Anda dapat menyingkirkan sub-tipe dan menempatkan kemampuan untuk mengatur ulang sebagai properti boolean dari tipe Akun (mengabaikan fitur-fitur lain dari sub-tipe.)

Tanpa pandangan yang lebih luas dari desain, tidak mungkin untuk mengatakan apakah ini merupakan solusi yang baik untuk sistem khusus Anda. Ini sederhana dan jika itu berfungsi untuk apa yang Anda lakukan, Anda kemungkinan besar tidak akan perlu terlalu memikirkannya lagi kecuali seseorang gagal memeriksa CanResetPassword () sebelum memanggil ResetPassword (). Anda juga dapat mengembalikan boolean atau gagal secara diam-diam (tidak disarankan). Itu benar-benar tergantung pada spesifikasi desain.

JimmyJames
sumber
Tidak semua akun dapat disetel ulang. Selain itu, dapatkah suatu akun memiliki lebih banyak fungsi daripada dapat diatur ulang?
TheCatWhisperer
2
"Tidak semua akun bisa disetel ulang." Tidak yakin apakah ini ditulis sebelum diedit. Kelas kedua adalah NotResetable. "mungkin sebuah akun memiliki lebih banyak fungsi daripada dapat diatur ulang" Saya harapkan begitu tetapi tampaknya tidak penting untuk pertanyaan yang ada.
JimmyJames
1
Ini adalah solusi yang berjalan pada arah yang benar, saya pikir, karena memberikan solusi yang bagus di dalam OO. Mungkin akan mengubah nama nama antarmuka menjadi IPasswordReseter (atau sesuatu seperti itu), untuk memisahkan antarmuka. IAccount dapat dinyatakan sebagai mendukung banyak antarmuka lainnya. Itu akan memberi Anda opsi untuk memiliki kelas dengan implementasi yang kemudian disusun dan digunakan oleh delegasi pada objek domain. @JimmyJames, nitpick kecil, di C # kedua kelas harus menentukan bahwa mereka menerapkan IAccount. Kalau tidak, kodenya terlihat agak aneh ;-)
Miyamoto Akira
1
Juga, periksa komentar dari Snowman di salah satu jawaban lain tentang CanX () dan DoX (). Namun, tidak selalu anti-pola, seperti yang digunakannya (misalnya tentang perilaku UI)
Miyamoto Akira
1
Jawaban ini dimulai dengan baik: ini adalah OOP yang buruk. Sayangnya solusi Anda sama buruknya karena itu juga merongrong sistem tipe tetapi melemparkan pengecualian. Solusi yang baik terlihat berbeda: tidak memiliki metode yang tidak diterapkan pada suatu jenis. Kerangka NET. Sebenarnya menggunakan pendekatan Anda untuk beberapa jenis tapi itu kesalahan jelas.
Konrad Rudolph
0

Menjawab

Jawaban saya yang sedikit beralasan untuk pertanyaan Anda adalah:

Kemampuan suatu objek harus diidentifikasi melalui diri mereka sendiri, bukan dengan mengimplementasikan antarmuka. Namun, bahasa yang diketik dengan sangat statis akan membatasi kemungkinan ini (sesuai desain).

Tambahan:

Bercabang pada tipe objek itu jahat.

Rambling

Saya melihat bahwa Anda tidak menambahkan c#tag, tetapi bertanya dalam object-orientedkonteks umum .

Apa yang Anda gambarkan adalah teknis dari bahasa yang diketik statis / kuat, dan tidak spesifik "OO" pada umumnya. Masalah Anda berasal, antara lain, dari kenyataan bahwa Anda tidak dapat memanggil metode dalam bahasa tersebut tanpa memiliki tipe yang cukup sempit pada waktu kompilasi (baik melalui definisi variabel, parameter metode / definisi nilai pengembalian, atau pemeran eksplisit). Saya telah melakukan kedua varian Anda di masa lalu, dalam jenis bahasa ini, dan keduanya tampak jelek bagi saya akhir-akhir ini.

Dalam bahasa OO yang diketik secara dinamis, masalah ini tidak terjadi karena konsep yang disebut "bebek mengetik". Singkatnya, ini berarti pada dasarnya Anda tidak mendeklarasikan jenis objek di mana saja (kecuali saat membuatnya). Anda mengirim pesan ke objek, dan objek menangani pesan itu. Anda tidak peduli apakah objek tersebut benar-benar memiliki metode nama itu. Beberapa bahasa bahkan memiliki metode umum, catch-all method_missingyang membuat ini lebih fleksibel.

Jadi, dalam bahasa seperti itu (Ruby, misalnya), kode Anda menjadi:

account.reset_password

Periode.

The accountbaik akan me-reset password, melempar "ditolak" pengecualian, atau melempar "tidak tahu bagaimana menangani 'reset_password'" pengecualian.

Jika Anda perlu bercabang secara eksplisit untuk alasan apa pun, Anda masih akan melakukannya pada kapabilitas, bukan pada kelas:

account.reset_password  if account.can? :reset_password

(Di mana can?hanya metode yang memeriksa apakah objek dapat menjalankan beberapa metode, tanpa memanggilnya.)

Perhatikan bahwa sementara preferensi pribadi saya saat ini menggunakan bahasa yang diketik secara dinamis, ini hanya preferensi pribadi. Bahasa yang diketik secara statis juga memiliki banyak hal untuk mereka, jadi tolong jangan menganggap ocehan ini sebagai bash terhadap bahasa yang diketik secara statis ...

AnoE
sumber
Senang memiliki perspektif Anda di sini! Kami di sisi java / C # cenderung lupa cabang OOP yang berbeda ada. Ketika saya memberi tahu kolega saya, JS (untuk semua masalahnya), dalam banyak hal, lebih banyak OO daripada C #, mereka menertawakan saya!
TheCatWhisperer
Saya suka C #, dan saya pikir itu adalah bahasa tujuan umum yang baik , tapi itu pendapat pribadi saya, bahwa dalam hal OO, itu sangat buruk. Baik dalam fitur maupun praktik, ia cenderung mendorong pemrograman prosedural.
TheCatWhisperer
2
Dapat diperdebatkan, pengujian untuk keberadaan metode dalam bahasa yang diketik bebek sama dengan pengujian untuk implementasi antarmuka dalam yang diketik secara statis: Anda melakukan pemeriksaan run-time apakah objek memiliki kemampuan tertentu. Setiap deklarasi metode secara efektif memiliki antarmuka sendiri, diidentifikasi hanya dengan nama metode, dan keberadaannya tidak dapat digunakan untuk menyimpulkan informasi lain tentang objek.
IMSoP
0

Tampaknya pilihan Anda berasal dari berbagai kekuatan motivasi. Yang pertama tampaknya adalah hack yang digunakan karena pemodelan objek yang buruk. Ini menunjukkan bahwa abstraksi Anda salah, karena mereka merusak polimorfisme. Lebih mungkin daripada tidak melanggar prinsip Open Open , yang menghasilkan kopling yang lebih ketat.

Pilihan kedua Anda bisa saja baik dari perspektif OOP, tetapi tipe casting membingungkan saya. Jika akun Anda memungkinkan Anda untuk mereset kata sandi, mengapa Anda perlu mengetik ulang? Jadi dengan asumsi bahwa CanResetPasswordklausa Anda mewakili beberapa persyaratan bisnis mendasar yang merupakan bagian dari abstraksi akun, opsi kedua Anda OK.

Zapadlo
sumber