Apakah metode beton utama menimpa bau kode?


31

Benarkah metode konkret yang utama adalah bau kode? Karena saya pikir jika Anda perlu mengganti metode konkret:

public class A{
    public void a(){
    }
}

public class B extends A{
    @Override
    public void a(){
    }
}

dapat ditulis ulang sebagai

public interface A{
    public void a();
}

public class ConcreteA implements A{
    public void a();
}

public class B implements A{
    public void a(){
    }
}

dan jika B ingin menggunakan kembali a () dalam A dapat ditulis ulang sebagai:

public class B implements A{
    public ConcreteA concreteA;
    public void a(){
        concreteA.a();
    }
}

yang tidak memerlukan warisan untuk mengganti metode, apakah itu benar?



4
Turun karena (dalam komentar di bawah), Telastyn dan Doc Brown setuju untuk mengganti, tetapi memberikan jawaban ya / tidak pada pertanyaan utama karena mereka tidak setuju tentang arti "bau kode". Jadi pertanyaan yang dimaksudkan tentang desain kode telah sangat digabungkan ke sepotong gaul. Dan saya pikir tidak perlu begitu, karena pertanyaan khusus adalah tentang satu teknik terhadap yang lain.
Steve Jessop

5
Pertanyaannya bukan tagged Java, tetapi kode tersebut tampaknya Java. Di C # (atau C ++), Anda harus menggunakan virtualkata kunci untuk mendukung penggantian. Jadi, masalah ini dibuat lebih (atau kurang) ambigu oleh rincian bahasa.
Erik Eidt

1
Sepertinya hampir setiap fitur bahasa pemrograman akhirnya diklasifikasikan sebagai "bau kode" setelah bertahun-tahun.
Brandon

2
Saya merasa seperti finalpengubah ada persis untuk situasi seperti ini. Jika perilaku metode ini sedemikian rupa sehingga tidak dirancang untuk diganti, maka tandai sebagai final. Jika tidak, harap pengembang dapat memilih untuk menimpanya.
Martin Tuskevicius

Jawaban:


34

Tidak, ini bukan bau kode.

  • Jika kelas tidak final, ini memungkinkan untuk disubklasifikasikan.
  • Jika suatu metode tidak final, ini memungkinkan untuk diganti.

Itu terletak dalam tanggung jawab masing-masing kelas untuk mempertimbangkan dengan hati-hati jika subklasifikasi sesuai, dan metode mana yang mungkin ditimpa .

Kelas dapat mendefinisikan dirinya sendiri atau metode apa pun sebagai final, atau dapat menempatkan batasan (pengubah visibilitas, konstruktor yang tersedia) tentang bagaimana dan di mana subkelasnya.

Kasus umum untuk metode utama adalah implementasi default di kelas dasar yang dapat dikustomisasi atau dioptimalkan dalam subkelas (terutama di Java 8 dengan munculnya metode default di antarmuka).

class A {
    public String getDescription(Element e) {
        // return default description for element
    }
}

class B extends A {
    public String getDescription(Element e) {
        // return customized description for element
    }
}

Sebuah alternatif untuk override perilaku adalah Pola Strategi , dimana perilaku tersebut disarikan sebagai interface, dan pelaksanaannya dapat diatur di kelas.

interface DescriptionProvider {
    String getDescription(Element e);
}

class A {
    private DescriptionProvider provider=new DefaultDescriptionProvider();

    public final String getDescription(Element e) {
       return provider.getDescription(e);
    }

    public final void setDescriptionProvider(@NotNull DescriptionProvider provider) {
        this.provider=provider;
    }
}

class B extends A {
    public B() {
        setDescriptionProvider(new CustomDescriptionProvider());
    }
}

Saya mengerti bahwa, di dunia nyata, mengungguli metode konkret mungkin menjadi bau kode bagi sebagian orang. Tapi, saya suka jawaban ini karena sepertinya lebih spek menurut saya.
Darkwater23

Dalam contoh terakhir Anda, bukankah harus Amengimplementasikan DescriptionProvider?
Melihat

@Spotted: A dapat mengimplementasikan DescriptionProvider, dan dengan demikian menjadi penyedia deskripsi default. Tetapi idenya di sini adalah pemisahan keprihatinan: Amembutuhkan deskripsi (beberapa kelas lain mungkin juga), sementara implementasi lainnya menyediakannya.
Peter Walser

2
Ok, terdengar lebih seperti pola strategi .
Terlihat

@Spotted: Anda benar, contoh menjabarkan pola strategi, bukan pola dekorator (dekorator akan menambah perilaku ke komponen). Saya akan memperbaiki jawaban saya, terima kasih.
Peter Walser

25

Benarkah metode konkret yang utama adalah bau kode?

Ya, secara umum, metode beton utama adalah bau kode.

Karena metode dasar memiliki perilaku yang dikaitkan dengan itu yang biasanya dihormati pengembang, perubahan itu akan mengarah ke bug ketika implementasi Anda melakukan sesuatu yang berbeda. Lebih buruk lagi, jika mereka mengubah perilaku, implementasi Anda yang sebelumnya benar dapat memperburuk masalah. Dan secara umum, cukup sulit untuk menghormati Prinsip Substitusi Liskov untuk metode konkret non-sepele.

Sekarang, ada beberapa kasus di mana ini bisa dilakukan dan bagus. Metode semi-sepele dapat dikendarai agak aman. Metode dasar yang ada hanya sebagai jenis perilaku "waras standar" yang dimaksudkan untuk ditunggangi memiliki kegunaan yang baik.

Oleh karena itu, ini sepertinya bau bagi saya - kadang-kadang baik, biasanya buruk, lihatlah untuk memastikan.


29
Penjelasan Anda baik-baik saja, namun saya sampai pada kesimpulan yang berlawanan: "tidak, metode beton utama bukan bau kode secara umum" - itu hanya bau kode ketika salah satu metode menimpa metode yang tidak dimaksudkan untuk diganti. ;-)
Doc Brown

2
@DocBrown Persis! Saya juga melihat bahwa penjelasan (dan khususnya kesimpulan) menentang pernyataan awal.
Tersosauros

1
@Spotted: Counterexample paling sederhana adalah Numberkelas dengan increment()metode yang menetapkan nilai ke nilai valid berikutnya. Jika Anda mensubklasifikasikannya ke EvenNumbertempat increment()menambahkan dua alih-alih satu (dan setter memberlakukan kemerataan), proposal pertama OP memerlukan implementasi duplikat dari setiap metode di kelas dan yang kedua membutuhkan metode pembungkus tulisan untuk memanggil metode dalam anggota. Bagian dari tujuan pewarisan adalah untuk kelas anak-anak untuk memperjelas bagaimana mereka berbeda dari orang tua dengan hanya menyediakan metode-metode yang perlu berbeda.
Blrfl

5
@DocBrown: menjadi bau kode tidak menyiratkan sesuatu pasti buruk, hanya saja sepertinya .
mikołak

3
@docbrown - bagi saya, ini jatuh bersama "komposisi nikmat daripada warisan". Jika Anda menggunakan metode konkret yang berlebihan, Anda sudah berada di tanah yang wangi karena memiliki warisan. Apa kemungkinan Anda menunggangi sesuatu yang tidak seharusnya Anda lakukan? Berdasarkan pengalaman saya, saya pikir itu mungkin. YMMV.
Telastyn

7

jika Anda perlu mengganti metode konkret [...] itu dapat ditulis ulang sebagai

Jika itu dapat ditulis ulang dengan cara itu berarti Anda memiliki kendali atas kedua kelas. Tetapi kemudian Anda tahu apakah Anda mendesain kelas A agar diturunkan dengan cara ini dan jika Anda melakukannya, itu bukan bau kode.

Sebaliknya, jika Anda tidak memiliki kendali atas kelas dasar, Anda tidak dapat menulis ulang dengan cara itu. Jadi, apakah kelas itu dirancang untuk diturunkan dengan cara ini, dalam hal ini lanjut saja, itu bukan bau kode, atau tidak, dalam hal ini Anda memiliki dua opsi: mencari solusi lain sama sekali, atau lanjutkan saja , karena kemurnian kerja desain truf.


Jika kelas A akan dirancang untuk diturunkan, tidakkah akan lebih masuk akal untuk memiliki metode abstrak untuk mengekspresikan maksud dengan jelas? Bagaimana orang yang mengganti metode dapat mengetahui bahwa metode itu dirancang dengan cara ini?
Melihat

@Spotted, ada banyak kasus di mana implementasi cam default disediakan dan masing-masing spesialisasi hanya perlu mengesampingkan beberapa dari mereka, baik untuk memperluas fungsionalitas atau untuk efisiensi. Contoh ekstrem adalah pengunjung. Pengguna tahu bagaimana metode dirancang dari dokumentasi; harus ada di sana untuk menjelaskan apa yang diharapkan dari override.
Jan Hudec

Saya mengerti sudut pandang Anda, tetap saya pikir berbahaya bahwa satu-satunya cara yang dimiliki pengembang, untuk mengetahui metode dapat diganti, adalah dengan membaca dokumen karena: 1) jika harus diganti, saya tidak punya jaminan itu akan selesai. 2) jika tidak boleh diganti, seseorang akan melakukannya untuk kenyamanannya 3) tidak semua orang membaca dokumen. Juga, saya tidak pernah menghadapi kasus di mana saya perlu menimpa seluruh metode, tetapi hanya bagian (dan saya akhirnya menggunakan pola templat).
Terlihat

@Spotted, 1) jika harus diganti, seharusnya abstract; tetapi ada banyak kasus di mana tidak harus. 2) jika tidak boleh diganti, itu harus final(non-virtual). Tetapi masih ada banyak kasus di mana metode mungkin ditimpa. 3) jika pengembang hilir tidak membaca dokumen, mereka akan menembak diri mereka sendiri, tetapi mereka akan melakukan itu terlepas dari tindakan apa pun yang Anda lakukan.
Jan Hudec

Ok jadi perbedaan sudut pandang kami hanya terletak pada 1 kata. Anda mengatakan bahwa setiap metode harus abstrak atau final sementara saya mengatakan bahwa setiap metode harus abstrak atau final. :) Apa kasus-kasus ini ketika mengganti metode diperlukan (dan aman)?
Terlihat

3

Pertama, izinkan saya menunjukkan bahwa pertanyaan ini hanya berlaku di sistem pengetikan tertentu. Misalnya, dalam pengetikan Struktural dan sistem pengetikan Bebek - suatu kelas yang hanya memiliki metode dengan nama & tanda tangan yang sama akan berarti bahwa kelas itu kompatibel dengan jenis (setidaknya untuk metode tertentu, dalam kasus pengetikan Bebek).

Ini (jelas) sangat berbeda dari ranah bahasa yang diketik secara statis , seperti Java, C ++, dll. Serta sifat pengetikan yang Kuat , seperti yang digunakan dalam Java & C ++, serta C # dan lainnya.


Saya menduga Anda berasal dari latar belakang Java, di mana metode harus secara eksplisit ditandai sebagai final jika perancang kontrak bermaksud agar mereka tidak diganti. Namun, dalam bahasa seperti C #, kebalikannya adalah default. Metode (tanpa dekorasi, kata kunci khusus) " disegel " (versi C # final), dan harus dinyatakan secara eksplisit seolah- virtualolah diizinkan untuk diganti.

Jika API atau pustaka telah dirancang dalam bahasa-bahasa ini dengan metode konkret yang dapat ditimpa, maka harus (setidaknya dalam beberapa kasus) masuk akal untuk ditimpa. Oleh karena itu, ini bukan bau kode untuk menggantikan metode unsealed / virtual / not finalconcrete.     (Ini tentu saja mengasumsikan bahwa perancang API mengikuti konvensi dan menandai virtual(C #), atau menandai sebagai final(Java) metode yang sesuai untuk apa yang mereka desain.)


Lihat juga


Dalam pengetikan bebek, apakah dua kelas memiliki metode tanda tangan yang sama yang cukup bagi mereka untuk menjadi tipe yang kompatibel, atau apakah juga perlu bahwa metode memiliki semantik yang sama, sehingga mereka dapat diganti liskov untuk beberapa kelas dasar yang tersirat?
Wayne Conrad

2

Ya itu karena dapat menyebabkan panggilan super anti-pola Itu juga dapat menghilangkan tujuan awal metode ini, memperkenalkan efek samping (=> bug) dan membuat tes gagal. Apakah Anda akan menggunakan kelas yang tes unitnya merah (failling)? Saya tidak akan.

Saya akan melangkah lebih jauh dengan mengatakan, bau kode awal adalah ketika suatu metode tidak abstractatau tidak final. Karena memungkinkan untuk mengubah (dengan mengesampingkan) perilaku entitas yang dibangun (perilaku ini dapat hilang atau diubah). Dengan abstractdan finalmaksudnya tidak ambigu:

  • Abstrak: Anda harus memberikan perilaku
  • Final: Anda tidak diizinkan mengubah perilaku

Cara paling aman untuk menambahkan perilaku ke kelas yang ada adalah apa yang ditunjukkan oleh contoh terakhir Anda: menggunakan pola dekorator.

public interface A{
    void a();
}

public final class ConcreteA implements A{
    public void a() {
        ...
    }
}

public final class B implements A{
    private final ConcreteA concreteA;
    public void a(){
        //Additionnal behaviour
        concreteA.a();
    }
}

9
Pendekatan hitam putih ini tidak terlalu berguna. Pikirkan Object.toString()di Jawa ... Jika ini abstract, banyak hal default tidak akan berfungsi, dan kode bahkan tidak dapat dikompilasi ketika seseorang belum mengganti toString()metode! Jika ya final, keadaan akan menjadi lebih buruk - tidak ada kemampuan untuk memungkinkan objek dikonversi menjadi string, kecuali untuk cara Java. Ketika jawaban @Peter Walser berbicara tentang, implementasi standar (seperti Object.toString()) adalah penting. Terutama di Java 8 metode standar.
Tersosauros

@Tersosauros Anda benar, apakah toString()itu salah abstractatau finalakan menyebalkan . Pendapat pribadi saya adalah bahwa metode ini rusak dan akan lebih baik untuk tidak memilikinya sama sekali (seperti equals dan kode hash ). Tujuan awal dari standar Java adalah untuk memungkinkan evolusi API dengan retrocompatibility.
Terlihat

Kata "retrocompatibility" memiliki banyak sekali suku kata.
Craig

@Spotted Saya sangat kecewa dengan penerimaan umum warisan konkret di situs ini, saya berharap lebih baik: / Jawaban Anda harus memiliki lebih banyak upvotes
TheCatWhisperer

1
@TheCatWhisperer saya setuju, tetapi di sisi lain saya butuh waktu lama untuk mencari tahu manfaat dari menggunakan dekorasi dibandingkan warisan beton (sebenarnya sudah jelas ketika saya mulai menulis tes pertama) bahwa Anda tidak bisa menyalahkan siapa pun untuk itu . Hal terbaik untuk dilakukan adalah mencoba mendidik yang lain sampai mereka menemukan kebenaran (lelucon). :)
Melihat
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.