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.
1 Answer 1
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
andLyrics
class to make them inherit from a class that provides for the common denominator. But the problem is that properties likepage
are not really a common denominator. - let
Book
,Film
andLyrics
class implement an interfaceMediaConvertible
, that ensures that the object can be converted into aMedia
. - get rid of the intermediary
Media
and letBook
,Film
andLyrics
class implement an interfaceMediaMappable
that implements a methodtoMap()
.
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).
Explore related questions
See similar questions with these tags.
Media
. Why don't you persist each library item in an individual database table, one which reflects the individual structure of each item type?Is there a pattern that I'm missing?
What's wrong with the never old fashioneddata mapper
? Basically, it's what you are doing right now inMedia
. So, it just takes you to create the right abstraction and move all that code into concrete implementations.