views:

304

answers:

4

The common Superclass of Rectangle and Circle is Shape.

If I initialize some shapes, what is a good way of converting the shape into a circle later and keeping the same properties set while it was a shape? Should I implement a initWithShape in the subclasses that looks something like this?

- (id) initWithShape:(Shape*)aShape {
    self = (id) aShape;

    // set circle or rectangle specific values

    return self;
}

Does anyone have an example that I can look at?

+2  A: 

Don't do what you just did. Think about what happens when you do this:

Shape *shape = ...;
Rectangle *rect = [[Rectangle alloc] initWithShape:shape];

In the second line, an instance of Rectangle gets allocated. Then, the return value for initWithShape is just shape again, so the new Rectangle that we just allocated has been leaked!

The cast to id is also unnecessary—any Objective-C object can be implicitly cast to id.

I'm not entirely clear on what you're trying to do. Perhaps if you clarified your question, I could tell you what you should be doing.

Adam Rosenfield
A: 

If you have a reference to a Shape, and it might be a Rectangle or Pentagram or whatever, and you want to 'convert' to a circle (I guess you mean a circle with the same bounding box?), you have to create a new object. You can't change the class of an object after it's been created (except through very nasty low-level hacks.)

So yes, you would create an -initWithShape: method in class Circle. But the method would look like a normal init method, setting up the instance variables of the new Circle object ('self'). It would access properties of the given Shape, like its position and size, and set up the new object accordingly.

Jens Alfke
+1  A: 

You cannot change an object after it has been created, except by freeing it and creating a new one (which you can do in an init method, and is in fact quite often done for singletons or class clusters), but that is not really what you're after.

Give an existing Shape object, with some properties, your only real option is to create a new object based on the shape properties. Something like:

In Shape.m:

- (id) initWithShape:(Shape*)aShape {
    self = [super init];
    if (self != nil) {
        // copy general properties
        _x = aShape.x;
        _y = aShape.y;
        _color = [aShape.color copy];
    }
    return self;
}

In Circle.m:

- (id) initWithShape:(Shape*)aShape {
    self = [super initWithShale:aShape];
    if (self != nil) {
        // base properties on the class of the shape
        if ( [aShape isKindOfClass:[Oval class]] ) {
            // average the short and long diameter to a radius
            _radius = ([(Oval*)aShape shortDiameter] + [(Oval*)aShape longDiameter])/4;
        } else {
            // or use generic Shape methods
            _radius = aShape.size / 2;
        }
    }
    return self;
}
Peter N Lewis
Thanks. This is what I was looking for. Putting an initWithShape in Shape.m really makes the code clear when looking at Circle.m
Keith Kraft
A: 

Why not implement a method in your shapes to take properties from other shapes rather than trying to replace the instance of the object altogether. It's probably safer.

// for rectangle
- (void) takePropertiesFrom:(Shape *) aShape
{
    if ([aShape isKindOfClass:[Circle class]])
    {
        float radius = [aShape radius];
        [self setWidth:radius * 2];
        [self setHeight:radius * 2];
    }
    else
    {
        [self setWidth:[aShape width]];
        [self setHeight:[aShape height]];
    }
}

// for circle
- (void) takePropertiesFrom:(Shape *) aShape
{
    if ([aShape isKindOfClass:[Rectangle class]])
        [self setRadius:[aShape width] / 2];
    else
        [self setRadius:[aShape radius]];
}

Obviously you would want to set up a public interface for Shape that exposes the basic properties of a shape, such as height and width, and then you won't need to hard-code property stealing based on class type.

dreamlax