I'd like to use Factory
to build an object from the String
and have multiple impls: 1) actual building and 2) caching (stores in-memory in HashMap
). The problem is that in case #1 it have to pass the ownership and in case #2 HashMap owns the value and a reference can be returned only.
use std::collections::HashMap;
// product interface
pub trait TProduct {
fn get_title(&self) -> &String;
}
// and concrete impls
pub struct ConcreteProduct1 {
}
impl TProduct for ConcreteProduct1 {
// ...
}
pub struct ConcreteProduct2 {
}
impl TProduct for ConcreteProduct2 {
// ...
}
// factory interface
pub trait TProductFactory {
fn product_from_text(&mut self, text: String) -> Box<dyn TProduct>;
// QUESTION: should it be Box (required for ProductFactory) or &Box (required for ProductCachingProxy)?
}
// actual building factory
pub struct ProductFactory {
}
impl TProductFactory for ProductFactory {
fn product_from_text(&mut self, text: String) -> Box<dyn TProduct> {
//...
// depending on some conditions
Box::new(ConcreteProduct1::from_text(text)); // has to pass the ownership
// or
Box::new(ConcreteProduct2::from_text(text)); // has to pass the ownership
//...
}
}
// caching proxy
trait TProductCache {
fn put(&mut self, text: &String, product: Box<dyn TProduct>);
fn get(&self, text: &String) -> Option<&Box<dyn TProduct>>;
fn clear(&mut self);
}
struct InMemoryProductCache {
map: HashMap<String, Box<dyn TProduct>>
}
impl InMemoryProductCache {
fn new() -> Self {
return InMemoryProductCache {
map: HashMap::new()
}
}
}
impl TProductCache for InMemoryProductCache {
fn put(&mut self, text: &String, product: Box<dyn TProduct>) {
self.map.insert(text.to_string(), product);
}
fn get(&self, text: &String) -> Option<&Box<dyn TProduct>> {
return match self.map.get(text) {
Some(boxed_product) => Some(boxed_product), // have to pass a reference to let it still own the value
None => None
}
}
fn clear(&mut self) {
self.map.clear();
}
}
struct ProductCachingProxy {
product_factory: Box<dyn TProductFactory>,
cache: Box<dyn TProductCache>
}
impl ProductCachingProxy {
fn new_for_factory(product_factory: Box<dyn TProductFactory>, cache: Box<dyn TProductCache>) -> Self {
return ProductCachingProxy {
product_factory,
cache
}
}
}
impl TProductFactory for ProductCachingProxy {
fn product_from_text(&mut self, text: String) -> &Box<dyn TProduct> { // can't pass ownership
let boxed_product = match self.cache.get(&text) {
Some(found_boxed_product) => found_boxed_product,
_ => {
// delegate creation to wrapped TProductFactory impl (`product_factory`)
let boxed_product = self.product_factory.product_from_text(text.clone());
// ... and put to the cache
self.cache.put(&text, boxed_product);
&boxed_product
}
};
return boxed_product;
}
}
// QUESTION: should it be Box
(required for ProductFactory) or &Box
(required for ProductCachingProxy) to be returned from TProductFactory.fn product_from_text(&mut self, text: String) -> Box<dyn TProduct>;
?
If caching proxy to return a Box
, how can it be created from a reference without copying/cloning (TProductCache.get(..)
)?
Replace Box
with Rc
(or Arc
if you use threads). It provides shared ownership and suites both your cases with single signature. Another option is to use Cow
that is a enum of owned and borrowed states.