Composition翻譯是組成,其實是組合
Python 中的組合
組合是一種面向對象的設計概念,它與模型具有關系。在組合中,稱為複合的類包含另一個稱為元件的類的對象。換言之,複合類具有另一個類的元件。
- 繼承的本質是靠父母的關系
- 組合的作用是利用朋友
組合允許複合類重用它所包含的元件的實作。複合類不繼承元件類接口,但它可以利用其實作。
兩個類之間的組合關系被認為是松散耦合的。這意味着對元件類的更改很少影響複合類,而對複合類的更改永遠不會影響元件類。
如何實作對象的組合
有這樣一個場景員工Employee 每個員工有位址,那麼 Address與Employee是沒有繼承關系的。但是員工有位址資訊,如何表示這種關系?通過組合
先定義一個Address類
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)
定義一個Employee員工類,它有一個屬性為address
class Employee:
def __init__(self, id, name):
self.id = id
self.name = name
self.address = None
這樣address與Employee就是一松散關系。
接着我們輸出員工相關資訊
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('')
如果員工有位址,則輸出員工的位址資訊
if employee.address:
print('- Sent to:')
print(employee.address)
組合的靈活設計
組合比繼承更靈活,因為它模拟了松散耦合的關系。對元件類的更改對複合類的影響很小或沒有影響。
可以通過提供實作這些行為的新元件來更改行為,而不是将新類添加到層次結構中。
看看上面的多重繼承示例。想象一下,新的薪資政策将如何影響設計。試着想象一下,如果需要新角色,類層次結構會是什麼樣子。正如你之前所看到的,過分依賴繼承會導緻階級爆炸。
最大的問題不在于設計中的類數量,而在于這些類之間的關系耦合程度。當引入更改時,緊密耦合的類會互相影響。
class ProductivitySystem:
def __init__(self):
self._roles = {
'manager': ManagerRole,
'secretary': SecretaryRole,
'sales': SalesRole,
'factory': FactoryRole,
}
def get_role(self, role_id):
role_type = self._roles.get(role_id)
if not role_type:
raise ValueError('role_id')
return role_type()
def track(self, employees, hours):
print('Tracking Employee Productivity')
print('==============================')
for employee in employees:
employee.work(hours)
print('')
class ManagerRole:
def perform_duties(self, hours):
return f'screams and yells for {hours} hours.'
class SecretaryRole:
def perform_duties(self, hours):
return f'does paperwork for {hours} hours.'
class SalesRole:
def perform_duties(self, hours):
return f'expends {hours} hours on the phone.'
class FactoryRole:
def perform_duties(self, hours):
return f'manufactures gadgets for {hours} hours.'
我們可以定義一批角色來實作員工薪資政策
class PayrollSystem:
def __init__(self):
self._employee_policies = {
1: SalaryPolicy(3000),
2: SalaryPolicy(1500),
3: CommissionPolicy(1000, 100),
4: HourlyPolicy(15),
5: HourlyPolicy(9)
}
def get_policy(self, employee_id):
policy = self._employee_policies.get(employee_id)
if not policy:
return ValueError(employee_id)
return policy
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('')
為每位員工保留一個内部工資政策資料庫。它公開了一個,給定一個員工,傳回其工資單政策
class PayrollPolicy:
def __init__(self):
self.hours_worked = 0
def track_work(self, hours):
self.hours_worked += hours
class SalaryPolicy(PayrollPolicy):
def __init__(self, weekly_salary):
super().__init__()
self.weekly_salary = weekly_salary
def calculate_payroll(self):
return self.weekly_salary
class HourlyPolicy(PayrollPolicy):
def __init__(self, hour_rate):
super().__init__()
self.hour_rate = hour_rate
def calculate_payroll(self):
return self.hours_worked * self.hour_rate
class CommissionPolicy(SalaryPolicy):
def __init__(self, weekly_salary, commission_per_sale):
super().__init__(weekly_salary)
self.commission_per_sale = commission_per_sale
@property
def commission(self):
sales = self.hours_worked / 5
return sales * self.commission_per_sale
def calculate_payroll(self):
fixed = super().calculate_payroll()
return fixed + self.commission
位址薄
class AddressBook:
def __init__(self):
self._employee_addresses = {
1: Address('121 Admin Rd.', 'Concord', 'NH', '03301'),
2: Address('67 Paperwork Ave', 'Manchester', 'NH', '03101'),
3: Address('15 Rose St', 'Concord', 'NH', '03301', 'Apt. B-1'),
4: Address('39 Sole St.', 'Concord', 'NH', '03301'),
5: Address('99 Mountain Rd.', 'Concord', 'NH', '03301'),
}
def get_employee_address(self, employee_id):
address = self._employee_addresses.get(employee_id)
if not address:
raise ValueError(employee_id)
return address
AddressBook有一堆屬性 _employee_addresses這個維護了員工id與位址對象的映射關系
get_employee_address可以根據員工id擷取其位址,這樣員工其實與位址之間沒有直接的關系,維持了一種松散的關系。
繼續優化Address類
__str__方法自己實作之後,當我們列印address對象時,會以更好的形式輸出
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)