Skip to content

Instantly share code, notes, and snippets.

@klaaspieter
Forked from gorenje/MixinTest.j
Created February 14, 2011 09:15
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save klaaspieter/825649 to your computer and use it in GitHub Desktop.
Save klaaspieter/825649 to your computer and use it in GitHub Desktop.
/*
* 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)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) );
}
@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. NOTE this does not work, so the test for this checks for failure.
*/
@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) testClassMethodsArentMixedin
{
[MixinClassMethods addToClass:ClassAddClassMethod];
try {
[ClassAddClassMethod myClassName];
[self assertTrue:NO];
} catch ( e ) {
regexp = new RegExp("CPInvalidArgumentException. . .ClassAddClassMethod myClassName. "+
"unrecognized selector sent to class");
[self assertTrue:regexp.test(e)];
}
}
- (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