tags:

views:

128

answers:

3

Suppose I have a definition for a door:

class Door
{
    public void Lock()
    {
        // lock the door
    }
}

This appeared to make sense to me, at least for awhile. But now, I'm not so sure. If I had a Person object that wanted to lock a Door, he would call aDoor.Lock(). But in real life, we do not lock doors by telling the door to lock itself.

It seems like a more accurate model of the situation would be the person being able to directly modify the state of aDoor, provided he has sufficient power to lock doors. For example, aCat should not be able to set aDoor.IsLocked = true. I could see how to do this with properties, if they supported parameters:

class Person
{
    public void LockDoor(Door door)
    {
        door.IsLocked(this) = true;
    }
}

class Door
{
    bool isLocked;

    public bool IsLocked(Person person)
    {
        set
        {
            if(person != null) // ensure there is a real person trying to lock the door
            {
                this.isLocked = value;
            }
        }
    }
}

static void Main()
{
    Person personFromThinAir = new Person();
    Door doorFromThinAir = new Door();
    personFromThinAir.LockDoor(doorFromThinAir);
}

Instead, what we can do is this:

class Person
{
    public void LockDoor(Door door)
    {
        door.SetLocked(this, true);
    }
}

class Door
{
    bool isLocked;

    public void SetLocked(Person person, bool locked)
    {
        if(person != null)
        {
            this.isLocked = locked;
        }
    }
}

Obviously these two classes are strongly coupled and both would probably have interfaces extracted in actual code, but that's not what I'm getting at. My question is, is this a better way to model the relationship between the two objects? Is there an even better way than this? The more I think about it, the less sense of aDoor.Lock() I can make; it seems to violate object-oriented design.

+6  A: 

OOP isn't really about modelling how things work in the "real world". Its more about managing complexity. Considering this, it is perfectly acceptable for the door to lock itself. Even in the real world, a person locking a door doesn't need to know anything about how the lock works other than turning the knob or the key.

Hiding the details of a complex idea behind an abstraction is what makes OOP so useful. The abstractions you use differ with the problem domain. In the example you gave the Person shouldn't need to know anything about the door other than how to operate it:

class Door
{
    public bool Open(){}
    public bool Close(){}
    public void Lock(){}
    public void Unlock(){}
}
codeelegance
+9  A: 

Although the person "locks" the door, in reality the person is toggling (or frobbing) on an element of the door (the lock handle) and that manipulation causes the lock to lock the door. You can think of this where, although the person is moving the deadbolt, the deadbolt is what is locking the door - not the person. So a better representation might be that a door has a lock, and the person calls lock.lock(), which then sets the lock being closed (locked).

The basic premise here is that, although the person is manipulating the lock, that is external (the function call). The lock's internal changes (the code inside the function) is what is actually causing the door to lock. The person is not taking off the handle and manipulating the inside to lock the door every time - they are simply toggling a state on the outside and expecting the machinery internal to handle it.

aperkins
well said - typed it faster than I!
Josh E
It helps to have actually tried to teach this concept for about 4 or 5 years too ;) Let me tell you, you find lots of examples eventually to try and get the concept of OOP through
aperkins
A: 

The most interesting design issue here to me is how to handle the coupling between the locker and the lockee since there are requirements which must be met for the locking/unlocking to be allowed. I look at this question and imagine a game where a player might sometimes be a human but other times be a cat (per the example given), and maybe is_human is the only requirement for locking/unlocking. But you might also want to have doors which require the matching key to be in the player's possesion in order for locking/unlocking to happen. If so, you have to add that to the criteria. Perhaps some doors can only be locked from one side and not the other, so the player's location must be added to the criteria. You could further add a lockpicking skill which some players might have (cat burglars, no doubt) to allow them to have a chance to unlock (but not lock) a door even if they didn't have the key. Etc. etc.

One can envision a conversation between the objects like:

Player: "I am trying to unlock you."
Lock: "Do you meet requirement A?"
Player: "Yes"
Lock: "Do you meet requirement B?"  // Only some doors would ask this.
Player: "Yes"
Lock: "OK, you succeed.  I am unlocked!"

But, you probably don't want to expose the involved fields publicly or clutter up the Player interface seen by objects that don't need to know about locking/unlocking requirements.

I am not a C# programmer, and it has been a while since I did Java, but I think an approach in Java which may also apply in C# would be to have the Player object pass an instance of a lock_unlock_credentials inner class as a parameter to the get_locked/get_unlocked methods of the Door object (or Lock object as has been suggested.) The lock_unlock_credentials object would have callback methods which, by virtue of its being an inner class of Player, could access relevant fields of the Player object, but those fields would otherwise not be exposed outside of Player. The Lockable object could then use those callback methods to check to see if the requirements it cares about are met. You can't avoid the coupling resulting from the requirements, but this way keeps the details internal to the interaction between the Player and the Lockable object.

Not sure if the same inner class approach applies to C#, but presenting this as something to think about.

Anon