写代码也有“套路”-谈谈设计模式

Crossin先生

编程教室开了这么久,已经有很多人从完全零基础的小白成为了会写代码的菜鸟程序员,能够自己独立开发程序。不过到此阶段,常常会遇到瓶颈,感觉功能可以实现,但代码看起来有些别扭:

  • 代码中有很多相似的重复代码
  • 代码中有大量的 if,以至于有很长的缩进
  • 单个代码写得很长,别人很难看懂,过阵子可能自己都看不懂
  • 总会有没有考虑到的情况导致 bug
  • 修复一个 bug 又会产生新的 bug
  • 单个功能可以实现,但多个功能组合在一起就理不清
  • 如果需求发生变动,代码修改起来很麻烦
  • 有多种实现方式时不知道该用哪一种
  • 很难和其他人协作开发

上述的问题你是不是有过类似困扰?解决的办法其实也简单,就是坚持“ 多写 ”和“ 多读 ”:

  1. 多写代码。很多时候你觉得不好处理,并不是因为高深的问题,只是你对代码的基本使用还不够熟练。同样的问题,踩过坑再爬出来,反复几次自然你也知道怎么绕开了。
  2. 多读代码。除了自己写,看看别人的代码也会学到很多。包括教程里的案例、官方示例、开源项目的源码等。所谓“熟读唐诗三百首,不会作诗也会吟”嘛。

除了这两个“笨办法”外,还有样东西,对于处在这个阶段的你或许有很大启发,这就是:

设计模式

设计模式是对于软件开发中常见的一些问题所总结出的解决方案,它并不关注具体的代码怎么写,而是 代码的结构应该如何设计 ,从而让代码 更加可靠、可读、可重用、易于维护 。它不是一种严格意义上的“技术”,而是一门“经验主义”,也就是开发者经常提到的“ 最佳实践 ”。所以设计模式其实就是在前人各种踩坑经验之上,总结出的各种开发“套路”。

举几个常见的设计模式例子:

单例模式

场景举例:代码中需要一个共享的资源管理器,保证在代码只有唯一的一个实例,且代码各处都可以访问到。

如果你的代码只有一个文件,可能不会遇上这个问题。但当项目大一点之后,这个问题十分常见。你可以选择定义一些全局变量来实现。但更好的“套路”是使用单例模式:它可以保证 只创建一个对象 (第一次访问时创建,之后访问时直接返回已有对象), 并提供全局的访问

代码演示



# 单例类
class Singleton(object):
    _instance = None
    def __new__(cls, *args, **kw):
        if not cls._instance:
            cls._instance = super(Singleton, cls).__new__(cls, *args, **kw)
        return cls._instance

# 继承单例
class MyClass(Singleton):  
    a = 1

# 使用
mc1 = MyClass()
mc2 = MyClass()
print(mc1==mc2)
mc1.a += 1
print(mc2.a)

输出



True
2

之前我们的文章《Crossin:Python单例模式(Singleton)的N种实现》有专门讨论过 Python 中的单例模式实现。

工厂模式

场景举例:在一个绘图工具里,有不同的笔刷。当点击不同的笔刷按钮时,需要创建对应的笔刷供使用。

如果把每个按钮响应里都去单独创建,代码会很冗余且不利于维护。这时可创建一个“工厂”类,将创建笔刷的代码放在其中, 只需要根据传入的参数不同,返回不同的笔刷实例即可 。就如同工厂根据订单生产产品一样。

代码演示



# 笔刷工厂,具体类代码略
def pen_factory(mode):
    if mode == 'PEN':
        return Pen()
    elif mode == 'PENCIL':
        return Pencil()
    elif mode == 'BRUSH':
        return Brush()
    else:
        return None

# 使用
p = pen_factory('BRUSH')

代理模式

场景举例:开发一个论坛,允许用户在帖子中上传图片,于是需要提供保存图片的功能。

一般为了网站的访问速度,会选择第三方的图片保存服务。那么在保存图片的时候,就需要调用其提供的接口。而应用代理模式的话,就会在网站和第三方服务间增加一层。这样的好处是可以 将一些额外的处理放在代理层中 ,当需要更换第三方服务时, 不需要修改网站的逻辑 ,只要调整代理层即可。

代码演示



class ImgService:
    @abstractmethod
    def save(self, img):
        pass

# 真实类
class XYZImgService(ImgService):
    def save(self, img):
        # 调用第三方服务接口

# 代理类
class ImgServiceProxy(ImgService):
    def __init__(self):
        self.service = XYZImgService()
    def save(self, img):
        return self.service.save(img)

# 使用
def save_img(img):
    proxy = ImgServiceProxy()
    proxy.save(img)

设计模式的 六大原则

1、 开闭原则 (Open Close Principle)
对扩展开放,对修改关闭。在需求变动时,尽可能不修改原有代码,而通过扩展实现。

2、 里氏代换原则 (Liskov Substitution Principle)
在使用继承时,在子类中尽量不要重写和重载父类的方法。

3、 依赖倒转原则 (Dependence Inversion Principle)
针对接口编程,细节依赖于抽象。

4、 接口隔离原则 (Interface Segregation Principle)
降低类之间的耦合度,不依赖不必要的接口。

5、 迪米特法则 ,又称 最少知道原则 (Demeter Principle)
模块之间相互独立,对自己依赖的类需要知道的信息越少越好。

6、 合成复用原则 (Composite Reuse Principle)
尽量使用合成/聚合的方式,而不是使用继承。

具体的解读,今天没法在一篇推送中展开详述,而且这是个需要不断体会和实践的事情。首先你可以找些相关书籍读一读。设计模式有一本经典书籍:《 Design Patterns: Elements of Reusable Object-Oriented Software 》(《设计模式:可复用面向对象软件的基础》)。此书又被称作“ GoF ”(Gang of Four,四人组),四位业内大牛总结出了 23 种设计模式。

然而,我强烈 推荐新手去看这本书,因为你很可能看不懂。我推荐的是一本叫做《 Head first 设计模式 》的书,可以算作上面那本的白话入门版,对于新手来说友好许多,或许帮助更大。

开发者对于设计模式的理解大致存在这样几个阶段:一开始只关注语法和库, 不懂设计模式 ,写代码无章法;后来了解了设计模式之后,开始尝试 套用设计模式 ,懂得重构代码,但有时难免教条化或陷入过度设计的误区;等到开发经验丰富之后, 不再拘泥于所谓的“模式” ,本身写出的代码就已经契合设计的原则。

书上的设计模式是一成不变的,不可能涵盖每一种开发场景,而软件技术本身也不断发展,很多模式已经被新的语言特性所实现。因此也有很多人对于设计模式的价值存在质疑。我的看法是,如果你处在新手阶段, 学习设计模式是很好的提升方式 ,也可以开拓你的编程思维。而当你已经走上进阶之路之后, 更多的应是关注模式背后的原则 ,写出更合理的代码,而并非为了模式而模式。

当然这一切,还是都离不开足够的代码量。每个程序员都是一行行代码堆出来。

════

其他文章及回答:

学编程:如何自学Python | 新手引导 | 一图学Python

开发案例:智能防挡弹幕 | 红包提醒 | 流浪地球

欢迎搜索及关注: Crossin的编程教室

阅读 1.2k

Crossin的编程教室
本专栏旨在为编程初学者提供浅显易懂的入门指导。 另有同名公众号,及面向零基础学习者的 Python 入门教...
936 声望
192 粉丝
0 条评论
936 声望
192 粉丝
文章目录
宣传栏