Define the subclass of the object when the parent class is built (dynamic subclasses)

advertisements

EDIT: To clear up some confusion, I am not actually using Male and Female. A better example would be parent-class Animal and sub-classes Cat and Dog.
I'm kind of new to OOP, but I've grasped it pretty well so far. However, there's one thing I can quite find a way of doing.
Firstly, I create a create a new object based around the user. The user can be be (for this example) male or female, but I cannot check if they are without checking in the database. I then need to create a new user object with the sub-class of their sex.
For example, here is my current (messy) code:

$user = new Person();
$userType = $user->GetUserType($userid);
if ($userType == 'Male') {
    $user = new Male($userid);
} else {
    $user = new Female($userid);
}

If I want to make a new object, I must do this every time, which is something I don't want to have to do.
Here's a little more code to help put this into perspective:

class Person {
function GetUserType($userid) {
        $db = mysqlConnect();
        $stmt = $db->stmt_init();
        // Get the players information
        $stmt->prepare("SELECT user_type FROM user_information WHERE user_id = ?");
        $stmt->bind_param('i', $userid);
        $stmt->execute();
        $stmt->bind_result($userType);
        $stmt->fetch();
        $stmt->close();
        $db->close();
        return $userType;
    }
}
class Male extends Person {
    // Male based things here
}
class Female extends Person {
    // Female based things here
}

So, in the __construct of the Person class, I'd like it to check for the users sex and set the appropriate sub-class.


So, in the __construct of the Person class, I'd like it to check for the users sex and set the appropriate sub-class.

Like already said elsewhere, ctors do not return, so you cannot do that. And ctors should not query the database anyway, because that would be doing work. If you cannot/want not create the correct subtype immediately via DataMapper or Repository, you can use a State Pattern:

Allow an object to alter its behavior when its internal state changes. The object will appear to change its class.

This is one of the Behavioral Patterns from the Gang Of Four book. The keyword, obviously, is behavior here. The State pattern is not about varying Subtypes (Cat, Dog) as such, but rather about the difference in the behavior those have. Hence my comments to your question.

Example:

interface GenderBehavior
{
    public function someBehavior();
    // can add more methods here
}

class FemalePersonBehavior implements GenderBehavior
{
    public function someBehavior()
    {
        // female specific behavior
    }
}

class MalePersonBehavior implements GenderBehavior
{
    public function someBehavior()
    {
        // male specific behavior
    }
}

class UnknownGenderBehavior implements GenderBehavior
{
    public function someBehavior()
    {
        // either implement a neutral behavior or
        throw new LogicException('Unknown Gender set');
    }
}

To create those instances, you add a Factory

class GenderBehaviorFactory
{
    public static function create($sex = null)
    {
        switch ($sex) {
           case 'male':
               return new MalePersonBehavior;
           case 'female':
               return new FemalePersonBehavior;
           default:
               return UnknownGenderBehavior;
        }
    }
}

Then you add the GenderBehavior interface to the Person class and tunnel all access to the interface methods to the Behavior instance:

class Person implements GenderBehavior
{
    private $behavior;

    public function __construct()
    {
        $this->behavior = GenderBehaviorFactory::create();
    }

    public function changeGender($sex)
    {
        $this->behavior = GenderBehaviorFactory::create($sex);
    }

    public function someBehavior()
    {
        $this->behavior->someBehavior();
    }
}

Now when you create a Person the first time, it will instantiate with an UnknownGenderBehavior and if you try to call someBehavior, it will raise an Exception. When you call changeGender with either male or female as argument, the appropriate GenderBehavior will be created and set to Person. And then you can call someBehavior.