Further Improving Design With Composition
00:13 This can be cumbersome and might make the composite class hard to use. One way to remedy this is by using the factory method to construct your classes. With the factory method, a class contains a method for constructing objects with the correct parameters. To create the object you just call that method, which has everything it needs to create the object.
This works, but it would be a lot nicer if we could create an
Employee by just supplying its
id. After all, the employee database contains the ID of every employee in the company, so it should be a pretty simple lookup. In this video, we’re going to further leverage our understanding of composition to make this possible. First, I want to work on the
ProductivitySystem. In its current state, the
ProductivitySystem can be instantiated as many times as we want.
This doesn’t really make much sense, as this program will only ever have one
ProductivitySystem in charge of creating role objects and tracking employees. To fix this, I’m going to make this class a singleton.
01:47 A singleton is a class that is only instantiated once, and that same instance is used everywhere else in the program. We could force this by rewriting the class, but an easier way is just to follow a naming convention.
We can say the class is internal to this module—it should only be instantiated inside of this module. I’m going to move to the bottom of this class and create a single instance of
_ProductivitySystem at the module level. After all, we can’t use the class without an instance of it. While we’re at it, let’s create two functions that can be accessed by any other module that imports this one.
What we’ve done here is create a public-facing interface for this module. That’s the
track() functions. Modules that import
productivity.py should use these functions—and these functions only—to interact with the
They will also have access to the
_ProductivitySystem object and class, but they are marked as private by appending an underscore (
_) to the beginning of their names, which tells other developers not to use them directly.
Now, I’ll create the two functions that will act as a public interface to this module. The
contacts module will follow. I’m going to mark the
AddressBook, instantiate it, and create the function to use it.
04:44 What we’ve done here is a form of abstraction. We’ve abstracted away the complex inner workings of all these systems, including instantiating them, and instead, we’ve provided one or two public functions that other modules can use to interact with these systems. It’s like a car.
05:06 I don’t understand the first thing about combustion engines—or any other car components, for that matter. Luckily, that’s all abstracted away from me and, instead, I’m given a steering wheel and some pedals to use, which in turn operates all of the complex inner car parts.
That lives in the
employees module, so I’ll move over there. Before I can start working on it, I need make sure our imports are all correct since we’ve changed some things. Instead of importing classes, those are now private, and so I’m just going to import the required functions from each of the three modules we worked on. As usual, I’ll mark this class so other developers know not to instantiate it directly. In my car analogy, that’s like the manufacturer putting a sign on the car that says, “Don’t try to ignite the engine manually.
_EmployeeDatabase class will be given an employee ID, which will then match against an employee in this list. So to make that process easier, I’m going to turn this list of dictionaries into a dictionary of dictionaries.
We no longer need a method for creating instances of
Employee, so I’ll replace this method with one called
.get_employee_info(), which will take in an ID and return information about the employee with that ID.
info will be the inner dictionary in the database above, which we can get with the dictionary’s
.get() method, passing in the
employee_id. Before we can return the dictionary we should make sure it exists, so I’ll do a quick
I’m first going to delete all of these parameters, as well as all of their associated instance attributes. Now, all we’re accepting is an ID. In order to finish constructing this
Employee object, we first need the dictionary of information associated with that employee’s ID.
The name, we can get by accessing the value associated with the
'name' key of that
info dictionary. To get the address we can use the
get_employee_address() function we imported at the top of this module, which will utilize that single
_AddressBook instance that lives in that module.
The role is similar, although this time I will mark it as private so it doesn’t show up in a dictionary generated from an
Employee object. For this, we can use the
get_role() function we imported at the top, but this function takes a role name as a string—not some ID.
And it looks like everything else in this module can stay the same. It’s time to move over to
program.py and finish this off. We have to make some pretty big changes to this file, so I’m just going to clear it and start fresh.
Just like before, we’re going to utilize the
json module to represent an
Employee object in JSON format. Then, we need to import the necessary functions, objects, and classes we’ll use to write the main program instructions.
track are functions,
employee_database is the database object, and
Employee is the class which can create
Employee objects given just an employee ID. Just like before, I’m going to write the one-liner method that will take a dictionary and print it in JSON format.
11:56 We could have actually made this method into a property since it’s not accepting any arguments, but I’ll be like a math book, and I’ll say that that’s left as an exercise for the reader—or I suppose, the viewer.
The last thing I want to do is grab an employee from the database and print out information about them. I’m going to grab the
TemporarySecretary object, which is employee with ID
5 in the database.
12:56 Okay! And this is where I reveal that I made a pretty big mistake. You might have caught this as we’ve been going along, but if not, that’s totally fine. Just follow along with me and I’ll show you how to fix it.
For one, we need to make sure that we have a valid
_EmployeeDatabase object in this module. The
program module relies on this to get a list of employees, but if we don’t have this object in the first place, we can’t get the list.
We also don’t need these system objects anymore since our
_EmployeeDatabase is no longer in charge of handling the things we obtained from them. In its current state, these would just give us exceptions because we don’t have imports for them anymore.
14:04 That’s just a simple naming mistake, which happens all the time. You changed the name of one variable in one place, and you forget to change it somewhere else. There we go. That should work now. Let’s try this out.
Great! It looks like we’re getting all of the right output, and if I scroll down here, you’ll see that we’re even printing information about employee
5 in JSON format. Before I close off this lengthy video, I want to point out how composition is being used in this modified design.
14:59 whereas the role and payroll objects provide extra functionality—notably, tracking productivity and calculating payroll. And along the way, we turned those classes into singletons and created public-facing interfaces in the form of functions that can be used to access that functionality.
Become a Member to join the conversation.