![]() |
Methoden in abgeleiteten Klassen ggf. einschränken
Ich bin gerade dabei, eine meiner Klassensammlungen etwas zu überarbeiten - es geht dabei um das Auslesen und Bearbeiten von Metadaten (aka "ID3-Tags") in Audio-Dateien. Vor ein paar Jahren habe ich einer ersten grundlegenden Überarbeitung einige Properties über eine abstrakte Basisklasse TAudioFile bereitgestellt. Die Getter und Setter für Dinge wie "Interpret" und "Album" und die Hauptmethoden "ReadFromFile" und "WriteToFile" werden in abgeleiteten Klassen wie TMp3File oder TFlacFile implementiert. Das dann kombiniert mit einer Factory-Klasse, und man muss praktisch nichts mehr über die unterschiedlichen Audioformate wissen, um die "einfachen" Daten auszulesen und zu verändern.
Delphi-Quellcode:
Soweit so gut. Ich würde die Funktionalität der Basisklasse gerne erweitern, und da stoße ich auf ein kleines Konzept-Problem.
aAudioFile := AudioFileFactory.CreateAudioFile(aFileName);
aAudioFile.ReadFromFile(aFileName); aAudioFile.Title := EditTitle.Text; aAudioFile.UpdateFile; // fertig. Ziel: Statt nur klar definierte Eigenschaften wie "Interpret" und "Titel" möchte ich auch über die Basisklasse eine Funktion anbieten wie "Liste mit (fast) allen Metadaten". In den unterschiedlichen Audioformaten kommen unterschiedliche Tagging-Formate zum Einsatz. Von ID3-Tags haben wohl viele schon was gehört. Dann gibt es Vorbis-Kommentare, Apev2-Tags und in M4a-Dateien nennt sich das "Meta-Atom" (oder so ähnlich). Abstrakt runtergebrochen findet sich in allen Systemen (vom rudimentären ID3v1-Tag mal abgesehen) eine Liste von "TTagItems". Jedes TagItem besitzt eine Art "Key", der etwas über die Bedeutung des Inhalts verrät, und dann eben entsprechende Daten. Häufig sind die Daten vom Typ "Text", manchmal "Text mit Metadaten", aber auch "Bild", "Bild mit Metadaten" oder einfach nur "Daten". Die Basisklasse TAudioFile müsste also eine Methode bekommen
Delphi-Quellcode:
Die Objekte vom Typ TTagItem darin bräuchten dann für die Anzeige und zum Bearbeiten Methoden wie
// alle TagItems in eine Liste einfügen
procedure GetAllTagItems(dest: TTagItemList);
Delphi-Quellcode:
Die müssten dann in den konkreten abgeleiteten Klassen wie TID3v2TagItem oder TOggVorbisCommentTagItem implementiert werden. Eigentlich ja kein Problem - wenn alle TagItems nur "Text" enthielten. Das ist aber nicht der Fall. Z.B. kann ein ID3v2Tag ein TagItem mit dem Key "APIC" enthalten, in dem dann ein Bild steckt. Wenn nun aber das TID3v2TagItem von TTagItem abgeleitet ist, dann kann man auch darauf SetText anwenden, und damit den Inhalt des Picture-Items ungültig machen. Das wäre irgendwie doof.
function TTagItem.GetText: String;
procedure TTagItem.SetText(Value: String); Frage: wie kann man halbwegs sicherstellen, dass der Anwender der Bibliothek (das schließt mich mit ein :stupid:) später keinen Mist damit baut (es sei denn, er legt es ausdrücklich darauf an)? Meine Idee wäre, in der konkreten Implementierung von "TSomeTagItem.SetText" zu überprüfen, ob das TagItem mit dem aktuell gesetzten Key überhaupt Textdaten enthält (das wird über den jeweiligen Standard bzw. die Dokumentation geregelt). Falls ja: ok, fein. Und falls nein? Einfach "nichts" machen? Aus der Set-Procedure eine Function machen und "False" zurückliefern? Eine Exception schmeißen? Gibt es da eine andere Möglichkeit, die ggf. "sauberer" oder eleganter ist? Eine Variante procedure GetAll_Text_TagItems(dest: TTagItemList); wird es natürlich auch geben. ;-) (Das Problem besteht in der aktuellen Version der Library auch schon. Ist dort aber nicht so tragisch, da der Zugriff auf "Tag-Ebene" nicht so einfach ist. Das geht nur, wenn man sich etwas mehr mit den unterschiedlichen Formaten auseinandersetzt.) |
AW: Methoden in abgeleiteten Klassen ggf. einschränken
Zitat:
Delphi-Quellcode:
und dann davon Ableitungen die Methoden für die konkreten Daten haben:TGenericTagItem = class ... procedure setData(var data); procedure setData(const data : pointer); procedure setData(const data : array of byte); procedure setData(const data : TByteArray); end
Delphi-Quellcode:
die ja dann intern wieder die generischeren Varianten aufrufen können. TTextTagItem = class(TGenericTagItem) procedure setText(const text : String); end; TImageTagItem = class(TGenericTagItem) procedure setImage(aImage : TImage); end; cu Ha-Joe |
AW: Methoden in abgeleiteten Klassen ggf. einschränken
Das Problem dabei wäre, dass ich dann (abgesehen von TGenericTagItem) keinen sinnvollen gemeinsamen Vorfahren für die TagItems in den jeweiligen MetaDaten-Formaten nutzen kann. Denn die Unterscheidung Text/Bild/ExtendendText/Binary/... gibt es jeweils für ID3v2Items, Apev2Items, VorbisCommentItems, ...
Dann müsste ich wieder im eigentlichen Programmcode für jedes Format anders casten bzw. den Typ checken, um "GetText" aufrufen zu können. Und genau davon möchte ich ja weg. |
AW: Methoden in abgeleiteten Klassen ggf. einschränken
Wie wäre es denn mit Interfaces?
Delphi-Quellcode:
Dann kannst du über Supports abfragen, welches Feature ein TagItem unterstützt oder nicht.
type
type ITextTag = interface ['{1EE16FB9-1BDD-4BDA-86DE-2A09C314E845}'] function GetText: string; procedure SetText(const Value: string); property Text: string read GetText write SetText; end; IImageTag = interface ['{99D31F17-7D69-47FA-98BE-F1F13CB02E39}'] function GetImage: TGraphic; procedure SetImage(const Value: TGraphic); property Image: TGraphic read GetImage write SetImage; end; IMetaDataTag = interface ['{F09ACD7B-CDC0-4548-A248-1F2DE6B7EE41}'] function GetMetaData: string; procedure SetMetaData(const Value: string); property MetaData: string read GetMetaData write SetMetaData; end; |
AW: Methoden in abgeleiteten Klassen ggf. einschränken
Zitat:
Aber abgesehen davon. Wenn ich Dein Problem richtig verstehe dann hast Du nach:
Delphi-Quellcode:
doch alle Informationen die Du brauchst. Kanst Du nicht beispielsweise AudioFileFactory um eine Methode erweitern die zu jedem Tag die richtige Klasse (oder alternativ Interface) registriert, und nutzt diese Informationen in der Methode GetAllTagItems um die richtigen Klassen zu instanzieren.
aAudioFile := AudioFileFactory.CreateAudioFile(aFileName);
aAudioFile.ReadFromFile(aFileName); aAudioFile.Title := EditTitle.Text; aAudioFile.UpdateFile;
Delphi-Quellcode:
dann hättest Du alle Informationen um beim Aufruf von GetAllTagItems eine Liste mit den "richtigen" Objekten zu konstruieren. type TGenericIDTag = class ... TTagClass = class of TGenericIDTag; TTextIDTag = class( TGenericIDTag ) end; TImageIDTag = class(TGenericIDTag) end; TAudioFile = class() private FFactory : TAudioFileFactory public procedure GetAllTagItems(destList: TTagItemList); end; TAudioFileFactory = class() public function CreateAudioFile(const aFileName : String) : TAudioFile; procedure RegisterTag(const aIdentifier : String; aTag : TTagClass); end;
Delphi-Quellcode:
Ich hofffe ich habe mich verständlich ausgedrückt.
TagList := TTagItemList.Create;
try aAudioFile.GetAllTagItems(TagList); for var tag : TGenericIDTag in TagList do begin if tag is TTextIDTag then begin .... end; end; finally end; hth Ha Jö |
AW: Methoden in abgeleiteten Klassen ggf. einschränken
Zitat:
Mit Interfaces und Supports() kann ich dann bei der Anwendung direkt eine ganze Reihe in der "2D-Klassen-Matrix" abfrühstücken, und muss nicht jede einzelne Klasse testen. Das wäre auf jeden Fall ein Fortschritt zur aktuellen (nicht-)Lösung. In meinem ersten Gedanken (Überprüfung in GetText bzw. SetText) wird diese Entscheidung erst später getroffen, indem die TagItem-Klasse checkt, ob es sinnvoll ist, diese oder jene geerbte Methode auszuführen. Ob Interfaces für meine Anwendungsfälle ideal wären, muss ich mir nochmal durch den Kopf gehen lassen. Eine Methode "GetText" ist nämlich manchmal auch für binäre Daten reizvoll (nicht druckbare Zeichen dann durch "." oder so ersetzt, wie bei Hex-Editoren auch). Und bei den "erweiterten Text-Items" müsste ich mir auch noch überlegen, ob ich dafür das Interface "GetText" bereitstelle, oder nicht. (Lyrics enthalten z.B. im ID3v2Tag nicht nur den eigentlichen Liedtext, sondern z.B. auch noch ein Feld für die Sprache. Da enthält quasi ein TTagItem selbst wieder Metadaten :pale:) Mit meiner zuerst angedachten Lösung könnte ich das Verhalten vom Anwender der Library über einen zusätzlichen Parameter "TextMode" steuern lassen, der z.B. "strict", "reasonable" oder "forced" sein kann. Im letzteren Fall (forced) könnte der Anwender dann Unsinn machen (zumindest mit SetText), wenn er will (oder genau weiß, was er tut). @hanvas: von genau diesen (vielen) Fallunterscheidungen im eigentlichen Programmcode möchte ich ja weg. Ich möchte quasi etwas mehr Programmlogik vom Anwendungscode in die Library verschieben. Aktuell ist das noch wirrer, da die "GetAllTagItems"-Methode in jeder Tag-Klasse anders heißt, und auch nicht immer den gleichen Listentyp haben will (TObjectList, TList, TStringList). Aber danke für die Anregungen - das hilft ja oft schon, um sich selbst besser klar zu werden, was man eigentlich will. :-D |
AW: Methoden in abgeleiteten Klassen ggf. einschränken
Zitat:
Zitat:
Delphi-Quellcode:
das würde Dir dann auch
TagList := TTagItemList.Create;
try aAudioFile.GetAllTagItems(TagList, TTextIDTag); for var tag : TTextIDTag in TagList do begin text := tag.GetText() machWasMitText(text) tag.SetText(Text); end; finally TagList.Free; end; Zitat:
cu Ha Jo |
AW: Methoden in abgeleiteten Klassen ggf. einschränken
Modern baut man sich natürlich statt dem GetAllTagItems einen Enumerator, :angle:
aber OK, ein Array als Result geht och, und sieht in diesem Fall von der Verwendung her gleich aus. :duck:
Delphi-Quellcode:
for var Tag in aAudioFile.Tags do
|
AW: Methoden in abgeleiteten Klassen ggf. einschränken
Zitat:
Und ich brauche (in dem Ansatz) eine Unterscheidung nach (B) TTextTagItem, TPictureTagItem, TBinaryTagItem (...), die die Daten darin für die Anzeige vorbereiten bzw. über sinnvolle Getter und/oder Setter bereitstellen. Wenn ich nur mit Klassen arbeite, müsste ich im Programmcode mangels Mehrfachvererbung dann sehr viele Typen (A*B viele) abfragen und einzeln behandeln. Mit Interfaces und Support könnte ich mit einer Abfrage alle TextTagItems behandeln - egal ob sie nun in einem ID3v2TagItem, TOggVorbisTagItem oder TApeTagItem implementiert sind. Das macht dann schon einen Unterschied. ;-) Zitat:
|
AW: Methoden in abgeleiteten Klassen ggf. einschränken
I would go with something like TMetaDataAudioFactory with half fully-abstracted base class that support everything.
The base with have everything you could use or has been implemented but in two ways, like for each type there is a method like
Code:
This will indicate that LyricsCount and LyricsItems are possible to use. (In an imaginary case where there is multiple lyrics in multiple language)
function LyricsSupported : Boolean;
In the base/abstract class LyricsSupported is an abstract method, meaning all the derived class must implement this one, this will force you to not forget about them. While in that base methods like LyricsCount and LyricsItems will raise an exception this will enforce you and the users of your library to check LyricsSupported before use, and will remove the need to implement in the derived and specific MetaData parsers, simply override the base classes to prevent raising exceptions. for the end user/developer the use is straight forward, call PictureSupported then use Picture, or Picture and PictureCount.... and may be PictureMaxCount, that depends on what you see fit for each file type. |
Alle Zeitangaben in WEZ +1. Es ist jetzt 14:06 Uhr. |
Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024-2025 by Thomas Breitkreuz