AGB  ·  Datenschutz  ·  Impressum  







Anmelden
Nützliche Links
Registrieren
Zurück Delphi-PRAXiS Programmierung allgemein Algorithmen, Datenstrukturen und Klassendesign Delphi Methoden in abgeleiteten Klassen ggf. einschränken
Thema durchsuchen
Ansicht
Themen-Optionen

Methoden in abgeleiteten Klassen ggf. einschränken

Ein Thema von Gausi · begonnen am 11. Jul 2024 · letzter Beitrag vom 15. Jul 2024
Antwort Antwort
Seite 1 von 2  1 2      
Benutzerbild von Gausi
Gausi

Registriert seit: 17. Jul 2005
872 Beiträge
 
Delphi 11 Alexandria
 
#1

Methoden in abgeleiteten Klassen ggf. einschränken

  Alt 11. Jul 2024, 17:49
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:
aAudioFile := AudioFileFactory.CreateAudioFile(aFileName);
aAudioFile.ReadFromFile(aFileName);
aAudioFile.Title := EditTitle.Text;
aAudioFile.UpdateFile;
// fertig.
Soweit so gut. Ich würde die Funktionalität der Basisklasse gerne erweitern, und da stoße ich auf ein kleines Konzept-Problem.

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:
// alle TagItems in eine Liste einfügen
procedure GetAllTagItems(dest: TTagItemList);
Die Objekte vom Typ TTagItem darin bräuchten dann für die Anzeige und zum Bearbeiten Methoden wie
Delphi-Quellcode:
function TTagItem.GetText: String;
procedure TTagItem.SetText(Value: String);
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.

Frage: wie kann man halbwegs sicherstellen, dass der Anwender der Bibliothek (das schließt mich mit ein ) 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.)
The angels have the phone box.
  Mit Zitat antworten Zitat
hanvas

Registriert seit: 28. Okt 2010
162 Beiträge
 
Delphi 11 Alexandria
 
#2

AW: Methoden in abgeleiteten Klassen ggf. einschränken

  Alt 11. Jul 2024, 19:34
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....

Soweit so gut. Ich würde die Funktionalität der Basisklasse gerne erweitern, und da stoße ich auf ein kleines Konzept-Problem.

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?
Wäre nicht der umgekehrte Weg, vom abstrakten zum konkreten, der bessere Weg. Also eine mehr oder weniger allgemeingültige Klasse

Delphi-Quellcode:

TGenericTagItem = class ...

 procedure setData(var data);
 procedure setData(const data : pointer);
 procedure setData(const data : array of byte);
 procedure setData(const data : TByteArray);

end
und dann davon Ableitungen die Methoden für die konkreten Daten haben:

Delphi-Quellcode:

TTextTagItem = class(TGenericTagItem)
 procedure setText(const text : String);
end;

TImageTagItem = class(TGenericTagItem)
 procedure setImage(aImage : TImage);
end;
die ja dann intern wieder die generischeren Varianten aufrufen können.

cu Ha-Joe
  Mit Zitat antworten Zitat
Benutzerbild von Gausi
Gausi

Registriert seit: 17. Jul 2005
872 Beiträge
 
Delphi 11 Alexandria
 
#3

AW: Methoden in abgeleiteten Klassen ggf. einschränken

  Alt 11. Jul 2024, 19:59
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.
The angels have the phone box.
  Mit Zitat antworten Zitat
Benutzerbild von Uwe Raabe
Uwe Raabe

Registriert seit: 20. Jan 2006
Ort: Lübbecke
11.288 Beiträge
 
Delphi 12 Athens
 
#4

AW: Methoden in abgeleiteten Klassen ggf. einschränken

  Alt 11. Jul 2024, 20:30
Wie wäre es denn mit Interfaces?
Delphi-Quellcode:
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;
Dann kannst du über Supports abfragen, welches Feature ein TagItem unterstützt oder nicht.
Uwe Raabe
Certified Delphi Master Developer
Embarcadero MVP
Blog: The Art of Delphi Programming
  Mit Zitat antworten Zitat
hanvas

Registriert seit: 28. Okt 2010
162 Beiträge
 
Delphi 11 Alexandria
 
#5

AW: Methoden in abgeleiteten Klassen ggf. einschränken

  Alt 11. Jul 2024, 22:59
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.
Warum machst Du das Verhalten nicht einfach konfigurierbar und überlässt die Wahl (nichts machen, den "Fehler" loggen, eine Exception auslösen, den "Fehler" in einer Variablen speichern und mittels einer Methode wie getLastErrorCode auslesen usw.) nicht einfach den Benutzer deiner Lib.

Aber abgesehen davon. Wenn ich Dein Problem richtig verstehe dann hast Du nach:

Delphi-Quellcode:
aAudioFile := AudioFileFactory.CreateAudioFile(aFileName);
aAudioFile.ReadFromFile(aFileName);
aAudioFile.Title := EditTitle.Text;
aAudioFile.UpdateFile;
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.

Delphi-Quellcode:

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;
dann hättest Du alle Informationen um beim Aufruf von GetAllTagItems eine Liste mit den "richtigen" Objekten zu konstruieren.

Delphi-Quellcode:
 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;
Ich hofffe ich habe mich verständlich ausgedrückt.

hth Ha Jö
  Mit Zitat antworten Zitat
Benutzerbild von Gausi
Gausi

Registriert seit: 17. Jul 2005
872 Beiträge
 
Delphi 11 Alexandria
 
#6

AW: Methoden in abgeleiteten Klassen ggf. einschränken

  Alt 12. Jul 2024, 06:17
Wie wäre es denn mit Interfaces?
Mit so einer Antwort habe ich gerechnet . Der Gedanke kam mir auch schon, und das wäre vermutlich aus Perspektive der Objekt-Orientierung die sauberste Lösung. Dann müsste ich mich während des Auslesens der Metadaten aus einer Datei vor Erstellung des jeweiligen TTagItems entscheiden, welche konkrete Klasse ich da instanziieren will/muss. Dann ist schon bei der Erstellung definiert, was das Objekt "kann".

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 )
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.
The angels have the phone box.
  Mit Zitat antworten Zitat
hanvas

Registriert seit: 28. Okt 2010
162 Beiträge
 
Delphi 11 Alexandria
 
#7

AW: Methoden in abgeleiteten Klassen ggf. einschränken

  Alt 12. Jul 2024, 09:32
Wie wäre es denn mit Interfaces?
@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.
Im Grunde ist die Unterscheidung zwischen meiner Lösung und der Verwendung von Interfaces reine Semantik. Sie sind äquivalent.

Zitat:
Dann müsste ich mich während des Auslesens der Metadaten aus einer Datei vor Erstellung des jeweiligen TTagItems entscheiden, welche konkrete Klasse ich da instanziieren will/muss.
So wars gedacht. Aber egal ob Interfaces oder Objekte, in jedem Fall könntest Du die Fallunterscheidung in die Methode GetAllTagItems verschieben wenn du einen zweiten Paramter als Filter einführst :

Delphi-Quellcode:
 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;
das würde Dir dann auch

Zitat:
Eine Variante procedure GetAll_Text_TagItems(dest: TTagItemList); wird es natürlich auch geben
ersparen. Du hättest es dann ja schon.

cu Ha Jo
  Mit Zitat antworten Zitat
Benutzerbild von himitsu
himitsu

Registriert seit: 11. Okt 2003
Ort: Elbflorenz
43.716 Beiträge
 
Delphi 12 Athens
 
#8

AW: Methoden in abgeleiteten Klassen ggf. einschränken

  Alt 12. Jul 2024, 09:47
Modern baut man sich natürlich statt dem GetAllTagItems einen Enumerator,
aber OK, ein Array als Result geht och, und sieht in diesem Fall von der Verwendung her gleich aus.

for var Tag in aAudioFile.Tags do
Garbage Collector ... Delphianer erzeugen keinen Müll, also brauchen sie auch keinen Müllsucher.
my Delphi wish list : BugReports/FeatureRequests
  Mit Zitat antworten Zitat
Benutzerbild von Gausi
Gausi

Registriert seit: 17. Jul 2005
872 Beiträge
 
Delphi 11 Alexandria
 
#9

AW: Methoden in abgeleiteten Klassen ggf. einschränken

  Alt 12. Jul 2024, 10:24
Im Grunde ist die Unterscheidung zwischen meiner Lösung und der Verwendung von Interfaces reine Semantik. Sie sind äquivalent.
Nicht ganz. Denn ich habe keine lineare Klassenstruktur, sondern gewissermaßen eine quadratische. Ich brauche für ein TagItem einmal die Unterscheidung nach (A) ID3v2TagItem, TOggVorbisTagItem, TApeTagItem (...). Darin gibt es dann jeweils Methoden, die die Daten aus einem FileStream (mp3-Datei, ogg-Datei, ape-Datei, ...) laden und passend aufbereiten.
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.

Modern baut man sich natürlich statt dem GetAllTagItems einen Enumerator,
Das wäre dann noch ein anderes Thema. Da ich aber auch noch "von damals" Compilerschalter für "nicht-Unicode" Versionen von Delphi drinhabe, werde ich mir diesen modernen Kram vermutlich sparen.
The angels have the phone box.
  Mit Zitat antworten Zitat
Kas Ob.

Registriert seit: 3. Sep 2023
306 Beiträge
 
#10

AW: Methoden in abgeleiteten Klassen ggf. einschränken

  Alt 12. Jul 2024, 11:12
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:
function LyricsSupported : Boolean;
This will indicate that LyricsCount and LyricsItems are possible to use. (In an imaginary case where there is multiple lyrics in multiple language)

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.
Kas
  Mit Zitat antworten Zitat
Antwort Antwort
Seite 1 von 2  1 2      


Forumregeln

Es ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.

BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus.
Trackbacks are an
Pingbacks are an
Refbacks are aus

Gehe zu:

Impressum · AGB · Datenschutz · Nach oben
Alle Zeitangaben in WEZ +1. Es ist jetzt 01:01 Uhr.
Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz