What is the difference between abstraction and encapsulation? Define both terms and then discuss the difference. Provide examples to support your definitions and discussion.
Encapsulation hides data, sealing it away from external use. This helps with loose coupling. It prevents data from "bleeding" from one part of the application into others, or being accessible in places it should not.
Abstraction defines what features a class provides and what services it can perform. Abstractions hide away how the code functions, so the programmer is only concerned with the results. If abstractions are used properly, the entire foundational data structure can be changed without affecting code relying on that class.
Abstraction defines the "face" of the class, while encapsulation prevents the internals from being seen.
A simple example of the two in use could be the idea of building a house. The abstraction is telling the carpenter to build the house, while the encapsulation is him preventing you from trying to make modifications with his tools. The carpenter knows he can't work well if the owner could walk up and use his internal tools (hammer, nails, wood, etc) without supervision, so he hides them. But, he knows the owner wants some input, so he opens himself up to orders such as "build the wall there" or "make a roof like this". This is the abstraction, the conduit through which the owner can control the building of the house.
A company has asked us to design a payroll system that will pay employees for the work they perform each month. Using a level of abstraction, similar to that shown in Chapter 1 of the textbook and as shown on slide 6 of lecture 2, develop a design for this system using the functional decomposition approach. You can assume the existence of a database that contains all of the information you need on the employees. First, describe the functional decomposition approach, discuss what assumptions you have made concerning this application domain, and then present your design.
The functional decomposition approach mirrors the actions one would take doing this by hand. Steps are broken down into smaller components until they can be solved simply. These steps are then executed in order to do the required task.
Assumptions:
- Database of employees, with details on hours worked and pay-per-hour for each
- The ability for the system to print checks
Functional decomposition (pay employees by month)
- connect to database
- collect all employees
- start at employee one
- get hours worked and pay rate from database for this employee
- multiply these and store the value
- print check to the employee (using the name stored in the database), with the amount calculated in the previous step (5)
- are there any employees left? if so, move to the next one and start back at 4
- work complete
Now, develop a design for the payroll system using the object-oriented approach, keeping in mind the points discussed on slides 22-27 of lecture 2. Identify the classes you would include in your design and their responsibilities. (As before, you can assume the existence of a database and that you'll be able to create objects based on the information stored in that database.) Then, identify what objects you would instantiate and in what order and how they would work together to fulfill the responsibilities associated with the payroll system.
EmployeeDatabase
static getEmployees()
// connects to the database and returns a collection of employees
// this would likely be through a series of objects dealing with db
// interaction, but that seems like too much detail here
Employee
getMonthlyHours(month)
// returns number of hours worked for a month
getHourlyRate()
// returns the rate for the employee
getMonthlyPay(month) {
return self.getMonthlyHours(month) * self.getHourlyRate();
}
Payer
static pay(employee) {
send_payment(employee.getMonthlyPay( Today.month ));
}
send_payment()
// some method that does the actual check writing (or deposit, whatever)
// this also seems out of scope
The main execution to accomplish this would then be something simple, like:
for employee in EmployeeDatabase.getEmployees() {
Payer.pay(employee)
}
Imagine that your customer has submitted the following change request: In December, managers receive a 25% bonus and front-line employees receive a 15% bonus on top of their normal monthly pay. Show how this request alters your two designs. You do not need to recreate each design from scratch to handle this new requirement, simply discuss how each design above would be changed to handle this change request.
- connect to database
- collect all employees
- start at employee one
- get hours worked and pay rate from database for this employee
- multiply these and store the value
- check if the month is december
- if it is, check if the employee is a manager
- if so, multiply the stored value by 1.25
- if not, check if they are a frontline employee
- if so, multiply the stored value by 1.15
- print check to the employee (using the name stored in the database), using the stored value
- are there any employees left? if so, move to the next one and start back at 4
- work complete
To handle this, we could create subclasses of Employee for the new types, and modify getMonthlyPay() to handle these special cases:
class Manager extends Employee {
getMonthlyPay(month) {
if (month == December){
return 1.25*super(month);
} else {
super(month)
}
}
}
class FrontLine extends Employee {
getMonthlyPay(month) {
if (month == December){
return 1.15*super(month);
} else {
super(month)
}
}
}
Or, if we didn't mind re-working the original classes, we could do the following:
class Employee {
BonusModifier = 1.0
getMonthlyPay(month) {
payment = self.getMonthlyHours(month) * self.getHourlyRate();
if (month == December){
return BonusModifier*payment;
}
return payment;
}
}
class Manager extends Employee {
BonusModifier = 1.25;
}
class FrontLine extends Employee {
BonusModifier = 1.15;
}