Typescript: Mendukung override kata kunci pada metode kelas

Dibuat pada 10 Feb 2015  ·  210Komentar  ·  Sumber: microsoft/TypeScript

(Pembaruan oleh @RyanCavanuagh)

Silakan lihat komentar ini sebelum meminta "pembaruan apa pun", "tolong tambahkan ini sekarang", dll.. Komentar yang tidak berarti menambah diskusi akan dihapus untuk menjaga agar panjang utas tetap masuk akal.


(CATATAN, ini _bukan_ duplikat dari Edisi #1524. Proposal di sini lebih sesuai dengan penentu penimpaan C++, yang jauh lebih masuk akal untuk TypeScript)

Kata kunci override akan sangat berguna dalam TypeScript. Ini akan menjadi kata kunci opsional pada metode apa pun yang menimpa metode kelas super, dan mirip dengan penentu override di C++ akan menunjukkan maksud bahwa "_nama+tanda tangan metode ini harus selalu cocok dengan nama+tanda tangan metode kelas super_" . Ini menangkap berbagai macam masalah dalam basis kode yang lebih besar yang sebaliknya dapat dengan mudah dilewatkan.

Sekali lagi mirip dengan C++, _itu bukan kesalahan untuk menghilangkan kata kunci override dari metode yang diganti_. Dalam hal ini kompiler hanya bertindak persis seperti saat ini, dan melewatkan pemeriksaan waktu kompilasi tambahan yang terkait dengan kata kunci override. Ini memungkinkan skenario javascript yang tidak diketik yang lebih kompleks di mana penggantian kelas turunan tidak sama persis dengan tanda tangan kelas dasar.

Contoh penggunaan

class Animal {
    move(meters:number):void {
    }
}

class Snake extends Animal {
    override move(meters:number):void {
    }
}

Contoh kondisi kesalahan kompilasi

// Add an additional param to move, unaware that the intent was 
// to override a specific signature in the base class
class Snake extends Animal {
    override move(meters:number, height:number):void {
    }
}
// COMPILE ERROR: Snake super does not define move(meters:number,height:number):void

// Rename the function in the base class, unaware that a derived class
// existed that was overriding the same method and hence it needs renaming as well
class Animal {
    megamove(meters:number):void {
    }
}
// COMPILE ERROR: Snake super does not define move(meters:number):void

// Require the function to now return a bool, unaware that a derived class
// existed that was still using void, and hence it needs updating
class Animal {
    move(meters:number):bool {
    }
}
// COMPILE ERROR: Snake super does not define move(meters:number):void

IntelliSense

Selain validasi waktu kompilasi tambahan, kata kunci override menyediakan mekanisme untuk typescript intellisense untuk dengan mudah menampilkan dan memilih metode super yang tersedia, di mana tujuannya adalah untuk secara khusus menimpa salah satunya di kelas turunan. Saat ini ini sangat kikuk dan melibatkan penelusuran melalui rantai kelas super, menemukan metode yang ingin Anda timpa, dan kemudian menyalinnya ke dalam kelas turunan untuk menjamin kecocokan tanda tangan.

Mekanisme IntelliSense yang diusulkan

Dalam deklarasi kelas:

  1. ketik override
  2. Sebuah drop-down lengkap otomatis muncul dari semua metode super untuk kelas
  3. Sebuah metode dipilih di drop down
  4. Tanda tangan untuk metode tersebut dipancarkan ke dalam deklarasi kelas setelah kata kunci override.
Add a Flag Revisit Suggestion

Komentar yang paling membantu

Mohon maafkan rengekan, tapi jujur, sementara argumen Anda berlaku untuk kata kunci public dalam bahasa di mana semuanya publik secara default, cukup membagi dunia, memiliki hal-hal seperti abstract dan override opsional Kata kunci

Overriding adalah salah satu dari sedikit aspek bahasa yang sangat sensitif terhadap kesalahan ketik, karena nama metode override yang salah ketik bukanlah masalah waktu kompilasi yang jelas. Manfaat override jelas, karena Anda mengizinkan Anda menyatakan niat Anda untuk menimpa - jika metode dasar tidak ada, ini adalah kesalahan waktu kompilasi. Semua memuji sistem tipe. Mengapa ada orang yang tidak menginginkan ini?

Semua 210 komentar

Benar-benar proposal yang bagus.

Namun bagaimana dengan contoh berikut, yang menimpa saya secara valid:

class Snake extends Animal {
    override move(meters:number, height=-1):void {
    }
}
class A {...}
class Animal {
    setA(a: A): void {...}
    getA(): A {...}
}

class B extends A {...}
class Snake extends Animal {
    override setA(a: B): void {...}
    override getA(): B {...}
}

Selain itu saya akan menambahkan flag compiler untuk memaksa kata kunci override hadir (atau dilaporkan sebagai peringatan).
Alasannya adalah untuk menangkap ketika mengganti nama metode di kelas dasar yang mewarisi kelas yang sudah diimplementasikan (tetapi tidak seharusnya menjadi pengganti).

Ah contoh yang bagus. Secara umum saya mengharapkan penggunaan kata kunci override untuk menegakkan _exact_ pencocokan tanda tangan, karena tujuan menggunakannya adalah untuk mempertahankan hierarki kelas yang diketik dengan ketat. Jadi untuk mengatasi contoh Anda:

  1. Menambahkan parameter default tambahan. Ini akan menghasilkan kesalahan kompilasi: Snake super tidak mendefinisikan move(meters:number):void. Sementara metode turunan secara fungsional konsisten, kode klien yang memanggil Animal.move mungkin tidak mengharapkan kelas turunan juga memfaktorkan tingginya (karena API dasar tidak mengeksposnya).
  2. Ini akan (dan harus selalu) menghasilkan kesalahan kompilasi, karena tidak konsisten secara fungsional. Perhatikan tambahan berikut untuk contoh:
class C extends A {...}
var animal : Animal = new Snake();
animal.setA(new C());
// This will have undefined run-time behavior, as C will be interpreted as type B in Snake.setA

Jadi contoh (2.) sebenarnya adalah demo yang bagus tentang bagaimana kata kunci override dapat menangkap kasus tepi halus pada waktu kompilasi yang seharusnya terlewatkan! :)

Dan sekali lagi saya akan menekankan bahwa kedua contoh mungkin valid dalam skenario javascript terkontrol/lanjutan tertentu yang mungkin diperlukan... dalam hal ini pengguna dapat memilih untuk menghilangkan kata kunci override.

Ini akan berguna. Saat ini kami mengatasi ini dengan memasukkan referensi dummy ke metode super:

class Snake extends Animal {
    move(meters:number, height?:number):void {
         super.move; // override fix
    }
}

Tapi ini hanya melindungi dari kasus kedua: metode super diganti namanya. Perubahan tanda tangan tidak memicu kesalahan kompilasi. Selanjutnya, ini jelas merupakan peretasan.

Saya juga tidak berpikir bahwa parameter default dan opsional dalam tanda tangan metode kelas turunan harus memicu kesalahan kompilasi. Itu mungkin benar tetapi bertentangan dengan fleksibilitas bawaan JavaScript.

@rwyborn
Sepertinya kita tidak mengharapkan perilaku yang sama.
Anda akan menggunakan kata kunci override ini untuk memastikan tanda tangan yang sama, sedangkan saya akan menggunakannya lebih sebagai opsi readibiliy (jadi permintaan saya untuk menambahkan opsi kompiler untuk memaksa penggunaannya).
Sebenarnya apa yang saya harapkan adalah bahwa TS mendeteksi metode override yang tidak valid (bahkan tanpa menggunakan override).
Khas:

class Snake extends Animal {
    move(meters:number, height:number):void {}
}

seharusnya memunculkan kesalahan, karena itu benar-benar merupakan penggantian Animal.move() (perilaku JS), tetapi yang tidak kompatibel (karena ketinggian tidak seharusnya opsional, sedangkan itu akan tidak ditentukan jika dipanggil dari "referensi" Hewan).
Sebenarnya menggunakan override hanya akan mengkonfirmasi (oleh kompiler) bahwa metode ini benar-benar ada di kelas dasar (dan dengan tanda tangan yang sesuai, tetapi karena poin sebelumnya, bukan karena kata kunci override).

@stephanedr , berbicara sebagai pengguna tunggal, saya benar-benar setuju dengan Anda bahwa kompiler harus selalu mengonfirmasi tanda tangan, karena saya pribadi suka menerapkan pengetikan yang ketat dalam hierarki kelas saya (bahkan jika javascript tidak!!).

Namun dalam mengusulkan bahwa perilaku ini adalah opsional melalui kata kunci override, saya mencoba untuk mengingat bahwa pada akhirnya javascript tidak diketik, dan karenanya menegakkan pencocokan tanda tangan yang ketat secara default akan menghasilkan beberapa pola desain javascript tidak lagi dapat diekspresikan dalam TypeScript.

@rwyborn Saya senang Anda menyebutkan implementasi C++ karena itulah yang saya bayangkan seharusnya bekerja sebelum saya tiba di sini - opsional. Meskipun, bendera kompiler yang memaksa penggunaan kata kunci override akan turun dengan baik di buku saya.

Kata kunci akan memungkinkan kesalahan waktu kompilasi untuk pengetikan kikuk pengembang, yang paling mengkhawatirkan saya tentang penggantian dalam bentuk mereka saat ini.

class Base {
    protected commitState() : void {

    }
}


class Implementation extends Base {
    override protected comitState() : void {   /// error - 'comitState' doesn't exist on base type

    }
}

Saat ini (mulai 1.4) kelas Implementation di atas hanya akan mendeklarasikan metode baru dan pengembang tidak akan lebih bijaksana sampai mereka melihat kode mereka tidak berfungsi.

Dibahas di review saran.

Kami benar-benar memahami kasus penggunaan di sini. Masalahnya adalah menambahkannya pada tahap ini dalam bahasa menambah lebih banyak kebingungan daripada menghilangkannya. Kelas dengan 5 metode, yang 3 di antaranya ditandai override , tidak akan menyiratkan bahwa 2 lainnya _aren't_ menimpa. Untuk membenarkan keberadaannya, pengubah benar-benar perlu membagi dunia lebih bersih dari itu.

Mohon maafkan rengekan, tapi jujur, sementara argumen Anda berlaku untuk kata kunci public dalam bahasa di mana semuanya publik secara default, cukup membagi dunia, memiliki hal-hal seperti abstract dan override opsional Kata kunci

Overriding adalah salah satu dari sedikit aspek bahasa yang sangat sensitif terhadap kesalahan ketik, karena nama metode override yang salah ketik bukanlah masalah waktu kompilasi yang jelas. Manfaat override jelas, karena Anda mengizinkan Anda menyatakan niat Anda untuk menimpa - jika metode dasar tidak ada, ini adalah kesalahan waktu kompilasi. Semua memuji sistem tipe. Mengapa ada orang yang tidak menginginkan ini?

Saya setuju 100% dengan @hdachev , inkonsistensi kecil yang dirujuk juga oleh @RyanCavanaugh mudah ditimbang oleh manfaat kata kunci dalam membawa pemeriksaan waktu kompilasi ke penggantian metode. Saya sekali lagi akan menunjukkan bahwa C++ menggunakan kata kunci override opsional berhasil dengan cara yang persis sama seperti yang disarankan untuk TypeScript.

Saya tidak bisa cukup menekankan seberapa banyak perbedaan yang dibuat oleh pemeriksaan override dalam basis kode skala besar dengan pohon OO yang kompleks.

Akhirnya saya akan menambahkan bahwa jika inkonsistensi kata kunci opsional benar-benar menjadi perhatian, maka pendekatan C# dapat digunakan, yaitu penggunaan wajib kata kunci "baru" atau "timpa":

class Dervied extends Base {

    new FuncA(newParam) {} // "new" says that I am implementing a new version of FuncA() with a different signature to the base class version

    override FuncB() {} // "override" says that I am implementing exactly the same signature as the base class version

    FuncC() {} // If FuncC exists in the base class then this is a compile error. I must either use the override keyword (I am matching the signature) or the new keyword (I am changing the signature)
}

Ini tidak sama dengan public karena properti tanpa pengubah akses dikenal publik; metode tanpa override _not_ dikenal sebagai non-override.

Berikut adalah solusi runtime-checked menggunakan dekorator (datang di TS1.5) yang menghasilkan pesan kesalahan yang baik dengan sedikit overhead:

/* Put this in a helper library somewhere */
function override(container, key, other1) {
    var baseType = Object.getPrototypeOf(container);
    if(typeof baseType[key] !== 'function') {
        throw new Error('Method ' + key + ' of ' + container.constructor.name + ' does not override any base class method');
    }
}

/* User code */
class Base {
    public baseMethod() {
        console.log('Base says hello');
    }
}

class Derived extends Base {
    // Works
    <strong i="9">@override</strong>
    public baseMethod() {
        console.log('Derived says hello');
    }

    // Causes exception
    <strong i="10">@override</strong>
    public notAnOverride() {
        console.log('hello world');
    }
}

Menjalankan kode ini menghasilkan kesalahan:

Kesalahan: Metode notAnOverride of Derived tidak menimpa metode kelas dasar apa pun

Karena kode ini berjalan pada waktu inisialisasi kelas, Anda bahkan tidak memerlukan pengujian unit khusus untuk metode yang dimaksud; kesalahan akan terjadi segera setelah kode Anda dimuat. Anda juga dapat berlangganan versi "cepat" dari override yang tidak melakukan pemeriksaan untuk penerapan produksi.

@RyanCavanaugh Jadi kami berada di Typescript 1.6 dan dekorator masih merupakan fitur eksperimental, bukan sesuatu yang ingin saya terapkan dalam basis kode produksi skala besar sebagai peretasan agar penggantian berfungsi.

Untuk mencoba sudut lain, setiap bahasa yang diketik populer di luar sana mendukung kata kunci "override"; Swift, ActionScript, C#, C++ dan F# untuk beberapa nama. Semua bahasa ini berbagi masalah kecil yang telah Anda ungkapkan di utas ini tentang penggantian, tetapi jelas ada kelompok besar di luar sana yang melihat bahwa manfaat penggantian jauh lebih besar daripada masalah kecil ini.

Apakah keberatan Anda murni berdasarkan biaya/manfaat? Jika saya benar-benar melanjutkan dan menerapkan ini dalam PR, apakah itu akan diterima?

Ini bukan hanya masalah biaya/manfaat. Seperti yang dijelaskan Ryan, masalahnya adalah menandai suatu metode sebagai penimpaan tidak berarti bahwa metode lain _bukan_ penimpaan. Satu-satunya cara yang masuk akal adalah jika semua penggantian perlu ditandai dengan kata kunci override (yang jika kami diamanatkan, akan menjadi perubahan besar).

@DanielRosenwasser Seperti diuraikan di atas, dalam C++ kata kunci override adalah opsional (persis seperti yang diusulkan untuk TypeScript) namun semua orang menggunakannya tidak masalah dan itu sangat berguna pada basis kode besar. Selanjutnya di TypeScript sebenarnya sangat masuk akal untuk menjadi opsional karena fungsi javascript yang berlebihan.

class Base {
    method(param: number): void { }
}

class DerivedA extends Base {
    // I want to *exactly* implement method with the same signature
    override method(param: number): void {}
}

class DerivedB extends Base {
    // I want to implement method, but support an extended signature
    method(param: number, extraParam: any): void {}
}

Adapun keseluruhan argumen "tidak menyiratkan bahwa metode lain bukan merupakan override", Ini persis analog dengan "pribadi". Anda dapat menulis seluruh basis kode tanpa pernah menggunakan kata kunci pribadi. Beberapa variabel dalam basis kode itu hanya akan diakses secara pribadi dan semuanya akan dikompilasi dan berfungsi dengan baik. Namun "pribadi" adalah gula sintaksis tambahan yang dapat Anda gunakan untuk memberi tahu kompiler "Tidak juga, kompilasi kesalahan jika seseorang mencoba mengakses ini". Dengan cara yang sama "overload" adalah gula sintaksis ekstra untuk memberi tahu kompiler "Saya ingin ini sama persis dengan deklarasi kelas dasar. Jika tidak mengkompilasi kesalahan".

Anda tahu, saya pikir kalian terpaku pada interpretasi literal dari "override". Benar-benar apa yang ditandai adalah "exactly_match_signature_of_superclass_method", tapi itu tidak terlalu mudah dibaca :)

class DerivedA extends Base {
    exactly_match_signature_of_superclass_method method(param: number): void {}
}

Saya juga ingin kata kunci override tersedia, dan kompiler menghasilkan kesalahan jika metode yang ditandai untuk override tidak ada, atau memiliki tanda tangan yang berbeda, di kelas dasar. Ini akan membantu keterbacaan dan untuk refactoring

+1, juga perkakas akan menjadi jauh lebih baik. Kasus penggunaan saya menggunakan reaksi. Saya telah memeriksa definisi setiap kali saya menggunakan metode ComponentLifecycle :

``` C#
antarmuka ComponentLifecycle

{
componentWillMount?(): void;
componentDidMount?(): batal;
componentWillReceiveProps?(nextProps: P, nextContext: any): void;
shouldComponentUpdate?(nextProps: P, nextState: S, nextContext: any): boolean;
componentWillUpdate?(nextProps: P, nextState: S, nextContext: any): void;
componentDidUpdate?(prevProps: P, prevState: S, prevContext: any): void;
componentWillUnmount?(): void;
}

With override, or other equivalent solution,you'll get a nice auto-completion. 

One problem however is that I will need to override interface methods...

``` C#
export default class MyControlextends React.Component<{},[}> {
    override /*I want intellisense here*/ componentWillUpdate(nextProps, nextState, nextContext): void {

    }
}

@olmobrutall sepertinya kasus penggunaan Anda lebih baik diselesaikan dengan layanan bahasa yang menawarkan refactoring seperti "implement interface" atau menawarkan penyelesaian yang lebih baik, bukan dengan menambahkan kata kunci baru ke bahasa.

Jangan terganggu :) Fitur layanan bahasa hanyalah sebagian kecil dari pemeliharaan hierarki antarmuka dalam basis kode yang besar. Sejauh ini, kemenangan terbesar sebenarnya adalah mendapatkan kesalahan waktu kompilasi ketika kelas turun dalam hierarki Anda di suatu tempat tidak sesuai. Inilah sebabnya mengapa C++ menambahkan kata kunci override opsional (perubahan yang tidak melanggar). Inilah mengapa TypeScript harus melakukan hal yang sama.

Dokumentasi Microsoft untuk penggantian C++ merangkum semuanya dengan baik, https://msdn.microsoft.com/en-us/library/jj678987.aspx

Gunakan penggantian untuk membantu mencegah perilaku pewarisan yang tidak disengaja dalam kode Anda. Contoh berikut menunjukkan di mana, tanpa menggunakan override, perilaku fungsi anggota dari kelas turunan mungkin tidak dimaksudkan. Kompiler tidak mengeluarkan kesalahan apa pun untuk kode ini.
...
Saat Anda menggunakan penggantian, kompiler menghasilkan kesalahan alih-alih membuat fungsi anggota baru secara diam-diam.

Sejauh ini, kemenangan terbesar sebenarnya adalah mendapatkan kesalahan waktu kompilasi ketika kelas turun dalam hierarki Anda di suatu tempat tidak sesuai.

Saya harus setuju. Satu perangkap yang muncul di tim kami adalah orang-orang mengira mereka telah mengganti metode padahal sebenarnya mereka sedikit salah mengetik atau memperluas kelas yang salah.

Perubahan antarmuka di perpustakaan dasar saat bekerja di banyak proyek lebih sulit daripada yang seharusnya dalam bahasa dengan agenda seperti TypeScript.

Kami memiliki begitu banyak hal hebat, tetapi kemudian ada keanehan seperti ini dan tidak ada properti const tingkat kelas.

@RyanCavanaugh benar, tetapi layanan bahasa dapat dipicu setelah penulisan override, seperti yang dilakukan banyak bahasa, tanpa kata kunci jauh lebih sulit untuk mengetahui kapan saat yang tepat.

Tentang mengimplementasikan antarmuka, perhatikan bahwa sebagian besar metode di antarmuka adalah opsional dan Anda dimaksudkan untuk menimpa hanya beberapa yang Anda butuhkan, bukan seluruh paket. Anda dapat membuka dialog dengan kotak centang tetapi tetap ...

Dan sementara titik kesulitan saya saat ini adalah menemukan nama metode, di masa depan akan sangat bagus untuk mendapatkan pemberitahuan tentang kesalahan waktu kompilasi jika seseorang mengganti nama atau mengubah tanda tangan dari metode dasar.

Tidak bisakah ketidakkonsistenan diselesaikan hanya dengan menambahkan peringatan saat Anda tidak menulis override? Saya pikir TypeScript melakukan hal yang benar menambahkan perubahan kecil yang masuk akal daripada mempertahankan keputusan yang buruk untuk akhir waktu.

Juga abstrak sudah ada, mereka akan menjadi pasangan yang luar biasa :)

Saya juga merasakan kebutuhan akan specifier 'override'. Dalam proyek menengah hingga besar, fitur ini menjadi penting dan dengan segala hormat saya berharap tim TypeScript akan mempertimbangkan kembali keputusan untuk menolak saran ini.

Bagi siapa pun yang tertarik, kami telah menulis aturan tslint khusus yang menyediakan fungsionalitas yang sama dengan kata kunci override dengan menggunakan dekorator, mirip dengan saran Ryan di atas tetapi diperiksa pada waktu kompilasi. Kami akan segera membukanya, saya akan memposting kembali jika sudah tersedia.

Saya sangat merasakan perlunya kata kunci 'override' juga.
Dalam kasus saya, saya mengubah beberapa nama metode di kelas dasar dan saya lupa mengganti nama beberapa nama metode utama. Tentu saja, ini menyebabkan beberapa bug.

Tapi, jika ada fitur seperti itu, kita bisa menemukan metode kelas ini dengan mudah.

Seperti yang disebutkan @RyanCavanaugh , jika kata kunci ini hanya kata kunci opsional, fitur ini membuat kebingungan. Jadi, bagaimana membuat sesuatu menandai di tsc untuk mengaktifkan fitur ini?

Harap pertimbangkan kembali fitur ini lagi....

Bagi saya, jika kata kunci override berguna, itu perlu ditegakkan, seperti di C#. Jika Anda menentukan metode dalam C# yang menggantikan tanda tangan dari metode dasar, Anda _harus_ melabelinya sebagai override atau baru.

C++ menjengkelkan dan lebih rendah dibandingkan dengan C# di beberapa tempat karena membuat terlalu banyak kata kunci opsional, dan karena itu menghalangi _consistency_. Misalnya, jika Anda membebani metode virtual, metode kelebihan beban dapat ditandai virtual atau tidak - keduanya akan menjadi virtual. Saya lebih suka yang terakhir karena membantu pengembang lain yang membaca kode, tetapi saya tidak bisa membuat kompiler untuk menerapkannya, yang berarti basis kode kami pasti akan kehilangan kata kunci virtual di tempat yang seharusnya. Kata kunci override juga opsional. Keduanya adalah omong kosong menurut saya. Apa yang diabaikan di sini adalah bahwa kode dapat berfungsi sebagai dokumentasi dan meningkatkan pemeliharaan dengan menerapkan kebutuhan akan kata kunci daripada pendekatan "ambil atau tinggalkan". Kata kunci "lempar" di C++ serupa.

Untuk mencapai tujuan di atas dalam TypeScript, kompiler memerlukan tanda untuk "mengaktifkan" perilaku ketat ini atau tidak.

Sejauh menyangkut tanda tangan fungsi dari basis dan override, mereka harus identik. Tetapi akan diinginkan untuk mengizinkan kovarians dalam spesifikasi tipe pengembalian untuk menegakkan pemeriksaan waktu kompilasi.

Saya datang ke TS dari AS3 jadi tentu saja, saya akan memberikan suara saya di sini untuk kata kunci override juga. Untuk pengembang yang baru mengenal basis kode apa pun, melihat override adalah petunjuk besar tentang apa yang mungkin terjadi di kelas (anak). Saya pikir kata kunci seperti itu sangat menambah nilai. Saya akan memilih untuk membuatnya wajib, tetapi saya dapat melihat bagaimana itu akan menjadi perubahan yang melanggar dan karenanya harus opsional. Saya benar-benar tidak melihat masalah dengan ambiguitas yang ditimbulkan, meskipun saya sensitif terhadap perbedaan antara override opsional dan default public kata kunci.

Untuk semua +1 -- dapatkah Anda berbicara tentang apa yang tidak cukup tentang solusi dekorator yang ditunjukkan di atas?

Bagi saya itu terasa seperti konstruksi buatan, bukan properti dari bahasa itu sendiri. Dan saya rasa itu berdasarkan desain, karena memang begitulah adanya. Yang saya kira mengirim pengembang (well, toh saya) pesan bahwa itu sementara, bukan praktik terbaik.

jelas setiap bahasa memiliki paradigmanya sendiri dan, karena baru mengenal TypeScript, saya lambat dengan perubahan paradigma. Saya harus mengatakan bahwa override TIDAK terasa seperti praktik terbaik bagi saya karena sejumlah alasan. Saya beralih ke TypeScript karena saya telah sepenuhnya menelan koolaid yang diketik dengan kuat, dan percaya bahwa biaya (dalam hal penekanan tombol dan kurva pembelajaran) sangat sebanding dengan manfaatnya baik untuk kode bebas kesalahan dan pemahaman kode. override adalah bagian yang sangat penting dari teka-teki itu, dan menyampaikan beberapa informasi yang sangat penting tentang peran metode utama.

Bagi saya ini bukan tentang kenyamanan IDE, meskipun itu tidak dapat disangkal luar biasa ketika didukung dengan benar, dan lebih banyak tentang memenuhi apa yang saya yakini sebagai prinsip-prinsip yang menjadi dasar Anda membangun bahasa ini.

@RyanCavanaugh beberapa masalah yang saya lihat:

  • Sintaks dekorator akan lebih sulit bagi IDE untuk memberikan penyelesaian kode (ya, saya kira itu bisa dilakukan tetapi mereka membutuhkan pengetahuan sebelumnya)
  • Kelas turunan dapat mengubah visibilitas suatu metode - yang dalam istilah ketat tidak boleh diizinkan
  • Sulit bagi dekorator untuk memeriksa daftar dan tipe argumen, dan tipe pengembalian yang kompatibel

Kompiler sudah memeriksa daftar argumen dan tipe pengembalian. Dan saya pikir meskipun override memang ada sebagai kata kunci kelas satu, kami tidak akan menerapkan beberapa aturan baru tentang kesamaan tanda tangan

Dan saya pikir bahkan jika override memang ada sebagai kata kunci kelas satu, kami tidak akan menerapkan beberapa aturan baru tentang kesamaan tanda tangan.

@RyanCavanaugh maka saya pikir Anda mungkin berada di halaman yang berbeda tentang maksud kata kunci. Inti dari itu adalah untuk kasus-kasus di mana Anda _ingin_ menegakkan identitas tanda tangan. Ini untuk pola desain di mana Anda memiliki hierarki kompleks yang mendalam yang duduk di kelas dasar yang mendefinisikan kontrak metode antarmuka, dan semua kelas dalam hierarki harus _ persis_ cocok dengan tanda tangan tersebut. Dengan menambahkan kata kunci override pada metode ini di semua kelas turunan, Anda diperingatkan untuk setiap kasus di mana tanda tangan menyimpang dari kontrak yang ditetapkan di kelas dasar.

Seperti yang terus saya katakan, ini bukan kasus penggunaan esoteris. Ketika bekerja pada basis kode yang besar (dengan katakanlah ratusan atau ribuan kelas) ini adalah kejadian _setiap hari_, yaitu seseorang perlu mengubah tanda tangan dari kelas dasar (memodifikasi kontrak), dan Anda ingin kompiler memperingatkan Anda untuk setiap kasus di seluruh hierarki yang tidak lagi cocok.

Kompiler sudah akan memperingatkan penggantian ilegal. Masalah dengan
implementasi saat ini adalah kurangnya niat saat mendeklarasikan override
metode.

Dekorator yang disebutkan sebelumnya sepertinya bertentangan dengan bahasanya
sedang berusaha untuk dicapai. Seharusnya tidak ada biaya runtime yang dikeluarkan untuk
sesuatu yang dapat ditangani pada waktu kompilasi, betapapun kecil biayanya.
Kami ingin menangkap sebanyak mungkin hal yang salah, tanpa perlu
jalankan kode untuk mencari tahu.

Dimungkinkan untuk mencapainya dengan menggunakan dekorator dan menyesuaikan tslint khusus
aturan, tetapi akan lebih baik untuk ekosistem perkakas dan komunitas untuk
bahasa untuk memiliki kata kunci resmi.

Saya pikir menjadi opsional adalah salah satu pendekatan, tetapi seperti dengan beberapa kompiler C++
ada flag yang dapat Anda atur yang memberlakukan penggunaannya (mis.
tidak konsisten-hilang-override). Ini tampaknya merupakan pendekatan terbaik untuk dihindari
melanggar perubahan dan tampaknya konsisten dengan fitur baru lainnya yang ditambahkan
baru-baru ini seperti tipe dan dekorator yang dapat dibatalkan.

Pada Rabu, 23 Mar 2016 pukul 21:31, Rowan [email protected] menulis:

Dan saya pikir bahkan jika override memang ada sebagai kata kunci kelas satu, kami
tidak akan memberlakukan beberapa aturan baru tentang kesamaan tanda tangan.

@RyanCavanaugh https://github.com/RyanCavanaugh maka saya pikir Anda mungkin
berada di halaman yang berbeda tentang maksud kata kunci. Inti dari
ini untuk kasus-kasus di mana Anda _ingin_ menerapkan kesamaan tanda tangan. Ini
adalah untuk pola desain di mana Anda memiliki hierarki kompleks yang dalam
pada kelas dasar yang mendefinisikan kontrak metode antarmuka, dan semua
kelas dalam hierarki harus _persis_ cocok dengan tanda tangan tersebut. Dengan menambahkan
kata kunci override pada metode ini di semua kelas turunan, Anda adalah
diperingatkan untuk setiap kasus di mana tanda tangan menyimpang dari kontrak yang ditetapkan
di kelas dasar.

Seperti yang terus saya katakan, ini bukan kasus penggunaan esoteris. Saat mengerjakan
basis kode besar (dengan katakanlah ratusan atau ribuan kelas) ini adalah _setiap
hari_ kejadian, yaitu seseorang perlu mengubah tanda tangan pangkalan
class (memodifikasi kontrak), dan Anda ingin kompiler memberi tahu Anda tentang
kasus di seluruh hierarki yang tidak lagi cocok.


Anda menerima ini karena Anda berlangganan utas ini.
Balas email ini secara langsung atau lihat di GitHub
https://github.com/Microsoft/TypeScript/issues/2000#issuecomment -200551774

@kungfusheep fwiw kompiler hanya menangkap kelas tertentu dari penggantian ilegal, di situlah ada bentrokan langsung antara params yang dideklarasikan. Itu _not_ menangkap penambahan atau penghapusan params, juga tidak menangkap perubahan tipe pengembalian. Pemeriksaan tambahan ini adalah apa yang akan diaktifkan oleh kata kunci penimpaan apa pun.

Kompiler dengan benar memperingatkan Anda jika Anda _menambahkan parameter ke fungsi utama.

Menghapus parameter, bagaimanapun, adalah _benar-benar valid_:

class BaseEventHandler {
  handleEvent(e: EventArgs, timestamp: number) { }
}

class DerivedEventHandler extends BaseEventHandler {
  handleEvent(e: EventArgs) {
    // I don't need timestamp, it's OK
  }
}

Mengubah tipe pengembalian juga _benar-benar valid_:

class Base {
  specialClone(): Base { ... }
}
class Derived extends Base {
  specialClone(): Derived { ... }
}

@RyanCavanaugh ya mereka valid dari perspektif bahasa yang ketat, tetapi inilah mengapa saya mulai menggunakan istilah "kontrak" di atas. Jika kelas dasar memberikan tanda tangan tertentu, ketika saya menggunakan override, saya menyatakan bahwa saya ingin kelas turunan saya secara ketat mengikuti tanda tangan itu. Jika kelas dasar menambahkan params tambahan atau mengubah tipe pengembalian, saya ingin tahu setiap titik dalam basis kode saya yang tidak lagi cocok, karena kontrak yang awalnya mereka lawan sekarang telah berubah dalam beberapa cara.

Gagasan memiliki hierarki kelas besar masing-masing dengan permutasi metode dasar yang sedikit berbeda (bahkan jika itu valid dari perspektif bahasa) adalah mimpi buruk yang mendorong dan membawa saya kembali ke masa lalu javascript yang buruk sebelum TypeScript datang :)

Jika opsionalitas adalah masalah utama dengan kata kunci, lalu mengapa tidak menjadikannya wajib ketika metode kelas dasar didefinisikan sebagai abstract ? Dengan cara ini, jika Anda ingin menerapkan pola secara ketat, Anda cukup menambahkan kelas dasar abstrak. Untuk menghindari pemecahan kode, sakelar kompiler dapat menonaktifkan pemeriksaan.

Kami tampaknya berbicara tentang dua set harapan yang berbeda sekarang. Ada situasi override/ilegal override yang hilang dan kemudian ada tanda tangan eksplisit. Bisakah kita semua setuju bahwa yang pertama adalah minimum mutlak yang kita harapkan dari kata kunci ini?

Saya hanya mengatakan ini karena ada cara lain untuk menerapkan tanda tangan metode eksplisit saat ini, seperti antarmuka, tetapi saat ini tidak ada cara untuk secara eksplisit menyatakan maksud untuk menimpa.

Oke, tidak ada cara untuk menerapkan tanda tangan eksplisit dari metode yang diganti tetapi mengingat bahwa kompiler memberlakukan bahwa setiap perubahan tanda tangan setidaknya 'aman' maka sepertinya ada percakapan terpisah yang bisa dilakukan seputar solusi untuk masalah itu.

Ya setuju. Jika saya harus memilih, situasi missing-override/ilegal override adalah masalah yang lebih penting untuk dipecahkan.

Saya agak terlambat ke pesta... Saya pikir inti dari TypeScript adalah untuk menegakkan aturan pada waktu kompilasi, bukan pada saat runtime, jika tidak, kita semua akan menggunakan Javascript biasa. Juga, agak aneh menggunakan peretasan/kludges untuk melakukan hal-hal yang standar dalam banyak bahasa.
Haruskah ada kata kunci override di TypeScript? Saya tentu percaya begitu. Haruskah itu wajib? Untuk alasan kompatibilitas, saya akan mengatakan bahwa perilakunya dapat ditentukan dengan argumen kompiler. Haruskah itu menegakkan tanda tangan yang tepat? Saya pikir ini harus menjadi diskusi terpisah, tetapi saya tidak punya masalah dengan perilaku saat ini sejauh ini.

Alasan asli untuk menutup ini tampaknya karena kami tidak dapat memperkenalkan perubahan yang melanggar bahwa semua metode yang diganti memerlukan specifier override , yang akan membuatnya membingungkan karena metode yang tidak ditandai dengan override juga dapat di fakta menjadi menimpa.

Mengapa tidak menerapkannya, baik dengan opsi kompiler, dan/atau jika ada _setidaknya satu_ metode yang ditandai override di kelas, maka semua metode yang ditimpa harus ditandai seperti itu, jika tidak, itu adalah kesalahan?

Apakah layak untuk membuka kembali ini saat sedang mengudara?

Pada Jumat, 8 Apr 2016 pukul 14:38, Peter Palotas [email protected] menulis:

Alasan asli untuk menutup ini tampaknya karena kami tidak dapat memperkenalkan
perubahan besar yang harus diganti oleh semua metode yang diganti
specifier, yang akan membuatnya membingungkan karena metode tidak ditandai dengan
'override' sebenarnya bisa juga override.

Mengapa tidak menerapkannya, baik dengan opsi kompiler, dan/atau jika ada di
setidaknya 'satu' metode ditandai override di kelas, maka semua metode yang
menimpa harus ditandai seperti itu, jika tidak, itu adalah kesalahan?


Anda menerima ini karena Anda disebutkan.
Balas email ini secara langsung atau lihat di GitHub
https://github.com/Microsoft/TypeScript/issues/2000#issuecomment -207434898

Saya katakan tolong buka kembali! Saya akan senang dengan opsi kompiler.

ya - silakan buka kembali

Buka kembali ini!

Tidakkah ES7 memerlukan overriding/overloading beberapa metode yang sama?
nama?
Pada 8 Apr 2016 10:56, "Aram Taieb" [email protected] menulis:

Ya, silakan buka kembali ini!


Anda menerima ini karena Anda berlangganan utas ini.
Balas email ini secara langsung atau lihat di GitHub
https://github.com/Microsoft/TypeScript/issues/2000#issuecomment -207466464

+1 - bagi saya ini adalah celah terbesar dalam keamanan tipe yang saya miliki dalam pengembangan TypeScript sehari-hari.

Setiap kali saya menerapkan metode siklus hidup React seperti "componentDidMount", saya mencari halaman dokumentasi reaksi yang relevan dan menyalin/menempelkan nama metode, untuk memastikan bahwa saya tidak salah ketik. Saya melakukan ini karena butuh 20 detik sedangkan bug dari kesalahan ketik tidak langsung dan halus, dan bisa memakan waktu lebih lama untuk dilacak.

kelas dengan 5 metode, yang 3 di antaranya ditandai override, tidak akan menyiratkan bahwa 2 lainnya tidak ditimpa. Untuk membenarkan keberadaannya, pengubah benar-benar perlu membagi dunia lebih bersih dari itu.

Jika ini yang menjadi perhatian utama, panggil kata kunci check_override untuk memperjelas bahwa Anda memilih untuk mengesampingkan pemeriksaan, dan implikasinya metode lain _tidak dicentang_ daripada _tidak diganti_.

Bagaimana dengan menggunakan implements. ? Menjadi sesuatu seperti ini:

class MyComponent extends React.Component<MyComponentProps, void>{
    implements.componentWillMount(){
        //do my stuff
    }
}

Sintaks ini memiliki beberapa keunggulan:

  • Ini menggunakan kata kunci yang sudah ada.
  • Setelah menulis implements. IDE memiliki peluang yang sangat baik untuk menampilkan popup pelengkapan otomatis.
  • kata kunci mengklarifikasi yang dapat digunakan untuk memaksa memeriksa metode abstrak kelas dasar tetapi juga mengimplementasikan antarmuka.
  • sintaksnya cukup ambigu untuk dapat digunakan juga untuk bidang, seperti ini:
class MyComponent<MyComponentProps, MyComponentState> {
    implements.state = {/*auto-completion for MyComponentState here*/};

    implements.componentWillMount(){
        //do my stuff
    }
}

Catatan: Atau kita bisa menggunakan base. , ini lebih penyortir dan lebih intuitif, tapi mungkin lebih membingungkan (mendefinisikan atau memanggil?) dan artinya tidak begitu kompatibel dengan implementasi antarmuka. Contoh:

class MyComponent<MyComponentProps, MyComponentState> {
    base.state = {/*auto-completion for MyComponentState here*/};

    base.componentWillMount(){ //DEFINING
        //do my stuff
        base.componentWillMount(); //CALLING
        //do other stuff
    }
}

Saya tidak berpikir implements cukup mencakup semua kasus penggunaan
disebutkan sebelumnya & itu sedikit ambigu. Itu tidak memberi lagi
informasi ke IDE yang override juga tidak bisa, jadi sepertinya
masuk akal untuk tetap menggunakan terminologi yang digunakan banyak bahasa lain
mencapai hal yang sama.
Pada Rabu, 13 Apr 2016 pukul 19:06, Olmo [email protected] menulis:

Bagaimana dengan menggunakan alat.? Menjadi sesuatu seperti ini:

kelas MyComponent memperluas React.Component{
implements.componentWillMount(){
//lakukan pekerjaanku
}
}

Sintaks ini memiliki beberapa keunggulan:

  • Ini menggunakan kata kunci yang sudah ada.
  • Setelah menulis alat. IDE memiliki peluang bagus untuk ditampilkan
    metode pelengkapan otomatis.
  • kata kunci mengklarifikasi yang dapat digunakan untuk memaksa memeriksa abstrak
    metode kelas dasar tetapi juga mengimplementasikan antarmuka.
  • sintaksnya cukup ambigu untuk dapat digunakan juga untuk bidang, seperti
    ini:

kelas Komponen Saya{
implements.state = {/_auto-completion for MyComponentState here_/};

implements.componentWillMount(){
    //do my stuff
}

}


Anda menerima ini karena Anda disebutkan.
Balas email ini secara langsung atau lihat di GitHub
https://github.com/Microsoft/TypeScript/issues/2000#issuecomment -209571753

Masalah dengan override adalah setelah Anda menggunakan kata kunci yang sama, Anda memiliki harapan yang sama:

  • itu harus wajib, setidaknya jika metodenya abstract .
  • Anda melewatkan beberapa kata kunci virtual untuk membubuhi keterangan metode yang dimaksudkan untuk diganti tetapi memiliki perilaku default.

Saya pikir tim TS tidak ingin menambahkan begitu banyak bagasi OO ke bahasa, dan saya pikir itu ide yang bagus.

Dengan menggunakan implements. Anda memiliki sintaks yang ringan untuk mendapatkan manfaat utama: pelengkapan otomatis dan nama yang diperiksa waktu kompilasi, tanpa membuat kata kunci baru atau menambah jumlah konsep.

Juga memiliki manfaat bekerja untuk kelas dan antarmuka, dan untuk metode (dalam prototipe) atau bidang langsung.

Cara membuat override wajib telah dibahas di utas dan solusinya tidak berbeda dengan fitur lain yang diterapkan oleh kompiler.

Kata kunci virtual tidak benar-benar masuk akal dalam konteks bahasa dan juga tidak dinamai dengan cara yang intuitif untuk orang yang belum pernah menggunakan bahasa seperti C++ sebelumnya. Mungkin solusi yang lebih baik untuk menyediakan jenis penjaga ini, jika diperlukan, adalah kata kunci final .

Saya setuju fitur bahasa tidak boleh ditambahkan dengan tergesa-gesa yang dapat membuat 'bagasi', tetapi override memasukkan lubang yang sah dalam sistem pewarisan yang dianggap perlu oleh banyak bahasa lain. Fungsionalitasnya paling baik dicapai dengan kata kunci baru, tentu saja ketika pendekatan alternatif menyarankan perubahan sintaksis mendasar.

Bagaimana dengan mengatur bidang yang diwarisi vs mendefinisikan ulang yang baru? React state adalah contoh yang bagus.

Atau menerapkan metode antarmuka opsional?

override berpotensi digunakan di bidang, jika dideklarasikan ulang
dalam tubuh subkelas. Metode antarmuka opsional tidak diganti begitu juga
di luar cakupan dari apa yang sedang kita bicarakan di sini.

Pada Kam, 14 Apr 2016 pukul 11:58, Olmo [email protected] menulis:

Bagaimana dengan mengatur bidang yang diwarisi vs mendefinisikan ulang yang baru? Keadaan reaksi
adalah contoh yang baik.

Atau menerapkan metode antarmuka opsional?


Anda menerima ini karena Anda disebutkan.
Balas email ini secara langsung atau lihat di GitHub
https://github.com/Microsoft/TypeScript/issues/2000#issuecomment -209879217

Metode antarmuka opsional tidak ditimpa, jadi berada di luar cakupan yang sedang kita bicarakan di sini.

Metode antarmuka opsional adalah konsep yang cukup unik untuk TypeScript sejauh yang saya tahu, dan saya pikir implementasi TypeScript dari "override" harus diterapkan pada mereka.

Dalam kasus metode siklus hidup React seperti componentDidMount, ini adalah metode antarmuka opsional yang tidak memiliki implementasi di superclass.

Dalam kasus metode siklus hidup React seperti componentDidMount, ini adalah metode antarmuka opsional yang tidak memiliki implementasi di superclass.

Tepat, jadi mereka tidak menimpa apa pun. Saya pikir kita menjadi bingung tentang apa yang dimaksudkan untuk disediakan oleh kata kunci override di sini, karena jika Anda hanya mencari cara untuk mendapatkan petunjuk kode/intellisense yang lebih baik, ada cara lain yang dapat dicapai tanpa menambahkan kata kunci baru untuk bahasa tersebut.

Teman-teman, dengan segala hormat, bisakah kita tetap fokus. Saya pikir membahas kata kunci alternatif kontraproduktif, terutama karena Masalah dimulai dengan permintaan khusus.

Bisakah kita semua setuju bahwa kita membutuhkan override dan meminta dengan baik tim TypeScript untuk menambahkannya atau membatalkan permintaan dan membiarkan Masalah ditutup?

Saya pikir selalu berguna untuk mengajukan permintaan dalam konteks apa masalahnya, selalu ada cara berbeda untuk menyelesaikan masalah. Poin penting dengan override adalah di sekitarnya menjadi wajib atau tidak (beberapa menyatakan bahwa itu tidak wajib dalam C++). Masalah yang dirujuk banyak orang adalah mendapatkan nama yang tepat untuk fungsi yang dapat ditimpa (yang mungkin atau mungkin tidak diimplementasikan di kelas super) - Fungsi siklus hidup React menjadi masalah utama.

Jika override tidak berhasil, maka mungkin kita harus menutup masalah ini dan membuka poin yang lebih umum tentang masalah ini. Masalah umum di sini adalah bahwa kontrak antarmuka dengan superclass tidak diketik dan tidak dicentang, dan itu membuat pengembang tersandung dan akan sangat bagus jika TS atau perkakas dapat membantu.

@ armandn Saya tidak berpikir kurangnya penutupan atau kebaikan saran kami adalah yang membuat permintaan ditolak, tetapi perbedaan antara semantik C # dan TS:

Penggantian metode dasar C #:

  • Memerlukan kata kunci override
  • Membuat rekor baru di VTable
  • Memeriksa abstrak/virtual wajib dalam metode dasar
  • Memeriksa tanda tangan yang identik
  • IDE: Memicu pelengkapan otomatis setelah penulisan override

Implementasi antarmuka C#:

  • Tidak ada kata kunci yang diperlukan, tetapi implementasi antarmuka eksplisit dimungkinkan menggunakan nama antarmuka.
  • Buat catatan antarmuka baru di VTable.
  • Memeriksa tanda tangan yang identik
  • IDE: QuickFix untuk mengimplementasikan antarmuka / mengimplementasikan antarmuka secara eksplisit.

Jadi perilaku dalam C # sangat berbeda tergantung jika Anda bertanya tentang kelas atau antarmuka, tetapi bagaimanapun Anda mendapatkan tiga manfaat utama:

  1. Memeriksa tanda tangan yang identik
  2. Memeriksa bahwa metode dapat/harus diganti (salah mengeja nama metode)
  3. Dukungan IDE menulis fungsi

Dengan semantik yang sedikit berbeda, kami sudah memiliki 1) di TS, sayangnya 2) dan 3) tidak ada. Alasan override ditolak, menurut pendapat saya, adalah karena sintaks yang serupa mengasumsikan perilaku yang serupa, dan ini tidak diinginkan karena:

Kelas dengan 5 metode, yang 3 di antaranya ditandai diganti, tidak akan menyiratkan bahwa 2 lainnya tidak diganti.

Kebetulan kita sudah memiliki 1) 2) dan 3) saat menulis _object literals_, tetapi tidak saat menulis anggota kelas yang memperluas kelas lain atau mengimplementasikan antarmuka.

Dengan pemikiran ini, saya pikir kita semua dapat menyetujui semantik ini:

  • _keyword_ harus cukup ambigu untuk menghindari masalah 'dunia terbelah'. (yaitu: check atau super. , atau MyInterface. )
  • Tidak ada pemeriksaan untuk abstract/virtual tetapi memeriksa keberadaan anggota di kelas dasar/antarmuka yang diimplementasikan. (Manfaat 2)
  • Memeriksa kompatibilitas tanda tangan yang cukup mirip seperti yang dilakukan TS sejauh ini. (Manfaat 1)
  • IDE: Memicu pelengkapan otomatis setelah kata kunci. (Manfaat 3)

Selain itu, saya pikir dua ini diperlukan untuk kelengkapan solusi*:

  • Ini harus bekerja untuk Kelas dan Antarmuka , karena TS terutama berbasis antarmuka dan kelas adalah jalan pintas untuk pewarisan prototipe. Diperlukan mempertimbangkan metode antarmuka opsional.
  • Ini harus bekerja untuk Metode dan Bidang , karena metode hanyalah fungsi yang berada di bidang.

Dua poin ini berguna dalam kasus penggunaan yang sangat nyata dari implementasi komponen React:

``` C#
kelas Komponen Saya{
implements.state = {/ pelengkapan otomatis untuk MyComponentState di sini /};

implements.componentWillMount(){
    //do my stuff
}

}
```

Solusi berbasis Anotasi dari @RyanCavanaugh tidak cukup karena:

  • Ini tidak akan bekerja dengan antarmuka.
  • Ini tidak akan bekerja dengan bidang.
  • Dengan asumsi infrastruktur seperti Roslyn untuk membantu perkakas, tidak ada cara untuk menambahkan QuickFix setelah menulis daftar pelengkapan otomatis setelah menulis @override .

Mengingat semantik, ini hanya tentang memilih sintaks yang tepat. Berikut beberapa alternatif:

  • override componentWillMount() : Intuitif tapi menyesatkan.
  • check componentWillMount() : Eksplisit tetapi memakan kata kunci.
  • super componentWillMount() :
  • implements componentWillMount() :
  • super.componentWillMount() :
  • implements.componentWillMount() :
  • this.componentWillMount() :
  • ReactComponent.componentWillMount() :

pendapat?

@olmobrutall ringkasan yang bagus. Beberapa poin:

Satu opsi kata kunci (diambil dari C#) akan menjadi baru - menunjukkan slot baru:

class MyComp extends React.Component<IProps,IState> {
...
    new componentWillMount() { ... }
    componentWillMount() { ...} // would compile, maybe unless strict mode is enabled
    new componentwillmount() { ... } <-- error

Poin saya yang lain adalah masalah dengan sifat wajib menggunakan yang di atas. Sebagai kontrak antara kelas super induk dan kelas turunan, masuk akal untuk menentukan titik antarmuka di mana sintaks di atas akan valid. Ini benar-benar poin ekstensi internal untuk kelas super, jadi kira-kira seperti:

class Component<P,S> {
    extendable componentWillMount() {...}
}

Hal yang sama akan berlaku untuk antarmuka juga.

Terima kasih :)

Bagaimana kalau hanya menulis this ?

class MyComponent<MyComponentProps, MyComponentState> {
    this.state = {/*auto-completion for MyComponentState here*/};

    this.componentWillMount(){
        //do my stuff
    }
}

Tentang extendable , sudah ada abstract di TS 1.6, dan menambahkan virtual mungkin akan membuat lagi masalah dunia terbelah?

Benar, saya memikirkan abstrak tetapi mungkin ada implementasi di kelas super, jadi itu tidak masuk akal. Begitu juga dengan virtual , karena itu akan menyiratkan anggota non-virtual tidak virtual - yang menyesatkan.

this. berfungsi, saya kira Anda bahkan bisa memiliki (sebagai bentuk panjang):

   this.componentWillMount = () => { }

Satu-satunya masalah dengan ini adalah bahwa itu hanya boleh dicakup ke titik ekstensi yang dialokasikan, tidak semua anggota kelas dasar.

apa yang sedang terjadi...

TypeScript tidak dan tidak pernah menjadi versi javascript dari C#. Jadi alasannya bukan karena fungsionalitas yang disarankan berbeda dari semantik C#. Alasan penutupan seperti yang diungkapkan Ryan dan kemudian diklarifikasi oleh Daniel R adalah

Seperti yang dijelaskan Ryan, masalahnya adalah menandai suatu metode sebagai penimpaan tidak berarti bahwa metode lain bukanlah penimpaan. Satu-satunya cara yang masuk akal adalah jika semua penggantian perlu ditandai dengan kata kunci override (yang jika kami diamanatkan, akan menjadi perubahan yang melanggar).

Namun Anda masih bertahan dengan masalah Anda seputar pelengkapan otomatis. Pelengkapan otomatis tidak memerlukan kata kunci baru dalam bahasa tersebut untuk memberikan saran yang lebih baik kepada Anda.

Alasan utas ini ada adalah untuk mendapatkan kesalahan kompiler ketika suatu fungsi ditimpa secara ilegal atau ketika suatu metode dinyatakan sebagai penimpaan tetapi tidak benar-benar menimpa fungsi di kelas dasar. Ini adalah konsep sederhana dan terdefinisi dengan baik di banyak bahasa yang juga mendukung sebagian besar fitur bahasa yang ditawarkan TypeScript. Itu tidak perlu menyelesaikan semua masalah dunia, itu hanya perlu menjadi kata kunci override, untuk override.

Sejak saat itu semakin banyak orang yang menunjukkan minat pada proposal asli, jadi mari kita tetap berpegang pada topik yang diangkat untuk masalah tersebut dan mengangkat masalah baru untuk ide-ide baru.

TypeScript tidak dan tidak pernah menjadi versi javascript dari C#.

Saya membandingkannya dengan C# sebagai bahasa referensi, karena saya menganggap ini adalah perilaku yang kalian asumsikan.

Pelengkapan otomatis tidak memerlukan kata kunci baru dalam bahasa tersebut untuk memberikan saran yang lebih baik kepada Anda.

Bagaimana Anda menyarankannya untuk dipicu? Jika Anda menampilkan kotak kombo pelengkapan otomatis saat menulis nama acak dalam konteks kelas akan sangat mengganggu ketika kita hanya ingin mendeklarasikan bidang atau metode baru.

Alasan utas ini ada adalah untuk mendapatkan kesalahan kompiler ketika suatu fungsi ditimpa secara ilegal atau ketika suatu metode dinyatakan sebagai penimpaan tetapi tidak benar-benar menimpa fungsi di kelas dasar.

Saya benar-benar menyertakan kasus penggunaan ini, di bawah Manfaat 2 .

Itu tidak perlu menyelesaikan semua masalah dunia, itu hanya perlu menjadi kata kunci override, untuk override.

Jadi saran Anda adalah memperbaiki _satu langkah pada satu waktu_ alih-alih mundur satu langkah dan melihat masalah yang serupa/terkait?. Itu mungkin ide yang bagus untuk Scrum Board Anda tetapi tidak untuk mendesain bahasa.

Alasan mereka konservatif dalam menambahkan kata kunci adalah karena mereka tidak dapat menghapus fitur dari suatu bahasa.

Beberapa kesalahan desain dalam C# karena kurangnya penyelesaian:

  • Co-varians / kontra-varians Array tidak aman.
  • var hanya berfungsi untuk tipe variabel, bukan untuk parameter generik otomatis atau tipe pengembalian. Mungkinkah auto akan menjadi kata kunci yang lebih baik seperti di C++?

Coba gunakan React sebentar dan Anda akan melihat sisi lain dari gambar.

Mengganti metode yang telah diimplementasikan di kelas dasar dan mengimplementasikan metode antarmuka adalah _ dua hal yang sama sekali berbeda _. Jadi ya, yang disarankan adalah memperbaiki salah satu skenario tersebut dengan kata kunci khusus alih-alih mencoba membuat beberapa kata kunci Swiss-Army.

Apa gunanya kata kunci yang dapat berarti salah satu dari 3 hal berbeda bagi pengembang yang membaca beberapa kode untuk pertama kalinya? Ini ambigu dan membingungkan, terutama jika Anda berbicara tentang menggunakan kata kunci seperti this yang sudah melakukan hal lain (sama sekali tidak terkait!) dalam bahasa - itu tidak bisa lebih umum, hampir tidak berguna.

Jika perhatian utama Anda adalah pelengkapan otomatis, editor memiliki cukup informasi _sekarang_ untuk dapat menyarankan metode dari kelas dasar dan mengimplementasikan antarmuka 'saat Anda mengetik'.

Mengganti metode yang telah diimplementasikan di kelas dasar dan mengimplementasikan metode antarmuka adalah dua hal yang sangat berbeda.

Dalam kasus umum ya, tetapi kita tidak berbicara tentang mengimplementasikan metode antarmuka apa pun. Kita berbicara tentang metode antarmuka opsional _di mana kelas induk mengimplementasikan antarmuka_. Dalam hal ini Anda dapat mengatakan bahwa 1) antarmuka mengizinkan metode untuk diimplementasikan sebagai undefined , 2) kelas induk memiliki implementasi yang tidak ditentukan, dan 3) kelas anak mengesampingkan implementasi yang tidak ditentukan dengan implementasi metode.

@olmobrutall Saya pikir komentar Anda tentang mendesain bahasa dan bagaimana itu bukan papan scrum sedikit mementingkan diri sendiri. Saya telah melihat sekitar empat pembaruan untuk TS dalam waktu kurang dari setahun.

Jika desain bahasa dipertimbangkan sebaik yang Anda maksudkan, maka akan ada dokumen spesifikasi bahasa yang memberi tahu kami dengan tepat bagaimana penggantian harus bekerja, dan kami bahkan mungkin tidak akan melakukan percakapan ini.

Saya tidak membuat komentar ini dengan penghinaan apa pun dari pengembang/desainer TS, karena TS sudah sangat baik dan saya akan takut harus menggunakan JS standar sebagai gantinya.

Ya TS bukan C# dan bukan C++. Tetapi sejumlah bahasa telah memilih kata kunci override untuk memenuhi tujuan yang dibahas di sini sehingga tampaknya kontraproduktif untuk menyarankan sintaks yang benar-benar asing.

Masalah utama tampaknya tidak ingin memperkenalkan perubahan yang melanggar. Jawaban sederhananya adalah flag compiler, akhir cerita. Untuk beberapa orang seperti saya, kata kunci override opsional tidak berguna. Bagi yang lain, mereka ingin memperindah kode mereka secara bertahap. Bendera kompiler memecahkan dilema.

Perbedaan tanda tangan adalah percakapan yang berbeda. Kata kunci baru tampaknya tidak perlu karena JS tidak dapat mendukung beberapa metode dengan nama yang sama (kecuali TS membuat nama rusak yang diturunkan dari tanda tangan a'la C++, yang sangat tidak mungkin).

Saya telah melihat sekitar empat pembaruan untuk TS dalam waktu kurang dari setahun.

Saya tidak bermaksud Anda tidak bisa cepat dan beralih dengan cepat. Saya sama senangnya dengan siapa pun karena ES6 dan TS berkembang pesat. Maksud saya adalah Anda harus mencoba memprediksi masa depan, untuk menghindari bahasa yang buntu.

Saya dapat menyetujui penggunaan kata kunci override . Dengan argumen yang tepat bahkan menjaga bidang dan antarmuka di luar ruang lingkup, tetapi saya tidak setuju dengan argumen '_mari tetap fokus dan selesaikan masalah khusus ini dengan cara bahasa lain melakukannya tanpa terlalu banyak berpikir_'.

Tetapi sejumlah bahasa telah memilih kata kunci override untuk memenuhi tujuan yang dibahas di sini sehingga tampaknya kontraproduktif untuk menyarankan sintaks yang benar-benar asing.

Tak satu pun dari bahasa ini memiliki pewarisan prototipe atau metode opsional (metode yang bukan abstrak atau virtual, mereka hanya _tidak ada_ saat runtime), dan ini adalah masalah terkait yang harus didiskusikan (dan mungkin dibuang) sebelum membuat komitmen.

Dengan kata lain: katakanlah kami melakukan seperti yang Anda sarankan dan kami menerapkan override tanpa banyak berpikir. Kemudian saya, atau siapa pun yang menggunakan TSX, menambahkan masalah mengapa override tidak bekerja dengan komponen React. Apa rencanamu?

Dalam kasus umum ya, tetapi kita tidak berbicara tentang mengimplementasikan metode antarmuka apa pun. Kita berbicara tentang metode antarmuka opsional di mana kelas induk mengimplementasikan antarmuka.

Tidak masalah di mana antarmuka diatur, faktanya mereka bukan hal yang sama dan karenanya tidak boleh membagikan kata kunci karena _intent_ program tidak jelas.

Anda dapat, misalnya, mengganti metode yang telah diterapkan untuk kepatuhan antarmuka di kelas dasar; Jika kita meletakkan semua telur kita dalam satu kata kunci untuk dua hal yang berbeda ini, bagaimana orang akan tahu jika ini adalah deklarasi awal dari fungsi itu atau menimpa yang sebelumnya didefinisikan di kelas dasar? Anda tidak akan melakukannya, dan tidak mungkin mengetahuinya tanpa pemeriksaan lebih lanjut dari kelas dasar - yang bahkan mungkin ada dalam file .d.ts pihak ke-3 & dengan demikian menjadikannya mimpi buruk mutlak untuk ditemukan, tergantung seberapa dalam dalam rantai pewarisan fungsi awalnya diimplementasikan.

Dengan kata lain: katakanlah kami melakukan seperti yang Anda sarankan dan kami menerapkan override tanpa banyak berpikir. Kemudian saya, atau siapa pun yang menggunakan TSX, menambahkan masalah mengapa override tidak berfungsi dengan komponen React. Apa rencanamu?

Mengapa ini perlu memperbaiki Bereaksi? Jika React memiliki masalah yang berbeda dengan apa yang coba diselesaikan maka saya tidak dapat seumur hidup memahami mengapa override perlu memperbaikinya? Sudahkah Anda mencoba membuka masalah lain untuk menyarankan sesuatu dilakukan tentang implementasi antarmuka?

Saya tidak akan setuju bahwa pemikiran yang cukup belum dimasukkan ke dalam ini. Kami menyarankan teknik yang telah dicoba dan diuji yang berhasil dalam setiap bahasa lain yang dapat saya pikirkan yang menerapkannya.

faktanya mereka bukan hal yang sama sehingga tidak boleh membagikan kata kunci karena maksud programnya tidak jelas.

Tidak? Lihatlah dua definisi alternatif dari BaseClass

class BaseClass {
     abstract myMethod(); 
}
interface ISomeInterface {
     myMethod?(); 
}

class BaseClass extends ISomeInterface {
}

Dan kemudian dalam kode Anda, Anda melakukan:

``` C#
kelas BetonKelas {
menimpa myMethod() {
// Lakukan sesuatu
}
}

You think it should work in just one case and not in the other? The effect is going to be 100% identical in Javascript (creating a new method in ConcreteClass prototype), from the external interface and from the tooling perspective. 

Even more, maybe you want to capture `this` inside of the method, implementing it with a lambda (useful for React event handling). In this case you'll write something like this:

``` C#
class ConcreteClass {
    override myMethod = () => { 
         // Do stuff
    }
}

Perilaku akan kembali identik jika metodenya abstrak atau berasal dari antarmuka: tambahkan bidang di kelas dengan lambda yang mengimplementasikannya. Tapi kelihatannya agak aneh untuk override sebuah bidang, karena Anda baru saja menetapkan nilai.

Tidak mari kita melihatnya menggunakan super. (sintaks favorit saya sekarang, tapi saya terbuka untuk alternatif).

``` C#
kelas BetonKelas {
super.myMethod() { //metode dalam prototipe
// Lakukan sesuatu
}

super.myMethod = () => {  //method in lambda
     // Do stuff
}

}
```

Sekarang konsepnya secara konseptual lebih sederhana: Kelas super saya mengatakan bahwa ada metode atau bidang dan ConcreteClass dapat mendefinisikannya dalam prototipe / menetapkannya / membacanya / menyebutnya.

Mengapa ini perlu memperbaiki Bereaksi?

Tidak hanya bereaksi, lihat sudut:

  • Bereaksi : 58 antarmuka dan 3 kelas.
  • Sudut : 108 antarmuka dan 11 kelas.

Tentu saja sebagian besar antarmuka tidak dimaksudkan untuk diimplementasikan, tidak juga semua kelas untuk ditimpa, tetapi satu hal yang jelas: dalam TypeScript, antarmuka lebih penting daripada kelas.

Sudahkah Anda mencoba membuka masalah lain untuk menyarankan sesuatu dilakukan tentang implementasi antarmuka?

Bagaimana saya harus menyebutnya? override untuk antarmuka dan bidang?

Kami menyarankan teknik yang telah dicoba dan diuji yang berhasil dalam setiap bahasa lain yang dapat saya pikirkan.

Bahasa yang Anda pikirkan sangat berbeda. Mereka memiliki warisan yang berbasis di VTable statis.

Dalam TypeScript, kelas hanyalah antarmuka + pewarisan metode prototipe otomatis. Dan metode hanyalah bidang dengan fungsi di dalamnya.

Untuk mengadaptasi fitur override ke TS, dua perbedaan mendasar ini harus dipertimbangkan.

Tolong @kungfusheep berusaha memikirkan solusi untuk masalah saya. Jika Anda ingin menambahkan kata kunci yang berbeda dan menerapkannya pada tahap kedua tidak apa-apa, tetapi luangkan waktu sejenak untuk membayangkan bagaimana seharusnya.

Saya tidak bisa memikirkan cara lain untuk mengatakan apa yang sudah saya katakan. Mereka tidak sama. Mereka serupa, tetapi tidak sama. Silakan lihat komentar ini oleh salah satu TS devs RE: kata kunci readonly - https://github.com/Microsoft/TypeScript/pull/6532#issuecomment -179563753 - yang memperkuat apa yang saya katakan.

Saya setuju dengan ide umum, tetapi mari kita lihat dalam praktiknya:

class MyComponent extends React.Component<{ prop : number }, { value: string; }> {

    //assign a field defined in the base class without re-defining it (you want type-checking)
    assign state = { value : number}; 

    //optional method defined in an interface implemented by the base class    
    implement componentDidMount(){ 
    }

    //abstract method defined in the base class 
    override render(){  
    }
}

Ini terlihat seperti VB atau Cobol, bukan?

Sepertinya masuk akal, setidaknya.

Pertimbangkan contoh ini, apakah hanya ada kata kunci override (atau hanya satu).

interface IDo {
    do?() : void;
}
class Component implements IDo {
    protected commitState() : void {
        /// do something
    }
    override public do() : void {
        /// base implements 'do' in this case
    }
}

Sekarang mari kita implementasikan komponen kita sendiri menggunakan apa yang baru saja kita tulis.

class MyComponent extends Component {
    override protected commitState(){
        /// do our own thing here
        super.commitState();
    }
    override do() : void {
        /// this is ambiguous. Am I implementing this from an interface or overriding a base method? I have no way of knowing. 
    }

}

Salah satu cara untuk mengetahuinya adalah dengan tipe super :

  override do() : void {
        super.do(); // this compiles, if it was an interface then super wouldn't support `do`
    }

tepat! yang berarti desainnya salah. seharusnya tidak ada investigasi yang terlibat dengan kode skimming, seharusnya masuk akal ketika Anda membacanya.

ini ambigu. Apakah saya mengimplementasikan ini dari antarmuka atau mengganti metode dasar? Saya tidak punya cara untuk mengetahuinya.

Apa perbedaan dalam praktik? Objek hanya memiliki satu spasi nama dan Anda hanya dapat menempatkan satu lambda di bidang do . Bagaimanapun, tidak ada cara untuk mengimplementasikan antarmuka secara eksplisit. Implementasinya akan menjadi:

MyComponent.prototype.do = function (){
    //your stuff
}

terlepas dari apa yang Anda tulis.

Tidak peduli apa outputnya. Anda bisa secara sengaja atau tidak sengaja mengesampingkan beberapa fungsi di kelas dasar, tetapi tidak ada maksud dalam kata kunci yang ambigu.

Kesalahan atau perilaku tak terduga apa yang akan diselesaikan dengan memiliki dua kata kunci?

Ayo sekarang sobat. Kau jelas pria yang pintar. Saya baru saja mengatakan "secara tidak sengaja mengesampingkan beberapa fungsi di kelas dasar"; tidak bisakah kita menyimpulkan dari sini setiap perilaku tak terduga yang berpotensi terjadi begitu saja?

Agar jelas, saya tidak mengusulkan mengubah masalah ini menjadi proposal untuk dua kata kunci. Masalah ini untuk kata kunci override - hal lain akan memerlukan proposal baru dan diskusinya sendiri tentang semantik.

Agar jelas, saya tidak mengusulkan mengubah masalah ini menjadi proposal untuk dua kata kunci. Masalah ini untuk kata kunci override - hal lain akan memerlukan proposal baru dan diskusinya sendiri tentang semantik.

Tidak terlalu penting berapa banyak isu yang harus didiskusikan, atau dari siapa ide itu datang. Anda mengusulkan untuk membagi dua pemikiran yang sangat terkait dan bahkan tidak mempertimbangkan sintaks yang konsisten.

Argumen apakah kita membutuhkan 1, 2 atau 3 kata kunci milik utas ini dan belum selesai (...tetapi menjadi berulang). Kalau begitu mungkin kita bisa membahas sintaksnya di utas lain (karena semantiknya akan sama :P).

Dalam contoh saya:

class MyComponent extends React.Component<{ prop : number }, { value: string; }> {

    //assign a field defined in the base class without re-defining it (you want type-checking)
    assign state = { value : number}; 

    //optional method defined in an interface implemented by the base class    
    implement componentDidMount(){ 
    }

    //abstract method defined in the base class 
    override render(){  
    }
}

Jangan assign , implement dan override melakukan hal yang persis sama: Periksa apakah namanya ada (di kelas dasar, antarmuka yang diimplementasikan, antarmuka yang diimplementasikan oleh basis kelas, dll...).

Jika ada konflik nama antara kelas dasar dan beberapa antarmuka yang diimplementasikan, Anda akan mendapatkan kesalahan waktu kompilasi baik dengan 1, 2 atau tanpa kata kunci sama sekali.

Juga pikirkan tentang objek literal:

var mc = new MyComponent(); 
mc.state = null;
mc.componentDidMount =null;
mc.render = null;

Dengan sintaks yang sama persis, saya dapat menetapkan ulang bidang atau metode secara independen jika berasal dari kelas dasar, implementasi antarmuka langsung, atau antarmuka yang diimplementasikan di kelas dasar.

Jangan menetapkan, mengimplementasikan, dan mengganti lakukan hal yang persis sama: Periksa apakah namanya ada (di kelas dasar, antarmuka yang diimplementasikan, antarmuka yang diimplementasikan oleh kelas dasar, dll...).

Anda baru saja menjelaskan 3 skenario berbeda di sana, jadi jelas mereka tidak sama. Saya punya perasaan saya bisa menjelaskan mengapa mereka berbeda dengan Anda sepanjang hari dan Anda masih akan duduk berdebat bahwa mereka tidak, jadi saya akan keluar dari garis diskusi khusus ini untuk saat ini. Tidak ada yang mengatakan orang-orang TS masih mempertimbangkan ini saat ini.

Dengan ditutupnya #6118 saya pikir ada alasan untuk membahas apakah masalah di sana dan masalah di sini dapat diatasi secara bersamaan.

Saya tidak mengetahui https://github.com/Microsoft/TypeScript/pull/6118. Idenya tampak seperti alternatif yang memungkinkan untuk menambahkan override .

Jika saya memahami masalahnya dengan benar, masalahnya adalah Anda dapat memiliki lebih dari satu kelas dasar/antarmuka dengan deklarasi anggota yang kompatibel tetapi tidak identik, dan mereka harus disatukan ketika diinisialisasi di kelas anak tanpa tipe.

Tanpa mengetahui konsekuensinya, saya akan senang jika:

  • anggota akan menghasilkan kesalahan waktu kompilasi yang memerlukan deklarasi tipe eksplisit pada kelas anak
  • anggota akan mengambil persimpangan dari semua jenis yang mungkin.

IMO yang lebih penting adalah memiliki beberapa cara untuk memicu penyelesaian otomatis saat menulis anggota kelas (Ctrl + Spasi). Saat kursor berada langsung di dalam kelas, Anda bisa mendefinisikan anggota baru dari mendefinisikan ulang yang diwariskan, sehingga pelengkapan otomatis tidak boleh terlalu agresif, tetapi dengan pemicuan manual, perilakunya akan baik-baik saja.

Mengenai komentar @RyanCavanaugh :

Kami benar-benar memahami kasus penggunaan di sini. Masalahnya adalah menambahkannya pada tahap ini dalam bahasa menambah lebih banyak kebingungan daripada menghilangkannya. Kelas dengan 5 metode, yang 3 di antaranya ditandai diganti, tidak akan menyiratkan bahwa 2 lainnya tidak diganti. Untuk membenarkan keberadaannya, pengubah benar-benar perlu membagi dunia lebih bersih dari itu.

Mengetik variabel sebagai any tidak menyiratkan bahwa beberapa variabel lain juga any . Tapi, ada compiler flat --no-implicit-any untuk memaksa kita mendeklarasikannya secara eksplisit. Kita bisa sama-sama memiliki --no-implicit-override , yang akan saya aktifkan jika tersedia.

Memiliki kata kunci override yang digunakan memberi pengembang banyak wawasan saat membaca kode yang tidak mereka kenal, dan kemampuan untuk menerapkannya akan memberikan kontrol waktu kompilasi tambahan.

Untuk semua +1 -- dapatkah Anda berbicara tentang apa yang tidak cukup tentang solusi dekorator yang ditunjukkan di atas?

Apakah ada cara di mana dekorator solusi yang lebih baik daripada kata kunci override? Ada banyak alasan mengapa ini lebih buruk: 1) Ia menambahkan overhead runtime betapapun kecilnya; 2) Ini bukan pemeriksaan waktu kompilasi; 3) Saya harus memasukkan kode ini ke setiap perpustakaan saya; 4) Tidak ada cara untuk menangkap fungsi yang tidak memiliki kata kunci override.

Mari kita beri contoh. Saya memiliki perpustakaan dengan tiga kelas ChildA , ChildB , Base . Saya telah menambahkan beberapa metode doSomething() ke ChildA dan ChildB . Setelah beberapa refactoring, saya telah menambahkan beberapa logika tambahan, dioptimalkan, dan memindahkan doSomething() ke kelas Base . Sementara itu, saya memiliki proyek lain yang bergantung pada perpustakaan saya. Di sana saya punya ChildC dengan doSomething() . Tidak ada cara ketika saya memperbarui dependensi saya untuk mengetahui bahwa ChildC sekarang secara implisit mengesampingkan doSomething() , tetapi dengan cara yang tidak dioptimalkan yang juga kehilangan beberapa pemeriksaan. Itulah sebabnya seorang dekorator @overrides tidak akan pernah cukup.

Yang kita butuhkan adalah kata kunci override , dan flag compiler --no-implicit-override .

Kata kunci override akan banyak membantu saya karena saya menggunakan hierarki sederhana dari kelas dasar dalam proyek saya untuk membuat semua komponen saya. Masalah saya terletak pada kenyataan bahwa komponen ini mungkin perlu mendeklarasikan metode untuk digunakan di tempat lain, dan metode ini mungkin atau mungkin tidak didefinisikan dalam kelas induk dan mungkin atau mungkin belum melakukan hal-hal yang saya butuhkan.

Sebagai contoh, katakanlah sebuah fungsi validate mengambil kelas dengan metode getValue() sebagai parameter. Untuk membangun kelas ini, saya dapat mewarisi kelas lain yang sudah dapat mendefinisikan metode getValue() ini, tetapi saya tidak dapat benar-benar mengetahuinya kecuali saya melihat kode sumbernya. Apa yang secara naluriah saya lakukan adalah menerapkan metode ini di kelas saya sendiri, dan selama tanda tangannya benar, tidak ada yang akan memberi tahu saya apa pun.

Tapi mungkin aku tidak seharusnya melakukan itu. Semua kemungkinan berikut menganggap bahwa saya melakukan penggantian implisit:

  1. Kelas dasar sudah mendefinisikan metode ini seperti yang saya lakukan, jadi saya menulis fungsi tanpa biaya. Tidak banyak masalah.
  2. Saya salah menulis ulang fungsinya, sebagian besar waktu karena saya lupa melakukan beberapa hal yang tidak jelas yang sudah ditangani oleh kelas dasar. Apa yang benar-benar perlu saya lakukan di sini adalah memanggil super dalam penggantian saya, tetapi tidak ada yang menyarankan saya untuk melakukannya.

Memiliki kata kunci penimpaan wajib akan memberi tahu saya "hei, Anda menimpa, jadi mungkin Anda harus memeriksa seperti apa metode aslinya sebelum melakukan hal-hal Anda". Itu akan sangat meningkatkan pengalaman saya dengan warisan.

Tentu saja, seperti yang disarankan, itu harus ditempatkan di bawah bendera --no-implicit-override karena itu akan menjadi perubahan yang melanggar dan kebanyakan orang tidak terlalu peduli dengan semua ini.

Saya suka @eggers perbandingan yang dibuat dengan any dan --no-implicit-any karena ini adalah jenis anotasi yang sama dan akan bekerja dengan cara yang sama persis.

@olmobrutall Saya melihat beberapa pembicaraan Anda tentang mengesampingkan metode abstrak & metode antarmuka.

Jika pendapat saya, override menyiratkan keberadaan super. Baik metode abstrak maupun metode yang didefinisikan dalam antarmuka tidak dapat dipanggil melalui panggilan super. , dan karenanya tidak boleh ditimpa ( tidak ada yang perlu ditimpa). Sebaliknya jika kita melakukan sesuatu yang lebih eksplisit untuk kasus tersebut, itu harus berupa kata kunci implements . Tapi, itu akan menjadi diskusi fitur terpisah.

Berikut adalah masalah seperti yang saya lihat, peringkat yang paling penting hingga yang paling tidak penting. Apakah saya melewatkan sesuatu?

Kegagalan untuk menimpa

Sangat mudah untuk _think_ Anda mengganti metode kelas dasar saat Anda tidak:

class Base {
  hasFilename(f: string) { return true; }
}
class Derived extends Base {
  // oops
  hasFileName(f: string) { return false; }
}

Ini mungkin masalah terbesar.

Kegagalan untuk menerapkan

Ini sangat terkait erat, terutama ketika antarmuka yang diimplementasikan memiliki properti opsional:

interface NeatMethods {
  hasFilename?(f: string): boolean;
}
class Mine implements NeatMethods {
  // oops
  hasFileName(f: string) { return false; }
}

Ini adalah masalah yang kurang penting daripada _failure to override_, tetapi masih buruk.

Penimpaan yang tidak disengaja

Dimungkinkan untuk mengganti metode kelas dasar tanpa menyadarinya

class Base {
  hasFilename(f: string) { return true; }
}
class Derived extends Base {
  // I didn't know there was a base method with this name, so oops?
  hasFilename(f: string) { return true; }
}

Ini seharusnya relatif jarang hanya karena ruang kemungkinan nama metode sangat besar, dan kemungkinan Anda juga akan menulis tanda tangan yang kompatibel _dan_ tidak berarti telah melakukan ini sejak awal rendah.

Kebetulan any s

Sangat mudah untuk berpikir bahwa metode override akan mendapatkan tipe parameter dari basisnya:

class Base {
  hasFilename(f: string) { return true; }
}
class Derived extends Base {
  // oops
  hasFilename(f) { return f.lentgh > 0; }
}

Kami mencoba mengetik parameter ini secara otomatis tetapi mengalami masalah. Jika hasFilename secara eksplisit ditandai override kita mungkin dapat menyelesaikan masalah tersebut dengan lebih mudah.

Keterbacaan

Tidak segera jelas kapan suatu metode menimpa metode kelas dasar dan kapan tidak. Ini sepertinya masalah yang lebih kecil karena ada solusi yang masuk akal yang tersedia saat ini (komentar, dekorator).

Pengalaman editor

Anda harus mengetikkan nama metode dasar secara manual saat menulis metode penggantian kelas turunan, yang menjengkelkan. Kami dapat dengan mudah memperbaikinya dengan selalu memberikan nama-nama itu di daftar penyelesaian (saya pikir kami sebenarnya sudah memiliki bug dalam hal ini), jadi kami tidak benar-benar membutuhkan fitur bahasa untuk memperbaiki masalah ini.

Non-masalah

Mencantumkan ini sebagai hal-hal yang termasuk dalam masalah terpisah atau tidak akan terjadi:

  • Pencocokan tanda tangan yang tepat -- ini bukan sesuatu yang harus dicampur dengan override , dan ini benar-benar memiliki semantik yang tidak diinginkan dalam banyak skenario bagus
  • Sintaks eksotis tidak memadai (misalnya implements.foo = ... ). Tidak perlu bersepeda di sini

@RyanCavanaugh Saya setuju dengan semua hal di atas tentang urutan itu juga. Sama seperti komentar, saya tidak berpikir bahwa kata kunci override harus menyelesaikan masalah "kegagalan untuk mengimplementasikan". Seperti yang saya katakan di atas, saya pikir masalah itu harus diselesaikan dengan kata kunci yang berbeda (misalnya implements ) sebagai bagian dari tiket yang berbeda. ( override harus menyiratkan metode super. )

@RyanCavanaugh Saya setuju dengan semua poin tetapi saya tidak melihat referensi apa pun ke masalah yang juga sangat terkait dari bidang yang diinisialisasi yang dideklarasikan dalam tipe induk tanpa mengesampingkan tipe (dan kemudian kehilangan pengecekan tipe). Saya pikir fitur tidak boleh terbatas pada deklarasi metode dalam bahasa di mana metode hanya berfungsi di bidang objek.

@eggers bahkan jika pada akhirnya diperlukan dua kata kunci, semantiknya akan sangat mirip dan saya pikir fitur-fiturnya harus dibahas bersama.

menimpa harus menyiratkan super. metode

Dalam C# override dapat (dan harus) digunakan juga untuk metode abstrak (tanpa .super). Di Java @override adalah atribut. Tentu saja mereka adalah bahasa yang berbeda tetapi dengan kata kunci yang sama Anda mengharapkan perilaku yang serupa.

Saya setuju dengan poin @RyanCavanaugh , meskipun saya akan mengatakan masalah "penggantian yang tidak disengaja" mungkin sedikit lebih umum daripada yang dia yakini (terutama ketika berurusan dengan memperluas kelas yang sudah memperluas sesuatu yang lain, seperti komponen Bereaksi yang sudah tentukan metode componentWillMount di ekstensi pertama).

Saya percaya, seperti yang @eggers katakan, bahwa masalah "kegagalan untuk mengimplementasikan" adalah sesuatu yang sangat berbeda, dan akan terasa aneh menggunakan kata kunci override untuk metode yang tidak ada di kelas induk. Saya tahu ini adalah bahasa yang berbeda dengan masalah yang berbeda, tetapi di C# override tidak digunakan untuk antarmuka. Saya akan menyarankan, jika kita bisa mendapatkan flag --no-implicit-override , metode antarmuka itu harus diawali dengan nama antarmuka (yang akan sangat mirip dengan C# dan dengan demikian terasa lebih alami).

Sesuatu seperti

interface IBase {
    method1?(): void
}

class Base {
    method2() { return true; }
}

class Test extends Base implements IBase {
    IBase.method1() { }
    override method2() { return true; }
}

Saya pikir @RyanCavanaugh mencantumkan masalah mendasar. Saya akan melengkapinya dengan "Apa perbedaan pendapatnya":

  • Apakah mendeklarasikan metode yang cocok dengan antarmuka dianggap sebagai override ? Sebagai contoh:
    interface IDrawable
    {
        draw?( centerPoint: Point ): void;
    }

    class Square implements IDrawable
    {
        draw( centerPoint: Point ): void; // is this an override?
    }
  • apakah mendeklarasikan properti yang cocok dengan antarmuka dianggap sebagai override ?
    interface IPoint2
    {
        x?: number;
        y?: number;
    }

    class Circle implements IPoint2
    {
        x: number; // is this an override?
        y: number; // is this an override?
        radius: number;
    }
  • Apakah diperlukan semacam kata kunci implements , untuk menangani kasus di atas alih-alih kata kunci override , dan bentuknya.

@kevmeister68 terima kasih telah secara eksplisit menyatakan ketidaksepakatan.

Satu hal: Anda harus membuat anggota antarmuka opsional untuk benar-benar menunjukkan masalah, seperti ini kompiler TypeScript akan mengeluh ketika bidang atau metode tidak diterapkan, memecahkan "Kegagalan untuk mengimplementasikan".

@olmobrutall Terima kasih untuk itu. Saya tidak pernah mendeklarasikan metode opsional - mungkinkah (saya berasal dari latar belakang C# dan tidak ada konsep seperti itu)? Saya telah memperbarui contoh yang saya cantumkan di atas untuk mengubah variabel anggota menjadi opsional - apakah itu lebih baik?

@kevmeister68 juga metode draw di IDrawable :)

@RyanCavanaugh

Sintaks eksotis tidak memadai (mis. implements.foo = ...). Tidak perlu bersepeda di sini

Terima kasih telah mengajari saya ekspresi baru . Saya tidak melihat banyak masalah dalam semantik fitur:

  • Kita semua menginginkan kesalahan waktu kompilasi jika metodenya tidak ada
  • Kita semua menginginkan kesalahan waktu kompilasi yang jenisnya tidak cocok.
  • Kami juga ingin parameter diketik secara implisit ke parameter induk, seperti yang dilakukan ekspresi lambda.
  • Kami ingin bantuan IDE untuk menulis metode dengan benar.

Sebagian besar masalah bergantung pada sintaks dan perilaku yang tersirat:

  • Anggota opsional di Antarmuka vs anggota di kelas Dasar
  • Metode vs bidang (tidak tahu di mana lambdas akan berada)

Mari kita bandingkan sintaks pohon yang lebih menjanjikan:

menimpa / mengimplementasikan

class Person{
    dateOfBirth: Date;

    abstract talk();
    walk(){ //...}
}

interface ICanFly{
    fly?();
    altitude?: number;
}


class SuperMan extends Person implements ICanFly {
     dateOfBirth = new Date(); //what goes here?

     override talk(){/*...*/}
     walk = () => {/* force 'this' to be captured*/}  //what goes here

     implements fly() {/*...*/}
     altitude = 1000; //what goes here?
}

Keuntungan:

  • Itu wajar bagi programmer dengan latar belakang OO.
  • Terasa benar ketika membandingkan dengan extends / implements .

Kekurangan:

  • Tidak memberikan solusi untuk masalah lapangan, tidak ada override atau implements yang terasa benar. Mungkin super. , tapi lalu apa yang kita lakukan dengan antarmuka?
  • Jika metode diganti menggunakan lambda (berguna untuk menangkap ini) dari function() masalah yang sama terjadi.

menimpa / InterfaceName.

class Person{
    dateOfBirth: Date;

    abstract talk();
    walk(){ //...}
}

interface ICanFly{
    fly?();
    altitude?: number;
}


class SuperMan extends Person implements ICanFly {
     dateOfBirth = new Date(); //what goes here?

     override talk(){/*...*/}
     walk = () => {/* force 'this' to be captured*/}  //what goes here

     ICanFly.fly() {/*...*/}
     ICanFly.altitude = 1000; //what goes here?
}

Keuntungan:

  • Juga wajar untuk programmer dengan latar belakang OO.
  • Memecahkan masalah bidang untuk antarmuka, tetapi tidak untuk kelas, tetapi kita dapat menggunakan super. untuk mereka.

Kekurangan:

  • Nama antarmuka bisa panjang, khususnya dengan generik, dan menampilkan daftar kompetisi otomatis juga akan bermasalah, memerlukan beberapa Alt+Space untuk membuatnya eksplisit.
  • Sepertinya Anda dapat mengimplementasikan dua anggota dengan nama yang sama dari antarmuka yang berbeda secara independen. Ini berfungsi di C # tetapi tidak akan berfungsi di Javascript.

this.

class Person{
    dateOfBirth: Date;

    abstract talk();
    walk(){ //...}
}

interface ICanFly{
    fly?();
    altitude?: number;
}


class SuperMan extends Person implements ICanFly {
     this.dateOfBirth = new Date();

     this.talk(){/*...*/}
     this.walk = () => {/* force 'this' to be captured*/} 

     this.fly() {/*...*/}
     this.altitude = 1000;
}

Keuntungan:

  • Memecahkan kelas, antarmuka, metode, dan bidang.
  • Menggunakan (penyalahgunaan?) hanya satu kata kunci yang sudah ada sebelumnya.
  • Menggunakan this. untuk memicu pelengkapan otomatis terasa alami.
  • Menjelaskan bahwa objek hanyalah satu namespace, dan Anda hanya dapat memiliki satu hal untuk setiap nama.

Kekurangan:

  • Terlihat seperti ekspresi, bukan deklarasi, membuatnya sulit untuk diurai oleh mata yang tidak terlatih.

Sementara kegagalan untuk mengimplementasikan metode antarmuka non-opsional akan menyebabkan kesalahan kompiler, jika antarmuka berubah di beberapa titik di masa mendatang (katakanlah metode dihapus) tidak akan ada peringatan untuk semua kelas yang menerapkan metode itu. Ini mungkin bukan masalah sebesar opsional, tetapi saya pikir adil untuk mengatakan bahwa ruang lingkup ini melampaui mereka.

Namun saya, masih percaya override adalah hal yang sangat berbeda dan berpotensi memiliki dua kata kunci daripada satu akan lebih baik menyampaikan maksud dari program.

Misalnya, pertimbangkan skenario di mana pengembang _percaya_ mereka menerapkan metode antarmuka padahal sebenarnya mereka mengganti metode yang telah dideklarasikan di kelas dasar. Dengan dua kata kunci, kompiler dapat mencegah kesalahan semacam ini dan memberikan kesalahan ke nada method already implemented in a base class .

interface IDelegate {
    execute?() : void;
}

class Base implements IDelegate {
    implement public execute() : void { /// fine, this is correctly implementing execute
    }
}

class Derived extends Base {
    implement public execute() : void { 
/// ERROR: `method "execute():void" already implemented in a base class`
    }
}

Saya tidak tahu apakah ini relevan di JS, tetapi bidang kelas seharusnya bersifat pribadi di OO, jadi saya rasa kita tidak perlu menimpa bidang secara eksplisit karena fakta bahwa bidang itu bersifat pribadi sudah mencegah kita untuk menimpa sama sekali .

Metode instance adalah bidang, dan dari apa yang saya kumpulkan banyak orang menggunakannya alih-alih metode prototipe. Tentang itu,

class Person{
    walk(){ //...}
}
class SuperMan extends Person  {
     walk = () => {/* force 'this' to be captured*/}
}

adalah kesalahan kompiler, karena walk adalah metode prototipe dalam Person dan metode instans dalam SuperMan .

Bagaimanapun, saya tidak yakin override akan cocok di sini, karena kami tidak menimpa bidang. Sekali lagi, ini akan terlihat seperti C#, tetapi saya lebih suka menggunakan kata kunci new daripada override di sini. Karena itu tidak sama dengan penggantian metode (saya dapat memanggil super.myMethod dalam penggantian, bukan di sini).

Solusi pilihan saya kemudian akan menjadi sesuatu seperti (dengan asumsi kita berada dalam mode override yang ketat):

class Person{
    dateOfBirth: Date;
    talk() { }
    walk = () => { }
}

interface ICanFly {
    fly?();
    altitude?: number;
}

class SuperMan extends Person implements ICanFly {
     new dateOfBirth = new Date();
     override talk() { }
     new walk = () => { }

     implements fly() {/*...*/}
     implements altitude = 1000;
}

Perhatian utama saya adalah tentang antarmuka. Kita tidak perlu menulis implements untuk anggota antarmuka non opsional, karena kita sudah ditangkap oleh kompilator. Kemudian, jika kita melakukannya, akan ada kebingungan antara apa yang berasal dari sebuah antarmuka dan apa yang tidak, karena tidak semua anggota antarmuka akan diawali dengan implements . Dan kata itu seteguk. Saya belum siap untuk menulis sesuatu seperti ini:

class C extends React.Component {
    implements componentWillMount() { }
    implements componentDidMount() { }
    implements componentWillReceiveProps(props) { }
    /// ... and the list goes on
}

Saya mengusulkan untuk awalan dengan nama antarmuka sebelumnya tetapi @olmobrutall menunjukkan kepada saya bahwa itu adalah ide yang lebih buruk.

Bagaimanapun, saya cukup yakin bahwa kita memerlukan 3 kata kunci baru yang berbeda untuk menangani hal ini dengan benar.

Saya juga tidak berpikir bahwa perlu untuk mengetikkan override secara implisit karena kompilator sudah akan mencegah kita untuk menulis sesuatu yang tidak kompatibel, terutama karena tampaknya sulit untuk dilakukan dengan benar.

Bukankah new , override dan implement terlalu banyak overhead kata kunci untuk semantik JS yang pada dasarnya sama? Bahkan jika dalam C # mereka adalah hal yang berbeda, virtual tidak ada perbedaan dalam JS/TS.

Ini juga cukup ambigu:

  • Mengapa bidang new untuk kelas tetapi implements untuk antarmuka?
  • Jika Anda mengubah metode dari kelas dasar yang diimplementasikan dengan antarmuka, apa yang harus Anda gunakan? Saya dapat melihat empat kemungkinan: override , implements , salah satu dari keduanya atau keduanya sekaligus?

Pikirkan juga tentang ini:

class Animal {
}

class Human extends Animal {
}

class Habitat {
    owner: Animal;
}

class House extends Habitat {
    owner = new Human();
}

var house = new House();
house.owner = new Dog(); //Should this be allowed??  

Pertanyaannya adalah:

Apakah owner = new Human(); mendefinisikan ulang bidang dengan tipe baru (tetapi kompatibel), atau hanya memberikan nilai.

Saya pikir secara umum Anda mendefinisikan ulang bidang kecuali jika Anda menggunakan _magic keyword_. Preferensi saya adalah untuk this. sekarang.

class House extends Habitat {
    this.owner = new Human(); //just setting a value, the type is still Animal
}

@olmobrutall Itu mungkin memang sedikit banyak overhead kata kunci, tetapi ketiganya memang _berbeda_ hal.

  • override akan berlaku untuk metode (didefinisikan pada prototipe), yang berarti _tidak_ menghapus metode asli, yang masih dapat diakses di belakang super .
  • new akan diterapkan ke bidang, dan berarti bidang asli telah dihapus oleh yang baru.
  • implements akan berlaku untuk apa pun dari sebuah antarmuka, dan berarti itu tidak menghapus atau mengganti apa pun yang sudah ada di kelas induk, karena antarmuka "tidak ada".

Jika Anda mengubah metode dari kelas dasar yang diimplementasikan dengan antarmuka, apa yang harus Anda gunakan? Saya dapat melihat empat kemungkinan: override , implements , salah satu dari keduanya atau keduanya sekaligus?

Tampaknya cukup logis bagi saya bahwa itu akan menjadi override karena itulah yang Anda lakukan secara efektif di sini. implements berarti Anda mengimplementasikan sesuatu dari antarmuka yang tidak ada. Dalam arti tertentu, override dan new akan memiliki prioritas di atas implements .

Dan tentang contoh penggantian bidang Anda, tidak ada yang harus berubah tentang cara kerjanya hari ini. Saya tidak yakin apa yang terjadi di sini, tetapi apa pun itu seharusnya tidak berubah (atau mungkin seharusnya, tetapi bukan itu yang sedang kita diskusikan di sini).

Saya merasa override akan cocok untuk metode dan properti dasar, termasuk yang abstrak (dijelaskan di bawah).

new adalah ide yang menarik dalam konsep, tetapi kata kunci khusus ini mungkin bingung dengan instantiasi kelas, sehingga dapat memberikan kesan yang salah itu hanya akan bekerja untuk kelas, meskipun fakta bahwa properti dapat memiliki primitif, antarmuka, serikat atau bahkan tipe fungsi. Mungkin kata kunci yang berbeda seperti reassign dapat bekerja lebih baik di sana, tetapi memiliki masalah bahwa itu dapat memberikan ide yang salah jika nilainya hanya dideklarasikan ulang tetapi tidak benar-benar ditetapkan dengan apa pun di kelas turunan ( new mungkin memiliki masalah itu juga). Saya pikir redefine juga menarik, tetapi dapat mengarah pada harapan yang salah bahwa properti 'didefinisikan ulang' juga dapat memiliki tipe yang berbeda dari yang dasar, jadi saya tidak yakin.. (_edit: sebenarnya saya memeriksa dan itu bisa memiliki tipe yang berbeda selama yang baru adalah subtipe dari yang dasar, jadi ini mungkin tidak terlalu buruk_).

implement (Saya lebih suka bentuk kata kerja khusus ini untuk konsistensi dengan override ) tampaknya berfungsi untuk antarmuka. Saya percaya ini juga bisa bekerja secara teknis untuk metode dasar abstrak, tetapi menggunakan override bisa terasa lebih konsisten, meskipun semantiknya sedikit berbeda. Alasan lain adalah akan sedikit merepotkan untuk mengubah metode dari abstract ke non-abstract, karena seseorang akan membutuhkan dan pergi ke semua kelas turunan dan mengubah override menjadi implement .

Mungkin ada ide yang lebih baik, tapi hanya itu yang saya miliki saat ini..

Saya mengusulkan kata kunci new karena itu yang digunakan C# untuk fitur yang tepat ini, tetapi saya setuju ini bukan yang terbaik. implement memang merupakan pilihan yang lebih baik daripada implements . Saya tidak berpikir kita harus menggunakannya untuk metode abstrak karena mereka adalah bagian dari kelas dasar dan bukan antarmuka. override + new -> kelas dasar, implement -> antarmuka. Itu tampak lebih jelas.

@JabX

Tentang mengganti prototipe dengan bidang instance

Saya sudah memeriksa dan Anda benar:

class Person{
    walk(){ //...}
}
class SuperMan extends Person  {
     walk = () => {/* force 'this' to be captured*/}
}

Gagal dengan kesalahan Kompilator: Class 'Person' defines instance member function 'walk', but extended class 'SuperMan' defines it as instance member property.

Saya tidak benar-benar melihat alasan untuk gagal dalam kasus ini, karena kontrak tipe induk terpenuhi dan Anda tetap dapat menulis ini:

var p = new SuperMan();
p.walk = () => { };

Atau bahkan

class SuperMan extends Person {
    constructor() {
        super();
        this.walk = () => { };
    }
}

Tentang override

override akan berlaku untuk metode (didefinisikan pada prototipe), yang berarti bahwa itu tidak menghapus metode asli, yang masih dapat diakses di belakang super.

Itu sebenarnya argumen. Setidaknya ada _some_ perbedaan yang dapat diamati antara override dan implements ... tetapi tidak cukup.

Baik override dan implements diimplementasikan dalam prototype , dapat menggunakan super adalah independen, karena Anda memanggil super.walk() bukan hanya super() , dan tersedia di setiap metode ( menimpa, mengimplementasikan, berita, dan definisi normal).

class SuperMan extends Person implements ICanFly {
     new dateOfBirth = new Date();
     override talk() { } //goes to prototype
     new walk = () => { }

     implements fly() {/*...*/}  //also goes to the prototype
     implements altitude = 1000;
}

Dan dapat menggunakan super.walk() juga berfungsi jika Anda menetapkan ke instance

class SuperMan extends Person {
    constructor() {
        super();
        this.walk = () => { super.walk(); };
    }

    //or with the this. syntax
    this.walk = () => { super.walk(); };
}

Sudah ada cara yang jelas untuk membedakan bidang prototype dari bidang contoh dan merupakan token = .

class SuperMan extends Person implements ICanFly {
     this.dateOfBirth = new Date(); //instance
     this.talk() { } //prototype
     this.walk = () => { } //instance

     this.fly() {/*...*/}  //prototype
     this.altitude = 1000; //instance
}

Dengan sintaks this. masalahnya diselesaikan dengan lebih elegan:

  • this. berarti memeriksa tipe saya (kelas dasar dan antarmuka yang diimplementasikan).
  • = berarti menetapkan ke instance alih-alih prototipe.

Tentang New

new akan diterapkan ke bidang, dan berarti bidang asli telah dihapus oleh yang baru.

Perhatikan bahwa Anda tidak dapat mengubah bidang atau metode dengan tipe yang berbeda karena Anda akan merusak sistem tipe. Di C# atau Java ketika Anda melakukan new metode baru hanya akan digunakan pada panggilan yang dikirim secara statis ke tipe baru. Dalam Javascript, semua panggilan akan dinamis dan jika Anda mengubah jenisnya, kode kemungkinan akan rusak saat run-time (Namun, memaksa jenis dapat diizinkan untuk tujuan praktis, tetapi tidak melebar).

class Person {
    walk() { }

    run() {
        this.walk();
        this.walk();
        this.walk();
    }
}

class SuperMan extends Person {
    new walk(destination: string) { } //Even if you write `new` this code will break
}

Jadi lebih dari new kata kuncinya harus assign , karena ini adalah satu-satunya hal yang aman untuk jenis yang dapat Anda lakukan:

class Person{
    dateOfBirth: Date;
}

class SuperMan extends Person implements ICanFly {
     assign dateOfBirth = new Date();
}

Perhatikan bahwa reassign tidak masuk akal, karena Person hanya mendeklarasikan bidang tetapi tidak menetapkan nilai apa pun di dalamnya.

Menulis assign tampaknya berlebihan namun, jelas bahwa kami menugaskan karena kami memiliki = di sisi lain.

Sekali lagi sintaks this. memecahkan masalah dengan anggun:

class Person{
    dateOfBirth: Date;
}

class SuperMan extends Person implements ICanFly {
     this.dateOfBirth = new Date();
}

Saya tidak yakin bagaimana Anda menerapkan ini ke bidang dengan cara yang masuk akal.

Kita berbicara tentang menambahkan kata kunci yang hanya berlaku di badan kelas, namun bidang dapat ditugaskan kembali di _setiap titik_ selama masa pakai instance.

TypeScript sudah memungkinkan Anda untuk menginisialisasi bidang di badan kelas:

class Person {
    dateOfBirth : new Date();
}

Ini juga memungkinkan Anda untuk menginisialisasi ulang bidang yang dideklarasikan di kelas induk, tetapi situasinya buruk. Tentu saja Anda bisa salah menulis nama (masalah serupa dengan override ) tetapi jenisnya juga terhapus. Ini berarti bahwa:

class Person {
    fullName: { firstName: string; lastName?: string };
}

class SuperMan extends Person {
    fullName = { firstName: "Clark" };

    bla() {
        this.fullName.lastName; //Error
    }
}

Kode ini gagal dengan: Properti 'lastName' tidak ada pada tipe '{ firstName: string;

Kita berbicara tentang menambahkan kata kunci yang hanya berlaku di badan kelas, namun bidang dapat dipindahkan kapan saja selama masa pakai instance.

Juga metode:

class Person {
    talk() { }
}

class SuperMan extends Person {

    talk() { }

    changeMe(){
        this.talk = () => { };      
    }

    changeMyPrototype() {
        SuperMan.prototype.talk = () => { };
    }
}

@kungfusheep Saya setuju, bidang dapat dipindahkan, tetapi begitu juga metode pada prototipe.
Alasan utama mengapa kita memerlukan kata kunci untuk bidang "mengganti" adalah karena metode instans _are_ bidang dan mereka banyak digunakan daripada metode prototipe yang tepat karena mereka lambda dan dengan demikian mengikat otomatis konteksnya.

@olmobrutall

Tentang mengganti metode dengan bidang

Itu dilarang dan dengan alasan yang bagus saya percaya (itu bukan hal yang sama sehingga seharusnya tidak kompatibel), tetapi Anda benar bahwa itu mungkin dilakukan ketika secara langsung memanipulasi instance kelas. Saya tidak tahu persis mengapa itu diizinkan, tetapi kami di sini bukan untuk mengubah cara kerja bahasa, jadi saya yakin kami tidak boleh ikut campur di sini.

Tentang antarmuka

Antarmuka kelas menggambarkan sisi instance kelas, yang berarti bidang serta metode dari prototipe. Saya bahkan tidak berpikir ada perbedaan antara metode instance dan prototipe dalam sebuah antarmuka, saya yakin Anda dapat menggambarkan metode sebagai satu atau yang lain, dan mengimplementasikannya sebagai satu atau yang lain. Jadi implement dapat (dan harus) juga berlaku untuk bidang.

Tentang baru

new harus memiliki semantik yang sama dengan override , jadi jelas Anda tidak dapat mengubah jenisnya menjadi sesuatu yang tidak kompatibel. Maksud saya, sekali lagi, kami tidak mengubah cara kerja bahasa. Saya hanya ingin kata kunci terpisah karena itu bukan redefinisi yang sama. Tapi, saya setuju bahwa, seperti yang Anda tunjukkan kepada saya, kita sudah dapat membedakan bidang dan metode dengan menggunakan = , jadi mungkin kata kunci/sintaks yang sama dapat digunakan tanpa ambiguitas.

Tentang ini.

Namun, sintaks penggantian bidang/metode terpadu tidak boleh this.method() atau this.field karena sangat mirip dengan Javascript yang valid, padahal tidak. Menambahkan kata kunci sebelum anggota akan lebih jelas terhadap fakta bahwa memang hal TypeScript.
this adalah ide yang bagus, jadi mungkin kita bisa menghilangkan titik dan menulis sesuatu yang kurang ambigu seperti

class Superman {
    this walk() { }
}

Tapi tetap saja, itu terlihat aneh. Dan mencampurnya dengan implement akan terlihat sedikit tidak pada tempatnya (karena kami setuju dengan fakta bahwa sintaks this ini tidak akan digunakan dengan antarmuka?), dan saya suka implement / override duo. Pengubah override pada bidang masih membuat saya kesal, tetapi mungkin lebih baik memiliki kata kunci new ambigu ketiga. Karena penugasan ( = ) membuat perbedaan antara metode dan bidang menjadi jelas, itu akan baik-baik saja bagi saya sekarang (berlawanan dengan apa yang saya katakan beberapa jam yang lalu).

Saya setuju, bidang dapat dipindahkan, tetapi begitu juga metode pada prototipe.

Ya, memodifikasi prototipe secara langsung, di TypeScript, sangat jauh dari bermain-main di bawah tenda. Ini sama sekali tidak biasa seperti menugaskan kembali properti pada instance sesuatu. Kami sangat jarang menggunakan bidang sebagai fungsi, lebih dari 150k+ basis kode baris, jadi saya pikir untuk mengatakan itu banyak digunakan mungkin subjektif.

Saya setuju dengan sebagian besar poin Anda yang lain, tetapi saya tidak setuju (dan sepertinya Anda juga tidak...) tentang penggunaan kata kunci new dalam konteks ini.

Kami sangat jarang menggunakan bidang sebagai fungsi, lebih dari 150k+ basis kode baris, jadi saya pikir untuk mengatakan itu banyak digunakan mungkin subjektif.

Saya juga tidak, tetapi saya harus menggunakan dekorator @autobind untuk mengikat this dengan benar dalam metode saya, yang lebih rumit daripada sekadar menulis lambda. Dan selama Anda tidak menggunakan warisan, menggunakan bidang berfungsi seperti yang Anda harapkan. Saya mendapat kesan bahwa, setidaknya di dunia JS/React, kebanyakan orang yang menggunakan kelas ES6 menggunakan fungsi panah sebagai metode.

Dan metode antarmuka dapat diimplementasikan baik dengan metode yang tepat atau dengan metode instan, yang tidak membantu memperjelas perbedaannya.

Saya percaya bidang memiliki masalah yang sama dengan metode sejauh menyangkut penggantian.

class A {
    firstName: string;
    get name() {
        return this.firstName;
    }
}

class B extends A {
    firstname = "Joe" // oops
}

Solusi yang mungkin di sini adalah menetapkan bidang di konstruktor

class B extends A {
    constructor() {
        this.firstName = "Joe"; // can't go wrong
    }
}

tapi itu tidak berlaku untuk antarmuka. Itu sebabnya saya (dan yang lainnya di sini) percaya bahwa kita memerlukan sesuatu untuk memeriksa pra-eksistensi suatu bidang ketika mendeklarasikannya secara langsung di badan kelas. Dan itu bukan override . Sekarang saya menaruh beberapa pemikiran lagi di sini, saya percaya masalah dengan bidang persis sama dengan masalah dengan anggota antarmuka. Saya akan mengatakan kita memerlukan kata kunci override untuk metode yang sudah memiliki implementasi di kelas induk (metode prototipe, dan mungkin juga metode instan), dan satu lagi untuk hal-hal yang didefinisikan tetapi tanpa implementasi (non-fungsi termasuk bidang).

Proposisi baru saya adalah, dan menggunakan kata kunci member tentatif (mungkin bukan pilihan terbaik):

interface IBase {
    interfaceField?: string;
    interfaceMethod(): void
}

abstract class Base {
    baseField: number;
    baseMethod() { }
    baseLambda: () => { };
    abstract baseAbstractMethod();
}

class Derived extends Base implements IBase {
    member interfaceField = "Hello";
    member interfaceMethod() { }
    member baseField = 2;
    override baseMethod() { }
    override baseLambda = () => { };
    member baseAbstractMethod() { }
}

Perhatikan bahwa saya akan memilih kata kunci member untuk metode abstrak karena saya mengatakan override adalah untuk hal-hal dengan implementasi, yang menunjukkan bahwa kami mengganti perilaku yang ada. Metode instance perlu menggunakan override berdasarkan jenis bidang khusus (yang sudah dikenali oleh kompiler karena tanda tangan metode dapat diimplementasikan dengannya).

Saya pikir TypeScript harus menjadi versi JavaScript yang diperiksa waktu kompilasi, dan dengan mempelajari TS Anda juga harus belajar JS, sama seperti dengan mempelajari C# Anda mendapatkan intuisi CLR yang baik.

Warisan prototipe adalah konsep JS yang keren, dan cukup sentral, dan TS tidak boleh mencoba menyembunyikannya dengan konsep dari bahasa lain yang tidak terlalu masuk akal di sini.

Warisan prototipe tidak ada bedanya jika Anda mewarisi metode atau bidang.

Sebagai contoh:

class Rectangle {
       x: number;
       y: number;
       color: string;
}

Rectangle.prototype.color = "black";

Di sini kita mengatur bidang sederhana di objek prototipe, jadi semua persegi panjang akan menjadi hitam secara default tanpa harus memiliki bidang contoh untuk itu.

class BoundingBox {
      override color = "transparent"; // or should be member? 
}

Juga kata kunci member membuat anggota lain di kelas cemburu.

Yang kita butuhkan adalah sintaks yang akan memungkinkan dalam konteks deklarasi kelas perilaku yang sama (waktu kompilasi diperiksa/pelengkapan otomatis/pengganti nama) yang sudah kita miliki dalam literal objek atau ekspresi anggota.

Mungkin alternatif yang lebih baik untuk this. hanya . :

class Derived {
   .interfaceField = "hello";
   .interfaceMethod() {}
   .baseField = 2;
   .baseMethod() {}
   .baseLambda = () => {};
   .baseAbstractMethod(){};

   someNewMethod(){}
   someNewField = 3;
}

Sebagai kesimpulan, saya tidak berpikir Typesystem harus melacak nilai mana yang berasal dari prototipe dan mana dari instance, karena ini adalah masalah yang sulit setelah Anda dapat mengakses prototipe secara imperatif, dan karena tidak akan memperbaiki masalah apa pun sementara akan membatasi ekspresivitas JS.

Jadi tidak ada perbedaan antara bidang fungsi, dan bidang fungsi panah dan metode, atau dalam sistem tipe, atau dalam kode yang dihasilkan (jika this tidak digunakan).

Hai teman-teman, ini adalah hal yang menarik, tapi ini cukup menarik dalam hal override secara khusus. Saya akan senang untuk memiliki beberapa masalah Diskusi baru untuk hal semacam ini untuk masuk kecuali kita dapat menautkan komentar ini kembali ke saran asli secara lebih konkret.

Banyak dari apa yang Anda katakan di sini sebenarnya tidak spesifik untuk TypeScript, jadi memulai utas ESDiscuss mungkin juga sesuai. Tentu saja mereka telah memikirkan hal semacam ini sebanyak (dalam hal apa yang terjadi pada prototipe vs contoh).

@olmobrutall
Kelas ES6 sudah menyembunyikan hal-hal prototipe, dan seperti yang dikatakan @kungfusheep , mengacaukannya secara langsung bukanlah hal yang normal untuk dilakukan.

Jadi tidak ada perbedaan antara bidang fungsi, dan bidang fungsi panah dan metode, atau dalam sistem tipe, atau dalam kode yang dihasilkan (jika ini tidak digunakan).

Nah, dalam kode yang dihasilkan, metode kelas dimasukkan ke dalam prototipe dan yang lainnya (tidak diawali dengan static ) dimasukkan ke dalam instance, yang membuat perbedaan dengan pewarisan.

Bagaimanapun, saya sekarang setuju bahwa kita tidak harus memiliki sintaks terpisah untuk kedua jenis metode, tetapi jika kita menggunakan kata kunci override , itu harus dibatasi pada metode dan kita harus menemukan sesuatu yang lain untuk sisanya. Memiliki kata kunci yang unik untuk semuanya bisa bagus tetapi harus sangat jelas tentang artinya. override jelas, tetapi hanya untuk metode. Anda dot sintaks, ini seperti this. masih terlalu dekat dengan JS yang ada untuk selera saya.

@RyanCavanaugh

Ini cukup keluar dari garis singgung dalam hal override

Masalah saya dengan override adalah itu tidak cukup umum untuk metode dan bidang antarmuka. Saya melihat tiga opsi:

  • gunakan override hanya untuk metode kelas dasar.
  • gunakan override untuk metode dan bidang antarmuka juga
  • pertimbangkan sintaks alternatif ( member , implement , this. , . , dll...)
  • tidak melakukan apa-apa (itu akan menyedihkan)

Saya pikir keputusan ini harus diambil di sini.

Banyak dari apa yang Anda katakan sebenarnya tidak spesifik untuk TypeScript

Sangat! kita semua senang dengan status transpiling saat ini, tetapi tidak pada pemeriksaan tipe / perkakas.

Apa pun kata kuncinya, itu hanya TypeScript (sama abstraknya)

@JabX

Anda benar tentang kode yang dihasilkan, tentu saja. Maksud saya adalah Anda dapat menulis sesuatu seperti ini:

class Person {
    name: string = "John"; 

    saySomething() {
        return "Hi " + this.name;
    }
}

Kami mendeklarasikan kelas, name akan pergi ke instance sementara saySomething ke prototipe. Masih saya bisa menulis ini:

Person.prototype.name = "Unknown"; 

Karena tipe Person.prototype adalah orang seutuhnya. TypeScript tidak melacak apa yang terjadi di sistem tipenya untuk kesederhanaan.

Memiliki kata kunci yang unik untuk semuanya bisa bagus tetapi harus sangat jelas tentang artinya.

Apa yang saya pikir lebih penting dan kita semua bisa setuju adalah semantik:

Modifikasi XXX memeriksa apakah anggota sudah dideklarasikan di kelas dasar atau antarmuka yang diimplementasikan, biasanya digunakan untuk menimpa fungsi dari kelas dasar.

Karena . atau this. terlihat terlalu asing, dan member berlebihan, saya pikir mungkin pilihan terbaik adalah menyalahgunakan override untuk semua kasus . Mengatur bidang atau mengimplementasikan metode antarmuka adalah fitur sampingan.

Ada banyak preseden:

  • Dalam C#, static class sebenarnya bukan _kelas objek_ lagi.
  • Tidak ada _static_ tentang bidang static .
  • virtual metode cukup _concrete_ (VB menggunakan Overrideable ).

Ini akan terlihat seperti ini:

class Person{
    dateOfBirth: Date;

    abstract talk();
    walk(){ //...}
}

interface ICanFly{
    fly?();
    altitude?: number;
}


class SuperMan extends Person implements ICanFly {
     override dateOfBirth = new Date();

     override talk(){/*...*/}
     override walk = () => {/* force 'this' to be captured*/} 

     override  fly() {/*...*/}
     override altitude = 1000; 
}

Bisakah kita menyelesaikan itu?

Saya lebih suka melihat kata kunci implement terpisah untuk apa pun yang berasal dari antarmuka (dengan prioritas ke override jika ada di keduanya), karena memang seperti itu: implementasi, bukan penggantian.
Kalau tidak, saya setuju mungkin yang terbaik adalah menyalahgunakan override untuk semuanya dari kelas induk.

Tetapi tidak ada perbedaan semantik antara implement dan override .

Keduanya akan memiliki pelengkapan otomatis / pesan kesalahan / transpilasi yang mirip dengan JS... hanya saja perbedaan filosofisnya.

Sangatlah berharga untuk menjelaskan dua kata kunci dan bahwa kompiler bertele-tele memberi tahu Anda: Error you should use 'override' instead of 'implement' .

Bagaimana dengan kasus ini:

interface IComparable {
     compare(): number;
} 

class BaseClass implements IComparable {
    implement compare(); 
}

class ChildClass extends BaseClass implements IComparable { //again 
     override compare(); // or implements... 
}

Pertanyaannya adalah... siapa yang peduli?.

Juga ada masalah implement / implements .

Mari kita menyalahgunakan override . Satu konsep satu kata kunci, ini yang penting.

Yah, saya sudah mengusulkan bahwa override harus memiliki prioritas di atas implement , tetapi saya mungkin tidak cukup jelas.
Saya masih tidak percaya ini adalah hal yang sama. Contoh lain: untuk anggota antarmuka wajib, kegagalan untuk mengimplementasikan adalah kesalahan kompiler sedangkan kegagalan untuk menimpa bukan masalah (kecuali anggota dasar abstrak yaitu ...).

Saya hanya tidak berpikir bahwa override pada sesuatu yang tidak dideklarasikan pada kelas induk akan masuk akal. Tapi mungkin hanya aku yang menginginkan perbedaan. Bagaimanapun, kata kunci apa pun yang akhirnya kami gunakan untuk ini, saya hanya berharap kami dapat menyelesaikan sesuatu dan memberikan mode ketat untuk menegakkan penggunaannya.

Yah, saya sudah mengusulkan bahwa penggantian harus diprioritaskan daripada implementasi, tetapi saya mungkin tidak cukup jelas.

Tentu, saya hanya ingin mengatakan bahwa ada empat kasus. Kelas Dasar, Antarmuka, Antarmuka di Kelas Dasar, Mengimplementasikan Kembali Antarmuka di Kelas Dasar.

@JabX

Saya hanya tidak berpikir bahwa menimpa sesuatu yang tidak dideklarasikan pada kelas induk akan masuk akal. Tapi mungkin hanya aku yang menginginkan perbedaan .

Kamu bukan. Mereka adalah dua hal yang berbeda secara fundamental dan ada manfaat memiliki pemisahan itu di tingkat bahasa.

Saya pikir jika antarmuka harus didukung dalam fungsi baru ini maka itu bukan solusi setengah matang yang telah dibaut ke override . Untuk alasan yang sama extends ada sebagai entitas terpisah untuk implements di tingkat kelas, override perlu dipasangkan dengan sesuatu di sepanjang baris implement agar untuk memperbaiki masalah ini. Ini _mengganti fungsionalitas_ vs _mendefinisikan fungsionalitas_.

@olmobrutall

Sangatlah berharga untuk menjelaskan dua kata kunci dan bahwa kompiler bertele-tele memberi tahu Anda: Kesalahan Anda harus menggunakan 'override' alih-alih 'implement'.

Saya merasa seperti saya telah membuat perbedaan ini sekitar 4 kali sekarang, tapi ini tidak bertele-tele; itu informasi yang sangat penting!

Pertimbangkan contoh Anda sendiri

interface IComparable {
     compare(): number;
} 

class BaseClass implements IComparable {
    implement compare(); 
}

class ChildClass extends BaseClass implements IComparable { //again 
     override compare(); // or implements... 
}

'atau mengimplementasikan ...' adalah asumsi yang sama sekali salah untuk dibuat dalam kasus ini. Hanya karena Anda telah memberi tahu kompiler bahwa Anda ingin mengimplementasikan antarmuka yang sama dengan kelas dasar yang Anda perluas tidak mengubah fakta bahwa Anda telah mengimplementasikan compare .
Jika Anda menulis implement di ChildClass alih-alih override maka Anda harus bersyukur bahwa kompiler akan memberi tahu Anda tentang asumsi Anda yang salah, karena itu masalah besar yang akan Anda hapus tanpa sadar keluar metode yang diterapkan sebelumnya!

Dalam basis kode yang menjadi tanggung jawab saya, ini pasti akan menjadi masalah besar; jadi saya menyambut setiap fitur kompiler yang dapat mencegah kesalahan pengembang seperti itu!

@kungfusheep

Jika Anda menulis implement di ChildClass alih-alih menimpa maka Anda harus bersyukur bahwa kompiler akan memberi tahu Anda tentang asumsi Anda yang salah, karena itu masalah besar bahwa Anda akan secara tidak sadar menghapus metode yang diterapkan sebelumnya!

Jika mengganti metode yang sudah diimplementasikan adalah keputusan yang penting, maka implement juga harus digunakan untuk metode abstrak. Jika Anda mengubah metode dari abstract menjadi virtual atau sebaliknya, Anda harus memeriksa (dan mengubah) semua versi yang diterapkan untuk mempertimbangkan memanggil super atau hanya menghapus metode.

Dalam praktiknya ini tidak pernah menjadi masalah di C#.

Saya setuju, override untuk metode yang diimplementasikan, dan implements untuk metode yang tidak diimplementasikan (abstrak/antarmuka).

Itu bisa digunakan untuk abstrak, ya.

Di C # tidak ada skenario antarmuka 'opsional', jadi mungkin tidak dianggap sebagai masalah.

Tetapi maksud saya adalah bahwa dalam C# kami override menerapkan dan tidak menerapkan metode dan saya belum pernah mendengar seseorang mengeluh.

Saya tidak berpikir itu benar-benar masalah yang pantas membuat menjelaskan fitur setidaknya dua kali lebih sulit.

Yah, satu-satunya alasan mengapa kita membutuhkan kata kunci implement adalah karena anggota antarmuka dapat opsional, sedangkan itu tidak mungkin dalam C# (dan mungkin di sebagian besar bahasa OO juga). Tidak ada kata kunci untuk itu di sana karena Anda tidak dapat _tidak_ mengimplementasikan antarmuka sepenuhnya, jadi tidak ada masalah. Jangan terlalu mendasarkan argumen Anda pada "itu sama dalam C#", karena kami memiliki masalah yang berbeda di sini.

override digunakan untuk metode _base class_, apakah itu abstrak atau tidak. Saya kira kita harus melakukan hal yang sama ( override daripada implement untuk metode abstrak) di sini karena saya percaya perbedaannya harus pada asal metode (kelas atau antarmuka) alih-alih pada keberadaan dari implementasi. Dan di masa depan, jika Anda memutuskan untuk memberikan implementasi default ke metode abstrak Anda, Anda tidak perlu melalui kode Anda (atau lebih buruk, ke kode orang lain yang menggunakan kelas Anda) untuk mengganti kata kunci.

Pertanyaan utama saya sekarang adalah, haruskah kita memiliki flag override / implement yang ketat (saya benar-benar menginginkan ini), haruskah kita memaksakan kata kunci implement pada anggota antarmuka wajib? Karena itu tidak banyak membantu (Anda tidak dapat gagal untuk mengimplementasikan ini karena jika tidak, itu tidak akan dikompilasi) dan dapat menyebabkan banyak verbositas yang tidak perlu. Tetapi di sisi lain, mungkin menipu untuk memiliki implement pada beberapa anggota antarmuka tetapi tidak pada semuanya.

@JabX Saya pribadi ingin peringatan jika kelas dasar menambahkan implementasi metode abstrak.

Namun, saya tidak 100% menjual ide kata kunci implementasi. Kompiler sudah akan memperingatkan Anda jika Anda tidak mengimplementasikan sesuatu. Satu-satunya tempat yang sangat membantu adalah untuk metode opsional.

Lagi pula, itu tidak ada hubungannya dengan mengapa saya ada di utas ini. Saya hanya mencari apakah ada cara untuk menentukan fungsi sebagai penggantian kelas induk.

Pertanyaan utama saya sekarang adalah, haruskah kita memiliki flag override/implement yang ketat (saya benar-benar menginginkan ini), haruskah kita memaksakan kata kunci implement pada anggota antarmuka wajib?

Saya pikir tidak menulis itu harus menjadi peringatan.

Satu-satunya tempat yang sangat membantu adalah untuk metode opsional.

Ya, itulah intinya di sini.

Tanpa ini, ini adalah kesalahan yang sangat umum pada metode salah ketik yang ingin Anda timpa tetapi sebenarnya Anda membuat metode baru. Pengenalan kata kunci tersebut hanya cara untuk menunjukkan maksud dari fungsi utama.

Itu bisa menjadi peringatan atau apa pun tetapi saat ini sangat menyakitkan.

Saya menambahkan anotasi override ke tampilan kelas UML di alm.tools :rose:

image

referensi https://github.com/alm-tools/alm/issues/84

Saya juga telah menambahkan indikator selokan untuk anggota kelas yang menimpa anggota kelas dasar di alm .

overrides

referensi https://github.com/alm-tools/alm/issues/111

Menerima PR untuk solusi tercakup yang menurut kami mencapai nilai paling banyak dengan kompleksitas paling sedikit:

  • Kata kunci baru override valid pada metode kelas dan deklarasi properti (termasuk get/set)

    • Semua tanda tangan (termasuk implementasi) harus memiliki override jika ada

    • Baik get dan set harus ditandai override jika keduanya

  • Merupakan kesalahan untuk memiliki override jika tidak ada properti/metode kelas dasar dengan nama ini
  • Sakelar baris perintah baru --noImplicitOverride (jangan ragu untuk mengganti nama di sini) membuat override wajib untuk hal-hal yang ditimpa

    • Sakelar ini tidak berpengaruh dalam konteks sekitar (yaitu file declare class atau .d.ts)

Di luar cakupan saat ini:

  • Mewarisi tanda tangan
  • Pengetikan parameter atau penginisialisasi kontekstual (mencoba ini sebelumnya dan itu adalah bencana)
  • Kata kunci yang sesuai untuk menunjukkan "Saya mengimplementasikan anggota antarmuka dengan deklarasi ini"

@RyanCavanaugh
Saat ini saya mencoba menerapkan ini (baru saja dimulai, dan saya akan menerima bantuan apa pun tentang ini, terutama untuk pengujian), tetapi saya tidak yakin saya mengerti apa yang ada di baliknya

Semua tanda tangan (termasuk implementasi) harus memiliki override jika ada

Karena Anda tidak dapat memiliki banyak tanda tangan dari suatu metode di suatu kelas, apakah Anda berbicara tentang konteks pewarisan? Maksud Anda itu (tanpa tanda yang memaksa penggunaan override , jika tidak, itu jelas merupakan kesalahan)

class A {
    method() {}
}

class B extends A {
    override method() {}
}

class C extends B {
    method() {} 
}

haruskah menampilkan kesalahan di kelas C karena saya tidak menulis override sebelum method ?

Jika itu masalahnya, saya tidak yakin mengapa kami ingin menegakkan itu. Dan haruskah itu juga bekerja sebaliknya? Keluarkan kesalahan di kelas B jika saya menentukan override di C dan bukan di B?

Ya, saya pikir itu berlaku untuk tanda tangan pohon warisan.
Dan saya tidak berpikir itu akan bekerja sebaliknya, itu harus mulai menegakkan aturan override setelah digunakan pada tingkat dalam hierarki. Dalam beberapa kasus, keputusan untuk menerapkan override dapat dibuat pada kelas yang diturunkan dari kelas yang tidak dimiliki oleh pengembang. Jadi, setelah digunakan, itu hanya berlaku untuk semua kelas turunan.

Saya punya pertanyaan lain tentang

Baik get dan set harus ditandai override jika keduanya

Apa yang terjadi di sana jika kelas induk saya hanya mendefinisikan pengambil dan saya ingin mendefinisikan penyetel di kelas turunan saya? Jika saya mengikuti aturan ini, itu berarti setter saya harus didefinisikan sebagai override, tetapi tidak ada setter di kelas induk, dan seharusnya merupakan kesalahan untuk menimpa sesuatu yang tidak ada. Nah, dalam praktiknya (dalam implementasi naif saya saat ini), tidak ada kontradiksi karena pengambil dan penyetel adalah untuk properti yang sama.
Saya bahkan bisa menulis sesuatu seperti:

class A {
    get value() { return 1; }
}

class B extends 1 {
   override set value(v: number) {}
}

Yang terlihat jelas salah bagi saya.

Karena Anda tidak dapat memiliki banyak tanda tangan dari suatu metode di kelas

Anda dapat memiliki beberapa tanda tangan kelebihan beban untuk metode yang sama:

class Base { bar(): { } }

class Foo extends Base {
  // Must write 'override' on each signature
  override bar(s: string): void;
  override bar(s?: number): void;
  override bar(s: string|number) { }
}

Saya pikir intuisi Anda tentang kasus-kasus lain benar.

Mengenai getter dan setter, menurut saya override hanya berlaku untuk slot properti. Menulis setter utama tanpa pengambil yang sesuai jelas sangat mencurigakan, tetapi tidak lebih atau kurang mencurigakan dengan adanya override . Karena kita tidak selalu tahu bagaimana kelas dasar diimplementasikan, saya pikir aturannya adalah bahwa pengambil atau penyetel override harus memiliki _some_ properti kelas dasar yang sesuai, dan jika Anda mengatakan override get { , setiap deklarasi set juga harus memiliki override (dan sebaliknya)

Oh ok saya tidak tahu kita bisa menulis tanda tangan metode di kelas seperti itu, itu cukup rapi.

Saya pikir virtual akan masuk akal juga.
Semua kata kunci: virtual , override , final sebenarnya akan membuat banyak perbedaan terutama ketika refactoring kelas.
Saya menggunakan banyak warisan, sekarang sangat mudah untuk mengganti metode "secara tidak sengaja".
virtual juga akan meningkatkan intellisense, karena Anda hanya dapat mengusulkan metode abstrak/virtual setelah mengganti kata kunci.

Tolong, tolong prioritaskan ini. Terima kasih!

Saya setuju dengan @pankleks - Saya sering menggunakan warisan, dan rasanya salah untuk mengganti fungsi tanpa menentukan apa pun secara langsung.

"virtual", "override" dan "final" akan sempurna.

Ini adalah masalah penting bagi saya.

@RyanCavanaugh

Pengetikan parameter atau penginisialisasi kontekstual (mencoba ini sebelumnya dan itu adalah bencana)

Bisakah Anda menguraikan ini? Saya benar-benar menemukan masalah ini ketika mencoba mencari tahu mengapa TS tidak menyimpulkan jenis parameter saya pada penggantian metode, yang mengejutkan saya. Saya tidak melihat kapan daftar parameter yang tidak identik akan benar saat mengganti, atau tipe pengembalian yang tidak kompatibel.

@pankleks

Saya pikir virtual akan masuk akal juga.

Secara alami semuanya "virtual" di JS. Dukungan untuk final sudah cukup IMHO: jika Anda memiliki metode (atau properti) yang tidak boleh ditimpa, Anda dapat menandainya untuk memicu kesalahan kompiler ketika ini dilanggar. Tidak perlu virtual kalau begitu, kan? Tapi ini dilacak dalam edisi #1534.

@avonwyss ada semacam dua masalah di sini.

Ketika kami mencoba mengetik _property initializers_ secara kontekstual, kami mengalami beberapa masalah yang dijelaskan di https://github.com/Microsoft/TypeScript/pull/6118#issuecomment -216595207 . Kami pikir kami memiliki pendekatan baru yang lebih sukses di #10570

Deklarasi metode adalah bola lilin yang berbeda dan di sana kami memiliki beberapa hal lain untuk diketahui, seperti apa yang seharusnya terjadi di sini:

declare class Base {
  method(x: string): string[];
  method(x: number, count: number): number[];
}
class Derived extends Base {
  method(x) { // x: ???
    return x;
  }
}

Kami mungkin memutuskan bahwa hanya metode tanda tangan tunggal yang mendapatkan tipe parameter dari basis. Tetapi ini tidak akan membuat semua orang senang dan tidak benar-benar menyelesaikan masalah yang sering muncul dari "Saya ingin kelas turunan saya memiliki metode _signatures_ yang sama tetapi hanya memberikan _implementasi_" yang berbeda.

Nah, tipe x akan menjadi string | number dalam kasus itu, tapi saya mengerti bahwa mungkin cukup sulit untuk mencari tahu secara konsisten.

Tetapi Anda mungkin tidak ingin tipe _externally-seen_ dari x menjadi string | number - dengan asumsi Derived memiliki semantik yang sama dengan Base , itu tidak akan' tidak sah untuk dipanggil dengan (3) atau ('foo', 3)

Hum ya, aku merindukan itu.

Saya melihat Anda menyarankan untuk membatasinya ke metode tanda tangan tunggal, tetapi saya yakin itu bisa "dengan mudah" diperluas ke metode "tanda tangan yang cocok", seperti dalam contoh Anda:

declare class Base {
  method(x: string): string[];
  method(x: number, count: number): number[];
}
class Derived extends Base {
  method(x, count) { // x: number, count: number
    return [];
  }
}

karena hanya ada pada signature pada base class yang cocok.

Itu akan jauh lebih baik daripada apa yang kita miliki (tidak ada), dan saya tidak berpikir itu _itu_ sulit untuk dilakukan? (masih jauh dari ruang lingkup untuk masalah ini dan mungkin lebih banyak pekerjaan)

@RyanCavanaugh Terima kasih atas jawabannya. Seperti yang disebutkan, harapan saya untuk override adalah bahwa ia akan mengurutkan masalah parameter (dan tipe pengembalian). Tapi saya tidak melihat contoh Anda bermasalah, karena kelebihan selalu harus memiliki satu implementasi "pribadi" yang kompatibel dengan semua kelebihan (itulah sebabnya saran dari @JabX tidak akan berfungsi secara konseptual).
Jadi, kita akan memiliki sesuatu seperti ini:

declare class Base {
  method(x: string): string[];
  method(x: number, count: number): number[];
  // implies: method(x: string|number, count?: number): string[]|number[]
  // or fancier: method<T extends string|number>(x: T, count?: number): T[]
}
class Derived extends Base {
  override method(x, count?) { // may only be called like the method on Base
    return [x];
  }
}

Logika ini sudah ada dan override jelas hanya akan tersedia untuk metode implementasi "pribadi", bukan override yang berbeda (seperti halnya Anda tidak dapat secara eksplisit mengimplementasikan satu overload). Jadi saya tidak melihat masalah di sini - atau apakah saya melewatkan sesuatu?

Metode yang dirancang untuk diganti biasanya tidak memiliki kelebihan beban, jadi bahkan mengambil jalan keluar yang sederhana dan tidak menyimpulkan parameter dan mengembalikan tipe jika kelebihan ada akan sangat baik dan berguna. Jadi, bahkan jika perilakunya apa adanya saat mengganti metode yang kelebihan beban, Anda akan mendapatkan kesalahan saat menjalankan dalam mode --no-implicit-any dan Anda harus menentukan tipe yang kompatibel, itu akan tampak seperti pendekatan yang sangat bagus.

Saya mungkin melewatkan sesuatu, tetapi dalam Javascript (dan dengan demikian TypeScript) Anda tidak dapat memiliki 2 metode dengan nama yang sama - meskipun mereka memiliki tanda tangan yang berbeda?

Di C#, Anda bisa memiliki

public string method(string blah) {};
public int method(int blah) {};

Tapi di sini Anda tidak akan pernah bisa memiliki 2 fungsi dengan nama yang sama, tidak peduli apa yang Anda lakukan dengan tanda tangan... jadi tidak ada contoh Anda yang mungkin, bukan? Kecuali jika kita berbicara tentang banyak ekstensi di kelas yang sama ... tapi bukan itu yang ditunjukkan oleh contoh Anda, jadi mungkin tidak? Apakah ini bukan masalah?

Saat ini saya menggunakan pewarisan, dan ini sangat membuat frustrasi... Saya harus memberikan komentar tentang fungsi untuk menunjukkan bahwa fungsi tersebut virtual (mis. Saya menimpanya di suatu tempat) atau menimpa (mis. bahwa fungsi ini mengesampingkan fungsi yang mendasarinya). Sangat mengganggu dan berantakan! Dan tidak aman!

@ sam-s4s Ya, dimungkinkan untuk mendeklarasikan beberapa tanda tangan kelebihan logis untuk suatu metode, tetapi semuanya berakhir pada implementasi tunggal yang sama (yang kemudian harus mencari tahu dengan parameter yang diteruskan yang disebut "kelebihan beban"). Ini banyak digunakan oleh banyak kerangka kerja JS, misalnya jQuery di mana $(function) menambahkan fungsi siap pakai, tetapi $(string) mencari DOM dengan pemilih.

Lihat di sini untuk dokumen: https://www.typescriptlang.org/docs/handbook/functions.html#overloads

@avonwyss Ah ya, deklarasi, tapi bukan implementasi, itu masuk akal :) Terima kasih

Ini telah berlangsung terlalu lama, dan saya merasa menjengkelkan bahwa saya harus membaca seluruh utas yang membengkak ini bahkan untuk mulai mencari tahu mengapa TS masih tidak memiliki pernyataan waktu kompilasi dasar ini.

Di mana kita berada pada (a) kata kunci override atau (b) anotasi jsdoc @override yang memberi tahu kompiler "sebuah metode yang memiliki nama dan jenis tanda tangan yang sama HARUS ada di superclass." ?

Lebih khusus lagi, apakah komentar di https://github.com/Microsoft/TypeScript/issues/2000#issuecomment -224776519 (8 Juni 2016) oleh @RyanCavanaugh menyarankan bahwa langkah selanjutnya adalah PR (komunitas)?

class Bar extends Foo {
  /**
   * <strong i="12">@override</strong>
   */
  public toString(): string {
     // ... 
  }

  override public toString(): string {
    // ...
  }
}

Lebih khusus lagi, apakah komentar di #2000 (komentar) (8 Jun 2016) oleh @RyanCavanaugh menunjukkan bahwa langkah selanjutnya adalah PR (komunitas)?

@pcj Benar

Saya sudah memulai PR ini di #13217. Siapa pun yang tertarik dengan fungsi ini dipersilakan untuk berpartisipasi dan/atau memberikan saran. Terima kasih!

Saya lebih suka yang sama untuk Java dengan dekorator

<strong i="6">@Override</strong>
public toString(): string {
   // ...
}

@Chris2011 Setuju ini terlihat familier, tetapi ini akan menyiratkan sebagai dekorator bahwa @Override akan dievaluasi pada saat runtime, bukan waktu kompilasi. Saya merencanakan javadoc /** <strong i="7">@override</strong> */ dalam PR terpisah setelah #13217 mengganti kata kunci ( public override toString() ) bergerak maju dalam peninjauan.

Mencoba mengikuti percakapan di thread ini. Apakah ini termasuk fungsi statis? Saya tidak melihat alasan mengapa kami juga tidak mengizinkan penggantian fungsi statis di kelas turunan dengan tanda tangan yang sama sekali berbeda. Ini didukung di ES6. Jika memiliki class A { } ; A.create = function (){} maka class B extends A { } ; B.create = function (x,y) { } , memanggil A.create() tidak akan memanggil B.create() dan menyebabkan masalah. Untuk alasan ini (dan untuk menciptakan kemampuan untuk menggunakan nama fungsi yang sama pada tipe sebagai fungsi pabrik), Anda juga harus mengizinkan penggantian tanda tangan fungsi statis dasar. Itu tidak merusak apa pun, dan menambahkan kemampuan untuk melakukan beberapa hal yang rapi (terutama untuk kerangka kerja mesin permainan, di mana sesuatu yang 'baru' benar-benar jahat jika digunakan sepanjang waktu tanpa menarik dari cache objek untuk mengurangi pembekuan GC). Karena konstruktor yang dapat dipanggil tidak didukung di bawah ES6, membuat konvensi penamaan umum untuk metode pada tipe adalah satu-satunya opsi lain; namun, melakukannya saat ini memerlukan fungsi statis turunan untuk menunjukkan kelebihan tanda tangan fungsi statis dasar bersama dengan miliknya sendiri, yang tidak berguna untuk fungsi pabrik pada tipe yang hanya berhubungan dengan tipe ITU. :/ Sementara itu satu-satunya pilihan yang saya miliki adalah memaksa pengguna kerangka kerja saya untuk menduplikasi semua tanda tangan fungsi statis hierarki dasar (seperti SomeType.create() ) pada tipe turunannya, dan seterusnya, yang menjadi sangat konyol .

Berikut adalah contoh tentang "kekonyolan" yang saya bicarakan (yang berhasil, tetapi bukan sesuatu yang saya banggakan dalam kerangka kerja yang dapat diperluas):

class A {
    static create(s: string) {
        var inst: A;
        /* new or from cache */
        inst.init(s);
    }
    protected init(s: string) { }
}

class B extends A {
    static create(s: string);
    static create(n: number);
    static create(n:any) {
        var inst: B;
        /* new or from cache */
        inst.init(n);
    }
    protected init(s: string);
    protected init(n: number);
    protected init(n: any) {
        super.init(n.toString());
    }
}

class C extends B {
    static create(s: string)
    static create(n: number)
    static create(b: boolean)
    static create(b: any) {
        var inst: C;
        /* new or from cache */
        inst.init(b);
    }
    protected init(s: string);
    protected init(n: number);
    protected init(b: boolean);
    protected  init(b: any) {
        super.init(b ? 0 : 1);
    }
}

(https://goo.gl/G01Aku)

Ini akan jauh lebih baik:

class A {
    static create(s: string) {
        var inst: A;
        /* new or from cache */
        inst.init(s);
    }
    protected init(s: string) { }
}

class B extends A {
    new static create(n:number) {
        var inst: B;
        /* new or from cache */
        inst.init(n);
    }
    new protected init(n: number) {
        super.init(n.toString());
    }
}

class C extends B {
    new static create(b: boolean) {
        var inst: C;
        /* new or from cache */
        inst.init(b);
    }
    new protected  init(b: boolean) {
        super.init(b ? 0 : 1);
    }
}

@rjamesnw Anda mungkin tertarik dengan Polimorfik "ini" untuk anggota statis yang memiliki kontrak untuk fungsi pabrik dibahas sebagai kasus penggunaan utama di seluruh komentar.

jadi, sekali lagi menyatakan perasaan yang diungkapkan @pcj ( komentar ) lebih dari setahun yang lalu, membingungkan harus membaca begitu banyak PR dan komentar di sini dan di tempat lain untuk menentukan di mana permintaan fitur ini berada.

sepertinya #13217 sangat dekat, kemudian ditembak jatuh lagi oleh @DanielRosenwasser dan perusahaan sebagai fitur yang mungkin tidak sesuai dengan bahasa, tampaknya memasuki kembali percakapan melingkar di sini tentang masalah ini tentang apakah itu harus dilakukan atau tidak. Mungkin dekorator 'waktu desain' (#2900) ini akan menyelesaikannya, mungkin tidak? Akan menyenangkan untuk mengetahuinya.

Berikut adalah beberapa kekurangan dari pendekatan dekorator runtime yang diposting beberapa tahun yang lalu yang tidak saya lihat disebutkan:

  • Itu tidak akan berfungsi dengan properti karena mereka bukan bagian dari prototipe
  • Properti dan pengakses dapat (semacam...) menimpa satu sama lain, yang juga tidak akan berfungsi
  • Kami memiliki kelas abstrak sekarang, tetapi karena hal-hal abstrak sebenarnya tidak ada saat runtime, itu tidak mungkin bekerja dengan mereka

Jika satu-satunya peringatan adalah bahwa pemeriksaan terjadi pada inisialisasi kelas maka saya mungkin bisa menerimanya sebagai tindakan sementara, tetapi karena ada terlalu banyak batasan.

Terus terang saya tidak percaya fitur ini masih sangat kontroversial selama 3 tahun sejak saya pertama kali mencatat masalah ini. Setiap bahasa berorientasi objek "serius" mendukung kata kunci ini, C#, F#, C++... sebut saja.

Anda dapat berdebat hipotetis sepanjang hari tentang mengapa javascript berbeda dan membutuhkan pendekatan yang berbeda. Tetapi dari perspektif praktis bekerja setiap hari dalam basis kode TypeScript yang sangat besar, saya dapat memberi tahu Anda bahwa penggantian akan membuat perbedaan besar pada keterbacaan dan pemeliharaan kode. Itu juga akan menghilangkan seluruh kelas bug karena kelas turunan secara tidak sengaja menimpa metode kelas dasar dengan tanda tangan yang kompatibel tetapi agak berbeda.

Saya sangat ingin melihat implementasi yang tepat dari virtual/override/final. Saya akan menggunakannya sepanjang waktu, dan itu akan membuat kode jauh lebih mudah dibaca dan kurang rawan kesalahan. Saya merasa ini adalah fitur penting.

Sepakat. Sangat frustasi melihat bagaimana fitur-fitur tepi-kasus yang relatif tidak jelas ditambahkan, sementara sesuatu yang begitu... mendasar ditolak. Apakah ada yang bisa kita lakukan untuk mendorong ini?

Ayo orang! Jika ada banyak komentar dan lebih dari tiga tahun, mengapa belum diterapkan!

Ayo :joy_cat:

Harap konstruktif dan spesifik; kami tidak membutuhkan lusinan komentar TOLONG LAKUKAN SUDAH

Tapi teman-teman, harap spesifik juga di pihak Anda.

Jelas komunitas sangat tertarik dengan fitur itu, dan tim TS tidak memberi tahu kami secara spesifik tentang masa depan yang satu ini.

Saya pribadi sepenuhnya setuju dengan @armandn , rilis terbaru TS menghadirkan fitur yang relatif jarang digunakan saat hal seperti ini diadakan.

Jika Anda tidak berencana untuk melakukannya, beri tahu kami. Jika tidak, beri tahu kami bagaimana komunitas dapat membantu.

Hanya garis waktu di sini karena ada lebih banyak komentar daripada yang bersedia ditampilkan GitHub saat dimuat:

Bukannya kami tidak mempertimbangkan di sini. Ini sedekat fitur masuk, dan kami telah menolak ide fitur kami sendiri dengan alasan yang sama - lihat #24423 untuk contoh terbaru.

Kami benar-benar ingin menumbuhkan bahasa dengan sengaja. Ini membutuhkan waktu, dan Anda harus bersabar. Sebagai perbandingan, C++ lebih tua dari banyak orang di utas ini ; TypeScript belum cukup umur untuk berada di rumah tanpa pengawasan orang dewasa. Setelah kami menambahkan sesuatu ke bahasa, kami tidak dapat menghapusnya lagi, jadi setiap penambahan harus dipertimbangkan dengan hati-hati terhadap pro dan kontranya. Saya berbicara dari pengalaman bahwa fitur yang diteriakkan orang (metode ekstensi, saya melihat Anda) akan menghancurkan kemampuan kami untuk terus mengembangkan bahasa (tidak ada tipe persimpangan, tidak ada tipe serikat pekerja, tidak ada tipe bersyarat) seandainya kami menerapkannya hanya karena ada banyak komentar GitHub. Saya tidak mengatakan override adalah hal yang berbahaya untuk ditambahkan, hanya saja kita selalu mendekati ini dengan kehati-hatian maksimal.

Jika saya harus meringkas masalah inti dengan override sekarang:

  • Itu tidak membiarkan Anda melakukan apa pun yang tidak dapat Anda lakukan hari ini dengan dekorator (jadi "bukan hal baru" dalam hal "hal yang dapat Anda capai")
  • Semantik tidak dapat secara tepat menyelaraskan dengan override dalam bahasa lain (meningkatkan beban kognitif)
  • Itu tidak "menyala" 100% tanpa bendera commandline baru, dan itu akan menjadi bendera yang mungkin kita inginkan di bawah strict tetapi secara realistis tidak bisa karena itu terlalu besar perubahan yang melanggar (mengurangi nilai yang dikirim). Dan apa pun yang menyiratkan flag commandline baru adalah penggandaan lain dari ruang konfigurasi yang perlu kami pertimbangkan dengan serius, karena Anda hanya dapat menggandakan sesuatu berkali-kali sebelum menghabiskan seluruh anggaran mental Anda saat membuat keputusan di masa mendatang
  • Ketidaksepakatan internal yang cukup kuat di sini tentang apakah override dapat diterapkan untuk mengimplementasikan anggota antarmuka (mengurangi nilai yang dirasakan karena harapan tidak sesuai dengan kenyataan untuk sebagian orang)

Keuntungan totalnya di sini adalah Anda dapat a) menunjukkan bahwa Anda menimpa sesuatu (yang dapat Anda lakukan hari ini dengan komentar), b) salah mengeja yang seharusnya membantu Anda dengan pelengkapan otomatis, dan c) dengan bendera baru, tangkap tempat di mana Anda "lupa" memasukkan kata kunci dan sekarang perlu (tugas mekanis biasa yang tidak mungkin menemukan bug nyata). Saya mengerti bahwa b) sangat membuat frustrasi tetapi sekali lagi, kita harus memenuhi bilah kompleksitas di sini.

Pada akhirnya jika menurut Anda kelas JS akan sangat terbantu oleh kata kunci override , maka memperjuangkan proposal TC39 untuk menambahkannya ke runtime inti akan menjadi tempat yang baik untuk memulai.

Itu tidak membiarkan Anda melakukan apa pun yang tidak dapat Anda lakukan hari ini dengan dekorator (jadi "bukan hal baru" dalam hal "hal yang dapat Anda capai")

Mungkin saya salah memahami ini, tetapi ada hal-hal penting yang tidak dapat dilakukan oleh seorang dekorator yang dapat dilakukan oleh kata kunci. Saya menyebutkan beberapa di https://github.com/Microsoft/TypeScript/issues/2000#issuecomment -389393397 . Perbaiki saya dengan pasti jika hal-hal itu mungkin, karena saya ingin menggunakan pendekatan dekorator tetapi metode non-abstrak hanya sebagian kecil dari kasus penggunaan saya untuk fitur ini.

Keuntungan lain yang tidak disebutkan adalah bahwa kata kunci override akan membuat perubahan sesuatu di kelas dasar jauh lebih layak. Mengubah nama atau membagi sesuatu menjadi dua bagian dll sulit mengetahui bahwa setiap kelas turunan akan dikompilasi dengan baik tanpa diperbarui untuk mencocokkan tetapi mungkin akan gagal saat runtime. Juga, mengingat bahwa tidak ada kata kunci final/tersegel/internal yang tersedia, hampir semua kelas yang diekspor mungkin diturunkan di suatu tempat yang membuat perubahan apa saja yang non-pribadi jauh lebih berisiko daripada dengan penggantian yang tersedia.

Kesan saya adalah bahwa aturan TSLint sejauh ini merupakan jalur paling mulus ke depan di sini. @kungfusheep secara singkat menyebutkan ide di https://github.com/Microsoft/TypeScript/issues/2000#issuecomment -192502734 , tapi saya tidak melihat tindak lanjut. Apakah ada orang lain yang mengetahui implementasi TSLint dari fitur ini? Jika tidak, saya mungkin akan mulai meretasnya ketika saya mendapat kesempatan. 😄.

Pikiran saya saat ini adalah itu hanya akan menjadi komentar, // @override , seperti // @ts-ignore dan berbagai arahan berbasis komentar lainnya yang pernah saya lihat. Saya pribadi lebih suka menghindar dari dekorator karena (dalam bentuknya saat ini) mereka memiliki semantik runtime dan karena mereka masih tahap 2 dan dinonaktifkan secara default.

Dengan sedikit keberuntungan, aturan TSLint kustom akan menjadi solusi 90%, hanya benar-benar kurang dalam estetika, dan tentu saja dapat bergerak maju tanpa harus berkomitmen pada detail bahasa apa pun. Dengan aturan tipe-sadar, layanan bahasa tslint, dan perbaikan otomatis, sebenarnya tidak ada banyak perbedaan antara kesalahan TSLint dan kesalahan TS bawaan dari perspektif pengembang. Harapan saya adalah bahwa plugin TSLint (atau beberapa plugin yang bersaing) akan memberi komunitas beberapa pengalaman dan kesempatan untuk mencapai lebih banyak kesepakatan tentang semantik terbaik. Kemudian mungkin itu bisa ditambahkan sebagai aturan inti TSLint, dan mungkin itu akan memberikan kejelasan dan motivasi yang cukup untuk membenarkan manfaat estetika dari kata kunci override dalam bahasa inti.

Hanya berpikir di luar kotak di sini. Kami memiliki dua skenario berbeda di sini. C# (sebagai contoh) menggunakan virtual dan override karena metode BUKAN virtual secara default. Dalam JavaScript semua fungsi di kelas adalah virtual secara default (secara alami). Tidakkah lebih masuk akal untuk membalikkan proses dan memiliki pengubah tipe nooverride sebagai gantinya? Tentu saja orang masih bisa memaksanya, tetapi setidaknya itu ada untuk membantu mendorong konvensi bahwa beberapa fungsi di kelas dasar tidak boleh disentuh. Sekali lagi, hanya berpikir di luar norma di sini. ;) Mungkin juga kurang dari perubahan yang melanggar.

Tidakkah lebih masuk akal untuk membalikkan proses dan memiliki pengubah tipe nooverride sebagai gantinya?

Saya suka cara berpikir Anda, dan saya pikir apa yang Anda cari sudah final .

Bagaimana dengan readonly ? Saya percaya mengganti metode di JS sebenarnya berarti mengganti metode induk dengan metode anak selama instantiasi (ketika rantai prototipe diterapkan). Dalam hal ini, memiliki metode readonly berarti "Itu ada untuk dilihat semua orang, tetapi saya tidak ingin siapa pun mengubahnya, baik dengan pewarisan atau secara dinamis setelah instantiasi" sangat masuk akal. Ini sudah diterapkan untuk anggota, mengapa tidak melakukannya untuk metode juga?

Apakah sudah ada usulan untuk itu? Jika tidak, mungkin perlu diselidiki sebagai alternatif untuk menimpa...

_edit:_ ternyata Anda dapat mengganti anggota yang hanya bisa dibaca, jadi seluruh argumen ini runtuh.

Mungkin mengizinkan fungsi kelas private adalah pilihan lain?

Sunting: Saya berpikir "alih-alih menandainya sebagai final" tetapi saya pasti setengah tertidur (jelas "final" berarti publik tetapi tidak dapat menimpa), LOL; sudahlah.

@rjamesnw Anda sudah dapat mendefinisikan fungsi kelas sebagai publik, dilindungi, pribadi.

Saya tidak berpikir bahwa hanya memiliki "final" akan menjadi solusi. Masalahnya adalah bahwa orang dapat secara tidak sengaja membuat fungsi baru di kelas yang mewarisi dari kelas dasar dengan nama yang sudah digunakan, dan kemudian Anda akan memecahkan sesuatu secara diam-diam tanpa mengetahui alasannya. Itu terjadi pada saya beberapa kali, sangat membuat frustrasi, karena Anda sering tidak mendapatkan kesalahan, dan hal-hal aneh terjadi begitu saja (atau tidak)...

Jadi saya pikir, sungguh, kita sedang melihat entri tsconfig.json baru yang akan memaksa kompiler untuk membuat kesalahan ketika sesuatu ditimpa tanpa ditandai sebagai virtual (dan yang utama ditandai sebagai penimpaan atau final).

Saya pikir tanpa override , TypeScript mengecewakan penggunanya pada janji [yang dirasakan] tentang keamanan waktu kompilasi dan keamanan jenis.
Argumen "gunakan perintah IDE untuk mengganti metode" rusak saat kode berkembang (seperti yang ditunjukkan orang lain).
Juga sepertinya semua bahasa utama yang sebanding telah menambahkan override dalam beberapa bentuk.
Untuk tidak membuatnya menjadi perubahan yang melanggar, itu bisa menjadi aturan tslint (mirip seperti di Jawa).
Btw, mengapa tidak mengizinkan perubahan yang melanggar antara bahasa versi utama misalnya 2.x -> 3.x. Kami tidak ingin terjebak, bukan?
Jika ternyata ada yang salah dengan detail tertentu dari override , dan itu memerlukan beberapa perubahan-perubahan-tweak di 3.x, biarlah. Saya pikir kebanyakan orang akan memahami dan menghargai pengorbanan kecepatan evolusi vs kompatibilitas. Benar-benar tanpa override Saya tidak dapat merekomendasikan TS sebagai sesuatu yang lebih praktis daripada Java atau C#...

Jika orang ingin menjadi ketat, harus ada beberapa cara override wajib (bisa menjadi aturan tslint opsional). Nilai dari override wajib jauh lebih rendah, karena kemungkinan secara tidak sengaja mengganti metode dari superclass tampaknya jauh lebih rendah daripada tidak sengaja tidak menimpa .

Sungguh, masalah yang dibuka sejak 2015 ini adalah seember air dingin untuk antusiasme dan penginjilan TypeScript saya ...

Ini tidak menangani metode penggantian dari grand*-parents:

/* Put this in a helper library somewhere */ function override(container, key, other1) { var baseType = Object.getPrototypeOf(container); if(typeof baseType[key] !== 'function') { throw new Error('Method ' + key + ' of ' + container.constructor.name + ' does not override any base class method'); } }

Hal ini juga menyedihkan dan mengecewakan bahwa No one assigned pada GH di sini pada masalah ini. Mungkin waktu untuk garpu TypeScript? ;) CompileTimeSafeScript...

Seluruh utas ini terlihat seperti anti-pola "Kelumpuhan oleh analisis" yang besar. Mengapa tidak melakukan "80% kasus bernilai dengan 20% pekerjaan" dan, mencobanya dalam praktik, dan, jika perlu, mengubahnya dalam 3.x ?

Kasus penggunaan yang sederhana, sering dan sangat berharga disandera oleh beberapa kasus sudut yang kebanyakan orang tidak perlu khawatirkan.

Jadi, ini dimungkinkan untuk mengetik-memeriksa pada waktu kompilasi melalui dekorator, meskipun bukan tanpa sedikit bantuan:

export const override = <P extends Function>() => <K extends keyof P["prototype"]>(
  target: Object,
  methodName: K,
  descriptor: TypedPropertyDescriptor<P["prototype"][K]>
) => {
  // this is a no-op. The checking is all performed at compile-time, so runtime checks are not needed.
}

class Bar {
  biz (): boolean {
    return true;
  }

  qux (): string {
    return "hi";
  }
}

class Foo extends Bar {
  // this is fine
  @override<typeof Bar>()
  biz (): boolean {
    return false;
  }

  // error: type '() => number' is not assignable to type '() => boolean'
  @override<typeof Bar>()
  biz (): number {
    return 5;
  }

  // error: argument of type '"baz"' is not assignable to parameter of type '"biz" | "qux"'
  @override<typeof Bar>()
  baz (): boolean {
    return false;
  }
}

Saya tidak yakin apakah mungkin untuk mendapatkan referensi ke tipe super tanpa meneruskannya secara eksplisit. Ini menimbulkan biaya runtime yang kecil, tetapi pemeriksaannya adalah semua waktu kompilasi.

@alangpierce

Kesan saya adalah bahwa aturan TSLint sejauh ini merupakan jalur paling mulus ke depan di sini.

[...]

Maka mungkin itu bisa ditambahkan sebagai aturan inti TSLint, dan mungkin itu akan memberikan kejelasan dan motivasi yang cukup untuk membenarkan manfaat estetika dari kata kunci override dalam bahasa inti.

100% setuju. Mari kita mulai!

Hai - sedikit lebih lambat dari yang direncanakan tetapi ini adalah aturan tslint kami yang diterapkan menggunakan dekorator.

https://github.com/bet365/override-linting-rule

Kami telah menggunakan ini di seluruh basis kode TypeScript ~1mloc kami selama beberapa tahun dan baru-baru ini memperbaruinya untuk menggunakan layanan bahasa, membuatnya lebih cepat untuk dieksekusi dan lebih layak untuk open source (iterasi sebelumnya membutuhkan penyelesaian proyek secara penuh untuk mengumpulkan informasi warisan).

Untuk kasus penggunaan kami, ini sangat berharga - meskipun kami masih berpendapat bahwa tanggung jawab pada akhirnya harus terletak pada kompiler dan karena itu aturan linting harus dilihat sebagai stop-gap.

Terima kasih

Saya setuju. Saya juga percaya bahwa solusi yang melibatkan kompiler TS akan jauh lebih baik daripada dekorator dan aturan serat.

Apa status fitur ini? Apakah sudah ditinjau kembali sebagai fitur kompiler?

Jadi, ini dimungkinkan untuk mengetik-memeriksa pada waktu kompilasi melalui dekorator, meskipun bukan tanpa sedikit bantuan:

Beberapa perbaikan:

function override< Sup >( sup : { prototype : Sup } ) {
    return <
        Field extends keyof Sup ,
        Proto extends { [ key in Field ] : Sup[ Field ] } ,
    >(
        proto : Proto ,
        field : Field ,
        descr : TypedPropertyDescriptor< Sup[ Field ] > ,
    )=> {}
}

class Foo {

    bar( a : number ) {
        return a 
    }

    bar2( a : number , b : number ) {
        return a 
    }

}

class Foo2 {

    @override( Foo )
    bar( a : number ) {
        return 1
    }

    @override( Foo )
    bar2( a : number , b : number ) {
        return 1 
    }

    xxx() { return '777' }

}

class Foo3 extends Foo2 {

    @override( Foo ) // OK
    bar( a : number ) { return 5 }

    @override( Foo ) // Error: less args than should
    bar2( a : number ) { return 5 }

    @override( Foo ) // Error: accidental override Foo2
    xxx() { return '666' }

    @override( Foo ) // Error: override of absent method
    yyy() { return 0 }

}

Tempat bermain

Oke, temukan solusi override yang mencegah kesalahan kompilasi.
Contoh dengan aliran Node Readable:

// Interface so you will keep typings for all Readable methods/properties that are not overriden:
// Fileds that are `Omit`-ed should be overriden (with any signature you want, it do not have to be compatible with parent class)
interface ReadableObjStream<T> extends Omit<stream.Readable, 'push' | 'read'> {}

// Use extends (TYPE as any) to avoid compilation errors and override `Omit`-ted methods
class ReadableObjStream<T> extends (stream.Readable as any) {
    constructor()  {
        super({objectMode: true}); // force object mode. You can merge it with original options
    }
    // Override `Omit`-ed methods with YOUR CUSTOM SIGNATURE (can be non-comatible with parent):
    push(myOwnNonCompatibleSignature: T): string  { /* implementation*/ };
    read(options_nonCompatibleSignature: {opts: keyof T} ): string  { /* implementation*/ }
}

let typedReadable = new ReadableObjMode<{myData: string}>();
typedReadable.push({something: 'else'}); // will throw compilation error as expected
typedReadable.pipe(...) // non overloaded methods typings supported as expected

Satu-satunya kelemahan dari solusi ini adalah kurangnya pengetikan saat memanggil super.parentMethod (tetapi berkat interface ReadableObjStream Anda memiliki semua pengetikan saat menggunakan instance ReadableObjStream.

@nin-jin @bioball Terima kasih atas kontribusinya dengan pemeriksaan waktu kompilasi dekorator.

Sayangnya sepertinya tidak berfungsi dengan anggota protected , hanya dengan public .

(contoh kesalahan di garpu taman bermain nin-jin saya

Penentu override adalah fitur pembunuh di c++ 11.
Ini sangat membantu dan melindungi kode refactoring.
Saya pasti ingin ini dalam dukungan basis TS tanpa rintangan (PILIH!)

Kami benar-benar telah menunjukkan dukungan liar untuk fitur ini, tetapi tampaknya mereka masih belum berencana untuk menambahkan fitur ini dalam waktu dekat.

Ide lain:

class Obj { 

    static override<
        This extends typeof Obj,
        Over extends keyof InstanceType<This> = never,
    >(this: This, ...overs: Over[]) { 
        return this as This & (
            new(...a:any[])=> InstanceType<This> & Protect< Omit<InstanceType<This> , Over > >
        )
    }

}

class Foo extends Obj {

    bar(a: number) {
        return 0
    }

    bar2(a: number) {
        return 0
    }

    foo = 1

}

class Foo2 extends Foo.override('bar') {

    foo = 2

    bar( a : number ) {
        return 1
    }

    // Error: Class 'Foo & Protect<Pick<Foo, "bar2" | "foo">>'
    // defines instance member property 'bar2',
    // but extended class 'Foo2' defines it as instance member function.
    bar2( a : number ) {
        return 1
    }

    bar3( a : number ) {
        return 1
    }

}

declare const Protected: unique symbol

type Protect<Obj> = {
    [Field in keyof Obj]:
    Object extends () => any
    ? Obj[Field] & { [Protected]: true }
    : Obj[Field]
}

Tautan Taman Bermain

Menambahkan dua sen saya di sini.
Kami memiliki komponen sudut khas, yang menangani formulir dan perlu berhenti berlangganan dari valueChanges dan semacamnya. Kami tidak ingin mengulangi kode di semua tempat (agak keadaan saat ini), jadi saya telah membuat "TypicalFormBaseComponent" yang (antara lain) mengimplementasikan metode angulars OnInit dan OnDestroy. Masalahnya adalah sekarang jika Anda benar-benar menggunakannya dan menambahkan metode OnDestroy Anda sendiri (yang cukup standar untuk dilakukan), Anda menyembunyikan yang asli dan merusak sistem. memanggil super.OnInit memperbaikinya, tetapi saat ini tidak ada mekanisme bagi saya untuk memaksa anak-anak melakukan itu.

Jika Anda melakukannya dengan konstruktor, itu memaksa Anda untuk memanggil super()... Saya sedang mencari sesuatu yang serupa, menemukan utas ini, dan sejujurnya, saya agak kecewa.
Menerapkan "baru" dan "menimpa" di ts mungkin merupakan perubahan yang melanggar, tetapi memperbaikinya akan sangat sederhana pada dasarnya setiap basis kode (hanya menambahkan 'override' di mana-mana di mana ia berteriak). Mungkin juga hanya peringatan .

Ngomong-ngomong, apakah ada cara lain bagiku untuk memaksakan panggilan super pada anak-anak? Aturan TSLint yang mencegah persembunyian, atau semacamnya?

PS: Saya tidak setuju dengan kebijakan "no comment". Ini mencegah orang untuk memberikan contoh di sini. Mungkin Anda tidak akan menerapkan 'override', tetapi Anda akan menerapkan sesuatu yang lain, yang menangani masalah mereka... yaitu, jika Anda benar-benar dapat membacanya.

@GonziHere Dalam bahasa lain seperti C# atau Java, overriding tidak menyiratkan persyaratan untuk memanggil metode super - sering kali kenyataannya tidak demikian. Konstruktor adalah "metode virtual" khusus dan bukan normal.

Kata kunci override akan digunakan untuk menentukan bahwa metode harus sudah didefinisikan dengan tanda tangan yang kompatibel di kelas dasar, dan kompiler dapat menegaskan ini.

Ya, tetapi kebutuhan untuk menimpa memaksa Anda untuk memperhatikan bahwa metode itu ada.

@GonziHere Sepertinya yang Anda butuhkan adalah membuat kelas dasar abstrak dengan fungsi abstrak. Mungkin Anda dapat membuat metode _onInit dan _onDestroy pribadi yang dapat lebih Anda andalkan, kemudian membuat fungsi abstrak onInit dan onDestroy yang dilindungi (atau fungsi biasa jika tidak diperlukan). Fungsi pribadi akan memanggil yang lain, lalu selesai seperti biasa.

@GonziHere @rjamesnw Hal yang aman untuk dilakukan sebenarnya adalah entah bagaimana menegakkan bahwa onInit dan onDestroy adalah _final_, dan mendefinisikan metode templat abstrak terlindungi kosong yang dipanggil oleh metode terakhir. Ini menjamin bahwa tidak seorang pun dapat secara tidak sengaja menimpa metode, dan mencoba melakukannya (dengan asumsi ada cara untuk menerapkan metode akhir) akan segera mengarahkan pengguna ke cara yang benar untuk mengimplementasikan apa yang mereka inginkan.

@shicks , @rjamesnw , saya berada dalam situasi yang sama dengan @GonziHere. Saya tidak ingin kelas dasar memiliki metode abstrak karena ada fungsionalitas yang ingin saya jalankan untuk setiap kait siklus hidup tersebut. Masalahnya adalah saya tidak ingin fungsionalitas dasar itu secara tidak sengaja diganti ketika seseorang menambahkan ngOnInit atau ngOnDestroy pada kelas anak tanpa memanggil super() . Memerlukan override akan menarik perhatian pengembang bahwa metode tersebut ada di kelas dasar dan bahwa mereka harus memilih jika perlu memanggil super() ;

Dari pengalaman saya menulis Java, @Override sangat umum sehingga menjadi noise. Dalam banyak kasus, metode yang diganti adalah abstrak atau kosong (di mana super() seharusnya _tidak_ dipanggil), membuat keberadaan override bukan sinyal yang sangat jelas bahwa super() diperlukan.

Dari pengalaman saya menulis Java, @Override sangat umum sehingga menjadi noise. Dalam banyak kasus, metode yang diganti adalah abstrak atau kosong (di mana super() seharusnya _tidak_ dipanggil), membuat keberadaan override bukan sinyal yang sangat jelas bahwa super() diperlukan.

Itu tidak ada hubungannya dengan subjek yang ada. Anda mungkin juga mengatakan untuk menggunakan komposisi daripada pewarisan untuk menghindari masalah.

Bagi saya, bagian paling berguna dari override adalah mendapatkan kesalahan saat refactoring .
Saat ini terlalu mudah untuk mengganti nama beberapa metode di kelas dasar dan lupa untuk mengganti nama yang menimpanya.
Mengingat untuk menelepon super tidak begitu penting. Bahkan benar untuk tidak menyebutnya dalam beberapa kasus.

Saya pikir ada kesalahpahaman tentang mengapa _override_ menjadi penting. Bukan untuk memaksa seseorang memanggil metode _super()_, tetapi untuk membuat mereka sadar bahwa metode itu ada dan memungkinkan mereka dengan hati-hati memutuskan apakah mereka ingin menekan perilakunya atau memperluasnya.

Pola yang saya gunakan untuk memastikan kebenaran adalah dengan menggunakan Parameters dan ReturnType sambil merujuk secara eksplisit ke kelas dasar, kira-kira seperti ini:

class Base {
    public methodName(arg1: string, arg2: number): boolean {
        return false; // base behaviour, may be stub.
    }
}
class Derived extends Base {
    public methodName(...args: Parameters<Base["methodName"]>): ReturnType<Base["methodName"]> {
        const [meaningful, variableNames] = args;
        return true; // implemented behaviour here.
    }
}

Pola semacam ini menjadi dasar saran saya untuk menambahkan kata kunci inherit . . Ini memastikan bahwa setiap pembaruan ke kelas dasar secara otomatis menyebar dan jika tidak valid di kelas turunan maka kesalahan diberikan, atau perubahan nama kelas dasar maka kesalahan juga diberikan, juga dengan ide inherit Anda tidak dibatasi untuk hanya menawarkan tanda tangan yang identik dengan kelas dasar tetapi dapat memperluasnya secara intuitif.

Saya juga berpikir ada kesalahpahaman tentang mengapa override penting, tetapi saya tidak begitu mengerti semua masalah ini dengan "apakah super() diperlukan".

Untuk menggemakan apa yang @lorenzodallavecchia dan penulis asli masalah katakan, menandai fungsi sebagai override adalah mekanisme untuk menghindari kesalahan yang terjadi ketika superclass di-refactored, khususnya ketika nama dan/atau tanda tangan dari fungsi yang ditimpa berubah atau fungsi tersebut dihapus.

Ingatlah bahwa orang yang mengubah tanda tangan dari fungsi yang diganti mungkin tidak menyadari bahwa ada penggantian. Jika (misalnya dalam C++) penggantian tidak secara eksplisit ditandai sebagai penggantian, maka mengubah nama/tanda tangan dari fungsi yang diganti tidak menimbulkan kesalahan kompilasi, itu hanya menyebabkan penggantian tersebut tidak lagi ditimpa. (dan.. mungkin tidak lagi berguna, tidak lagi dipanggil oleh kode yang digunakan untuk memanggil mereka, dan memperkenalkan banyak bug baru karena hal-hal yang seharusnya memanggil penggantian sekarang memanggil implementasi dasar)

Kata kunci override, seperti yang diimplementasikan dalam C++, menyelamatkan orang yang mengubah implementasi dasar dari masalah ini, karena segera setelah refactoring mereka, kesalahan kompilasi akan menunjukkan kepada mereka bahwa ada sekelompok override (yang menimpa implementasi dasar yang sekarang tidak ada) dan petunjuk mereka ke fakta bahwa mereka mungkin perlu refactor menimpa juga.

Ada manfaat kegunaan IDE sekunder untuk memiliki pengubah override juga (juga disebutkan oleh penulis asli), yaitu bahwa setelah mengetik kata override IDE dapat membantu menunjukkan kepada Anda banyak kemungkinan tentang apa dapat ditimpa.

Bersama dengan kata kunci override , sebaiknya juga memperkenalkan tanda yang, bila diaktifkan, mengharuskan semua metode yang menimpa yang lain menggunakan kata kunci override , untuk menghindari kasus di mana Anda membuat metode di subkelas dan di masa depan kelas dasar (yang bisa di perpustakaan pihak ke-3) membuat metode dengan nama yang sama dengan metode yang Anda buat di subkelas (yang dapat menyebabkan bug sekarang, karena subkelas telah mengganti metode itu ).

Mendapatkan kesalahan kompilasi dalam kasus ini, mengatakan bahwa Anda perlu menambahkan kata kunci override dalam metode ini (walaupun sulit, Anda mungkin tidak benar-benar mengganti metode tersebut, cukup ubah namanya agar tidak menimpa metode yang baru dibuat di kelas dasar), akan jauh lebih baik dan menghindari kemungkinan bug runtime.

Dari pengalaman saya menulis Java, @Override sangat umum sehingga menjadi noise.

Setelah bertahun-tahun di Jawa, saya sangat tidak setuju dengan pernyataan di atas. Override adalah cara komunikasi, seperti pengecualian yang diperiksa. Ini bukan tentang suka atau tidak suka, tetapi tentang kualitas dan harapan.

Dan oleh karena itu +1 besar untuk @lucasbasquerotto , tetapi dari sudut pandang lain: jika saya memperkenalkan metode dalam subkelas, saya ingin memiliki semantik yang jelas untuk mengesampingkan atau tidak mengesampingkan. Misalnya jika saya ingin mengganti metode, maka saya ingin memiliki cara untuk mengatakannya secara eksplisit. Jika ada yang salah dengan implementasi saya, misalnya salah ketik atau salah salin&tempel, maka saya ingin mendapatkan umpan balik tentangnya. Atau cara lain: jika saya tidak berharap untuk menimpa, saya ingin umpan balik juga, jika menimpa kadang-kadang terjadi.

Umpan balik dari pengembang Java lain... @override adalah satu-satunya fitur yang benar-benar saya lewatkan dari TypeScript.

Saya memiliki ~15 pengalaman Java dan 1-2 tahun atau TypeScript jadi cukup nyaman dengan keduanya.

Zen dari @override adalah Anda dapat menghapus metode dari antarmuka dan kompilasi akan rusak.

Ini seperti kebalikan dari pengikatan ketat untuk metode baru. Ini memungkinkan Anda untuk menghapus yang lama.

Jika Anda menerapkan antarmuka dan TAMBAHKAN metode, kompilasi akan gagal tetapi tidak ada kebalikan dari ini.

Jika Anda menghapus suatu metode, Anda hanya akan berakhir dengan kode mati.

(meskipun mungkin itu bisa diperbaiki dengan linter)

Untuk lebih jelasnya, ketika saya mengatakan bahwa @Override adalah kebisingan, saya secara khusus mengacu pada komentar tentang itu sebagai sinyal bahwa Anda perlu menelepon super . Pengecekan yang diberikannya sangat berharga, tetapi untuk metode apa pun yang diganti, itu benar-benar membingungkan apakah itu diperlukan atau tidak ada gunanya untuk memanggil super . Karena sebagian besar metode yang diganti _tidak boleh_, itu tidak efektif untuk tujuan itu.

Maksud saya adalah bahwa jika Anda ingin memastikan subkelas memanggil kembali ke metode Anda yang dapat ditimpa, satu-satunya cara efektif untuk melakukannya adalah dengan membuat metode final (lihat #33446, antara lain) dan memanggilnya ke _nama berbeda_ metode templat kosong yang dapat diganti dengan aman _tanpa_ panggilan super . Tidak ada cara lain yang masuk akal untuk mendapatkan invarian itu. Saya sepenuhnya mendukung override , tetapi sebagai seseorang yang telah dibakar oleh pengguna yang salah mensubklasifikasikan API saya (dan saya siap untuk tidak merusaknya), saya yakin perlu mengalihkan perhatian ke final sebagai solusi yang tepat untuk masalah ini, bukan override seperti yang disarankan upthread.

Saya agak bingung mengapa orang terus menyarankan salah satu dari final atau override daripada yang lain. Tentunya kita menginginkan keduanya , karena keduanya memecahkan masalah yang berbeda.

Mengesampingkan
Mencegah orang yang memperluas kelas agar tidak secara tidak sengaja menimpa fungsi dengan miliknya sendiri, merusak banyak hal dalam proses tanpa mengetahuinya. Memiliki kata kunci override memungkinkan pengembang mengetahui bahwa mereka sebenarnya mengesampingkan fungsi yang ada, dan kemudian mereka dapat memilih untuk mengganti nama fungsi mereka atau menggunakan override (dan kemudian mereka dapat mengetahui apakah mereka perlu memanggil super).

Terakhir
Cegah orang yang memperluas kelas agar tidak menimpa fungsi sepenuhnya, sehingga pembuat asli kelas dapat menjamin kontrol penuh.

@ sam-s4s Kami juga ingin mendefinisikan :-)

Contoh soal..

Awal

class Base {}
class Entity extends Base {
    id() {
        return 'BUG-123' // busisess entity id
    }
}

Kelas dasar di-refactored

class Base {
    id() {
        return '84256635572' // storage object id
    }
}
class Entity extends Base {
    id() {
        return '12' // busisess entity id
    }
}

Kami mendapat kelebihan yang tidak disengaja di sini.

Kasus dengan kata kunci define

class Base {
    define id() {
        return '84256635572' // storage object id
    }
}
class Entity extends Base {
    define id() {
        return '12' // busisess entity id
    }
}

Shold be error: Mendefinisikan ulang secara tidak sengaja.

Solusi dengan dekorator

@nin-jin tidak define sama dengan _not_ menggunakan override ?

@ sam-s4s Kami juga ingin mendefinisikan :-)

Oh, saya belum pernah melihat siapa pun di sini menyebutkan kata kunci define - tetapi dari apa yang Anda gambarkan, saya berasumsi maksud Anda seperti kata virtual dalam C# ?

(Saya juga menemukan contoh Anda sedikit membingungkan, karena kelas Anda Base dan Entity tidak terkait. Apakah maksud Anda Entity untuk memperpanjang Base ?)

Saya kira ada 2 skenario ...
a) Anda menulis kelas dasar Anda, dengan asumsi semuanya tanpa final dapat diganti.
b) Anda menulis kelas dasar Anda dengan asumsi semuanya tanpa virtual tidak dapat ditimpa.

@sam-s4s Entitas memperluas Basis, tentu saja. :-) Saya telah memperbaiki pesan saya. virtual adalah tentang sesuatu yang lain.
@lorenzodallavecchia tidak menggunakan override adalah define | override untuk kompiler. Menggunakan define hanya define dan kompiler dapat memeriksa ini dengan ketat.

@nin-jin Dalam model Anda, ini berarti tidak menggunakan kata kunci override masih legal, kan? Contohnya:

class Base {
  myMethod () { ... }
}

class Overridden extends Base {
  // this would not fail because this is interpreted as define | override.
  myMethod () { ... }
}

Idealnya, contoh di atas menghasilkan kesalahan kecuali Anda menggunakan kata kunci override . Meskipun, saya membayangkan bahwa akan ada flag kompiler untuk memilih keluar dari pemeriksaan override, di mana memilih keluar adalah hal yang sama dengan override | define untuk semua metode

@bioball ya, itu diperlukan untuk kompatibilitas dengan banyak kode yang ada.

@sam-s4s Entitas memperluas Basis, tentu saja. :-) Saya telah memperbaiki pesan saya. virtual adalah tentang sesuatu yang lain.

Saya masih tidak yakin apa yang Anda maksud dengan define... ini adalah definisi dari virtual di C#:

The virtual keyword is used to modify a method, property, indexer, or event declaration and allow for it to be overridden in a derived class.

Mungkin maksud Anda sebaliknya, di mana, alih-alih perlu menandai fungsi sebagai virtual untuk menimpa, Anda dapat menandai fungsi sebagai define untuk berarti Anda harus menggunakan override kata kunci untuk menimpanya?

(mengapa define ? Saya belum melihat kata kunci itu dalam bahasa lain)

Saya membaca sekilas thead ini dalam beberapa menit, tetapi saya belum melihat ada orang yang mengusulkan menggunakan override sebagai cara untuk menghindari spesifikasi parameter duplikat dan mengembalikan nilai. Sebagai contoh:

class Animal {
    move(meters:number):void {
    }
}

class Snake extends Animal {
    override move(meters) {
    }
}

Dalam contoh di atas, move akan memiliki tipe pengembalian wajib yang sama ( void ) dan meters juga akan memiliki tipe yang sama, tetapi tidak perlu menentukannya. Perusahaan besar tempat saya bekerja sedang mencoba untuk memigrasikan semua javascript kami ke TypeScript, jauh dari pengetikan kompiler Penutupan. Namun, sementara di Penutupan kita hanya bisa menggunakan @override dan semua tipe akan disimpulkan dari superclass. Hal ini cukup berguna dalam mengurangi kemungkinan terjadinya mismatch dan mengurangi duplikasi. Seseorang bahkan dapat membayangkan menerapkan ini sedemikian rupa sehingga ekstensi metode dengan parameter tambahan masih dimungkinkan, meskipun tidak menentukan tipe untuk parameter yang ditentukan dalam superclass. Sebagai contoh:

class Animal {
    move(meters:number):number {
    }
}

class Snake extends Animal {
    override move(meters, time:number) {
    }
}

Motivasi saya datang ke sini dan menulis komentar adalah bahwa perusahaan kami sekarang mengharuskan kami menentukan semua jenis bahkan untuk metode yang diganti, karena TypeScript tidak memiliki fungsi ini (dan kami sedang dalam migrasi yang panjang). Ini cukup mengganggu.

FWIW, sementara lebih mudah untuk menulis, menghilangkan tipe parameter (dan contoh inferensi tipe lintas file lainnya) menghalangi keterbacaan, karena memerlukan seseorang yang tidak terbiasa dengan kode untuk menggali untuk menemukan di mana superclass didefinisikan untuk mengetahui apa tipe meters adalah (dengan asumsi Anda membacanya di luar IDE, yang cukup umum untuk proyek asing di mana Anda belum menyiapkan IDE). Dan kode lebih sering dibaca daripada ditulis.

@shicks Anda bisa mengatakan itu tentang semua variabel atau kelas yang diimpor. Menduplikasi semua informasi yang mungkin Anda perlukan ke dalam satu file mengalahkan tujuan abstraksi dan modularitas. Memaksa tipe untuk diduplikasi melanggar prinsip KERING.

Apakah halaman ini membantu?
0 / 5 - 0 peringkat

Masalah terkait

manekinekko picture manekinekko  ·  3Komentar

siddjain picture siddjain  ·  3Komentar

seanzer picture seanzer  ·  3Komentar

weswigham picture weswigham  ·  3Komentar

MartynasZilinskas picture MartynasZilinskas  ·  3Komentar