4 ms·
From a very modified version of something I was working on recently, but with the stuff I couldn't do actually done here (and non-functionality code because of
by RHSeeger 1y ago
From a very modified version of something I was working on recently, but with the stuff I couldn't do actually done here (and non-functionality code because of that, but is shows the idea)
public interface MetadataSource {
Metadata metadata = null;
default Metadata getMetadata() {
if (metadata == null) {
metadata = fetchMetadata();
}
return metadata;
}
// This can be relatively costly
Metadata fetchMetadata();
}
public class Image implements MetadataSource {
public Metadata fetchMetadata() {
// goes to externally hosted image to fetch metadata
}
}
public class Video implements MetadataSource {
public Metadata fetchMetadata() {
// goes to video hosting service to get metadata
}
}
public class Document implements MetadataSource {
public Metadata fetchMetadata() {
// goes to database to fetch metadata
}
}
Each of the above have completely different ways to fetch their metadata (ex, Title and Creator), and of them has different characteristics related to the cost of getting that data. So, by default, we want the interface to cache the result so that the
1. The thing that _has_ the metadata only needs to know how to fetch it when it's asked for (implementation of fetchMetadata), and it doesn't need to worry about the cost of doing so (within limits of course)
2. The things that _use_ the metadata only need to know how to ask for it (getMetadata) and can assume it has minimal cost.
3. Neither one of those needs to know anything about it being cached.
I had a case recently where I needed to check "does this have metadata available" separate from "what is the metadata". And fetching it twice would add load.
- svieira 1y agoOne way you could fix this with composition is: class CachedMetadataSource implements MetadataSource { CachedMetadataSource(MetadataSource uncachedSource) {} Metadata getMetadata() { if (metadata == null) { metadata = uncachedSource.getMetadata(); } return metadata; } }
- RHSeeger 1y agoI don't see how that solves the problem. It seems like Video will need to keep it's own copy of CachedMetadaSource, which points back to itself, and go through that access it's metadata in the getMetadata implementation it makes available to it's users. At that point, it might as well just cache the value itself without the extra hoops. The difficult part isn't caching the value, it's preventing every class that implements MetadataSource from having to do so.
- svieira 1y agoIt would be the other way around. You wouldn't pass around the underlying suppliers directly, you'd wrap them. But if you must have state _and_ behavior, then `abstract class` is your friend in Java (while in Scala traits can have fields and constructors, so there is no problem).
- josephg 1y agoHere's my take on implementing this in rust. I made a trait for fetching metadata, that can be implemented by Image, Video, Document, etc: trait MetadataSource { fn fetch_metadata(&self) -> Metadata; } impl MetadataSource for Image { ... } impl MetadataSource for Video { ... } impl MetadataSource for Document { ... } And a separate object which stores an image / video / document alongside its cached metadata: struct ThingWithMetadata<T> { obj: T, // Assuming you need to store this too? metadata: Option<Metadata> } impl<T: MetadataSource> ThingWithMetadata { fn get_metadata(&self) -> &Metadata { if self.metadata.is_none() { self.metadata = Some(self.obj.fetch_metadata()); } self.metadata.as_ref().unwrap() } } Its not the most beautiful thing in the world, but it works. And it'd be easy enough to add more methods, behaviour and state to those metadata sources if you want. (Eg if you want Image to actually load / store an image or something.) In this case, it might be even simpler if you made Image / Video / Document into an enum. Then fetch_metadata could be a regular function with a match expression (switch statement). If you want to be tricky, you could even make struct ThingWithMetadata also implement MetadataSource. If you do that, you can mix and match cached and uncached metadata sources without the consumer needing to know the difference. https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=821d108d26632d122e6e705f1c18cfe6 https://play.rust-lang.org/?version=stable&mode=debug&editio...
- zozbot234 1y agoIsn't this essentially the generic typestate pattern in Rust? In my view there is a pretty obvious connection between that particular pattern and how other languages implement OO inheritance, though in all fairness I don't think that connection is generally acknowledged. (For one thing, it's quite obvious to see that the pattern itself is rather anti-modular, and the ways generic typestate is used are also quite divergent from the usual style of inheritance-heavy OO design.)
- RHSeeger 1y agoWhen you call myImageInstance.fetchMetadata, what does it do? I don't know rust, so it's not clear to me how the value gets cached.