图像应该能够在OOP中调整大小吗?


9

我正在编写一个具有Image实体的应用程序,并且在确定每个任务应由谁负责方面已经遇到了麻烦。

首先我Image上课。它具有路径,宽度和其他属性。

然后,我创建了一个ImageRepository类,用于通过一个经过测试的方法来检索图像,例如:findAllImagesWithoutThumbnail()

但是现在我还需要能够createThumbnail()。谁应该处理?我当时正在考虑开设一个ImageManager类,该类将是特定于应用程序的类(还可以选择第三方图像处理可重用的组件,而我并不是在重新发明轮子)。

还是让Image自身调整大小为0K ?还是让ImageRepositoryImageManager成为同一个类?

你怎么看?


到目前为止,Image会做什么?
温斯顿·埃韦特

您如何期望调整图像的大小?您是只缩小图像,还是可以想象要返回完整尺寸?

@WinstonEwert它生成诸如格式的分辨率(例如:字符串'1440x900'),不同的URL之类的数据,并允许您修改某些统计信息,例如“视图”或“投票”。
ChocoDeveloper 2012年

@StevenBurnap我认为原始图像是最重要的,我只使用缩略图来加快浏览速度,或者如果用户想要调整大小以适合他的桌面或其他东西,那么它们是独立的。
ChocoDeveloper 2012年

我将使Image类不可变,这样您在传递它时就不必防御性地复制它。
dan_waterworth 2012年

Answers:


8

所提出的问题太含糊,无法给出真正的答案,因为它实际上取决于Image对象的使用方式。

如果仅使用一种尺寸的图像,并且由于源图像的尺寸错误而正在调整大小,则最好让读取的代码进行大小调整。让您的createImage方法采用宽度/高度,然后返回以该宽度/高度调整大小的图像。

如果您需要多种尺寸,并且不关心内存,那么最好将图像保留在原始读取的内存中,并在显示时进行任何调整大小。在这种情况下,您可能会使用几种不同的设计。图像widthheight将被固定的,但你要么有一个display其采取了位置和目标高度/宽度的方法或你有一些方法采取任何返回的任何显示系统使用的是物体的宽度/高度。我使用的大多数绘图API都可以让您在绘制时指定目标尺寸。

如果要求导致经常使用不同尺寸的图纸来引起性能问题,那么您可以使用一种方法来基于原始图像创建不同尺寸的新图像。一种替代方法是让您的Image类在内部缓存不同的表示形式,这样,第一次display使用缩略图大小进行调用时,它将进行大小调整,而第二次,它只绘制从上次保存的缓存副本。这会使用更多的内存,但是很少有几种常见的调整大小。

另一种选择是让一个Image类保留基本图像,并使该类包含一个或多个表示形式。一个Image本身并不具有高度/宽度。相反,它与一个开始ImageRepresentation即有高度和宽度。您将绘制此表示形式。要“调整”图像的大小,您Image需要使用来确定具有特定高度/宽度指标的图像。这将导致它随后包含此新表示以及原始表示。这使您可以完全控制内存中所徘徊的内容,但为此付出了额外的复杂性。

我个人不喜欢包含该单词的类,Manager因为“ manager”是一个非常模糊的单词,实际上并不能告诉您有关类的确切信息。它管理对象寿命吗?它是否位于应用程序的其余部分和它管理的事物之间?


“这实际上取决于如何使用Image对象。” 该声明与封装和松散耦合的概念并不完全一致(尽管在这种情况下,可能会使原理有点过头了)。更好的区分点是Image和Image的状态是否有意义地包括大小。
克里斯·拜

您似乎在谈论“图像编辑”应用程序的观点。还不清楚OP是否想要这个吗?
andho 2012年

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类的职责呢?什么时候委托电话而不是将职责转移到新的班级?
桑戈,2012年

2
@Songo:2个可能的原因:(1)用于委派给-可能已经存在的-独立组件的代码不仅是一个简单的直线,而且可能只是4到6个命令的序列,因此您需要一个存放它的地方。(2)语法糖/易用性:写起来会很帅Image thumbnail = img.createThumbnail(x,y)
布朗

我知道了+1。感谢您的解释:)
Songo 2012年

4

我认为它必须是Image该类的一部分,因为在外部类中调整大小将要求调整大小者知道的实现Image,从而违反了封装。我假设它Image是一个基类,您将获得针对具体图像类型(PNG,JPEG,SVG等)的单独子类。因此,您要么必须具有相应的调整大小类,要么需要具有switch根据实现类调整大小的语句的通用大小调整器-一种经典的设计气味。

一种方法是让您的Image构造函数获取包含图像以及height和width参数的资源,并适当地创建自己。然后,调整大小就像使用原始资源(缓存在图像中)和新的size参数创建新对象一样简单。例如foo.createThumbnail(),简单地return new Image(this.source, 250, 250)。(当然Image是的具体类型foo)。这样可以使图像保持不变,并使它们的实现保持私有。


我喜欢这种解决方案,但是我不明白为什么您说调整器需要了解的内部实现Image。它所需要的只是源尺寸和目标尺寸。
ChocoDeveloper 2012年

虽然我认为拥有外部类是没有意义的,因为这是意外的,但执行调整大小时既不必违反封装,也不需要具有switch()语句。归根结底,图像是二维数组,只要界面允许获取和设置单个像素,您就可以确定图像的大小。
whatsisname

4

我知道OOP就是将数据和行为封装在一起,但是在这种情况下,对于Image嵌入调整大小逻辑不是一个好主意,因为Image 不需要知道如何调整大小即可一个图像。

缩略图实际上是不同的图像。也许您的数据结构可能包含照片与其缩略图(两者均为图像)之间的关系。

我尝试将程序分为事物(如图像,照片,缩略图等)和服务(如PhotoRepository,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;
        }
    }
}

...如果您想要一个具有惰性求值的数据结构。(很抱歉,我没有包含null检查,也没有使它成为线程安全的,如果您想模仿不可变的数据结构,这是您想要的)。

如您所见,这些类中的任何一个都由某种PhotographRepository构建,该类可能引用了通过依赖项注入获得的ThumbnailGenerator。


有人告诉我,我不应该创建没有行为的类。不确定当您说“数据结构”时是在指的是类还是C ++中的某种东西(是这种语言吗?)。我知道和使用的唯一数据结构是原语。服务和DI部分已经准备就绪,我可能最终会这样做。
ChocoDeveloper 2012年

@ChocoDeveloper:视情况而定,有时无行为的类有时是有用或必要的。这些被称为值类。普通的OOP类是具有硬编码行为的类。可组合的OOP类也具有硬编码的行为,但是其组成结构会引起软件应用程序所需的许多行为。
rwong 2012年

3

您已经确定了要实现的单个功能,那么为什么不将其与迄今为止确定的所有功能区分开?这就是“ 单一责任原则”所建议的解决方案。

创建一个IImageResizer界面,该界面允许您传递图像和目标尺寸,并返回新图像。然后创建该接口的实现。实际上,有无数种调整图像大小的方法,因此您甚至可能获得不止一种!


+1为相关的SRP,但我没有实现实际的大小调整,如上所述,该大小已委托给第三方库。
ChocoDeveloper 2012年

3

我假设有关方法的事实会调整图像的大小:

  • 它必须返回图像的新副本。您无法修改图像本身,因为它会破坏引用该图像的其他代码。
  • 它不需要访问Image类的内部数据。图像类通常需要提供对这些数据(或副本)的公共访问。
  • 图像调整大小很复杂,需要许多不同的参数。甚至不同大小调整算法的可扩展性点。传递所有内容将导致较大的方法签名。

基于这些事实,我想说Image大小调整方法没有理由成为Image类本身的一部分。最好将其实现为类的静态辅助方法。


好的假设。虽然不确定为什么它应该是静态方法,但我尝试避免它们的可测试性。
ChocoDeveloper 2012年

2

图像处理类可能是合适的(或您所说的图像管理器)。例如,将图像传递到图像处理器的CreateThumbnail方法,以检索缩略图。

我建议使用此路线的原因之一是您说您正在使用第三方图像处理库。将调整大小功能移出Image类本身可能会使您更容易隔离任何特定于平台或第三方的代码。因此,如果可以在所有平台/应用程序中使用基本Image类,则不必使用平台或库特定的代码对其进行污染。那可以全部位于图像处理器中。


好点子。这里的大多数人都不理解我已经将最复杂的部分委派给了第三方库,也许我还不够清楚。
ChocoDeveloper 2012年

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

我认为在“图像域”中,您只有不可变且单调的Image对象。您要求映像提供调整大小的版本,并且它返回其自身的调整大小的版本。然后,您可以决定要摆脱原始文件还是保留两者。

现在,图像的缩略图,头像等版本完全是另一个域,可以向图像域询问要提供给用户的某个图像的不同版本。通常,这个域也不是那么庞大或通用,因此您可以将其保留在应用程序逻辑中。

在小型应用程序中,我将在读取时调整图像的大小。例如,我可以有一个Apache重写规则,如果图像“ http://my.site.com/images/thumbnails/image1.png”(该文件将使用名称image1.png来检索),则该脚本将php委托给php并调整大小并存储在“ thumbnails / image1.png”中。然后,在对同一图像的下一个请求时,apache将直接提供该图像,而无需运行php脚本。除非您需要进行统计,否则Apache会自动回答您的findAllImagesWithoutThumbnails问题。

在大型应用程序中,我会将所有新图像发送到后台作业,该作业负责生成图像的不同版本并将其保存在适当的位置。我不会费心创建一个完整的域或类,因为该域极不可能变成一团糟的意大利面和烂酱。


0

简短答案:

我的建议是将此方法添加到图像类:

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

Image对象仍然是不变的,这些方法返回一个新图像。


0

已经有很多不错的答案,因此,我将详细说明识别对象及其职责的方法背后的启发式方法。

OOP与现实生活的不同之处在于,现实生活中的对象通常是被动的,而在OOP中则是主动的。这是对象思考的核心。例如,谁会在现实生活中调整图像大小?在这方面很聪明的人。但是在OOP中没有人,因此对象是聪明的。在OOP中实现这种“以人为中心”方法的方法是利用服务类,例如臭名昭​​著的Manager类。因此,对象被视为被动数据结构。这不是OOP方式。

因此,有两种选择。第一个方法(创建方法Image::createThumbnail())已被考虑。第二个是创建一个ResizedImage类。它可能是的装饰器Image(取决于是否保留Image接口,这取决于您的域),尽管它会导致一些封装问题,因为ResizedImage必须具有Image的源。但Image不会因调整细节大小而感到不知所措,而是根据SRP将其留给单独的域对象。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.