From 9617ec93f997170b9fdd321ccd3008c30c457567 Mon Sep 17 00:00:00 2001 From: JC Dela Cuesta Date: Thu, 6 Mar 2025 11:11:37 -0800 Subject: [PATCH 1/2] Update README.md --- README.md | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/README.md b/README.md index 5b02b3c..ad2e5f1 100644 --- a/README.md +++ b/README.md @@ -21,3 +21,35 @@ This repository contains Swift playground exercises demonstrating Object-Oriente - ✅ Implements error handling with custom PaymentError types - ✅ Creates concrete implementations (CreditCardProcessor, CashProcessor) - ✅ Demonstrates try-catch error handling for payment processing + +iOS OOP & POP Programming Exercises + +This repository contains Swift playground exercises demonstrating Object-Oriented Programming (OOP) and Protocol-Oriented Programming (POP) concepts for my iOS development bootcamp. + +### Exercise 1: Social Media Post Class + +- ✅ Demonstrates basic class creation with properties and methods +- ✅ Implements a Post class with author, content, and likes properties +- ✅ Includes a display() method to format post information + +### Exercise 2: Shopping Cart Singleton + +- ✅ Implements the Singleton design pattern with a ShoppingCartSingleton class +- ✅ Uses composition for managing Product objects +- ✅ Implements discount strategies with the Strategy pattern +- ✅ Features cart management functionality (add/remove products, clear cart) +- ✅ Calculates pricing with configurable discount strategies + +### Exercise 3: Payment Processor Protocol + +- ✅ Uses protocol-oriented programming with the PaymentProcessor protocol +- ✅ Implements error handling with custom PaymentError types +- ✅ Creates concrete implementations (CreditCardProcessor, CashProcessor) +- ✅ Demonstrates try-catch error handling for various payment scenarios + +### Implementation Notes + +- Code is designed for Swift Playgrounds environment +- Exercise 2 incorporates the nonisolated(unsafe) modifier for Xcode 16 compatibility +- All exercise requirements are implemented with proper Swift syntax and conventions +- Error handling is implemented using Swift's do-try-catch mechanism From 81724093431c03eacb830d230342a954c56c7dbe Mon Sep 17 00:00:00 2001 From: JC Dela Cuesta Date: Thu, 6 Mar 2025 11:14:29 -0800 Subject: [PATCH 2/2] Revised Playground file I updated the code based on mentor feedback and the README file with implementation notes. --- .../Contents.swift | 371 ++++++++++++++++++ .../contents.xcworkspacedata | 7 + .../UserInterfaceState.xcuserstate | Bin 0 -> 24252 bytes 3 files changed, 378 insertions(+) create mode 100644 OOP_POP_Exercises 1-3_JC_Revised.playground/Contents.swift create mode 100644 OOP_POP_Exercises 1-3_JC_Revised.playground/playground.xcworkspace/contents.xcworkspacedata create mode 100644 OOP_POP_Exercises 1-3_JC_Revised.playground/playground.xcworkspace/xcuserdata/jdelacuesta.xcuserdatad/UserInterfaceState.xcuserstate diff --git a/OOP_POP_Exercises 1-3_JC_Revised.playground/Contents.swift b/OOP_POP_Exercises 1-3_JC_Revised.playground/Contents.swift new file mode 100644 index 0000000..5a3a3d2 --- /dev/null +++ b/OOP_POP_Exercises 1-3_JC_Revised.playground/Contents.swift @@ -0,0 +1,371 @@ +import SwiftUI + +// MARK: - Exercise 1: Social Media Post Class + +class Post { + var author: String + var content: String + var likes: Int + + init(author: String, content: String, likes: Int = 0) { + self.author = author + self.content = content + self.likes = likes + } + + func display() { + print("Post by \(author):") + print("\"\(content)\"") + print("Likes: \(likes)") + print(String(repeating: "-", count: 30)) // Separator line for better readability + } +} + +// Create first post +let post1 = Post(author: "Alice", content: "Just finished my first iOS assignment!", likes: 15) + +// Create second post +let post2 = Post(author: "Bob", content: "Learning Swift classes is fun!", likes: 7) + +// Display both posts +post1.display() +post2.display() + +// MARK: - Exercise 2: Using the Singleton Pattern to create a more flexible shopping cart system + +// Product Class +class Product { + let name: String + let price: Double + var quantity: Int + + init(name: String, price: Double, quantity: Int = 1) { + self.name = name + self.price = price + self.quantity = quantity + } +} + +//MARK: - DiscountStrategy Protocol and Implementations +protocol DiscountStrategy { + func calculateDiscount(total: Double) -> Double +} + +class NoDiscountStrategy: DiscountStrategy { + func calculateDiscount(total: Double) -> Double { + return 0.0 // No discount + } +} + +class PercentageDiscountStrategy: DiscountStrategy { + let percentage: Double + + init(percentage: Double) { + self.percentage = min(percentage, 100.0) // Ensure discount doesn't exceed 100% + } + + func calculateDiscount(total: Double) -> Double { + return total * (percentage / 100.0) + } +} + +//MARK: - ShoppingCartSingleton Class + +class ShoppingCartSingleton { + // Static property for the singleton instance + // Using nonisolated(unsafe) as per mentor's feedback for Xcode 16 compatibility + private static nonisolated(unsafe) var instance: ShoppingCartSingleton? + + // Private array to store products (composition) + private var products: [Product] = [] + + // Discount strategy + var discountStrategy: DiscountStrategy = NoDiscountStrategy() + + // Private initializer to enforce singleton pattern + private init() {} + + // Method to access the singleton instance + static func sharedInstance() -> ShoppingCartSingleton { + if instance == nil { + instance = ShoppingCartSingleton() + } + return instance! + } + + // Method to add a product to the cart + func addProduct(product: Product, quantity: Int = 1) { + // Check if the product is already in the cart + if let index = products.firstIndex(where: { $0.name == product.name }) { + // If it is, update the quantity + products[index].quantity += quantity + } else { + // If not, add the product with the specified quantity + let newProduct = Product(name: product.name, price: product.price, quantity: quantity) + products.append(newProduct) + } + } + + // Method to remove a product from the cart + func removeProduct(product: Product) { + products.removeAll { $0.name == product.name } + } + + // Method to clear the cart + func clearCart() { + products.removeAll() + } + + // Method to calculate the total price + func getTotalPrice() -> Double { + let subtotal = products.reduce(0.0) { $0 + ($1.price * Double($1.quantity)) } + let discount = discountStrategy.calculateDiscount(total: subtotal) + return subtotal - discount + } + + // Helper method to display cart contents + func displayCart() { + if products.isEmpty { + print("Shopping cart is empty.") + return + } + + print("Shopping Cart Contents:") + print("------------------------") + for product in products { + print("\(product.name) - $\(product.price) x \(product.quantity) = $\(product.price * Double(product.quantity))") + } + + let subtotal = products.reduce(0.0) { $0 + ($1.price * Double($1.quantity)) } + let discount = discountStrategy.calculateDiscount(total: subtotal) + + print("------------------------") + print("Subtotal: $\(subtotal)") + print("Discount: $\(discount)") + print("Total: $\(getTotalPrice())") // Using getTotalPrice() method as suggested by mentor + print("------------------------") + } +} + +// Get the singleton shopping cart instance +let cart = ShoppingCartSingleton.sharedInstance() + +// Create some products +let laptop = Product(name: "Laptop", price: 1299.99) +let phone = Product(name: "Smartphone", price: 799.99) +let charger = Product(name: "USB-C Charger", price: 19.99) + +// Add products to the cart +cart.addProduct(product: laptop) +cart.addProduct(product: phone, quantity: 2) +cart.addProduct(product: charger, quantity: 3) + +// Display the cart with no discount +print("Cart with no discount:") +cart.displayCart() + +// Apply a 10% discount +cart.discountStrategy = PercentageDiscountStrategy(percentage: 10.0) +print("\nCart with 10% discount:") +cart.displayCart() + +// Remove a product +cart.removeProduct(product: phone) +print("\nCart after removing smartphones:") +cart.displayCart() + +// Clear the cart +cart.clearCart() +print("\nCart after clearing:") +cart.displayCart() + +// MARK: - Exercise 3: Payment Processor Protocol + +// Custom Error Type for Payment Processing +enum PaymentError: Error { + case insufficientFunds + case invalidCard + case cardExpired + case networkError + case paymentLimitExceeded + case cashRegisterEmpty + + var message: String { + switch self { + case .insufficientFunds: + return "Insufficient funds to complete the transaction." + case .invalidCard: + return "The card information is invalid." + case .cardExpired: + return "The card has expired." + case .networkError: + return "Network error occurred during payment processing." + case .paymentLimitExceeded: + return "The payment amount exceeds the allowed limit." + case .cashRegisterEmpty: + return "Cash register is empty and cannot provide change." + } + } +} + +// Payment Processor Protocol +protocol PaymentProcessor { + func processPayment(amount: Double) throws + var name: String { get } +} + +// Credit Card Processor +class CreditCardProcessor: PaymentProcessor { + let name = "Credit Card" + private let cardNumber: String + private let expiryDate: String + private let cvv: String + private let balance: Double + + init(cardNumber: String, expiryDate: String, cvv: String, balance: Double) { + self.cardNumber = cardNumber + self.expiryDate = expiryDate + self.cvv = cvv + self.balance = balance + } + + func processPayment(amount: Double) throws { + // Validate the amount + if amount <= 0 { + throw PaymentError.invalidCard + } + + // Check for payment limits + if amount > 10000 { + throw PaymentError.paymentLimitExceeded + } + + // Simulate card validation + if cardNumber.count != 16 { + throw PaymentError.invalidCard + } + + // Simulate expiry date check (very basic simulation) + if expiryDate == "01/20" { + throw PaymentError.cardExpired + } + + // Check for sufficient funds + if amount > balance { + throw PaymentError.insufficientFunds + } + + // Simulate random network error (10% chance) + if Double.random(in: 0...1) < 0.1 { + throw PaymentError.networkError + } + + // If we reach here, payment is successful + print("Credit card payment of $\(amount) processed successfully.") + } +} + +// Cash Processor +class CashProcessor: PaymentProcessor { + let name = "Cash" + private var cashInRegister: Double + + init(cashInRegister: Double) { + self.cashInRegister = cashInRegister + } + + func processPayment(amount: Double) throws { + // Validate the amount + if amount <= 0 { + throw PaymentError.invalidCard // Reusing error for simplicity + } + + // Check if providing change is possible + if cashInRegister < amount { + throw PaymentError.cashRegisterEmpty + } + + // Processing payment + cashInRegister -= amount + + // If below messages display, payment is successful + print("Cash payment of $\(amount) processed successfully.") + print("Remaining cash in register: $\(cashInRegister)") + } +} + +// Create payment processors +let creditCardProcessor = CreditCardProcessor( + cardNumber: "1234567890123456", + expiryDate: "12/25", + cvv: "123", + balance: 5000.0 +) + +let expiredCardProcessor = CreditCardProcessor( + cardNumber: "9876543210987654", + expiryDate: "01/20", + cvv: "456", + balance: 1000.0 +) + +let cashProcessor = CashProcessor(cashInRegister: 200.0) + +// Process payments with try-catch blocks as per exercise requirements +print("\n--- Payment Processing Examples ---") + +// Example 1: Successful credit card payment +print("\nAttempting credit card payment of $50.00:") +do { + try creditCardProcessor.processPayment(amount: 50.0) + print("✅ Transaction completed successfully") +} catch let error as PaymentError { + print("❌ Payment Error: \(error.message)") +} catch { + print("❌ Unexpected error: \(error.localizedDescription)") +} + +// Example 2: Payment exceeding limit +print("\nAttempting large credit card payment of $20000.00:") +do { + try creditCardProcessor.processPayment(amount: 20000.0) + print("✅ Transaction completed successfully") +} catch let error as PaymentError { + print("❌ Payment Error: \(error.message)") +} catch { + print("❌ Unexpected error: \(error.localizedDescription)") +} + +// Example 3: Expired card +print("\nAttempting payment with expired card:") +do { + try expiredCardProcessor.processPayment(amount: 30.0) + print("✅ Transaction completed successfully") +} catch let error as PaymentError { + print("❌ Payment Error: \(error.message)") +} catch { + print("❌ Unexpected error: \(error.localizedDescription)") +} + +// Example 4: Successful cash payment +print("\nAttempting cash payment of $75.00:") +do { + try cashProcessor.processPayment(amount: 75.0) + print("✅ Transaction completed successfully") +} catch let error as PaymentError { + print("❌ Payment Error: \(error.message)") +} catch { + print("❌ Unexpected error: \(error.localizedDescription)") +} + +// Example 5: Cash payment exceeding register amount +print("\nAttempting cash payment of $300.00 (register only has $200.00):") +do { + try cashProcessor.processPayment(amount: 300.0) + print("✅ Transaction completed successfully") +} catch let error as PaymentError { + print("❌ Payment Error: \(error.message)") +} catch { + print("❌ Unexpected error: \(error.localizedDescription)") +} + diff --git a/OOP_POP_Exercises 1-3_JC_Revised.playground/playground.xcworkspace/contents.xcworkspacedata b/OOP_POP_Exercises 1-3_JC_Revised.playground/playground.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..ca3329e --- /dev/null +++ b/OOP_POP_Exercises 1-3_JC_Revised.playground/playground.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/OOP_POP_Exercises 1-3_JC_Revised.playground/playground.xcworkspace/xcuserdata/jdelacuesta.xcuserdatad/UserInterfaceState.xcuserstate b/OOP_POP_Exercises 1-3_JC_Revised.playground/playground.xcworkspace/xcuserdata/jdelacuesta.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 0000000000000000000000000000000000000000..c358454fe7daf36a6d933ffd061425c1e7f7601b GIT binary patch literal 24252 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 KpZ5Pn>;4}DB&3u8 literal 0 HcmV?d00001