Membuat kelas abstrak di Objective-C

507

Saya awalnya seorang programmer Java yang sekarang bekerja dengan Objective-C. Saya ingin membuat kelas abstrak, tetapi itu tampaknya tidak mungkin di Objective-C. Apakah ini mungkin?

Jika tidak, seberapa dekat dengan kelas abstrak yang bisa saya dapatkan di Objective-C?

Jonathan Arbogast
sumber
18
Jawaban di bawah ini bagus. Saya menemukan ini masalah kelas abstrak secara tangensial terkait dengan metode pribadi - keduanya adalah metode untuk membatasi apa yang dapat dilakukan kode klien, dan tidak ada di Objective-C. Saya pikir itu membantu untuk memahami bahwa mentalitas bahasa itu sendiri pada dasarnya berbeda dari Jawa. Lihat jawaban saya untuk: stackoverflow.com/questions/1020070/#1020330
Quinn Taylor
Terima kasih atas info tentang mentalitas komunitas Objective-C yang bertentangan dengan bahasa lain. Itu benar-benar menyelesaikan sejumlah pertanyaan terkait yang saya miliki (seperti mengapa tidak ada mekanisme langsung untuk metode pribadi, dll).
Jonathan Arbogast
1
jadi lihat situs CocoaDev yang memberikannya perbandingan java cocoadev.com/index.pl?AbstractSuperClass
2
Meskipun Barry menyebutkannya sebagai pemikiran setelahnya (maafkan saya jika saya salah membaca), saya pikir Anda sedang mencari Protokol di Objective C. Lihat, misalnya, Apa itu Protokol? .
jww

Jawaban:

633

Biasanya, kelas Objective-C adalah abstrak oleh konvensi saja — jika penulis mendokumentasikan kelas sebagai abstrak, hanya saja jangan menggunakannya tanpa mensubklasifikasikan. Namun, tidak ada penegakan waktu kompilasi yang mencegah instantiasi kelas abstrak. Bahkan, tidak ada yang menghentikan pengguna dari memberikan implementasi metode abstrak melalui kategori (yaitu saat runtime). Anda bisa memaksa pengguna untuk setidaknya menimpa metode tertentu dengan meningkatkan pengecualian dalam implementasi metode tersebut di kelas abstrak Anda:

[NSException raise:NSInternalInconsistencyException 
            format:@"You must override %@ in a subclass", NSStringFromSelector(_cmd)];

Jika metode Anda mengembalikan nilai, itu sedikit lebih mudah digunakan

@throw [NSException exceptionWithName:NSInternalInconsistencyException
                               reason:[NSString stringWithFormat:@"You must override %@ in a subclass", NSStringFromSelector(_cmd)]
                             userInfo:nil];

karena Anda tidak perlu menambahkan pernyataan kembali dari metode.

Jika kelas abstrak benar-benar sebuah antarmuka (yaitu tidak memiliki implementasi metode konkret), menggunakan protokol Objective-C adalah pilihan yang lebih tepat.

Barry Wark
sumber
18
Saya pikir bagian paling tepat dari jawaban itu menyebutkan Anda bisa menggunakan @protocol sebagai gantinya hanya mendefinisikan metode.
Chad Stewart
13
Untuk memperjelas: Anda dapat mendeklarasikan metode dalam @protocoldefinisi, tetapi Anda tidak dapat menentukan metode di sana.
Richard
Dengan metode kategori pada NSException + (instancetype)exceptionForCallingAbstractMethod:(SEL)selectorini berfungsi dengan sangat baik.
Patrick Pijnappel
Ini berhasil bagi saya karena, IMHO, melempar pengecualian lebih jelas bagi pengembang lain bahwa ini adalah perilaku yang diinginkan lebih daripada doesNotRecognizeSelector.
Chris
Gunakan protokol (untuk kelas yang sepenuhnya abstrak) atau pola metode templat di mana kelas abstrak memiliki logika implementasi / aliran parsial seperti yang diberikan di sini stackoverflow.com/questions/8146439/… . Lihat jawaban saya di bawah ini.
hadaytullah
267

Tidak, tidak ada cara untuk membuat kelas abstrak di Objective-C.

Anda bisa mengejek kelas abstrak - dengan membuat metode / penyeleksi memanggil doesNotRecognizeSelector: dan karenanya memunculkan pengecualian membuat kelas tidak dapat digunakan.

Sebagai contoh:

- (id)someMethod:(SomeObject*)blah
{
     [self doesNotRecognizeSelector:_cmd];
     return nil;
}

Anda juga dapat melakukan ini untuk init.

Grouchal
sumber
5
@Chuck, saya tidak downvote, tetapi NSObjectreferensi menyarankan menggunakan ini di mana Anda TIDAK ingin mewarisi metode, bukan untuk menegakkan menimpa metode. Meskipun itu mungkin hal yang sama, mungkin :)
Dan Rosenstark
Mengapa Anda tidak ingin hanya menggunakan protokol dalam hal ini? Bagi saya, ini bagus untuk mengetahui hanya metode bertopik, tetapi tidak untuk seluruh kelas abstrak. Kasus penggunaan untuk ini tampaknya terbatas.
lewiguez
Saya tidak menghapusnya, tetapi saran bahwa Anda harus menyebabkan pengecualian yang muncul dalam metode init adalah alasan yang paling mungkin untuk itu. Format yang paling umum untuk subclass akan memulai metode init sendiri dengan memanggil self = [super init] - yang dengan patuh akan melempar pengecualian. Format ini berfungsi dengan baik untuk sebagian besar metode, tapi saya tidak akan pernah melakukannya di mana subclass mungkin menyebutnya super implementasi.
Xono
5
Anda benar-benar dapat membuat kelas abstrak di Objective-C, dan itu cukup umum. Ada sejumlah kelas kerangka kerja Apple yang melakukan ini. Kelas abstrak Apple memberikan pengecualian khusus (NSInvalidArgumentException), seringkali dengan memanggil NSInvalidAbstractInvocation (). Memanggil metode abstrak adalah kesalahan pemrograman, itulah sebabnya ia melempar pengecualian. Pabrik abstrak umumnya diimplementasikan sebagai klaster kelas.
quellish
2
@quellish: Seperti yang Anda katakan: memanggil metode abstrak adalah kesalahan pemrograman. Itu harus diperlakukan seperti itu, bukannya mengandalkan pelaporan kesalahan runtime (NSException). Ini menurut saya adalah masalah terbesar bagi pengembang yang berasal dari bahasa lain di mana abstrak berarti "tidak dapat membuat instance objek jenis ini" dalam Obj-C itu berarti "ada yang salah saat runtime ketika Anda membuat instance kelas ini".
Cross_
60

Hanya membaca jawaban @Barry Wark di atas (dan memperbarui untuk iOS 4.3) dan meninggalkan ini untuk referensi saya sendiri:

#define mustOverride() @throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"%s must be overridden in a subclass/category", __PRETTY_FUNCTION__] userInfo:nil]
#define methodNotImplemented() mustOverride()

maka dalam metode Anda, Anda dapat menggunakan ini

- (void) someMethod {
     mustOverride(); // or methodNotImplemented(), same thing
}



Catatan: Tidak yakin apakah membuat makro terlihat seperti fungsi C adalah ide yang baik atau tidak, tapi saya akan menyimpannya sampai bersekolah sebaliknya. Saya pikir itu lebih tepat untuk digunakan NSInvalidArgumentException(daripada NSInternalInconsistencyException) karena itulah yang melempar sistem runtime sebagai tanggapan doesNotRecognizeSelectordipanggil (lihat NSObjectdokumen).

Dan Rosenstark
sumber
1
Tentu saja, @TomA, saya harap ini memberi Anda ide untuk kode lain yang bisa Anda makro. Makro saya yang paling sering digunakan adalah referensi sederhana ke singleton: kode mengatakan universe.thingtetapi diperluas [Universe universe].thing. Sangat menyenangkan, menghemat ribuan surat kode ...
Dan Rosenstark
Bagus. Mengubahnya sedikit, meskipun: #define mustOverride() @throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"%s must be overridden in a subclass/category", __PRETTY_FUNCTION__] userInfo:nil].
noamtm
1
@ Yar: Saya kira tidak. Kami menggunakan __PRETTY_FUNCTION__semua tempat, melalui makro DLog (...), seperti yang disarankan di sini: stackoverflow.com/a/969291/38557 .
noamtm
1
untuk lebih jelasnya -#define setMustOverride() NSLog(@"%@ - method not implemented", NSStringFromClass([self class])); mustOverride()
gbk
1
jika Anda menambahkan kelas dasar dan kemudian mewarisi kelas ini misalnya 10 kali dan lupa mengimplementasikannya di salah satu kelas, Anda akan mendapatkan pesan dengan nama kelas Dasar yang tidak diwarisi seperti Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[BaseDynamicUIViewController localizeUI] must be overridden in a subclass/category'dalam kasus saya mengusulkan Anda juga mendapatkan di HomeViewController - method not implementedmana HomeViewController diwarisi dari Base - ini akan memberikan informasi lebih lanjut
gbk
42

Solusi yang saya temukan adalah:

  1. Buat protokol untuk semua yang Anda inginkan di kelas "abstrak" Anda
  2. Buat kelas dasar (atau mungkin menyebutnya abstrak) yang mengimplementasikan protokol. Untuk semua metode yang Anda inginkan "abstrak" menerapkannya dalam file .m, tetapi bukan file .h.
  3. Mintalah kelas anak Anda mewarisi dari kelas dasar DAN mengimplementasikan protokol.

Dengan cara ini kompiler akan memberi Anda peringatan untuk metode apa pun dalam protokol yang tidak diterapkan oleh kelas anak Anda.

Ini tidak sesingkat di Jawa, tetapi Anda mendapatkan peringatan kompiler yang diinginkan.

makanan merah
sumber
2
+1 Ini benar-benar solusi yang paling dekat dengan Kelas abstrak di Jawa. Saya telah menggunakan pendekatan ini sendiri dan ini bekerja dengan baik. Bahkan diizinkan untuk memberi nama protokol yang sama dengan kelas dasar (seperti yang Apple lakukan NSObject). Jika Anda kemudian meletakkan protokol dan deklarasi kelas dasar dalam file header yang sama itu hampir tidak bisa dibedakan dari kelas abstrak.
codingFriend1
Ah, tapi kelas abstrak saya mengimplementasikan bagian dari sebuah protokol, sisanya diimplementasikan oleh subclass.
Roger CS Wernersson
1
Anda bisa saja hanya kelas anak yang mengimplementasikan protokol dan membiarkan metode superclass bersama-sama alih-alih kosong. kemudian memiliki properti jenis Superclass <MyProtocol>. juga, untuk menambah fleksibilitas Anda dapat mengawali metode Anda dalam protokol Anda dengan @ opsional.
dotToString
Tampaknya ini tidak berfungsi di Xcode 5.0.2; itu hanya menghasilkan peringatan untuk kelas "abstrak". Tidak memperluas kelas "abstrak" menghasilkan peringatan compiler yang tepat, tetapi jelas tidak membiarkan Anda mewarisi metode.
Topher Fangio
Saya lebih suka solusi ini tetapi benar-benar tidak menyukainya, itu sebenarnya bukan struktur kode yang baik dalam proyek. // harus menggunakan ini sebagai tipe dasar untuk subclass. ketikkan BaseClass <BaseClassProtocol> BASECLASS; Ini hanya aturan seminggu, saya tidak suka itu.
Itachi
35

Dari milis Omni Group :

Objective-C tidak memiliki konstruksi kompiler abstrak seperti Java saat ini.

Jadi yang Anda lakukan adalah mendefinisikan kelas abstrak sebagai kelas normal lainnya dan menerapkan metode stubs untuk metode abstrak yang kosong atau melaporkan non-dukungan untuk pemilih. Sebagai contoh...

- (id)someMethod:(SomeObject*)blah
{
     [self doesNotRecognizeSelector:_cmd];
     return nil;
}

Saya juga melakukan hal berikut untuk mencegah inisialisasi kelas abstrak melalui penginisialisasi default.

- (id)init
{
     [self doesNotRecognizeSelector:_cmd];
     [self release];
     return nil;
}
Peter Mortensen
sumber
1
Saya tidak pernah berpikir untuk menggunakan -doesNotRecognizeSelector: dan saya agak suka pendekatan ini dalam beberapa hal. Adakah yang tahu cara membuat kompiler mengeluarkan peringatan untuk metode "abstrak" yang dibuat dengan cara ini atau dengan menaikkan pengecualian? Itu akan luar biasa ...
Quinn Taylor
26
Pendekatan doesNotRecognizeSelector mencegah pola self = [super init] yang disarankan Apple.
dlinsin
1
@david: Saya cukup yakin intinya adalah untuk meningkatkan pengecualian sesegera mungkin. Idealnya harus pada waktu kompilasi, tetapi karena tidak ada cara untuk melakukan itu, mereka memutuskan untuk menjalankan pengecualian waktu. Ini mirip dengan kegagalan pernyataan, yang seharusnya tidak pernah diangkat dalam kode produksi di tempat pertama. Bahkan, pernyataan (false) mungkin sebenarnya lebih baik karena lebih jelas bahwa kode ini tidak boleh dijalankan. Pengguna tidak dapat melakukan apa pun untuk memperbaikinya, pengembang harus memperbaikinya. Dengan demikian mengajukan pengecualian atau kegagalan pernyataan di sini terdengar seperti ide yang bagus.
Senseful
2
Saya tidak berpikir ini adalah solusi yang layak karena subclass mungkin memiliki panggilan sah untuk [super init].
Raffi Khatchadourian
@ Dlinsin: Itulah yang Anda inginkan ketika kelas abstrak tidak seharusnya diinisialisasi melalui init. Subkelasnya mungkin tahu metode super apa yang harus dihubungi, tetapi ini menghentikan permohonan sembarangan melalui "baru" atau "alokasikan / init".
gnasher729
21

Alih-alih mencoba membuat kelas dasar abstrak, pertimbangkan untuk menggunakan protokol (mirip dengan antarmuka Java). Ini memungkinkan Anda untuk mendefinisikan serangkaian metode, dan kemudian menerima semua objek yang sesuai dengan protokol dan mengimplementasikan metode. Sebagai contoh, saya dapat mendefinisikan protokol Operasi, dan kemudian memiliki fungsi seperti ini:

- (void)performOperation:(id<Operation>)op
{
   // do something with operation
}

Di mana op dapat berupa objek yang mengimplementasikan protokol Operasi.

Jika Anda membutuhkan kelas dasar abstrak untuk melakukan lebih dari sekadar mendefinisikan metode, Anda bisa membuat kelas Objective-C reguler dan mencegahnya agar tidak dipakai. Timpa saja fungsi init - (id) dan buat kembali nil atau menegaskan (false). Ini bukan solusi yang sangat bersih, tetapi karena Objective-C sepenuhnya dinamis, benar-benar tidak ada yang setara langsung dengan kelas dasar abstrak.

Ben Gotow
sumber
Bagi saya, ini tampaknya menjadi cara yang tepat untuk kasus-kasus di mana Anda akan menggunakan kelas abstrak, setidaknya ketika itu dimaksudkan untuk berarti "antarmuka" (seperti dalam C ++). Apakah ada kelemahan tersembunyi dari pendekatan ini?
febeling
1
@ febeling, kelas abstrak - setidaknya di Jawa - bukan hanya antarmuka. Mereka juga mendefinisikan beberapa (atau sebagian besar) perilaku. Namun, pendekatan ini bisa baik dalam beberapa kasus.
Dan Rosenstark
Saya membutuhkan kelas dasar untuk mengimplementasikan fungsionalitas tertentu yang dibagi semua subkelas saya (menghapus duplikasi), tetapi saya juga membutuhkan protokol untuk metode lain yang tidak boleh ditangani oleh baseclass (bagian abstrak). Jadi saya perlu menggunakan keduanya, dan di situlah sulit untuk memastikan subclass Anda menerapkan sendiri dengan benar.
LightningStryk
19

Utas ini agak lama, dan sebagian besar yang ingin saya bagikan sudah ada di sini.

Namun, metode favorit saya tidak disebutkan, dan AFAIK tidak ada dukungan asli di Dentang saat ini, jadi di sini saya pergi ...

Pertama, dan yang terpenting (seperti yang telah ditunjukkan orang lain) kelas abstrak adalah sesuatu yang sangat tidak biasa di Objective-C - kita biasanya menggunakan komposisi (kadang-kadang melalui delegasi) sebagai gantinya. Ini mungkin alasan mengapa fitur seperti itu belum ada dalam bahasa / kompiler - terlepas dari @dynamicproperti, yang IIRC telah ditambahkan dalam ObjC 2.0 yang menyertai pengenalan CoreData.

Tetapi mengingat (setelah menilai situasi Anda dengan cermat!) Anda sampai pada kesimpulan bahwa delegasi (atau komposisi secara umum) tidak cocok untuk menyelesaikan masalah Anda, inilah cara saya melakukannya:

  1. Menerapkan setiap metode abstrak di kelas dasar.
  2. Buat implementasi itu [self doesNotRecognizeSelector:_cmd];...
  3. ... diikuti dengan __builtin_unreachable();membungkam peringatan yang akan Anda dapatkan untuk metode non-void, memberi tahu Anda "kontrol mencapai fungsi non-void tanpa pengembalian".
  4. Menggabungkan langkah 2. dan 3. dalam makro, atau membubuhi keterangan -[NSObject doesNotRecognizeSelector:]menggunakan __attribute__((__noreturn__))dalam kategori tanpa implementasi agar tidak menggantikan implementasi asli dari metode itu, dan termasuk header untuk kategori itu dalam PCH proyek Anda.

Saya pribadi lebih suka versi makro karena memungkinkan saya untuk mengurangi boilerplate sebanyak mungkin.

Ini dia:

// Definition:
#define D12_ABSTRACT_METHOD {\
 [self doesNotRecognizeSelector:_cmd]; \
 __builtin_unreachable(); \
}

// Usage (assuming we were Apple, implementing the abstract base class NSString):
@implementation NSString

#pragma mark - Abstract Primitives
- (unichar)characterAtIndex:(NSUInteger)index D12_ABSTRACT_METHOD
- (NSUInteger)length D12_ABSTRACT_METHOD
- (void)getCharacters:(unichar *)buffer range:(NSRange)aRange D12_ABSTRACT_METHOD

#pragma mark - Concrete Methods
- (NSString *)substringWithRange:(NSRange)aRange
{
    if (aRange.location + aRange.length >= [self length])
        [NSException raise:NSInvalidArgumentException format:@"Range %@ exceeds the length of %@ (%lu)", NSStringFromRange(aRange), [super description], (unsigned long)[self length]];

    unichar *buffer = (unichar *)malloc(aRange.length * sizeof(unichar));
    [self getCharacters:buffer range:aRange];

    return [[[NSString alloc] initWithCharactersNoCopy:buffer length:aRange.length freeWhenDone:YES] autorelease];
}
// and so forth…

@end

Seperti yang Anda lihat, makro menyediakan implementasi penuh dari metode abstrak, mengurangi jumlah yang diperlukan pelat ke minimum absolut.

Opsi yang lebih baik lagi adalah melobi tim Dentang untuk menyediakan atribut kompiler untuk kasus ini, melalui permintaan fitur. (Lebih baik, karena ini juga akan memungkinkan diagnostik waktu kompilasi untuk skenario di mana Anda mensubclass misalnya NSIncrementalStore.)

Mengapa Saya Memilih Metode Ini

  1. Ini menyelesaikan pekerjaan secara efisien, dan agak nyaman.
  2. Ini cukup mudah dimengerti. (Oke, itu __builtin_unreachable()mungkin mengejutkan orang, tetapi juga cukup mudah dimengerti.)
  3. Itu tidak bisa dilucuti dalam rilis rilis tanpa menghasilkan peringatan kompiler lain, atau kesalahan - tidak seperti pendekatan yang didasarkan pada salah satu makro pernyataan.

Poin terakhir itu perlu penjelasan, saya kira:

Beberapa (sebagian besar?) Orang melepaskan pernyataan dalam versi rilis. (Saya tidak setuju dengan kebiasaan itu, tapi itu cerita lain ...) Gagal menerapkan metode yang diperlukan - namun - buruk , mengerikan , salah , dan pada dasarnya akhir dari jagat raya untuk program Anda. Program Anda tidak dapat bekerja dengan benar dalam hal ini karena tidak terdefinisi, dan perilaku tidak terdefinisi adalah hal terburuk yang pernah ada. Oleh karena itu, dapat menghapus diagnostik tersebut tanpa menghasilkan diagnostik baru akan sepenuhnya tidak dapat diterima.

Sudah cukup buruk bahwa Anda tidak dapat memperoleh diagnosa waktu kompilasi yang tepat untuk kesalahan programmer seperti itu, dan harus menggunakan penemuan pada saat run-time untuk hal ini, tetapi jika Anda dapat memplesternya dalam rilis build, mengapa mencoba memiliki kelas abstrak di tempat pertama?

danyowdee
sumber
Ini adalah solusi favorit saya yang baru - __builtin_unreachable();permata membuat ini bekerja dengan sempurna. Makro membuatnya mendokumentasikan diri sendiri dan perilaku cocok dengan apa yang terjadi jika Anda memanggil metode yang hilang pada objek.
Alex MDC
12

Menggunakan @propertydan @dynamicjuga bisa bekerja. Jika Anda mendeklarasikan properti dinamis dan tidak memberikan implementasi metode pencocokan, semuanya akan tetap dikompilasi tanpa peringatan, dan Anda akan mendapatkan unrecognized selectorkesalahan saat runtime jika Anda mencoba mengaksesnya. Ini pada dasarnya hal yang sama dengan menelepon [self doesNotRecognizeSelector:_cmd], tetapi dengan mengetik jauh lebih sedikit.

Cameron Spickert
sumber
7

Dalam Xcode (menggunakan dentang dll) saya suka menggunakan __attribute__((unavailable(...)))untuk menandai kelas abstrak sehingga Anda mendapatkan kesalahan / peringatan jika Anda mencoba dan menggunakannya.

Ini memberikan beberapa perlindungan terhadap tidak sengaja menggunakan metode ini.

Contoh

Di @interfacetag kelas dasar metode "abstrak":

- (void)myAbstractMethod:(id)param1 __attribute__((unavailable("You should always override this")));

Mengambil langkah ini lebih jauh, saya membuat makro:

#define UnavailableMacro(msg) __attribute__((unavailable(msg)))

Ini memungkinkan Anda melakukan ini:

- (void)myAbstractMethod:(id)param1 UnavailableMacro(@"You should always override this");

Seperti yang saya katakan, ini bukan perlindungan kompiler yang sebenarnya, tetapi ini sama baiknya dengan yang Anda dapatkan dalam bahasa yang tidak mendukung metode abstrak.

Richard Stelling
sumber
1
Saya tidak bisa mendapatkannya. Saya menerapkan saran Anda di kelas dasar saya init metode dan sekarang Xcode tidak memungkinkan saya membuat sebuah instance dari kelas mewarisi dan waktu kompilasi kesalahan terjadi tidak tersedia ... . Tolong jelaskan lebih lanjut.
anonim
Pastikan Anda memiliki -initmetode dalam subkelas Anda.
Richard Stelling
2
Itu sama dengan NS_UNAVAILABLE yang akan memicu kesalahan setiap kali Anda mencoba memanggil metode yang ditandai dengan atribut tersebut. Saya tidak melihat bagaimana ini bisa digunakan pada kelas abstrak.
Ben Sinclair
7

Jawaban atas pertanyaan tersebar di komentar di bawah jawaban yang sudah diberikan. Jadi, saya hanya merangkum dan menyederhanakan di sini.

Opsi1: Protokol

Jika Anda ingin membuat kelas abstrak tanpa implementasi, gunakan 'Protokol'. Kelas-kelas yang mewarisi protokol diwajibkan untuk mengimplementasikan metode-metode dalam protokol.

@protocol ProtocolName
// list of methods and properties
@end

Opsi2: Pola Metode Templat

Jika Anda ingin membuat kelas abstrak dengan implementasi parsial seperti "Pola Metode Templat" maka ini solusinya. Objective-C - Pola metode templat?

hadaytullah
sumber
6

Alternatif lain

Cukup periksa kelas di kelas Abstrak dan Tegas atau Pengecualian, apa pun yang Anda suka.

@implementation Orange
- (instancetype)init
{
    self = [super init];
    NSAssert([self class] != [Orange class], @"This is an abstract class");
    if (self) {
    }
    return self;
}
@end

Ini menghilangkan keharusan untuk menimpa init

km besar
sumber
Apakah efisien untuk mengembalikan nol saja? Atau apakah itu menyebabkan pengecualian / kesalahan lainnya dilemparkan?
user2277872
Yah ini hanya untuk menangkap programmer untuk menggunakan kelas secara langsung, yang saya pikir membuat penggunaan pernyataan yang bagus.
bigkm
5

(lebih dari saran terkait)

Saya ingin memiliki cara untuk membiarkan programmer tahu "jangan menelepon dari anak" dan untuk menimpanya sepenuhnya (dalam kasus saya masih menawarkan beberapa fungsi default atas nama orang tua ketika tidak diperpanjang):

typedef void override_void;
typedef id override_id;

@implementation myBaseClass

// some limited default behavior (undesired by subclasses)
- (override_void) doSomething;
- (override_id) makeSomeObject;

// some internally required default behavior
- (void) doesSomethingImportant;

@end

Keuntungannya adalah bahwa programmer akan MELIHAT "menimpa" dalam deklarasi dan akan tahu mereka tidak boleh menelepon [super ..].

Memang, itu jelek harus mendefinisikan jenis kembali individu untuk ini, tetapi berfungsi sebagai petunjuk visual yang cukup baik dan Anda tidak dapat dengan mudah menggunakan bagian "override_" dalam definisi subclass.

Tentu saja sebuah kelas masih dapat memiliki implementasi standar ketika ekstensi bersifat opsional. Tapi seperti jawaban lain katakan, mengimplementasikan pengecualian run-time saat yang tepat, seperti untuk kelas abstrak (virtual).

Akan menyenangkan untuk memiliki built-in petunjuk kompiler seperti ini, bahkan petunjuk untuk saat yang terbaik untuk melakukan pra / posting panggilan implementasi super, daripada harus menggali melalui komentar / dokumentasi atau ... menganggap.

contoh dari petunjuk

Ivan Dossev
sumber
4

Jika Anda terbiasa dengan kompiler yang menangkap pelanggaran instantiasi abstrak dalam bahasa lain, maka perilaku Objective-C mengecewakan.

Sebagai bahasa pengikat yang terlambat, jelaslah bahwa Objective-C tidak dapat membuat keputusan statis tentang apakah suatu kelas benar-benar abstrak atau tidak (Anda mungkin menambahkan fungsi pada saat runtime ...), tetapi untuk kasus-kasus penggunaan umum ini sepertinya kekurangan. Saya lebih suka kompilasi flat-out mencegah instance kelas abstrak daripada melemparkan kesalahan saat runtime.

Berikut adalah pola yang kami gunakan untuk mendapatkan pemeriksaan statis jenis ini menggunakan beberapa teknik untuk menyembunyikan inisialisasi:

//
//  Base.h
#define UNAVAILABLE __attribute__((unavailable("Default initializer not available.")));

@protocol MyProtocol <NSObject>
-(void) dependentFunction;
@end

@interface Base : NSObject {
    @protected
    __weak id<MyProtocol> _protocolHelper; // Weak to prevent retain cycles!
}

- (instancetype) init UNAVAILABLE; // Prevent the user from calling this
- (void) doStuffUsingDependentFunction;
@end

//
//  Base.m
#import "Base.h"

// We know that Base has a hidden initializer method.
// Declare it here for readability.
@interface Base (Private)
- (instancetype)initFromDerived;
@end

@implementation Base
- (instancetype)initFromDerived {
    // It is unlikely that this becomes incorrect, but assert
    // just in case.
    NSAssert(![self isMemberOfClass:[Base class]],
             @"To be called only from derived classes!");
    self = [super init];
    return self;
}

- (void) doStuffUsingDependentFunction {
    [_protocolHelper dependentFunction]; // Use it
}
@end

//
//  Derived.h
#import "Base.h"

@interface Derived : Base
-(instancetype) initDerived; // We cannot use init here :(
@end

//
//  Derived.m
#import "Derived.h"

// We know that Base has a hidden initializer method.
// Declare it here.
@interface Base (Private)
- (instancetype) initFromDerived;
@end

// Privately inherit protocol
@interface Derived () <MyProtocol>
@end

@implementation Derived
-(instancetype) initDerived {
    self= [super initFromDerived];
    if (self) {
        self->_protocolHelper= self;
    }
    return self;
}

// Implement the missing function
-(void)dependentFunction {
}
@end
Menyeberang_
sumber
3

Mungkin situasi seperti ini seharusnya hanya terjadi pada waktu pengembangan, jadi ini mungkin berhasil:

- (id)myMethodWithVar:(id)var {
   NSAssert(NO, @"You most override myMethodWithVar:");
   return nil;
}
juanjo
sumber
3

Anda dapat menggunakan metode yang diusulkan oleh @Yar (dengan beberapa modifikasi):

#define mustOverride() @throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"%s must be overridden in a subclass/category", __PRETTY_FUNCTION__] userInfo:nil]
#define setMustOverride() NSLog(@"%@ - method not implemented", NSStringFromClass([self class])); mustOverride()

Di sini Anda akan mendapatkan pesan seperti:

<Date> ProjectName[7921:1967092] <Class where method not implemented> - method not implemented
<Date> ProjectName[7921:1967092] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[<Base class (if inherited or same if not> <Method name>] must be overridden in a subclass/category'

Atau pernyataan:

NSAssert(![self respondsToSelector:@selector(<MethodName>)], @"Not implemented");

Dalam hal ini Anda akan mendapatkan:

<Date> ProjectName[7926:1967491] *** Assertion failure in -[<Class Name> <Method name>], /Users/kirill/Documents/Projects/root/<ProjectName> Services/Classes/ViewControllers/YourClass:53

Anda juga dapat menggunakan protokol dan solusi lain - tetapi ini adalah salah satu yang paling sederhana.

gbk
sumber
2

Kakao tidak menyediakan apa pun yang disebut abstrak. Kita dapat membuat abstrak kelas yang diperiksa hanya saat runtime, dan pada waktu kompilasi ini tidak dicentang.

Hussain Shabbir
sumber
1

Saya biasanya hanya menonaktifkan metode init di kelas yang ingin saya abstraksi:

- (instancetype)__unavailable init; // This is an abstract class.

Ini akan menghasilkan kesalahan pada waktu kompilasi setiap kali Anda memanggil init pada kelas itu. Saya kemudian menggunakan metode kelas untuk yang lainnya.

Objective-C tidak memiliki cara bawaan untuk mendeklarasikan kelas abstrak.

mahoukov
sumber
Tetapi saya mendapatkan kesalahan ketika saya memanggil [super init] dari kelas turunan dari kelas abstrak itu. Bagaimana mengatasinya?
Sahil Doshi
@ SahilDoshi Saya kira itulah kelemahan menggunakan metode ini. Saya menggunakannya ketika saya tidak ingin mengizinkan kelas untuk dipakai, & tidak ada yang mewarisi dari kelas itu.
mahoukov
ya, saya mengerti itu. tetapi solusi Anda akan membantu untuk menciptakan sesuatu seperti kelas singleton di mana kami tidak ingin ada yang memanggil init. sesuai pengetahuan saya jika kelas abstrak beberapa kelas akan mewarisinya. lain apa penggunaan kelas abstrak.
Sahil Doshi
0

Mengubah sedikit apa yang disarankan @redfood dengan menerapkan komentar @ dotToString, Anda sebenarnya memiliki solusi yang diadopsi oleh IGListKit Instagram .

  1. Buat protokol untuk semua metode yang tidak masuk akal untuk didefinisikan di kelas dasar (abstrak) yaitu mereka membutuhkan implementasi spesifik pada anak-anak.
  2. Buat kelas dasar (abstrak) yang tidak mengimplementasikan protokol ini. Anda dapat menambahkan ke kelas ini metode lain yang masuk akal untuk memiliki implementasi bersama.
  3. Di mana-mana di proyek Anda, jika seorang anak dari AbstractClassharus diinput ke atau output dengan beberapa metode, ketik itu sebagai AbstractClass<Protocol>gantinya.

Karena AbstractClasstidak diterapkan Protocol, satu-satunya cara untuk memiliki AbstractClass<Protocol>instance adalah dengan subclassing. Karena AbstractClasssendirian tidak dapat digunakan di mana pun dalam proyek, itu menjadi abstrak.

Tentu saja, ini tidak mencegah pengembang yang tidak disarankan untuk menambahkan metode baru yang hanya merujuk AbstractClass, yang pada akhirnya memungkinkan turunan dari kelas abstrak (tidak lagi).

Contoh dunia nyata: IGListKit memiliki kelas dasar IGListSectionControlleryang tidak mengimplementasikan protokol IGListSectionType, namun setiap metode yang membutuhkan turunan dari kelas itu, sebenarnya meminta jenisnya IGListSectionController<IGListSectionType>. Oleh karena itu tidak ada cara untuk menggunakan objek bertipe IGListSectionControlleruntuk apa pun yang berguna dalam kerangka mereka.

Gobe
sumber
0

Faktanya, Objective-C tidak memiliki kelas abstrak, tetapi Anda dapat menggunakan Protokol untuk mencapai efek yang sama. Berikut ini contohnya:

CustomProtocol.h

#import <Foundation/Foundation.h>

@protocol CustomProtocol <NSObject>
@required
- (void)methodA;
@optional
- (void)methodB;
@end

TestProtocol.h

#import <Foundation/Foundation.h>
#import "CustomProtocol.h"

@interface TestProtocol : NSObject <CustomProtocol>

@end

TestProtocol.m

#import "TestProtocol.h"

@implementation TestProtocol

- (void)methodA
{
  NSLog(@"methodA...");
}

- (void)methodB
{
  NSLog(@"methodB...");
}
@end
YorkFish
sumber
0

Contoh sederhana membuat kelas abstrak

// Declare a protocol
@protocol AbcProtocol <NSObject>

-(void)fnOne;
-(void)fnTwo;

@optional

-(void)fnThree;

@end

// Abstract class
@interface AbstractAbc : NSObject<AbcProtocol>

@end

@implementation AbstractAbc

-(id)init{
    self = [super init];
    if (self) {
    }
    return self;
}

-(void)fnOne{
// Code
}

-(void)fnTwo{
// Code
}

@end

// Implementation class
@interface ImpAbc : AbstractAbc

@end

@implementation ImpAbc

-(id)init{
    self = [super init];
    if (self) {
    }
    return self;
}

// You may override it    
-(void)fnOne{
// Code
}
// You may override it
-(void)fnTwo{
// Code
}

-(void)fnThree{
// Code
}

@end
Say2Manuj
sumber
-3

Tidak bisakah Anda membuat delegasi saja?

Delegasi adalah seperti kelas dasar abstrak dalam arti bahwa Anda mengatakan fungsi apa yang perlu didefinisikan, tetapi Anda tidak benar-benar mendefinisikannya.

Kemudian setiap kali Anda menerapkan delegasi Anda (yaitu kelas abstrak) Anda akan diperingatkan oleh kompiler tentang fungsi opsional dan wajib yang Anda butuhkan untuk mendefinisikan perilaku.

Ini kedengarannya seperti kelas dasar abstrak bagi saya.

pengguna1709076
sumber