摘要:组合是面向对象编程 (OOP) 中的一个基本概念,对于设计健壮且适应性强的软件系统至关重要。它对类之间的“has-a” 关系进行建模,与传统的基于继承的方法相比,它实现了更灵活和松散耦合的设计。
组合是面向对象编程 (OOP) 中的一个基本概念,对于设计健壮且适应性强的软件系统至关重要。它对类之间的 “has-a” 关系进行建模,与传统的基于继承的方法相比,它实现了更灵活和松散耦合的设计。
从本质上讲,组合涉及通过在复杂对象中包含其他类的对象(称为组件)来构造复杂对象。
包含一个或多个此类对象的类称为复合类。
这种设计模式允许复合类利用其组件类的功能,而无需直接从它们继承。实质上,复合类 “具有” 另一个类的一个 (或多个组件) 组件。
可 重用: 组合通过允许复合类利用其组件的实现来促进可重用性。这意味着,类可以简单地包含现有类的对象以使用其功能,而不是重写代码。松耦合:组合中 composite 类和 component 类之间的关系以松散耦合为特征。这意味着组件类的更改对复合类的影响最小,从而提高了系统的可维护性和灵活性。易于更改: 由于松散耦合,采用组合设计的软件系统更易于修改和扩展。可以引入新功能或修改现有功能,而对系统的其他部分几乎没有影响。继承的灵活性:虽然继承建立了严格的 “is-a” 关系,但组合提供了更灵活的 “has-a” 框架。这种灵活性通常使组合成为软件需求可能随时间变化的情况中的首选。继承和组合都是 OOP 中强大的设计工具,但它们的用途不同。继承最适合于一个类应扩展另一个类的功能的情况,这意味着一种强关系。另一方面,组合非常适合一个类只需要利用其他类的功能而不扩展它们的情况,这表明关系较弱。
基于组合的设计通常比基于继承的设计更灵活,更能适应变化。使用组合,可以更轻松地修改组件的使用方式或完全切换组件,而不会中断复合类的设计。
组合提供了一个动态而强大的框架,用于设计软件系统、促进可重用性、易于修改和组件之间的松散耦合。
通过在适当的情况下首选组合而不是继承,开发人员可以在其设计中获得更大的灵活性,从而更容易适应新的要求和更改。这使得组合成为面向对象编程和设计库中的宝贵工具。
让我们用一个简单的例子来说明 Python 中的组合概念。考虑我们正在为一所学校构建软件系统的场景。在这个系统中,我们有代表 Teachers 和 Departments 的类。我们没有使用继承(这意味着教师“是一个”部门),而是使用组合来模拟更现实的“有”关系,即一个部门“有”教师。
首先,我们定义一个 Teacher 类。每位教师都有一个名字和他们教授的科目。
class Teacher: def __init__(self, name, subject): self.name = name self.subject = subject def get_details(self): return f"{self.name} teaches {self.subject}."接下来,我们定义 Department 类。一个部门由多个教师组成。因此,我们使用组合来包括 Department 中的 Teacher 实例。
class Department: def __init__(self, name): self.name = name self.teachers = # Composition happens here def add_teacher(self, teacher): self.teachers.append(teacher) def get_department_details(self): details = f"Department: {self.name}\n" details += "Teachers:\n" for teacher in self.teachers: details += f"- {teacher.get_details}\n" return details在实践中使用合成现在,让我们创建一些 Teacher 实例和一个 Department 实例来查看组合的实际效果。
# Creating teacher instancesteacher1 = Teacher("Alice Smith", "Mathematics")teacher2 = Teacher("Bob Johnson", "Science")# Creating a department and adding teachers to itmath_science_department = Department("Math & Science")math_science_department.add_teacher(teacher1)math_science_department.add_teacher(teacher2)# Displaying department detailsprint(math_science_department.get_department_details)输出:
Department: Math & ScienceTeachers:- Alice Smith teaches Mathematics.- Bob Johnson teaches Science.在此示例中,我们演示了组合如何允许 Department 类使用 Teacher 类的实例,而无需从它继承。通过聚合 Teacher 对象,Department 类可以表示由更简单的实体组成的更复杂的实体。
这种方法增强了模块化,促进了可重用性,并在类之间保持了松散耦合,体现了在面向对象设计中使用组合的优势。
在此示例中,我们将演示如何在 Python 中使用 abc 模块(抽象基类)来创建一个灵活且可维护的系统,该系统同时利用组合和抽象。
我们将为一个简单的媒体播放器设计一个系统,该系统可以播放不同类型的媒体文件,展示合成(通过类关系)和抽象(通过 ABC)如何协同工作。
定义抽象基类首先,我们定义一个抽象基类 MediaFile,它概述了不同类型媒体文件的结构。这个类将使用 abc 模块来声明一个抽象方法 play,这是子类必须实现的。
from abc import ABC, abstractmethodclass MediaFile(ABC): def __init__(self, name): self.name = name @abstractmethod def play(self): pass实现具体类接下来,我们为不同的媒体类型(如 AudioFile 和 VideoFile)创建 MediaFile 类的具体实现。
class AudioFile(MediaFile): def play(self): return f"Playing audio file: {self.name}"class VideoFile(MediaFile): def play(self): return f"Playing video file: {self.name}"现在,我们设计了 MediaPlayer 类,该类使用合成来包含多个媒体文件,而不管它们的类型如何,从而展示了系统的灵活性。
class MediaPlayer: def __init__(self): self.playlist = def add_media(self, media_file: MediaFile): self.playlist.append(media_file) def play_all(self): for media in self.playlist: print(media.play)演示组合和抽象最后,我们实例化媒体文件和媒体播放器,然后将这些文件添加到播放器的播放列表中,以演示我们的系统如何运行。
# Creating instances of media filesaudio1 = AudioFile("song1.mp3")video1 = VideoFile("video1.mp4")# Creating the media playerplayer = MediaPlayer# Adding media files to the player's playlistplayer.add_media(audio1)player.add_media(video1)# Playing all media in the playlistplayer.play_all输出:
Playing audio file: song1.mp3Playing video file: video1.mp4在此系统中,合成允许 MediaPlayer 类聚合不同类型的媒体文件,展示了其处理各种媒体类型的灵活性,而无需与它们的具体实现紧密耦合。
通过使用抽象基类进行抽象,确保每种媒体类型都遵循一个通用接口,从而允许 MediaPlayer 通过统一的方法 (play) 与它们进行交互。这种设计不仅促进了可重用性和可维护性,还说明了如何将组合和抽象结合起来,在 Python 中创建一个健壮且可扩展的系统。
示例:数据科学项目的 Advanced Compensation System在软件开发领域,尤其是在数据科学领域,根据员工的项目贡献有效地管理和补偿员工对于保持积极性和确保公平薪酬至关重要。
此示例介绍了一个复杂的系统,该系统旨在通过利用面向对象编程 (OOP) 的概念来处理这些方面,特别是关注抽象和组合。
概述该系统是围绕一系列类构建的,这些类对数据科学项目任务和处理这些任务的员工进行建模。通过将项目任务抽象为类层次结构并使用组合将这些任务与员工相关联,系统可以动态且灵活地计算薪酬。
系统的核心是一个抽象基类 ProjectTask,它为数据科学项目中的所有任务定义一个通用接口。此类利用 Python 标准库中的抽象基类 (ABC) 模块在所有子类化任务中强制执行 get_effort_estimate 方法的实现。这种方法至关重要,因为它返回完成任务所需的估计工作量,构成计算员工薪酬的基础。
from abc import ABC, abstractmethodclass ProjectTask(ABC): """Represents a task within a data science project.""" @abstractmethod def get_effort_estimate(self) -> float: """Returns the effort estimate to complete the task.""" ProjectTask 是一个抽象基类 (ABC),表示数据科学项目中的通用任务。它使用 @abstractmethod 装饰器将 get_effort_estimate 声明为抽象方法。此方法旨在返回完成任务的工作量估计值,但其实现必须由子类提供。具体任务类ProjectTask 的子类表示数据科学项目中遇到的特定类型的任务,例如 DataCollectionTask、AnalysisTask 和 ModelingTask。每个类都实现了 get_effort_estimate 方法,根据特定于任务的参数(例如,数据源的数量、复杂程度或要开发的模型数量)提供独特的工作量估算计算。
DataCollectionTask 中:
class DataCollectionTask(ProjectTask): """Task related to data collection efforts.""" def __init__(self, data_sources: int): self.data_sources = data_sources def get_effort_estimate(self) -> float: return 2.0 * self.data_sourcesclass AnalysisTask(ProjectTask): """Task for data analysis.""" def __init__(self, complexity_level: int): self.complexity_level = complexity_level def get_effort_estimate(self) -> float: return 5.0 * self.complexity_level表示数据分析任务。__init__ 方法使用复杂度级别初始化对象。通过根据任务的复杂度级别返回估计值来实现get_effort_estimate。class ModelingTask(ProjectTask): """Machine Learning modeling task.""" def __init__(self, number_of_models: int): self.number_of_models = number_of_models def get_effort_estimate(self) -> float: return 10.0 * self.number_of_models表示机器学习建模任务。使用要开发的模型数进行初始化。get_effort_estimate 方法根据模型数量返回估计值。DataScienceEmployee 类表示从事数据科学项目的员工。
它由多个 ProjectTask 实例组成,反映了员工可能被分配的各种任务。除了 name 和 ID 等基本属性外,它还包括 task assignments 的 project_tasks 列表和 base_salary 字段以及可选的 bonus。
compute_compensation 方法根据员工分配的任务和工作计算员工的总薪酬,包括基本工资和与任务完成相关的任何奖金。
from typing import Optional, Listclass DataScienceEmployee: """Represents an employee working on data science projects.""" def __init__(self, name: str, id: int, project_tasks: List[ProjectTask], base_salary: float, bonus: Optional[float] = None): self.name = name self.id = id self.project_tasks = project_tasks self.base_salary = base_salary self.bonus = bonus def compute_compensation(self) -> float: total_effort = sum(task.get_effort_estimate for task in self.project_tasks) compensation = self.base_salary if self.bonus is not None: compensation += self.bonus * total_effort return compensation表示从事数据科学项目的员工。使用名称、ID、项目任务列表(ProjectTask 实例)、基本工资和可选奖金进行初始化。compute_compensation 方法根据基本工资和与已分配任务的工作量估计相关的额外奖金计算总薪酬。主要功能main 函数通过创建任务类的实例,将它们分配给员工,然后计算和显示员工的总薪酬来展示如何利用此系统。
这个实际示例展示了该系统处理各种项目任务的能力,并以反映每个员工实际贡献的方式计算薪酬。
def main: """Demonstrate the data science project management system.""" alice_tasks = [DataCollectionTask(data_sources=5), AnalysisTask(complexity_level=3)] alice = DataScienceEmployee(name="Alice", id=101, project_tasks=alice_tasks, base_salary=70000, bonus=150) bob_tasks = [ModelingTask(number_of_models=2)] bob = DataScienceEmployee(name="Bob", id=102, project_tasks=bob_tasks, base_salary=85000, bonus=300) print(f"{alice.name} has tasks with a total effort estimate of {sum(task.get_effort_estimate for task in alice_tasks)} and total compensation of ${alice.compute_compensation}.") print(f"{bob.name} has tasks with a total effort estimate of {sum(task.get_effort_estimate for task in bob_tasks)} and total compensation of ${bob.compute_compensation}.")if __name__ == "__main__": main创建任务和员工(alice 和 bob),并为每个任务分配不同的任务。根据每个员工的任务和工作量计算和打印其总薪酬。完整代码:
from abc import ABC, abstractmethodfrom typing import Optional, Listclass ProjectTask(ABC): """Represents a task within a data science project.""" @abstractmethod def get_effort_estimate(self) -> float: """Returns the effort estimate to complete the task."""class DataCollectionTask(ProjectTask): """Task related to data collection efforts.""" def __init__(self, data_sources: int): self.data_sources = data_sources def get_effort_estimate(self) -> float: # Assume each data source requires a fixed amount of effort return 2.0 * self.data_sourcesclass AnalysisTask(ProjectTask): """Task for data analysis.""" def __init__(self, complexity_level: int): self.complexity_level = complexity_level def get_effort_estimate(self) -> float: # Higher complexity increases effort linearly return 5.0 * self.complexity_levelclass ModelingTask(ProjectTask): """Machine Learning modeling task.""" def __init__(self, number_of_models: int): self.number_of_models = number_of_models def get_effort_estimate(self) -> float: # Assume each model requires a substantial amount of effort return 10.0 * self.number_of_modelsclass DataScienceEmployee: """Represents an employee working on data science projects.""" def __init__(self, name: str, id: int, project_tasks: List[ProjectTask], base_salary: float, bonus: Optional[float] = None): self.name = name self.id = id self.project_tasks = project_tasks self.base_salary = base_salary self.bonus = bonus def compute_compensation(self) -> float: """Compute the total compensation including base salary and bonus for task completion.""" total_effort = sum(task.get_effort_estimate for task in self.project_tasks) compensation = self.base_salary if self.bonus is not None: compensation += self.bonus * total_effort return compensationdef main: """Demonstrate the data science project management system.""" alice_tasks = [DataCollectionTask(data_sources=5), AnalysisTask(complexity_level=3)] alice = DataScienceEmployee(name="Alice", id=101, project_tasks=alice_tasks, base_salary=70000, bonus=150) bob_tasks = [ModelingTask(number_of_models=2)] bob = DataScienceEmployee(name="Bob", id=102, project_tasks=bob_tasks, base_salary=85000, bonus=300) print(f"{alice.name} has tasks with a total effort estimate of {sum(task.get_effort_estimate for task in alice_tasks)} and total compensation of ${alice.compute_compensation}.") print(f"{bob.name} has tasks with a total effort estimate of {sum(task.get_effort_estimate for task in bob_tasks)} and total compensation of ${bob.compute_compensation}.")if __name__ == "__main__": main这个针对这些数据科学项目的高级补偿系统说明了 OOP 原则(如抽象和组合)的力量。
通过将项目任务抽象为灵活的类层次结构,并使用组合将这些任务与员工相关联,该系统为管理项目贡献和支付员工薪酬提供了一个动态且可扩展的解决方案。
这种方法增强了代码库的模块化和可维护性,使其在出现新项目任务时更容易适应和扩展。
在数据科学项目中使用组合可以提高模块化、增强代码可重用性并简化维护。这在组件的行为可能需要灵活或可互换的数据科学项目中特别有用。
让我们考虑数据科学项目中的另一个场景,我们正在构建各种预测模型。
我们需要创建可以训练、评估并具有预测结果能力的模型。我们不是从基模型类继承,而是使用组合来插入不同的预处理和评估策略。
首先,我们定义预处理和评估策略的接口。这些接口将规定任何符合它们的策略都必须实现哪些方法。
class PreprocessingStrategy: def preprocess(self, data): raise NotImplementedError("Subclasses should implement this!")class EvaluationStrategy: def evaluate(self, model, X_test, y_test): raise NotImplementedError("Subclasses should implement this!")然后,我们实施符合这些接口的特定策略。
import numpy as npfrom sklearn.preprocessing import StandardScaler, MinMaxScalerclass StandardScalerPreprocessing(PreprocessingStrategy): def preprocess(self, data): scaler = StandardScaler return scaler.fit_transform(data)class MinMaxScalerPreprocessing(PreprocessingStrategy): def preprocess(self, data): scaler = MinMaxScaler return scaler.fit_transform(data)class RMSEEvaluation(EvaluationStrategy): def evaluate(self, model, X_test, y_test): predictions = model.predict(X_test) mse = np.mean((predictions - y_test) ** 2) return np.sqrt(mse)现在,我们将定义使用这些策略的 Model 类。我们将使用这些策略来编写我们的 PredictiveModel 类,而不是从它们继承。
class PredictiveModel: def __init__(self, model, preprocessing_strategy, evaluation_strategy): self.model = model self.preprocessing = preprocessing_strategy self.evaluation = evaluation_strategy def train(self, X_train, y_train): X_train_processed = self.preprocessing.preprocess(X_train) self.model.fit(X_train_processed, y_train) def evaluate(self, X_test, y_test): X_test_processed = self.preprocessing.preprocess(X_test) return self.evaluation.evaluate(self.model, X_test_processed, y_test)让我们看看如何将 PredictiveModel 类与不同的策略一起使用。这允许我们在不修改模型类本身的情况下更改预处理或评估策略。
from sklearn.datasets import load_bostonfrom sklearn.model_selection import train_test_splitfrom sklearn.linear_model import LinearRegression# Load and split datadata = load_bostonX_train, X_test, y_train, y_test = train_test_split(data.data, data.target, test_size=0.2, random_state=42)# Initialize model with strategiesmodel = PredictiveModel(LinearRegression, StandardScalerPreprocessing, RMSEEvaluation)# Train and evaluate the modelmodel.train(X_train, y_train)rmse = model.evaluate(X_test, y_test)print(f"RMSE: {rmse}")与继承相比,组合具有多个优势,使其成为许多情况下的首选:
灵活性: 组合式通过允许运行时更改组件的行为,在代码结构中提供更大的灵活性。您可以轻松切换系统的各个部分,而无需重新设计它们之间的接口。模块性:使用组合有助于构建高度模块化的代码。每个组件都可以独立开发和测试,从而减少整个代码库的依赖性。易于维护: 使用组合设计的系统通常更易于维护和扩展,因为系统某一部分的变化不一定会波及其他部分。避免类层次结构复杂性:继承可能会导致深奥而复杂的类层次结构,这可能会变得难以导航和管理。Composition 通过鼓励更简单、更扁平的结构来回避这一点。当您需要动态行为时:如果您的应用程序需要在运行时更改其行为,则组合允许您轻松替换组件,而无需重构整个系统。当组件可以跨多个上下文使用时: 如果应用程序的不同部分需要相同的功能,则组合允许您在各种上下文中重用组件,而无需从该组件继承。要避免继承中的 Diamond 问题,请执行以下操作:这是支持多重继承的语言中的常见问题,其中类继承自两个类,这两个类都继承自一个公共基类。Composition 通过允许受控和清晰的组件使用来消除这种情况。当您想要封装复杂结构时: 组合可以将功能分解为更小、更易于理解和使用的可管理部分,从而简化复杂系统的管理。总之,当您需要应用程序的灵活性、易于维护和模块化时,首选组合而不是继承。它有助于保持系统解耦,并促进代码更简洁、更易于理解。
来源:自由坦荡的湖泊AI一点号