--- title: "Jam 07 - Exercise 4: Employee Hierarchy & Inheritance" tags: - 2 ๐Ÿ“ in writing - 3 ๐Ÿงช in testing - 4 ๐Ÿฅณ done - inheritance - interfaces - polymorphism - uml - abstract-classes - junit --- <!-- markdownlint-disable line-length single-h1 no-inline-html --> <!-- markdownlint-configure-file { "ul-indent": { "indent": 4 }, "link-fragments": {"ignore_case": true} } --> {%hackmd dJZ5TulxSDKme-3fSY4Lbw %} # Exercise 4 - Employee Hierarchy & Inheritance ## Overview - Exercise 4 In this exercise, you'll create your first inheritance hierarchy by implementing different types of employees in our retail system. This builds on your experience with interfaces from Exercise 1 and introduces you to inheritance, abstract classes, and polymorphic behavior. The employee hierarchy will be crucial for controlling access to different parts of the system. :::info ๐Ÿ”‘ **Key Concepts** - Abstract classes and inheritance - Method overriding - Protected vs private access - Constructor chaining - Polymorphic behavior - Role-based access control ::: ## The Problem - Exercise 4 Our retail system needs to handle different types of employees (Cashiers and Managers) who have different permissions and responsibilities. While each role has unique capabilities, they all share common attributes and behaviors. This is a perfect use case for inheritance! The employee hierarchy will be used to control access to different parts of the system. For example: - Only logged-in employees can access the register - Only managers have administrative privileges - Different roles have different access to system features **Requirements**: 1. Create an abstract `Employee` class with common attributes and behaviors 2. Implement role-specific subclasses with appropriate permissions 3. Add login/logout functionality 4. Implement the `Searchable` interface in the hierarchy 5. Write test cases to validate the inheritance behavior 6. Ensure permissions align with system access requirements This scenario demonstrates why inheritance is valuable - it allows us to share common code while allowing each role to have its own specialized behavior and control over system access. ## Understanding Inheritance and Access Control Inheritance allows us to create a hierarchy of classes where child classes (subclasses) inherit attributes and behaviors from their parent class (superclass). Think of it like a family tree - children inherit traits from their parents but can also have their own unique characteristics. For example, our `Employee` hierarchy will work like this: - `Employee` (abstract parent class) - Common attributes: id, name, hireDate, loggedIn - Common behaviors: login/logout, toString - Abstract methods: permission checks - Access controls: canAccessRegister, hasAdminPrivileges - `Cashier` (concrete child class) - Inherits all Employee attributes/behaviors - Implements permission methods for cashier role - Can access register but not admin features - `Manager` (concrete child class) - Inherits all Employee attributes/behaviors - Implements permission methods for manager role - Has full system access and admin privileges ## Step 1: Create the Employee Abstract Class Design and implement the `Employee` abstract class according to this UML: ```plantuml @startuml +abstract class Employee { -id: String -name: String -hireDate: LocalDate -loggedIn: boolean #Employee(id: String, name: String, hireDate: LocalDate) +isLoggedIn(): boolean +login(): boolean +logout(): boolean +{abstract} canAccessRegister(): boolean +{abstract} hasAdminPrivileges(): boolean +toString(): String } @enduml ``` Here's the the UML source code if you want a copy/pasteable list of method names, etc.: ```text +abstract class Employee { -id: String -name: String -hireDate: LocalDate -loggedIn: boolean #Employee(id: String, name: String, hireDate: LocalDate) +isLoggedIn(): boolean +login(): boolean +logout(): boolean +{abstract} canAccessRegister(): boolean +{abstract} hasAdminPrivileges(): boolean +toString(): String } ``` Create this `public abstract` class in a new file called `Employee.java` in your `jam07` package. Note that: - The constructor is protected (#) to prevent direct instantiation - Review all instance variables and decide if they should be set to a value, even if they are not a constructor parameter - At this stage, most getters and setters will not be shown on the UMLs. You'll need to decide what makes sense as `final` and what needs `get` and what needs `set`. (the exception is `isLoggedIn` is technically a getter - see the note below) - Here are some implementation details for some of the methods: - State management methods: - `isLoggedIn()`: Returns current login state (is technically a getter) - `login()`: Sets loggedIn to true if not already logged in - `logout()`: Sets loggedIn to false if currently logged in - Abstract methods: - `canAccessRegister()`: Returns true if employee can access the register - `hasAdminPrivileges()`: Returns true if employee has admin privileges - All non-abstract methods need default implementations :::info ๐Ÿ” **Boolean Getter Convention** Getters for boolean fields typically follow a different naming convention than getters for other types. Instead of `getLoggedIn()`, we use `isLoggedIn()`. This makes the code read more naturally - `employee.isLoggedIn()` is clearer than `employee.getLoggedIn()`. This is a widely-followed convention in Java. For boolean getters, we use: - `is` prefix for simple boolean properties (e.g., `isLoggedIn`) - `can` prefix for permission/ability checks (e.g., `canAccessRegister`) - `has` prefix for possession/state checks (e.g., `hasAdminPrivileges`) ::: ## Step 2: Implement the Cashier Class Create the `Cashier` class that extends `Employee`: ```plantuml @startuml +class Cashier { +Cashier(id: String, name: String, hireDate: LocalDate) +canAccessRegister(): boolean +hasAdminPrivileges(): boolean +toString(): String } @enduml ``` Here's the the UML source code if you want a copy/pasteable list of method names, etc.: ```text +class Cashier { +Cashier(id: String, name: String, hireDate: LocalDate) +canAccessRegister(): boolean +hasAdminPrivileges(): boolean +toString(): String } ``` Key points about the implementation: - Use `super()` to call the parent constructor - Override abstract methods with appropriate permissions - Override `toString()` to include role information - Cashiers can: - Access the register - Cannot access admin features ## Step 3: Implement the Manager Class Create the `Manager` class that extends `Employee`: ```plantuml @startuml +class Manager { +Manager(id: String, name: String, hireDate: LocalDate) +canAccessRegister(): boolean +hasAdminPrivileges(): boolean +toString(): String } @enduml ``` Here's the the UML source code if you want a copy/pasteable list of method names, etc.: ```text +class Manager { +Manager(id: String, name: String, hireDate: LocalDate) +canAccessRegister(): boolean +hasAdminPrivileges(): boolean +toString(): String } ``` Key points about the implementation: - Use `super()` to call the parent constructor - Override abstract methods with appropriate permissions - Override `toString()` to include role information. Below is one way to handle the child `toString()`. Feel free to use it or not, but *you should understand it* - Managers can: - Access the register - Access admin features ```java @Override public String toString() { return String.format("Manager[%s]", super.toString()); } ``` ## Step 4: Write Test Cases Now that you have your `Employee` hierarchy implemented, it's time to test it. While you've already written tests for regular classes, testing inheritance introduces some new considerations. ### Creating the Test Class Let's use IntelliJ's test generator to create our test structure: 1. Right-click on the `Employee` class in the Project view 2. Select "Generate" (or press โŒ˜N on Mac, Alt+Insert on Windows) 3. Choose "Test" from the menu 4. In the "Create Test" dialog: - Name: `EmployeeTest` - Testing library: JUnit 5 - Destination package: `jam07` - Check "Generate setUp()" to create the `@BeforeEach` method - Select these key methods to test: - isLoggedIn() - login() - logout() - canAccessRegister() - hasAdminPrivileges() - toString() IntelliJ will create a test class with basic method stubs. Here's how to think about implementing each test: ### Test Structure Your test class needs these key components: ```java private Employee employee; // Using Cashier as concrete implementation private Cashier cashier; private Manager manager; private LocalDate hireDate; @BeforeEach void setUp() { hireDate = LocalDate.of(2024, 1, 1); employee = new Cashier("E001", "John Doe", hireDate); // Using Cashier as concrete implementation cashier = new Cashier("C001", "Jane Smith", hireDate); manager = new Manager("M001", "Bob Wilson", hireDate); } ``` ### Key Areas to Test For each method, think about: 1. **Constructor Testing** - Do we need explicit constructor tests? No, because: - The constructor's behavior is verified through other tests - `isLoggedIn()` tests verify the initial loggedIn state - `toString()` tests verify all fields are properly set - Permission tests verify role-specific behavior - This is a common pattern in testing - we verify constructor behavior through the object's public methods 2. **Login/Logout Tests** - Test initial state (logged out) - Test successful login sequence - Test duplicate login attempts - Test successful logout sequence - Test duplicate logout attempts - Verify state changes through isLoggedIn() 3. **Permission Tests** - Test role-specific permissions (Cashier vs Manager) - Test polymorphic behavior through Employee references - Verify inheritance relationships - Test both positive and negative cases 4. **toString Tests** - Test format for each role - Verify all required fields are shown - Check role information is included - Verify consistent formatting ### Example Test Method Here's one example to get you started: ```java @Test void isLoggedIn() { // Arrange - No additional setup needed as we're testing initial state // Act - No action needed as we're testing initial state // Assert assertFalse(employee.isLoggedIn()); assertFalse(cashier.isLoggedIn()); assertFalse(manager.isLoggedIn()); // Arrange - No additional setup needed // Act employee.login(); // Assert assertTrue(employee.isLoggedIn()); } ``` This example shows how to: - Test initial state for all employee types - Test state change after login - Use clear comments to separate test phases - Follow the Arrange-Act-Assert pattern Now implement the remaining tests using the method stubs given to you by the generator as a guide. ## Exercise 4 Checkpoint > ๐Ÿ” **Checkpoint** > > Before proceeding, verify: > > **Employee Hierarchy** > > - `Employee` abstract class is properly defined > - All subclasses extend `Employee` correctly > - Constructor chaining works properly > - Protected access is used appropriately > > **Role Implementation** > > - Each role has correct permissions > - Manager has additional capabilities > - toString() includes role information > - Permissions are properly implemented > > **Testing** > > - All test cases are implemented and pass > - Tests cover common and role-specific behavior > - Tests verify inheritance relationships > - Tests verify permissions > - Tests follow best practices ## Save Your Work - Exercise 4 **Run all tests to verify your implementation**: ```bash # Right click on the test folder in IntelliJ # Click "Run Tests in 'csci205_jams'" # Ensure all tests pass ``` **Stage your changes**: ```bash git add src/main/java/jam07/Employee.java src/main/java/jam07/Cashier.java src/main/java/jam07/Manager.java src/test/java/jam07/EmployeeTest.java ``` **Verify what files are uncommitted**: ```bash git status ``` **Commit your work**: ```bash git commit -m "jam07: Implement Employee hierarchy with role-based permissions" ``` :::success ๐Ÿ”‘ **Key Takeaways** - Abstract classes define common behavior - Inheritance enables code reuse - Method overriding allows specialized behavior - Protected access supports inheritance - Testing verifies both common and specialized behavior - Employee roles control system access :::