Python类:如何处理「未来」实例——OOP抽象层次进阶问询
嘿,很高兴看到你在OOP的路上迈出了第一步!写极简通讯录是个超棒的练习——从基础类到提升抽象层次,咱们一步一步来拆解,帮你理清思路~
你现在大概率是把所有功能(存储单个联系人、管理联系人列表、查找删除)都塞在一个Contact类里吧?其实OOP的核心思路之一就是单一职责原则——一个类只专注干一件事。咱们可以把代码拆成两个协作的类:
Contact类:只负责存储单个联系人的核心信息(姓名、电话、邮箱),再加个方便打印的__str__方法就行AddressBook类:专门负责管理多个联系人,比如添加、删除、查找、列出所有联系人这些操作
举个实际代码例子:
class Contact(object): def __init__(self, name, phone, email): self.name = name self.phone = phone self.email = email def __str__(self): return f"Name: {self.name}, Phone: {self.phone}, Email: {self.email}" class AddressBook(object): def __init__(self): self.contacts = [] # 用列表存储Contact实例 def add_contact(self, contact): if isinstance(contact, Contact): self.contacts.append(contact) print(f"Added contact: {contact.name}") else: print("Invalid contact object!") def find_contact(self, name): for contact in self.contacts: if contact.name.lower() == name.lower(): return contact return None def list_all_contacts(self): if not self.contacts: print("Address book is empty.") return print("All contacts:") for idx, contact in enumerate(self.contacts, 1): print(f"{idx}. {contact}")
这样拆分后,逻辑瞬间清晰了——以后要给通讯录加“导出到文件”的功能,直接在AddressBook里加方法就行,完全不用动Contact类,扩展性拉满。
假设你以后想扩展通讯录,区分“个人联系人”(带生日)和“工作联系人”(带公司名称),这时候就可以用继承来抽离公共部分:先做一个父类BaseContact,把所有联系人都有的属性和方法放进去,然后子类继承它并添加专属功能。
代码示例:
class BaseContact(object): def __init__(self, name, phone, email): self.name = name self.phone = phone self.email = email def get_contact_info(self): return f"Name: {self.name}, Phone: {self.phone}, Email: {self.email}" class PersonalContact(BaseContact): def __init__(self, name, phone, email, birthday): # 调用父类的初始化方法 super(PersonalContact, self).__init__(name, phone, email) self.birthday = birthday def get_contact_info(self): # 重写父类方法,添加生日信息 base_info = super(PersonalContact, self).get_contact_info() return f"{base_info}, Birthday: {self.birthday}" class WorkContact(BaseContact): def __init__(self, name, phone, email, company): super(WorkContact, self).__init__(name, phone, email) self.company = company def get_contact_info(self): base_info = super(WorkContact, self).get_contact_info() return f"{base_info}, Company: {self.company}"
这时候AddressBook完全不用修改,照样能添加这两种联系人——因为它们都是BaseContact的子类,isinstance判断会生效。而且调用get_contact_info方法时,不同子类会返回不同的内容,这就是多态的魔力:相同的调用方式,不同的执行结果。
现在AddressBook里的contacts列表是直接暴露的,外部代码可以直接写address_book.contacts.clear()清空你的通讯录,这太危险了!咱们得用封装把内部实现藏起来,只提供安全的公共方法让外部操作。
修改AddressBook,把contacts改成私有属性(Python里用双下划线__contacts):
class AddressBook(object): def __init__(self): self.__contacts = [] # 私有属性,外部无法直接访问 def add_contact(self, contact): if isinstance(contact, BaseContact): self.__contacts.append(contact) print(f"Added contact: {contact.name}") else: print("Invalid contact object!") def find_contact(self, name): for contact in self.__contacts: if contact.name.lower() == name.lower(): return contact return None # 其他方法都用self.__contacts来操作,外部只能通过这些方法修改数据
这样外部代码只能通过你定义的add_contact、find_contact等方法来操作通讯录,你还可以在方法里加验证逻辑(比如不能添加重复姓名的联系人),避免数据被意外破坏。
比如以后你想把通讯录保存到文件、甚至数据库里,别直接把读写代码写死在AddressBook里。咱们可以定义一个抽象的存储接口,然后实现不同的存储方式:
class ContactStorage(object): # 抽象方法,子类必须实现 def save_contacts(self, contacts): raise NotImplementedError("Subclasses must implement save_contacts") def load_contacts(self): raise NotImplementedError("Subclasses must implement load_contacts") class FileStorage(ContactStorage): def __init__(self, filename): self.filename = filename def save_contacts(self, contacts): with open(self.filename, 'w') as f: for contact in contacts: # 用逗号分隔的格式保存,也可以用pickle序列化 f.write(f"{contact.name},{contact.phone},{contact.email}\n") def load_contacts(self): contacts = [] try: with open(self.filename, 'r') as f: for line in f: name, phone, email = line.strip().split(',') contacts.append(BaseContact(name, phone, email)) except FileNotFoundError: pass return contacts
然后修改AddressBook,让它接受一个存储对象:
class AddressBook(object): def __init__(self, storage=None): self.__contacts = [] # 默认用文件存储,也可以传入其他存储对象 self.storage = storage or FileStorage("contacts.txt") # 初始化时加载已保存的联系人 self.__contacts = self.storage.load_contacts() def save_to_storage(self): self.storage.save_contacts(self.__contacts)
以后如果想换用数据库存储,只需要新建一个DatabaseStorage类实现ContactStorage的接口,完全不用修改AddressBook的代码——这就是依赖倒置原则,高层模块依赖抽象,而不是具体实现,代码灵活性瞬间提升。
内容的提问来源于stack exchange,提问作者Rahjael

