组合是一个面向对象的设计概念,模型a是有关系的。在composition中,一个称为composite的类包含另一个称为component的类的对象。换句话说,一个复合类有另一个类的组件
组合允许复合类重用其包含的组件的实现。复合类不继承组件类的接口,但可以利用其实现
两类之间的构成关系被认为是松散耦合的。这意味着对组件类的更改很少会影响组合类,而对复合类的更改则永远不会影响组件类
这提供了更好的变更适应性,并允许应用程序引入新的要求而不会影响现有代码
当查看两种竞争软件设计时,一种基于继承,另一种基于组成,那么组成解决方案通常是最灵活的。您现在可以查看合成的工作原理
您已经在我们的示例中使用了合成。如果您查看Employee
类,则会看到它包含两个属性
# In contacts.py
class Address:
def __init__(self, street, city, state, zipcode, street2=''):
self.street = street
self.street2 = street2
self.city = city
self.state = state
self.zipcode = zipcode
def __str__(self):
lines = [self.street]
if self.street2:
lines.append(self.street2)
lines.append(f'{self.city}, {self.state} {self.zipcode}')
return '\n'.join(lines)
您实现了一个基本地址类,其中包含地址的常用组件。您将street2属性设置为可选,因为并非所有地址都具有该组件
您实现了__str __()
来提供地址的漂亮表示。您可以在交互式解释器中看到此实现
from contacts import Address
address = Address('55 Main St.', 'Concord', 'NH', '03301')
print(address)
当您打印address
变量时,会调用特殊的方法__str__()
。因为您重载了方法来返回一个格式化为地址的字符串,所以您得到了一个很好的、可读的表示。自定义Python类中的操作符和函数重载很好地概述了类中可用的特殊方法,这些方法可用于自定义对象的行为
# In employees.py
class Employee:
def __init__(self, id, name):
self.id = id
self.name = name
self.address = None
您现在将address
属性初始化为None
使其成为可选,但是通过这样做,您现在可以将一个Address
分配给Employee
。还请注意,employee
模块中没有对contact
模块的引用
复合是一种松散耦合的关系,通常不需要复合类具有组件的知识
# In hr.py
class PayrollSystem:
def calculate_payroll(self, employees):
print('Calculating Payroll')
print('===================')
for employee in employees:
print(f'Payroll for: {employee.id} - {employee.name}')
print(f'- Check amount: {employee.calculate_payroll()}')
if employee.address:
print('- Sent to:')
print(employee.address)
print('')
您检查以查看雇员对象是否有地址,如果有,则将其打印出来。现在,您可以修改程序以为员工分配一些地址
# In program.py
import hr
import employees
import productivity
import contacts
manager = employees.Manager(1, 'Mary Poppins', 3000)
manager.address = contacts.Address(
'121 Admin Rd',
'Concord',
'NH',
'03301'
)
secretary = employees.Secretary(2, 'John Smith', 1500)
secretary.address = contacts.Address(
'67 Paperwork Ave.',
'Manchester',
'NH',
'03101'
)
sales_guy = employees.SalesPerson(3, 'Kevin Bacon', 1000, 250)
factory_worker = employees.FactoryWorker(4, 'Jane Doe', 40, 15)
temporary_secretary = employees.TemporarySecretary(5, 'Robin Williams', 40, 9)
employees = [
manager,
secretary,
sales_guy,
factory_worker,
temporary_secretary,
]
productivity_system = productivity.ProductivitySystem()
productivity_system.track(employees, 40)
payroll_system = hr.PayrollSystem()
payroll_system.calculate_payroll(employees)
您向经理和秘书对象添加了两个地址。运行程序时,您将看到打印的地址
$ python program.py
Tracking Employee Productivity
==============================
Mary Poppins: screams and yells for {hours} hours.
John Smith: expends {hours} hours doing office paperwork.
Kevin Bacon: expends {hours} hours on the phone.
Jane Doe: manufactures gadgets for {hours} hours.
Robin Williams: expends {hours} hours doing office paperwork.
Calculating Payroll
===================
Payroll for: 1 - Mary Poppins
- Check amount: 3000
- 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: 1250
Payroll for: 4 - Jane Doe
- Check amount: 600
Payroll for: 5 - Robin Williams
- Check amount: 360
请注意,经理和秘书对象的工资单输出如何显示支票发送的地址
Employee
类利用Address
类的实现,而不知道Address
对象是什么或它是如何表示的。这种类型的设计非常灵活,您可以更改Address
类,而不会对Employee
类造成任何影响