天天看點

Python面向對象的另一種選擇:Composition

作者:洪較瘦不着調退役it人

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就是一松散關系。

Python面向對象的另一種選擇:Composition

接着我們輸出員工相關資訊

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)           

未完待續

Python面向對象的另一種選擇:Composition