Welcome to the Treehouse Community

The Treehouse Community is a meeting place for developers, designers, and programmers of all backgrounds and skill levels to get support. Collaborate here on code errors or bugs that you need feedback on, or asking for an extra set of eyes on your latest project. Join thousands of Treehouse students and alumni in the community today. (Note: Only Treehouse students can comment or ask questions, but non-students are welcome to browse our conversations.)

Looking to learn something new?

Treehouse offers a seven day free trial for new students. Get access to thousands of hours of content and a supportive community. Start your free trial today.

Python

Stephen Hopkinson
Stephen Hopkinson
4,648 Points

Objects accessing each other's attributes

I've just finished Kenneth's object-oriented programming course, and I'm trying to apply OOP techniques to creating an analogue clock. Unfortunately, I've run into some difficulties.

My model uses a separate instance of the Hand class for each hand on the clock face, plus a separate instance of the Clock class that handles all of the time updates (to avoid each hand having to separately call datetime.datetime.now() at each update).

Here's the problem: I need each Hand instance to reference the Clock instance in their update() method, but I don't know how to get objects to reference other objects' attributes.

In short: I need a way for hand.update() to access clock.now, but I don't know how to reference an object's attributes from another object. I'm sure I've missed something obvious, but I'm not sure what. Any tips much appreciated!

Stephen Hopkinson
Stephen Hopkinson
4,648 Points

Here's the code, if that helps. First, clock.py:

import datetime

class Clock:
    def __init__(self):
        self.update()

    def update(self):
        now = datetime.datetime.now()
        self.second = now.second
        self.minute = now.minute
        self.hour = now.hour

Then, hands.py:

import math

class Hand:
    def __init__(self):
        self.length = 1
        self.width = 1
        self.divisions = 12
        self.get_position()

    def get_position(self):
        self.x_position =  # this is where I want to reference clock.hour
        self.y_position = # here too!

And finally, main.py:

class Main:

    def setup(self):
        self.clock = Clock()
        self.hands = [
            Hand(),
            Hand(),
            Hand() # imagine these correspond to hour, minute and second hands
        ]

    def __init__(self):
        self.setup()

        while True:
            self.clock.update()
            for hand in self.hands:
                hand.get_position()
            # code to draw clock

Main()
Stephen Hopkinson
Stephen Hopkinson
4,648 Points

Just woken up and had an epiphany. Presumably the answer is to add 'time' as an argument in the hand class's get_position() method, and then pass in clock.hour (for example) in the main loop?

So hands.py would be updated as so:

def get_position(self, time):
        self.x_position = # code to turn time into co-ordinates 
        self.y_position = # code to turn time into co-ordinates 

And the main loop would be updated as so:

    while True:
        self.clock.update()
        for hand in self.hands:
            hand.get_position(clock.hour)
        # code to draw clock

Would this be the best way to handle it?

2 Answers

Sang Han
Sang Han
1,257 Points

I would say that having your Hand instances being able to call the update on your Clock is probably not the best solution to the problem.

Instead, it's probably better for Main to deal with datetime.datetime.now() rather than Clock.

When time changes, have your Main deal with it and talk to Clock which will update it's hand accordingly. Have Main deal with drawing your clock by merely checking the attributes of the clock.

This is different than what you have now where your Main class is composed of both a Clock and Hands which will mean that your Hands will need to access Clock. AClock has Hands, so naturally you want to have a Clock which will delegate to Hand

Stephen Hopkinson
Stephen Hopkinson
4,648 Points

Thanks Sang, that makes sense. So presumably that means that OOP is best suited to hierarchical relationships between objects, rather than independent objects communicating with each other?

One question: is the difficulty of having objects accessing each other's attributes limited to Python's implementation, or is that true of all object-oriented languages?

Sang Han
Sang Han
1,257 Points

Sorry, I didn't respond sooner!

But you're spot on, the idea that objects are individual entities kind of like people or things, that have a concrete representation of their "self" is a key OOP tenets. The idea that your hand object requires access to properties of the clock would mean that your clock doesn't fully contain the entirety of it's hands, and the hands are a separate entity that interacts with the clock on it's own.

Encapsulation

Kind of sounds abstract, but it's the key concept to the notion of Object Encapsulation, or information hiding. Well built objects are designed with the idea that you can hide an objects internal representation that can be swapped in and out, and other people only need to worry about the Interface to use your object (tidbit: Python uses duck-typing for creating interfaces, in other compiled oop languages interfaces are much more strict). This makes programming robust and code reusable. It's means that you don't need to know how to build a Shovel in order to show someone how to use one. Using good data hiding principles means you can take care of building a good Shovel class, and others only need to know how to use it's shovel.plot() method.

Mechanisms of Delegation

Another key OOP principle is the notion of building more complex objects or specialized forms of a genral object that is otherwise known as Delegation. Delegation means that an object shall perform only what it knows best, and leave the rest to other objects. Delegation can be implemented with two different mechanisms: Composition or Inheritance.

Object Composition

You can create more complex/specialized object by simply composing it of other objects. Your new object will have it explicitly manage the work they do in order to maintain encapsulation. So in your code example you're using Delegation through object composition (although you may not have known that was what it's called) and trying to managing those explicit relationships is a topic where many design patterns have resulted in order to build robust systems.

Class Inheritance

The other way of doing delegation is through Class Inheritance. Inheritance is not as explicit as Composition, since the inherited object merely gains all the facilities and internal makeup of it's superclass. This can be a great tool for code reuse and extending functionality. It reduces a lot of boilerplate code without the need to design interconnections between objects. But as well, it also be a source of much frustration, especially in cases where tight control is needed over an objects makeup since the subclass has no control of the code it inherits. It can also be a case much confusion in languages like Python which support multiple inheritance, since it's can become hard to pinpoint which parent in the Class Hierarchy an object received which behavior.

This is where the whole "has-a" or "is-a" distinction comes to play. Generally if your object == "has-a" relationship use composition, whereas if an is related by object == "is-a" relationship, you want to use inheritance.

For instance, if here is an example of building a House you might build it like so.

class Door:
    """
    Construct a pretty sweet door
    so you can hide yo kids hide yo wives.
    """
    def __init__(self, position='open'):
        self.position = position

    def close(self):
        if not self.position == 'closed':
            self.position = 'closed'

class House:
    """
    Construct a house since being homeless isn't fun.
    """
    def __init__(self):
         self.door = Door()
         self.locked = False

    def lock(self):
        self.door.close()
        self.locked = True

    def unlock(self):
        if not self.door.position == 'closed':
            raise WhoLeftTheDoorOpenError
        self.locked = False

Obviously, since a house "has-a" door, rather than a house "is-a" door.

A case where you might rather use inheritance would be in the case you wanted to build different types of houses.

class Mansion(House):
    """
    Because every mansion comes with a butler.
    """
    def __init__(self, butler):
        super(House, self).__init__(self)
        self.butler = butler

    def lock(self):
        self.butler.get(self.door, 'locked')

    def unlock(self):
        self.butler.get(self.door, 'unlock')

There are tradeoffs when choosing between the two, and since the Python has inheritance built into the syntax, most people think it's the only way. But it's important to make the distinction because it makes your programs much easier to reason to follow the pattern and not have some weird behavior.

Usually composition is said to be a very generic technique that needs no special syntax, while inheritance and its rules are strongly dependent on the language of choice. Actually, the strong dynamic nature of Python softens the boundary line between the two techniques.