运行时切换子类的最优方案?现有Tkinter展示实现求优化
优化数据展示类的实现方案咨询
我正在开发一款存储数据的软件,用户可通过多种方式查看数据。当前的实现方式是定义一个父类Display,用于初始化展示数据的组件,每个子类重写show()方法来实现不同的数据展示形式。现有方案可以正常运行,但想知道是否存在更优的实现方式。
补充说明:数据来源于数据库,数据的展示格式也需要存入数据库,展示方式的变更并非仅视觉层面,还会影响我开发的其他软件的运行逻辑。
核心实现代码
import tkinter as tk from tkinter import ttk class Display(ttk.Frame): def __init__(self, master=None): ttk.Frame.__init__(self, master, relief='sunken', padding='20') self.widgets = [ ttk.Label(self, text="Data Value #1"), ttk.Label(self, text="Data Value #2"), ttk.Label(self, text="Data Value #3"), ttk.Label(self, text="Data Value #4"), ttk.Label(self, text="Data Value #5"), ttk.Label(self, text="Data Value #6"), ttk.Label(self, text="Data Value #7"), ttk.Label(self, text="Data Value #8"), ttk.Label(self, text="Data Value #9"), ttk.Label(self, text="Data Value #10"), ] def show(self): for i in self.widgets: i.pack() class Display1(Display): def show(self): for i, widget in enumerate(self.widgets): if i % 2 == 0: widget.pack() class Display2(Display): def show(self): for i, widget in enumerate(self.widgets): if i % 2 == 1: widget.pack() class Display3(Display): def show(self): for i, widget in enumerate(self.widgets): if i > 4: widget.pack() class Display4(Display): def show(self): for i, widget in enumerate(self.widgets): if i < 5: widget.pack() class Display5(Display): def show(self): for i, widget in enumerate(self.widgets): self.widgets[-i].pack() class Window(tk.Tk): def __init__(self): tk.Tk.__init__(self) # Initialize buttons to select how to view the data. self.selection = tk.StringVar(self, value="Option 1") self.display_options = [ ttk.Radiobutton(self, text="Option 1", variable=self.selection, value="Option 1", command=self.change_display), ttk.Radiobutton(self, text="Option 2", variable=self.selection, value="Option 2", command=self.change_display), ttk.Radiobutton(self, text="Option 3", variable=self.selection, value="Option 3", command=self.change_display), ttk.Radiobutton(self, text="Option 4", variable=self.selection, value="Option 4", command=self.change_display), ttk.Radiobutton(self, text="Option 5", variable=self.selection, value="Option 5", command=self.change_display), ] for i, button in enumerate(self.display_options): button.grid(row=i, column=1, padx=20) # Initialize the frame holding the data self.data_frame = Display(self) self.data_frame.grid(row=0, column=0, rowspan=10) self.data_frame.show() def change_display(self): for i in self.data_frame.pack_slaves(): i.pack_forget() self.data_frame.grid_forget() match self.selection.get(): case "Option 1": self.data_frame = Display1(self) case "Option 2": self.data_frame = Display2(self) case "Option 3": self.data_frame = Display3(self) case "Option 4": self.data_frame = Display4(self) case "Option 5": self.data_frame = Display5(self) case _: self.data_frame = Display(self) self.data_frame.show() self.data_frame.grid(row=0, column=0, rowspan=10) if __name__ == '__main__': win = Window() win.mainloop()
优化方向建议
1. 用策略模式替代子类爆炸
当前每个展示逻辑对应一个子类,新增展示方式会导致类数量急剧膨胀。可以用策略模式把展示逻辑封装成独立函数,让Display类接收策略参数来执行不同逻辑:
class Display(ttk.Frame): def __init__(self, master=None, display_strategy=None): ttk.Frame.__init__(self, master, relief='sunken', padding='20') self.widgets = [...] # 保持原有组件初始化 # 默认展示全部数据 self.display_strategy = display_strategy or (lambda widgets: [w.pack() for w in widgets]) def show(self): self.display_strategy(self.widgets) # 定义各类展示策略函数 def show_even_indices(widgets): for i, w in enumerate(widgets): if i % 2 == 0: w.pack() def show_reversed(widgets): for w in reversed(widgets): w.pack() # 在Window中切换策略 def change_display(self): # 清空旧组件 for w in self.data_frame.pack_slaves(): w.pack_forget() # 策略映射表 strategy_map = { "Option 1": show_even_indices, "Option 2": lambda w: [w.pack() for i, w in enumerate(w) if i%2==1], "Option 3": lambda w: [w.pack() for i, w in enumerate(w) if i>4], "Option 4": lambda w: [w.pack() for i, w in enumerate(w) if i<5], "Option 5": show_reversed } # 更新策略并展示 self.data_frame.display_strategy = strategy_map.get(self.selection.get(), strategy_map["Option 1"]) self.data_frame.show()
这种方式新增展示逻辑只需添加策略函数,无需创建子类,还能方便地把策略名称存入数据库,后续通过名称映射到对应函数。
2. 复用组件而非重建Frame
当前切换展示方式时会销毁旧Display实例并新建,实际上可以复用同一个对象,仅切换策略并重新渲染:
class Window(tk.Tk): def __init__(self): # ... 原有按钮初始化 self.data_frame = Display(self) self.data_frame.grid(row=0, column=0, rowspan=10) self.data_frame.show() # 预定义策略映射 self.strategy_map = { "Option 1": lambda w: [w.pack() for w in w], "Option 2": lambda w: [w.pack() for i, w in enumerate(w) if i%2==1], "Option 3": lambda w: [w.pack() for i, w in enumerate(w) if i>4], "Option 4": lambda w: [w.pack() for i, w in enumerate(w) if i<5], "Option 5": lambda w: [w.pack() for w in reversed(w)] } def change_display(self): # 清空现有已展示的组件 for w in self.data_frame.pack_slaves(): w.pack_forget() # 获取并应用当前策略 strategy = self.strategy_map.get(self.selection.get(), self.strategy_map["Option 1"]) self.data_frame.display_strategy = strategy self.data_frame.show()
这样避免了频繁创建销毁组件,提升性能,减少资源消耗。
3. 分离数据与展示组件
目前Display类同时负责组件初始化和展示逻辑,建议将数据、组件、展示逻辑分离:
class DataModel: @staticmethod def get_data(): # 模拟从数据库获取数据 return [f"Data Value #{i+1}" for i in range(10)] class Display(ttk.Frame): def __init__(self, master=None, display_strategy=None): super().__init__(master, relief='sunken', padding='20') # 默认返回全部数据 self.display_strategy = display_strategy or (lambda data: data) self.data = DataModel.get_data() self.widgets = [] def create_widgets(self, filtered_data): # 销毁旧组件 for w in self.widgets: w.destroy() # 根据筛选后的数据生成新组件 self.widgets = [ttk.Label(self, text=item) for item in filtered_data] def show(self): # 先筛选数据,再生成组件并展示 filtered_data = self.display_strategy(self.data) self.create_widgets(filtered_data) for w in self.widgets: w.pack() # 策略函数改为处理数据 def strategy_even_indices(data): return [item for i, item in enumerate(data) if i%2 ==0]
这种架构符合单一职责原则,数据、组件、展示逻辑互不耦合,后续修改数据来源或展示规则时更灵活,也方便把筛选/排序规则存入数据库直接应用到数据处理。
内容的提问来源于stack exchange,提问作者djurgen
相关产品推荐
相关产品推荐

