Skip to content

Instantly share code, notes, and snippets.

@gorenje
Created February 9, 2011 10:33
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save gorenje/818278 to your computer and use it in GitHub Desktop.
Save gorenje/818278 to your computer and use it in GitHub Desktop.
An example of how to define "Mixin Classes" that can be mixed into existing classes. Useful for avoiding ever longer inheritence chains.
/*
* Provide a simple mixin helper to extend classes without inheritence. At the same
* time, this demostrates various scenarios when using mixins.
*
* For more information, check out (branch URL - release 0.8.1 of cappuccino)
* https://github.com/280north/cappuccino/blob/release-0.8.1/Objective-J/Runtime.js
* Note: this code is base on version 0.8.1 of Cappuccino.
*
* Install OJTest framework (https://github.com/280north/OJTest/wiki) and this class
* can be called:
* ojtest MixinTest.j
*
*/
@import <Foundation/CPObject.j>
/****************************************************************************************
* Provide a Mixin-Helper. A Mixin class can subclass this and then can be mixed into the
* target class by:
* [MixinClass addToClass:TargetClass]
* This then adds all instance variables and methods.
*
* *NOTES*
* This is not perfect since a certain amount of checking should be done, in particular
* ClassFourToBeMixed is mixed each time an object is instanciated which is not necessary.
*
* Also class methods ARE NOT mixed in, only instance methods.
*/
@implementation MixinHelper : CPObject
+ (void)addToClassOfObject:(CPObject)anObject
{
[self mixIntoClass:[anObject class] usingClass:self];
}
+ (void)addToClass:(id)aClass
{
[self mixIntoClass:aClass usingClass:self];
}
+ (void)mixIntoClass:(id)targetClass usingClass:(id)mixinClass
{
/*
* This is basically how to implemenet mixin at runtime for cappuccino 0.8.1
*/
class_addIvars(targetClass, class_copyIvarList(mixinClass));
class_addMethods(targetClass, class_copyMethodList(mixinClass) );
/*
* Copy class methods across, there are no class variables, so there
* is nothing to copy.
*/
class_addMethods(targetClass.isa, class_copyMethodList(mixinClass.isa) );
}
@end
/****************************************************************************************
* Mixin Classes
*/
/*
* This contains all the methods that we want to "mixin"
*/
@implementation MixinResultString : MixinHelper
{
CPString m_result;
}
- (void) methodNoArgsNoReturn
{
m_result = ("methodNoArgsNoReturn - " + class_getName([self class]));
}
- (CPString) getResult
{
return m_result;
}
@end
/*
* Add a counter
*/
@implementation MixinCounter : MixinHelper
{
int m_counter;
}
- (int)increment
{
if ( !m_counter ) m_counter = 0;
return (++m_counter);
}
@end
/*
* Assuming that class has instance variable
*/
@implementation MixinFunctionality : MixinHelper
- (int) decrement
{
return (--m_counter);
}
@end
/*
* Override an existing method.
*/
@implementation MixinOverride : MixinHelper
- (CPString) toBeOverridden:(CPString)returnValue
{
return returnValue;
}
@end
/*
* Mixin Class Methods. Class methods are also mixed in.
*/
@implementation MixinClassMethods : MixinHelper
+ (CPString)myClassName
{
return "+" + class_getName(self) + "+";
}
@end
/****************************************************************************************
* Target classes to demostrate external and internal mixing in.
*/
@implementation ClassHasInstanceVariable : CPObject
{
int m_counter;
}
- (id)init
{
self = [super init];
if ( self ) {
m_counter = 100;
}
return self;
}
@end
@implementation ClassAddClassMethod : CPObject
@end
@implementation ClassExternalMixin : CPObject
@end
@implementation ClassCombineMixins : CPObject
@end
@implementation ClassInternalMixin : CPObject
- (id)init
{
self = [super init];
if ( self ) {
[MixinCounter addToClass:[self class]];
}
return self;
}
@end
@implementation ClassToBeOverridden : CPObject
/*
* This won't be ovrridden because of different signature.
*/
- (int) toBeOverridden
{
return 1;
}
/*
* This will be replaced because it has the same signature as the mixin method.
*/
- (CPString) toBeOverridden:(CPString)aString
{
return "[" + aString + "]";
}
@end
/****************************************************************************************
* Unit test class for playing around with these mixin classes.
*/
@implementation MixinTest : OJTestCase
- (void) testClassMethodsAreMixedin
{
[MixinClassMethods addToClass:ClassAddClassMethod];
[self assert:[ClassAddClassMethod myClassName] equals:"+ClassAddClassMethod+"];
}
- (void) testMixingInFunctionality
{
[MixinFunctionality addToClass:ClassHasInstanceVariable];
var obj = [[ClassHasInstanceVariable alloc] init];
[obj decrement];
[obj decrement];
[self assert:[obj decrement] equals:97];
}
/*
* Explicitly mixin a mixin into two separate classes.
*/
- (void) testExternalMixin
{
[MixinResultString addToClass:ClassExternalMixin];
var obj = [[ClassExternalMixin alloc] init];
[obj methodNoArgsNoReturn];
[self assert:[obj getResult] equals:"methodNoArgsNoReturn - ClassExternalMixin"];
}
/*
* Test the automatical mixing in of a mixin
*/
- (void) testInternalMixin
{
var objOne = [[ClassInternalMixin alloc] init];
var objTwo = [[ClassInternalMixin alloc] init];
var objTre = [[ClassInternalMixin alloc] init];
[objOne increment];
[objOne increment];
[objOne increment];
[objTwo increment];
[objTre increment];
[objTre increment];
[objTre increment];
[objTre increment];
[objTre increment];
[objTre increment];
[self assert:[objTwo increment] equals:2];
[self assert:[objOne increment] equals:4];
[self assert:[objTre increment] equals:7];
}
- (void) testCombiningMixins
{
[MixinResultString addToClass:ClassCombineMixins];
[MixinHelper mixIntoClass:ClassCombineMixins usingClass:MixinCounter];
var obj = [[ClassCombineMixins alloc] init];
[obj methodNoArgsNoReturn];
[self assert:[obj getResult] equals:"methodNoArgsNoReturn - ClassCombineMixins"];
[obj increment];
[obj increment];
[self assert:[obj increment] equals:3];
}
- (void) testOverriding
{
var obj = [[ClassToBeOverridden alloc] init];
[self assert:[obj toBeOverridden:@"fibar"] equals:@"[fibar]"];
[self assert:[obj toBeOverridden] equals:1];
[MixinOverride addToClass:ClassToBeOverridden];
/*
* Note that mixin work for existing objects too, i.e. the object was created before
* the class was mixin'ed
*/
[self assert:[obj toBeOverridden:@"fibar"] equals:@"fibar"];
[self assert:[obj toBeOverridden] equals:1];
}
@end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment