Ich schreibe eine App, die eine Image
Entität haben wird, und ich habe bereits Probleme zu entscheiden, wessen Verantwortung jede Aufgabe sein soll.
Zuerst habe ich die Image
Klasse. Es hat einen Pfad, eine Breite und andere Attribute.
Dann habe ich eine ImageRepository
Klasse erstellt, um Bilder mit einer einzigen und getesteten Methode abzurufen, z findAllImagesWithoutThumbnail()
.
Aber jetzt muss ich auch dazu in der Lage sein createThumbnail()
. Wer sollte sich darum kümmern? Ich habe darüber nachgedacht, eine ImageManager
Klasse zu haben, die eine App-spezifische Klasse sein würde (es würde auch eine wiederverwendbare Komponente zur Bildbearbeitung von Drittanbietern geben, ich erfinde das Rad nicht neu).
Oder wäre es vielleicht 0K, die Image
Größe selbst ändern zu lassen ? Oder lassen Sie die ImageRepository
und ImageManager
die gleiche Klasse sein?
Was denkst du?
quelle
Antworten:
Die gestellte Frage ist zu vage, um eine echte Antwort zu erhalten, da sie wirklich davon abhängt, wie die
Image
Objekte verwendet werden sollen.Wenn Sie das Bild nur in einer Größe verwenden und die Größe ändern, weil das Quellbild die falsche Größe hat, ist es möglicherweise am besten, den Lesecode die Größe ändern zu lassen. Lassen Sie Ihre
createImage
Methode eine Breite / Höhe annehmen und dann das Bild zurückgeben, dessen Größe auf diese Breite / Höhe geändert wurde.Wenn Sie mehrere Größen benötigen und der Speicher keine Rolle spielt, ist es am besten, das Bild wie ursprünglich gelesen im Speicher zu belassen und die Größe zur Anzeigezeit zu ändern. In einem solchen Fall können Sie verschiedene Designs verwenden. Das Bild
width
undheight
wäre fixiert, aber Sie hätten entweder einedisplay
Methode, die eine Position und eine Zielhöhe / -breite einnimmt, oder eine Methode, die eine Breite / Höhe verwendet, die ein Objekt zurückgibt, das von dem von Ihnen verwendeten Anzeigesystem verwendet wird. Mit den meisten Zeichnungs-APIs, die ich verwendet habe, können Sie die Zielgröße zum Zeitpunkt des Zeichnens angeben.Wenn die Anforderungen dazu führen, dass häufig mit unterschiedlichen Größen gezeichnet wird, damit die Leistung ein Problem darstellt, können Sie eine Methode verwenden, mit der ein neues Bild mit einer anderen Größe basierend auf dem Original erstellt wird. Eine Alternative wäre, Ihre
Image
Klasse intern verschiedene Darstellungen zwischenspeichern zu lassen, damit beim ersten Aufrufdisplay
mit der Miniaturbildgröße die Größenänderung vorgenommen wird, während beim zweiten Mal nur die zwischengespeicherte Kopie gezeichnet wird, die beim letzten Mal gespeichert wurde. Dies verbraucht mehr Speicher, aber es kommt selten vor, dass Sie mehr als ein paar häufige Größenänderungen haben.Eine andere Alternative besteht darin, eine einzelne
Image
Klasse zu haben , die das Basisbild beibehält und diese Klasse eine oder mehrere Darstellungen enthält. EinImage
selbst hätte keine Höhe / Breite. Stattdessen würde es mit einem beginnenImageRepresentation
, der eine Höhe und Breite hatte. Sie würden diese Darstellung zeichnen. Um die Größe eines Bildes zu ändern, müssen SieImage
eine Darstellung mit bestimmten Höhen- / Breitenmetriken anfordern. Dies würde dazu führen, dass es dann diese neue Darstellung sowie das Original enthält. Dies gibt Ihnen viel Kontrolle darüber, was genau im Speicher herumhängt, auf Kosten der zusätzlichen Komplexität.Ich persönlich mag keine Klassen, die das Wort enthalten,
Manager
weil "Manager" ein sehr vages Wort ist, das Ihnen nicht wirklich viel darüber sagt, was die Klasse genau tut. Verwaltet es die Objektlebensdauer? Steht es zwischen dem Rest der Anwendung und dem, was es verwaltet?quelle
Halten Sie die Dinge einfach, solange es nur wenige Anforderungen gibt, und verbessern Sie Ihr Design, wenn Sie müssen. Ich denke, in den meisten Fällen der realen Welt ist nichts falsch daran, mit einem solchen Design zu beginnen:
Dinge können anders werden, wenn Sie mehr Parameter übergeben müssen
createThumbnail()
, und diese Parameter benötigen eine eigene Lebensdauer. Nehmen wir beispielsweise an, Sie erstellen Miniaturansichten für mehrere hundert Bilder mit einer bestimmten Zielgröße, einem bestimmten Größenänderungsalgorithmus oder einer bestimmten Qualität. Dies bedeutet, dass Sie diecreateThumbnail
in eine andere Klasse verschieben können, z. B. eine Manager-Klasse oder eineImageResizer
Klasse, in der diese Parameter vom Konstruktor übergeben werden und somit an die Lebensdauer desImageResizer
Objekts gebunden sind .Eigentlich würde ich später mit dem ersten Ansatz und Refactor beginnen, wenn ich ihn wirklich brauche.
quelle
ImageResizer
in erster Linie in die Verantwortung einer Klasse stellen? Wann delegieren Sie einen Anruf, anstatt die Verantwortung auf eine neue Klasse zu verlagern?Image thumbnail = img.createThumbnail(x,y)
.Ich denke, es müsste Teil der
Image
Klasse sein, da die Größenänderung in einer externen Klasse erfordern würde, dass der Resizer die Implementierung von kenntImage
, wodurch die Kapselung verletzt wird. Ich gehe davon aus, dassImage
es sich um eine Basisklasse handelt, und Sie erhalten separate Unterklassen für konkrete Bildtypen (PNG, JPEG, SVG usw.). Daher benötigen Sie entweder entsprechende Größenänderungsklassen oder einen generischen Größenänderer mit einerswitch
Anweisung, deren Größe basierend auf der Implementierungsklasse geändert wird - ein klassischer Designgeruch.Ein Ansatz könnte darin bestehen, dass Ihr
Image
Konstruktor eine Ressource mit dem Bild sowie den Parametern für Höhe und Breite verwendet und sich entsprechend erstellt. Die Größenänderung kann dann so einfach sein wie das Erstellen eines neuen Objekts unter Verwendung der ursprünglichen Ressource (im Bild zwischengespeichert) und der neuen Größenparameter. ZBfoo.createThumbnail()
würde einfachreturn new Image(this.source, 250, 250)
. (mitImage
der konkreten Artfoo
von natürlich). Dies hält Ihre Bilder unveränderlich und ihre Implementierungen privat.quelle
Image
. Alles, was es braucht, ist die Quell- und die Zieldimension.Ich weiß , dass OOP ist etwa gemeinsam Daten und Verhalten einkapseln, aber ich glaube nicht , es ist eine gute Idee für ein Bild ist die Resize - Logik in diesem Fall eingebettet zu haben, weil ein Bild nicht müssen wissen , wie sich die Größe zu sein ein Bild.
Ein Miniaturbild ist eigentlich ein anderes Bild. Vielleicht haben Sie eine Datenstruktur, die die Beziehung zwischen einem Foto und seinem Miniaturbild enthält (beide sind Bilder).
Ich versuche, meine Programme in Dinge (wie Bilder, Fotos, Miniaturansichten usw.) und Dienste (wie PhotographRepository, ThumbnailGenerator usw.) zu unterteilen. Machen Sie Ihre Datenstrukturen richtig und definieren Sie dann die Services, mit denen Sie diese Datenstrukturen erstellen, bearbeiten, transformieren, beibehalten und wiederherstellen können. Ich füge meinen Datenstrukturen nicht mehr Verhalten hinzu, als sicherzustellen, dass sie ordnungsgemäß erstellt und ordnungsgemäß verwendet werden.
Daher sollte ein Bild nicht die Logik zum Erstellen eines Miniaturbilds enthalten. Es sollte einen ThumbnailGenerator-Dienst geben, der eine Methode wie die folgende hat:
Meine größere Datenstruktur könnte folgendermaßen aussehen:
Das könnte natürlich bedeuten, dass Sie sich beim Erstellen des Objekts keine Mühe machen, also würde ich so etwas auch als OK betrachten:
... für den Fall, dass Sie eine Datenstruktur mit verzögerter Auswertung wünschen. (Entschuldigung, ich habe meine Nullprüfungen nicht eingeschlossen und sie nicht threadsicher gemacht. Dies ist etwas, das Sie möchten, wenn Sie versuchen, eine unveränderliche Datenstruktur nachzuahmen.)
Wie Sie sehen können, wird eine dieser Klassen von einer Art PhotographRepository erstellt, das wahrscheinlich einen Verweis auf einen ThumbnailGenerator enthält, den es durch Abhängigkeitsinjektion erhalten hat.
quelle
Sie haben eine einzelne Funktionalität identifiziert, die Sie implementieren möchten. Warum sollte sie nicht von allem getrennt sein, was Sie bisher identifiziert haben? Das ist es, was das Prinzip der Einzelverantwortung als Lösung vorschlagen würde.
Erstellen Sie eine
IImageResizer
Schnittstelle, über die Sie ein Bild und eine Zielgröße übergeben können und die ein neues Bild zurückgibt. Erstellen Sie dann eine Implementierung dieser Schnittstelle. Es gibt tatsächlich unzählige Möglichkeiten, die Größe von Bildern zu ändern, sodass Sie sogar mehr als eine haben können!quelle
Ich nehme diese Fakten über die Methode an, die die Größe von Bildern ändert:
Aufgrund dieser Tatsachen würde ich sagen, dass es keinen Grund gibt, dass die Methode zur Größenänderung von Bildern Teil der Bildklasse selbst ist. Die Implementierung als statische Hilfsmethode der Klasse wäre am besten.
quelle
Möglicherweise ist eine Bildverarbeitungsklasse geeignet (oder ein Bildmanager, wie Sie ihn genannt haben). Übergeben Sie Ihr Bild an eine CreateThumbnail-Methode des Bildprozessors, um beispielsweise ein Miniaturbild abzurufen.
Einer der Gründe, warum ich diese Route vorschlagen würde, ist, dass Sie sagen, dass Sie eine Bildverarbeitungsbibliothek eines Drittanbieters verwenden. Wenn Sie die Größenänderungsfunktion aus der Image-Klasse selbst entfernen, können Sie möglicherweise plattformspezifischen Code oder Code von Drittanbietern leichter isolieren. Wenn Sie Ihre Basis-Image-Klasse also für alle Plattformen / Apps verwenden können, müssen Sie sie nicht mit plattform- oder bibliotheksspezifischem Code verschmutzen. Das kann alles im Bildprozessor sein.
quelle
Grundsätzlich wie Doc Brown bereits sagte:
Erstellen Sie eine
getAsThumbnail()
Methode für die Bildklasse, aber diese Methode sollte die Arbeit eigentlich nur an eineImageUtils
Klasse delegieren . Es würde also ungefähr so aussehen:Und
Dies ermöglicht einen übersichtlicheren Code. Vergleichen Sie Folgendes:
Oder
Wenn letzteres für Sie gut erscheint, können Sie sich auch daran halten, diese Hilfsmethode irgendwo zu erstellen.
quelle
Ich denke, in der "Bilddomäne" haben Sie nur das Bildobjekt, das unveränderlich und monadisch ist. Sie fragen das Bild nach einer verkleinerten Version und es gibt eine verkleinerte Version von sich zurück. Dann können Sie entscheiden, ob Sie das Original entfernen oder beide behalten möchten.
Jetzt ist die Miniaturansicht, der Avatar usw. des Bildes eine andere Domäne, die die Bilddomäne nach verschiedenen Versionen eines bestimmten Bildes fragen kann, die sie einem Benutzer geben soll. Normalerweise ist diese Domain auch nicht so groß oder generisch, sodass Sie dies wahrscheinlich in der Anwendungslogik beibehalten können.
In einer kleinen App würde ich die Größe der Bilder zum Zeitpunkt des Lesens ändern. Zum Beispiel könnte ich eine Apache-Umschreiberegel haben, die delegiert, um ein Skript zu phpieren, wenn das Bild 'http://my.site.com/images/thumbnails/image1.png', wobei die Datei unter dem Namen image1.png abgerufen wird und in der Größe geändert und in 'thumbnails / image1.png' gespeichert. Bei der nächsten Anforderung an dasselbe Image wird Apache das Image dann direkt bereitstellen, ohne das PHP-Skript auszuführen. Ihre Frage zu findAllImagesWithoutThumbnails wird automatisch von Apache beantwortet, es sei denn, Sie müssen Statistiken erstellen?
In einer groß angelegten App würde ich die neuen Bilder an einen Hintergrundjob senden, der sich darum kümmert, die verschiedenen Versionen des Bildes zu generieren und an geeigneten Stellen zu speichern. Ich würde mir nicht die Mühe machen, eine ganze Domain oder Klasse zu erstellen, da es sehr unwahrscheinlich ist, dass diese Domain zu einem schrecklichen Durcheinander von Spaghetti und schlechter Sauce wird.
quelle
Kurze Antwort:
Meine Empfehlung ist, diese Methoden der Bildklasse hinzuzufügen:
Das Image-Objekt ist immer noch unveränderlich. Diese Methoden geben ein neues Image zurück.
quelle
Es gibt bereits einige gute Antworten, daher werde ich ein wenig auf eine Heuristik eingehen, die hinter der Identifizierung von Objekten und ihrer Verantwortung steckt.
OOP unterscheidet sich vom realen Leben darin, dass Objekte im realen Leben oft passiv sind und in OOP aktiv sind. Und das ist ein Kern des Objektdenkens . Wer würde beispielsweise die Größe eines Bildes im wirklichen Leben ändern? Ein Mensch, der in dieser Hinsicht klug ist. Aber in OOP gibt es keine Menschen, also sind Objekte stattdessen intelligent. Eine Möglichkeit, diesen "menschenzentrierten" Ansatz in OOP zu implementieren, besteht darin, Serviceklassen zu verwenden, beispielsweise berüchtigte
Manager
Klassen. Somit werden Objekte als passive Datenstrukturen behandelt. Es ist kein OOP-Weg.Es gibt also zwei Möglichkeiten. Die erste Methode zum Erstellen einer Methode
Image::createThumbnail()
wird bereits berücksichtigt. Die zweite besteht darin, eineResizedImage
Klasse zu erstellen . Es kann ein Dekorateur von seinImage
(es hängt von Ihrer Domain ab, ob eineImage
Schnittstelle beibehalten werden soll oder nicht), obwohl dies zu einigen Kapselungsproblemen führt, daResizedImage
eineImage
Quelle erforderlich wäre . EsImage
würde jedoch nicht überfordert sein, die Größe von Details zu ändern und sie einem separaten Domänenobjekt zu überlassen, das gemäß einer SRP handelt.quelle