Piszę aplikację, która będzie miała Image
encję, i już mam problemy z ustaleniem, za kogo powinno być każde zadanie.
Najpierw mam Image
klasę. Ma ścieżkę, szerokość i inne atrybuty.
Następnie stworzyłem ImageRepository
klasę do wyszukiwania obrazów za pomocą jednej i przetestowanej metody, np findAllImagesWithoutThumbnail()
. :
Ale teraz muszę też móc createThumbnail()
. Kto powinien sobie z tym poradzić? Myślałem o posiadaniu ImageManager
klasy, która byłaby klasą specyficzną dla aplikacji (byłby też wybrany element do manipulacji obrazem innej firmy, nie zmieniam koła).
A może byłoby 0K, aby pozwolić na Image
zmianę rozmiaru? Albo niech ImageRepository
i ImageManager
być tej samej klasy?
Co myślisz?
object-oriented
domain-driven-design
ChocoDeveloper
źródło
źródło
Odpowiedzi:
Zadane pytanie jest zbyt niejasne, aby dać prawdziwą odpowiedź, ponieważ tak naprawdę zależy od tego, w jaki sposób
Image
będą używane obiekty.Jeśli używasz obrazu tylko w jednym rozmiarze i zmieniasz rozmiar, ponieważ obraz źródłowy ma niewłaściwy rozmiar, najlepiej może być zmiana rozmiaru przez odczytany kod. Niech twoja
createImage
metoda przyjmie szerokość / wysokość, a następnie zwróci obraz, którego rozmiar został zmieniony na tę szerokość / wysokość.Jeśli potrzebujesz wielu rozmiarów i jeśli pamięć nie stanowi problemu, najlepiej jest zachować obraz w pamięci jako pierwotnie odczytany i wykonać dowolną zmianę rozmiaru w czasie wyświetlania. W takim przypadku możesz użyć kilku różnych wzorów. Obraz
width
iheight
będzie ustalona, ale trzeba mieć albodisplay
sposobu, który zajął stanowisko i docelową wysokość / szerokość lub chcesz mieć jakąś metodę robienia szerokość / wysokość, że zwracany obiekt, który bez względu na system wyświetlania używasz. Większość używanych interfejsów API do rysowania pozwala określić docelowy rozmiar w czasie rysowania.Jeśli wymagania powodują rysowanie w różnych rozmiarach wystarczająco często, aby wydajność stanowiła problem, możesz mieć metodę, która tworzy nowy obraz o innym rozmiarze na podstawie oryginału. Alternatywą byłoby
Image
wewnętrzne przechowywanie w pamięci podręcznej różnych reprezentacji, aby po pierwszym wywołaniudisplay
z rozmiarem miniatury zmienił rozmiar, a za drugim razem po prostu narysował buforowaną kopię, którą zapisał z ostatniego razu. To zużywa więcej pamięci, ale rzadko zdarza się, że masz więcej niż kilka typowych zmian rozmiaru.Inną alternatywą jest posiadanie jednej
Image
klasy, która zachowuje obraz podstawowy i aby klasa zawierała jedną lub więcej reprezentacji.Image
Sama nie miałaby wysokość / szerokość. Zamiast tego zaczynałby się od takiego,ImageRepresentation
który miał wysokość i szerokość. Narysowałbyś tę reprezentację. Aby „zmienić rozmiar” obrazu, należy poprosićImage
o reprezentację z pewnymi wskaźnikami wysokości / szerokości. Spowodowałoby to, że zawierałoby to nową reprezentację, a także oryginał. Daje to dużą kontrolę nad tym, co dokładnie kręci się w pamięci, kosztem dodatkowej złożoności.Osobiście nie lubię klas zawierających to słowo,
Manager
ponieważ „manager” to bardzo niejasne słowo, które tak naprawdę niewiele mówi o tym, co robi klasa. Czy zarządza czasem życia obiektu? Czy stoi między resztą aplikacji a tym, czym zarządza?źródło
Uprość wszystko, o ile jest tylko kilka wymagań, i popraw swój projekt, kiedy będziesz musiał. Myślę, że w większości rzeczywistych przypadków nie ma nic złego w rozpoczęciu od takiego projektu:
Rzeczy mogą się różnić, gdy musisz przekazać więcej parametrów
createThumbnail()
, a parametry te wymagają własnego życia. Załóżmy na przykład, że masz zamiar utworzyć miniatury dla kilkuset zdjęć, wszystkie o określonym rozmiarze docelowym, algorytmie zmiany rozmiaru lub jakości. Oznacza to, że możesz przenieśćcreateThumbnail
inną klasę, na przykład klasę menedżera lubImageResizer
klasę, w której te parametry są przekazywane przez konstruktor, a zatem są związane z czasem życiaImageResizer
obiektu.Właściwie zacznę od pierwszego podejścia i refaktoryzuję później, kiedy naprawdę będę tego potrzebować.
źródło
ImageResizer
klasy? Kiedy delegujesz rozmowę zamiast przenosić odpowiedzialność na nową klasę?Image thumbnail = img.createThumbnail(x,y)
.Myślę, że musiałby to być element
Image
klasy, ponieważ zmiana rozmiaru w klasie zewnętrznej wymagałaby od resizera znajomości implementacjiImage
, co naruszyłoby enkapsulację. Zakładam, żeImage
jest to klasa podstawowa, a ty skończysz z osobnymi podklasami dla konkretnych typów obrazów (PNG, JPEG, SVG itp.). Dlatego musisz albo mieć odpowiednie klasy zmiany rozmiaru, albo ogólny resizer zswitch
instrukcją, która zmienia rozmiar w oparciu o klasę implementacji - klasyczny zapach projektowy.Jednym z podejść może być
Image
pobranie konstruktora od zasobu zawierającego obraz oraz parametry wysokości i szerokości i utworzenie go odpowiednio. Następnie zmiana rozmiaru może być tak prosta, jak utworzenie nowego obiektu przy użyciu oryginalnego zasobu (buforowanego wewnątrz obrazu) i nowych parametrów rozmiaru. Np . Pofoo.createThumbnail()
prostureturn new Image(this.source, 250, 250)
. ( oczywiścieImage
z konkretnym typemfoo
). Dzięki temu twoje obrazy pozostają niezmienne, a ich implementacje prywatne.źródło
Image
. Wszystko czego potrzebuje to wymiary źródłowe i docelowe.Wiem, że OOP polega na enkapsulacji danych i zachowaniu razem, ale nie sądzę, że dobrym pomysłem jest, aby Image miał w tym przypadku logikę zmiany rozmiaru, ponieważ Obraz nie musi wiedzieć, jak się zmienić obraz.
Miniatura jest w rzeczywistości innym obrazem. Być może masz strukturę danych, która utrzymuje relację między zdjęciem a jego miniaturą (oba są obrazami).
Próbuję podzielić moje programy na rzeczy (takie jak obrazy, fotografie, miniatury itp.) I usługi (takie jak PhotographRepository, ThumbnailGenerator itp.). Popraw swoje struktury danych, a następnie zdefiniuj usługi, które pozwalają tworzyć, manipulować, przekształcać, utrwalać i odzyskiwać te struktury danych. Nie stawiam więcej zachowań w moich strukturach danych niż upewnianie się, że są one prawidłowo tworzone i odpowiednio wykorzystywane.
Dlatego nie, obraz nie powinien zawierać logiki dotyczącej sposobu tworzenia miniatury. Powinna istnieć usługa ThumbnailGenerator, która ma metodę taką jak:
Moja większa struktura danych może wyglądać następująco:
Oczywiście może to oznaczać, że robisz wysiłek, którego nie chcesz robić podczas konstruowania obiektu, więc rozważę coś takiego:
... w przypadku, gdy chcesz mieć strukturę danych z leniwą oceną. (Przepraszam, że nie uwzględniłem moich zerowych czeków i nie uczyniłem go bezpiecznym dla wątków, czego chciałbyś, gdybyś próbował naśladować niezmienną strukturę danych).
Jak widać, każda z tych klas jest budowana przez pewnego rodzaju PhotographRepository, który prawdopodobnie ma odniesienie do ThumbnailGenerator, który otrzymał poprzez wstrzyknięcie zależności.
źródło
Zidentyfikowałeś jedną funkcjonalność, którą chcesz wdrożyć, więc dlaczego nie powinna być oddzielna od wszystkiego, co dotychczas zidentyfikowałeś? To właśnie sugerowałaby zasada jednolitej odpowiedzialności .
Utwórz
IImageResizer
interfejs, który pozwala przekazać obraz i rozmiar docelowy i który zwraca nowy obraz. Następnie utwórz implementację tego interfejsu. Istnieje wiele sposobów na zmianę rozmiaru zdjęć, więc możesz nawet uzyskać więcej niż jeden!źródło
Zakładam te fakty na temat metody, która zmienia rozmiar obrazów:
Na podstawie tych faktów powiedziałbym, że nie ma powodu, aby metoda zmiany rozmiaru obrazu była częścią samej klasy Image. Najlepszym rozwiązaniem byłoby wdrożenie go jako statycznej metody pomocniczej klasy.
źródło
Odpowiednia może być klasa przetwarzania obrazu (lub menedżer obrazu, jak ją nazywacie). Przekaż swój obraz do metody CreateThumbnail w procesorze obrazu, na przykład, aby uzyskać obraz miniatury.
Jednym z powodów, dla których zasugerowałem tę trasę, jest to, że mówisz, że używasz zewnętrznej biblioteki przetwarzania obrazu. Usunięcie funkcji zmiany rozmiaru z samej klasy Image może ułatwić wyodrębnienie kodu specyficznego dla platformy lub kodu innej firmy. Jeśli więc możesz używać swojej podstawowej klasy obrazu na wszystkich platformach / aplikacjach, nie musisz zanieczyszczać jej kodem specyficznym dla platformy lub biblioteki. Wszystko to może znajdować się w procesorze obrazu.
źródło
Zasadniczo, jak już powiedział Doc Brown:
Utwórz
getAsThumbnail()
metodę dla klasy obrazu, ale ta metoda powinna tak naprawdę delegować pracę do jakiejśImageUtils
klasy. Więc wyglądałoby to tak:I
Umożliwi to łatwiejszy do przeglądania kod. Porównaj następujące:
Lub
Jeśli ta ostatnia wydaje się dla ciebie dobra, możesz także trzymać się gdzieś tej metody pomocniczej.
źródło
Myślę, że w „domenie obrazu” masz tylko obiekt obrazu, który jest niezmienny i monadyczny. Pytasz obraz o wersję o zmienionym rozmiarze, a on zwraca wersję o zmienionym rozmiarze. Następnie możesz zdecydować, czy chcesz pozbyć się oryginału, czy zachować oba.
Teraz miniatura, awatar itp. Wersje obrazu to zupełnie inna domena, która może poprosić domenę obrazu o różne wersje określonego obrazu, które użytkownik może przekazać. Zwykle ta domena również nie jest tak duża ani ogólna, więc prawdopodobnie możesz zachować ją w logice aplikacji.
W aplikacji na małą skalę zmieniałem rozmiar obrazów w czasie odczytu. Na przykład mogę mieć regułę przepisywania apache, która deleguje do php skryptu, jeśli obraz „http://my.site.com/images/thumbnails/image1.png”, gdzie plik zostanie pobrany za pomocą nazwy image1.png oraz zmieniono rozmiar i przechowywano w pliku „thumbnails / image1.png”. Następnie przy następnym żądaniu do tego samego obrazu apache będzie wyświetlał obraz bezpośrednio bez uruchamiania skryptu php. Odpowiedź na pytanie findAllImagesWithoutThumbnails jest automatycznie udzielana przez apache, chyba że potrzebujesz statystyk.
W aplikacji na dużą skalę wysyłałbym wszystkie nowe obrazy do pracy w tle, która zajmuje się generowaniem różnych wersji obrazu i zapisuje go w odpowiednich miejscach. Nie zawracałbym sobie głowy tworzeniem całej domeny lub klasy, ponieważ jest mało prawdopodobne, aby ta domena przerodziła się w straszny bałagan spaghetti i złego sosu.
źródło
Krótka odpowiedź:
Moim zaleceniem jest dodanie tej metody do klasy image:
Obiekt Image jest nadal niezmienny, te metody zwracają nowy obraz.
źródło
Jest już kilka świetnych odpowiedzi, więc rozwinę trochę heurystyki za sposobem identyfikacji obiektów i ich obowiązków.
OOP różni się od prawdziwego życia tym, że przedmioty w prawdziwym życiu są często pasywne, aw OOP są aktywne. I to jest sedno myślenia obiektowego . Na przykład, kto zmieniłby rozmiar obrazu w prawdziwym życiu? Człowiek, który jest mądry pod tym względem. Ale w OOP nie ma ludzi, więc obiekty są inteligentne. Sposobem na wdrożenie tego „skoncentrowanego na człowieku” podejścia w OOP jest wykorzystanie klas usług, na przykład
Manager
klas notorycznych . Zatem obiekty są traktowane jako pasywne struktury danych. To nie jest metoda OOP.Istnieją więc dwie opcje. Pierwszy, tworzenie metody
Image::createThumbnail()
, jest już rozważany. Drugim jest stworzenieResizedImage
klasy. Może być dekoratoremImage
(zależy od domeny, czy zachowaćImage
interfejs, czy nie), chociaż powoduje pewne problemy z enkapsulacją, ponieważResizedImage
musiałby miećImage
źródło. AleImage
nie byłby przytłoczony zmianą wielkości szczegółów, pozostawiając go osobnemu obiektowi domeny, działając zgodnie z SRP.źródło