AI 驱动的开发者(MEAP)(一)(3)https://developer.aliyun.com/article/1516312
4.1.4 调整折旧策略。
在我们尝试创建DepreciationStrategy
类之前,让我们回顾一下上一章创建的类图。
图 4.6 接口类DepreciationStrategy
定义了在我们的领域模型中计算资产折旧的功能。有四个具体的类代表了我们如何对资产进行折旧。
这个类的实现包含相当复杂的隐藏细节。如果不给 Copilot 提供非常具体的指令来执行计算,Copilot 将无法提供正确的算法。
列表 4.7 不完整和不准确的提示,以便 Copilot 创建DepreciationStrategies
# Define an interface called DepreciationStrategy. # It should have four concrete implementations of the interface: StraightLineDepreciationStrategy, DecliningBalanceDepreciationStrategy, DoubleDecliningDepreciationStrategy, and NoDepreciationStrategy. # Each implementation overrides the calculate_depreciation() method to provide a specific way of calculating depreciation for an asset based on its funding details. # The calculate_depreciation() method should take a FundingDetails object as a parameter and return a float value representing the depreciation amount.
这个提示将返回一些乍一看似乎正确的方法。然而,它们并不是。例如,如果我们检查直线折旧方法,我们会发现它既没有在计算中包括残值,也没有使用有用寿命。
列表 4.8 直线折旧的不正确实现
class StraightLineDepreciationStrategy(DepreciationStrategy): def calculate_depreciation(self, funding_details: FundingDetails) -> float: return funding_details.get_asset().get_cost() * funding_details.get_depreciation_rate()
我们应该使用实际的公式来更新提示以修正这一点。谢天谢地,我们不需要提供精确的语法,只需要提供计算公式。Copilot 将转换我们的算法以验证 Python 代码。更新后的提示在列表 4.9 中。
列表 4.9 是一个准确的提示,其中包含我们折旧方法的正确计算。
# Define an interface called DepreciationStrategy. # It should have four concrete implementations of the interface: StraightLineDepreciationStrategy, DecliningBalanceDepreciationStrategy, DoubleDecliningDepreciationStrategy, and NoDepreciationStrategy. # Each implementation overrides the calculate_depreciation() method to provide a specific way of calculating depreciation for an asset based on its funding details. # The calculate_depreciation() method should take a FundingDetails object as a parameter and return a float value representing the depreciation amount. # NoDepreciationStrategy should return 0 for all assets. # The other three strategies should return the depreciation amount based on the following formulas: # - Straight Line: (cost - salvage value) / useful_life # - Declining Balance: cost * (1-rate/100)^(current_year - purchase_year) # - Double Declining: Declining Balance * 2
请注意我们可以使用伪代码。Copilot 会处理剩下的事情。
列表 4.10 是我们的提示输出,其中包含正确计算我们折旧方法的结果。
from abc import ABC, abstractmethod from itam.domain.funding_details import FundingDetails from math import pow class DepreciationStrategy(ABC): @abstractmethod def calculate_depreciation(self, funding_details: FundingDetails) -> float: pass class StraightLineDepreciationStrategy(DepreciationStrategy): def calculate_depreciation(self, funding_details: FundingDetails) -> float: cost = funding_details.get_asset().get_cost() salvage_value = funding_details.get_asset().get_salvage_value() useful_life = funding_details.get_asset().get_useful_life() return (cost - salvage_value) / useful_life class DecliningBalanceDepreciationStrategy(DepreciationStrategy): def calculate_depreciation(self, funding_details: FundingDetails) -> float: cost = funding_details.get_asset().get_cost() depreciation_rate = funding_details.get_depreciation_rate() return cost * pow(1 - (depreciation_rate / 100), datetime.now().year - asset.get_purchase_date().year) class DoubleDecliningDepreciationStrategy(DepreciationStrategy): def calculate_depreciation(self, funding_details: FundingDetails) -> float: cost = funding_details.get_asset().get_cost() depreciation_rate = funding_details.get_depreciation_rate() return DecliningBalanceDepreciationStrategy().calculate_depreciation(funding_details) * 2 class NoDepreciationStrategy(DepreciationStrategy): def calculate_depreciation(self, asset: Asset) -> float: return 0
还有许多其他类未定义。因此,您可以自由地通过实现缺失的类来练习完善您的提示工程技能。例如,您可以添加Organizations
、MaintenanceSchedule
、Address
、UsageStatistics
、User
、Role
和Warranty
。
在接下来的部分,我们将使用 Copilot 将模式应用到我们的代码中,首先是一个熟悉的模式。
4.2 编织模式、模式、模式
在接下来的部分,我们将用常用设计模式丰富我们的领域模型。设计模式是软件开发中经常出现的设计问题的常见解决方案。它们代表了最佳实践,并且可以通过提供一套经过验证的解决方案集来加快开发过程。设计模式不是现成的代码片段,而是有关如何以灵活并可在不同项目中重用的方式解决特定问题的通用指南。
4.2.1 访问我们的部门
我们将重新讨论的第一个模式是访问者模式。正如您可能还记得的第三章所述,您使用访问者模式来为给定类添加新的行为或更改现有行为。首先,让我们来看看它在上一章中是如何模型化的。
图 4.7 是DepartmentVIsitor
接口(更新为使用新名称,以前称为LineOfBusinessVisitor
)。此类“访问”硬件和软件,并在内部保持总成本和折旧。
让我们设计一个提示来让 Copilot 生成我们的Vistor
模式的一个实现。DepartmentVisitor
。
列表 4.11 是要求 Copilot 创建一个访问我们Asset
的Vistor
的提示。
# Define an interface called class DepartmentVisitor with the following methods: # +visit(self, asset: Asset) -> None # Additionally, define a class called class DepartmentStatisticsVisitor that implements the interface DepartmentVisitor
Copilot 会生成这个类的简单实现。
列表 4.12 是Vistor
的实现。
from abc import ABC, abstractmethod from itam.domain.asset import Asset class DepartmentVisitor(ABC): @abstractmethod def visit(self, asset: Asset) -> None: pass class DepartmentStatisticsVisitor(DepartmentVisitor): def __init__(self): self._total_cost = 0 self._total_depreciation = 0 self._total_allocation = 0 def visit(self, asset: Asset) -> None: self._total_cost += asset.get_cost() self._total_depreciation += asset.get_depreciation() self._total_allocation += asset.get_allocation() def get_total_cost(self): return self._total_cost def get_total_depreciation(self): return self._total_depreciation def get_total_allocation(self): return self._total_allocation
4.2.2 使用工厂(模式)创建对象
Factory 模式是另一种创建模式,它允许你将你试图创建的对象的一些细节抽象出来。在我们的信息技术资产管理系统中,我们有两种类型的 Assets
(当前):Hardware
和 Software
。这些对象的每一个都有其自己特有的属性。这的一个效果是我们的应用程序不需要知道具体类的细节(例如,硬件)。从应用程序的角度来看,它只是获得了一个具有附加字段的 Asset
实例。让我们看看如何让 Copilot 为我们创建一个工厂。
列表 4.13 要求 Copilot 为我们创建一个 AssetFactory
类的提示
# Define a class called AssetFactory # It should have the following methods: # +new(self, asset_type: str, asset_name: str, asset_cost: float, useful_life: int, depreciation_strategy: str, depreciation_rate: float, salvage_value: float, asset_purchase_date: str) -> Asset # When asset_type is equal to "hardware" it should delegate the call to a class called HardwareAssetFactory # When asset_type is equal to "software" it should delegate the call to a class called SoftwareAssetFactory # If it is neither "hardware" nor "software" it should raise a ValueError # When depreciation_strategy is equal to "straight_line" it should return a new instance of StraightLineDepreciationStrategy # When depreciation_strategy is equal to "double_declining_balance" it should return a new instance of DoubleDecliningDepreciationStrategy # When depreciation_strategy is equal to "none" it should return a new instance of NoDepreciationStrategy # When depreciation_strategy is equal to "declining_balance" it should return a new instance of DecliningBalanceDepreciationStrategy # The HardwareAssetFactory and the SoftwareAssetFactory should get the id from the AssetFactory on each call to new.
这个提示是我们遇到的最详细的提示之一。随着我们需求变得越来越专业,我们的提示自然会变得更加细致。我们将详细研究这段代码,从 new
方法开始。
列表 4.14 我们的 AssetFactory
类的结果代码,包含两个嵌入的工厂
from datetime import datetime from itam.domain.asset import Asset from itam.domain.hardware import Hardware from itam.domain.funding_details import FundingDetails from itam.domain.depreciation_strategy import ( DepreciationStrategy, StraightLineDepreciationStrategy, DecliningBalanceDepreciationStrategy, DoubleDecliningDepreciationStrategy, NoDepreciationStrategy ) class AssetFactory: def __init__(self): self.id = 0 def date_from_string(self, date_string: str) -> datetime: return datetime.strptime(date_string, "%Y-%m-%d") def new(self, asset_type: str, asset_name: str, asset_cost: float, useful_life: int, depreciation_strategy: str, depreciation_rate: float, salvage_value: float, asset_purchase_date: str) -> Asset: self.id += 1 purchase_date = self.date_from_string(asset_purchase_date) if asset_type == "hardware": return HardwareAssetFactory().new(self.id, asset_name, asset_cost, useful_life, depreciation_strategy, depreciation_rate, salvage_value, purchase_date) elif asset_type == "software": return SoftwareAssetFactory().new(self.id, asset_name, asset_cost, useful_life, depreciation_strategy, depreciation_rate, salvage_value, purchase_date) else: raise ValueError
新方法将根据 asset_type 参数确定需要调用哪些特定的工厂。此外,它会跟踪它创建的资产数量,确保标识符(“id”字段)始终唯一。否则,如果将标识符创建推迟到特定的工厂对象,我们将获得重复的键。
注意
此实现不是线程安全的。对 id 字段的访问应该同步。
列表 4.15 嵌入的 HardwareAssetFactory
类
class HardwareAssetFactory: def new(self, id: int, asset_name: str, asset_cost: float, useful_life: int, depreciation_strategy: str, depreciation_rate: float, salvage_value: float, purchase_date: datetime) -> Asset: a = Hardware ( id=id, name=asset_name, category="hardware", cost=asset_cost, useful_life=useful_life, status="active", salvage_value=salvage_value, purchase_date=purchase_date ) funding_details=FundingDetails( asset=a, depreciation_strategy= self._get_depreciation_strategy(depreciation_strateg, depreciation_rate=depreciation_rate, department_allocations=dict() ) a.funding_details = funding_details return a def _get_depreciation_strategy(self, depreciation_strategy: str) -> DepreciationStrategy: if depreciation_strategy == "straight_line": return StraightLineDepreciationStrategy() elif depreciation_strategy == "double_declining_balance": return DoubleDecliningDepreciationStrategy() elif depreciation_strategy == "none": return NoDepreciationStrategy() elif depreciation_strategy == "declining_balance": return DecliningBalanceDepreciationStrategy() else: raise ValueError
HardwareAssetFactory
类的新方法相对简单。此方法接受来自 AssetFactory
的参数,并尝试解析 DepreciationStrategy
,并设置一些合理的默认值。
列表 4.16 嵌入的 SoftwareAssetFactory
类
class SoftwareAssetFactory: def new(self, id: int, asset_name: str, asset_cost: float, useful_life: int, depreciation_strategy: str, depreciation_rate: float, salvage_value: float, purchase_date: datetime) -> Asset: a = Asset( id=id, name=asset_name, category="software", cost=asset_cost, useful_life=useful_life, status="active", salvage_value=salvage_value, purchase_date=purchase_date ) funding_details=FundingDetails( asset=a, depreciation_strategy=self._get_depreciation_strategy(depreciation_strategy), depreciation_rate=depreciation_rate, department_allocations=dict() ) a.funding_details = funding_details return a def _get_depreciation_strategy(self, depreciation_strategy: str) -> DepreciationStrategy: if depreciation_strategy == "straight_line": return StraightLineDepreciationStrategy() elif depreciation_strategy == "double_declining_balance": return DoubleDecliningDepreciationStrategy() elif depreciation_strategy == "none": return NoDepreciationStrategy() elif depreciation_strategy == "declining_balance": return DecliningBalanceDepreciationStrategy() else: raise ValueError
SoftwareAssetFactory
类与 HardwareAssetFactory
类几乎相同。以至于它可能存在一些问题,你可能会有重构的冲动,因为这似乎违反了 DRY 原则(不要重复你自己)。
实际上有一种更简单的方法来处理这种去重。为了做到这一点,我们将看看我们的下一个设计模式:Builder 模式。
Builder 模式
Builder 模式是一种创建型设计模式,通过逐步提供创建对象的说明,为对象的创建提供了流畅的 API。
4.2.3 指导系统如何构建
首先,我们将编写一个提示,让 Copilot 为我们创建我们的构建器:一个用于资产,一个用于 FundingDetails
。我们将让 Builder 知道,如果 asset_type
是硬件,它应该返回 Hardware
的实例。对于 Software
也是一样。
列表 4.17 提示创建 AssetBuilder
和 FundingDetailsBuilder
类
# Create a class called AssetBuilder # It should use the Builder pattern to build an Asset # Create another class called FundingDetailsBuilder # It should use the Builder pattern to build a FundingDetails # The AssetBuilder should have an embedded FundingDetailsBuilder # When the category is "hardware" the AssetBuilder should create a Hardware object # When the category is "software" the AssetBuilder should create a Software object # When depreciation_strategy is equal to "straight_line" it should return a new instance of StraightLineDepreciationStrategy # When depreciation_strategy is equal to "double_declining_balance" it should return a new instance of DoubleDecliningDepreciationStrategy # When depreciation_strategy is equal to "none" it should return a new instance of NoDepreciationStrategy # When depreciation_strategy is equal to "declining_balance" it should return a new instance of DecliningBalanceDepreciationStrategy # The variables will need to be held in local variables and then passed to the Asset and FundingDetails objects when they are created. # The final method of the AssetBuilder should return an Asset and be called build(). # The final method of the FundingDetailsBuilder should return a FundingDetails and be called build().
值得注意的一点是,所有的值都需要存储在本地变量中;否则,我们会遇到后初始化异常。AssetBuilder
的生成代码如下图所示。出于简洁起见,省略了导入语句和访问器方法。
清单 4.18 对应的AssetBuilder
类
class AssetBuilder: def __init__(self): self.id = 0 self.name = "" self.category = "" self.cost = 0.0 self.useful_life = 0 self.status = "" self.salvage_value = 0.0 self.purchase_date = datetime.now() def with_name(self, name): self.name = name return self ...
接下来,我们将检查AssetBuilder
类的build()
方法。这个方法将使用类别字段来返回Asset
的正确子类。
清单 4.19 AssetBuilder
类的build()
方法
def build(self) -> Asset: if self.category == "hardware": return Hardware( id=self.id, name=self.name, category=self.category, cost=self.cost, useful_life=self.useful_life, status=self.status, salvage_value=self.salvage_value, purchase_date=self.purchase_date ) elif self.category == "software": return Software( id=self.id, name=self.name, category=self.category, cost=self.cost, useful_life=self.useful_life, status=self.status, salvage_value=self.salvage_value, purchase_date=self.purchase_date ) else: return Asset( id=self.id, name=self.name, category=self.category, cost=self.cost, useful_life=self.useful_life, status=self.status, salvage_value=self.salvage_value, purchase_date=self.purchase_date )
现在我们可以看一下FundingDetailsBuilder
。这个类将与AssetBuilder
非常相似,只是没有多态的build()
方法。
清单 4.20 FundingDetailsBuilder
类
class FundingDetailsBuilder: def __init__(self): self.asset = None self.depreciation_strategy = "" self.depreciation_rate = 0.0 self.department_allocations = dict() def with_asset(self, asset: Asset) -> FundingDetailsBuilder: self.asset = asset return self ...
类的build()
方法实现非常简单;它只是在将参数应用到构造函数之后返回一个FundingDetails
对象的实例。
清单 4.21 FundingDetailsBuilder
类的build()
方法
def build(self) -> FundingDetails: return FundingDetails( asset=self.asset, depreciation_strategy=self.depreciation_strategy, depreciation_rate=self.depreciation_rate, department_allocations=self.department_allocations)
接下来,让我们从AssetFactory
类中提取_get_depreciation_strategy
方法,将折旧策略的名称映射到DepreciationStrategy
的实例的逻辑统一起来。
清单 4.22 包括从str
到DepreciationStrategy
的映射的FundingDetailsBuilder
类的更新的build()
方法
def _get_depreciation_strategy(self, depreciation_strategy: str) -> DepreciationStrategy: if depreciation_strategy == "straight_line": return StraightLineDepreciationStrategy() elif depreciation_strategy == "double_declining_balance": return DoubleDecliningDepreciationStrategy() elif depreciation_strategy == "none": return NoDepreciationStrategy() elif depreciation_strategy == "declining_balance": return DecliningBalanceDepreciationStrategy() else: raise ValueError def build(self) -> FundingDetails: return FundingDetails( asset=self.asset, depreciation_strategy=self._get_depreciation_strategy(depreciation_strategy), depreciation_rate=self.depreciation_rate, department_allocations=self.department_allocations )
现在我们已经编写了构建器,我们可以修改AssetFactory
以使用它们。
一个藏在明处的模式:适配器
适配器模式是一种结构设计模式,允许将目标接口与具有不兼容接口的类之间建立联系。例如,在我们的情况下,我们可以在一个称为StringDepreciationStrategyAdapter
的适配器中将str
->DepreciationStrategy
的接口形式化。
我们将更新AssetFactory
类中的提示,以使用新的构建器来构建Asset
和FundingDetails
的实例。
清单 4.23 提示更新AssetFactory
类以使用AssetBuilder
# Define a class called AssetFactory # It should have the following methods: # +new(asset_type: str, asset_name: str, asset_cost: float, useful_life: int, depreciation_strategy: str, depreciation_rate: float, salvage_value: float) -> Asset # Create a function that will take a string and return a datetime # Use the AssetBuilder to create the Asset and use the FundingDetailsBuilder to create the FundingDetails
使用新的构建器将大大减少此类的代码量。例如,new
方法现在只有 8 行长(在 Microsoft VS Code 中格式化)。
清单 4.24 提示更新AssetFactory
类以使用AssetBuilder
def new(self, asset_type: str, asset_name: str, asset_cost: float, useful_life: int, depreciation_strategy: str, depreciation_rate: float, salvage_value: float, asset_purchase_date: str) -> Asset: self.id += 1 purchase_date = self.date_from_string(asset_purchase_date) a = AssetBuilder().with_id(self.id).with_name(asset_name).with_category(asset_type).with_cost(asset_cost).with_useful_life(useful_life).with_status("active").with_salvage_value(salvage_value).with_purchase_date(purchase_date).build() f = FundingDetailsBuilder().with_asset(a).with_depreciation_strategy(depreciation_strategy).with_depreciation_rate(depreciation_rate).build() a.funding_details = f return a
4.2.4 观察变化
接下来,我们将观察者模式应用于我们的信息技术资产管理系统。这种模式的真正价值将在下一章中变得更加明显,但我们将在这里播种种子。
观察者模式
观察者模式是一种行为模式,其中 Subject 类通过通知向 Observer 类报告某些状态变化。该模式在用户界面和事件驱动系统中非常有用。
我们将在服务目录中创建一个名为AssetManager
的新类。AssetManager
类将负责与外部世界进行交互(在下一部分中会更详细地介绍)。我们将使用列表 4.22 中的提示来指导 Copilot 如何定义这个类。
列表 4.25 创建AssetManager
的简单提示
# Define a class called AssetManager # with CRUD operations for Asset
我们目前的AssetManager
实现使用列表来跟踪它管理的Assets
的生命周期。AssetManager
支持Assets
的所有 CRUD(创建、读取、更新和删除)操作。
列表 4.26 AssetManager
的实现
from itam.domain.asset import Asset class AssetManager: def __init__(self): self._assets = [] def create(self, asset: Asset) -> None: self._assets.append(asset) def read(self, asset_id: int) -> Asset: for asset in self._assets: if asset.get_id() == asset_id: return asset raise ValueError("Asset not found") def update(self, asset: Asset) -> None: for i in range(len(self._assets)): if self._assets[i].get_id() == asset.get_id(): self._assets[i] = asset return raise ValueError("Asset not found") def delete(self, asset_id: int) -> None: for i in range(len(self._assets)): if self._assets[i].get_id() == asset_id: del self._assets[i] return raise ValueError("Asset not found") def get_assets(self): return self._assets
我们将更新我们的提示,以添加通知到我们的AssetManager
类中。现在,我们不会分配任何观察者。我们会把这个留到下一章,当我们开始处理数据流时。
列表 4.27 更新后的提示,将观察者模式应用到AssetManager
# Define a class called AssetManager # with CRUD operations for Asset # and a method to notify observers
作为对我们提示的回应,如果我们重新创建整个类,Copilot 将添加通知框架,我们最终会扩展它。
列表 4.28 更新后的AssetManager
定义,启用了通知功能
from itam.domain.asset import Asset class AssetManager: def __init__(self): self._assets = [] self.observers = [] def add_observer(self, observer): self.observers.append(observer) def remove_observer(self, observer): self.observers.remove(observer) def notify_observers(self, event, asset): for observer in self.observers: observer.update(event, asset) def read(self, asset_id: int) -> Asset: for asset in self._assets: if asset.get_id() == asset_id: return asset raise ValueError("Asset not found") def create(self, asset: Asset) -> None: self.notify_observers("CREATE", asset) self._assets.append(asset) def update(self, asset: Asset) -> None: for i in range(len(self._assets)): if self._assets[i].get_id() == asset.get_id(): self._assets[i] = asset self.notify_observers("UPDATE", asset) return raise ValueError("Asset not found") def delete(self, asset_id: int) -> None: for i in range(len(self._assets)): if self._assets[i].get_id() == asset_id: del self._assets[i] self.notify_observers("DELETE", asset_id) return raise ValueError("Asset not found") def get_assets(self): return self._assets
我们将在本章的下一部分看到另外两种模式:单例模式和存储库模式。然而,在我们看到它们之前,我们需要设置一个控制器来与我们的系统交互。这将引导我们使用端口和适配器。
AI 驱动的开发者(MEAP)(一)(5)https://developer.aliyun.com/article/1516315