Tạo một lớp trừu tượng trong Objective-C


507

Tôi ban đầu là một lập trình viên Java, hiện đang làm việc với Objective-C. Tôi muốn tạo một lớp trừu tượng, nhưng điều đó dường như không thể thực hiện được trong Objective-C. Điều này có thể không?

Nếu không, tôi có thể đến gần lớp trừu tượng như thế nào trong Objective-C?


18
Các câu trả lời dưới đây là tuyệt vời. Tôi thấy vấn đề này của các lớp trừu tượng liên quan đến các phương thức riêng tư - cả hai đều là các phương thức để hạn chế những gì mã khách hàng có thể làm và không tồn tại trong Objective-C. Tôi nghĩ nó giúp hiểu rằng tâm lý của chính ngôn ngữ này khác về cơ bản với Java. Xem câu trả lời của tôi về: stackoverflow.com/questions/1020070/#1020330
Quinn Taylor

Cảm ơn thông tin về tâm lý của cộng đồng Objective-C trái ngược với các ngôn ngữ khác. Điều đó thực sự giải quyết được một số câu hỏi liên quan mà tôi đã có (như tại sao không có cơ chế đơn giản cho các phương thức riêng tư, v.v.).
Jonathan Arbogast

1
vì vậy hãy xem trang web của Cốc Cốc

2
Mặc dù Barry đề cập đến nó như là một suy nghĩ sau (tha thứ cho tôi nếu tôi đọc sai), tôi nghĩ rằng bạn đang tìm kiếm một Giao thức trong Mục tiêu C. Xem, ví dụ, Giao thức là gì? .
jww

Câu trả lời:


633

Thông thường, lớp Objective-C chỉ trừu tượng theo quy ước, nếu tác giả ghi lại một lớp là trừu tượng, chỉ không sử dụng nó mà không phân lớp nó. Tuy nhiên, không có thực thi thời gian biên dịch nào ngăn chặn việc khởi tạo một lớp trừu tượng, tuy nhiên. Trong thực tế, không có gì ngăn người dùng cung cấp việc triển khai các phương thức trừu tượng thông qua một danh mục (tức là trong thời gian chạy). Bạn có thể buộc người dùng ít nhất ghi đè các phương thức nhất định bằng cách đưa ra một ngoại lệ trong việc thực hiện các phương thức đó trong lớp trừu tượng của bạn:

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

Nếu phương thức của bạn trả về một giá trị, nó sẽ dễ sử dụng hơn một chút

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

vì sau đó bạn không cần thêm câu lệnh return từ phương thức.

Nếu lớp trừu tượng thực sự là một giao diện (nghĩa là không có triển khai phương thức cụ thể), sử dụng giao thức Objective-C là tùy chọn phù hợp hơn.


18
Tôi nghĩ rằng phần thích hợp nhất của câu trả lời là đề cập đến việc bạn có thể sử dụng @protatio thay vì chỉ định nghĩa các phương thức.
Chad Stewart

13
Để làm rõ: Bạn có thể khai báo các phương thức trong một @protocolđịnh nghĩa, nhưng bạn không thể định nghĩa các phương thức ở đó.
Richard

Với một phương thức thể loại trên NSException, + (instancetype)exceptionForCallingAbstractMethod:(SEL)selectorđiều này hoạt động rất độc đáo.
Patrick Pijnappel

Điều này đã giúp ích cho tôi vì IMHO, đưa ra một ngoại lệ rõ ràng hơn với các nhà phát triển khác rằng đây là một hành vi mong muốn nhiều hơn thế doesNotRecognizeSelector.
Chris

Sử dụng các giao thức (cho lớp hoàn toàn trừu tượng) hoặc mẫu phương thức mẫu trong đó lớp trừu tượng có logic thực hiện / luồng một phần như được đưa ra ở đây stackoverflow.com/questions/8146439/ . Xem câu trả lời của tôi dưới đây.
hadaytullah

267

Không, không có cách nào để tạo một lớp trừu tượng trong Objective-C.

Bạn có thể giả định một lớp trừu tượng - bằng cách gọi các phương thức / bộ chọn gọi doesNotRecognizeSelector: và do đó đưa ra một ngoại lệ làm cho lớp không thể sử dụng được.

Ví dụ:

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

Bạn cũng có thể làm điều này cho init.


5
@Chuck, tôi không downvote, nhưng NSObjecttài liệu tham khảo gợi ý sử dụng phương pháp này khi bạn KHÔNG muốn kế thừa một phương thức, không thực thi ghi đè phương thức. Mặc dù đó có thể là điều tương tự, có lẽ :)
Dan Rosenstark

Tại sao bạn không muốn chỉ sử dụng một giao thức trong trường hợp này? Đối với tôi, điều này là tốt để biết chỉ sơ khai phương thức, nhưng không phải cho cả một lớp trừu tượng. Các trường hợp sử dụng cho điều này dường như hạn chế.
lewiguez

Tôi đã không đánh giá thấp nó, nhưng gợi ý rằng bạn nên gây ra một ngoại lệ trong phương thức init là lý do rất có thể cho nó. Định dạng phổ biến nhất cho một lớp con sẽ bắt đầu phương thức init của riêng nó bằng cách gọi self = [super init] - sẽ ngoan ngoãn ném một ngoại lệ. Định dạng này hoạt động tốt đối với hầu hết các phương thức, nhưng tôi sẽ không bao giờ thực hiện nó trong bất kỳ điều gì mà lớp con có thể gọi đó là siêu triển khai.
Xono

5
Bạn hoàn toàn có thể tạo các lớp trừu tượng trong Objective-C và nó khá phổ biến. Có một số lớp khung của Apple làm điều này. Các lớp trừu tượng của Apple đưa ra các ngoại lệ cụ thể (NSInvalidArgumentException), thường bằng cách gọi NSInvalid AbTHERInvocation (). Gọi một phương thức trừu tượng là một lỗi lập trình, đó là lý do tại sao nó ném một ngoại lệ. Các nhà máy trừu tượng thường được thực hiện như các cụm lớp.
quellish

2
@quellish: Như bạn đã nói: gọi một phương thức trừu tượng là một lỗi lập trình. Nó nên được xử lý như vậy, thay vì dựa vào báo cáo lỗi thời gian chạy (NSException). Tôi nghĩ đây là vấn đề lớn nhất đối với các nhà phát triển đến từ các ngôn ngữ khác, trong đó phương tiện trừu tượng "không thể khởi tạo một đối tượng thuộc loại này" trong Obj-C, điều đó có nghĩa là "mọi thứ sẽ sai khi chạy khi bạn khởi tạo lớp này".
Cross_ 7/07/2015

60

Chỉ cần riff về câu trả lời của @Barry Wark ở trên (và cập nhật cho iOS 4.3) và để lại cho tôi tham khảo:

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

sau đó trong phương pháp của bạn, bạn có thể sử dụng

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



Lưu ý: Không chắc việc tạo một macro trông giống như hàm C có phải là một ý tưởng tốt hay không, nhưng tôi sẽ giữ nó cho đến khi đi học ngược lại. Tôi nghĩ rằng nó đúng hơn để sử dụng NSInvalidArgumentException(chứ không phải NSInternalInconsistencyException) vì đó là những gì hệ thống thời gian chạy ném để đáp ứng với doesNotRecognizeSelectorviệc được gọi (xem NSObjecttài liệu).


1
Chắc chắn, @TomA, tôi hy vọng nó cung cấp cho bạn ý tưởng cho mã khác mà bạn có thể macro. Macro được sử dụng nhiều nhất của tôi là một tham chiếu đơn giản đến một singleton: mã nói universe.thingnhưng nó mở rộng ra [Universe universe].thing.
Niềm

Tuyệt quá. Thay đổi một chút, mặc dù : #define mustOverride() @throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"%s must be overridden in a subclass/category", __PRETTY_FUNCTION__] userInfo:nil].
noamtm

1
@Yar: Tôi không nghĩ vậy. Chúng tôi sử dụng __PRETTY_FUNCTION__mọi nơi, thông qua macro DLog (...), như được đề xuất ở đây: stackoverflow.com/a/969291/38557 .
noamtm

1
để biết thêm chi tiết -#define setMustOverride() NSLog(@"%@ - method not implemented", NSStringFromClass([self class])); mustOverride()
gbk

1
Nếu bạn thêm lớp cơ sở và sau đó kế thừa lớp này 10 lần và quên thực hiện điều này trong một trong các lớp, bạn sẽ nhận được thông báo với tên của lớp Cơ sở không được kế thừa như Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[BaseDynamicUIViewController localizeUI] must be overridden in a subclass/category'trong trường hợp tôi đề xuất bạn cũng nhận được HomeViewController - method not implementednơi HomeViewControll được kế thừa từ Base - điều này sẽ cung cấp thêm thông tin
gbk

42

Giải pháp tôi đưa ra là:

  1. Tạo một giao thức cho mọi thứ bạn muốn trong lớp "trừu tượng" của bạn
  2. Tạo một lớp cơ sở (hoặc có thể gọi nó là trừu tượng) thực hiện giao thức. Đối với tất cả các phương thức bạn muốn "trừu tượng" triển khai chúng trong tệp .m, nhưng không phải tệp .h.
  3. Có lớp con bạn kế thừa từ lớp cơ sở VÀ thực hiện giao thức.

Bằng cách này, trình biên dịch sẽ đưa ra cảnh báo cho bất kỳ phương thức nào trong giao thức không được lớp con của bạn triển khai.

Nó không ngắn gọn như trong Java, nhưng bạn có được cảnh báo trình biên dịch mong muốn.


2
+1 Đây thực sự là giải pháp gần nhất với một lớp trừu tượng trong Java. Tôi đã sử dụng phương pháp này bản thân mình và nó hoạt động tuyệt vời. Nó thậm chí còn được phép đặt tên giao thức giống như lớp cơ sở (giống như Apple đã làm với NSObject). Nếu sau đó bạn đặt giao thức và khai báo lớp cơ sở trong cùng một tệp tiêu đề thì nó gần như không thể phân biệt được với một lớp trừu tượng.
mã hóaFriend1

Ah, nhưng lớp trừu tượng của tôi thực hiện một phần của giao thức, phần còn lại được thực hiện bởi các lớp con.
Roger CS Wernersson

1
bạn chỉ có thể có các lớp con thực hiện giao thức và bỏ tất cả các phương thức siêu lớp thay vì để trống. sau đó có các thuộc tính của loại Superclass <MyProtocol>. đồng thời, để tăng tính linh hoạt, bạn có thể thêm tiền tố vào các phương thức trong giao thức của mình bằng @optional.
dotToString

Điều này dường như không hoạt động trong Xcode 5.0.2; nó chỉ tạo ra một cảnh báo cho lớp "trừu tượng". Không mở rộng lớp "trừu tượng" sẽ tạo ra cảnh báo trình biên dịch phù hợp, nhưng rõ ràng không cho phép bạn kế thừa các phương thức.
Topher Fangio

Tôi thích giải pháp này nhưng thực sự không thích nó, nó thực sự không phải là một cấu trúc mã tốt trong dự án. // nên sử dụng kiểu này làm kiểu cơ sở cho các lớp con. typedef BaseClass <BaseClassProtocol> BASECLASS; Đó chỉ là quy tắc một tuần, tôi không thích nó.
Itachi

35

Từ danh sách gửi thư của Omni Group :

Objective-C không có cấu trúc trình biên dịch trừu tượng như Java tại thời điểm này.

Vì vậy, tất cả những gì bạn làm là định nghĩa lớp trừu tượng như bất kỳ lớp bình thường nào khác và triển khai các phương thức cho các phương thức trừu tượng trống hoặc báo cáo không hỗ trợ cho bộ chọn. Ví dụ...

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

Tôi cũng làm như sau để ngăn chặn việc khởi tạo lớp trừu tượng thông qua trình khởi tạo mặc định.

- (id)init
{
     [self doesNotRecognizeSelector:_cmd];
     [self release];
     return nil;
}

1
Tôi đã không nghĩ đến việc sử dụng -doesNotRecognizeSelector: và tôi thích cách tiếp cận này theo một số cách. Có ai biết một cách để làm cho trình biên dịch đưa ra cảnh báo cho một phương thức "trừu tượng" được tạo ra theo cách này hoặc bằng cách đưa ra một ngoại lệ không? Điều đó thật tuyệt vời ...
Quinn Taylor

26
Cách tiếp cận doesNotRecognizeSelector ngăn chặn mẫu tự = [super init] được đề xuất của Apple.
dlinsin

1
@david: Tôi khá chắc chắn rằng toàn bộ vấn đề là đưa ra một ngoại lệ càng sớm càng tốt. Lý tưởng nhất là vào thời gian biên dịch, nhưng vì không có cách nào để làm điều đó, họ đã giải quyết cho một ngoại lệ thời gian chạy. Điều này giống như một thất bại khẳng định, không bao giờ nên được nêu ra trong mã sản xuất ở nơi đầu tiên. Trong thực tế, một khẳng định (sai) thực sự có thể tốt hơn vì rõ ràng hơn là mã này sẽ không bao giờ chạy. Người dùng không thể làm gì để sửa nó, nhà phát triển phải sửa nó. Do đó, đưa ra một ngoại lệ hoặc thất bại khẳng định ở đây nghe có vẻ là một ý tưởng tốt.
Có ý thức

2
Tôi không nghĩ rằng đây là một giải pháp khả thi vì các lớp con có thể có các lệnh gọi hoàn toàn hợp pháp tới [super init].
Raffi Khatchadourian 6/212

@dlinsin: Đó chính xác là những gì bạn muốn khi lớp trừu tượng không được yêu cầu khởi tạo thông qua init. Các lớp con của nó có lẽ biết siêu phương thức để gọi, nhưng điều này ngăn chặn việc gọi bất cẩn thông qua "mới" hoặc "alloc / init".
gnasher729

21

Thay vì cố gắng tạo một lớp cơ sở trừu tượng, hãy xem xét sử dụng một giao thức (tương tự như giao diện Java). Điều này cho phép bạn xác định một tập hợp các phương thức và sau đó chấp nhận tất cả các đối tượng phù hợp với giao thức và thực hiện các phương thức. Ví dụ: tôi có thể định nghĩa một giao thức Hoạt động và sau đó có một chức năng như thế này:

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

Trong đó op có thể là bất kỳ đối tượng nào thực hiện giao thức Hoạt động.

Nếu bạn cần lớp cơ sở trừu tượng của mình để thực hiện nhiều hơn chỉ đơn giản là xác định các phương thức, bạn có thể tạo một lớp Objective-C thông thường và ngăn không cho nó được khởi tạo. Chỉ cần ghi đè hàm init (id) và làm cho nó trở về nil hoặc assert (false). Đây không phải là một giải pháp rất rõ ràng, nhưng vì Objective-C hoàn toàn năng động, nên thực sự không có tương đương trực tiếp với một lớp cơ sở trừu tượng.


Đối với tôi, đây dường như là cách thích hợp cho các trường hợp bạn sẽ sử dụng lớp trừu tượng, ít nhất là khi nó có nghĩa là "giao diện" thực sự (như trong C ++). Có bất kỳ nhược điểm ẩn cho phương pháp này?
febeling

1
@febeling, các lớp trừu tượng - ít nhất là trong Java - không chỉ là các giao diện. Họ cũng định nghĩa một số (hoặc hầu hết) hành vi. Cách tiếp cận này có thể là tốt trong một số trường hợp, mặc dù.
Dan Rosenstark

Tôi cần một lớp cơ sở để thực hiện một số chức năng nhất định mà các lớp con của tôi đều chia sẻ (loại bỏ trùng lặp), nhưng tôi cũng cần một giao thức cho các phương thức khác mà lớp cơ sở không nên xử lý (phần trừu tượng). Vì vậy, tôi cần sử dụng cả hai, và đó là nơi có thể gặp khó khăn để đảm bảo các lớp con của bạn đang tự thực hiện đúng.
LightningStryk

19

Chủ đề này là loại cũ, và hầu hết những gì tôi muốn chia sẻ đã ở đây.

Tuy nhiên, phương pháp yêu thích của tôi không được đề cập và AFAIK không có hỗ trợ riêng trong Clang hiện tại, vì vậy tôi đến đây

Đầu tiên và trên hết (như những người khác đã chỉ ra) các lớp trừu tượng là một thứ rất không phổ biến trong Objective-C - chúng ta thường sử dụng bố cục (đôi khi thông qua ủy quyền) thay vào đó. Đây có lẽ là lý do tại sao một tính năng như vậy không tồn tại trong ngôn ngữ / trình biên dịch - ngoài @dynamiccác thuộc tính, IIRC đã được thêm vào ObjC 2.0 kèm theo sự ra mắt của CoreData.

Nhưng cho rằng (sau khi đánh giá cẩn thận về tình huống của bạn!) Bạn đã đi đến kết luận rằng ủy nhiệm (hoặc thành phần nói chung) không phù hợp để giải quyết vấn đề của bạn, đây là cách tôi thực hiện:

  1. Thực hiện mọi phương thức trừu tượng trong lớp cơ sở.
  2. Hãy thực hiện điều đó [self doesNotRecognizeSelector:_cmd];...
  3. Sau đó, ông dùng __builtin_unreachable();để tắt tiếng cảnh báo bạn sẽ nhận được các phương thức không có giá trị, cho bạn biết quyền kiểm soát của bạn đã đạt đến cuối chức năng không có khoảng trống mà không cần trả lại.
  4. Kết hợp các bước 2. và 3. trong một macro hoặc chú thích -[NSObject doesNotRecognizeSelector:]bằng cách sử dụng __attribute__((__noreturn__))trong một danh mục mà không thực hiện để không thay thế triển khai ban đầu của phương thức đó và bao gồm tiêu đề cho danh mục đó trong PCH của dự án của bạn.

Cá nhân tôi thích phiên bản macro hơn vì điều đó cho phép tôi giảm bản tóm tắt càng nhiều càng tốt.

Đây là:

// 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

Như bạn có thể thấy, macro cung cấp việc thực hiện đầy đủ các phương thức trừu tượng, giảm số lượng nồi hơi cần thiết đến mức tối thiểu.

Một lựa chọn thậm chí tốt hơn sẽ là vận động nhóm Clang cung cấp thuộc tính trình biên dịch cho trường hợp này, thông qua các yêu cầu tính năng. (Tốt hơn, bởi vì điều này cũng sẽ cho phép chẩn đoán thời gian biên dịch cho các tình huống trong đó bạn phân lớp, ví dụ NSIncrementalStore.)

Tại sao tôi chọn phương pháp này

  1. Nó hoàn thành công việc một cách hiệu quả và có phần thuận tiện.
  2. Nó khá dễ hiểu. (Được rồi, điều đó __builtin_unreachable()có thể làm mọi người ngạc nhiên, nhưng nó cũng đủ dễ hiểu.)
  3. Nó không thể bị tước trong các bản dựng phát hành mà không tạo ra các cảnh báo hoặc lỗi biên dịch khác - không giống như cách tiếp cận dựa trên một trong các macro xác nhận.

Điểm cuối cùng cần một số lời giải thích, tôi đoán:

Một số (hầu hết?) Mọi người xác nhận dải trong các bản dựng phát hành. (Tôi không đồng ý với thói quen đó, nhưng đó là một câu chuyện khác ...) Không thực hiện một phương pháp cần thiết - tuy nhiên - là xấu , khủng khiếp , sai , và về cơ bản kết thúc của vũ trụ cho chương trình của bạn. Chương trình của bạn không thể hoạt động chính xác trong vấn đề này vì nó không được xác định và hành vi không xác định là điều tồi tệ nhất. Do đó, việc có thể loại bỏ các chẩn đoán đó mà không tạo ra các chẩn đoán mới sẽ là hoàn toàn không thể chấp nhận được.

Nó đủ tệ đến mức bạn không thể có được chẩn đoán thời gian biên dịch thích hợp cho các lỗi lập trình viên đó và phải dùng đến khám phá tại thời điểm đó, nhưng nếu bạn có thể khắc phục nó trong các bản dựng phát hành, tại sao lại thử có một lớp trừu tượng trong địa điểm đầu tiên?


Đây là giải pháp yêu thích mới của tôi - __builtin_unreachable();đá quý làm cho công việc này hoàn hảo. Macro làm cho nó tự ghi lại tài liệu và hành vi khớp với những gì xảy ra nếu bạn gọi một phương thức bị thiếu trên một đối tượng.
Alex MDC

12

Sử dụng @property@dynamiccũng có thể làm việc. Nếu bạn khai báo một thuộc tính động và không đưa ra cách thực hiện phương thức phù hợp, mọi thứ vẫn sẽ được biên dịch mà không có cảnh báo và bạn sẽ gặp unrecognized selectorlỗi khi chạy nếu bạn cố truy cập vào nó. Điều này về cơ bản giống như gọi [self doesNotRecognizeSelector:_cmd], nhưng với việc gõ ít hơn nhiều.


7

Trong Xcode (sử dụng clang, v.v.) tôi muốn sử dụng __attribute__((unavailable(...)))để gắn thẻ các lớp trừu tượng để bạn gặp lỗi / cảnh báo nếu bạn thử và sử dụng nó.

Nó cung cấp một số bảo vệ chống lại vô tình sử dụng phương pháp.

Thí dụ

Trong @interfacethẻ lớp cơ sở, các phương thức "trừu tượng":

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

Tiến lên một bước này, tôi tạo ra một macro:

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

Điều này cho phép bạn làm điều này:

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

Như tôi đã nói, đây không phải là sự bảo vệ trình biên dịch thực sự nhưng nó cũng tốt như việc bạn sẽ có được một ngôn ngữ không hỗ trợ các phương thức trừu tượng.


1
Tôi không thể có được nó. Tôi áp dụng gợi ý của bạn trên lớp cơ sở của tôi init phương pháp và bây giờ Xcode không cho phép tôi tạo ra một thể hiện của lớp kế thừa và một lỗi thời gian biên dịch xảy ra không có ... . Bạn vui lòng giải thích thêm?
anonim

Hãy chắc chắn rằng bạn có một -initphương thức trong lớp con của bạn.
Richard Stelling

2
Nó giống như NS_UNAVAILABLE sẽ gây ra lỗi mỗi lần bạn cố gắng gọi phương thức được đánh dấu bằng thuộc tính đó. Tôi không thấy làm thế nào nó có thể được sử dụng trên lớp trừu tượng.
Ben Sinclair

7

Câu trả lời cho câu hỏi nằm rải rác trong các bình luận dưới các câu trả lời đã được đưa ra. Vì vậy, tôi chỉ tóm tắt và đơn giản hóa ở đây.

Tùy chọn1: Giao thức

Nếu bạn muốn tạo một lớp trừu tượng không có triển khai, hãy sử dụng 'Giao thức'. Các lớp kế thừa một giao thức có nghĩa vụ phải thực hiện các phương thức trong giao thức.

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

Tùy chọn 2: Mẫu phương thức mẫu

Nếu bạn muốn tạo một lớp trừu tượng với triển khai một phần như "Mẫu phương thức mẫu" thì đây là giải pháp. Objective-C - Mẫu phương thức mẫu?


6

Một cách khác

Chỉ cần kiểm tra lớp trong lớp Trừu tượng và Khẳng định hoặc Ngoại lệ, bất cứ điều gì bạn thích.

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

Điều này loại bỏ sự cần thiết phải ghi đè init


Nó sẽ có hiệu quả để chỉ trở về con số không? Hoặc điều đó sẽ gây ra một ngoại lệ / lỗi khác được ném?
dùng2277872

Vâng, đây chỉ là để bắt các lập trình viên sử dụng lớp trực tiếp, mà tôi nghĩ rằng làm cho việc sử dụng tốt một khẳng định.
bigkm 17/214

5

(thêm một gợi ý liên quan)

Tôi muốn có một cách để cho lập trình viên biết "không gọi từ trẻ em" và ghi đè hoàn toàn (trong trường hợp của tôi vẫn cung cấp một số chức năng mặc định thay cho cha mẹ khi không được gia hạn):

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

Ưu điểm là lập trình viên sẽ XEM "ghi đè" trong khai báo và sẽ biết họ không nên gọi [super ..].

Cấp, thật xấu khi phải xác định các kiểu trả về riêng cho việc này, nhưng nó phục vụ như một gợi ý trực quan đủ tốt và bạn không thể dễ dàng sử dụng phần "override_" trong định nghĩa lớp con.

Tất nhiên một lớp vẫn có thể có một triển khai mặc định khi một phần mở rộng là tùy chọn. Nhưng giống như các câu trả lời khác, thực hiện ngoại lệ thời gian chạy khi thích hợp, như đối với các lớp trừu tượng (ảo).

Sẽ thật tuyệt nếu được xây dựng trong các gợi ý về trình biên dịch như thế này, thậm chí là gợi ý khi nào là tốt nhất để gọi trước / đăng cuộc gọi của siêu thực hiện, thay vì phải tìm hiểu các bình luận / tài liệu hoặc ... giả định.

ví dụ về gợi ý


4

Nếu bạn đã quen với trình biên dịch bắt các vi phạm khởi tạo trừu tượng trong các ngôn ngữ khác, thì hành vi Objective-C gây thất vọng.

Là ngôn ngữ ràng buộc muộn, rõ ràng Objective-C không thể đưa ra quyết định tĩnh về việc một lớp có thực sự trừu tượng hay không (bạn có thể thêm các hàm khi chạy ...), nhưng đối với các trường hợp sử dụng thông thường thì điều này có vẻ như là một thiếu sót. Tôi thích trình biên dịch ngăn chặn ngay lập tức các lớp trừu tượng thay vì đưa ra một lỗi trong thời gian chạy.

Dưới đây là một mẫu chúng tôi đang sử dụng để có được loại kiểm tra tĩnh này bằng cách sử dụng một vài kỹ thuật để ẩn trình khởi tạo:

//
//  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

3

Có lẽ loại tình huống này chỉ xảy ra ở thời điểm phát triển, vì vậy điều này có thể hoạt động:

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

3

Bạn có thể sử dụng một phương pháp được đề xuất bởi @Yar (với một số sửa đổi):

#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()

Tại đây bạn sẽ nhận được một tin nhắn như:

<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'

Hoặc khẳng định:

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

Trong trường hợp này, bạn sẽ nhận được:

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

Ngoài ra, bạn có thể sử dụng các giao thức và các giải pháp khác - nhưng đây là một trong những giao thức đơn giản nhất.


2

Ca cao không cung cấp bất cứ thứ gì gọi là trừu tượng. Chúng ta có thể tạo một bản tóm tắt lớp chỉ được kiểm tra khi chạy và tại thời điểm biên dịch, điều này không được kiểm tra.


1

Tôi thường chỉ vô hiệu hóa phương thức init trong một lớp mà tôi muốn trừu tượng hóa:

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

Điều này sẽ tạo ra một lỗi tại thời gian biên dịch bất cứ khi nào bạn gọi init trên lớp đó. Sau đó tôi sử dụng các phương thức lớp cho mọi thứ khác.

Objective-C không có cách dựng sẵn để khai báo các lớp trừu tượng.


Nhưng tôi gặp lỗi khi tôi gọi [super init] từ lớp dẫn xuất của lớp trừu tượng đó. Làm thế nào để giải quyết điều đó?
Sahil Doshi

@SahilDoshi Tôi cho rằng đó là nhược điểm của việc sử dụng phương pháp này. Tôi sử dụng nó khi tôi không muốn cho phép một lớp được khởi tạo và không có gì thừa hưởng từ lớp đó.
mahoukov

vâng, tôi hiểu điều đó nhưng giải pháp của bạn sẽ giúp tạo ra một cái gì đó giống như lớp singleton nơi chúng tôi không muốn ai gọi init. theo kiến ​​thức của tôi trong trường hợp lớp trừu tượng, một số lớp sẽ kế thừa nó. khác những gì sử dụng của lớp trừu tượng.
Sahil Doshi

0

Thay đổi một chút những gì @redfood đề xuất bằng cách áp dụng nhận xét của @ dotToString, bạn thực sự có giải pháp được IGListKit của Instagram áp dụng .

  1. Tạo một giao thức cho tất cả các phương thức không có nghĩa được định nghĩa trong lớp cơ sở (trừu tượng), tức là chúng cần các triển khai cụ thể ở trẻ em.
  2. Tạo một lớp cơ sở (trừu tượng) không thực hiện giao thức này. Bạn có thể thêm vào lớp này bất kỳ phương thức nào khác có ý nghĩa để có một triển khai chung.
  3. Ở mọi nơi trong dự án của bạn, nếu một đứa trẻ từ AbstractClassphải được nhập vào hoặc xuất ra theo một phương thức nào đó, hãy nhập nó làm AbstractClass<Protocol>thay thế.

Bởi vì AbstractClasskhông thực hiện Protocol, cách duy nhất để có một AbstractClass<Protocol>thể hiện là phân lớp. Vì AbstractClassmột mình không thể được sử dụng ở bất cứ đâu trong dự án, nó trở nên trừu tượng.

Tất nhiên, điều này không ngăn các nhà phát triển không được yêu cầu thêm các phương thức mới chỉ đơn giản là tham chiếu AbstractClass, điều này cuối cùng sẽ cho phép một thể hiện của lớp trừu tượng (không còn nữa).

Ví dụ trong thế giới thực: IGListKit có một lớp cơ sở IGListSectionControllerkhông triển khai giao thức IGListSectionType, tuy nhiên mọi phương thức yêu cầu một thể hiện của lớp đó, thực sự yêu cầu loại IGListSectionController<IGListSectionType>. Do đó, không có cách nào để sử dụng một đối tượng loại IGListSectionControllercho bất kỳ thứ gì hữu ích trong khung của chúng.


0

Trên thực tế, Objective-C không có các lớp trừu tượng, nhưng bạn có thể sử dụng Giao thức để đạt được hiệu quả tương tự. Đây là mẫu:

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

0

Một ví dụ đơn giản về việc tạo một lớp trừu tượng

// 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

-3

Bạn không thể tạo một đại biểu?

Một đại biểu giống như một lớp cơ sở trừu tượng theo nghĩa là bạn nói những chức năng nào cần được xác định, nhưng bạn không thực sự định nghĩa chúng.

Sau đó, bất cứ khi nào bạn thực hiện ủy nhiệm của mình (tức là lớp trừu tượng), bạn sẽ được trình biên dịch cảnh báo về các hàm tùy chọn và bắt buộc mà bạn cần để xác định hành vi cho.

Điều này nghe có vẻ như một lớp cơ sở trừu tượng với tôi.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.