OOPで画像のサイズを変更できる必要がありますか?


9

私はImageエンティティを持つアプリを作成していますが、各タスクの責任を決定するのにすでに苦労しています。

最初に私はImageクラスを持っています。パス、幅、その他の属性があります。

次にImageRepository、単一のテスト済みメソッドで画像を取得するためのクラスを作成しましたfindAllImagesWithoutThumbnail()

しかし今、私もできるようにする必要がありますcreateThumbnail()。誰がそれに対処すべきですか?ImageManagerアプリ固有のクラスになるクラスを考えていました(選択したサードパーティの画像操作の再利用可能なコンポーネントもあり、私はホイールを再発明していません)。

それとも、Imageサイズ変更自体を行うには0Kでしょうか?または聞かせてImageRepositoryImageManager同じクラスで?

どう思いますか?


これまでにImageは何をしますか?
Winston Ewert 2012

画像のサイズをどのように変更する予定ですか?画像を縮小するだけですか、それともフルサイズに戻したいと想像できますか?
Gort the Robot

@WinstonEwertこれは、フォーマットされた解像度(例:文字列 '1440x900')、異なるURLなどのデータを生成し、「ビュー」や「投票」などの一部の統計を変更できます。
ChocoDeveloper 2012

@StevenBurnap私は元の画像を最も重要なものと考えています。サムネイルを使用するのは、閲覧を速くするためか、ユーザーがデスクトップなどに合わせてサイズを変更したい場合のみです。それらは別のものです。
ChocoDeveloper 2012

Imageクラスを不変にして、渡すときに防御的にコピーする必要がないようにします。
dan_waterworth 2012

回答:


8

質問は曖昧すぎて、Imageオブジェクトがどのように使用されるかに依存するため、本当の答えを出すことはできません。

1つのサイズの画像のみを使用していて、ソース画像のサイズが間違っているためにサイズを変更している場合は、読み取りコードでサイズを変更するのが最適な場合があります。あなたの持っているcreateImage方法は、幅/高さを取るし、その幅/高さにリサイズされた画像を返します。

複数のサイズが必要で、メモリが問題にならない場合は、画像を最初に読み取ったとおりにメモリに保持し、表示時にサイズを変更することをお勧めします。このような場合、いくつかの異なるデザインを使用できます。画像widthとはheight固定されますが、あなたはどちらか持っていると思いますdisplay位置と目標の高さ/幅を取った方法をかあなたが使用しているどんなディスプレイシステムというオブジェクトを返さ幅/高さを取って、いくつかの方法を持っていると思います。私が使用したほとんどの描画APIでは、描画時にターゲットサイズを指定できます。

パフォーマンスが問題になるほど頻繁に異なるサイズで描画する必要がある場合は、元のサイズに基づいて異なるサイズの新しい画像を作成する方法が考えられます。別の方法として、Imageクラスに内部的に異なる表現をキャッシュさせ、最初displayにサムネイルサイズで呼び出したときにサイズ変更が行われ、2回目には前回保存されたキャッシュコピーを描画するだけにすることもできます。これはより多くのメモリを使用しますが、一般的なサイズ変更がいくつかあることはまれです。

別の方法としてImage、ベースイメージを保持する単一のクラスを作成し、そのクラスに1つ以上の表現を含める方法があります。Imageそれ自体は高さ/幅を持っていないでしょう。代わりに、ImageRepresentation高さと幅のあるものから始めます。この表現を描画します。画像を「サイズ変更」するにImageは、特定の高さ/幅のメトリックを使用して表現を要求します。これにより、オリジナルだけでなくこの新しい表現も含まれるようになります。これにより、余分な複雑さを犠牲にして、メモリ内で何がハングしているのかを正確に制御できます。

Manager「マネージャー」はクラスが何をするのか正確にはあまり語らない非常に曖昧な言葉なので、私は個人的にその単語を含むクラスを嫌います。オブジェクトの有効期間を管理しますか?それはアプリケーションの残りの部分とそれが管理するものの間に立っていますか?


「これは、Imageオブジェクトがどのように使用されるかに依存します。」このステートメントは、カプセル化と疎結合の概念と実際には一致していません(ただし、この場合、原則が少し離れすぎている可能性があります)。より良い区別のポイントは、Imageの状態に意味のあるサイズが含まれているかどうかです。
Chris Bye

あなたは画像編集アプリケーションの観点から話しているようです。OPがこれを望んでいたかどうかは完全に明確ではありませんか?
andho '25年

6

要件がいくつかある限り、物事をシンプルに保ち、必要に応じて設計を改善します。私はほとんどの実際のケースでは、そのようなデザインから始めるのに何も問題はないと思います:

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

より多くのパラメーターをに渡すcreateThumbnail()必要があり、それらのパラメーターに独自の存続期間が必要な場合、状況は異なる場合があります。たとえば、数百の画像のサムネイルを作成する場合を考えてみましょう。すべての宛先サイズ、サイズ変更アルゴリズム、または品質が指定されています。つまり、これらのパラメーターがコンストラクターによって渡され、オブジェクトの有効期間にバインドされているcreateThumbnail別のクラス(マネージャークラスやクラスなど)にを移動できます。ImageResizerImageResizer

実際、最初のアプローチから始めて、本当に必要なときに後でリファクタリングします。


ImageResizerさて、別のコンポーネントへの呼び出しを既に委任している場合は、そもそもそれをクラスの責任にしてみませんか?責任を新しいクラスに移す代わりに、いつ電話を委任しますか?
Songo 2012

2
@Songo:2つの考えられる理由:(1)に委任するためのコード-おそらく既に存在する-個別のコンポーネントは、単純な1行ではなく、おそらく4から6のコマンドのシーケンスだけなので、それを格納する場所が必要です。 。(2)構文上の砂糖/使いやすさ:書くのは非常にハンサムImage thumbnail = img.createThumbnail(x,y)です。
Doc Brown

+1 aahなるほど。説明してくれてありがとう:)
Songo

4

Image外部クラスでサイズを変更するには、リサイザがの実装を認識している必要があるため、Imageカプセル化に違反しているため、クラスの一部である必要があると思います。Imageは基本クラスであると想定しています。具体的な画像タイプ(PNG、JPEG、SVGなど)の個別のサブクラスを用意します。したがって、対応するサイズ変更クラスを用意するかswitch、実装クラスに基づいてサイズ変更するステートメントを含む一般的なサイズ変更ツール(クラシックなデザインのにおい)を用意する必要があります。

1つのアプローチとして、Imageコンストラクターに画像を含むリソースと、高さと幅のパラメーターを取得して適切に作成させることが考えられます。次に、サイズ変更は、元のリソース(画像内にキャッシュされている)と新しいサイズパラメータを使用して新しいオブジェクトを作成するのと同じくらい簡単です。例えばfoo.createThumbnail()、単にでしょうreturn new Image(this.source, 250, 250)。(もちろんImage、の具体的なタイプですfoo)。これにより、画像は不変になり、その実装は非公開になります。


私はこのソリューションが好きですが、リサイザがの内部実装を知っている必要があると言う理由がわかりませんImage。必要なのは、ソースとターゲットのディメンションだけです。
ChocoDeveloper 2012

外部クラスを使用することは、予期しないことになるので意味がないと思いますが、カプセル化に違反する必要も、サイズ変更を実行するときにswitch()ステートメントを使用する必要もありません。最終的に画像は何かの2D配列であり、インターフェイスで個々のピクセルを取得および設定できる限り、画像のサイズを確実に変更できます。
whatsisname

4

私はOOPが一緒にデータと振る舞いをカプセル化についてであることを知っているが、画像がありませんので、私は、それがこのケースに埋め込まれてリサイズロジックを持っているイメージのために良いアイデアだとは思わない必要があること自体のサイズを変更する方法を知っています画像。

サムネイルは実際には別の画像です。おそらく、写真とサムネイル(どちらも画像)の関係を保持するデータ構造を持っている可能性があります。

プログラムを(画像、写真、サムネイルなど)とサービス(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;
        }
    }
}

...遅延評価のあるデータ構造が必要な場合。(申し訳ありませんが、nullチェックを含めず、スレッドセーフにしませんでした。これは、不変のデータ構造を模倣しようとした場合に必要なものです)。

ご覧のとおり、これらのクラスのいずれかは、何らかの依存関係の注入によって取得されたThumbnailGeneratorへの参照を持っている、何らかのPhotographRepositoryによって構築されています。


私は振る舞いのないクラスを作るべきではないと言われました。「データ構造」と言ったときに、C ++のクラスまたは何かを参照しているかどうかはわかりません(この言語ですか?)。私が知って使用しているデータ構造は、プリミティブだけです。サービスとDIの部分はしっかりしていて、結局これをやってしまうかもしれません。
ChocoDeveloper 2012

@ChocoDeveloper:状況に応じて、動作のないクラスが役立つ場合や必要な場合があります。それらは値クラスと呼ばれます。通常のOOPクラスは、ハードコードされた動作を持つクラスです。合成可能なOOPクラスにもハードコードされた動作がありますが、それらの構成の構造により、ソフトウェアアプリケーションで必要な多くの動作が発生する可能性があります。
rwong 2012

3

実装する単一の機能を特定したので、これまでに特定したすべての機能と切り離してはならないのですか?それが単一責任原則が提案するソリューションです。

IImageResizer画像とターゲットサイズを渡し、新しい画像を返すインターフェースを作成します。次に、そのインターフェースの実装を作成します。実際に画像のサイズを変更する方法はたくさんあるので、複数になる可能性もあります!


関連するSRPの+1。ただし、実際のサイズ変更は実装していません。すでに述べたように、サードパーティのライブラリに委任されています。
ChocoDeveloper 2012

3

画像のサイズを変更する方法について、これらの事実を前提としています。

  • 画像の新しいコピーを返す必要があります。イメージ自体を変更することはできません。このイメージを参照している他のコードが破損するためです。
  • Imageクラスの内部データにアクセスする必要はありません。画像クラスは通常、それらのデータ(またはそのコピー)へのパブリックアクセスを提供する必要があります。
  • 画像のサイズ変更は複雑で、多くの異なるパラメーターが必要です。おそらく、さまざまなサイズ変更アルゴリズムの拡張性ポイントです。すべてを渡すと、メソッドの署名が大きくなります。

これらの事実に基づいて、Imageサイズ変更メソッドがImageクラス自体の一部である理由はないと思います。クラスの静的ヘルパーメソッドとして実装するのが最適です。


良い仮定。なぜそれが静的メソッドでなければならないのかはわかりませんが、私はテストしやすいようにそれらを避けようとしています。
ChocoDeveloper 2012

2

画像処理クラスが適切な場合があります(または、イメージマネージャー。たとえば、画像を画像プロセッサのCreateThumbnailメソッドに渡して、サムネイル画像を取得します。

このルートを提案する理由の1つは、サードパーティの画像処理ライブラリを使用しているということです。Imageクラス自体からサイズ変更機能を取り除くと、プラットフォーム固有のコードまたはサードパーティのコードを簡単に分離できる場合があります。したがって、すべてのプラットフォーム/アプリでベースのImageクラスを使用できる場合、プラットフォームまたはライブラリ固有のコードで汚染する必要はありません。これらはすべてイメージプロセッサに配置できます。


いい視点ね。ここのほとんどの人は、私がすでに最も複雑な部分をサードパーティのライブラリに委任していることを理解していませんでした。
ChocoDeveloper 2012

2

基本的にDoc Brownはすでに言ったように:

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

「画像ドメイン」では、不変でモナディックな画像オブジェクトを持っていると思います。画像にサイズ変更されたバージョンを要求すると、画像のサイズ変更されたバージョンが返されます。次に、オリジナルを削除するか、両方を保持するかを決定できます。

これで、画像のサムネイル、アバターなどのバージョンは完全に別のドメインになり、特定の画像の異なるバージョンをユーザーに提供するように画像ドメインに要求できます。通常、このドメインはそれほど巨大でも汎用的でもないので、おそらくこれをアプリケーションロジックに保持できます。

小規模なアプリでは、読み取り時に画像のサイズを変更します。たとえば、画像が「http://my.site.com/images/thumbnails/image1.png」の場合、php1スクリプトにphpに委任するapache書き換えルールを設定できます。この場合、ファイルはimage1.pngという名前を使用して取得されます。 'thumbnails / image1.png'にサイズ変更および保存されます。次に、この同じ画像に対する次のリクエストで、Apacheはphpスクリプトを実行せずに画像を直接提供します。findAllImagesWithoutThumbnailsに関するあなたの質問は、統計を行う必要がない限り、Apacheによって自動的に回答されますか?

大規模なアプリでは、すべての新しい画像をバックグラウンドジョブに送信します。これにより、画像のさまざまなバージョンが生成され、適切な場所に保存されます。このドメインがスパゲッティと悪いソースのひどい混乱に発展する可能性は非常に低いので、私はドメイン全体またはクラス全体を作成することを気にしません。


0

簡潔な答え:

私の推奨は、このメソッドに画像クラスを追加することです:

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

Imageオブジェクトは依然として不変であり、これらのメソッドは新しい画像を返します。


0

すばらしい回答はすでにいくつかあるので、オブジェクトとその責任を識別する方法の背後にあるヒューリスティックスについて少し詳しく説明します。

OOPは、実際のオブジェクトとはしばしば受動的であり、OOPではアクティブであるという点で、実際の生活とは異なります。そして、これはオブジェクト思考の中核です。たとえば、実際に画像のサイズを変更するのは誰ですか?その点で賢い人間。しかし、OOPには人間がいないため、オブジェクトはスマートです。OOPでこの「人間中心」のアプローチを実装する方法は、悪名高いManagerクラスなどのサービスクラスを利用することです。したがって、オブジェクトは受動的なデータ構造として扱われます。OOPの方法ではありません。

したがって、2つのオプションがあります。最初のメソッドImage::createThumbnail()、メソッドの作成はすでに検討されています。2つ目は、ResizedImageクラスを作成することです。のソースが必要になるため、カプセル化の問題が発生Imageしますが、それは(Imageインターフェイスを保持するかどうかはドメインによって異なります)のデコレータにすることができます。しかし、は詳細のサイズ変更に圧倒されず、SRPに従って動作する別のドメインオブジェクトに任せます。ResizedImageImageImage

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.