Cél és előfeltétel
Több mezőt ritkán kapunk meg ugyanabban a pillanatban. Egy űrlap, parancssori kapcsolók vagy konfigurációs sorok fokozatosan állítják elő az adatot. A builder ezt az átmeneti állapotot képviseli, a kész objektum pedig csak ellenőrzés után jön létre. A konverziós gyakorlatra építve most teljes objektumok érvényességi határát tervezzük meg.
Az átmeneti hiány nem kész adat
A kötelező mezőt a builderben Option<T> jelölheti. A None jelentése az, hogy a hívó még nem adta meg az értéket. Ez különbözik a megadott, de hibás szövegtől vagy számtól. A kész típusban már ne szerepeljen ugyanez az opcionális mező, ha használat közben mindig szükség van rá. Így nem kell az összes későbbi műveletben a hiány lehetőségét újra kezelni.
A build visszatérési típusa Result<Kesz, Hiba>. Sikernél minden kötelező mező rendelkezésre áll, és az összefüggő szabályok is teljesülnek. Hibánál a félkész objektum nem szivárog ki sikeres eredményként. A példák a buildert érték szerint fogyasztják el; ez világossá teszi, hogy egy konkrét összeállítás befejeződött. Javítható, megőrzött builder is tervezhető, de az már más tulajdonjogi szerződés volna.
Alapérték és ellenőrzési sorrend
Az alapérték csak valóban opcionális döntéshez tartozzon. A feladat neve nélkül nem indítunk feladatot, az ismétlésszám azonban kaphat alapértéket. A nullával vagy üres szöveggel helyettesített kötelező mező összekeverné a hiányt egy tényleges felhasználói választással. Az Option dokumentációjában az ok_or alkalmas a hiány névvel ellátott hibává alakítására.
Előre rögzítsd azt is, hogy több hiba közül melyiket jelzed. Egy kezdő időpont nélkül az időablak végét még nem tudjuk értelmesen összevetni a kezdettel. Ezért a példák előbb a mezők meglétét, utána a helyi értékeket, végül a mezők kapcsolatát ellenőrzik. A hibák prioritása így ismételt futáskor is kiszámítható.
A settereink self értéket vesznek át és visszaadják a buildert. Ez lehetővé teszi a láncolást, de a metódushívás eredményét használni kell. Ha ugyanazt a mezőt kétszer állítod, a bemutatott szabály szerint az utolsó érték számít. Ezt se hagyd puszta megvalósítási véletlennek: a teszt rögzítheti, hogy az ismételt beállítás felülírás vagy hiba legyen.
A builder akkor hasznos, ha az átmeneti állapot önálló jelentést kap. Egy egyszerű, két kötelező argumentumú függvény helyére nem feltétlenül kell új építőobjektumot tenni. Gondold át, hogy a mezők neve, a sok opcionális beállítás vagy a fokozatos összeállítás teszi-e könnyebbé a hívást. A kész objektumot ezután kezeld stabil értékként: ne tedd ismét opcionálissá azt a mezőt, amelynek meglétét a build már bizonyította. Ha később bővül a konfiguráció, az új alapérték és az új ellenőrzés sorrendje is része lesz a felület viselkedésének. Ezt érdemes néhány célzott teszttel rögzíteni.
1. feladat: kötelező feladatnév
Készíts JobBuilder típust egy kötelező névvel. A build vágja le a név szélső whitespace karaktereit, de az üres eredményt utasítsa el. A kész Job már saját String nevet tartalmazzon. Külön hibaszöveg jelölje a meg nem adott és a megadott, de üres nevet. A jó név tisztított alakját is ellenőrizd.
Megoldás
#[derive(Default)]
struct JobBuilder { name: Option<String> }
#[derive(Debug)]
struct Job { name: String }
impl JobBuilder {
fn name(mut self, name: &str) -> Self { self.name = Some(name.to_owned()); self }
fn build(self) -> Result<Job, &'static str> {
let name = self.name.ok_or("hiányzó név")?;
let name = name.trim();
if name.is_empty() { return Err("üres név"); }
Ok(Job { name: name.to_owned() })
}
}
fn main() {
assert_eq!(JobBuilder::default().build().unwrap_err(), "hiányzó név");
assert_eq!(JobBuilder::default().name(" ").build().unwrap_err(), "üres név");
assert_eq!(JobBuilder::default().name(" Mentés ").build().unwrap().name, "Mentés");
}
A kész példányban nincs Option<String>. A hiányt a létrehozási határon oldottuk fel, ezért a használó már nevet kap, nem újabb ellenőrzési feladatot.
2. feladat: megnevezett alapérték
A Retries builder alapértelmezésben két újrapróbálkozást engedjen, és csak nulla és három közötti explicit értéket fogadjon el. A nulla itt tudatos választás: nincs újrapróbálkozás. A kétszer meghívott setter utolsó értéke maradjon meg. A program csak beállítást készít, sem hálózati kérést, sem tényleges ismétlést nem hajt végre.
Megoldás
struct Retries { count: u8 }
impl Default for Retries {
fn default() -> Self { Self { count: 2 } }
}
impl Retries {
fn count(mut self, n: u8) -> Self { self.count = n; self }
fn build(self) -> Result<u8, &'static str> {
if self.count <= 3 { Ok(self.count) } else { Err("túl sok próba") }
}
}
fn main() {
assert_eq!(Retries::default().build(), Ok(2));
assert_eq!(Retries::default().count(0).build(), Ok(0));
assert_eq!(Retries::default().count(1).count(3).build(), Ok(3));
assert!(Retries::default().count(4).build().is_err());
}
Az alapérték és a megengedett tartomány külön döntés. A felhasználó által megadott nullát nem írjuk felül azzal az indokkal, hogy „üresnek” látszik.
3. feladat: összefüggő időablak
A kezdő és záró egész időpont is legyen kötelező. Azonos időpontok érvényes, nulla hosszúságú ablakot alkotnak; a vég nem előzheti meg a kezdetet. Rögzítsd a hiányzó mezők hibasorrendjét is. Nem valódi órát olvasunk: a számok egy alkalmazás saját egységeit képviselik, ezért az ellenőrzés teljesen determinisztikus.
Megoldás
#[derive(Default)]
struct WindowBuilder { start: Option<u32>, end: Option<u32> }
impl WindowBuilder {
fn start(mut self, n: u32) -> Self { self.start = Some(n); self }
fn end(mut self, n: u32) -> Self { self.end = Some(n); self }
fn build(self) -> Result<(u32, u32), &'static str> {
let start = self.start.ok_or("kezdet hiányzik")?;
let end = self.end.ok_or("vég hiányzik")?;
if end < start { return Err("fordított ablak"); }
Ok((start, end))
}
}
fn main() {
assert_eq!(WindowBuilder::default().build(), Err("kezdet hiányzik"));
assert_eq!(WindowBuilder::default().start(2).build(), Err("vég hiányzik"));
assert_eq!(WindowBuilder::default().start(2).end(2).build(), Ok((2, 2)));
assert!(WindowBuilder::default().start(3).end(2).build().is_err());
}
Elfogadási feltételek
- A hiányzó és az üres név megkülönböztethető, a kész feladat pedig tisztított saját szöveget tárol.
- Az alapérték két újrapróbálkozás, az explicit nulla megmarad, az ismételt setter viselkedését állítás ellenőrzi.
- Az időablak hiányzó mezői a megadott sorrendben hibáznak, az azonos végpontok elfogadottak, a fordított ablak hibás.
Továbblépés
A builder átmeneti szabadságot enged, a kész típus viszont szűkebb állapotot képvisel. A hibák és forrásláncok leckében a rövid példahibákból olyan hibafelületet építünk, amely a jelentést és az eredeti okot is megőrzi.