设计模式与编程实践解析

内容分享9小时前发布
0 0 0

table {
border-collapse: collapse;
width: 100%;
margin-bottom: 1rem;
}
th, td {
border: 1px solid #ddd;
padding: 8px;
text-align: left;
}
th {
background-color: #f2f2f2;
}
tr:nth-child(even) {
background-color: #f9f9f9;
}
pre {
background-color: #f8f8f8;
padding: 15px;
border-radius: 4px;
overflow-x: auto;
}

24、回顾我们讨论过的模式,并思考可能实现它们的理想场景。你可能想将适配器模式应用于现有代码,因为它通常适用于与现有库进行接口交互,而非新代码。如何使用适配器来确保两个接口正确地相互交互?


适配器对象位于两个不同的接口之间,可动态地在它们之间进行转换。适配可能需要执行各种任务,如将参数转换为不同的格式、重新排列参数的顺序、调用不同名称的方法或提供默认参数。

例如,当 `Interface1` 希望调用 `make_action(some, arguments)` 方法,而现有的 `Interface2` 类提供的是 `different_action(other, arguments)` 方法时,`Adapter` 类可实现 `make_action` 接口,并将参数映射到现有的接口,这样就能让两个接口正确交互。

25、抽象工厂模式,或者更具 Python 风格的衍生模式,对于创建一键配置系统非常有用。你能想到哪些地方会用到这样的系统呢?

常见的抽象工厂模式应用场景包括:

操作系统无关的工具包

数据库后端

特定国家的格式化器或计算器

例如:

操作系统无关的 GUI 工具包可根据不同操作系统返回不同的窗口组件

Django 可根据配置设置返回与特定数据库后端交互的对象关系类

不同国家有不同的零售商品税费计算系统,抽象工厂可返回特定的税费计算对象

26、考虑组合模式。在编程中,我们周围到处都是树状结构;其中一些,就像我们的文件层次结构示例一样,显而易见;而另一些则相当隐蔽。在哪些情况下组合模式会有用呢?你能想到在自己的代码中可以使用它的地方吗?如果稍微调整一下这个模式,例如,为不同类型的对象包含不同类型的叶子节点或组合节点,会怎样呢?

组合模式的应用场景

组合模式在多种树状结构中非常有用,包括:

GUI 小部件层级结构

文件层级结构

树集

HTML DOM

在实际编程中,只要涉及树状结构的场景都可能适用,例如:

操作文件目录

构建 GUI 界面

若稍微调整模式,可以根据不同类型对象定义不同的叶子节点或组合节点。例如,在文件系统中,除了普通文件和文件夹,还可以有链接文件等特殊类型。通过定义不同节点类型,可以灵活满足各种业务需求。

27、在启动新项目或处理现有代码时,如何实践测试驱动开发?

实践测试驱动开发

这是你的首个练习。若开启新项目,做起来会更轻松;若要处理现有代码,可先为新实现的每个特性编写测试。

28、编写测试运行覆盖率报告有什么作用?测试驱动开发和为现有代码编写测试在代码覆盖率方面有什么不同特点?即使代码覆盖率达到 100% 就意味着测试了所有可能的输入吗?

在测试中运行覆盖率报告可检查是否遗漏代码行测试。测试驱动开发较易自然达到 100% 覆盖率,而为现有代码写测试则更可能存在未测试的边界条件,即便覆盖率达 100% 也不一定测试了所有可能输入。

29、请列举一些代码测试中的边缘情况,并说明考虑这些边缘情况对代码的意义。


代码测试中的边缘情况指的是那些与预期情况有所不同的值,如:

- 空列表(当期望得到满列表时却得到空列表)
- 特殊数值(与中间整数相比的零、一或无穷大)
- 不能精确舍入的浮点数
- 类型不符的值(当期望得到数字时却得到字符串)
- 无意义的值(当期望得到有意义的东西时却得到无处不在的 `None` 值)

考虑这些边缘情况对代码的意义在于,通过对这些边缘情况进行测试,可以确保代码在各种情况下都能正常工作,从而使代码处于良好状态。

30、请简要介绍 execnet、Parallel python、Cython、PyPy – STM 和 Gevent 这几个第三方库的功能。


execnet

是一个允许本地和远程无共享并发的库;


Parallel python

是一个可以并行执行线程的替代解释器;


Cython

是一种与 Python 兼容的语言,可编译为 C 语言,并有释放全局解释器锁(GIL)以利用完全并行多线程的原语;


PyPy – STM

是在超快速的 PyPy Python 解释器实现之上的软件事务内存的实验性实现;


Gevent

可用于提供额外上下文。

这些库能帮助开发者在不同场景下选择合适的并发解决方案。

31、首先,回想一下你最近完成的一个编程项目。找出设计中最突出的对象。尽可能多地为这个对象想出属性。它有以下属性吗:颜色、重量、尺寸、利润、成本、名称、ID 号、价格、样式?思考这些属性的类型。它们是基本类型还是类?是否有些属性实际上是伪装的行为?有时看似数据的东西实际上是根据对象上的其他数据计算得出的,你可以使用方法来进行这些计算。这个对象还有哪些其他方法或行为?哪些对象调用了这些方法?它们与这个对象有哪些类型的关系?


## 编程项目分析指南

需结合自身最近完成的编程项目进行思考和分析,具体步骤如下:

1. **确定最突出的对象**  
   在项目中识别出最关键、核心的对象。

2. **列举可能的属性**  
   分析该对象可能具有的各种属性。

3. **判断属性类型**  
   明确每个属性的数据类型(如字符串、整数、布尔值、对象等)。

4. **识别伪装成数据的行为**  
   找出那些看似是数据,但其实可以通过计算或逻辑处理得到的行为,并将其转化为**方法**。

5. **明确其他方法和行为**  
   确定该对象还具备哪些方法和行为。

6. **确定方法的调用者**  
   分析哪些对象会调用这些方法。

7. **梳理对象间关系**  
   明确各个对象之间的交互与依赖关系。

32、现在,尝试设计一个更大的项目。它不一定要真正实现什么功能,但要确保你尝试使用包和模块的导入语法。在不同的模块中添加一些函数,并尝试从其他模块和包中导入它们。使用相对导入和绝对导入。观察它们的区别,并尝试设想在哪些场景下你会想要使用其中一种。

可按以下步骤操作:

设计一个大项目,不要求实现具体功能。

在不同模块添加函数。

用绝对导入(指定完整路径,如

import ecommerce.products


from ecommerce.products import Product

)和相对导入(根据当前模块相对位置导入)进行模块和函数导入。

比较两者区别,绝对导入路径明确,适用于跨包、跨项目导入;相对导入简洁,适用于同一包内模块间导入。

33、看看你工作空间里的一些实体物品,看看能否用继承层次结构来描述它们。几个世纪以来,人类一直在以这种方式对世界进行分类,所以这应该不难。各类物体之间是否存在一些不明显的继承关系?如果要在计算机应用程序中对这些物体进行建模,它们会共享哪些属性和方法?哪些需要进行多态重写?它们之间哪些属性会完全不同?

可将家具作为父类,桌子、椅子作为子类构建继承层次结构。

不明显的继承关系需具体分析。

建模时共享属性如颜色、材质等。

共享方法如显示信息等。

子类可能重写父类的移动方法等。

不同属性如桌子有桌面大小,椅子有椅面高度等。

34、现在编写一些新代码。设想一个需要身份验证和授权的程序,并编写一些使用 auth 模块的代码。如果该模块不够灵活,可以随意修改它。尝试以合理的方式处理所有异常。若想不出需要身份验证的程序,可以为 auth 模块本身添加授权功能,比如在允许添加或更改权限之前需要管理员用户名和密码。

以下是一个简单示例,假设

auth

模块包含

Authenticator


Authorizor

类:


import auth

# 创建认证器和授权器实例
authenticator = auth.Authenticator()
authorizor = auth.Authorizor(authenticator)

# 添加管理员用户
try:
    authenticator.add_user('admin', 'adminpassword')
    authorizor.add_permission('manage_permissions')
    authorizor.permit_user('manage_permissions', 'admin')
except auth.UsernameAlreadyExists:
    print('管理员用户已存在')
except auth.PasswordTooShort:
    print('管理员密码太短')

# 模拟用户登录和权限检查
username = input('请输入用户名: ')
password = input('请输入密码: ')
try:
    authenticator.login(username, password)
    if authorizor.check_permission('manage_permissions', username):
        print('你有权限管理权限')
        # 这里可以添加管理权限的代码
    else:
        print('你没有权限管理权限')
except auth.InvalidUsername:
    print('用户名无效')
except auth.InvalidPassword:
    print('密码错误')

此示例仅为演示,实际应用中可能需要根据

auth

模块的具体实现进行调整。

35、为什么要将自行抛出的异常作为 API 的一部分进行设计和文档记录?

将自行抛出的异常作为 API 的一部分进行设计和文档记录,是因为异常是代码与他人代码之间极好的沟通工具。

通过这种方式,使用该 API 的开发者能够清楚地知道代码中可能抛出哪些异常,以及应该如何处理这些异常。

如果不将自行抛出的异常作为 API 的一部分进行设计和文档记录,使用该 API 的开发者可能不知道代码中会抛出哪些异常,也不知道如何处理这些异常。这可能导致程序在运行时出现未处理的异常,从而导致程序崩溃或产生不可预期的结果。

例如:

一个 API 中的某个方法可能会在输入参数不合法时抛出

IllegalArgumentException

但如果没有在 API 文档中说明这一点,调用该方法的开发者可能不会对这种情况进行处理。

当输入不合法参数时,程序就会抛出未捕获的异常而崩溃。

36、为案例研究中创建的各个类添加一些错误处理程序。它们应确保输入的是单个字符,不尝试将光标移过文件的末尾或开头,不删除不存在的字符,以及不保存没有文件名的文件。尽量想出尽可能多的边缘情况,并对其进行处理。考虑不同的处理方式;当用户尝试移过文件末尾时,是应该抛出异常,还是只停留在最后一个字符上?

可按以下思路编写代码实现:


确保输入单个字符

:在插入字符时检查输入长度是否为 1。


不将光标移过文件末尾或开头

:在移动光标时检查位置是否越界,可选择抛出异常或让光标停在边界。


不删除不存在的字符

:删除前检查位置是否合法。


不保存无文件名的文件

:保存前检查文件名是否为空。

以下是示例代码:


class Document:
    def __init__(self):
        self.characters = []
        self.cursor = Cursor(self)
        self.filename = ''

    def insert(self, character):
        if len(str(character)) != 1:
            raise ValueError('只能插入单个字符')
        self.characters.insert(self.cursor.position, character)
        self.cursor.forward()

    def delete(self):
        if self.cursor.position < 0 or self.cursor.position >= len(self.characters):
            raise IndexError('要删除的位置不存在字符')
        del self.characters[self.cursor.position]

    def save(self):
        if not self.filename:
            raise ValueError('文件名不能为空')
        with open(self.filename, 'w') as f:
            f.write(''.join(map(str, self.characters)))

class Cursor:
    def __init__(self, document):
        self.document = document
        self.position = 0

    def forward(self):
        if self.position < len(self.document.characters):
            self.position += 1
        else:
            # 这里选择让光标停在最后一个字符位置,也可选择抛出异常
            pass

    def back(self):
        if self.position > 0:
            self.position -= 1

    def home(self):
        while self.position > 0 and self.document.characters[self.position - 1] != '
':
            self.position -= 1

    def end(self):
        while self.position < len(self.document.characters) and self.document.characters[self.position] != '
':
            self.position += 1

37、学习如何选择正确数据结构的最佳方法是先犯几次错误。选取你最近编写的一些代码,或者编写一些使用列表的新代码。尝试用不同的数据结构重写它。分析哪些数据结构的使用更有意义,哪些没有意义,以及哪些代码最优雅。


选取近期编写的含列表代码或编写使用列表的新代码,用元组、命名元组、字典、集合、队列等不同数据结构重写,分析各数据结构使用时的合理性、代码优雅性,如仅需不可变序列用元组,需键值对用字典,需去重或检查成员关系用集合等。

38、你最近是否编写过一些容器对象,可以通过继承内置对象并覆盖一些“特殊”的双下划线方法来改进它们?你可能需要进行一些研究(使用

dir


help

,或者参考 Python 库文档),以确定需要覆盖哪些方法。你确定继承是合适的方法吗?基于组合的解决方案是否会更有效?如果可能的话,在做决定之前两种方法都尝试一下。尝试找出在哪些不同情况下,每种方法比另一种更好。

容器对象编写实践

需对编写过的容器对象尝试以下两种方法:


继承内置对象并覆盖双下划线方法


– 通过继承如

list


dict

等内置类型

– 覆盖其双下划线方法(如

__getitem__


__setitem__

等)

– 实现自定义行为


基于组合的解决方案


– 不使用继承

– 通过内部包含一个内置容器对象

– 通过委托方式实现所需功能

对比与分析

方法 优点 缺点 更适用场景
继承内置对象 代码简洁,行为与内置类型一致 可能带来耦合性,覆盖方法易出错 需要高度兼容内置类型行为时
基于组合 更灵活,封装性好 代码量略多,需手动委托 需要高度定制或组合多种行为时

结论

在不同场景下选择合适的方法:

若需保持与内置类型高度一致,且仅需少量扩展,

继承

是更直接的选择。

若需要更灵活的控制、避免继承带来的副作用,或需组合多个对象行为,

组合

方案更为合适。

39、如果你之前没有接触过 with 语句和上下文管理器,应该怎么做来确保文件安全关闭以及如何寻找编写自己上下文管理器的切入点?

在旧代码中,要找出所有打开文件的地方,使用

with

语句安全地关闭这些文件。编写自己的上下文管理器时,可从丑陋或重复的

try...finally

子句处着手,当需要在特定上下文中执行前置和/或后置任务时,上下文管理器都会很有用。

40、探讨一些将函数作为可调用对象传递的应用场景,以及使用

call

方法使自定义对象可调用的情况。在哪些情况下会使用一种语法,什么时候使用另一种语法更合适?

将函数作为可调用对象传递常用于事件驱动编程、回调机制等场景,如在图形工具包或异步服务器中,当某个事件发生时调用传递的函数。

使用

__call__

方法使自定义对象可调用,适用于需要将对象像函数一样调用的场景,例如实现可调用的类实例,让其表现得像函数。

当需要复用已有的函数逻辑时,传递函数更合适;当需要封装复杂逻辑并以对象形式管理时,使用

__call__

方法使对象可调用更合适。

41、尝试重构邮件列表对象,以便能为不同目的使用不同的 send_email 函数。能否用回调函数构建邮件列表,使 send_mailing 函数使用传入的任何函数?若未提供回调函数,则默认使用当前版本。

可以按以下思路操作:在

MailingList

类的构造函数中添加一个可选的回调参数,默认值为当前的

send_email

函数。在

send_mailing

方法中,使用传入的回调函数来发送邮件。示例代码如下:


class MailingList:
    def __init__(self, send_email_callback=None):
        if send_email_callback is None:
            self.send_email = self.default_send_email
        else:
            self.send_email = send_email_callback

    def default_send_email(self, subject, message, from_addr, *emails, headers=None):
        # 当前 send_email 函数的实现
        pass

    def send_mailing(self, subject, message, from_addr, *groups, headers=None):
        emails = self.emails_in_groups(*groups)
        self.send_email(subject, message, from_addr, *emails, headers=headers)

42、考虑编写一个你自己的类,该类有一个

format

方法,通过该方法自定义对象的格式化行为。

在 Python 中,

__format__

方法可用于自定义对象的格式化行为。以下是一个简单示例:


class CustomNumber:
    def __init__(self, value):
        self.value = value

    def __format__(self, format_spec):
        if format_spec == 'squared':
            return str(self.value ** 2)
        elif format_spec == 'cubed':
            return str(self.value ** 3)
        else:
            return str(self.value)

num = CustomNumber(5)
print(format(num, 'squared'))  # 输出25
print(format(num, 'cubed'))  # 输出125
print(format(num, ''))  # 输出5

在这个示例中,

CustomNumber

类的

__format__

方法根据传入的

format_spec

参数返回不同的格式化结果。

43、在线学习正则表达式中命名组、贪婪匹配与懒惰匹配以及正则表达式标志这三个特性。并思考何时适合使用这些特性,何时不适合使用这些特性。

此问题是一项学习任务,需自行在线学习正则表达式中命名组、贪婪匹配与懒惰匹配、正则表达式标志的相关知识,思考并判断合适的使用场景。

44、如果你曾经编写过一个适配器,用于从文件或数据库中加载少量数据并将其转换为对象,不妨考虑使用 pickle 来替代。尝试用多种方式进行编码:使用 pickle、文本文件或小型数据库。你觉得哪种方式最容易使用?

使用

pickle

通常较容易,因为它能直接序列化和反序列化 Python 对象,代码实现简单;文本文件需手动处理数据格式;小型数据库要考虑数据库连接、表结构和 SQL 操作。不过具体哪种方式最容易使用,会因实际需求和场景而异。

45、尝试对数据进行序列化(pickling),然后修改保存数据的类,再将序列化的数据加载到新类中。哪些操作可行?哪些不可行?是否有一种方法可以对类进行重大更改,例如重命名属性或将其拆分为两个新属性,同时仍能从旧的序列化数据中提取数据?(提示:尝试在每个对象上设置一个私有序列化版本号,并在每次更改类时更新它;然后可以在

setstate

方法中设置迁移路径。)

可以通过在每个对象上设置一个私有序列化版本号,每次更改类时更新它,并在

__setstate__

方法中设置迁移路径,来处理类重大更改后仍能从旧序列化数据提取数据。

46、测试列表推导式比 for 循环更快这一说法。使用 Python 内置的 timeit 模块来完成测试。编写两个执行相同操作的函数,一个使用列表推导式,另一个使用 for 循环。将每个函数传递给 timeit.timeit 并比较结果。同时,比较生成器和生成器表达式的执行时间。注意,除非代码要执行大量次数,例如处理巨大的输入列表或文件,否则代码不需要极快的速度。

以下是一个具体的 Python 代码示例来完成上述测试:


import timeit

# 使用列表推导式的函数
def list_comprehension():
    return [i for i in range(1000)]

# 使用 for 循环的函数
def for_loop():
    result = []
    for i in range(1000):
        result.append(i)
    return result

# 测试列表推导式的执行时间
list_comp_time = timeit.timeit(list_comprehension, number=10000)

# 测试 for 循环的执行时间
for_loop_time = timeit.timeit(for_loop, number=10000)

print(f'列表推导式执行时间: {list_comp_time} 秒')
print(f'for 循环执行时间: {for_loop_time} 秒')

# 比较结果
if list_comp_time < for_loop_time:
    print('列表推导式更快')
else:
    print('for 循环更快')

# 生成器和生成器表达式的测试
def generator_function():
    for i in range(1000):
        yield i

def generator_expression():
    return (i for i in range(1000))

generator_func_time = timeit.timeit(lambda: list(generator_function()), number=10000)
generator_expr_time = timeit.timeit(lambda: list(generator_expression()), number=10000)

print(f'生成器函数执行时间: {generator_func_time} 秒')
print(f'生成器表达式执行时间: {generator_expr_time} 秒')

if generator_func_time < generator_expr_time:
    print('生成器函数更快')
else:
    print('生成器表达式更快')

这段代码首先定义了使用列表推导式和 for 循环的函数,然后使用

timeit.timeit

函数分别测试它们的执行时间并进行比较。接着,又定义了生成器函数和生成器表达式的函数,同样使用

timeit.timeit

函数测试它们的执行时间并比较。

47、协程滥用了迭代器协议,但实际上并未实现迭代器模式。请构建一个非协程版本的代码,从日志文件中获取序列号。要求采用面向对象的方法,在类上存储额外的状态,并且创建一个可以直接替代现有协程的对象。

从日志文件获取序列号的非协程面向对象实现

要构建非协程版本的代码从日志文件获取序列号并采用面向对象方法,可定义一个类,在类的构造函数中初始化必要的状态,如文件路径等,再定义方法读取文件并提取序列号。

以下是示例代码:


class LogSerialNumberExtractor:
    def __init__(self, log_file_path):
        self.log_file_path = log_file_path

    def get_serial_number(self):
        try:
            with open(self.log_file_path, 'r') as file:
                for line in file:
                    # 假设序列号格式为 'Serial Number: XXXXXXX',这里简单示例
                    if 'Serial Number:' in line:
                        parts = line.split('Serial Number:')
                        if len(parts) > 1:
                            return parts[1].strip()
            return None
        except FileNotFoundError:
            print(f'文件 {self.log_file_path} 未找到。')
            return None

# 使用示例
log_extractor = LogSerialNumberExtractor('your_log_file.log')
serial_number = log_extractor.get_serial_number()
print(serial_number)

此代码定义了

LogSerialNumberExtractor

类,在构造函数中接收日志文件路径,

get_serial_number

方法用于读取文件并尝试提取序列号。实际应用中,需根据日志文件的具体格式调整提取逻辑。

© 版权声明

相关文章

暂无评论

none
暂无评论...