2 - פרויקט מסכם - פתרון פרויקט
פתרון פרויקט עיבוד מדיניות¶
הפתרון הבא מרכז את החוזים בקובץ אחד כדי שאפשר יהיה להריצו מיד. בפרויקט ההגשה יש להעביר את טיפוסי הספרייה ל־lib ולשמור את בחירת המדיניות בתוכנית הפקודה.
#[derive(Debug)]
struct Record { name: String, value: u64 }
#[derive(Debug, PartialEq)]
enum PolicyError { Overflow, ZeroWeight }
trait Policy {
fn score(&self, value: u64) -> Result<Option<u64>, PolicyError>;
}
struct Threshold(u64);
impl Policy for Threshold {
fn score(&self, value: u64) -> Result<Option<u64>, PolicyError> {
Ok((value >= self.0).then_some(value))
}
}
struct Weight(u64);
impl Weight {
fn new(weight: u64) -> Result<Self, PolicyError> {
if weight == 0 { return Err(PolicyError::ZeroWeight); }
Ok(Self(weight))
}
}
impl Policy for Weight {
fn score(&self, value: u64) -> Result<Option<u64>, PolicyError> {
value.checked_mul(self.0).map(Some).ok_or(PolicyError::Overflow)
}
}
fn process<'a, P: Policy + ?Sized>(records: &'a [Record], policy: &P)
-> Result<Vec<(&'a str, u64)>, PolicyError>
{
let mut report = Vec::new();
for record in records {
if let Some(score) = policy.score(record.value)? {
report.push((record.name.as_str(), score));
}
}
Ok(report)
}
fn own_report(report: &[(&str, u64)]) -> Vec<(String, u64)> {
report.iter().map(|(name, score)| ((*name).to_owned(), *score)).collect()
}
fn replace_weight(current: &mut Box<dyn Policy>, weight: u64) -> Result<(), PolicyError> {
let next = Weight::new(weight)?;
*current = Box::new(next);
Ok(())
}
macro_rules! records {
($($name:expr => $value:expr),* $(,)?) => {
vec![$(Record { name: ($name).into(), value: $value }),*]
};
}
fn main() {
let input = records!["small" => 2, "large" => 10];
assert_eq!(process(&input, &Threshold(5)).unwrap(), [("large", 10)]);
assert_eq!(process(&input, &Weight::new(3).unwrap()).unwrap(), [("small", 6), ("large", 30)]);
let mut selected: Box<dyn Policy> = Box::new(Threshold(5));
assert_eq!(replace_weight(&mut selected, 0), Err(PolicyError::ZeroWeight));
assert_eq!(process(&input, selected.as_ref()).unwrap(), [("large", 10)]);
replace_weight(&mut selected, 2).unwrap();
let owned = own_report(&process(&input, selected.as_ref()).unwrap());
drop(input);
assert_eq!(owned[0], ("small".into(), 4));
let large = records!["overflow" => u64::MAX];
assert_eq!(process(&large, &Weight::new(2).unwrap()), Err(PolicyError::Overflow));
let mut calls = 0;
let generated = records!["one" => { calls += 1; 7 }];
assert_eq!(calls, 1);
assert_eq!(generated[0].value, 7);
}
החסם ?Sized מאפשר ל־process לקבל גם הפניה לאובייקט trait, ולכן אותה לולאה משמשת במסלול הגנרי ובמסלול הדינמי. סוג התוצאה קושר את השמות רק לחיי הרשומות, ולא לחיי המדיניות, משום שהמדיניות מחזירה מספרים עצמאיים.
החלפת המשקל בונה ומאמתת ערך חדש לפני שינוי current. במקרה של משקל אפס הפעולה חוזרת מוקדם והמדיניות הישנה נשארת פעילה. הדוח בבעלות נבנה רק מן הרשומות שהתקבלו, ולכן אינו משכפל שמות שנדחו.
בדיקת גלישה בודקת את חוזה Policy ולא מניחה דבר על התנהגות overflow של פרופיל הבנייה. המאקרו יוצר את ערכי הרשומות ישירות ומעריך כל ביטוי פעם אחת. במערכת גדולה יותר נפריד את בדיקות המדיניות מבדיקות בחירתה, ונוסיף מטריצת תכונות בנייה רק אם יש בפועל יכולות אופציונליות.
אפשר להרחיב את הדוח עם שדות נוספים, אבל שינוי סוג השגיאה או דרישת bound חדשה דורשים בחינת לקוחות. אין הצדקה לדרוש Clone מכל מדיניות כאשר האלגוריתם רק שואל אותה, ואין הצדקה למחיקת טיפוס בתוך הלולאה אם בחירת המדיניות כבר נעשתה בגבול החיצוני.