Table of Contents:
- Why use Objects and Object Oriented Programming?
- One to Many Relationships
- Many to Many Relationships
- Conclusion
Why use Objects and Object Oriented Programming (OOP)?
If you have coding experience, you have likely come across classes and objects, whether they're ones you created or ones native to your programming language. A class in programming simply refers to a bundle of related data and functionality. 'Hello'
in Python, for example, is an object (or instance) of the String
class. It comes with its own methods that enhance its functionality, such as capitalize()
, join()
, find()
, etc. Classes create abstractions of real concepts with understandable, reusable code.
Creating your own classes and instances can add a lot of functionality and conciseness to your code, but what if you want those objects to be related to each other? Say you have objects representing paintings and objects representing artists, but now you want to bring them together. Once you have an understanding of how to build object relationships, you can model those real world connections in your code.
Here is a basic set up for the Artist
and Painting
classes. Right now they are completely separate entities. Each only includes the __init__
and __repr__
methods to initialize object attributes and generate a prettier string when printing the object. I'm also tracking all instances of each class with the class attribute all
.
class Artist:
all = []
def __init__(self, name):
self.name = name
Artist.all.append(self)
def __repr__(self):
return f"Artist: {self.name}"
class Painting:
all = []
def __init__(self, title, genre):
self.title = title
self.genre = genre
Painting.all.append(self)
def __repr__(self):
return f"Painting: {self.title}, {self.genre}"
One to Many Relationships
One to many relationships refer to when an object of type A is associated with multiple objects of type B, but an object of type B only belongs to one object of type A.
Implementing a One-to-Many Relationships
From the example above, artists can have many paintings but paintings only belong to one artist. In order to model this one to many relationship, the object which 'belongs' to another object should include an attribute to keep track of that relationship. Since a painting can only belong to one artist, I'll add an attribute called artist
to my Painting
class.
class Painting:
all = []
# Added artist attribute below
def __init__(self, title, genre, artist):
self.title = title
self.genre = genre
self.artist = artist
Painting.all.append(self)
def __repr__(self):
return f"Painting: {self.title} by {self.artist.name}, {self.genre}"
NOTE: If you are creating your own application, it'd be beneficial to manage these attributes with properties which perform validation checks. For example, you can assert that a painting's
artist
attribute is an object of typeArtist
before setting it. You can read more about how to do this here.
Now if I create an example of a painting and its artist, I will always have access to that artist through the painting.
vangogh = Artist("Vincent van Gogh")
starry_night = Painting("Starry Night", "post-impressionism", vangogh)
print(starry_night)
print(starry_night.artist)
# LOG: Painting: Starry Night by Vincent van Gogh, post-impressionism
# Artist: Vincent van Gogh
This is a good start, but this relationship is still one-sided. We need to do a little more if we also want to view all of an artist's paintings. An object of type A, which has many objects of type B, can include a method to return all of its B objects. In this example, I can use list comprehension to access all paintings and print only the ones that belong to the current artist.
class Artist:
all = []
def __init__(self, name):
self.name = name
Artist.all.append(self)
def __repr__(self):
return f"Artist: {self.name}"
# New method
def paintings(self):
return [painting for painting in Painting.all if painting.artist == self]
vangogh = Artist("Vincent van Gogh")
starry_night = Painting("Starry Night", "post-impressionism", vangogh)
sower = Painting("The Sower", "post-impressionism", vangogh)
print(vangogh.paintings())
# LOG: [Painting: Starry Night by Vincent van Gogh, post-impressionism,
# Painting: The Sower by Vincent van Gogh, post-impressionism]
Adding another one-to-many relationship
The one to many relationship between artists and paintings is now set up! I can easily add another one that behaves in a similar way. Here's an example of an additional class, Museum
, that also has a one to many relationship with Painting
:
class Artist:
all = []
def __init__(self, name):
self.name = name
Artist.all.append(self)
def __repr__(self):
return f"Artist: {self.name}"
def paintings(self):
return [painting for painting in Painting.all if painting.artist == self]
class Museum:
all = []
def __init__(self, name):
self.name = name
Museum.all.append(self)
def __repr__(self):
return f"Museum: {self.name}"
def paintings(self):
return [painting for painting in Painting.all if painting.museum == self]
class Painting:
all = []
def __init__(self, title, genre, artist, museum):
self.title = title
self.genre = genre
self.artist = artist
self.museum = museum
Painting.all.append(self)
def __repr__(self):
return f"Painting: {self.title} by {self.artist.name}, {self.genre}, located at {self.museum}"
Many to Many Relationships
Many-to-many refers to relationships where an object of type A can be associated with multiple instances of type B and vice versa. There are many real world examples that fall under this category, such as students and teachers, employees and projects, and actors and movies.
Because artists can have multiple paintings, any of which potentially belonging to different museums, it makes sense to say that an artist may be featured at multiple museums. Similarly, museums contain multiple paintings that may belong to different artists, so we can also say that museums feature multiple artists. This relationship between Artist
and Museum
would be considered a many-to-many relationship.
Implementing a Many-to-Many Relationship
Many-to-many object relationships can be implemented with intermediary classes where two classes are related to each other through the intermediary class. Because artists cannot be featured at museums without a painting and vice versa, Painting
will serve as our intermediary class. Other options include creating intermediary classes for the primary purpose of connecting two other classes, or storing relationships in list attributes.
NOTE: If you are not using an intermediary class, be sure to store the many-to-many relationship in just one class to maintain a single source of truth. Without the
Painting
class,Artist
might have a property calledmuseums
which is a list containing its associated museums. TheMuseum
class should not include anartists
property.Museum
can instead include anartists()
method that finds all artists which contain the current museum in itsmuseums
list.
In this example, we should be able to view all of an artist's museums by finding its paintings, then accessing the museums that those paintings belong to.
We already have a paintings()
method in our Artist
class so we can make another short method to access the corresponding museums.
class Artist:
all = []
def __init__(self, name):
self.name = name
Artist.all.append(self)
def __repr__(self):
return f"Artist: {self.name}"
def paintings(self):
return [painting for painting in Painting.all if painting.artist == self]
# New method
def museums(self):
return [painting.museum for painting in self.paintings()]
If we look at an Artist
example, we can now access all of the museums they're featured at.
vangogh = Artist("Vincent van Gogh")
met = Museum("The MET")
npg = Museum("The National Portrait Gallery")
starry_night = Painting("Starry Night", "post-impressionism", vangogh, met)
sower = Painting("The Sower", "post-impressionism", vangogh, npg)
print(vangogh.museums())
# LOG: [Museum: The MET,
# Museum: The National Portrait Gallery]
To complete the relationship, we need to add a similar method in the Museum
class to access its featured artists through its paintings.
class Museum:
all = []
def __init__(self, name):
self.name = name
Museum.all.append(self)
def __repr__(self):
return f"Museum: {self.name}"
def paintings(self):
return [painting for painting in Painting.all if painting.museum == self]
def artists(self):
return [painting.artist for painting in self.paintings()]
Now if we have multiple artists featured at one museum, we can access them directly from the Museum
object.
met = Museum("The MET")
vangogh = Artist("Vincent van Gogh")
pollock = Artist("Jackson Pollock")
starry_night = Painting("Starry Night", "post-impressionism", vangogh, met)
convergence = Painting("Convergence", "abstract expressionism", pollock, met)
print(met.artists())
# LOG: [Artist: Vincent van Gogh,
# Artist: Jackson Pollock]
Conclusion
As you expand your knowledge of OOP, you may use object relationships in the context of databases. While this is a simple example only involving python, your future object methods may include queries to a database to access and maintain up-to-date information. Modeling object relationships allows us to build scalable, flexible, and complex systems.
Top comments (0)