1

I want some ideas on how to program this class that needs all its variables and properties to be set beforehand for it to work. I was thinking to create an method to set the 3 CCSprites. But what about the properties? should I do an NSAssert to make sure they are set by the user, or is there a better way?

@interface DigParallaxBackgroundLayer : CCLayer
{
    CCSprite *background1;
    CCSprite *background2;
    CCSprite *background3;
}

@property (nonatomic, assign) float bg1ScrollSpeed;
@property (nonatomic, assign) float bg2ScrollSpeed;
@property (nonatomic, assign) float bg3ScrollSpeed;

@property (nonatomic, assign) CGPoint initialOffset;
mipadi
  • 398,885
  • 90
  • 523
  • 479
Tom Lilletveit
  • 1,872
  • 3
  • 31
  • 57
  • Is a scroll speed of 0 an error? – tc. Apr 25 '13 at 00:26
  • tc: not really, that just means it does not scroll at all. which is not wrong. I was thinking to do as andyvn22 metioned: creating an designated initializer that initialized the scroll speed, and then just do a NSAssert if the CCSprites are nil – Tom Lilletveit Apr 25 '13 at 00:29
  • 1
    If they *need* to be non-nil, make the user pass them in `-init` and check that they're non-nil in `-init`. A random assert somewhere else in your code is more difficult to debug. Alternatively, you could allow some of the layers to be nil, or you could support a variable number of layers. – tc. Apr 25 '13 at 00:33
  • I have a guide about dependency injection, as well as a library (you don't _need_ a library, but it could help) here: http://www.typhoonframework.org – Jasper Blues Apr 25 '13 at 00:46
  • I'd like add that you can also hide the `init` method so that the user of the class is forced to use the designated initializer: `- (instancetype) init __attribute__((unavailable("init not available")));`. I find myself using this a lot and made a snippet for it. – mkko Feb 03 '14 at 20:35

3 Answers3

2

Find reasonable default values for the properties, and/or create an initializer that includes values for each of them:

-initWithBG1ScrollSpeed:bg2ScrollSpeed:bg3ScrollSpeed:
andyvn22
  • 14,696
  • 1
  • 52
  • 74
2

Its a great idea to provide the the class's collaborators externally, rather than have the class make its own collaborators. This is called Dependency Injection . . . (if you had the class set its own default values and collaborators, then wanted to change these you'd have to find all of the place's they're used and change them one-by-one. . . also the class would be hard to unit test).

. . . If you propagate this approach throughout your classes, you'll end up with a top-level assembly/wiring class that can be used to perform configuration management (sensible default values) as well as wire the components together into a functioning whole.

There are two varieties that could be used here:

  • Initializer injection: This is where the required collaborators are provided by an initializer method. One of the benefit of this approach is that its very easy to assert that the class is in a required state before continuing.

  • Property injection: This is where the required collaborators are set via a property setter. One of the benefits of this approach is that it promotes readability, when a class is composed of many other classes. A drawback to this approach is that it can be easy to get the object into an incorrect state. . . Some Dependency Injection libraries provide a call-back method to avoid this.

In the case where an object is immutable (ie the collaborators won't change at runtime) I like to use read-only properties and inject them via an initiailzer. This promotes a clear contract, and makes it easy to assert that the object is in the required state before continuing. . . . otherwise I like to use a method that gets called after property setters have been executed.

NB: assert is great for checking required state in debug-mode, but by default it won't be executed at run-time. (This may or may not be what you want).

Jasper Blues
  • 28,258
  • 22
  • 102
  • 185
  • "Its a great idea to inject the class's collaborators externally - rather than have the class make its own collaborators. This is called Dependency Injection" Yes I want this class to be easily modified externally so could be used in different scenarios. – Tom Lilletveit Apr 25 '13 at 00:32
  • This answer is better and more thorough than mine, but I still suggest reading the "Further Commentary" section of my answer. – Aaron Brager Apr 25 '13 at 00:41
  • Aaron: I read it, your right I might at a later point want more than 3 backgrounds so I should Have a NSArray store them indeed. – Tom Lilletveit Apr 25 '13 at 00:43
  • Thanks Jasper. This is the clearest explanation of DI I've come across. – Gaurav Sharma Jul 08 '14 at 10:28
  • @GauravSharma Very kind of you! If you like we've also put together a library that helps put DI "on rails" : http://typhoonframework.org – Jasper Blues Jul 08 '14 at 11:01
1

Answer

You should set reasonable default values in your init method. You should also make sure the properties are set to reasonable values when you change them.

Note that NSAssert by default won't be executed in the production version of your app (you can change this behavior, but it's not recommended; see https://stackoverflow.com/a/6445429/1445366)

You can also create a custom class factory method. Something like:

+ (DigParallaxBackgroundLayer *) newBackgroundLayerWithDefaultScrollSpeed:(float)defaultSpeed {
    DigParallaxBackgroundLayer * newLayer = [[DigParallaxBackgroundLayer alloc] init];
    newLayer.bg1ScrollSpeed = newlayer.bg2ScrollSpeed = newLayer.bg3ScrollSpeed = defaultSpeed;
    return newLayer;
}

Further Commentary

When you start a new class with variable names like something1, something2, something3 you will almost always regret it.

It is almost always better to create an NSArray of CCSprites and another NSArray with your speeds in them. (You can use NSValue or NSNumber to wrap simple variables in an object so they'll go in an NSArray).

Even better, you could create another very simple class with properties background and scrollSpeed, and then put those in an NSArray.

Community
  • 1
  • 1
Aaron Brager
  • 65,323
  • 19
  • 161
  • 287
  • I like the "even better" solution - create a class with background and scrollspeed properties. . . worth the effort for code clarity, and very little overhead. – Jasper Blues Apr 25 '13 at 06:17