Bir görüntü OOP'de kendini yeniden boyutlandırmalı mı?


9

Bir Imagevarlığı olacak bir uygulama yazıyorum ve zaten her görevin kimin sorumluluğuna karar vermede sorun yaşıyorum.

İlk önce Imagedersim var. Bir yolu, genişliği ve diğer nitelikleri vardır.

Sonra bir yaratılmış ImageRepositorytek ve test yöntemiyle, örneğin görüntüleri almak için, sınıf: findAllImagesWithoutThumbnail().

Ama şimdi de yapabilmem gerekiyor createThumbnail(). Bununla kim ilgilenmeli? ImageManagerUygulamaya özgü bir sınıf olacak bir sınıfa sahip olmayı düşünüyordum .

Veya Imageyeniden boyutlandırmaya izin vermek 0K olur mu? Ya izin ImageRepositoryve ImageManageraynı sınıf olmak?

Ne düşünüyorsun?


Image şu ana kadar ne yapıyor?
Winston Ewert

Görüntüleri nasıl yeniden boyutlandırmayı düşünüyorsunuz? Hiç resim küçülüyor musunuz yoksa tam boyuta geri dönmek istediğinizi hayal edebiliyor musunuz?
Robotu Gort

@WinstonEwert Biçimlendirilmiş çözünürlük (ör: '1440x900' dizesi), farklı URL'ler gibi veriler üretir ve 'görünümler' veya 'oylar' gibi bazı istatistikleri değiştirmenize olanak tanır.
ChocoDeveloper

@StevenBurnap Orijinal görüntüyü en önemli şey olarak görüyorum, sadece daha hızlı göz atmak için küçük resimler kullanıyorum veya kullanıcı masaüstüne veya başka bir şeye uyacak şekilde yeniden boyutlandırmak istiyorsa, bunlar ayrı bir şeydir.
ChocoDeveloper

Image sınıfını değiştirilemez hale getirdim, böylece etrafta geçerken defansif olarak kopyalamak zorunda kalmazsınız.
dan_waterworth

Yanıtlar:


8

Sorulan soru, Imagenesnelerin nasıl kullanılacağına bağlı olduğu için gerçek bir cevaba sahip olamayacak kadar belirsizdir .

Görüntüyü yalnızca bir boyutta kullanıyorsanız ve kaynak görüntü yanlış boyutta olduğu için yeniden boyutlandırıyorsanız, okuma kodunun yeniden boyutlandırmayı yapması en iyisi olabilir. createImageYöntemin bir genişlik / yükseklik almasını ve ardından yeniden boyutlandırılan görüntüyü bu genişlik / yükseklikte döndürmesini sağlayın .

Birden fazla boyuta ihtiyacınız varsa ve bellek bir endişe değilse, görüntüyü orijinal olarak okunduğu gibi tutmak ve görüntüleme zamanında yeniden boyutlandırma yapmak en iyisidir. Böyle bir durumda kullanabileceğiniz birkaç farklı tasarım vardır. Görüntü düzeltildi widthve heightdüzeltildi, ancak displaybir konum ve hedef yükseklik / genişlik alan bir yönteminiz olurdu veya kullandığınız görüntüleme sistemi ne olursa olsun bir nesneyi döndüren bir genişlik / yükseklik alan bir yönteminiz olurdu. Kullandığım çoğu çizim API'sı, çizim zamanında hedef boyutu belirlemenizi sağlar.

Gereksinimler, performansın endişe kaynağı olacak kadar farklı boyutlarda çizim yapmasına neden oluyorsa, orijinaline göre farklı boyutta yeni bir görüntü oluşturan bir yönteminiz olabilir. Bir alternatif, Imagesınıfınızın farklı gösterimleri dahili olarak önbelleğe almasını sağlamaktır, böylece displayküçük resim boyutu ile ilk aradığınızda yeniden boyutlandırma yapar, ikinci kez son kez kaydedilen önbelleğe alınmış kopyayı çizer. Bu daha fazla bellek kullanır, ancak birkaç yaygın yeniden boyutlandırmadan daha fazlasına sahip olmanız nadirdir.

Başka bir alternatif, Imagetemel görüntüyü tutan tek bir sınıfa sahip olmak ve bu sınıfın bir veya daha fazla gösterim içermesini sağlamaktır. Bir Imagekişinin yüksekliği / genişliği olmazdı. Bunun yerine, ImageRepresentationyüksekliği ve genişliği olan biriyle başlar . Bu temsili çizersiniz. Bir görüntüyü "yeniden boyutlandırmak" Imageiçin, belirli yükseklik / genişlik metriklerine sahip bir gösterim istersiniz . Bu, orijinalin yanı sıra bu yeni temsili de içermesine neden olur. Bu, ekstra karmaşıklık pahasına tam olarak bellekte asılı olan şey üzerinde çok fazla kontrol sağlar.

Şahsen kelimeyi içeren sınıflardan hoşlanmıyorum Managerçünkü "yönetici" tam olarak sınıfın tam olarak ne yaptığından bahsetmeyen çok belirsiz bir kelimedir. Nesne ömrünü yönetiyor mu? Uygulamanın geri kalanı ile yönettiği şey arasında mı duruyor?


"bu gerçekten Görüntü nesnelerinin nasıl kullanılacağına bağlıdır." Bu ifade gerçekten kapsülleme ve gevşek bağlantı kavramıyla uyumlu değildir (bu durumda prensibi biraz fazla ileriye götürüyor olabilir). Daha iyi bir farklılaşma noktası, Image'ın durumunun anlamlı şekilde boyut içerip içermediğidir.
Chris Bye

Bir Resim Düzenleme uygulaması bakış açısından bahsettiğiniz anlaşılıyor. OP bunu isteseydi ya da istemeseydi tamamen açık değil mi?
andho

6

Yalnızca birkaç gereksinim olduğu sürece işleri basit tutun ve gerektiğinde tasarımınızı geliştirin. Çoğu gerçek dünyada, böyle bir tasarımla başlamak için yanlış bir şey yok sanırım:

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

Daha fazla parametre geçirmeniz gerektiğinde işler farklı hale gelebilir createThumbnail()ve bu parametrelerin kendilerine ait bir ömürleri gerekir. Örneğin, tümü belirli bir hedef boyut, yeniden boyutlandırma algoritması veya kalitesi olan birkaç yüz görüntü için küçük resimler oluşturacağınızı varsayalım. Bu, createThumbnailbaşka bir sınıfa, örneğin bir yönetici sınıfına veya ImageResizerbu parametrelerin yapıcı tarafından iletildiği ve böylece ImageResizernesnenin ömrüne bağlı olduğu bir sınıfa taşınabileceğiniz anlamına gelir .

Aslında, ilk yaklaşımı ve yeniden düzenleyiciyi daha sonra gerçekten ihtiyacım olduğunda başlayacağım.


Peki, zaten ayrı bir bileşene çağrıyı devrediyorsam, neden ImageResizerilk etapta bir sınıfın sorumluluğunu yapmıyorsunuz ? Sorumluluğu yeni bir sınıfa taşımak yerine bir çağrıyı ne zaman devredersiniz?
Songo

2
@Songo: 2 olası neden: (1) - belki de zaten var olan - ayrı bileşene devretme kodu sadece basit bir astar değil, belki sadece 4 ila 6 komut dizisidir, bu yüzden saklamak için bir yere ihtiyacınız vardır . (2) Sözdizimsel şeker / kullanım kolaylığı: yazmak çok yakışıklı olacak Image thumbnail = img.createThumbnail(x,y).
Doc Brown

+1 aah görüyorum. Açıklama için teşekkürler :)
Songo

4

ImageDış sınıfta yeniden boyutlandırma yeniden boyutlandırıcının uygulanmasını bilmek Image, böylece kapsülleme ihlal gerektireceği için sınıfın bir parçası olması gerektiğini düşünüyorum . ImageBir temel sınıf olduğunu varsayıyorum ve somut görüntü türleri (PNG, JPEG, SVG, vb.) İçin ayrı alt sınıflarla dolacaksınız. Bu nedenle, karşılık gelen yeniden boyutlandırma sınıflarına veya switchuygulama sınıfına dayalı olarak yeniden boyutlandırılan bir ifadeye sahip genel bir yeniden boyutlandırmaya sahip olmanız gerekir - klasik bir tasarım kokusu.

Bir yaklaşım, kurucunuzun Imagegörüntüyü, yükseklik ve genişlik parametrelerini içeren bir kaynak almasını ve kendisini uygun şekilde oluşturmasını sağlamak olabilir. Daha sonra yeniden boyutlandırma, orijinal kaynağı (görüntünün içinde önbelleğe alınmış) ve yeni boyut parametrelerini kullanarak yeni bir nesne oluşturmak kadar basit olabilir. Örneğin foo.createThumbnail()basitçe return new Image(this.source, 250, 250). ( elbette Imagesomut türü olarak foo). Bu, görüntülerinizi değiştirilemez ve uygulamalarını gizli tutar.


Bu çözümü seviyorum, ama neden yeniden boyutlandırıcının dahili uygulamasını bilmek gerektiğini söylediğini anlamıyorum Image. Tek ihtiyacı olan kaynak ve hedef boyutlardır.
ChocoDeveloper

Dış bir sınıfa sahip olmanın beklenmedik olacağı için mantıklı olmadığını düşünmeme rağmen, ne kapsüllemeyi ihlal etmek ne de yeniden boyutlandırma yaparken bir switch () deyimi olmasını gerektirmez. Sonuçta bir görüntü bir şey 2d dizisidir ve arayüz tek tek pikselleri alıp ayarlamaya izin verdiği sürece görüntüleri kesinlikle yeniden boyutlandırabilirsiniz.
whatsisname

4

OOP'nin veri ve davranışı birlikte kapsüllemekle ilgili olduğunu biliyorum, ancak bir Görüntünün bu durumda yeniden boyutlandırma mantığının gömülü olması iyi bir fikir değil, çünkü bir Görüntünün kendisini nasıl yeniden boyutlandırması gerektiğini bilmesine gerek yok bir şekil.

Küçük resim aslında farklı bir Resimdir. Belki bir Fotoğraf ile Küçük Resmi (her ikisi de Görüntülerdir) arasındaki ilişkiyi tutan bir veri yapınız olabilir.

Programlarımı (Görüntüler, Fotoğraflar, Küçük Resimler vb.) Ve Hizmetler'e (PhotographRepository, ThumbnailGenerator vb.) Bölmeye çalışıyorum. Veri yapılarınızı doğru hale getirin ve ardından bu veri yapılarını oluşturmanıza, yönetmenize, dönüştürmenize, sürdürmenize ve kurtarmanıza olanak tanıyan hizmetleri tanımlayın. Veri yapılarıma, doğru bir şekilde oluşturulduklarından ve uygun şekilde kullanıldıklarından emin olmaktan başka bir davranış getirmem.

Bu nedenle, hayır, bir Görüntüde nasıl bir Küçük Resim oluşturma mantığı bulunmamalıdır. Şunun gibi bir yöntemi olan bir ThumbnailGenerator hizmeti olmalıdır:

Image GenerateThumbnailFrom(Image someImage);

Daha büyük veri yapım şöyle görünebilir:

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

    public Image Thumbnail { get; private set; }
}

Tabii ki bu, nesneyi oluştururken yapmak istemediğiniz anlamına geldiğiniz anlamına gelebilir, bu yüzden böyle bir şeyi de düşünürüm:

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;
        }
    }
}

... tembel değerlendirmeli bir veri yapısı istediğiniz durumda. (Üzgünüm, null kontrollerimi dahil etmedim ve iş parçacığı açısından güvenli hale getirmedim, bu da değişmez bir veri yapısını taklit etmeye çalışsaydınız isteyeceğiniz bir şey).

Gördüğünüz gibi, bu sınıflardan herhangi biri muhtemelen bağımlılık enjeksiyonu yoluyla elde edilen bir ThumbnailGenerator referansına sahip olan bir çeşit PhotographRepository tarafından inşa ediliyor.


Bana davranışları olmayan sınıflar yaratmamam gerektiği söylendi. 'Veri yapıları' derken, sınıflardan veya C ++ 'dan gelen bir şeyden söz edip etmediğinizden emin değilsiniz (bu dil mi?). Bildiğim ve kullandığım tek veri yapıları ilkellerdir. Hizmetler ve DI kısmı spot, ben bunu sonunda olabilir.
ChocoDeveloper

@ChocoDeveloper: zaman zaman davranışa sahip olmayan sınıflar duruma bağlı olarak yararlı veya gereklidir. Bunlara değer sınıfları denir . Normal OOP sınıfları, sabit kodlanmış davranışa sahip sınıflardır. Bileşilebilir OOP sınıfları da sabit kodlu davranışa sahiptir, ancak bunların bileşimlerinin yapısı bir yazılım uygulamasının ihtiyaç duyduğu birçok davranışa neden olabilir.
rwong

3

Uygulamak istediğiniz tek bir işlevsellik belirlediniz, neden şimdiye kadar tanımladığınız her şeyden ayrı olmamalısınız? Yani ne İlke tek sorumluluk öneririm çözümdür.

IImageResizerBir görüntüyü ve hedef boyutu iletmenize olanak tanıyan ve yeni bir görüntü döndüren bir arabirim oluşturun . Ardından bu arayüzün bir uygulamasını oluşturun. Aslında görüntüleri yeniden boyutlandırmanın tonlarca yolu vardır, böylece birden fazla bile olabilirsiniz!


İlgili SRP için +1, ancak gerçek yeniden boyutlandırmayı kullanmıyorum, bu zaten belirtildiği gibi bir üçüncü taraf kütüphanesine devredildi.
ChocoDeveloper

3

Görüntüleri yeniden boyutlandıran yöntem hakkında bu gerçekleri varsayalım:

  • Resmin yeni bir kopyasını döndürmelidir. Görüntünün kendisini değiştiremezsiniz, çünkü bu görüntüye referans olan diğer kodları kırar.
  • Image sınıfının dahili verilerine erişmesi gerekmez. Görüntü sınıflarının genellikle bu verilere (veya kopyasına) herkese açık erişim sunmaları gerekir.
  • Görüntü yeniden boyutlandırma karmaşıktır ve birçok farklı parametre gerektirir. Farklı yeniden boyutlandırma algoritmaları için genişletilebilirlik noktaları bile olabilir. Her şeyi geçmek büyük yöntem imzasıyla sonuçlanır.

Bu gerçeklere dayanarak, Image resizing yönteminin Image sınıfının bir parçası olması için bir neden olmadığını söyleyebilirim. Sınıfı statik yardımcı yöntem olarak uygulamak en iyisi olacaktır.


İyi varsayımlar. Neden statik bir yöntem olması gerektiğinden emin değilim, test edilebilirlik için onlardan kaçınmaya çalışıyorum.
ChocoDeveloper

2

Bir Görüntü İşleme sınıfı uygun olabilir (veya istediğiniz şekilde Image Manager). Örneğin, küçük resim almak için Görüntünüzü Görüntü İşlemcinin CreateThumbnail yöntemine aktarın.

Bu rotayı önermemin nedenlerinden biri, 3. taraf görüntü işleme kütüphanesi kullandığınızı söylemenizdir. Yeniden boyutlandırma işlevini Image sınıfının dışına çıkarmak, platforma özgü veya 3. taraf kodlarını izole etmenizi kolaylaştırabilir. Dolayısıyla, temel Image sınıfınızı tüm platformlarda / uygulamalarda kullanabiliyorsanız, platforma veya kütüphaneye özgü kodla kirletmeniz gerekmez. Bunların tümü Görüntü İşlemci'de bulunabilir.


İyi bir nokta. Buradaki çoğu insan, zaten bir üçüncü taraf kütüphanesine en karmaşık kısmı devrettiğimi anlamadı, belki de yeterince net değildim.
ChocoDeveloper

2

Temelde Doc Brown'un söylediği gibi:

İmage getAsThumbnail()sınıfı için bir yöntem oluşturun , ancak bu yöntem aslında işi bir ImageUtilssınıfa devretmelidir . Yani şöyle bir şey olurdu:

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

Ve

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

Bu, bakılması daha kolay bir kod sağlar. Aşağıdakileri karşılaştırın:

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

Veya

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

İkincisi sizin için iyi görünüyorsa, sadece bu yardımcı yöntemi bir yerde yaratmaya da devam edebilirsiniz.


1

Bence "Görüntü Alanı" nda, sadece değişmez ve monadik olan Image nesnesine sahipsiniz. Resmin yeniden boyutlandırılmış bir sürümünü istersiniz ve yeniden boyutlandırılmış bir sürümünü döndürür. Ardından orijinalden kurtulmak mı yoksa her ikisini de tutmak mı istediğinize karar verebilirsiniz.

Artık Görüntünün küçük resim, avatar vb. Sürümleri tamamen başka bir Alan adıdır ve bu da Görüntü alanından belirli bir görüntünün farklı sürümlerini bir kullanıcıya vermesini isteyebilir. Genellikle bu etki alanı da çok büyük veya genel değildir, bu nedenle bunu uygulama mantığında tutabilirsiniz.

Küçük ölçekli bir uygulamada, görüntüleri okuma zamanında yeniden boyutlandıracağım. Örneğin, 'http://my.site.com/images/thumbnails/image1.png' resmi, dosyanın image1.png adı kullanılarak alınacağı bir php komut dosyasını temsil eden bir apache yeniden yazma kuralına sahip olabilirim. boyutlandırıldı ve 'küçük resimler / image1.png' içinde saklandı. Sonra bu aynı görüntüye bir sonraki istek üzerine, apache php betiğini çalıştırmadan görüntüyü doğrudan sunacaktır. FindAllImagesWithoutThumbnails ile ilgili sorunuz, istatistik yapmanız gerekmediği sürece otomatik olarak apache tarafından cevaplandırılıyor mu?

Büyük ölçekli bir uygulamada, tüm yeni görüntüleri arka plan işine gönderirdim, bu da görüntünün farklı sürümlerini oluşturmaya özen gösterir ve uygun yerlere kaydeder. Bu alanın spagetti ve kötü sosun korkunç bir karmaşasına dönüşmesi pek olası olmadığından, bir alan veya sınıf yaratma zahmetine girmezdim.


0

Kısa cevap:

Tavsiyem, bu yöntemi image class ekliyor:

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

Image nesnesi hala değiştirilemez, bu yöntemler yeni bir görüntü döndürür.


0

Zaten birkaç harika cevap var, bu yüzden nesneleri ve sorumluluklarını tanımlama yolunun arkasındaki bir sezgisel tarama hakkında biraz ayrıntıya gireceğim.

OOP gerçek yaşamdan farklıdır, çünkü gerçek yaşamdaki nesneler genellikle pasiftir ve OOP'de aktiftirler. Ve bu, nesne düşüncesinin çekirdeğidir . Örneğin, gerçek hayatta bir görüntüyü kim yeniden boyutlandırabilir? Bu açıdan akıllı olan bir insan. Ama OOP'de insan yok, bu yüzden nesneler akıllı. OOP'da bu "insan merkezli" yaklaşımın uygulanmasının bir yolu, hizmet sınıflarını, örneğin kötü şöhretli Managersınıfları kullanmaktır. Böylece nesneler pasif veri yapıları olarak ele alınır. OOP yolu değil.

Yani iki seçenek var. İlki, bir yöntem yaratan Image::createThumbnail(), zaten dikkate alınmıştır. İkincisi bir ResizedImagesınıf yaratmaktır . Bir kaynağın olması gerekeceğinden , bazı kapsülleme sorunlarına neden olsa da, ( Imagebir Imagearayüzün korunup korunmayacağı alanınıza bağlıdır ) . Ancak, ayrıntıların yeniden boyutlandırılması, bir SRP'ye göre hareket ederek ayrı bir etki alanı nesnesine bırakılması bunalmaz.ResizedImageImageImage

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.