Hacker News new | ask | show | jobs
by RHSeeger 401 days ago
> First, traits don't define any fields.

How does one handle cases where fields are useful? For example, imagine you have a functionality to go fetch a value and then cache it so that future calls to get that functionality are not required (resource heavy, etc).

    // in Java because it's easier for me
    public interface hasMetadata {
        Metadata getMetadata() {
            // this doesn't work because interfaces don't have fields
            if (this.cachedMetadata == null) {
                this.cachedMetadata = generateMetadata();
            }
            return this.cachedMetadata;
        }
        // relies on implementing class to provide
        Metadata fetchMetadata(); 

    }
1 comments

Getters and setters that get specified by the implementing type.
But then you have the getters, setters, and field on every class that implements the functionality. It works, sure, it just feels off to me. This is code that will be the same everywhere, and you're pulling it out of the common class and implementing it everywhere.
Yep. Or ... don't put that in the interface at all. It looks like an implementation concern to me.
But if there's a lot of classes that implement the same thing, then not duplicating code makes sense. And saying "it's an implementation detail" leads to having the same code in a bunch of different classes. It feels very similar to the idea of default implementations to me; when the implementation will be the same everywhere, it makes sense to have it in one place.
So to be clear about your example: You have a whole lot of different - totally distinct - types of things, which all need to have the same logic to cache HTTP requests? Can you give some examples of these different types you're creating? Why do you have lots of distinct types that need exactly the same caching logic?

It sounds like you could solve that problem in a lot of different ways. For example, you could make an HTTP client wrapper which internally cached responses. Or make a LazyResource struct which does the caching - and use that in all those different types you're making. Or make a generic struct which has the caching logic. The type parameter names the special individual behaviour. Or something else - I don't have enough information to know how I'd approach your problem.

Can you describe a more detailed example of the problem you're imagining? As it is, your requirements sound random and kind of arbitrary.

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.

Here'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&editio...

One 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;
      }
    }