diff --git a/src/main/java/com/amigoscode/_3_oop/_1_encapsulation/BankAccount.java b/src/main/java/com/amigoscode/_3_oop/_1_encapsulation/BankAccount.java index 45ab6df..7c7c0e2 100644 --- a/src/main/java/com/amigoscode/_3_oop/_1_encapsulation/BankAccount.java +++ b/src/main/java/com/amigoscode/_3_oop/_1_encapsulation/BankAccount.java @@ -18,17 +18,29 @@ public class BankAccount { // - accountNumber (String) // - balance (double) // - ownerName (String) + private String accountNumber; + private double balance; + private String ownerName; // TODO: 2 - Create a constructor that takes accountNumber, ownerName, // and an initialBalance. Validate that initialBalance >= 0, // throwing IllegalArgumentException if not. Assign all fields. + public BankAccount(String accountNumber, String ownerName, double initialBalance){ + if(initialBalance <0) throw new IllegalArgumentException("Balance should be greater or equal to 0!"); + this.accountNumber=accountNumber; + this.ownerName = ownerName; + this.balance = initialBalance; + } + // TODO: 3 - Create a getter method for balance (getBalance). // Do NOT create a setter for balance — it should only change // through deposit() and withdraw(). - + public double getBalance(){ + return this.balance; + } // TODO: 4 - Create a deposit(double amount) method. // - If amount <= 0, throw IllegalArgumentException with message @@ -36,7 +48,13 @@ public class BankAccount { // - Add amount to balance // - Call the private logTransaction() helper with a descriptive message // - Return the new balance + public double deposit(double amount){ + if(amount<=0) throw new IllegalArgumentException("Deposit amount must be positive"); + this.balance += amount; + logTransaction("$%s was added successfully to account: %s".formatted(amount, accountNumber)); + return this.balance; + } // TODO: 5 - Create a withdraw(double amount) method. // - If amount <= 0, throw IllegalArgumentException with message @@ -46,32 +64,50 @@ public class BankAccount { // - Subtract amount from balance // - Call the private logTransaction() helper with a descriptive message // - Return the new balance + public double withdraw(double amount){ + if(amount<=0) throw new IllegalArgumentException("Withdrawal amount must be positive"); + if(amount > balance) throw new IllegalStateException("Insufficient funds"); + this.balance -= amount; + logTransaction("$%s was removed successfully to account: %s".formatted(amount, accountNumber)); + return this.balance; + } // TODO: 6 - Override toString() to return a string in the format: // "BankAccount{accountNumber='XXX', ownerName='XXX', balance=XXX}" + @Override + public String toString() { + return "BankAccount{" + + "accountNumber='" + accountNumber + '\'' + + ", balance=" + balance + + ", ownerName='" + ownerName + '\'' + + '}'; + } + // TODO: 7 - Create a private helper method logTransaction(String message) // that prints the message to the console prefixed with // "[Transaction Log] ". This method should NOT be accessible // from outside the class. - + private void logTransaction(String message){ + System.out.println("[Transaction Log]: %s".formatted(message)); + } public static void main(String[] args) { // Uncomment and test after completing the TODOs: - // BankAccount account = new BankAccount("ACC-001", "Alice", 1000.0); - // System.out.println(account); - // System.out.println("Balance: " + account.getBalance()); - // - // account.deposit(500.0); - // System.out.println("After deposit: " + account.getBalance()); - // - // account.withdraw(200.0); - // System.out.println("After withdrawal: " + account.getBalance()); + BankAccount account = new BankAccount("ACC-001", "Alice", 1000.0); + System.out.println(account); + System.out.println("Balance: " + account.getBalance()); // + account.deposit(500.0); + System.out.println("After deposit: " + account.getBalance()); + + account.withdraw(200.0); + System.out.println("After withdrawal: " + account.getBalance()); + // // These should throw exceptions: - // // account.deposit(-100); - // // account.withdraw(999999); + //account.deposit(-100); + //account.withdraw(999999); } } diff --git a/src/main/java/com/amigoscode/_3_oop/_1_encapsulation/ImmutablePerson.java b/src/main/java/com/amigoscode/_3_oop/_1_encapsulation/ImmutablePerson.java index 69223bf..6126c3b 100644 --- a/src/main/java/com/amigoscode/_3_oop/_1_encapsulation/ImmutablePerson.java +++ b/src/main/java/com/amigoscode/_3_oop/_1_encapsulation/ImmutablePerson.java @@ -19,45 +19,77 @@ public final class ImmutablePerson { // - name (String) // - age (int) // - email (String) + private final String name; + private final int age; + private final String email; // TODO: 2 - Create a constructor that takes name, age, and email // and assigns them to the fields. Since the fields are final, // they can only be set here. - + public ImmutablePerson(String name, int age, String email){ + this.name = name; + this.age = age; + this.email = email; + } // TODO: 3 - Create getter methods for all three fields: // getName(), getAge(), getEmail(). // Do NOT create any setter methods — this class is immutable. + public String getName(){ + return name; + } + public int getAge(){ + return age; + } + + public String getEmail() { + return email; + } // TODO: 4 - Create a withName(String newName) method that returns // a NEW ImmutablePerson with the changed name but the same // age and email. The original object must remain unchanged. + public ImmutablePerson withName(String newName){ + return new ImmutablePerson(newName, age, email); + } + // TODO: 5 - Create a withAge(int newAge) method that returns // a NEW ImmutablePerson with the changed age but the same // name and email. The original object must remain unchanged. - + public ImmutablePerson withAge(int newAge){ + return new ImmutablePerson(name, newAge, email); + } // TODO: 6 - Override toString() to return a string in the format: // "ImmutablePerson{name='XXX', age=XXX, email='XXX'}" + @Override + public String toString() { + return "ImmutablePerson{" + + "name='" + name + '\'' + + ", age=" + age + + ", email='" + email + '\'' + + '}'; + } + public static void main(String[] args) { // Uncomment and test after completing the TODOs: - // ImmutablePerson person = new ImmutablePerson("Alice", 30, "alice@example.com"); - // System.out.println(person); - // - // // withName returns a NEW object — original is unchanged - // ImmutablePerson renamed = person.withName("Bob"); - // System.out.println("Original: " + person); - // System.out.println("Renamed: " + renamed); - // - // // withAge returns a NEW object — original is unchanged - // ImmutablePerson aged = person.withAge(31); - // System.out.println("Original: " + person); - // System.out.println("Aged: " + aged); + ImmutablePerson person = new ImmutablePerson("Alice", 30, "alice@example.com"); + System.out.println(person); + + // withName returns a NEW object — original is unchanged + ImmutablePerson renamed = person.withName("Bob"); + System.out.println("Original: " + person); + System.out.println("Renamed: " + renamed); + + // withAge returns a NEW object — original is unchanged + ImmutablePerson aged = person.withAge(31); + System.out.println("Original: " + person); + System.out.println("Aged: " + aged); } } diff --git a/src/main/java/com/amigoscode/_3_oop/_2_inheritance/Animal.java b/src/main/java/com/amigoscode/_3_oop/_2_inheritance/Animal.java index 3eac414..387211f 100644 --- a/src/main/java/com/amigoscode/_3_oop/_2_inheritance/Animal.java +++ b/src/main/java/com/amigoscode/_3_oop/_2_inheritance/Animal.java @@ -17,31 +17,48 @@ public class Animal { // TODO: 1 - Declare two protected fields: // - name (String) // - age (int) - + protected String name; + protected int age; // TODO: 2 - Create a constructor that takes name and age // and assigns them to the fields. - + public Animal(String name, int age){ + this.name = name; + this.age = age; + } // TODO: 3 - Create a makeSound() method that prints: // "Some generic animal sound" // Subclasses will override this with their specific sound. - + public void makeSound(){ + System.out.println("Some generic animal sound"); + } // TODO: 4 - Create an eat(String food) method that prints: // " is eating " // For example: "Buddy is eating kibble" + public void eat(String food){ + String msg = "%s is eating %s".formatted(this.name, food); + System.out.println(msg); + } // TODO: 5 - Override toString() to return: // "Animal{name='XXX', age=XXX}" + @Override + public String toString() { + return "Animal{" + + "name='" + name + '\'' + + ", age=" + age + + '}'; + } public static void main(String[] args) { // Uncomment and test after completing the TODOs: - // Animal animal = new Animal("Generic", 5); - // System.out.println(animal); - // animal.makeSound(); - // animal.eat("food"); + Animal animal = new Animal("Generic", 5); + System.out.println(animal); + animal.makeSound(); + animal.eat("food"); } } diff --git a/src/main/java/com/amigoscode/_3_oop/_2_inheritance/Dog.java b/src/main/java/com/amigoscode/_3_oop/_2_inheritance/Dog.java index 9d7d696..ca73dcb 100644 --- a/src/main/java/com/amigoscode/_3_oop/_2_inheritance/Dog.java +++ b/src/main/java/com/amigoscode/_3_oop/_2_inheritance/Dog.java @@ -16,43 +16,61 @@ // TODO: 1 - Make this class extend Animal. // Change the class declaration so Dog inherits from Animal. -public class Dog { +public class Dog extends Animal{ // TODO: 2 - Add a private field: // - breed (String) + private String breed; // TODO: 3 - Create a constructor that takes name, age, and breed. // Call super(name, age) to initialize the parent fields, // then set the breed field. - + public Dog(String name, int age, String breed){ + super(name, age); + this.breed = breed; + } // TODO: 4 - Override the makeSound() method to print: // " says: Woof! Woof!" // Use the @Override annotation. You can access `name` because // it is a protected field in Animal. - + @Override + public void makeSound(){ + String sound = "%s says: Woof! Woof!".formatted(name); + System.out.println(sound); + } // TODO: 5 - Add a fetch(String item) method specific to Dog. // This method should print: // " fetches the !" // This method does not exist in Animal — it is unique to Dog. - + public void fetch(String item){ + String msg = "%s fetches the %s".formatted(name, item); + System.out.println(msg); + } // TODO: 6 - Override toString() to return: // "Dog{name='XXX', age=XXX, breed='XXX'}" - + @Override + public String toString() { + return "Dog{" + + "name='" + name + '\'' + + ", age=" + age + + ", breed='" + breed + '\'' + + '}'; + } public static void main(String[] args) { // Uncomment and test after completing the TODOs: - // Dog dog = new Dog("Buddy", 3, "Golden Retriever"); - // System.out.println(dog); - // dog.makeSound(); - // dog.eat("kibble"); // inherited from Animal - // dog.fetch("tennis ball"); // specific to Dog + Dog dog = new Dog("Buddy", 3, "Golden Retriever"); + System.out.println(dog); + dog.makeSound(); + dog.eat("kibble"); // inherited from Animal + dog.fetch("tennis ball"); // specific to Dog // // // Polymorphism: a Dog IS-AN Animal - // Animal animal = new Dog("Rex", 5, "German Shepherd"); - // animal.makeSound(); // calls Dog's overridden version + Animal animal = new Dog("Rex", 5, "German Shepherd"); + animal.makeSound(); // calls Dog's overridden version } } diff --git a/src/main/java/com/amigoscode/_3_oop/_2_inheritance/ElectricCar.java b/src/main/java/com/amigoscode/_3_oop/_2_inheritance/ElectricCar.java index b2483db..67881cf 100644 --- a/src/main/java/com/amigoscode/_3_oop/_2_inheritance/ElectricCar.java +++ b/src/main/java/com/amigoscode/_3_oop/_2_inheritance/ElectricCar.java @@ -14,47 +14,70 @@ */ // TODO: 1 - Make this class extend Vehicle. -public class ElectricCar { +public class ElectricCar extends Vehicle{ // TODO: 2 - Add a private field: // - batteryLevel (int) representing percentage from 0 to 100 - + private int batteryLevel; // TODO: 3 - Create a constructor that takes make, model, year, and batteryLevel. // Call super(make, model, year) first, then set the batteryLevel field. - + public ElectricCar(String make, String model, int year, int batteryLevel){ + super(make, model, year); + this.batteryLevel = batteryLevel; + } // TODO: 4 - Override the start() method to print: // " electric motor starting silently..." // Use the @Override annotation. Access make and model from // the parent class (they are protected). - + @Override + public void start(){ + String msg = "%s %s electric motor starting silently...".formatted(make, model); + System.out.println(msg); + } // TODO: 5 - Add a charge(int percent) method specific to ElectricCar. // - Add percent to batteryLevel // - Cap batteryLevel at 100 (use Math.min) // - Print: "Charging... Battery now at %" + public void charge(int percent){ + this.batteryLevel = Math.min(batteryLevel + percent, 100); + System.out.println("Charging... Battery not at %s%%".formatted(batteryLevel)); + } // TODO: 6 - Add a getBatteryStatus() method that returns a String: // "Battery: %" // Also override toString() to return: // "ElectricCar{make='XXX', model='XXX', year=XXX, batteryLevel=XXX%}" + public String getBatteryStatus(){ + return "Battery: %s%%".formatted(this.batteryLevel); + } + @Override + public String toString() { + return "ElectricCar{" + + "make='" + make + '\'' + + ", model='" + model + '\'' + + ", year=" + year + + ", batteryLevel=" + batteryLevel + '%' + + '}'; + } public static void main(String[] args) { // Uncomment and test after completing the TODOs: - // ElectricCar tesla = new ElectricCar("Tesla", "Model 3", 2024, 85); - // System.out.println(tesla); - // tesla.start(); // overridden — silent start - // System.out.println(tesla.getInfo()); // inherited from Vehicle - // System.out.println(tesla.getBatteryStatus()); - // - // tesla.charge(20); - // System.out.println(tesla.getBatteryStatus()); + ElectricCar tesla = new ElectricCar("Tesla", "Model 3", 2024, 85); + System.out.println(tesla); + tesla.start(); // overridden — silent start + System.out.println(tesla.getInfo()); // inherited from Vehicle + System.out.println(tesla.getBatteryStatus()); + + tesla.charge(20); + System.out.println(tesla.getBatteryStatus()); // // // Polymorphism: an ElectricCar IS-A Vehicle - // Vehicle vehicle = new ElectricCar("Rivian", "R1T", 2025, 60); - // vehicle.start(); // calls ElectricCar's overridden version + Vehicle vehicle = new ElectricCar("Rivian", "R1T", 2025, 60); + vehicle.start(); // calls ElectricCar's overridden version } } diff --git a/src/main/java/com/amigoscode/_3_oop/_2_inheritance/Vehicle.java b/src/main/java/com/amigoscode/_3_oop/_2_inheritance/Vehicle.java index b168453..edd923a 100644 --- a/src/main/java/com/amigoscode/_3_oop/_2_inheritance/Vehicle.java +++ b/src/main/java/com/amigoscode/_3_oop/_2_inheritance/Vehicle.java @@ -18,31 +18,51 @@ public class Vehicle { // - make (String) e.g., "Toyota" // - model (String) e.g., "Camry" // - year (int) e.g., 2024 - + protected String make; + protected String model; + protected int year; // TODO: 2 - Create a constructor that takes make, model, and year // and assigns them to the fields. - + public Vehicle(String make, String model, int year){ + this.make=make; + this.model=model; + this.year=year; + } // TODO: 3 - Create a start() method that prints: // " engine is starting... Vroom!" // For example: "Toyota Camry engine is starting... Vroom!" + public void start(){ + String msg = "%s %s engine is starting... Vroom!".formatted(make, model); + System.out.println(msg); + } // TODO: 4 - Create a getInfo() method that returns a String: // " " // For example: "2024 Toyota Camry" - + public String getInfo(){ + return "%s %s %s".formatted(year, make, model); + } // TODO: 5 - Override toString() to return: // "Vehicle{make='XXX', model='XXX', year=XXX}" + @Override + public String toString() { + return "Vehicle{" + + "make='" + make + '\'' + + ", model='" + model + '\'' + + ", year=" + year + + '}'; + } public static void main(String[] args) { // Uncomment and test after completing the TODOs: - // Vehicle vehicle = new Vehicle("Toyota", "Camry", 2024); - // System.out.println(vehicle); - // vehicle.start(); - // System.out.println(vehicle.getInfo()); + Vehicle vehicle = new Vehicle("Toyota", "Camry", 2024); + System.out.println(vehicle); + vehicle.start(); + System.out.println(vehicle.getInfo()); } } diff --git a/src/main/java/com/amigoscode/_3_oop/_3_abstractclasses/Circle.java b/src/main/java/com/amigoscode/_3_oop/_3_abstractclasses/Circle.java index 5b00683..869fd5e 100644 --- a/src/main/java/com/amigoscode/_3_oop/_3_abstractclasses/Circle.java +++ b/src/main/java/com/amigoscode/_3_oop/_3_abstractclasses/Circle.java @@ -13,33 +13,43 @@ */ // TODO: 1 - Make this class extend Shape. -public class Circle { +public class Circle extends Shape { // TODO: 2 - Declare a private field: // - radius (double) - + private double radius; // TODO: 3 - Create a constructor that takes a radius. // Validate that radius > 0, throwing IllegalArgumentException if not. // Assign the field. - + public Circle(double radius){ + if(radius <= 0) throw new IllegalArgumentException("Radius must be a positive number"); + this.radius = radius; + } // TODO: 4 - Implement the area() method from Shape. // Formula: Math.PI * radius * radius // Use the @Override annotation. - + @Override + public double area() { + return Math.PI * radius * radius; + } // TODO: 5 - Implement the perimeter() method from Shape. // Formula: 2 * Math.PI * radius // Use the @Override annotation. - + @Override + public double perimeter() { + return 2 * Math.PI * radius; + } public static void main(String[] args) { // Uncomment and test after completing the TODOs: - // Circle circle = new Circle(5.0); - // System.out.println("Area: " + circle.area()); - // System.out.println("Perimeter: " + circle.perimeter()); - // circle.describe(); // inherited concrete method from Shape - // System.out.println(circle); + Circle circle = new Circle(5.0); + System.out.println("Area: " + circle.area()); + System.out.println("Perimeter: " + circle.perimeter()); + circle.describe(); // inherited concrete method from Shape + System.out.println(circle); + } } diff --git a/src/main/java/com/amigoscode/_3_oop/_3_abstractclasses/Employee.java b/src/main/java/com/amigoscode/_3_oop/_3_abstractclasses/Employee.java index 41fb45e..cf6d370 100644 --- a/src/main/java/com/amigoscode/_3_oop/_3_abstractclasses/Employee.java +++ b/src/main/java/com/amigoscode/_3_oop/_3_abstractclasses/Employee.java @@ -5,11 +5,11 @@ /** * Exercise: Abstract Classes - Employee Hierarchy - * + *

* Build an employee hierarchy using abstract classes. The abstract Employee * defines common behavior, while FullTimeEmployee and ContractEmployee * provide different pay calculation strategies. - * + *

* Key concepts: * - Abstract class with both abstract and concrete methods * - Multiple subclasses with different implementations @@ -21,22 +21,39 @@ // - name (String) // - baseSalary (double) // Create a constructor that takes both fields and assigns them. -class Employee { +abstract class Employee { + protected String name; + protected double baseSalary; + public Employee(String name, double baseSalary){ + this.name = name; + this.baseSalary = baseSalary; + } // TODO: 2 - Declare an abstract method: double calculatePay() // Each subclass will compute pay differently. - + public abstract double calculatePay(); // TODO: 3 - Create a concrete method: String getDetails() // Return: " - Pay: $" // Use String.format("%.2f", calculatePay()) for formatting. - + public String getDetails(){ + return "%s - Pay: $<%s>".formatted(name, String.format("%.2f", calculatePay())); + } } // TODO: 4 - Create a FullTimeEmployee class that extends Employee. // - Constructor takes name and baseSalary, calls super(name, baseSalary) // - Implement calculatePay() to simply return baseSalary +class FullTimeEmployee extends Employee{ + public FullTimeEmployee(String name, double baseSalary){ + super(name, baseSalary); + } + @Override + public double calculatePay() { + return baseSalary; + } +} // TODO: 5 - Create a ContractEmployee class that extends Employee. // - Add two private fields: hourlyRate (double) and hoursWorked (int) @@ -44,6 +61,21 @@ class Employee { // (pass name and 0.0 as baseSalary to super) // - Implement calculatePay() to return hourlyRate * hoursWorked +class ContractEmployee extends Employee{ + private double hourlyRate; + private int hoursWorked; + + public ContractEmployee(String name, double hourlyRate, int hoursWorked){ + super(name, 0.0); + this.hourlyRate = hourlyRate; + this.hoursWorked = hoursWorked; + } + + @Override + public double calculatePay() { + return hourlyRate * hoursWorked; + } +} // TODO: 6 - In the EmployeeDemo class below, complete the main method: // - Create a List with at least one FullTimeEmployee @@ -57,5 +89,28 @@ class Employee { class EmployeeDemo { public static void main(String[] args) { // Complete TODOs 6 and 7 here + List employees = new ArrayList<>(); + employees.add(new FullTimeEmployee("John", 3000.0)); + employees.add(new ContractEmployee("Bob", 100.0, 160)); + + for(Employee employee: employees){ + System.out.println(employee.getDetails()); + } + + Employee highestPaid = getHighestPaid(employees); + System.out.println("Highest paid: " + highestPaid.getDetails()); + } + + static Employee getHighestPaid(List employees){ + double highestSalary = 0.0; + Employee employeeWithHighestEarnings = null; + for(Employee employee : employees){ + if(employee.calculatePay() > highestSalary){ + highestSalary = employee.calculatePay(); + employeeWithHighestEarnings = employee; + } + } + + return employeeWithHighestEarnings; } } diff --git a/src/main/java/com/amigoscode/_3_oop/_3_abstractclasses/Rectangle.java b/src/main/java/com/amigoscode/_3_oop/_3_abstractclasses/Rectangle.java index 87bc713..79598a3 100644 --- a/src/main/java/com/amigoscode/_3_oop/_3_abstractclasses/Rectangle.java +++ b/src/main/java/com/amigoscode/_3_oop/_3_abstractclasses/Rectangle.java @@ -13,34 +13,49 @@ */ // TODO: 1 - Make this class extend Shape. -public class Rectangle { +public class Rectangle extends Shape{ // TODO: 2 - Declare two private fields: // - width (double) // - height (double) - + private double width; + private double height; // TODO: 3 - Create a constructor that takes width and height. // Validate that both are > 0, throwing IllegalArgumentException if not. // Assign the fields. - + public Rectangle(double width, double height){ + if(width <= 0 || height <= 0) throw new IllegalArgumentException("Width and Height must be positive numbers"); + this.width = width; + this.height = height; + } // TODO: 4 - Implement the area() method from Shape. // Formula: width * height // Use the @Override annotation. - + @Override + public double area() { + return width * height; + } // TODO: 5 - Implement the perimeter() method from Shape. // Formula: 2 * (width + height) // Use the @Override annotation. - + @Override + public double perimeter() { + return 2 * (width + height); + } public static void main(String[] args) { // Uncomment and test after completing the TODOs: - // Rectangle rect = new Rectangle(4.0, 6.0); - // System.out.println("Area: " + rect.area()); - // System.out.println("Perimeter: " + rect.perimeter()); - // rect.describe(); // inherited concrete method from Shape - // System.out.println(rect); + Rectangle rect = new Rectangle(4.0, 6.0); + System.out.println("Area: " + rect.area()); + System.out.println("Perimeter: " + rect.perimeter()); + rect.describe(); // inherited concrete method from Shape + System.out.println(rect); } + + + + } diff --git a/src/main/java/com/amigoscode/_3_oop/_3_abstractclasses/Shape.java b/src/main/java/com/amigoscode/_3_oop/_3_abstractclasses/Shape.java index 6232c4c..528150f 100644 --- a/src/main/java/com/amigoscode/_3_oop/_3_abstractclasses/Shape.java +++ b/src/main/java/com/amigoscode/_3_oop/_3_abstractclasses/Shape.java @@ -16,26 +16,35 @@ // TODO: 1 - Make this class abstract by adding the `abstract` keyword. // An abstract class cannot be instantiated directly. -public class Shape { +public abstract class Shape { // TODO: 2 - Declare an abstract method: double area() // Abstract methods have no body — just the signature followed by ; // Every subclass of Shape must implement this method. - + public abstract double area(); // TODO: 3 - Declare an abstract method: double perimeter() // Every subclass of Shape must implement this method. - + public abstract double perimeter(); // TODO: 4 - Create a concrete (non-abstract) method: void describe() // This method should print: // "This shape has area: and perimeter: " // Note: you can call abstract methods from concrete methods! // At runtime, the subclass implementation will be used. - + public void describe(){ + String description= "This shape has area: <%s> and perimeter: <%s>".formatted(area(), perimeter()); + System.out.println(description); + } // TODO: 5 - Override toString() to return: // "Shape[area=, perimeter=]" // Use String.format("%.2f", value) to format to 2 decimal places. - + @Override + public String toString() { + return "Shape[" + + "area=<" + String.format("%.2f", area()) + '>' + + ", perimeter=<" + String.format("%.2f", perimeter()) + '>' + + "]"; + } } diff --git a/src/main/java/com/amigoscode/_3_oop/_4_polymorphism/PaymentProcessor.java b/src/main/java/com/amigoscode/_3_oop/_4_polymorphism/PaymentProcessor.java index cb66c66..c86f2d8 100644 --- a/src/main/java/com/amigoscode/_3_oop/_4_polymorphism/PaymentProcessor.java +++ b/src/main/java/com/amigoscode/_3_oop/_4_polymorphism/PaymentProcessor.java @@ -24,7 +24,15 @@ // - A default method: void printReceipt(double amount) that prints: // "Receipt: $ paid via " // Default methods provide a body in the interface itself. +interface Payment { + boolean processPayment(double amount); + String getPaymentMethod(); + + default void printReceipt(double amount){ + System.out.println("$<%s> paid via <%s>".formatted(amount, getPaymentMethod())); + } +} // TODO: 2 - Create a CreditCardPayment class that implements Payment. // - Add a private field: cardNumber (String) @@ -33,7 +41,24 @@ // "Processing credit card payment of $ with card " // and return true // - Implement getPaymentMethod() to return "Credit Card" +class CreditCardPayment implements Payment{ + private String cardNumber; + + public CreditCardPayment(String cardNumber) { + this.cardNumber = cardNumber; + } + + @Override + public boolean processPayment(double amount) { + System.out.println("Processing credit card payment of $<%s> with card <%s>".formatted(amount, cardNumber)); + return true; + } + @Override + public String getPaymentMethod() { + return "Credit Card"; + } +} // TODO: 3 - Create a PayPalPayment class that implements Payment. // - Add a private field: email (String) @@ -43,6 +68,25 @@ // and return true // - Implement getPaymentMethod() to return "PayPal" +class PayPalPayment implements Payment{ + private String email; + + public PayPalPayment(String email) { + this.email = email; + } + + @Override + public boolean processPayment(double amount) { + System.out.println("Processing PayPal payment of $<%s> from <%s>".formatted(amount, email)); + return true; + } + + @Override + public String getPaymentMethod() { + return "PayPal"; + } +} + // TODO: 4 - Create a BankTransferPayment class that implements Payment. // - Add a private field: bankAccountId (String) @@ -51,20 +95,50 @@ // "Processing bank transfer of $ from account " // and return true // - Implement getPaymentMethod() to return "Bank Transfer" +class BankTransferPayment implements Payment{ + private String bankAccountId; + public BankTransferPayment(String bankAccountId) { + this.bankAccountId = bankAccountId; + } + + @Override + public boolean processPayment(double amount) { + System.out.println("Processing bank transfer of $<%s> from account <%s>".formatted(amount, bankAccountId)); + return true; + } + + @Override + public String getPaymentMethod() { + return "Bank Transfer"; + } +} // TODO: 5 - Create a PaymentProcessor class with a method: // void processAllPayments(List payments, double amount) // Iterate over the list and call processPayment(amount) on each. // After each payment, call printReceipt(amount). - +class PaymentProcessor{ + void processAllPayments(List payments, double amount){ + for(Payment payment: payments){ + payment.processPayment(amount); + payment.printReceipt(amount); + } + } +} class PaymentDemo { public static void main(String[] args) { // TODO: 6 - Create a List containing one of each payment type: // CreditCardPayment, PayPalPayment, BankTransferPayment. // Then create a PaymentProcessor and call processAllPayments(). + List payments = new ArrayList<>(); + payments.add(new CreditCardPayment("4000-5000-6000-7000")); + payments.add(new PayPalPayment("john@gmail.com")); + payments.add(new BankTransferPayment("A-1234567890")); + PaymentProcessor processor = new PaymentProcessor(); + processor.processAllPayments(payments, 50.0); // TODO: 7 - Demonstrate runtime polymorphism: // Create a Payment variable and assign different implementations to it. @@ -75,6 +149,9 @@ public static void main(String[] args) { // payment.processPayment(100.0); // payment = new PayPalPayment("user@email.com"); // payment.processPayment(200.0); - + Payment payment = new CreditCardPayment("1000-2000-3000-4000"); + payment.processPayment(500.0); + payment = new BankTransferPayment("A-0987654321"); + payment.processPayment(1000.0); } } diff --git a/src/main/java/com/amigoscode/_3_oop/_4_polymorphism/ShapeCalculator.java b/src/main/java/com/amigoscode/_3_oop/_4_polymorphism/ShapeCalculator.java index c0551bb..555afcd 100644 --- a/src/main/java/com/amigoscode/_3_oop/_4_polymorphism/ShapeCalculator.java +++ b/src/main/java/com/amigoscode/_3_oop/_4_polymorphism/ShapeCalculator.java @@ -28,15 +28,36 @@ public class ShapeCalculator { // Print: "The has an area of " // Use shape.getClass().getSimpleName() to get the class name. // Use String.format("%.2f", shape.area()) for formatting. - + public void printShapeArea(Shape shape){ + System.out.println("The <%s> has an area of <%s>" + .formatted(shape.getClass().getSimpleName(), + String.format("%.2f", shape.area()) + ) + ); + } // TODO: 2 - Create a method: double totalArea(List shapes) // Iterate over all shapes and return the sum of their areas. - + public double totalArea(List shapes){ + double sumOfAreas=0.0; + for (Shape shape: shapes){ + sumOfAreas += shape.area(); + } + return sumOfAreas; + } // TODO: 3 - Create a method: Shape largestShape(List shapes) // Return the shape with the largest area. // If the list is empty, return null. + public Shape largestShape(List shapes){ + Shape largestShape = null; + for(Shape shape: shapes){ + if(largestShape==null || shape.area() > largestShape.area()){ + largestShape = shape; + } + } + return largestShape; + } // TODO: 4 - Create a method: String describeShape(Shape shape) @@ -46,13 +67,24 @@ public class ShapeCalculator { // (just return "Circle detected with area: " + c.area()) // - If shape is a Rectangle r: return "Rectangle detected with area: " + r.area() // - Otherwise: return "Unknown shape with area: " + shape.area() - + public String describeShape(Shape shape){ + if(shape instanceof Circle){ + return "Circle detected with area: " + shape.area(); + } else if (shape instanceof Rectangle) { + return "Rectangle detected with area: " + shape.area(); + }else{ + return "Unknown shape with area: " + shape.area(); + } + } // TODO: 5 - Create a method: String formatSummary(List shapes) // Return a formatted summary string like: // "Summary: shapes, total area: , largest area: " // Use the totalArea() and largestShape() methods you already wrote. - + public String formatSummary(List shapes){ + return "Summary: <%s> shapes, total area: <%s>, largest area: <%s>" + .formatted(shapes.size(), totalArea(shapes), largestShape(shapes)); + } // TODO: 6 - In main, create a List with at least two Circles // and two Rectangles. Call all the methods above and print results. @@ -61,5 +93,21 @@ public class ShapeCalculator { public static void main(String[] args) { // Complete TODO 6 here. + + List shapes = new ArrayList<>(); + shapes.add(new Circle(3.0)); + shapes.add(new Circle(4.5)); + shapes.add(new Rectangle(5.0, 6.0)); + shapes.add(new Rectangle(8.0, 8.0)); + + ShapeCalculator shapeCalculator = new ShapeCalculator(); + for(Shape shape: shapes){ + shapeCalculator.printShapeArea(shape); + System.out.println(shapeCalculator.describeShape(shape)); + } + System.out.println("Total area: %s".formatted(String.format("%.2f", shapeCalculator.totalArea(shapes)))); + System.out.println("Largest: %s".formatted(shapeCalculator.largestShape(shapes))); + System.out.println(shapeCalculator.formatSummary(shapes)); + } } diff --git a/src/main/java/com/amigoscode/_3_oop/_5_dependencyinjection/NotificationService.java b/src/main/java/com/amigoscode/_3_oop/_5_dependencyinjection/NotificationService.java index afbefd0..037c32e 100644 --- a/src/main/java/com/amigoscode/_3_oop/_5_dependencyinjection/NotificationService.java +++ b/src/main/java/com/amigoscode/_3_oop/_5_dependencyinjection/NotificationService.java @@ -17,17 +17,29 @@ // TODO: 1 - Create a MessageSender interface with a single method: // void send(String to, String message) - +interface MessageSender{ + void send(String to, String message); +} // TODO: 2 - Create an EmailSender class that implements MessageSender. // Implement send() to print: // "[Email] Sending to : " - +class EmailSender implements MessageSender{ + @Override + public void send(String to, String message) { + System.out.println("[Email] Sending to <%s>: <%s>".formatted(to, message)); + } +} // TODO: 3 - Create an SmsSender class that implements MessageSender. // Implement send() to print: // "[SMS] Sending to : " - +class SmsSender implements MessageSender{ + @Override + public void send(String to, String message) { + System.out.println("[SMS] Sending to <%s>: <%s>".formatted(to, message)); + } +} // TODO: 4 - Create the NotificationService class. // - Add a private final field: messageSender (MessageSender) @@ -40,6 +52,18 @@ // void sendNotification(String to, String message) // This method should delegate to messageSender.send(to, message). // NotificationService does NOT know whether it is using email or SMS. +class NotificationService{ + private final MessageSender messageSender; + + public NotificationService(MessageSender messageSender) { + this.messageSender = messageSender; + } + + void sendNotification(String to, String message){ + messageSender.send(to, message); + } + +} class NotificationDemo { @@ -48,6 +72,13 @@ public static void main(String[] args) { // Call sendNotification("alice@example.com", "Hello via email!"). // Then create ANOTHER NotificationService with an SmsSender. // Call sendNotification("+1234567890", "Hello via SMS!"). + MessageSender emailSender = new EmailSender(); + NotificationService emailNotificationSender = new NotificationService(emailSender); + emailNotificationSender.sendNotification("alice@example.com", "Hello via email!"); + + MessageSender smsSender = new SmsSender(); + NotificationService smsNotificationSender = new NotificationService(smsSender); + smsNotificationSender.sendNotification("+306971234567", "Hello via SMS!"); // TODO: 7 - Demonstrate swapping implementations: @@ -57,6 +88,13 @@ public static void main(String[] args) { // create a new NotificationService and send a message. // Notice how NotificationService code never changed — // only the injected dependency changed. + MessageSender messageSender = new EmailSender(); + NotificationService notificationService = new NotificationService(messageSender); + notificationService.sendNotification("maria@example.com", "Hello Maria via email!"); + + messageSender = new SmsSender(); + NotificationService notificationService2 = new NotificationService(messageSender); + notificationService2.sendNotification("+306981234567", "Hello via SMS!"); } } diff --git a/src/main/java/com/amigoscode/_3_oop/_5_dependencyinjection/OrderProcessor.java b/src/main/java/com/amigoscode/_3_oop/_5_dependencyinjection/OrderProcessor.java index e63ec54..b57ad7c 100644 --- a/src/main/java/com/amigoscode/_3_oop/_5_dependencyinjection/OrderProcessor.java +++ b/src/main/java/com/amigoscode/_3_oop/_5_dependencyinjection/OrderProcessor.java @@ -18,13 +18,32 @@ // boolean charge(double amount) // Also create a concrete StripeGateway class that implements it. // In charge(), print "[Stripe] Charging $" and return true. +interface PaymentGateway{ + boolean charge(double amount); +} +class StripeGateway implements PaymentGateway{ + @Override + public boolean charge(double amount) { + System.out.println("[Stripe] Charging $<%s>".formatted(amount)); + return true; + } +} // TODO: 2 - Create an OrderRepository interface with: // void save(Order order) // Also create a concrete InMemoryOrderRepository class that implements it. // In save(), print "[Repository] Order saved: " +interface OrderRepository{ + void save(Order order); +} +class InMemoryOrderRepository implements OrderRepository{ + @Override + public void save(Order order) { + System.out.println("[Repository] Order saved: <%s>".formatted(order)); + } +} // TODO: 3 - Create an Order class with three fields: // - id (String) @@ -32,7 +51,26 @@ // - amount (double) // Create a constructor, getters, and a toString() method. // (You may use a record if you prefer: record Order(String id, String item, double amount) {} ) +class Order{ + private String id; + private String item; + private double amount; + + public Order(String id, String item, double amount) { + this.id = id; + this.item = item; + this.amount = amount; + } + + public String getId() { return id; } + public String getItem() { return item; } + public double getAmount() { return amount; } + @Override + public String toString() { + return "Order{id='" + id + "', item='" + item + "', amount=" + amount + "}"; + } +} // TODO: 4 - Create the OrderProcessor class. // - Add two private final fields: @@ -47,7 +85,24 @@ // - If charge returns true, call orderRepository.save(order) and return true // - If charge returns false, print "Payment failed for order: " // and return false +class OrderProcessor { + private final PaymentGateway paymentGateway; + private final OrderRepository orderRepository; + public OrderProcessor(PaymentGateway paymentGateway, OrderRepository orderRepository) { + this.paymentGateway = paymentGateway; + this.orderRepository = orderRepository; + } + boolean processOrder(Order order){ + if(paymentGateway.charge(order.getAmount())){ + orderRepository.save(order); + return true; + }else{ + System.out.println("Payment failed for order: <%s>".formatted(order.getId())); + return false; + } + } +} class OrderProcessorDemo { public static void main(String[] args) { @@ -60,6 +115,12 @@ public static void main(String[] args) { // - Notice: OrderProcessor has no idea which gateway or // repository it uses. You could swap in a PayPalGateway // or a DatabaseOrderRepository without changing OrderProcessor. + PaymentGateway stripeGateway = new StripeGateway(); + OrderRepository inMemoryOrderRepository = new InMemoryOrderRepository(); + OrderProcessor orderProcessor = new OrderProcessor(stripeGateway, inMemoryOrderRepository); + Order javaCourseOrder = new Order("ORD-001", "Java Course", 29.99); + boolean result = orderProcessor.processOrder(javaCourseOrder); + System.out.println("Order processed: %s".formatted(result)); } } diff --git a/src/main/java/com/amigoscode/_3_oop/_6_solid/SolidExercises.java b/src/main/java/com/amigoscode/_3_oop/_6_solid/SolidExercises.java index 62c6a7d..25c971a 100644 --- a/src/main/java/com/amigoscode/_3_oop/_6_solid/SolidExercises.java +++ b/src/main/java/com/amigoscode/_3_oop/_6_solid/SolidExercises.java @@ -1,8 +1,5 @@ package com.amigoscode._3_oop._6_solid; -import java.util.ArrayList; -import java.util.List; - /** * Exercise: SOLID Principles * @@ -48,7 +45,41 @@ void createUser(String name, String email) { // Then create a refactored UserManager that uses all three via // constructor injection and has a createUser(name, email) method. + static class UserValidator{ + void validate(String name, String email){ + if (name == null || name.isEmpty()) throw new IllegalArgumentException("Invalid name"); + } + } + + static class UserRepository{ + void save(String name, String email){ + System.out.println("Saving user " + name + " to database..."); + } + } + + static class UserNotifier{ + void sendWelcome(String email){ + System.out.println("Sending welcome email to " + email + "..."); + } + } + + static class UserManager { + private final UserValidator userValidator; + private final UserRepository userRepository; + private final UserNotifier userNotifier; + + public UserManager(UserValidator userValidator, UserRepository userRepository, UserNotifier userNotifier) { + this.userValidator = userValidator; + this.userRepository = userRepository; + this.userNotifier = userNotifier; + } + void createUser(String name, String email) { + userValidator.validate(name, email); + userRepository.save(name, email); + userNotifier.sendWelcome(email); + } + } // ========================================================================= // OCP - Open/Closed Principle // "Open for extension, closed for modification." @@ -76,6 +107,29 @@ static class DiscountCalculatorBroken { // that just calls discount.apply(price) // Now new discount types can be added without modifying DiscountCalculator. + interface Discount{ + double apply(double price); + } + + static class SeasonalDiscount implements Discount{ + @Override + public double apply(double price) { + return price * 0.9; + } + } + + static class ClearanceDiscount implements Discount{ + @Override + public double apply(double price) { + return price * 0.5; + } + } + + static class DiscountCalculator{ + double calculate(Discount discount, double price){ + return discount.apply(price); + } + } // ========================================================================= // LSP - Liskov Substitution Principle @@ -109,7 +163,37 @@ static class MutableSquareBroken extends MutableRectangleBroken { // a final field side, constructor, and area() returning side * side // Now neither class pretends to be the other. Both satisfy LspShape. + interface LspShape{ + int area(); + } + + static class ImmutableRectangle implements LspShape{ + private final int width; + private final int height; + + public ImmutableRectangle(int width, int height) { + this.width = width; + this.height = height; + } + + @Override + public int area() { + return width * height; + } + } + + static class ImmutableSquare implements LspShape{ + private final int side; + + public ImmutableSquare(int side) { + this.side = side; + } + @Override + public int area() { + return side * side; + } + } // ========================================================================= // ISP - Interface Segregation Principle // "No client should be forced to depend on methods it does not use." @@ -145,6 +229,42 @@ public void sleep() { /* Robots don't sleep — forced to implement! */ } // - RobotWorker class implementing only Workable // Now RobotWorker is not forced to implement methods it cannot use. + interface Workable{ + void work(); + } + + interface Eatable{ + void eat(); + } + + interface Sleepable{ + void sleep(); + } + + static class HumanWorker implements Workable, Eatable, Sleepable{ + @Override + public void work() { + System.out.println("Human working"); + } + + @Override + public void eat() { + System.out.println("Human eating"); + } + + @Override + public void sleep() { + System.out.println("Human sleeping"); + } + + } + + static class RobotWorker implements Workable{ + @Override + public void work() { + System.out.println("Robot working"); + } + } // ========================================================================= // DIP - Dependency Inversion Principle @@ -175,7 +295,37 @@ String generateReport() { // (its query() returns "PostgreSQL result for: " + sql) // - Create ReportGenerator that takes Database in its constructor // (constructor injection) and uses it in generateReport() + interface Database{ + String query(String sql); + } + static class MySQLDatabase implements Database{ + @Override + public String query(String sql) { + return "MySQL result for: " + sql; + } + } + + static class PostgreSQLDatabase implements Database{ + @Override + public String query(String sql) { + return"PostgreSQL result for: " + sql; + } + } + + static class ReportGenerator{ + private final Database database; + + public ReportGenerator(Database database) { + this.database = database; + } + + String generateReport() { + return database.query("SELECT * FROM reports"); + } + + + } // ========================================================================= // Main method to test all exercises @@ -185,16 +335,31 @@ public static void main(String[] args) { // TODO: 6 - Test SRP: Create UserValidator, UserRepository, UserNotifier, // and a refactored UserManager. Call createUser("Alice", "alice@test.com"). + UserValidator userValidator = new UserValidator(); + UserRepository userRepository = new UserRepository(); + UserNotifier userNotifier = new UserNotifier(); + UserManager userManager = new UserManager(userValidator, userRepository, userNotifier); + userManager.createUser("Alice", "alice@test.com"); // TODO: 7 - Test OCP: Create a DiscountCalculator and several Discount // implementations. Calculate discounts for a $100 item and print results. + DiscountCalculator discountCalculator = new DiscountCalculator(); + double seasonalDiscount = discountCalculator.calculate(new SeasonalDiscount(), 100.0); + System.out.println("Seasonal discount on $100: $%s".formatted(seasonalDiscount)); + double clearanceDiscount = discountCalculator.calculate(new ClearanceDiscount(), 150.0); + System.out.println("Clearance discount on $100: $%s".formatted(clearanceDiscount)); // TODO: 8 - Test DIP: Create a ReportGenerator with MySQLDatabase, // generate a report. Then create another with PostgreSQLDatabase // and generate a report. Print both results to show the // implementation was swapped without changing ReportGenerator. + ReportGenerator mySQLReportGenerator = new ReportGenerator(new MySQLDatabase()); + ReportGenerator postgreSQLReportGenerator = new ReportGenerator(new PostgreSQLDatabase()); + + System.out.println(mySQLReportGenerator.generateReport()); + System.out.println(postgreSQLReportGenerator.generateReport()); } }