代码重构(OOP)-小栗子(PyQt5)

JimyFengqi 2020-03-08

主要是为了练习下 面向对象, 不断提醒自己代码一定要写成 营销风格, 和优雅.

最近在B站上看一下关于 Python GUI 编程的内容. 恰好呢, 前不久的一个 将本地 Ecxcel 数据 发布到 Tableau Server 中, 当核心接口搞定后, 工程化领导让弄 web, 我们果断拒绝了, 不太熟前端也暂时不想学, 就用 Python 自带的 Tk 模块来写一版, 涉及安全就不能共享出来, 总体蛮简单的. 后来, 倒是引发我对 gui 的一点小兴趣. 就没事看看 PyQt5 的内容, 没事学学, 目的就是为了熟练 Pyhton 而已.

在 tk 中, 给控件绑定自定义函数, 通过 command 来实现, 还有 bind 等事件绑定. 跟 js 也是一样的. PyQt5 中, 被称为 信号与槽. 从开发的角度来看, 差不多的内容. 然后 我发现, 代码重构 是一个特别有趣, 特别有挑战的事情, 重构就意味着要 封装性更好, 代码质量高. 我是有这方面的洁癖, so, 总是喜欢不断优化, 追求完美.

这里取了一个B站视频的 小片段而已, 怎么把 乱七八糟的代码 进行 面向对象.

需求

简化了哈, 就是要自己实现一个顶层窗口, 包含3个控件, 最大化, 最小化, 退出. (蛮简单的哈) 效果图如下

代码重构(OOP)-小栗子(PyQt5)

然后就照着写, 代码并不难, 就是控件的计算可能需要绕一点而已.

from PyQt5.Qt import *
import sys

# 1. 创建应用程序的对象
app = QApplication(sys.argv)  # 接收命令行参数(整个项目都能用)

# 2. 控件的操作
# 2.1 创建控件

# 去掉边框, 标题栏
# win = QWidget(flags=Qt.FramelessWindowHint)

win = QWidget()

# 去掉标题栏 + 设置透明度
win.setWindowFlags(Qt.FramelessWindowHint)
win.setWindowOpacity(0.8)

# 2.2 设置控件
win.setWindowTitle("顶层窗口操作-案例")
win.resize(500, 300)

top_margin = 10
btn_w = 80
btn_h = 30
# 添加3个子控件, 窗口的右上角
close_btn = QPushButton(win)
close_btn.setText("关闭")
close_btn.resize(btn_w, btn_h)

# move_x: 窗口的宽度 - 控件的宽度
# close_btn_x = win.width()- close_btn.width()
close_btn_x = win.width() - btn_w
close_btn_y = top_margin

close_btn.move(close_btn_x, close_btn_y)

# 最大化按钮
max_btn = QPushButton(win)
max_btn.setText("最大化")
max_btn.resize(btn_w, btn_h)

max_btn_x = close_btn_x - btn_w
max_btn.move(max_btn_x, top_margin)

mini_btn = QPushButton(win)
mini_btn.setText("最小化")
mini_btn.resize(btn_w, btn_h)

mini_btn_x = max_btn_x - btn_w
mini_btn.move(mini_btn_x, top_margin)

# 监听: 信号与槽, 点击实现 最大化, 最小化, 关闭
close_btn.pressed.connect(win.close)


def max_normal():
    if win.isMaximized():
        win.showNormal()  # 变回普通
        max_btn.setText("最大化")
    else:
        win.showMaximized()
        max_btn.setText("恢复")


max_btn.pressed.connect(max_normal)
mini_btn.pressed.connect(win.showMinimized)

# 2.3 展示控件
win.show()

# 3. 让主程序进入死循环 (tk 里面的 mainloop)
if __name__ == '__main__':
    sys.exit(app.exec())

其实写到这里, 就会发现, 随着控件的增多, 代码逻辑其实就会混乱. 代码跟函数 放混在一起, 这是绝不能这样做的. 不然后面就维护起来非常困难了. 因此才需要重构.

代码重构

其实就是将这个 业务过程的代码, 封装为面向对象, 阅读体验更好和维护更加容易.

重构 - 封装继承 QWidget

最为直接 和 最为最要的一点就是, 封装类的结构设计. 这里首先就是要对 设计全局的 QWidget 对象进行封装, 我们全程需要用到它, 而且又有新的功能扩充或者交互, 因此, 我们首先设计的类, 必须要 继承 , 然后新功能则 重写.

OOP

掌握 继承, 就是拥有父类的所有功能, 当然, 理解 self 的本质 和 super() 也是很关键的. self 还好, 就是一个形参, 表示类对象自身嘛(属性和方法) ;

理解 super( ) 从功能上, 修复了 类搜索的 "钻石调用问题", 采用了其 mro 顺序搜索. (本质是 广度优先哦) 不清楚可以 看看之前的记录:

多继承 MRO: https://www.cnblogs.com/chenjieyouge/p/12246113.html

from PyQt5.Qt import *
import sys


class Window(QWidget):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.setWindowTitle("顶层窗口操作")
        self.resize(500, 300)

        # 去掉标题栏 + 设置透明度
        self.setWindowFlags(Qt.FramelessWindowHint)
        self.setWindowOpacity(0.8)



# 1. 创建应用程序的对象
app = QApplication(sys.argv)  # 接收命令行参数(整个项目都能用)

# win = QWidget()
win = Window()

# 先省略了后面的代码.

这种继承再重写, 追加的思维, 真的蛮用得很普遍的. 很优雅. 但我的数据工作呢. 其实更多是过程, 脚本..只能在博客上来练习面向对象...

重构 __ init __ 与 ui 连接

  • 将控件都封装在 setup_ui () 中, 并 将其置与 __ init __()
  • 将 win 全部替换为 self
class Window(QWidget):
    def __init__(self, *args, **kwargs):
        
        # 为了直接表示, 省略了这里的代码.

        # 控件相关
        self.setup_ui()

    def setup_ui(self):
        # 公共数据 (全局变量)
        top_margin = 10
        btn_w = 80
        btn_h = 30

        # 控件布局
        # 添加3个子控件, 窗口的右上角
        close_btn = QPushButton(self)
        close_btn.setText("关闭")
        close_btn.resize(btn_w, btn_h)

        close_btn_x = self.width() - btn_w
        close_btn_y = top_margin

        close_btn.move(close_btn_x, close_btn_y)

        # 最大化按钮
        max_btn = QPushButton(self)
        max_btn.setText("最大化")
        max_btn.resize(btn_w, btn_h)

        max_btn_x = close_btn_x - btn_w
        max_btn.move(max_btn_x, top_margin)

        mini_btn = QPushButton(self)
        mini_btn.setText("最小化")
        mini_btn.resize(btn_w, btn_h)

        mini_btn_x = max_btn_x - btn_w
        mini_btn.move(mini_btn_x, top_margin)

        # 监听: 信号与槽, 点击实现 最大化, 最小化, 关闭
        close_btn.pressed.connect(self.close)

        def max_normal():
            if self.isMaximized():
                self.showNormal()  # 变回普通
                max_btn.setText("最大化")
            else:
                self.showMaximized()
                max_btn.setText("恢复")

        max_btn.pressed.connect(max_normal)
        mini_btn.pressed.connect(self.showMinimized)

重构 - 全局变量

  • 将用到的全局变量, 给 self 绑定, 作为类的一部分
  • 修复
from PyQt5.Qt import *
import sys


class Window(QWidget):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.setWindowTitle("顶层窗口操作")
        self.resize(500, 300)

        # 去掉标题栏 + 设置透明度
        self.setWindowFlags(Qt.FramelessWindowHint)
        self.setWindowOpacity(0.8)


        # 公共数据 (全局变量)
        self.top_margin = 10
        self.btn_w = 80
        self.btn_h = 30

        # 控件相关, 一定要放在最后哦
        self.setup_ui()

    def setup_ui(self):
        # 控件布局
        # 添加3个子控件, 窗口的右上角
        close_btn = QPushButton(self)
        self.close_btn = close_btn  # 临时局部变量
        close_btn.setText("关闭")
        close_btn.resize(self.btn_w, self.btn_h)

        # 最大化按钮
        max_btn = QPushButton(self)
        self.max_btn = max_btn
        max_btn.setText("最大化")
        max_btn.resize(self.btn_w, self.btn_h)

        mini_btn = QPushButton(self)
        self.mini_btn = mini_btn
        mini_btn.setText("最小化")
        mini_btn.resize(self.btn_w, self.btn_h)

        # 监听: 信号与槽, 点击实现 最大化, 最小化, 关闭
        close_btn.pressed.connect(self.close)

        def max_normal():
            if self.isMaximized():
                self.showNormal()  # 变回普通
                max_btn.setText("最大化")
            else:
                self.showMaximized()
                max_btn.setText("恢复")

        max_btn.pressed.connect(max_normal)
        mini_btn.pressed.connect(self.showMinimized)

    def resizeEvent(self, QResizeEvent):

        close_btn_x = self.width() - self.btn_w
        close_btn_y = self.top_margin
        self.close_btn.move(close_btn_x, close_btn_y)

        max_btn_x = close_btn_x - self.btn_w
        self.max_btn.move(max_btn_x, self.top_margin)

        mini_btn_x = max_btn_x - self.btn_w
        self.mini_btn.move(mini_btn_x, self.top_margin)


app = QApplication(sys.argv)
win = Window()
win.show()


if __name__ == '__main__':
    sys.exit(app.exec())

完成重构

达到非常容易阅读, 和易维护的工程代码.

代码重构(OOP)-小栗子(PyQt5)

小结

  • 通过这类 GUI 程序来练习代码重构, 类的封装, 继承 (重写, 追加) 等操作
  • 加深的 self 和 super() 从本质上去理解, 非常关键对于面向对象
  • 锻炼下代码风格, 在功能实现的基础上, 如何增加可读性, 和鲁棒性, 易维护等. 我觉得更为重要.

咱说呢, 我感觉, GUI 用 纯Pyhthon 来写, 感觉还可以哦, 虽然现在 web 是主流, 但我感觉, gui 反而让我更加有成就感一点. 后面也适当记录一波学习的过程. 虽然都是搬砖, 但理解是不同的.

相关推荐

王磊的程序员之路 / 0评论 2020-06-26
lianback / 0评论 2020-05-19