Чи має зображення мати можливість змінювати розмір в OOP?


9

Я пишу програму, яка матиме Imageсутність, і мені вже виникають проблеми з вирішенням відповідальності, перед якою відповідальністю повинна відповідати кожна задача.

Спершу маю Imageклас. Він має шлях, ширину та інші атрибути.

Потім я створив ImageRepositoryклас, для отримання зображень за допомогою одного і перевіреного методу, наприклад: findAllImagesWithoutThumbnail().

Але тепер мені теж потрібно вміти createThumbnail(). Хто з цим повинен мати справу? Я думав про те, щоб мати ImageManagerклас, який би був специфічним для додатків класом (також було б стороннє використання маніпуляцій із зображенням багаторазового використання, я не винаходити колесо).

А може, було б 0К, щоб дозволити Imageсобі зміни розміру? Або дозволити ImageRepositoryі ImageManagerбути тим самим класом?

Як ти гадаєш?


Що робить Image досі?
Вінстон Еверт

Як ви очікуєте зміни розміру зображень? Ви лише коли-небудь стискаєте зображення чи можете собі уявити, що хочете повернутися до повного розміру?
Gort the Robot

@WinstonEwert Він генерує такі дані, як відформатована роздільна здатність (наприклад: рядок "1440x900"), різні URL-адреси, і дозволяє змінювати деякі статистичні дані, наприклад "перегляди" або "голоси".
ChocoDeveloper

@StevenBurnap Я вважаю оригінальне зображення найважливішим, я використовую лише мініатюри для швидшого перегляду або якщо користувач хоче змінити розмір, щоб підходити до свого робочого столу чи чогось, тому вони є окремою річчю.
ChocoDeveloper

Я зробив би клас Image непорушним, щоб вам не довелося оборонно копіювати його під час передачі.
dan_waterworth

Відповіді:


8

На поставлене запитання занадто розпливчастий, щоб мати справжню відповідь, оскільки це насправді залежить від того, яким чином Imageоб’єкти будуть використовуватися.

Якщо ви використовуєте зображення лише в одному розмірі і змінюєте розмір, оскільки вихідний образ має неправильний розмір, можливо, найкраще буде прочитати код читання. Запропонуйте вашому createImageметоду взяти ширину / висоту, а потім поверніть зображення, змінивши розмір на цій ширині / висоті.

Якщо вам потрібні декілька розмірів, і якщо пам'ять не викликає особливих проблем, найкраще зберегти зображення в пам'яті як спочатку прочитане, а також змінити розміри під час відображення. У такому випадку ви можете використовувати кілька різних конструкцій. Зображення widthі heightбуло б виправлене, але у вас був би displayметод, який займав позицію та цільову висоту / ширину, або ви мали б якийсь метод зйомки ширини / висоти, який повертав об'єкт, який би не використовували будь-яку систему відображення. Більшість API, які я використовував, дозволяють вказати розмір цілі під час малювання.

Якщо вимоги викликають малювання різного розміру досить часто, щоб продуктивність викликала занепокоєння, у вас може бути метод, який створює нове зображення іншого розміру на основі оригіналу. Альтернативою було б мати Imageкеш класу різних представлень внутрішньо, так що перший раз, коли ви дзвоните displayз розміром мініатюри, він робить зміну розміру, а другий раз просто малює кешовану копію, збережену минулого разу. Для цього використовується більше пам'яті, але рідко у вас буде більше кількох загальних розмірів.

Іншою альтернативою є створення одного Imageкласу, який зберігає базове зображення, і цей клас містить одне або більше представлень. Сама Imageпо собі не мала б висоти / ширини. Натомість він би починався з тієї, ImageRepresentationяка мала висоту та ширину. Ви намалювали б це уявлення. Щоб "змінити розмір" зображення, ви попросите Imageпредставлення з певними показниками висоти / ширини. Це призвело б до того, щоб він містив це нове представлення, а також оригінал. Це дає вам великий контроль над тим, що саме зависає в пам’яті ціною додаткової складності.

Мені особисто не подобаються класи, які містять слово, Managerтому що "менеджер" - це дуже розпливчасте слово, яке насправді не розповідає багато про те, що саме робить клас. Чи керує воно життям об'єкта? Чи стоїть він між рештою програми та предметом, яким він керує?


"це дійсно залежить від того, як будуть використовуватися об'єкти Зображення." Це твердження насправді не відповідає поняттю інкапсуляції та нещільної зв'язку (хоча в цьому випадку принцип може бути занадто далеко). Кращою точкою диференціації буде те, чи зміст штату зображення має розмір чи ні.
Кріс Бай

Здається, ви говорите про точку зору програми редагування зображень. Не зовсім зрозуміло, хотіла ОП цього чи ні?
andho

6

Дотримуйтесь простих речей, доки є лише кілька вимог, і вдосконалюйте дизайн, коли потрібно. Я думаю, що в більшості реальних випадків немає нічого поганого, щоб почати з такого дизайну:

class Image
{
    public Image createThumbnail(int sizeX, int sizeY)
    {
         // ...
         // later delegate the actual resize operation to a separate component
    }
}

Речі можуть стати іншими, коли вам потрібно передати більше параметрів createThumbnail(), і ці параметри потребують власного життя. Наприклад, припустимо, що ви збираєтеся створювати ескізи для декількох сотень зображень, які мають певний розмір призначення, алгоритм зміни розміру або якість. Це буде означати, що ви можете перенести createThumbnailінший клас, наприклад, клас менеджера або ImageResizerклас, де ці параметри передаються конструктором і так пов'язані з терміном експлуатації ImageResizerоб'єкта.

Насправді я б почав з першого підходу та рефактора пізніше, коли мені це справді потрібно.


добре, якщо я вже делегую виклик окремому компоненту, чому б ImageResizerв першу чергу не зробити його відповідальністю за клас? Коли ви делегуєте виклик замість того, щоб перекладати відповідальність на новий клас?
Сонго

2
@Songo: 2 можливі причини: (1) код для делегування до - можливо вже існуючого - окремого компонента - це не просто простий одноклапник, можливо, лише послідовність команд від 4 до 6, тому вам потрібно місце для його зберігання . (2) Синтаксичний цукор / простота використання: писати буде просто дуже красиво Image thumbnail = img.createThumbnail(x,y).
Doc Brown

+1 аа я бачу. Дякую за пояснення :)
Songo

4

Я думаю, що це повинно бути частиною Imageкласу, оскільки зміна розміру у зовнішньому класі вимагатиме від резизера знати реалізацію Image, тим самим порушуючи інкапсуляцію. Я припускаю Image, що це базовий клас, і ви закінчите окремі підкласи для конкретних типів зображень (PNG, JPEG, SVG тощо). Тому вам або доведеться мати відповідні класи зміни розміру, або загальний резизер із switchзаявою, яка змінює розмір на основі класу реалізації - класичний запах дизайну.

Одним із підходів може бути те, щоб ваш Imageконструктор взяв ресурс, що містить параметри зображення, висоти та ширини, і створити себе належним чином. Тоді зміна розміру може бути настільки ж простою, як створення нового об'єкта за допомогою оригінального ресурсу (кешований всередині зображення) та нових параметрів розміру. Наприклад foo.createThumbnail(), просто return new Image(this.source, 250, 250). ( звичайно, Imageконкретний тип foo). Це зберігає ваші зображення незмінними та їх реалізацію приватними.


Мені подобається це рішення, але я не розумію, чому ви говорите, що резизер повинен знати внутрішню реалізацію Image. Все, що йому потрібно, - це вихідний і цільовий розміри.
ChocoDeveloper

Хоча я думаю, що мати зовнішній клас не має сенсу, оскільки це було б несподівано, воно не повинно було б порушувати інкапсуляцію, а також не вимагати наявності оператора switch () під час виконання розміру. Зрештою, зображення є двовимірним масивом чогось, і ви точно можете змінити розмір зображень до тих пір, поки інтерфейс дозволяє отримувати та встановлювати окремі пікселі.
whatsisname

4

Я знаю, що OOP - це інкапсуляція даних та поведінки разом, але я не думаю, що в Image це гарна ідея, щоб у цьому випадку була вбудована логіка розміру, тому що Image не повинен знати, як змінити розмір, щоб бути Зображення.

Зображення - це фактично інше зображення. Можливо, у вас може бути структура даних, яка містить зв’язок між фотографією та її ескізом (обидва це зображення).

Я намагаюся розділити свої програми на речі (наприклад, зображення, фотографії, мініатюри тощо) та послуги (наприклад, PhotographRepository, ThumbnailGenerator тощо). Налаштуйте свої структури даних правильно, а потім визначте служби, які дозволять створювати, маніпулювати, трансформувати, зберігати та відновити ці структури даних. Я не вкладаю більше поведінки в свої структури даних, ніж переконуюсь, що вони створені належним чином та використовуються належним чином.

Тому ні, зображення не повинно містити логіки щодо створення ескізу. Повинна бути служба ThumbnailGenerator, яка має такий метод, як:

Image GenerateThumbnailFrom(Image someImage);

Моя більша структура даних може виглядати приблизно так:

class Photograph : Image
{
    public Photograph(Image thumbnail)
    {
        if(thumbnail == null) throw new ArgumentNullException("thumbnail");
        this.Thumbnail = thumbnail;
    }

    public Image Thumbnail { get; private set; }
}

Звичайно, це може означати, що ви докладаєте зусиль, яких не хочете робити під час створення об'єкта, тому я також вважаю, що таке ОК:

class Photograph : Image
{
    private Image thumbnail = null;
    private readonly Func<Image,Image> generateThumbnail;

    public Photograph(Func<Image,Image> generateThumbnail)
    {
        this.generateThumbnail = generateThumbnail;
    }


    public Image Thumbnail
    {
        get
        {
            if(this.thumbnail == null)
            {
                this.thumbnail = this.generateThumbnail(this);
            }
            return this.thumbnail;
        }
    }
}

... у випадку, коли ви хочете структуру даних з ледачою оцінкою. (Вибачте, що я не включив свої нульові перевірки, і я не зробив це безпечним для потоків. Це те, що ви хотіли б, якби ви намагалися наслідувати незмінну структуру даних).

Як бачимо, будь-який з цих класів будується за допомогою якогось PhotographRepository, який, ймовірно, має посилання на ThumbnailGenerator, який він отримав за допомогою ін'єкції залежності.


Мені сказали, що я не повинен створювати заняття без поведінки. Не впевнений, якщо ви говорите "структури даних", ви посилаєтесь на класи або щось із C ++ (це ця мова?). Єдині структури даних, які я знаю і використовую, - це примітиви. Частина сервісів та DI є на місці, я можу це зробити.
ChocoDeveloper

@ChocoDeveloper: час від часу заняття без поведінки корисні чи необхідні, залежно від ситуації. Вони називаються класами цінності . Нормальні класи OOP - це класи з жорсткою поведінкою. Компонентні класи OOP також мають жорстку поведінку, але структура їх складу може спричинити багато поведінки, необхідних програмному застосуванню.
rwong

3

Ви визначили єдину функціональність, яку хочете реалізувати, то чому б вона не була відокремлена від усього, що ви визначили досі? Саме це може запропонувати Принцип єдиної відповідальності .

Створіть IImageResizerінтерфейс, який дозволяє передавати зображення та цільовий розмір, який повертає нове зображення. Потім створіть реалізацію цього інтерфейсу. Насправді існує безліч способів зміни розміру зображень, так що ви навіть можете створити більше, ніж один!


+1 для відповідних SRP, але я не здійснюю фактичного зміни розміру, це вже делеговано третій сторонній бібліотеці, як зазначено.
ChocoDeveloper

3

Я припускаю ті факти про метод, який змінює розміри зображень:

  • Він повинен повернути нову копію зображення. Ви не можете змінити саме зображення, оскільки воно порушило б інший код, що має посилання на це зображення.
  • Він не потребує доступу до внутрішніх даних класу Image. Класи зображень зазвичай повинні пропонувати доступ публіці до цих даних (або копії).
  • Змінення розміру зображення є складним і вимагає безлічі різних параметрів. Можливо, навіть точки розширення для різних алгоритмів зміни розміру. Передача всього цього призведе до великого підпису методу.

Виходячи з цих фактів, я б сказав, що немає ніяких причин метод зміни розміру зображення бути частиною самого класу Image. Реалізовувати його як статичний допоміжний метод класу було б найкраще.


Гарні припущення. Не впевнений, чому це повинен бути статичний метод, але я намагаюся уникати їх для перевірки.
ChocoDeveloper

2

Клас обробки зображень може бути відповідним (або Image Manager, як ви його назвали). Передайте зображення, наприклад, методу CreateThumbnail Процесора зображень, щоб отримати мініатюрне зображення.

Однією з причин, чому я пропоную цей маршрут, є те, що ви говорите, що використовуєте сторонні бібліотеку обробки зображень. Виведення функціонального розміру із класу Image може полегшити вам ізоляцію будь-якого коду, що відповідає конкретній платформі, або стороннього коду. Отже, якщо ви можете використовувати базовий клас зображень на всіх платформах / програмах, вам не доведеться забруднювати його конкретним кодом платформи чи бібліотеки. Це все може бути розміщено в процесорі зображення.


Гарна думка. Більшість людей тут не розуміли, що я вже делегую найскладнішу частину до бібліотеки третьої партії, можливо, я був недостатньо зрозумілий.
ChocoDeveloper

2

В основному, як Док Браун вже сказав:

Створіть getAsThumbnail()метод для класу зображень, але цей метод насправді повинен просто делегувати роботу деякому ImageUtilsкласу. Так би виглядало приблизно так:

 class Image{
   // ...
   public Thumbnail getAsThumbnail{
     return ImageUtils.convertToThumbnail(this);
   }
   // ...
 }

І

 class ImageUtils{
   // ...
   public static Thumbnail convertToThumbnail(Image i){
     // ...
   }
   // ...
 }

Це дозволить простіше переглянути код. Порівняйте наступне:

Image i = ...
someComponent.setThumbnail(i.getAsThumbnail());

Або

Image i = ...
Thumbnail t = ImageUtils.convertToThumbnail(i);
someComponent.setThumbnail(t); 

Якщо останній здається вам хорошим, ви також можете дотримуватися десь просто створення цього помічника.


1

Я думаю, що у "Домен зображень" у вас просто є об'єкт "Зображення", який є незмінним і монадичним. Ви запитуєте зображення для зміни розміру, і воно повертає змінену версію себе. Тоді ви можете вирішити, чи хочете ви позбутися оригіналу чи зберегти обоє.

Тепер ескіз, аватар та ін. Версії Зображення цілком є ​​іншим Доменом, який може попросити домен зображення для різних версій певного зображення надати користувачеві. Зазвичай цей домен також не такий величезний або загальний, тому ви, ймовірно, можете зберігати це в логіці програми.

У невеликому масштабі додаток я міняв би розмір зображень за час читання. Наприклад, у мене може бути правило перезаписати apache, яке делегує php-скрипт, якщо зображення 'http://my.site.com/images/thumbnails/image1.png', де файл буде отриманий за допомогою імені image1.png і змінено розмір і зберігається у "мініатюрах / image1.png". Тоді при наступному запиті до цього ж зображення, apache подаватиме зображення безпосередньо, не запускаючи скрипт php. На ваше запитання findAllImagesWithoutThumbnails автоматично відповідає відповідь apache, якщо вам не потрібно робити статистику?

У великому масштабі додаток я б надіслав усі нові зображення на фонове завдання, яке піклується про створення різних версій зображення та зберігає їх у відповідних місцях. Я б не переймався створенням цілого домену чи класу, оскільки цей домен навряд чи переросте в жахливий безлад спагетті та поганий соус.


0

Коротка відповідь:

Моя рекомендація додає цим методам клас зображень:

public Image getResizedVersion(int width, int height);
public Image getResizedVersion(double percentage);

Об'єкт Image досі є незмінним, ці методи повертають нове зображення.


0

Вже є досить багато чудових відповідей, тому я детально розповім про евристику, яка лежить в основі способу визначення об'єктів та їх обов'язків.

OOP відрізняється від реального життя тим, що об'єкти в реальному житті часто пасивні, а в ООП вони активні. І це ядро об’єктного мислення . Наприклад, хто змінив би розмір зображення в реальному житті? Людина, яка розумна в цьому відношенні. Але в ООП немає людей, тому натомість об'єкти розумні. Способом реалізації цього "орієнтованого на людину" підходу в ООП є використання службових класів, наприклад, горезвісних Managerкласів. Таким чином, об'єкти трактуються як пасивні структури даних. Це не спосіб OOP.

Тож є два варіанти. Перший, створюючи метод Image::createThumbnail(), вже розглядається. Другий - створити ResizedImageклас. Він може бути декоратором Image(це залежить від вашого домену, зберегти Imageінтерфейс чи ні), хоча це призводить до деяких проблем з інкапсуляцією, оскільки ResizedImageмає мати Imageджерело. Але не Imageбуло б переповнене зміною розміру деталей, залишаючи її окремому об'єкту домену, діючи відповідно до SRP.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.