前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >使用组合自定义行为

使用组合自定义行为

作者头像
公众号---人生代码
发布2020-05-18 16:14:47
4360
发布2020-05-18 16:14:47
举报
文章被收录于专栏:人生代码

如果您的设计依赖于继承,则需要找到一种方法来更改对象的类型以更改其行为。对于组合,您只需要更改对象使用的策略

想象一下,我们的经理突然变成了按小时计酬的临时雇员。您可以通过以下方式在程序执行期间修改对象

代码语言:javascript
复制
# In program.py

from hr import PayrollSystem, HourlyPolicy
from productivity import ProductivitySystem
from employees import EmployeeDatabase

productivity_system = ProductivitySystem()
payroll_system = PayrollSystem()
employee_database = EmployeeDatabase()
employees = employee_database.employees
manager = employees[0]
manager.payroll = HourlyPolicy(55)

productivity_system.track(employees, 40)
payroll_system.calculate_payroll(employees)

该程序从EmployeeDatabase获取员工列表,并检索第一个员工,即我们想要的经理。然后,它会创建一个新的HourlyPolicy,初始化为每小时$55,并将其分配给manager对象

代码语言:javascript
复制
$ python program.py

Tracking Employee Productivity
==============================
Employee 1 - Mary Poppins:
- screams and yells for 40 hours.

Employee 2 - John Smith:
- does paperwork for 40 hours.

Employee 3 - Kevin Bacon:
- expends 40 hours on the phone.

Employee 4 - Jane Doe:
- manufactures gadgets for 40 hours.

Employee 5 - Robin Williams:
- does paperwork for 40 hours.


Calculating Payroll
===================
Payroll for: 1 - Mary Poppins
- Check amount: 2200
- Sent to:
121 Admin Rd.
Concord, NH 03301

Payroll for: 2 - John Smith
- Check amount: 1500
- Sent to:
67 Paperwork Ave
Manchester, NH 03101

Payroll for: 3 - Kevin Bacon
- Check amount: 1800.0
- Sent to:
15 Rose St
Apt. B-1
Concord, NH 03301

Payroll for: 4 - Jane Doe
- Check amount: 600
- Sent to:
39 Sole St.
Concord, NH 03301

Payroll for: 5 - Robin Williams
- Check amount: 360
- Sent to:
99 Mountain Rd.
Concord, NH 03301

在Python中选择继承和组合

到目前为止,您已经了解了在Python中继承和组合是如何工作的。您已经看到派生类继承了它们的基类的接口和实现。您还看到了组合允许您重用另一个类的实现

对于同一个问题,您已经实现了两个解决方案。第一个解决方案使用多重继承,第二个使用复合

您还看到Python的duck类型化允许您通过实现所需的接口来重用具有程序现有部分的对象。在Python中,没有必要从基类派生出要重用的类

此时,您可能会问什么时候在Python中使用继承与组合。它们都支持代码重用。继承和组合可以解决Python程序中的类似问题

一般的建议是使用在两个类之间创建较少依赖关系的关系。这种关系就是组成。不过,有时继承会更有意义。

以下部分提供了一些指导原则,帮助您在Python中的继承和组合之间做出正确的选择

继承到模型“a”关系

继承仅应用于为一个关系建模。Liskov的替换原理说,继承自Base的Derived类型的对象可以替换Base类型的对象,而无需更改程序的所需属性

Liskov的替代原则是决定继承是否是合适的设计解决方案的最重要的指导原则。不过,答案可能并非在所有情况下都是直截了当的。幸运的是,您可以使用一个简单的测试来确定您的设计是否遵循Liskov的替换原则

假设您有一个类a,它提供了一个您希望在另一个类B中重用的实现和接口。您最初的想法是可以从a派生出B,并继承接口和实现。为了确保这是正确的设计,您需要遵循以下步骤:

您有一个类矩形,它公开一个.area属性。您需要一个类Square,它也有一个.area。似乎正方形是一种特殊类型的矩形,所以您可以从它派生并利用接口和实现。

正方形是长方形,因为它的面积是由它的高乘以它的长计算出来的。约束条件是这个平方。高度和广场。长度必须相等。

它是有意义的。你可以证明这种关系,并解释为什么正方形是长方形。让我们来颠倒一下这种关系,看看它是否有意义

长方形是正方形,因为它的面积是由它的高乘以它的长计算出来的。差值就是这个矩形。高度和矩形。宽度可以独立变化

代码语言:javascript
复制
# In rectangle_square_demo.py

class Rectangle:
    def __init__(self, length, height):
        self._length = length
        self._height = height

    @property
    def area(self):
        return self._length * self._height

使用长度和高度初始化Rectangle类,它提供一个.area属性来返回该区域。长度和高度被封装,以避免直接改变它们。

代码语言:javascript
复制
# In rectangle_square_demo.py

class Square(Rectangle):
    def __init__(self, side_size):
        super().__init__(side_size, side_size)

Square类使用side_size初始化,该side_size用于初始化基类的两个组件。现在,您编写一个小程序来测试行为

代码语言:javascript
复制
# In rectangle_square_demo.py

rectangle = Rectangle(2, 4)
assert rectangle.area == 8

square = Square(2)
assert square.area == 4

print('OK!')

运行程序

代码语言:javascript
复制
$ python rectangle_square_demo.py

OK!
代码语言:javascript
复制
# In rectangle_square_demo.py

class Rectangle:
    def __init__(self, length, height):
        self._length = length
        self._height = height

    @property
    def area(self):
        return self._length * self._height

    def resize(self, new_length, new_height):
        self._length = new_length
        self._height = new_height

.resize()采用对象的new_lengthnew_width。您可以将以下代码添加到程序中,以验证其是否正常运行

代码语言:javascript
复制
# In rectangle_square_demo.py

rectangle.resize(3, 5)
assert rectangle.area == 15

print('OK!')

您调整矩形对象的大小,并断言新区域正确。您可以运行该程序以验证行为

代码语言:javascript
复制
$ python rectangle_square_demo.py

OK!

那么,如果调整正方形大小会怎样?修改程序,然后尝试修改正方形对象

代码语言:javascript
复制
# In rectangle_square_demo.py

square.resize(3, 5)
print(f'Square area: {square.area}')

将与矩形相同的参数传递给square.resize(),然后打印该区域。当你运行程序时,你会看到

代码语言:javascript
复制
$ python rectangle_square_demo.py

Square area: 15
OK!

程序显示,新的区域是15像矩形对象。现在的问题是,square对象不再满足其长度和高度必须相等的square类约束

你怎么解决这个问题?你可以尝试几种方法,但所有的方法都会很尴尬。您可以在square中覆盖.resize()并忽略height参数,但是这对于查看程序的其他部分的人来说会很混乱,因为这些部分的矩形正在被调整大小,而其中一些矩形并没有得到预期的区域,因为它们实际上是正方形。

在一个像这样的小程序中,可能很容易发现奇怪行为的原因,但是在一个更复杂的程序中,问题就更难找到了

事实是,如果能够以两种方式证明两个类之间的继承关系,就不应该从另一个类派生出另一个类

在本例中,SquareRectangle继承.resize()的接口和实现是没有意义的。这并不意味着方形对象不能调整大小。这意味着接口是不同的,因为它只需要一个side_size参数

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-05-15,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 CryptoCode 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 在Python中选择继承和组合
  • 继承到模型“a”关系
相关产品与服务
云开发 CloudBase
云开发(Tencent CloudBase,TCB)是腾讯云提供的云原生一体化开发环境和工具平台,为200万+企业和开发者提供高可用、自动弹性扩缩的后端云服务,可用于云端一体化开发多种端应用(小程序、公众号、Web 应用等),避免了应用开发过程中繁琐的服务器搭建及运维,开发者可以专注于业务逻辑的实现,开发门槛更低,效率更高。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档