Created
January 8, 2026 04:59
-
-
Save isaacssemugenyi/b8f4a3ef98edcd2358ac79db28de4fde to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // Existing global state (KEEP) | |
| let contacts = []; | |
| let lastId = 0; | |
| /** | |
| * NEW: AddressBook class | |
| * We introduce it but DO NOT break existing code | |
| */ | |
| class AddressBook { | |
| constructor(store, clock = () => new Date().toISOString()) { | |
| this.store = store; | |
| this.clock = clock; | |
| } | |
| /** | |
| * REFACTORED METHOD | |
| * - Cleaner validation | |
| * - Single responsibility | |
| * - No behavior change | |
| */ | |
| addContact(name, phone, email, address) { | |
| if (!name || !phone) { | |
| return false; | |
| } | |
| const existing = this.store.find(c => c.phone === phone); | |
| if (existing) { | |
| return false; | |
| } | |
| lastId = lastId + 1; | |
| const contact = { | |
| id: lastId, | |
| name: name.trim(), | |
| phone: phone.trim(), | |
| email: email ? email.trim() : "", | |
| address: address ? address.trim() : "", | |
| createdAt: this.clock() | |
| }; | |
| this.store.push(contact); | |
| return true; | |
| } | |
| } | |
| // Create ONE shared instance | |
| const addressBook = new AddressBook(contacts); | |
| /** | |
| * 🔁 BACKWARD-COMPATIBLE FUNCTION | |
| * Tests still call this — so we keep it | |
| */ | |
| function addContact(name, phone, email, address) { | |
| return addressBook.addContact(name, phone, email, address); | |
| } | |
| /* | |
| function addContact(name, phone, email, address) { | |
| if (name == null || name === "") { | |
| console.log("Name is required"); | |
| return false; | |
| } | |
| if (phone == null || phone === "") { | |
| console.log("Phone is required"); | |
| return false; | |
| } | |
| for (let i = 0; i < contacts.length; i++) { | |
| if (contacts[i].phone === phone) { | |
| console.log("Phone already exists"); | |
| return false; | |
| } | |
| } | |
| lastId = lastId + 1; | |
| let contact = { | |
| id: lastId, | |
| name: name, | |
| phone: phone, | |
| email: email ? email : "", | |
| address: address ? address : "", | |
| createdAt: new Date().toISOString() | |
| }; | |
| contacts.push(contact); | |
| console.log("Contact added:", contact.name); | |
| return true; | |
| } */ | |
| function getAllContacts() { | |
| if (contacts.length === 0) { | |
| console.log("No contacts found"); | |
| return []; | |
| } | |
| let result = []; | |
| for (let i = 0; i < contacts.length; i++) { | |
| result.push(contacts[i]); | |
| } | |
| return result; | |
| } | |
| function findContactByPhone(phone) { | |
| if (!phone) { | |
| console.log("Phone required"); | |
| return null; | |
| } | |
| for (let i = 0; i < contacts.length; i++) { | |
| if (contacts[i].phone === phone) { | |
| return contacts[i]; | |
| } | |
| } | |
| return null; | |
| } | |
| function updateContact(phone, newName, newEmail, newAddress) { | |
| let found = false; | |
| for (let i = 0; i < contacts.length; i++) { | |
| if (contacts[i].phone === phone) { | |
| if (newName) { | |
| contacts[i].name = newName; | |
| } | |
| if (newEmail) { | |
| contacts[i].email = newEmail; | |
| } | |
| if (newAddress) { | |
| contacts[i].address = newAddress; | |
| } | |
| contacts[i].updatedAt = new Date().toISOString(); | |
| console.log("Contact updated"); | |
| found = true; | |
| } | |
| } | |
| if (!found) { | |
| console.log("Contact not found"); | |
| return false; | |
| } | |
| return true; | |
| } | |
| function deleteContact(phone) { | |
| let index = -1; | |
| for (let i = 0; i < contacts.length; i++) { | |
| if (contacts[i].phone === phone) { | |
| index = i; | |
| } | |
| } | |
| if (index === -1) { | |
| console.log("Contact not found"); | |
| return false; | |
| } | |
| contacts.splice(index, 1); | |
| console.log("Contact deleted"); | |
| return true; | |
| } | |
| function searchContacts(keyword) { | |
| let results = []; | |
| if (!keyword) { | |
| return results; | |
| } | |
| for (let i = 0; i < contacts.length; i++) { | |
| if ( | |
| contacts[i].name.includes(keyword) || | |
| contacts[i].phone.includes(keyword) || | |
| contacts[i].email.includes(keyword) | |
| ) { | |
| results.push(contacts[i]); | |
| } | |
| } | |
| return results; | |
| } | |
| /************* | |
| * Demo usage | |
| *************/ | |
| if (require.main === module) { | |
| addContact('John Doe', '0700000001', 'john@email.com', 'Kampala'); | |
| addContact("Jane Smith", "0700000002", "", ""); | |
| updateContact("0700000001", "John D.", null, "Ntinda"); | |
| console.log(getAllContacts()); | |
| console.log(findContactByPhone("0700000002")); | |
| console.log(searchContacts("John")); | |
| } | |
| module.exports = { | |
| addContact, | |
| getAllContacts, | |
| findContactByPhone, | |
| updateContact, | |
| deleteContact, | |
| searchContacts | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // Existing global state (KEEP) | |
| let contacts = []; | |
| let lastId = 0; | |
| /** | |
| * NEW: AddressBook class | |
| * We introduce it but DO NOT break existing code | |
| */ | |
| class AddressBook { | |
| constructor(store, clock = () => new Date().toISOString()) { | |
| this.store = store; | |
| this.clock = clock; | |
| } | |
| /** | |
| * REFACTORED METHOD | |
| * - Cleaner validation | |
| * - Single responsibility | |
| * - No behavior change | |
| */ | |
| addContact(name, phone, email, address) { | |
| if (!name || !phone) { | |
| return false; | |
| } | |
| const existing = this.store.find(c => c.phone === phone); | |
| if (existing) { | |
| return false; | |
| } | |
| lastId = lastId + 1; | |
| const contact = { | |
| id: lastId, | |
| name: name.trim(), | |
| phone: phone.trim(), | |
| email: email ? email.trim() : "", | |
| address: address ? address.trim() : "", | |
| createdAt: this.clock() | |
| }; | |
| this.store.push(contact); | |
| return true; | |
| } | |
| getAllContacts() { | |
| if (this.store.length === 0) { | |
| console.log("No contacts found"); | |
| return []; | |
| } | |
| return this.store; | |
| } | |
| } | |
| // Create ONE shared instance | |
| const addressBook = new AddressBook(contacts); | |
| /** | |
| * 🔁 BACKWARD-COMPATIBLE FUNCTION | |
| * Tests still call this — so we keep it | |
| */ | |
| function addContact(name, phone, email, address) { | |
| return addressBook.addContact(name, phone, email, address); | |
| } | |
| /* function getAllContacts() { | |
| if (contacts.length === 0) { | |
| console.log("No contacts found"); | |
| return []; | |
| } | |
| let result = []; | |
| for (let i = 0; i < contacts.length; i++) { | |
| result.push(contacts[i]); | |
| } | |
| return result; | |
| } */ | |
| function getAllContacts(){ | |
| return addressBook.getAllContacts(); | |
| } | |
| function findContactByPhone(phone) { | |
| if (!phone) { | |
| console.log("Phone required"); | |
| return null; | |
| } | |
| for (let i = 0; i < contacts.length; i++) { | |
| if (contacts[i].phone === phone) { | |
| return contacts[i]; | |
| } | |
| } | |
| return null; | |
| } | |
| function updateContact(phone, newName, newEmail, newAddress) { | |
| let found = false; | |
| for (let i = 0; i < contacts.length; i++) { | |
| if (contacts[i].phone === phone) { | |
| if (newName) { | |
| contacts[i].name = newName; | |
| } | |
| if (newEmail) { | |
| contacts[i].email = newEmail; | |
| } | |
| if (newAddress) { | |
| contacts[i].address = newAddress; | |
| } | |
| contacts[i].updatedAt = new Date().toISOString(); | |
| console.log("Contact updated"); | |
| found = true; | |
| } | |
| } | |
| if (!found) { | |
| console.log("Contact not found"); | |
| return false; | |
| } | |
| return true; | |
| } | |
| function deleteContact(phone) { | |
| let index = -1; | |
| for (let i = 0; i < contacts.length; i++) { | |
| if (contacts[i].phone === phone) { | |
| index = i; | |
| } | |
| } | |
| if (index === -1) { | |
| console.log("Contact not found"); | |
| return false; | |
| } | |
| contacts.splice(index, 1); | |
| console.log("Contact deleted"); | |
| return true; | |
| } | |
| function searchContacts(keyword) { | |
| let results = []; | |
| if (!keyword) { | |
| return results; | |
| } | |
| for (let i = 0; i < contacts.length; i++) { | |
| if ( | |
| contacts[i].name.includes(keyword) || | |
| contacts[i].phone.includes(keyword) || | |
| contacts[i].email.includes(keyword) | |
| ) { | |
| results.push(contacts[i]); | |
| } | |
| } | |
| return results; | |
| } | |
| /************* | |
| * Demo usage | |
| *************/ | |
| if (require.main === module) { | |
| addContact('John Doe', '0700000001', 'john@email.com', 'Kampala'); | |
| addContact("Jane Smith", "0700000002", "", ""); | |
| updateContact("0700000001", "John D.", null, "Ntinda"); | |
| console.log(getAllContacts()); | |
| console.log(findContactByPhone("0700000002")); | |
| console.log(searchContacts("John")); | |
| } | |
| module.exports = { | |
| addContact, | |
| getAllContacts, | |
| findContactByPhone, | |
| updateContact, | |
| deleteContact, | |
| searchContacts | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // Existing global state (KEEP) | |
| let contacts = []; | |
| let lastId = 0; | |
| /** | |
| * NEW: AddressBook class | |
| * We introduce it but DO NOT break existing code | |
| */ | |
| class AddressBook { | |
| constructor(store, clock = () => new Date().toISOString()) { | |
| this.store = store; | |
| this.clock = clock; | |
| } | |
| /** | |
| * REFACTORED METHOD | |
| * - Cleaner validation | |
| * - Single responsibility | |
| * - No behavior change | |
| */ | |
| addContact(name, phone, email, address) { | |
| if (!name || !phone) { | |
| return false; | |
| } | |
| const existing = this.store.find(c => c.phone === phone); | |
| if (existing) { | |
| return false; | |
| } | |
| lastId = lastId + 1; | |
| const contact = { | |
| id: lastId, | |
| name: name.trim(), | |
| phone: phone.trim(), | |
| email: email ? email.trim() : "", | |
| address: address ? address.trim() : "", | |
| createdAt: this.clock() | |
| }; | |
| this.store.push(contact); | |
| return true; | |
| } | |
| getAllContacts() { | |
| return [...this.store]; | |
| } | |
| findContactByPhone(phone) { | |
| if (!phone) return null; | |
| return this.store.find(c => c.phone === phone) || null; | |
| } | |
| } | |
| // Create ONE shared instance | |
| const addressBook = new AddressBook(contacts); | |
| /** | |
| * 🔁 BACKWARD-COMPATIBLE FUNCTION | |
| * Tests still call this — so we keep it | |
| */ | |
| function addContact(name, phone, email, address) { | |
| return addressBook.addContact(name, phone, email, address); | |
| } | |
| function getAllContacts() { | |
| return addressBook.getAllContacts(); | |
| } | |
| /* | |
| function findContactByPhone(phone) { | |
| if (!phone) { | |
| console.log("Phone required"); | |
| return null; | |
| } | |
| for (let i = 0; i < contacts.length; i++) { | |
| if (contacts[i].phone === phone) { | |
| return contacts[i]; | |
| } | |
| } | |
| return null; | |
| }*/ | |
| function findContactByPhone(phone){ | |
| return addressBook.findContactByPhone(phone); | |
| } | |
| function updateContact(phone, newName, newEmail, newAddress) { | |
| let found = false; | |
| for (let i = 0; i < contacts.length; i++) { | |
| if (contacts[i].phone === phone) { | |
| if (newName) { | |
| contacts[i].name = newName; | |
| } | |
| if (newEmail) { | |
| contacts[i].email = newEmail; | |
| } | |
| if (newAddress) { | |
| contacts[i].address = newAddress; | |
| } | |
| contacts[i].updatedAt = new Date().toISOString(); | |
| console.log("Contact updated"); | |
| found = true; | |
| } | |
| } | |
| if (!found) { | |
| console.log("Contact not found"); | |
| return false; | |
| } | |
| return true; | |
| } | |
| function deleteContact(phone) { | |
| let index = -1; | |
| for (let i = 0; i < contacts.length; i++) { | |
| if (contacts[i].phone === phone) { | |
| index = i; | |
| } | |
| } | |
| if (index === -1) { | |
| console.log("Contact not found"); | |
| return false; | |
| } | |
| contacts.splice(index, 1); | |
| console.log("Contact deleted"); | |
| return true; | |
| } | |
| function searchContacts(keyword) { | |
| let results = []; | |
| if (!keyword) { | |
| return results; | |
| } | |
| for (let i = 0; i < contacts.length; i++) { | |
| if ( | |
| contacts[i].name.includes(keyword) || | |
| contacts[i].phone.includes(keyword) || | |
| contacts[i].email.includes(keyword) | |
| ) { | |
| results.push(contacts[i]); | |
| } | |
| } | |
| return results; | |
| } | |
| /************* | |
| * Demo usage | |
| *************/ | |
| if (require.main === module) { | |
| addContact('John Doe', '0700000001', 'john@email.com', 'Kampala'); | |
| addContact("Jane Smith", "0700000002", "", ""); | |
| updateContact("0700000001", "John D.", null, "Ntinda"); | |
| console.log(getAllContacts()); | |
| console.log(findContactByPhone("0700000002")); | |
| console.log(searchContacts("John")); | |
| } | |
| module.exports = { | |
| addContact, | |
| getAllContacts, | |
| findContactByPhone, | |
| updateContact, | |
| deleteContact, | |
| searchContacts | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // Existing global state (KEEP) | |
| let contacts = []; | |
| let lastId = 0; | |
| /** | |
| * NEW: AddressBook class | |
| * We introduce it but DO NOT break existing code | |
| */ | |
| class AddressBook { | |
| constructor(store, clock = () => new Date().toISOString()) { | |
| this.store = store; | |
| this.clock = clock; | |
| } | |
| /** | |
| * REFACTORED METHOD | |
| * - Cleaner validation | |
| * - Single responsibility | |
| * - No behavior change | |
| */ | |
| addContact(name, phone, email, address) { | |
| if (!name || !phone) { | |
| return false; | |
| } | |
| const existing = this.store.find(c => c.phone === phone); | |
| if (existing) { | |
| return false; | |
| } | |
| lastId = lastId + 1; | |
| const contact = { | |
| id: lastId, | |
| name: name.trim(), | |
| phone: phone.trim(), | |
| email: email ? email.trim() : "", | |
| address: address ? address.trim() : "", | |
| createdAt: this.clock() | |
| }; | |
| this.store.push(contact); | |
| return true; | |
| } | |
| getAllContacts() { | |
| return [...this.store]; | |
| } | |
| findContactByPhone(phone) { | |
| if (!phone) return null; | |
| return this.store.find(c => c.phone === phone) || null; | |
| } | |
| updateContact(phone, newName, newEmail, newAddress) { | |
| const contact = this.findContactByPhone(phone); | |
| if (!contact) return false; | |
| if (newName) contact.name = newName; | |
| if (newEmail) contact.email = newEmail; | |
| if (newAddress) contact.address = newAddress; | |
| contact.updatedAt = this.clock(); | |
| return true; | |
| } | |
| } | |
| // Create ONE shared instance | |
| const addressBook = new AddressBook(contacts); | |
| /** | |
| * 🔁 BACKWARD-COMPATIBLE FUNCTION | |
| * Tests still call this — so we keep it | |
| */ | |
| function addContact(name, phone, email, address) { | |
| return addressBook.addContact(name, phone, email, address); | |
| } | |
| function getAllContacts() { | |
| return addressBook.getAllContacts(); | |
| } | |
| function findContactByPhone(phone) { | |
| return addressBook.findContactByPhone(phone); | |
| } | |
| /* | |
| function updateContact(phone, newName, newEmail, newAddress) { | |
| let found = false; | |
| for (let i = 0; i < contacts.length; i++) { | |
| if (contacts[i].phone === phone) { | |
| if (newName) { | |
| contacts[i].name = newName; | |
| } | |
| if (newEmail) { | |
| contacts[i].email = newEmail; | |
| } | |
| if (newAddress) { | |
| contacts[i].address = newAddress; | |
| } | |
| contacts[i].updatedAt = new Date().toISOString(); | |
| console.log("Contact updated"); | |
| found = true; | |
| } | |
| } | |
| if (!found) { | |
| console.log("Contact not found"); | |
| return false; | |
| } | |
| return true; | |
| } */ | |
| function updateContact(phone, newName, newEmail, newAddress) { | |
| return addressBook.updateContact(phone, newName, newEmail, newAddress) | |
| } | |
| function deleteContact(phone) { | |
| let index = -1; | |
| for (let i = 0; i < contacts.length; i++) { | |
| if (contacts[i].phone === phone) { | |
| index = i; | |
| } | |
| } | |
| if (index === -1) { | |
| console.log("Contact not found"); | |
| return false; | |
| } | |
| contacts.splice(index, 1); | |
| console.log("Contact deleted"); | |
| return true; | |
| } | |
| function searchContacts(keyword) { | |
| let results = []; | |
| if (!keyword) { | |
| return results; | |
| } | |
| for (let i = 0; i < contacts.length; i++) { | |
| if ( | |
| contacts[i].name.includes(keyword) || | |
| contacts[i].phone.includes(keyword) || | |
| contacts[i].email.includes(keyword) | |
| ) { | |
| results.push(contacts[i]); | |
| } | |
| } | |
| return results; | |
| } | |
| /************* | |
| * Demo usage | |
| *************/ | |
| if (require.main === module) { | |
| addContact('John Doe', '0700000001', 'john@email.com', 'Kampala'); | |
| addContact("Jane Smith", "0700000002", "", ""); | |
| updateContact("0700000001", "John D.", null, "Ntinda"); | |
| console.log(getAllContacts()); | |
| console.log(findContactByPhone("0700000002")); | |
| console.log(searchContacts("John")); | |
| } | |
| module.exports = { | |
| addContact, | |
| getAllContacts, | |
| findContactByPhone, | |
| updateContact, | |
| deleteContact, | |
| searchContacts | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // Existing global state (KEEP) | |
| let contacts = []; | |
| let lastId = 0; | |
| /** | |
| * NEW: AddressBook class | |
| * We introduce it but DO NOT break existing code | |
| */ | |
| class AddressBook { | |
| constructor(store, clock = () => new Date().toISOString()) { | |
| this.store = store; | |
| this.clock = clock; | |
| } | |
| /** | |
| * REFACTORED METHOD | |
| * - Cleaner validation | |
| * - Single responsibility | |
| * - No behavior change | |
| */ | |
| addContact(name, phone, email, address) { | |
| if (!name || !phone) { | |
| return false; | |
| } | |
| const existing = this.store.find(c => c.phone === phone); | |
| if (existing) { | |
| return false; | |
| } | |
| lastId = lastId + 1; | |
| const contact = { | |
| id: lastId, | |
| name: name.trim(), | |
| phone: phone.trim(), | |
| email: email ? email.trim() : "", | |
| address: address ? address.trim() : "", | |
| createdAt: this.clock() | |
| }; | |
| this.store.push(contact); | |
| return true; | |
| } | |
| getAllContacts() { | |
| return [...this.store]; | |
| } | |
| findContactByPhone(phone) { | |
| if (!phone) return null; | |
| return this.store.find(c => c.phone === phone) || null; | |
| } | |
| updateContact(phone, newName, newEmail, newAddress) { | |
| const contact = this.findContactByPhone(phone); | |
| if (!contact) return false; | |
| if (newName) contact.name = newName; | |
| if (newEmail) contact.email = newEmail; | |
| if (newAddress) contact.address = newAddress; | |
| contact.updatedAt = this.clock(); | |
| return true; | |
| } | |
| deleteContact(phone) { | |
| const index = this.store.findIndex(c => c.phone === phone); | |
| if (index === -1) return false; | |
| this.store.splice(index, 1); | |
| return true; | |
| } | |
| } | |
| // Create ONE shared instance | |
| const addressBook = new AddressBook(contacts); | |
| /** | |
| * 🔁 BACKWARD-COMPATIBLE FUNCTION | |
| * Tests still call this — so we keep it | |
| */ | |
| function addContact(name, phone, email, address) { | |
| return addressBook.addContact(name, phone, email, address); | |
| } | |
| function getAllContacts() { | |
| return addressBook.getAllContacts(); | |
| } | |
| function findContactByPhone(phone) { | |
| return addressBook.findContactByPhone(phone); | |
| } | |
| function updateContact(phone, newName, newEmail, newAddress) { | |
| return addressBook.updateContact(phone, newName, newEmail, newAddress) | |
| } | |
| /* | |
| function deleteContact(phone) { | |
| let index = -1; | |
| for (let i = 0; i < contacts.length; i++) { | |
| if (contacts[i].phone === phone) { | |
| index = i; | |
| } | |
| } | |
| if (index === -1) { | |
| console.log("Contact not found"); | |
| return false; | |
| } | |
| contacts.splice(index, 1); | |
| console.log("Contact deleted"); | |
| return true; | |
| } */ | |
| function deleteContact(phone){ | |
| return addressBook.deleteContact(phone); | |
| } | |
| function searchContacts(keyword) { | |
| let results = []; | |
| if (!keyword) { | |
| return results; | |
| } | |
| for (let i = 0; i < contacts.length; i++) { | |
| if ( | |
| contacts[i].name.includes(keyword) || | |
| contacts[i].phone.includes(keyword) || | |
| contacts[i].email.includes(keyword) | |
| ) { | |
| results.push(contacts[i]); | |
| } | |
| } | |
| return results; | |
| } | |
| /************* | |
| * Demo usage | |
| *************/ | |
| if (require.main === module) { | |
| addContact('John Doe', '0700000001', 'john@email.com', 'Kampala'); | |
| addContact("Jane Smith", "0700000002", "", ""); | |
| updateContact("0700000001", "John D.", null, "Ntinda"); | |
| console.log(getAllContacts()); | |
| console.log(findContactByPhone("0700000002")); | |
| console.log(searchContacts("John")); | |
| } | |
| module.exports = { | |
| addContact, | |
| getAllContacts, | |
| findContactByPhone, | |
| updateContact, | |
| deleteContact, | |
| searchContacts | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // Existing global state (KEEP) | |
| let contacts = []; | |
| let lastId = 0; | |
| /** | |
| * NEW: AddressBook class | |
| * We introduce it but DO NOT break existing code | |
| */ | |
| class AddressBook { | |
| constructor(store, clock = () => new Date().toISOString()) { | |
| this.store = store; | |
| this.clock = clock; | |
| } | |
| /** | |
| * REFACTORED METHOD | |
| * - Cleaner validation | |
| * - Single responsibility | |
| * - No behavior change | |
| */ | |
| addContact(name, phone, email, address) { | |
| if (!name || !phone) { | |
| return false; | |
| } | |
| const existing = this.store.find(c => c.phone === phone); | |
| if (existing) { | |
| return false; | |
| } | |
| lastId = lastId + 1; | |
| const contact = { | |
| id: lastId, | |
| name: name.trim(), | |
| phone: phone.trim(), | |
| email: email ? email.trim() : "", | |
| address: address ? address.trim() : "", | |
| createdAt: this.clock() | |
| }; | |
| this.store.push(contact); | |
| return true; | |
| } | |
| getAllContacts() { | |
| return [...this.store]; | |
| } | |
| findContactByPhone(phone) { | |
| if (!phone) return null; | |
| return this.store.find(c => c.phone === phone) || null; | |
| } | |
| updateContact(phone, newName, newEmail, newAddress) { | |
| const contact = this.findContactByPhone(phone); | |
| if (!contact) return false; | |
| if (newName) contact.name = newName; | |
| if (newEmail) contact.email = newEmail; | |
| if (newAddress) contact.address = newAddress; | |
| contact.updatedAt = this.clock(); | |
| return true; | |
| } | |
| deleteContact(phone) { | |
| const index = this.store.findIndex(c => c.phone === phone); | |
| if (index === -1) return false; | |
| this.store.splice(index, 1); | |
| return true; | |
| } | |
| searchContacts(keyword) { | |
| if (!keyword) return []; | |
| return this.store.filter(c => | |
| c.name.includes(keyword) || | |
| c.phone.includes(keyword) || | |
| c.email.includes(keyword) | |
| ); | |
| } | |
| } | |
| // Create ONE shared instance | |
| const addressBook = new AddressBook(contacts); | |
| /** | |
| * 🔁 BACKWARD-COMPATIBLE FUNCTION | |
| * Tests still call this — so we keep it | |
| */ | |
| function addContact(name, phone, email, address) { | |
| return addressBook.addContact(name, phone, email, address); | |
| } | |
| function getAllContacts() { | |
| return addressBook.getAllContacts(); | |
| } | |
| function findContactByPhone(phone) { | |
| return addressBook.findContactByPhone(phone); | |
| } | |
| function updateContact(phone, newName, newEmail, newAddress) { | |
| return addressBook.updateContact(phone, newName, newEmail, newAddress) | |
| } | |
| function deleteContact(phone) { | |
| return addressBook.deleteContact(phone); | |
| } | |
| /* | |
| function searchContacts(keyword) { | |
| let results = []; | |
| if (!keyword) { | |
| return results; | |
| } | |
| for (let i = 0; i < contacts.length; i++) { | |
| if ( | |
| contacts[i].name.includes(keyword) || | |
| contacts[i].phone.includes(keyword) || | |
| contacts[i].email.includes(keyword) | |
| ) { | |
| results.push(contacts[i]); | |
| } | |
| } | |
| return results; | |
| } */ | |
| function searchContacts(keyword) { | |
| return addressBook.searchContacts(keyword); | |
| } | |
| /************* | |
| * Demo usage | |
| *************/ | |
| if (require.main === module) { | |
| addContact('John Doe', '0700000001', 'john@email.com', 'Kampala'); | |
| addContact("Jane Smith", "0700000002", "", ""); | |
| updateContact("0700000001", "John D.", null, "Ntinda"); | |
| console.log(getAllContacts()); | |
| console.log(findContactByPhone("0700000002")); | |
| console.log(searchContacts("John")); | |
| } | |
| module.exports = { | |
| addContact, | |
| getAllContacts, | |
| findContactByPhone, | |
| updateContact, | |
| deleteContact, | |
| searchContacts | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // Existing global state (KEEP) | |
| let contacts = []; | |
| let lastId = 0; | |
| /** | |
| * NEW: AddressBook class | |
| * We introduce it but DO NOT break existing code | |
| */ | |
| class AddressBook { | |
| constructor(store, clock = () => new Date().toISOString()) { | |
| this.store = store; | |
| this.clock = clock; | |
| } | |
| /** | |
| * REFACTORED METHOD | |
| * - Cleaner validation | |
| * - Single responsibility | |
| * - No behavior change | |
| */ | |
| addContact(name, phone, email, address) { | |
| if (!name || !phone) { | |
| return false; | |
| } | |
| const existing = this.store.find(c => c.phone === phone); | |
| if (existing) { | |
| return false; | |
| } | |
| lastId = lastId + 1; | |
| const contact = { | |
| id: lastId, | |
| name: name.trim(), | |
| phone: phone.trim(), | |
| email: email ? email.trim() : "", | |
| address: address ? address.trim() : "", | |
| createdAt: this.clock() | |
| }; | |
| this.store.push(contact); | |
| return true; | |
| } | |
| getAllContacts() { | |
| return [...this.store]; | |
| } | |
| findContactByPhone(phone) { | |
| if (!phone) return null; | |
| return this.store.find(c => c.phone === phone) || null; | |
| } | |
| updateContact(phone, newName, newEmail, newAddress) { | |
| const contact = this.findContactByPhone(phone); | |
| if (!contact) return false; | |
| if (newName) contact.name = newName; | |
| if (newEmail) contact.email = newEmail; | |
| if (newAddress) contact.address = newAddress; | |
| contact.updatedAt = this.clock(); | |
| return true; | |
| } | |
| deleteContact(phone) { | |
| const index = this.store.findIndex(c => c.phone === phone); | |
| if (index === -1) return false; | |
| this.store.splice(index, 1); | |
| return true; | |
| } | |
| searchContacts(keyword) { | |
| if (!keyword) return []; | |
| return this.store.filter(c => | |
| c.name.includes(keyword) || | |
| c.phone.includes(keyword) || | |
| c.email.includes(keyword) | |
| ); | |
| } | |
| } | |
| // Create ONE shared instance | |
| const addressBook = new AddressBook(contacts); | |
| module.exports = { | |
| addContact: (...args) => addressBook.addContact(...args), | |
| getAllContacts: () => addressBook.getAllContacts(), | |
| findContactByPhone: phone => addressBook.findContactByPhone(phone), | |
| updateContact: (...args) => addressBook.updateContact(...args), | |
| deleteContact: phone => addressBook.deleteContact(phone), | |
| searchContacts: keyword => addressBook.searchContacts(keyword) | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // Existing global state (KEEP) | |
| // let contacts = []; | |
| // let lastId = 0; | |
| /** | |
| * NEW: AddressBook class | |
| * We introduce it but DO NOT break existing code | |
| */ | |
| class AddressBook { | |
| constructor(clock = () => new Date().toISOString()) { | |
| this.store = []; | |
| this.lastId = 0; | |
| this.clock = clock; | |
| } | |
| /** | |
| * REFACTORED METHOD | |
| * - Cleaner validation | |
| * - Single responsibility | |
| * - No behavior change | |
| */ | |
| addContact(name, phone, email, address) { | |
| if (!name || !phone) { | |
| return false; | |
| } | |
| const existing = this.store.find(c => c.phone === phone); | |
| if (existing) { | |
| return false; | |
| } | |
| this.lastId++; | |
| const contact = { | |
| id: this.lastId, | |
| name: name.trim(), | |
| phone: phone.trim(), | |
| email: email ? email.trim() : "", | |
| address: address ? address.trim() : "", | |
| createdAt: this.clock() | |
| }; | |
| this.store.push(contact); | |
| return true; | |
| } | |
| getAllContacts() { | |
| return [...this.store]; | |
| } | |
| findContactByPhone(phone) { | |
| if (!phone) return null; | |
| return this.store.find(c => c.phone === phone) || null; | |
| } | |
| updateContact(phone, newName, newEmail, newAddress) { | |
| const contact = this.findContactByPhone(phone); | |
| if (!contact) return false; | |
| if (newName) contact.name = newName; | |
| if (newEmail) contact.email = newEmail; | |
| if (newAddress) contact.address = newAddress; | |
| contact.updatedAt = this.clock(); | |
| return true; | |
| } | |
| deleteContact(phone) { | |
| const index = this.store.findIndex(c => c.phone === phone); | |
| if (index === -1) return false; | |
| this.store.splice(index, 1); | |
| return true; | |
| } | |
| searchContacts(keyword) { | |
| if (!keyword) return []; | |
| return this.store.filter(c => | |
| c.name.includes(keyword) || | |
| c.phone.includes(keyword) || | |
| c.email.includes(keyword) | |
| ); | |
| } | |
| } | |
| // Create ONE shared instance | |
| const addressBook = new AddressBook(); | |
| module.exports = { | |
| addContact: (...args) => addressBook.addContact(...args), | |
| getAllContacts: () => addressBook.getAllContacts(), | |
| findContactByPhone: phone => addressBook.findContactByPhone(phone), | |
| updateContact: (...args) => addressBook.updateContact(...args), | |
| deleteContact: phone => addressBook.deleteContact(phone), | |
| searchContacts: keyword => addressBook.searchContacts(keyword) | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /************************************** | |
| * ADDRESS BOOK (REFACTORED VERSION) | |
| * Behavior preserved, design improved | |
| **************************************/ | |
| class AddressBook { | |
| constructor(clock = () => new Date().toISOString()) { | |
| this.contacts = []; | |
| this.lastId = 0; | |
| this.clock = clock; | |
| } | |
| addContact(name, phone, email, address) { | |
| if (!name) return false; | |
| if (!phone) return false; | |
| if (this.contacts.some(c => c.phone === phone)) { | |
| return false; | |
| } | |
| this.lastId++; | |
| this.contacts.push({ | |
| id: this.lastId, | |
| name, | |
| phone, | |
| email: email || "", | |
| address: address || "", | |
| createdAt: this.clock() | |
| }); | |
| return true; | |
| } | |
| getAllContacts() { | |
| return [...this.contacts]; | |
| } | |
| findContactByPhone(phone) { | |
| if (!phone) return null; | |
| return this.contacts.find(c => c.phone === phone) || null; | |
| } | |
| updateContact(phone, newName, newEmail, newAddress) { | |
| const contact = this.findContactByPhone(phone); | |
| if (!contact) return false; | |
| if (newName) contact.name = newName; | |
| if (newEmail) contact.email = newEmail; | |
| if (newAddress) contact.address = newAddress; | |
| contact.updatedAt = this.clock(); | |
| return true; | |
| } | |
| deleteContact(phone) { | |
| const index = this.contacts.findIndex(c => c.phone === phone); | |
| if (index === -1) return false; | |
| this.contacts.splice(index, 1); | |
| return true; | |
| } | |
| searchContacts(keyword) { | |
| if (!keyword) return []; | |
| return this.contacts.filter(c => | |
| c.name.includes(keyword) || | |
| c.phone.includes(keyword) || | |
| c.email.includes(keyword) | |
| ); | |
| } | |
| } | |
| /* ---------- Backward-compatible API ---------- */ | |
| const addressBook = new AddressBook(); | |
| module.exports = { | |
| addContact: (...args) => addressBook.addContact(...args), | |
| getAllContacts: () => addressBook.getAllContacts(), | |
| findContactByPhone: phone => addressBook.findContactByPhone(phone), | |
| updateContact: (...args) => addressBook.updateContact(...args), | |
| deleteContact: phone => addressBook.deleteContact(phone), | |
| searchContacts: keyword => addressBook.searchContacts(keyword) | |
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /************************************** | |
| * ADDRESS BOOK (LEGACY / MESSY VERSION) | |
| **************************************/ | |
| let contacts = []; | |
| let lastId = 0; | |
| function addContact(name, phone, email, address) { | |
| if (name == null || name === "") { | |
| console.log("Name is required"); | |
| return false; | |
| } | |
| if (phone == null || phone === "") { | |
| console.log("Phone is required"); | |
| return false; | |
| } | |
| for (let i = 0; i < contacts.length; i++) { | |
| if (contacts[i].phone === phone) { | |
| console.log("Phone already exists"); | |
| return false; | |
| } | |
| } | |
| lastId = lastId + 1; | |
| let contact = { | |
| id: lastId, | |
| name: name, | |
| phone: phone, | |
| email: email ? email : "", | |
| address: address ? address : "", | |
| createdAt: new Date().toISOString() | |
| }; | |
| contacts.push(contact); | |
| console.log("Contact added:", contact.name); | |
| return true; | |
| } | |
| function getAllContacts() { | |
| if (contacts.length === 0) { | |
| console.log("No contacts found"); | |
| return []; | |
| } | |
| let result = []; | |
| for (let i = 0; i < contacts.length; i++) { | |
| result.push(contacts[i]); | |
| } | |
| return result; | |
| } | |
| function findContactByPhone(phone) { | |
| if (!phone) { | |
| console.log("Phone required"); | |
| return null; | |
| } | |
| for (let i = 0; i < contacts.length; i++) { | |
| if (contacts[i].phone === phone) { | |
| return contacts[i]; | |
| } | |
| } | |
| return null; | |
| } | |
| function updateContact(phone, newName, newEmail, newAddress) { | |
| let found = false; | |
| for (let i = 0; i < contacts.length; i++) { | |
| if (contacts[i].phone === phone) { | |
| if (newName) { | |
| contacts[i].name = newName; | |
| } | |
| if (newEmail) { | |
| contacts[i].email = newEmail; | |
| } | |
| if (newAddress) { | |
| contacts[i].address = newAddress; | |
| } | |
| contacts[i].updatedAt = new Date().toISOString(); | |
| console.log("Contact updated"); | |
| found = true; | |
| } | |
| } | |
| if (!found) { | |
| console.log("Contact not found"); | |
| return false; | |
| } | |
| return true; | |
| } | |
| function deleteContact(phone) { | |
| let index = -1; | |
| for (let i = 0; i < contacts.length; i++) { | |
| if (contacts[i].phone === phone) { | |
| index = i; | |
| } | |
| } | |
| if (index === -1) { | |
| console.log("Contact not found"); | |
| return false; | |
| } | |
| contacts.splice(index, 1); | |
| console.log("Contact deleted"); | |
| return true; | |
| } | |
| function searchContacts(keyword) { | |
| let results = []; | |
| if (!keyword) { | |
| return results; | |
| } | |
| for (let i = 0; i < contacts.length; i++) { | |
| if ( | |
| contacts[i].name.includes(keyword) || | |
| contacts[i].phone.includes(keyword) || | |
| contacts[i].email.includes(keyword) | |
| ) { | |
| results.push(contacts[i]); | |
| } | |
| } | |
| return results; | |
| } | |
| /************* | |
| * Demo usage | |
| *************/ | |
| if (require.main === module) { | |
| addContact('John Doe', '0700000001', 'john@email.com', 'Kampala'); | |
| addContact("Jane Smith", "0700000002", "", ""); | |
| updateContact("0700000001", "John D.", null, "Ntinda"); | |
| console.log(getAllContacts()); | |
| console.log(findContactByPhone("0700000002")); | |
| console.log(searchContacts("John")); | |
| } | |
| module.exports = { | |
| addContact, | |
| getAllContacts, | |
| findContactByPhone, | |
| updateContact, | |
| deleteContact, | |
| searchContacts | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment