Objective-c - Recommended pattern to cascade decision making in a class cluster

136 Views Asked by At

I would like to create a "multi-level" class cluster such that each "concrete" class could return a more specific instance when certain criteria are met.

For instance in the base class:

@interface BaseClass : NSObject  // this is the public API of the service

+(instancetype)initWithData:(Data *)data;
// common interface of all the specific implementations..
...
...

@end


@implementation BaseClass

// basically implementing the "cluster"
+(instancetype)initWithData:(Data *)data {
  // testing some conditions to decide on the more specific version of the class to return...
  if (data.condition == condition1) {
     return [[SomeClassOne alloc] initWithData:data];
  }
  if(data.condition == condition2) {
     return [[SomeClassTwo alloc] initWithData:data];
  }
  ... 
  // have many types which could be returned 
}


// an example of a specific instance that should be returned from the cluster - all of these classes are "private" implementations of the base class
@implementation SomeClassOne

-(instancetype)initWithData:(Data *)data {
  self = [super initWithData:data];
  // all was good until a new optimization came about...
  // now this instance can refine the class cluster even better
  // what I would want is a way to do:
  self = [[SomeClassOne_EvenBetterVersion alloc] initWithData:data];
  // but this would be bad - and would cause recursion if the new version inherits this version...
}
@end

I don't want to constantly add new conditions in the base class (The big "if" statement) because the conditions become very specific to the concrete classes - mostly optimizations that have to do with new capabilities.

Is there a better pattern for this?

I thought about creating a class method in each sub class that would do additional checks - but this becomes really awkward having to call [Subclass initWithData:data] in each subclass

1

There are 1 best solutions below

4
On

A few things that might improve this:

1) Override allocWithZone: for your BaseClass to return a singleton instance of BaseClass. This prevents extra object allocations. The initWithData: method of the BaseClass will then return you the real instance.

2) Structure or transform the Data parameter in such a way for easy dictionary lookups to get your concrete implementation class. Instantiate that in the initWithData:

Create a structure statically in +initialize (or dynamically whenever) that looks like:

static_dictionary = @{ @"Some string or hashable condition" : [CoolSubclass class],
                       @"Condition2" : [CoolerSubclass class] };

Now in the initWithData: you have constant time look ups for your type and clean code.

Class my_type = [static_dictionary objectForKey: transformated_data_condition];
if(my_type == Nil) { // throw? return nil? }
return [[my_type alloc] initWithData: data];

Now it becomes easy to repeat the process - Maybe CoolerSubclass is another class cluster that has its own dictionary of types and test method.