程式碼大全中保持鬆散耦合的demo

易先讯發表於2024-07-27

Q:

假設你有一段子程式,透過輸入僱用日期和工作級別來查詢員工每年獲得的休假數量。這段子程式被命名為LookupVacationBenefit()。假設在另一個模組裡已經有了一個employee物件,其中包含了僱用日期和工作級別,以及其他一些資訊,該模組把這種物件傳給LookupVacationBenefit()。
按照其他耦合標準,這兩個模組間的耦合關係看上去是很鬆散的。這兩個模組之間的employee連線是可見的,而且這裡只存在一個連線。現在假設你需要在第三個模組中使用 LookupVacationBenefit(),這一模組中沒有 employee 物件,但卻包含僱用日期和工作級別。這樣,LookupVacationBenefit()一下子就變得不太友好了,它無法和新的模組協同工作。
要使第三個模組能使用LookupVacationBenefit(),它就必須瞭解Employee類。它可能去臨時拼湊一個只包含兩個欄位的employee 物件,但是這就需要了解 LookupvacationBenefit()的內部機制,也就是說,只有這兩個欄位才是 LookupvacationBenefit()所需的。這樣的解決方案非常牽強,而且也十分醜陋。第二種解決方案可能是對 LookupVacationBenefit()做出修改,使它以僱用日期和工作級別作為引數,而不用employee。無論怎樣,原有的那個模組與過去相比都已經變得更不靈活了。
從這個故事得到的好訊息是,可以讓一個不友好的模組變友好,只要把它變得更靈活--在這個例子裡,這件事是透過明確以僱用日期和工作級別取代employee作為引數來實現的。

A:

在軟體設計中,保持模組間的低耦合和高內聚是提高程式碼可維護性和可擴充套件性的關鍵。針對你描述的情況,以下是一些改進 LookupVacationBenefit() 函式的建議,使其更加靈活和友好:

1. 重構函式引數

LookupVacationBenefit() 函式的引數從 employee 物件更改為所需的具體欄位(即僱用日期和工作級別)。這樣可以減少函式對 employee 物件的依賴,使其更易於在不同的模組中重用。

def LookupVacationBenefit(hire_date, job_level):
    # 計算休假數量的邏輯
    vacation_days = calculate_vacation_days(hire_date, job_level)
    return vacation_days

  

package main

import (
	"fmt"
	"time"
)

// Employee 結構體包含員工資訊
type Employee struct {
	HireDate  time.Time
	JobLevel  string
}

// LookupVacationBenefit 根據員工的僱傭日期和工作級別查詢休假天數
func LookupVacationBenefit(employee Employee) int {
	// 示例計算邏輯:根據工作年限和工作級別計算休假天數
	yearsOfService := time.Since(employee.HireDate).Hours() / (24 * 365)
	vacationDays := int(yearsOfService * 2) // 假設每年2天休假
	if employee.JobLevel == "Senior" {
		vacationDays += 5 // 高階員工額外5天
	}
	return vacationDays
}

func main() {
	employee := Employee{
		HireDate:  time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC),
		JobLevel:  "Senior",
	}

	days := LookupVacationBenefit(employee)
	fmt.Printf("Employee has %d vacation days\n", days)
}

  

package main

import (
	"fmt"
	"time"
)

// LookupVacationBenefit 根據僱傭日期和工作級別查詢休假天數
func LookupVacationBenefit(hireDate time.Time, jobLevel string) int {
	yearsOfService := time.Since(hireDate).Hours() / (24 * 365)
	vacationDays := int(yearsOfService * 2)
	if jobLevel == "Senior" {
		vacationDays += 5
	}
	return vacationDays
}

func main() {
	hireDate := time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC)
	jobLevel := "Senior"

	days := LookupVacationBenefit(hireDate, jobLevel)
	fmt.Printf("Employee has %d vacation days\n", days)
}

  

2. 使用依賴注入

透過依賴注入(Dependency Injection, DI),可以將所需的依賴(如 employee 物件)傳遞給函式或類,而不是讓函式或類自己查詢這些依賴。這有助於降低模組間的耦合度。

class VacationService:
    def __init__(self, employee):
        self.employee = employee

    def get_vacation_days(self):
        return LookupVacationBenefit(self.employee.hire_date, self.employee.job_level)

def LookupVacationBenefit(hire_date, job_level):
    # 計算休假數量的邏輯
    vacation_days = calculate_vacation_days(hire_date, job_level)
    return vacation_days

  

package main

import (
	"fmt"
	"time"
)

type Employee interface {
	GetHireDate() time.Time
	GetJobLevel() string
}

type ConcreteEmployee struct {
	HireDate  time.Time
	JobLevel  string
}

func (e *ConcreteEmployee) GetHireDate() time.Time {
	return e.HireDate
}

func (e *ConcreteEmployee) GetJobLevel() string {
	return e.JobLevel
}

func LookupVacationBenefit(employee Employee) int {
	yearsOfService := time.Since(employee.GetHireDate()).Hours() / (24 * 365)
	vacationDays := int(yearsOfService * 2)
	if employee.GetJobLevel() == "Senior" {
		vacationDays += 5
	}
	return vacationDays
}

func main() {
	employee := &ConcreteEmployee{
		HireDate:  time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC),
		JobLevel:  "Senior",
	}

	days := LookupVacationBenefit(employee)
	fmt.Printf("Employee has %d vacation days\n", days)
}

  

3. 封裝 employee 物件

如果 employee 物件包含多個欄位,但只有少數字段需要被 LookupVacationBenefit() 函式使用,可以考慮建立一個更小的封裝物件或資料傳輸物件(Data Transfer Object, DTO),只包含必要的欄位。

class EmployeeInfo:
    def __init__(self, hire_date, job_level):
        self.hire_date = hire_date
        self.job_level = job_level

def LookupVacationBenefit(employee_info):
    return calculate_vacation_days(employee_info.hire_date, employee_info.job_level)

  

4. 使用介面或抽象類

定義一個介面或抽象類來規範 employee 物件的行為,確保所有實現該介面的類都提供必要的方法和屬性。這樣可以在不修改 LookupVacationBenefit() 函式的情況下,使用不同型別的 employee 物件。

from abc import ABC, abstractmethod

class Employee(ABC):
    @abstractmethod
    def get_hire_date(self):
        pass

    @abstractmethod
    def get_job_level(self):
        pass

def LookupVacationBenefit(employee):
    return calculate_vacation_days(employee.get_hire_date(), employee.get_job_level())

  

5. 考慮使用工廠模式

如果 employee 物件的建立過程複雜,可以考慮使用工廠模式來封裝物件的建立過程。這樣,LookupVacationBenefit() 函式只需要關心如何使用 employee 物件,而不需要關心其建立過程。

class EmployeeFactory:
    @staticmethod
    def create_employee(hire_date, job_level):
        return Employee(hire_date, job_level)

def LookupVacationBenefit(hire_date, job_level):
    employee = EmployeeFactory.create_employee(hire_date, job_level)
    return calculate_vacation_days(employee.hire_date, employee.job_level)

  

透過這些方法,可以使 LookupVacationBenefit() 函式更加靈活,易於在不同的模組中重用,同時降低模組間的耦合度。

相關文章