Книга: Practical Common Lisp
Versioned Concrete Frame Classes
Versioned Concrete Frame Classes
In the original definition, generic-frame
subclassed id3-frame
. But now id3-frame
has been replaced with the two version-specific base classes, id3v2.2-frame
and id3v2.3-frame
. So, you need to define two new versions of generic-frame
, one for each base class. One way to define this classes would be like this:
(define-binary-class generic-frame-v2.2 (id3v2.2-frame)
((data (raw-bytes :size size))))
(define-binary-class generic-frame-v2.3 (id3v2.3-frame)
((data (raw-bytes :size size))))
However, it's a bit annoying that these two classes are the same except for their superclass. It's not too bad in this case since there's only one additional field. But if you take this approach for other concrete frame classes, ones that have a more complex internal structure that's identical between the two ID3 versions, the duplication will be more irksome.
Another approach, and the one you should actually use, is to define a class generic-frame
as a mixin: a class intended to be used as a superclass along with one of the version-specific base classes to produce a concrete, version-specific frame class. The only tricky bit about this approach is that if generic-frame
doesn't extend either of the frame base classes, then you can't refer to the size
slot in its definition. Instead, you must use the current-binary-object
function I discussed at the end of the previous chapter to access the object you're in the midst of reading or writing and pass it to size
. And you need to account for the difference in the number of bytes of the total frame size that will be left over, in the case of a version 2.3 frame, if any of the optional fields are included in the frame. So, you should define a generic function data-bytes
with methods that do the right thing for both version 2.2 and version 2.3 frames.
(define-binary-class generic-frame ()
((data (raw-bytes :size (data-bytes (current-binary-object))))))
(defgeneric data-bytes (frame))
(defmethod data-bytes ((frame id3v2.2-frame))
(size frame))
(defmethod data-bytes ((frame id3v2.3-frame))
(let ((flags (flags frame)))
(- (size frame)
(if (frame-compressed-p flags) 4 0)
(if (frame-encrypted-p flags) 1 0)
(if (frame-grouped-p flags) 1 0))))
Then you can define concrete classes that extend one of the version-specific base classes and generic-frame
to define version-specific generic frame classes.
(define-binary-class generic-frame-v2.2 (id3v2.2-frame generic-frame) ())
(define-binary-class generic-frame-v2.3 (id3v2.3-frame generic-frame) ())
With these classes defined, you can redefine the find-frame-class
function to return the right versioned class based on the length of the identifier.
(defun find-frame-class (id)
(ecase (length id)
(3 'generic-frame-v2.2)
(4 'generic-frame-v2.3)))
- Structure of an ID3v2 Tag
- Defining a Package
- Integer Types
- String Types
- ID3 Tag Header
- ID3 Frames
- Detecting Tag Padding
- Supporting Multiple Versions of ID3
- Versioned Frame Base Classes
- Versioned Concrete Frame Classes
- What Frames Do You Actually Need?
- Text Information Frames
- Comment Frames
- Extracting Information from an ID3 Tag
- Versioned Frame Base Classes
- Comment Frames
- Специфика .NET Compact Framework: ADO.NET
- Классы синхронизации, внедренные в версии .NET Framework 4.0
- 9.1 Data Frame
- 9.1.1 Start of Frame (SOF)
- 9.2 Remote Frame
- 9.3 Error Frame
- 9.4 Overload Frame
- Рекомендации по обработке событий в среде .NET Framework
- Chapter 1 The .NET Framework
- Chapter 4 Classes and Objects