0

I have an existing system that inserts books into a library database, and I want to generalize it to include other media.

The other media is similar, but not exactly the same, and all media are third party dependencies/models that I cannot change myself. (Otherwise I would have them all implement a single interface). Is it possible to do an Adapter, Converter, or Bridge pattern here?

public class libraryDAO {

    private DatabaseConnection db;
    
    public libraryDAO () {
       this.db = DatabaseConnection.getNewConnection(); 
    }

    public insertIntoDB (Book book) {
        this.db.insert(book.toMap());
    }

}

public class Book {
    public String title;
    public String author;
    public String coverArtId;
    public Integer pages;
}

Now I have more media types this system needs to support (and imagine 10 others)

public class Film {
    public String title;
    public String director;
    public Integer runtimeInMinutes;
}

public class Lyrics {
    public String song;
    public String lyricist;
}

So I naively created a huge class that can convert any of these media types:

public class Media {
    public String title;
    public String creator;
    public Integer runtimeInMinutes;
    public Integer pages; 

    public Media (Book book) {
        this.title            = book.title;
        this.creator          = book.author;
        this.coverArtId       = book.coverArtId;
        this.runtimeInMinutes = 0;
        this.pages            = book.pages;
    }

    public Media (Film film) {
        this.title            = film.title;
        this.creator          = film.director;
        this.coverArtId       = 0;
        this.runtimeInMinutes = film.runtimeInMinutes;
        this.pages            = 0;
    }

    public Media (Lyrics lyrics) {
        this.title            = lyrics.song;
        this.creator          = lyrics.lyricist;
        this.coverArtId       = 0;
        this.runtimeInMinutes = 0;
        this.pages            = 1;
    }

    public Map<String, Object> toMap() {
        //code that maps all the fields to a Map for DB insertion
    }
}

And then update my libraryDAO like so:

public class libraryDAO {

    private DatabaseConnection db;
    
    public libraryDAO () {
       this.db = DatabaseConnection.getNewConnection(); 
    }

    public insertIntoDB (Media media) {
        this.db.insert(media.toMap());
    }

}

But this doesn't scale, right? And it must be incorrect to have a giant class that just converts a wide-range of objects into a DB-acceptable state?

Is there a pattern that I'm missing? I can't use interfaces since I don't control the media types Book, Film, Lyrics, so I don't think I can use the Adapter, Converter, Bridge patterns.

4
  • 6
    From your question, it is not clear why you need to convert those library items to objects of a single class Media. Why don't you persist each library item in an individual database table, one which reflects the individual structure of each item type?
    – Doc Brown
    Nov 10, 2022 at 22:15
  • serialise to json/xml and save to a "data" column?
    – Ewan
    Nov 10, 2022 at 22:47
  • Does this answer your question? How to recursively serialize an object using reflection?
    – Doc Brown
    Nov 10, 2022 at 22:52
  • Is there a pattern that I'm missing? What's wrong with the never old fashioned data mapper? Basically, it's what you are doing right now in Media. So, it just takes you to create the right abstraction and move all that code into concrete implementations.
    – Laiv
    Nov 11, 2022 at 13:30

1 Answer 1

0

What's wrong with your current design?

The design of the Media class breaks the Open / Closed Principle, that says a class should be open for extension and closed for modification: every time you want to add a new kind of media you have to modify your class with the risk of breaking something.

Moreover, having the Media conversion (e.g. constructing a Media from a Book) requires to expose the properties as public. This is not in line with sound encapsulation principles.

How to improve it?

There are several alternative designs that you could consider:

  • refactor the Book, Film and Lyrics class to make them inherit from a class that provides for the common denominator. But the problem is that properties like page are not really a common denominator.
  • let Book, Film and Lyrics class implement an interface MediaConvertible, that ensures that the object can be converted into a Media.
  • get rid of the intermediary Media and let Book, Film and Lyrics class implement an interface MediaMappable that implements a method toMap().

Digging further

This last approach would allow to generalise your DAO and isolate DAO specifics from the rest:

public interface MediaMappable {
    public Map<String, Object> toMap();
}

public class libraryDAO {
    ...
    public insertIntoDB (MediaMappable media) {
        this.db.insert(media.toMap());
    }    
}

You then can change your different classes to provide directly a relevant media mapping:

public class Book : MediaMappable {
    ...
    public Map<String, Object> toMap() {
        // code to get a map with all media relevant fields
    }
}    

The advantage is that it's simple to add new media, and can work with all the class properties being private. By the way, the map plays a similar role than a Memento, with the difference being that its internal state is shared between a sender (the media object) and a receiver (the DAO).

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.