Haruskah gambar dapat mengubah ukurannya sendiri di OOP?

9

Saya sedang menulis aplikasi yang akan memiliki Imageentitas, dan saya sudah mengalami kesulitan dalam menentukan tanggung jawab masing-masing tugas.

Pertama saya punya Imagekelas. Ini memiliki jalur, lebar, dan atribut lainnya.

Lalu saya membuat sebuah ImageRepositorykelas, untuk mengambil gambar dengan metode tunggal dan diuji, misalnya: findAllImagesWithoutThumbnail().

Tapi sekarang aku juga harus bisa createThumbnail(). Siapa yang harus menghadapinya? Saya berpikir tentang memiliki ImageManagerkelas, yang akan menjadi kelas khusus aplikasi (juga akan ada manipulasi gambar pihak ketiga komponen pilihan yang dapat digunakan kembali, saya tidak menciptakan kembali roda).

Atau mungkin 0K untuk membiarkan Imageukurannya sendiri? Atau biarkan ImageRepositorydan ImageManagermenjadi kelas yang sama?

Bagaimana menurut anda?

ChocoDeveloper
sumber
Apa yang dilakukan Image sejauh ini?
Winston Ewert
Bagaimana Anda berharap untuk mengubah ukuran gambar? Apakah Anda hanya mengecilkan gambar atau dapatkah Anda membayangkan ingin kembali ke ukuran penuh?
Gort the Robot
@ WinstonEwert Ini menghasilkan data seperti resolusi yang diformat (misalnya: string '1440x900'), URL yang berbeda, dan memungkinkan Anda memodifikasi beberapa statistik seperti 'tampilan' atau 'suara'.
ChocoDeveloper
@StevenBurnap Saya menganggap gambar asli sebagai hal yang paling penting, saya hanya menggunakan thumbnail untuk menjelajah lebih cepat atau jika pengguna ingin mengubah ukuran agar sesuai dengan desktop atau sesuatu, jadi itu adalah hal yang terpisah.
ChocoDeveloper
Saya akan membuat kelas Image tidak berubah sehingga Anda tidak perlu menyalinnya secara defensif ketika Anda menyebarkannya.
dan_waterworth

Jawaban:

8

Pertanyaan yang diajukan terlalu samar untuk memiliki jawaban nyata karena sangat tergantung pada bagaimana Imagebenda akan digunakan.

Jika Anda hanya menggunakan gambar pada satu ukuran, dan mengubah ukuran karena gambar sumber adalah ukuran yang salah, mungkin yang terbaik adalah meminta kode baca melakukan pengubahan ukuran. Mintalah createImagemetode Anda mengambil lebar / tinggi dan kemudian mengembalikan gambar yang telah diubah ukurannya pada lebar / tinggi itu.

Jika Anda membutuhkan beberapa ukuran, dan jika memori tidak menjadi masalah, yang terbaik adalah menyimpan gambar dalam memori seperti yang dibaca semula dan melakukan pengubahan ukuran pada waktu tampilan. Dalam kasus seperti itu, ada beberapa desain berbeda yang mungkin Anda gunakan. Gambar widthdan heightakan diperbaiki, tetapi Anda akan memiliki displaymetode yang mengambil posisi dan tinggi target / lebar atau Anda akan memiliki beberapa metode mengambil lebar / tinggi yang mengembalikan objek yang apa pun sistem tampilan yang Anda gunakan. Sebagian besar API gambar yang saya gunakan memungkinkan Anda menentukan ukuran target pada waktu menggambar.

Jika persyaratan yang menyebabkan gambar pada ukuran yang berbeda cukup sering untuk kinerja menjadi perhatian, Anda dapat memiliki metode yang membuat gambar baru dengan ukuran berbeda berdasarkan aslinya. Alternatif lain adalah membuat Imagerepresentasi cache kelas Anda berbeda secara internal sehingga pertama kali Anda menelepon displaydengan ukuran thumbnail itu mengubah ukuran sementara yang kedua hanya menggambar salinan cache yang disimpan dari waktu lalu. Ini menggunakan lebih banyak memori, tetapi jarang Anda memiliki lebih dari beberapa resizings umum.

Alternatif lain adalah memiliki satu Imagekelas yang mempertahankan gambar dasar dan memiliki kelas yang berisi satu atau lebih representasi. An Imageitu sendiri tidak akan memiliki tinggi / lebar. Sebaliknya, itu akan mulai dengan ImageRepresentationyang memiliki tinggi dan lebar. Anda akan menggambar representasi ini. Untuk "mengubah ukuran" gambar, Anda akan meminta Imagerepresentasi dengan metrik tinggi / lebar tertentu. Ini akan menyebabkannya memuat representasi baru ini serta yang asli. Ini memberi Anda banyak kontrol atas apa yang berkeliaran di memori dengan biaya kompleksitas ekstra.

Saya pribadi tidak suka kelas yang mengandung kata Managerkarena "manajer" adalah kata yang sangat samar yang tidak benar-benar memberi tahu Anda banyak tentang apa yang dilakukan kelas. Apakah itu mengatur objek seumur hidup? Apakah itu berdiri di antara sisa aplikasi dan hal yang dikelola?

Gort the Robot
sumber
"Itu benar-benar tergantung pada bagaimana objek Gambar akan digunakan." Pernyataan ini tidak benar-benar sejalan dengan gagasan enkapsulasi dan kopling longgar (meskipun mungkin mengambil prinsip sedikit terlalu jauh dalam kasus ini). Titik diferensiasi yang lebih baik adalah apakah dan negara Gambar bermakna termasuk ukuran atau tidak.
Chris Bye
Sepertinya Anda berbicara tentang sudut pandang aplikasi Pengeditan Gambar. Tidak sepenuhnya jelas apakah OP menginginkan ini atau tidak ya?
andho
6

Buat segala sesuatunya sederhana selama hanya ada beberapa persyaratan, dan tingkatkan desain Anda ketika Anda harus. Saya kira dalam kebanyakan kasus dunia nyata tidak ada yang salah untuk memulai dengan desain seperti itu:

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

Hal-hal mungkin menjadi berbeda ketika Anda perlu memberikan lebih banyak parameter createThumbnail(), dan parameter-parameter itu membutuhkan masa pakai sendiri. Misalnya, mari kita asumsikan Anda akan membuat thumbnail untuk beberapa ratus gambar, semua dengan ukuran tujuan tertentu, algoritma ukuran atau kualitas. Itu berarti bahwa Anda dapat memindahkan createThumbnailkelas lain, misalnya, kelas manajer, atau ImageResizerkelas, di mana parameter tersebut dilewatkan oleh konstruktor dan terikat dengan masa pakai ImageResizerobjek.

Sebenarnya, saya akan mulai dengan pendekatan pertama dan refactor nanti ketika saya benar-benar membutuhkannya.

Doc Brown
sumber
baik, jika saya mendelegasikan panggilan ke komponen yang terpisah, mengapa tidak membuatnya menjadi tanggung jawab ImageResizerkelas? Kapan Anda mendelegasikan panggilan alih-alih memindahkan tanggung jawab ke kelas baru?
Songo
2
@Songo: 2 kemungkinan alasan: (1) kode untuk mendelegasikan ke - komponen yang mungkin sudah ada - komponen yang terpisah bukan hanya satu baris, mungkin hanya urutan 4 hingga 6 perintah, sehingga Anda memerlukan tempat untuk menyimpannya . (2) Gula sintaksis / kemudahan penggunaan: itu akan sangat tampan untuk ditulis Image thumbnail = img.createThumbnail(x,y).
Doc Brown
+1 aah, begitu. Terima kasih atas penjelasannya :)
Songo
4

Saya pikir itu harus menjadi bagian dari Imagekelas, karena mengubah ukuran di kelas eksternal akan membutuhkan pengubah untuk mengetahui implementasi Image, sehingga melanggar enkapsulasi. Saya berasumsi Imageadalah kelas dasar, dan Anda akan berakhir dengan subclass terpisah untuk tipe gambar konkret (PNG, JPEG, SVG, dll). Oleh karena itu, Anda harus memiliki kelas ukuran yang sesuai, atau resizer generik dengan switchpernyataan yang mengubah ukuran berdasarkan kelas implementasi - bau desain klasik.

Salah satu pendekatan mungkin membuat Imagekonstruktor Anda mengambil sumber daya yang berisi gambar, dan parameter tinggi dan lebar dan membuatnya sendiri dengan tepat. Kemudian mengubah ukuran bisa sesederhana membuat objek baru menggunakan sumber asli (di-cache di dalam gambar) dan parameter ukuran baru. Misalnya foo.createThumbnail()akan sederhana return new Image(this.source, 250, 250). (dengan Imagemenjadi tipe konkret foo, tentu saja). Ini membuat gambar Anda tidak berubah dan implementasinya pribadi.

TMN
sumber
Saya suka solusi ini, tetapi saya tidak mengerti mengapa Anda mengatakan bahwa resizer perlu mengetahui implementasi internal Image. Yang dibutuhkan hanyalah sumber dan dimensi target.
ChocoDeveloper
Meskipun saya pikir memiliki kelas eksternal tidak masuk akal karena itu akan menjadi tidak terduga, tidak ada yang harus melanggar enkapsulasi juga tidak perlu memiliki pernyataan switch () ketika melakukan pengubahan ukuran. Pada akhirnya gambar adalah susunan 2d dari sesuatu, dan Anda pasti dapat mengubah ukuran gambar selama antarmuka memungkinkan untuk mendapatkan dan mengatur masing-masing piksel.
whatsisname
4

Saya tahu bahwa OOP adalah tentang merangkum data dan perilaku bersama-sama, tetapi saya tidak berpikir itu ide yang baik untuk Gambar untuk memiliki ukuran logika yang tertanam dalam kasus ini, karena Gambar tidak perlu tahu bagaimana mengubah ukuran dirinya menjadi sebuah Gambar.

Thumbnail sebenarnya adalah Gambar yang berbeda. Mungkin Anda memiliki struktur data yang menahan hubungan antara sebuah Foto dan Gambar Kecil (keduanya adalah Gambar).

Saya mencoba membagi program saya menjadi beberapa hal (seperti Gambar, Foto, Gambar Kecil, dll.) Dan Layanan (seperti PhotographRepository, ThumbnailGenerator, dll.). Dapatkan struktur data Anda dengan benar, dan kemudian tentukan layanan yang memungkinkan Anda membuat, memanipulasi, mengubah, bertahan, dan memulihkan struktur data tersebut. Saya tidak menaruh perilaku lagi dalam struktur data saya selain memastikan mereka dibuat dengan benar dan digunakan dengan tepat.

Karena itu, tidak, sebuah Gambar tidak boleh berisi logika tentang cara membuat Gambar Kecil. Harus ada layanan ThumbnailGenerator yang memiliki metode seperti:

Image GenerateThumbnailFrom(Image someImage);

Struktur data saya yang lebih besar mungkin terlihat seperti ini:

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

    public Image Thumbnail { get; private set; }
}

Tentu saja itu mungkin berarti Anda melakukan upaya yang tidak ingin Anda lakukan saat membangun objek, jadi saya akan mempertimbangkan sesuatu seperti ini juga OK:

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

... dalam kasus di mana Anda menginginkan struktur data dengan evaluasi malas. (Maaf saya tidak menyertakan cek nol saya dan saya tidak membuatnya aman, yang merupakan sesuatu yang Anda inginkan jika Anda mencoba meniru struktur data yang tidak dapat diubah).

Seperti yang Anda lihat, salah satu dari kelas-kelas ini sedang dibangun oleh semacam PhotographRepository, yang mungkin memiliki referensi ke ThumbnailGenerator yang didapat melalui injeksi dependensi.

Scott Whitlock
sumber
Saya diberi tahu bahwa saya seharusnya tidak membuat kelas tanpa perilaku. Tidak yakin jika ketika Anda mengatakan 'struktur data' Anda merujuk ke kelas atau sesuatu dari C ++ (apakah ini bahasa ini?). Satu-satunya struktur data yang saya tahu dan gunakan adalah primitif. Layanan dan bagian DI tepat, saya mungkin akhirnya melakukan ini.
ChocoDeveloper
@ChocoDeveloper: sesekali kelas tanpa perilaku berguna atau perlu, tergantung pada situasinya. Itu disebut kelas nilai . Kelas OOP normal adalah kelas dengan perilaku kode keras. Kelas OOP yang dapat dikomposisikan juga memiliki perilaku hard-coded, tetapi struktur komposisinya dapat menimbulkan banyak perilaku yang dibutuhkan oleh aplikasi perangkat lunak.
rwong
3

Anda telah mengidentifikasi satu fungsi yang ingin Anda terapkan, jadi mengapa tidak terpisah dari semua yang telah Anda identifikasi sejauh ini? Itulah yang disarankan oleh Prinsip Tanggung Jawab Tunggal sebagai solusinya.

Buat IImageResizerantarmuka yang memungkinkan Anda mengirimkan gambar dan ukuran target, dan yang mengembalikan gambar baru. Kemudian buat implementasi dari antarmuka itu. Sebenarnya ada banyak cara untuk mengubah ukuran gambar, sehingga Anda bahkan bisa menghasilkan lebih dari satu!

Chris Pitman
sumber
+1 untuk SRP yang relevan, tapi saya tidak menerapkan pengubahan ukuran yang sebenarnya, yang sudah didelegasikan ke perpustakaan pihak ketiga seperti yang dinyatakan.
ChocoDeveloper
3

Saya berasumsi fakta-fakta tentang metode, yang mengubah ukuran gambar:

  • Itu harus mengembalikan salinan gambar baru. Anda tidak dapat memodifikasi gambar itu sendiri, karena itu akan merusak kode lain yang memiliki referensi ke gambar ini.
  • Tidak perlu akses ke data internal kelas Image. Kelas gambar biasanya perlu menawarkan akses publik ke data tersebut (atau salinan).
  • Pengubahan ukuran gambar rumit dan membutuhkan banyak parameter berbeda. Mungkin bahkan poin ekstensibilitas untuk berbagai algoritma pengubahan ukuran. Melewati semuanya akan menghasilkan tanda tangan metode besar.

Berdasarkan fakta-fakta itu, saya akan mengatakan bahwa tidak ada alasan untuk metode mengubah ukuran gambar menjadi bagian dari kelas Image itu sendiri. Menerapkannya sebagai metode pembantu statis kelas akan menjadi yang terbaik.

Euforia
sumber
Asumsi bagus. Tidak yakin mengapa itu harus menjadi metode statis, saya mencoba menghindarinya untuk diuji.
ChocoDeveloper
2

Kelas Pemrosesan Gambar mungkin sesuai (atau Pengelola Gambar, seperti Anda menyebutnya). Lewati gambar Anda ke metode CreateThumbnail dari Prosesor Gambar, misalnya, untuk mengambil gambar thumbnail.

Salah satu alasan saya menyarankan rute ini adalah karena Anda mengatakan Anda menggunakan pustaka pemrosesan gambar pihak ketiga. Mengambil fungsi pengubahan ukuran dari kelas Image itu sendiri mungkin membuatnya lebih mudah bagi Anda untuk mengisolasi setiap platform spesifik atau kode pihak ke-3. Jadi, jika Anda dapat menggunakan kelas Gambar dasar di semua platform / aplikasi, maka Anda tidak perlu mencemarinya dengan platform atau kode khusus perpustakaan. Itu semua dapat ditemukan di Image Processor.

GrandmasterB
sumber
Poin yang bagus. Kebanyakan orang di sini tidak mengerti saya sudah mendelegasikan bagian yang paling rumit ke perpustakaan pihak ke-3, mungkin saya tidak cukup jelas.
ChocoDeveloper
2

Pada dasarnya seperti yang dikatakan Doc Brown:

Buat getAsThumbnail()metode untuk kelas gambar, tetapi metode ini seharusnya hanya mendelegasikan pekerjaan ke beberapa ImageUtilskelas. Jadi akan terlihat seperti ini:

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

Dan

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

Ini akan memungkinkan kode yang lebih mudah dilihat. Bandingkan yang berikut ini:

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

Atau

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

Jika yang terakhir tampaknya baik untuk Anda, Anda juga dapat tetap membuat metode pembantu ini di suatu tempat.

Deiwin
sumber
1

Saya pikir dalam "Domain Gambar", Anda hanya memiliki objek Gambar yang tidak berubah dan monadik. Anda meminta gambar untuk versi yang diubah ukurannya dan mengembalikan versi ukurannya sendiri. Kemudian Anda dapat memutuskan apakah Anda ingin menyingkirkan yang asli atau menyimpan keduanya.

Sekarang versi thumbnail, avatar, dll dari Gambar adalah sepenuhnya Domain lain, yang dapat meminta domain Gambar untuk versi berbeda dari gambar tertentu untuk diberikan kepada pengguna. Biasanya domain ini juga tidak terlalu besar atau generik, jadi Anda mungkin dapat menyimpannya dalam logika aplikasi.

Dalam aplikasi skala kecil, saya akan mengubah ukuran gambar pada waktu baca. Misalnya saya bisa memiliki aturan apache penulisan ulang yang mendelegasikan ke php script jika gambar 'http://my.site.com/images/thumbnails/image1.png', di mana file akan diambil menggunakan nama image1.png dan diubah ukurannya dan disimpan dalam 'thumbnails / image1.png'. Kemudian pada permintaan berikutnya untuk gambar yang sama ini, apache akan melayani gambar secara langsung tanpa menjalankan skrip php. Pertanyaan Anda tentang findAllImagesWithoutThumbnails secara otomatis dijawab oleh apache, kecuali Anda perlu melakukan statistik?

Dalam aplikasi berskala besar, saya akan mengirim semua gambar baru ke pekerjaan latar belakang, yang menangani pembuatan berbagai versi gambar dan menyimpannya di tempat yang tepat. Saya tidak akan repot-repot membuat seluruh domain atau kelas karena domain ini sangat tidak mungkin untuk tumbuh menjadi berantakan spageti dan saus yang buruk.

andho
sumber
0

Jawaban singkat:

Rekomendasi saya adalah menambahkan metode ini ke kelas gambar:

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

Objek Gambar masih tidak dapat diubah, metode ini mengembalikan gambar baru.

Tulains Córdova
sumber
0

Ada beberapa jawaban bagus sudah, jadi saya akan menguraikan sedikit tentang heuristik di balik cara mengidentifikasi objek dan tanggung jawab mereka.

OOP berbeda dari kehidupan nyata karena benda-benda dalam kehidupan nyata seringkali pasif, dan dalam OOP mereka aktif. Dan ini adalah inti dari pemikiran objek . Misalnya, siapa yang akan mengubah ukuran gambar di kehidupan nyata? Seorang manusia, yang cerdas dalam hal itu. Tapi di OOP tidak ada manusia, jadi objeknya pintar. Cara untuk menerapkan pendekatan "human-centric" ini dalam OOP adalah memanfaatkan kelas layanan, misalnya, Managerkelas-kelas terkenal . Dengan demikian objek diperlakukan sebagai potongan data pasif. Ini bukan cara OOP.

Jadi ada dua opsi. Yang pertama, membuat metode Image::createThumbnail(), sudah dipertimbangkan. Yang kedua adalah membuat ResizedImagekelas. Ini bisa menjadi dekorator dari Image(tergantung pada domain Anda apakah akan mempertahankan Imageantarmuka atau tidak), meskipun itu menghasilkan beberapa masalah enkapsulasi, karena ResizedImageharus memiliki Imagesumber. Tetapi Imagetidak akan kewalahan dengan mengubah ukuran detail, meninggalkannya ke objek domain terpisah, bertindak sesuai dengan SRP.

Vadim Samokhin
sumber