我有一个非常数据驱动的程序,其中包含不同类型的实体,这些实体的结构非常相似,只是在特定位置不同。
例如,每个实体都有一个可以更改的名称。 two example methods展示了这些方法可能如何相似:
pub fn rename_blueprint(
&mut self,ctx: &mut Context,db_handle: &Transaction,blueprint_id: Uuid,new_name: &str,) -> Result<(),DataError> {
ctx.debug(format!(
"Renaming blueprint {} to {}",blueprint_id,new_name
));
self.assert_blueprint_exists(ctx,db_handle,blueprint_id)?;
let mut stmt = db_handle
.prepare("UPDATE `blueprints` SET `name` = ? WHERE `id` == ?")
.on_err(|_| ctx.err("Unable to prepare update statement"))?;
let changed_rows = stmt
.execute(params![new_name.to_string(),blueprint_id])
.on_err(|_| ctx.err("Unable to update name in database"))?;
if changed_rows != 1 {
ctx.err(format!("Invalid amount of rows changed: {}",changed_rows));
return Err(DataError::InvalidChangeCount {
changes: changed_rows,expected_changes: 1,});
}
ctx.blueprint_renamed(blueprint_id,new_name);
Ok(())
}
pub fn rename_attribute(
&mut self,attribute_id: Uuid,DataError> {
ctx.debug(format!(
"Renaming attribute {} to {}",attribute_id,new_name
));
self.assert_attribute_exists(ctx,attribute_id)?;
let mut stmt = db_handle
.prepare("UPDATE `attributes` SET `name` = ? WHERE `id` == ?")
.on_err(|_| ctx.err("Unable to prepare update statement"))?;
let changed_rows = stmt
.execute(params![new_name.to_string(),attribute_id])
.on_err(|_| ctx.err("Unable to update name in database"))?;
if changed_rows != 1 {
ctx.err(format!("Invalid amount of rows changed: {}",});
}
ctx.attribute_renamed(attribute_id,new_name);
Ok(())
}
对于5-11种更多类型的实体,现在需要使用具有几乎相同代码的相同方法。我通常可以将Blueprint
替换为其他实体类型的名称,这一切都可以工作。但是,这似乎是一个愚蠢的解决方案。
同样,编写一个接受所有相关字符串,方法之类的帮助器方法似乎也很愚蠢。
我不认为我可以通过传入一些“策略”或其他间接帮助器(EntityRenamer
或类似的东西)来避免这种情况,因为无论如何都需要在其中编码逻辑。只是将问题向上移动了。
应该指出,这是较短的方法之一。实体也可以移动,删除,创建等。所有实体都具有相似的代码-有时长30+行。
How to avoid code duplication of different structs with semantically equal fields/properties?无法解决我的问题。这个问题基本上是在问“当不存在继承时如何进行继承”,而我的代码却在努力将非常相似的逻辑集体化为最低公分母。特性或常见的实现方式不会解决我的问题,因为代码仍然存在-只能将其移动到其他地方。
您将如何去重复此代码?
我正在寻找指导方针,而不是为我编写代码的人。一些可能的解决方案可能是:
-
使用宏,然后使用类似
entity_rename_impl!(args)
的宏
-
对于每个可能因函数而异的特定事物,使用具有不同参数的辅助方法
-
不要尝试抽象整个方法,而应该专注于为较小的事物编写辅助函数,以便这些方法可以重复,但是在其他地方抽象的代码很少
MCVE(playground):
#![allow(unused)]
pub struct Transaction {}
impl Transaction {
pub fn execute_sql(&self,sql: &str) -> i32 {
// .. do something in the database
0
}
pub fn bind_id(&self,id: Uuid) {}
}
#[derive(Clone,Copy)]
pub struct Uuid {}
impl std::fmt::Display for Uuid {
fn fmt(&self,f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f,"mockup")
}
}
pub fn assert_blueprint_exists(blueprint_id: Uuid) {}
pub fn track_blueprint_rename(id: Uuid,new_name: String) {}
pub fn assert_attribute_exists(blueprint_id: Uuid) {}
pub fn track_attribute_rename(id: Uuid,new_name: String) {}
pub fn rename_blueprint(
db_handle: &Transaction,String> {
println!("Renaming blueprint {} to {}",new_name);
assert_blueprint_exists(blueprint_id);
db_handle.bind_id(blueprint_id);
let changed_rows = db_handle.execute_sql("UPDATE `blueprints` SET `name` = ? WHERE `id` == ?");
if changed_rows != 1 {
println!("Invalid amount of rows changed: {}",changed_rows);
return Err("Invalid change count in blueprint rename".to_string());
}
track_blueprint_rename(blueprint_id,new_name.to_string());
Ok(())
}
pub fn rename_attribute(
db_handle: &Transaction,String> {
println!("Renaming attribute {} to {}",new_name);
assert_attribute_exists(attribute_id);
db_handle.bind_id(attribute_id);
let changed_rows = db_handle.execute_sql("UPDATE `attributes` SET `name` = ? WHERE `id` == ?");
if changed_rows != 1 {
println!("Invalid amount of rows changed: {}",changed_rows);
return Err("Invalid change count in attribute rename".to_string());
}
track_attribute_rename(attribute_id,new_name.to_string());
Ok(())
}