Quelqu'un peut-il m'expliquer la différence entre les catégories et l'héritage dans l'objectif C? J'ai lu l'entrée dans Wikipedia et la discussion sur les catégories n'y ressemble pas à celle de l'héritage. J'ai également regardé la discussion sur le sujet dans le livre "Open iPhone Development" et je ne comprends toujours pas.
Parfois, l'héritage semble être plus problématique qu'il n'en vaut la peine. Il est correctement utilisé lorsque vous souhaitez ajouter quelque chose à une classe existante qui est un changement dans le comportement de cette classe.
Avec une catégorie, vous voulez juste que l'objet existant fasse un peu plus. Comme déjà indiqué, si vous voulez simplement avoir une classe de chaîne qui gère la compression, vous n'avez pas besoin de sous-classer la classe de chaîne, vous créez simplement une catégorie qui gère la compression. De cette façon, vous n'avez pas besoin de changer le type des classes de chaînes que vous utilisez déjà.
L'indice réside dans la restriction selon laquelle les catégories ajoutent uniquement des méthodes, vous ne pouvez pas ajouter de variables à une classe à l'aide de catégories. Si la classe a besoin de plus de propriétés, alors elle doit être sous-classée (modifier: vous pouvez utiliser le stockage associatif, je crois).
Les catégories sont un bon moyen d'ajouter des fonctionnalités tout en se conformant à un principe orienté objet pour préférer la composition à l'héritage.
Modifier janvier 2012
Les choses ont changé maintenant. Avec le compilateur LLVM actuel et le runtime 64 bits moderne, vous pouvez ajouter des iVars et des propriétés aux extensions de classe (pas aux catégories). Cela vous permet de garder les iVars privés hors de l'interface publique. Mais, si vous déclarez des propriétés pour les iVars, elles peuvent toujours être accessibles/modifiées via KVC, car il n'existe toujours pas de méthode privée dans Objective-C.
Les catégories vous permettent d'ajouter des méthodes aux classes existantes. Ainsi, plutôt que de sous-classer NSData pour ajouter vos nouvelles méthodes de chiffrement géniales, vous pouvez les ajouter directement à la classe NSData. Chaque objet NSData de votre application a désormais accès à ces méthodes.
Pour voir à quel point cela peut être utile, regardez: CocoaDev
NSString est l'une des illustrations préférées des catégories Objective-c en action. NSString est défini dans le framework Foundation, qui n'a aucune notion de vues ou de fenêtres. Cependant, si vous utilisez une NSString dans une application Cocoa, vous remarquerez qu'elle répond à des messages comme – drawInRect:withAttributes:
.
AppKit définit une catégorie pour NSString qui fournit des méthodes de dessin supplémentaires. La catégorie permet à de nouvelles méthodes d'être ajoutées à une classe existante, nous ne traitons donc que des NSStrings. Si AppKit implémentait plutôt le dessin par sous-classement, nous aurions à traiter avec 'AppKitStrings' ou 'NSSDrawableStrings' ou quelque chose comme ça.
Les catégories vous permettent d'ajouter des méthodes spécifiques à l'application ou au domaine aux classes existantes. Il peut être assez puissant et pratique.
Si vous, en tant que programmeur, disposez d'un ensemble complet de code source pour une bibliothèque de code ou une application, vous pouvez devenir fou et changer tout ce dont vous avez besoin pour atteindre votre objectif de programmation avec ce code.
Malheureusement, ce n'est pas toujours le cas ni même souhaitable. Souvent, on vous donne un kit bibliothèque/objet binaire et un ensemble d'en-têtes à utiliser.
Ensuite, une nouvelle fonctionnalité est nécessaire pour une classe afin que vous puissiez faire quelques choses:
créer une nouvelle classe entière au lieu d'une classe de stock - répliquant toutes ses fonctions et ses membres, puis réécrit tout le code pour utiliser la nouvelle classe.
créer une nouvelle classe wrapper qui contient la classe stock en tant que membre (composition) et réécrire la base de code pour utiliser la nouvelle classe.
correctifs binaires de la bibliothèque pour changer le code (bonne chance)
forcez le compilateur à voir votre nouvelle classe comme l'ancienne et espérez qu'elle ne dépende pas d'une certaine taille ou place en mémoire et de points d'entrée spécifiques.
spécialisation de sous-classe - créez des sous-classes pour ajouter des fonctionnalités et modifiez le code du pilote pour utiliser la sous-classe à la place - en théorie, il devrait y avoir peu de problèmes et si vous devez ajouter des membres de données, cela est nécessaire, mais l'empreinte mémoire sera différente. Vous avez l'avantage d'avoir à la fois le nouveau code et l'ancien code disponibles dans la sous-classe et de choisir lequel utiliser, la méthode de classe de base ou la méthode substituée.
modifier la classe objc nécessaire avec une définition de catégorie contenant des méthodes pour faire ce que vous voulez et/ou remplacer les anciennes méthodes dans les classes de stock.
Cela peut également corriger des erreurs dans la bibliothèque ou personnaliser des méthodes pour de nouveaux périphériques matériels ou autre. Ce n'est pas une panacée, mais cela permet d'ajouter des méthodes de classe sans recompiler la classe/bibliothèque inchangée. La classe d'origine est la même en ce qui concerne le code, la taille de la mémoire et les points d'entrée, afin que les applications héritées ne se cassent pas. Le compilateur place simplement la ou les nouvelles méthodes dans le runtime comme appartenant à cette classe et remplace les méthodes avec la même signature que dans le code d'origine.
un exemple:
Vous avez une classe Bing qui sort sur un terminal, mais pas sur un port série, et maintenant c'est ce dont vous avez besoin. (pour certaines raisons). Vous avez Bing.h et libBing.so, mais pas Bing.m dans votre kit.
La classe Bing fait toutes sortes de choses en interne, vous ne savez même pas quoi, vous avez juste l'API publique dans l'en-tête.
Vous êtes intelligent, vous créez donc une catégorie (SerialOutput) pour la classe Bing.
[Bing_SerialOutput.m]
@interface Bing (SerialOutput) // a category
- (void)ToSerial: (SerialPort*) port ;
@end
@implementation Bing (SerialOutput)
- (void)ToSerial: (SerialPort*) port
{
... /// serial output code ///
}
@end
Le compilateur oblige à créer un objet qui peut être lié à votre application et le runtime sait maintenant que Bing répond à @selector (ToSerial :) et vous pouvez l'utiliser comme si la classe Bing avait été construite avec cette méthode. Vous ne pouvez pas ajouter uniquement des méthodes de membres de données et cela n'était pas destiné à créer des tumeurs géantes de code attachées aux classes de base, mais cela a ses avantages par rapport aux langages strictement typés.
Je pense que certaines de ces réponses indiquent au moins l'idée que l'héritage est un moyen plus lourd d'ajouter des fonctionnalités à une classe existante, tandis que les catégories sont plus légères.
L'héritage est utilisé lorsque vous créez une nouvelle hiérarchie de classes (toutes les cloches et les sifflets) et apporte sans doute beaucoup de travail lorsqu'il est choisi comme méthode pour ajouter des fonctionnalités aux classes existantes.
Comme quelqu'un d'autre l'a dit ici ... Si vous utilisez l'héritage pour ajouter une nouvelle méthode par exemple à NSString, vous devez aller changer le type que vous utilisez dans tout autre code où vous souhaitez utiliser cette nouvelle méthode. Si, toutefois, vous utilisez des catégories, vous pouvez simplement appeler la méthode sur les types NSString existants, sans sous-classement.
Les mêmes objectifs peuvent être atteints avec les deux, mais les catégories semblent nous donner une option plus simple et nécessitant moins de maintenance (probablement).
Quelqu'un sait-il s'il y a des situations où des catégories sont absolument nécessaires?
Une catégorie est comme un mixin: un module en Ruby, ou un peu comme une interface en Java. Vous pouvez le considérer comme des "méthodes nues". Lorsque vous ajoutez une catégorie, vous ajoutez des méthodes à la classe. L'article Wikipedia a bonnes choses .
La meilleure façon de voir cette différence est que: 1. l'héritage: quand vous voulez le tourner exactement sur votre chemin. exemple: AsyncImageView pour implémenter le chargement différé. Ce qui se fait en héritant UIView. 2. catégorie: Je veux juste y ajouter une saveur supplémentaire. exemple: Nous voulons remplacer tous les espaces du texte d'un champ de texte
@interface UITextField(setText)
- (NSString *)replaceEscape;
@end
@implementation UITextField(setText)
- (NSString *)replaceEscape
{
self.text=[self.text stringByTrimmingCharactersInSet:
[NSCharacterSet whitespaceCharacterSet]];
return self.text;
}
@end
--- Il ajoutera une nouvelle propriété à textfield pour vous permettre d'échapper à tous les espaces blancs. Tout comme lui ajouter une nouvelle dimension sans changer complètement son chemin.