Должно ли изображение иметь возможность изменять размеры в ООП?

9

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

Сначала у меня есть Imageкласс. У него есть путь, ширина и другие атрибуты.

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

Но теперь мне тоже нужно уметь createThumbnail(). Кто должен иметь дело с этим? Я думал о том, чтобы иметь ImageManagerкласс, который был бы классом для конкретного приложения (был бы также сторонний компонент многократного использования для манипулирования изображениями, я не изобретаю колесо).

Или, может быть, было бы 0K, чтобы позволить Imageизменить сам размер? Или позвольте ImageRepositoryи ImageManagerбыть того же класса?

Что вы думаете?

ChocoDeveloper
источник
Что делает 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потому что «менеджер» - очень расплывчатое слово, которое мало что говорит о том, что именно делает класс. Управляет ли он временем жизни объекта? Стоит ли оно между остальной частью приложения и тем, чем оно управляет?

Горт Робот
источник
«Это действительно зависит от того, как объекты Image будут использоваться». Это утверждение на самом деле не согласуется с понятием инкапсуляции и слабой связи (хотя в этом случае оно может зайти слишком далеко). Лучшей точкой дифференциации было бы то, включает ли состояние Image значимо размер или нет.
Крис Бай
Кажется, вы говорите о точке зрения приложения редактирования изображений. Не совсем понятно, хотел ли ОП этого или нет?
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).
Док Браун
+1 ааа понятно Спасибо за объяснение :)
Songo
4

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

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

TMN
источник
Мне нравится это решение, но я не понимаю, почему вы говорите, что ресайзер должен знать внутреннюю реализацию Image. Все, что ему нужно, это исходный и целевой размеры.
ChocoDeveloper
Хотя я думаю, что иметь внешний класс не имеет смысла, потому что это было бы неожиданно, ему не нужно было бы нарушать инкапсуляцию и не требовалось бы наличия оператора switch () при выполнении изменения размера. В конечном итоге изображение представляет собой двумерный массив чего-либо, и вы определенно можете изменить размер изображения, если интерфейс позволяет получать и настраивать отдельные пиксели.
whatsisname
4

Я знаю, что ООП - это инкапсуляция данных и поведения вместе, но я не думаю, что для 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: иногда классы без поведения полезны или необходимы, в зависимости от ситуации. Это так называемые классы значений . Обычные классы ООП - это классы с жестко запрограммированным поведением. Композиционные ООП классы также имеют жестко запрограммированное поведение, но их структура их состава может привести к многим поведениям, необходимым программному приложению.
rwong
3

Вы определили одну функциональность, которую хотите реализовать, так почему бы не выделить ее отдельно от всего, что вы определили до сих пор? Вот что предлагает принцип единой ответственности - это решение.

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

Крис Питман
источник
+1 для соответствующего SRP, но я не использую фактическое изменение размера, которое уже делегировано сторонней библиотеке, как указано.
ChocoDeveloper
3

Я предполагаю те факты о методе, который изменяет размеры изображений:

  • Он должен вернуть новую копию изображения. Вы не можете изменить само изображение, потому что оно нарушит другой код, имеющий ссылку на это изображение.
  • Ему не нужен доступ к внутренним данным класса Image. Классы изображений обычно должны предлагать открытый доступ к этим данным (или их копиям).
  • Изменение размера изображения является сложным и требует много различных параметров. Может быть, даже точки расширяемости для различных алгоритмов изменения размера. Передача всего приведет к большой сигнатуре метода.

Исходя из этих фактов, я бы сказал, что нет никакой причины для метода изменения размера изображения быть частью самого класса Image. Реализация этого как статического вспомогательного метода класса была бы лучшей.

Euphoric
источник
Хорошие предположения. Не уверен, почему это должен быть статический метод, я стараюсь избегать их для тестируемости.
ChocoDeveloper
2

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

Одна из причин, по которой я бы предложил этот маршрут, заключается в том, что вы говорите, что используете стороннюю библиотеку обработки изображений. Исключение функциональности изменения размера из самого класса Image может облегчить вам выделение кода для любой платформы или стороннего кода. Поэтому, если вы можете использовать свой базовый класс Image на всех платформах / приложениях, вам не нужно загрязнять его кодом, специфичным для платформы или библиотеки. Все это может быть расположено в процессоре изображений.

GrandmasterB
источник
Хорошая точка зрения. Большинство людей здесь не поняли, что я уже делегировал самую сложную часть в стороннюю библиотеку, возможно, я не был достаточно ясен.
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); 

Если последний вариант вам подходит, вы также можете просто создать где-нибудь этот вспомогательный метод.

Deiwin
источник
1

Я думаю, что в «Области изображения» у вас есть только объект Image, который является неизменным и монадическим. Вы запрашиваете изображение для измененной версии, и оно возвращает измененную версию самой себя. Затем вы можете решить, хотите ли вы избавиться от оригинала или оставить оба.

Теперь версии изображения, аватара и т. Д. - это совершенно другой домен, который может запрашивать у домена изображения разные версии определенного изображения для предоставления пользователю. Обычно этот домен также не такой большой или общий, так что вы, вероятно, можете сохранить его в логике приложения.

В небольшом приложении я бы изменял размеры изображений во время чтения. Например, у меня может быть правило перезаписи apache, которое делегирует php скрипт, если изображение 'http://my.site.com/images/thumbnails/image1.png', где файл будет извлечен с использованием имени image1.png и изменить размер и сохранить в «thumbnails / image1.png». Затем при следующем запросе к этому же изображению apache будет обслуживать изображение напрямую, без запуска сценария php. Apache автоматически отвечает на ваш вопрос findAllImagesWithoutThumbnails, если вам не нужна статистика?

В крупномасштабном приложении я отправляю все новые изображения в фоновое задание, которое заботится о создании различных версий изображения и сохраняет его в соответствующих местах. Я бы не стал создавать целый домен или класс, так как этот домен вряд ли превратится в ужасный беспорядок спагетти и плохого соуса.

andho
источник
0

Короткий ответ:

Я рекомендую добавить эти методы в класс изображений:

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

Объект Image по-прежнему неизменен, эти методы возвращают новое изображение.

Тулаинс Кордова
источник
0

Уже есть довольно много отличных ответов, поэтому я немного подробнее расскажу об эвристике, лежащей в основе определения объектов и их обязанностей.

ООП отличается от реальной жизни тем, что объекты в реальной жизни часто пассивны, а в ООП они активны. И это ядро объектного мышления . Например, кто изменит размер изображения в реальной жизни? Человек, который умен в этом отношении. Но в ООП нет людей, поэтому объекты умнее. Одним из способов реализации этого «ориентированного на человека» подхода в ООП является использование классов обслуживания, например пресловутых Managerклассов. Таким образом, объекты рассматриваются как пассивные структуры данных. Это не ООП способ.

Так что есть два варианта. Первый, создающий метод Image::createThumbnail(), уже рассмотрен. Второй - создать ResizedImageкласс. Он может быть декоратором Image(зависит от вашего домена, сохранять ли Imageинтерфейс или нет), хотя это приводит к некоторым проблемам инкапсуляции, поскольку ResizedImageдолжен иметь Imageисходный текст. Но an Imageне будет перегружен деталями изменения размера, оставляя это отдельному объекту домена, действующему в соответствии с SRP.

Вадим Самохин
источник