Một hình ảnh có thể tự thay đổi kích thước trong OOP không?


9

Tôi đang viết một ứng dụng sẽ có một Imagethực thể và tôi đã gặp khó khăn khi quyết định trách nhiệm của mỗi nhiệm vụ.

Đầu tiên tôi có Imagelớp. Nó có một đường dẫn, chiều rộng và các thuộc tính khác.

Sau đó, tôi đã tạo một ImageRepositorylớp, để truy xuất hình ảnh với một phương thức duy nhất và được thử nghiệm, ví dụ : findAllImagesWithoutThumbnail().

Nhưng bây giờ tôi cũng cần có thể createThumbnail(). Ai nên đối phó với điều đó? Tôi đã suy nghĩ về việc có một ImageManagerlớp, đó sẽ là một lớp dành riêng cho ứng dụng (cũng sẽ có một thành phần có thể tái sử dụng hình ảnh của bên thứ ba mà tôi lựa chọn, tôi không phát minh lại bánh xe).

Hoặc có thể là 0K để cho phép Imagethay đổi kích thước chính nó? Hoặc để cho ImageRepositoryImageManagerlà cùng một lớp?

Bạn nghĩ sao?


Hình ảnh làm gì cho đến nay?
Winston Ewert

Làm thế nào để bạn mong muốn thay đổi kích thước hình ảnh? Bạn chỉ bao giờ thu nhỏ hình ảnh hoặc bạn có thể tưởng tượng muốn quay trở lại kích thước đầy đủ?
Gort Robot

@WinstonEwert Nó tạo ra dữ liệu như độ phân giải được định dạng (ví dụ: chuỗi '1440x900'), các URL khác nhau và cho phép bạn sửa đổi một số thống kê như 'lượt xem' hoặc 'phiếu bầu'.
ChocoDeveloper

@StevenBurnap Tôi coi hình ảnh gốc là điều quan trọng nhất, tôi chỉ sử dụng hình thu nhỏ để duyệt nhanh hơn hoặc nếu người dùng muốn thay đổi kích thước để phù hợp với máy tính để bàn của mình hoặc một cái gì đó, vì vậy chúng là một thứ riêng biệt.
ChocoDeveloper

Tôi sẽ làm cho lớp Image không thay đổi để bạn không phải sao chép nó một cách phòng thủ khi bạn vượt qua nó.
dan_waterworth

Câu trả lời:


8

Câu hỏi khi được hỏi quá mơ hồ để có câu trả lời thực sự vì nó thực sự phụ thuộc vào cách các Imageđối tượng sẽ được sử dụng.

Nếu bạn chỉ sử dụng hình ảnh ở một kích thước và đang thay đổi kích thước vì hình ảnh nguồn có kích thước sai, tốt nhất nên để mã đọc thực hiện thay đổi kích thước. Yêu cầu createImagephương pháp của bạn lấy chiều rộng / chiều cao và sau đó trả lại hình ảnh đã được thay đổi kích thước ở chiều rộng / chiều cao đó.

Nếu bạn cần nhiều kích cỡ và nếu bộ nhớ không phải là vấn đề đáng lo ngại, tốt nhất bạn nên giữ hình ảnh trong bộ nhớ như đã đọc ban đầu và thực hiện bất kỳ thay đổi kích thước nào tại thời điểm hiển thị. Trong trường hợp như vậy, có một vài thiết kế khác nhau mà bạn có thể sử dụng. Hình ảnh widthheightsẽ được sửa, nhưng bạn sẽ có một displayphương thức lấy vị trí và chiều cao / chiều rộng mục tiêu hoặc bạn có một số phương pháp lấy chiều rộng / chiều cao trả về một đối tượng mà bất kỳ hệ thống hiển thị nào bạn đang sử dụng. Hầu hết các API vẽ tôi đã sử dụng cho phép bạn chỉ định kích thước mục tiêu tại thời điểm vẽ.

Nếu các yêu cầu gây ra cho việc vẽ ở các kích thước khác nhau thường đủ để hiệu suất trở thành mối quan tâm, bạn có thể có một phương pháp tạo ra một hình ảnh mới có kích thước khác dựa trên bản gốc. Một cách khác là để các Imagelớp biểu diễn bộ đệm trong lớp của bạn khác nhau bên trong để lần đầu tiên bạn gọi displayvới kích thước hình thu nhỏ, nó sẽ thay đổi kích thước trong khi lần thứ hai nó chỉ vẽ bản sao được lưu trong bộ nhớ cache mà nó đã lưu từ lần trước. Điều này sử dụng nhiều bộ nhớ hơn, nhưng hiếm khi bạn có nhiều hơn một vài cách giải quyết phổ biến.

Một cách khác là có một Imagelớp duy nhất giữ lại hình ảnh cơ sở và có lớp đó chứa một hoặc nhiều biểu diễn. Bản Imagethân nó sẽ không có chiều cao / chiều rộng. Thay vào đó, nó sẽ bắt đầu với một ImageRepresentationcái có chiều cao và chiều rộng. Bạn sẽ vẽ đại diện này. Để "thay đổi kích thước" một hình ảnh, bạn sẽ yêu cầu Imageđại diện với các số liệu chiều cao / chiều rộng nhất định. Điều này sẽ khiến nó sau đó chứa đại diện mới này cũng như bản gốc. Điều này cung cấp cho bạn rất nhiều quyền kiểm soát chính xác những gì đang tồn tại trong bộ nhớ với chi phí cực kỳ phức tạp.

Cá nhân tôi không thích các lớp có chứa từ này Managervì "người quản lý" là một từ rất mơ hồ không thực sự cho bạn biết nhiều về chính xác những gì lớp học làm. Nó quản lý suốt đời đối tượng? Liệu nó có đứng giữa phần còn lại của ứng dụng và thứ mà nó quản lý không?


"nó thực sự phụ thuộc vào cách các đối tượng Image sẽ được sử dụng." Tuyên bố này không thực sự phù hợp với khái niệm đóng gói và khớp nối lỏng lẻo (mặc dù nó có thể đưa nguyên tắc đi quá xa trong trường hợp này). Điểm khác biệt tốt hơn sẽ là liệu trạng thái của Image có bao gồm kích thước hay không.
Chris Bye

Có vẻ như bạn đang nói về quan điểm của ứng dụng Chỉnh sửa hình ảnh. Không hoàn toàn rõ ràng nếu OP muốn điều này hay không?
andho

6

Giữ mọi thứ đơn giản miễn là chỉ có một vài yêu cầu và cải thiện thiết kế của bạn khi bạn phải làm. Tôi đoán trong hầu hết các trường hợp thực tế, không có gì sai khi bắt đầu với một thiết kế như thế:

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

Mọi thứ có thể trở nên khác biệt khi bạn cần truyền nhiều tham số hơn createThumbnail()và các tham số đó cần thời gian tồn tại của riêng chúng. Ví dụ: giả sử bạn sẽ tạo hình thu nhỏ cho hàng trăm hình ảnh, tất cả với một số kích thước đích đã cho, thuật toán thay đổi kích thước hoặc chất lượng. Điều đó có nghĩa là bạn có thể di chuyển createThumbnailsang một lớp khác, ví dụ, lớp trình quản lý hoặc một ImageResizerlớp, trong đó các tham số đó được truyền vào bởi hàm tạo và do đó bị ràng buộc với vòng đời của ImageResizerđối tượng.

Trên thực tế, tôi sẽ bắt đầu với cách tiếp cận đầu tiên và tái cấu trúc sau này khi tôi thực sự cần nó.


tốt, nếu tôi đang ủy thác cuộc gọi cho một thành phần riêng biệt, tại sao không biến nó thành trách nhiệm của một ImageResizerlớp ngay từ đầu? Khi nào bạn ủy thác một cuộc gọi thay vì chuyển trách nhiệm sang một lớp mới?
Songo

2
@Songo: 2 lý do có thể: (1) mã để ủy quyền cho - có lẽ đã tồn tại - thành phần riêng biệt không chỉ là một lớp lót đơn giản, có lẽ chỉ là một chuỗi gồm 4 đến 6 lệnh, do đó bạn cần một nơi để lưu trữ nó . (2) Đường không thực tế / dễ sử dụng: nó sẽ rất đẹp trai để viết Image thumbnail = img.createThumbnail(x,y).
Doc Brown

+1 tôi thấy. Cảm ơn đã giải thích :)
Songo

4

Tôi nghĩ rằng nó sẽ phải là một phần của Imagelớp, vì thay đổi kích thước trong một lớp bên ngoài sẽ yêu cầu trình thay đổi để biết việc thực hiện Image, do đó vi phạm đóng gói. Tôi giả sử Imagelà một lớp cơ sở và bạn sẽ kết thúc với các lớp con riêng biệt cho các loại hình ảnh cụ thể (PNG, JPEG, SVG, v.v.). Do đó, bạn sẽ phải có các lớp thay đổi kích thước tương ứng hoặc một bộ thay đổi chung với switchcâu lệnh thay đổi kích thước dựa trên lớp thực hiện - một mùi thiết kế cổ điển.

Một cách tiếp cận có thể là để nhà Imagexây dựng của bạn lấy một tài nguyên chứa hình ảnh, các tham số chiều cao và chiều rộng và tự tạo một cách thích hợp. Sau đó, thay đổi kích thước có thể đơn giản như tạo một đối tượng mới bằng cách sử dụng tài nguyên ban đầu (được lưu trong bộ nhớ cache) và các tham số kích thước mới. Ví dụ foo.createThumbnail()đơn giản return new Image(this.source, 250, 250). (với loại Imagelà cụ thể của foo, tất nhiên). Điều này giữ cho hình ảnh của bạn bất biến và việc thực hiện chúng riêng tư.


Tôi thích giải pháp này, nhưng tôi không hiểu lý do tại sao bạn nói rằng trình chỉnh sửa lại cần biết triển khai bên trong Image. Tất cả những gì nó cần là nguồn và kích thước đích.
ChocoDeveloper

Mặc dù tôi nghĩ rằng có một lớp bên ngoài không có ý nghĩa gì vì nó sẽ bất ngờ, nhưng nó sẽ không phải vi phạm đóng gói cũng như không cần phải có câu lệnh switch () khi thực hiện thay đổi kích thước. Cuối cùng, một hình ảnh là một mảng 2d của một cái gì đó và bạn chắc chắn có thể thay đổi kích thước hình ảnh miễn là giao diện cho phép nhận và thiết lập các pixel riêng lẻ.
whatsisname

4

Tôi biết rằng OOP là về việc đóng gói dữ liệu và hành vi cùng nhau, nhưng tôi không nghĩ rằng một hình ảnh nên có logic thay đổi kích thước được nhúng trong trường hợp này, bởi vì một hình ảnh không cần biết cách thay đổi kích thước để trở thành một tấm ảnh.

Một hình thu nhỏ thực sự là một hình ảnh khác nhau. Có lẽ bạn có thể có một cơ sở hạ tầng giữ mối quan hệ giữa một Bức ảnh và đó là Hình thu nhỏ (cả hai đều là Hình ảnh).

Tôi cố gắng phân chia các chương trình của mình thành nhiều thứ (như Hình ảnh, Hình ảnh, Hình thu nhỏ, v.v.) và Dịch vụ (như Chụp ảnh lưu trữ, ThumbnailGenerator, v.v.). Nhận cấu trúc dữ liệu của bạn đúng và sau đó xác định các dịch vụ cho phép bạn tạo, thao tác, chuyển đổi, duy trì và khôi phục các cấu trúc dữ liệu đó. Tôi không đặt thêm bất kỳ hành vi nào vào cấu trúc dữ liệu của mình ngoài việc đảm bảo chúng được tạo đúng và được sử dụng phù hợp.

Do đó, không, một Hình ảnh không nên chứa logic về cách tạo Hình thu nhỏ. Cần có một dịch vụ ThumbnailGenerator có phương thức như:

Image GenerateThumbnailFrom(Image someImage);

Cấu trúc dữ liệu lớn hơn của tôi có thể trông như thế này:

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

    public Image Thumbnail { get; private set; }
}

Tất nhiên điều đó có thể có nghĩa là bạn đang nỗ lực mà bạn không muốn làm trong khi xây dựng đối tượng, vì vậy tôi cũng sẽ xem xét một cái gì đó như thế này 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;
        }
    }
}

... trong trường hợp bạn muốn cấu trúc dữ liệu với đánh giá lười biếng. (Xin lỗi tôi đã không bao gồm các kiểm tra null của mình và tôi đã không làm cho nó an toàn theo luồng, đó là điều bạn muốn nếu bạn đang cố gắng bắt chước một cấu trúc dữ liệu bất biến).

Như bạn có thể thấy, một trong hai lớp này đang được xây dựng bởi một loại Chụp ảnh nào đó, có thể có tham chiếu đến ThumbnailGenerator mà nó có được thông qua việc tiêm phụ thuộc.


Tôi đã nói rằng tôi không nên tạo các lớp không có hành vi. Không chắc chắn nếu bạn nói 'cấu trúc dữ liệu', bạn đang đề cập đến các lớp hoặc một cái gì đó từ C ++ (đó có phải là ngôn ngữ này không?). Các cấu trúc dữ liệu duy nhất tôi biết và sử dụng là nguyên thủy. Các dịch vụ và phần DI là tại chỗ, tôi có thể sẽ làm điều này.
ChocoDeveloper

@ChocoDeveloper: đôi khi các lớp không có hành vi là hữu ích hoặc cần thiết, tùy thuộc vào tình huống. Chúng được gọi là các lớp giá trị . Các lớp OOP bình thường là các lớp có hành vi mã hóa cứng. Các lớp OOP có thể kết hợp cũng có hành vi được mã hóa cứng, nhưng cấu trúc thành phần của chúng có thể tạo ra nhiều hành vi cần thiết cho một ứng dụng phần mềm.
rwong

3

Bạn đã xác định một chức năng duy nhất bạn muốn triển khai, vậy tại sao không nên tách biệt với mọi thứ bạn đã xác định cho đến nay? Đó là những gì Nguyên tắc Trách nhiệm duy nhất sẽ đề xuất là giải pháp.

Tạo một IImageResizergiao diện cho phép bạn vượt qua trong một hình ảnh và kích thước mục tiêu và trả về một hình ảnh mới. Sau đó tạo ra một thực hiện của giao diện đó. Thực tế có rất nhiều cách để thay đổi kích thước hình ảnh, vì vậy bạn thậm chí có thể kết thúc với nhiều hơn một!


+1 cho SRP có liên quan, nhưng tôi không thực hiện thay đổi kích thước thực tế, điều đó đã được ủy quyền cho thư viện bên thứ ba như đã nêu.
ChocoDeveloper

3

Tôi giả sử những sự thật về phương pháp, thay đổi kích thước hình ảnh:

  • Nó phải trả lại bản sao mới của hình ảnh. Bạn không thể sửa đổi hình ảnh, bởi vì nó sẽ phá vỡ mã khác có tham chiếu đến hình ảnh này.
  • Nó không cần truy cập vào dữ liệu nội bộ của lớp Image. Các lớp hình ảnh thường cần cung cấp quyền truy cập công khai vào những dữ liệu đó (hoặc bản sao của).
  • Thay đổi kích thước hình ảnh là phức tạp và đòi hỏi nhiều thông số khác nhau. Thậm chí có thể mở rộng điểm cho các thuật toán thay đổi kích thước khác nhau. Vượt qua tất cả mọi thứ sẽ dẫn đến chữ ký phương pháp lớn.

Dựa trên những sự thật đó, tôi sẽ nói rằng không có lý do gì cho phương thức thay đổi kích thước hình ảnh là một phần của chính lớp Image. Thực hiện nó như là phương thức trợ giúp tĩnh của lớp sẽ là tốt nhất.


Giả định tốt. Không chắc chắn tại sao nó phải là một phương thức tĩnh, tôi cố gắng tránh chúng để kiểm tra.
ChocoDeveloper

2

Một lớp xử lý hình ảnh có thể phù hợp (hoặc Trình quản lý hình ảnh, như bạn đã gọi nó). Chuyển hình ảnh của bạn sang phương thức CreatThumbnail của Bộ xử lý hình ảnh, ví dụ, để lấy hình ảnh thu nhỏ.

Một trong những lý do tôi muốn đề xuất tuyến đường này là bạn nói rằng bạn đang sử dụng thư viện xử lý hình ảnh của bên thứ 3. Việc lấy chức năng thay đổi kích thước ra khỏi lớp Image có thể giúp bạn dễ dàng cách ly bất kỳ mã bên thứ ba hoặc mã cụ thể nào của nền tảng. Vì vậy, nếu bạn có thể sử dụng lớp Hình ảnh cơ sở của mình trên tất cả các nền tảng / ứng dụng, thì bạn không cần phải làm ô nhiễm nó với mã cụ thể của nền tảng hoặc thư viện. Tất cả có thể được đặt trong Bộ xử lý hình ảnh.


Điểm tốt. Hầu hết mọi người ở đây không hiểu rằng tôi đã ủy thác phần phức tạp nhất cho thư viện bên thứ 3, có lẽ tôi đã không đủ rõ ràng.
ChocoDeveloper

2

Về cơ bản như Doc Brown đã nói:

Tạo một getAsThumbnail()phương thức cho lớp hình ảnh, nhưng phương thức này thực sự chỉ nên ủy thác công việc cho một số ImageUtilslớp. Vì vậy, nó sẽ trông giống như thế này:

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

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

Điều này sẽ cho phép mã dễ nhìn hơn. So sánh như sau:

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

Hoặc là

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

Nếu cách thứ hai có vẻ tốt cho bạn, bạn cũng có thể chỉ cần tạo phương thức trợ giúp này ở đâu đó.


1

Tôi nghĩ trong "Miền hình ảnh", bạn chỉ cần có đối tượng Hình ảnh bất biến và đơn điệu. Bạn yêu cầu hình ảnh cho một phiên bản đã thay đổi kích thước và nó trả về một phiên bản đã thay đổi kích thước của chính nó. Sau đó, bạn có thể quyết định xem bạn muốn thoát khỏi bản gốc hay giữ cả hai.

Giờ đây, các phiên bản hình thu nhỏ, hình đại diện, v.v ... của Hình ảnh hoàn toàn là một Tên miền khác, có thể yêu cầu miền Hình ảnh cho các phiên bản khác nhau của một hình ảnh nhất định để cung cấp cho người dùng. Thông thường tên miền này cũng không quá lớn hoặc chung chung, vì vậy bạn có thể giữ điều này trong logic ứng dụng.

Trong một ứng dụng quy mô nhỏ, tôi sẽ thay đổi kích thước hình ảnh trong thời gian đọc. Ví dụ: tôi có thể có quy tắc viết lại apache ủy nhiệm cho php một tập lệnh nếu hình ảnh 'http://my.site.com/images/thumbnails/image1.png', trong đó tệp sẽ được truy xuất bằng tên image1.png và thay đổi kích thước và được lưu trữ trong 'hình thu nhỏ / image1.png'. Sau đó, trong yêu cầu tiếp theo cho cùng một hình ảnh này, apache sẽ phục vụ hình ảnh trực tiếp mà không cần chạy tập lệnh php. Câu hỏi của bạn về findAllImagesWithoutThumbnails được trả lời tự động bằng apache, trừ khi bạn cần làm số liệu thống kê?

Trong một ứng dụng quy mô lớn, tôi sẽ gửi tất cả các hình ảnh mới cho một công việc nền, đảm nhiệm việc tạo các phiên bản khác nhau của hình ảnh và lưu nó ở những nơi thích hợp. Tôi sẽ không bận tâm đến việc tạo ra cả một miền hoặc lớp vì miền này rất khó phát triển thành một mớ hỗn độn khủng khiếp của mì spaghetti và nước sốt xấu.


0

Câu trả lời ngắn:

Giới thiệu của tôi là thêm phương thức này vào lớp hình ảnh:

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

Đối tượng Image vẫn không thể thay đổi, các phương thức này trả về một hình ảnh mới.


0

Đã có khá nhiều câu trả lời hay, vì vậy tôi sẽ giải thích một chút về phương pháp phỏng đoán đằng sau cách xác định đối tượng và trách nhiệm của chúng.

OOP khác với cuộc sống thực ở chỗ các đối tượng trong cuộc sống thực thường bị động và trong OOP chúng hoạt động. Và đây là cốt lõi của suy nghĩ đối tượng . Ví dụ, ai sẽ thay đổi kích thước một hình ảnh trong cuộc sống thực? Một con người, đó là thông minh trong khía cạnh đó. Nhưng trong OOP không có con người, vì vậy các đối tượng là thông minh thay thế. Một cách để thực hiện phương pháp "lấy con người làm trung tâm" này trong OOP là sử dụng các lớp dịch vụ, ví dụ, Managercác lớp khét tiếng . Do đó, các đối tượng được coi là cấu trúc dữ liệu thụ động. Đó không phải là một cách OOP.

Vì vậy, có hai lựa chọn. Cách thứ nhất, tạo một phương thức Image::createThumbnail(), đã được xem xét. Cái thứ hai là tạo một ResizedImagelớp. Nó có thể là một trang trí của một Image(nó phụ thuộc vào miền của bạn có duy trì Imagegiao diện hay không), mặc dù điều đó dẫn đến một số vấn đề đóng gói, vì ResizedImagesẽ phải có Imagenguồn. Nhưng người Imageta sẽ không bị choáng ngợp với việc thay đổi kích thước chi tiết, để nó thành một đối tượng miền riêng biệt, hoạt động theo SRP.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.