31

長さはご容赦ください。ただし、2つのプログラムがあります。どちらもまったく同じですが、1つはセッター、ゲッター、コンストラクターがあり、もう1つはありません。

私は以前に基本的なC++クラスを受講したことがありますが、それらのいずれも覚えていません。現時点では、それらの要点がわかりません。誰かが女性の言葉で説明していただければ幸いです。現時点では、コードを長く見せるためのスペースの浪費にすぎないようですが、先生はそれらが重要であると言っています(これまでのところそれだけです)。

前もって感謝します!そして今ここにコードがあります:Mileage.java:

package gasMileage;

import java.util.Scanner; //program uses class Scanner

public class Mileage 
{
    public int restart;
    public double miles, gallons, totalMiles, totalGallons, milesPerGallon;
    public Mileage(int newRestart, double newMiles, double newGallons, 
                   double newTotalMiles, double newTotalGallons, double newMilesPerGallon)
    {
        setRestart(newRestart);
        setMiles(newMiles);
        setGallons(newGallons);
        setTotalMiles(newTotalMiles);
        setTotalGallons(newTotalGallons);
        setMilesPerGallon(newMilesPerGallon);
    }
    public void setRestart(int newRestart)
    {
        restart = newRestart;
    }
    public int getRestart()
    {
        return restart;
    }
    public void setMiles(double newMiles)
    {
        miles = newMiles;
    }
    public double getMiles()
    {
        return miles;
    }
    public void setGallons(double newGallons)
    {
        gallons = newGallons;
    }
    public double getGallons()
    {
        return gallons;
    }
    public void setTotalMiles(double newTotalMiles)
    {
        totalMiles = newTotalMiles;
    }
    public double getTotalMiles()
    {
        return totalMiles;
    }
    public void setTotalGallons(double newTotalGallons)
    {
        totalGallons = newTotalGallons;
    }
    public double getTotalGallons()
    {
        return totalGallons;
    }
    public void setMilesPerGallon(double newMilesPerGallon)
    {
        milesPerGallon = newMilesPerGallon;
    }
    public double getMilesPerGallon()
    {
        return milesPerGallon;
    }
    public void calculateMileage()
    {
        Scanner input = new Scanner(System.in);
        while(restart == 1)
        {
            System.out.print("Please input number of miles you drove: ");
            miles = input.nextDouble();
            totalMiles = totalMiles + miles;
            System.out.print("Please input number of gallons you used: ");
            gallons = input.nextDouble();
            totalGallons = totalGallons + gallons;
            milesPerGallon = miles / gallons;
            System.out.printf("Your mileage is %.2f MPG.\n", milesPerGallon);
            System.out.print("Would you like to try again? 1 for yes, 2 for no: ");
            restart = input.nextInt();
        }
        milesPerGallon = totalMiles / totalGallons;
        System.out.printf("Your total mileage for these trips is: %.2f.\nYour total gas consumed on these trips was: %.2f.\n", totalMiles, totalGallons);
        System.out.printf("Your total mileage for these trips is: %.2f MPG", milesPerGallon);
    }
}

Mileagetest.java:

package gasMileage;

public class Mileagetest 
{
    public static void main(String[] args) 
    {
        Mileage myMileage = new Mileage(1,0,0,0,0,0);
        myMileage.calculateMileage();
    }
}

そして今、セッターとゲッターのないもののために:

Testmileage.java:

package gasMileage;

import java.util.Scanner;

public class Testmileage 
{
    int restart = 1;
    double miles = 0, milesTotal = 0, gas = 0, gasTotal = 0, mpg = 0;
    Scanner input = new Scanner(System.in);
    public void testCalculate()
    {
        while(restart == 1)
        {
            System.out.print("Please input miles: ");
            miles = input.nextDouble();
            milesTotal = milesTotal + miles;
            System.out.print("Please input gas: ");
            gas = input.nextDouble();
            gasTotal = gasTotal + gas;
            mpg = miles/gas;
            System.out.printf("MPG: %.2f", mpg);
            System.out.print("\nContinue? 1 = yes, 2 = no: ");
            restart = input.nextInt();
        }
            mpg = milesTotal / gasTotal;
            System.out.printf("Total Miles: %.2f\nTotal Gallons: %.2f\nTotal MPG: %.2f\n", milesTotal, gasTotal, mpg);
    }
}

Testmileagetest.java:

package gasMileage;

public class Testmileagetest 
{

    /**
     * @param args
     */
    public static void main(String[] args) 
    {
        Testmileage test = new Testmileage();
        test.testCalculate();
    }

}

再度、感謝します!

4

14 に答える 14

28

ゲッターとセッターのポイントは、言語に関係なく、基になる変数を非表示にすることです。これにより、値を設定しようとするときに検証ロジックを追加できます。たとえば、誕生日のフィールドがある場合、そのフィールドを過去のある時点にのみ設定できるようにしたい場合があります。フィールドが公的にアクセス可能で変更可能である場合、これを強制することはできません。ゲッターとセッターが必要です。

まだ確認が必要ない場合でも、将来必要になる可能性があります。ゲッターとセッターを作成することで、インターフェースの一貫性が保たれるようになり、変更しても既存のコードが壊れることはありません。

于 2009-09-22T18:06:40.400 に答える
25

カプセル化

アクセサメソッド(「セッターとゲッター」)は、オブジェクト内のデータがどのように格納されているかについての詳細を隠そうとします。実際には、これらはオブジェクト指向ではない方法でデータを保存および取得するための栄光の手段です。アクセサは、次の2つのコードの間に実際的な違いがほとんどないという点で、効果的に何もカプセル化しません。

Person bob = new Person();
Colour hair = bob.getHairColour();
hair.setRed( 255 );

この:

Person bob = new Person();
Colour hair = bob.hairColour;
hair.red = 255;

どちらのコードスニペットも、人が髪の毛と緊密に結合しているという考えを明らかにしています。この緊密な結合は、コードベース全体に現れ、ソフトウェアが脆弱になります。つまり、人の髪の毛の保管方法を変更することが難しくなります。

その代わり:

Person bob = new Person();
bob.colourHair( Colour.RED );

これは、「教えて、聞かないで」という前提に沿ったものです。つまり、オブジェクトは(他のオブジェクトによって)特定のタスクを実行するように指示される必要があります。これがオブジェクト指向プログラミングの要点です。そして、それを理解している人はほとんどいないようです。

2つのシナリオの違いは次のとおりです。

  • 最初の状況では、ボブは自分の髪の色を制御できませんでした。赤毛が好きなヘアスタイリストには最適ですが、その色を軽蔑するボブにはあまり適していません。
  • 2番目の状況では、システム内の他のオブジェクトがボブの許可なしにその色を変更することは許可されていないため、ボブは自分の髪の色を完全に制御できます。

この問題を回避する別の方法は、ボブに結合されなくなったボブの髪の色のコピーを(新しいインスタンスとして)返すことです。他のクラスが人の髪の毛を使って望んでいる行動があり、それはもはや人自身とは関係がないことを意味するので、私はそれがエレガントでない解決策であると思います。これにより、コードを再利用する機能が低下し、コードが重複することになります。

データ型を非表示にする

戻り型のみが異なる2つのメソッドシグネチャを持つことができないJavaでは、オブジェクトによって使用される基になるデータ型を実際に非表示にすることはありません。次のことはめったに見られません。

public class Person {
  private long hColour = 1024;

  public Colour getHairColour() {
    return new Colour( hColour & 255, hColour << 8 & 255, hColour << 16 & 255 );
  }
}

通常、個々の変数は、対応するアクセサーを使用してデータ型を逐語的に公開し、それを変更するにはリファクタリングが必要です。

public class Person {
  private long hColour = 1024;

  public long getHairColour() {
    return hColour;
  }

  /** Cannot exist in Java: compile error. */
  public Colour getHairColour() {
    return new Colour( hColour & 255, hColour << 8 & 255, hColour<< 16 & 255 );
  }
}

それは抽象化のレベルを提供しますが、それは緩い結合のために何もしない薄いベールです。

教えて、聞かないで

このアプローチの詳細については、「伝える、聞かないでください」を参照してください

ファイルの例

ColinDの回答から少し変更された次のコードについて考えてみます。

public class File {
   private String type = "";

   public String getType() {
      return this.type;
   }

   public void setType( String type ) {
      if( type = null ) {
        type = "";
      }

      this.type = type;
   }

   public boolean isValidType( String type ) {
      return getType().equalsIgnoreCase( type );
   }
}

この場合のメソッドgetType()は冗長であり、必然的に(実際には)次のような重複したコードになります。

public void arbitraryMethod( File file ) {
  if( file.getType() == "JPEG" ) {
    // Code.
  }
}

public void anotherArbitraryMethod( File file ) {
  if( file.getType() == "WP" ) {
    // Code.
  }
}

問題:

  • データ・タイプ。type属性を文字列から整数(または別のクラス)に簡単に変更することはできません。
  • 暗黙のプロトコル。型を特定の( PNG、、、)から一般的な(、、)に抽象化するには時間がかかります。JPEGTIFFEPSIMAGEDOCUMENTSPREADSHEET
  • バグを紹介します。暗黙のプロトコルを変更しても、バグにつながる可能性のあるコンパイラエラーは生成されません。

他のクラスがデータを要求しないようにすることで、問題を完全に回避します。

public void arbitraryMethod( File file ) {
  if( file.isValidType( "JPEG" ) ) {
    // Code.
  }
}

これは、getアクセサメソッドをprivate次のように変更することを意味します。

public class File {
   public final static String TYPE_IMAGE = "IMAGE";

   private String type = "";

   private String getType() {
      return this.type;
   }

   public void setType( String type ) {
      if( type == null ) {
        type = "";
      }
      else if(
        type.equalsIgnoreCase( "JPEG" ) ||
        type.equalsIgnoreCase( "JPG" ) ||
        type.equalsIgnoreCase( "PNG" ) ) {
        type = File.TYPE_IMAGE;
      }

      this.type = type;
   }

   public boolean isValidType( String type ) {
      // Coerce the given type to a generic type.
      //
      File f = new File( this );
      f.setType( type );

      // Check if the generic type is valid.
      //
      return isValidGenericType( f.getType() );
   }
}

Fileクラスが暗黙のプロトコルを特定のタイプ(JPEGなど)からジェネリック型(IMAGEなど)に移行するときに、システム内の他のコードが破損することはありません。システム内のすべてのコードはisValidType、呼び出し元オブジェクトに型を与えないが、型を検証するようにクラスに指示するメソッドを使用する必要があります。File

于 2009-09-22T20:33:38.810 に答える
18

他の回答は、一般的に、ゲッターとセッターを使用するいくつかの理由の良いアイデアを提供しますが、それらがなぜ有用であるかについて、いくらか完全な例を示したいと思います。

たとえば、ファイルを取り上げましょう(FileJavaのクラスの存在を無視します)。このFileクラスには、ファイルの種類(.pdf、.exe、.txtなど)を格納するためのフィールドがあります...他のすべては無視します。

String最初に、ゲッターとセッターなしでそれを保存することにしました。

public class File {
   // ...
   public String type;
   // ...
}

ゲッターとセッターを使用しない場合のいくつかの問題があります。

フィールドの設定方法を制御することはできません。

あなたのクラスのどんなクライアントも彼らがそれでやりたいことをすることができます:

public void doSomething(File file) {
   // ...
   file.type = "this definitely isn't a normal file type";
   // ...
}

後で、おそらく彼らにそうさせたくないと判断します...しかし、彼らはクラスのフィールドに直接アクセスできるので、それを防ぐ方法はありません。

内部表現を簡単に変更できない:

後で、ファイルタイプをと呼ばれるインターフェイスのインスタンスとして保存し、FileTypeいくつかの動作をさまざまなファイルタイプに関連付けることができるようにすることにしました。ただし、クラスの多くのクライアントはすでにファイルタイプをStringsとして取得および設定しています。Stringしたがって、そこで問題が発生します...フィールドをaから。に変更しただけでは、多くのコード(ライブラリの場合は自分で修正できない他のプロジェクトのコードでも)が壊れますFileType

ゲッターとセッターがこれをどのように解決するか

privateここで、代わりにタイプフィールドを作成して作成したと想像してください。

public String getType() {
   return this.type;
}

public void setType(String type) {
   this.type = type;
}

プロパティの設定を制御します。

これで、特定の文字列のみが有効なファイルタイプであり、他の文字列を防ぐという要件を実装する場合は、次のように記述できます。

public void setType(String type) {
   if(!isValidType(type)) {
       throw new IllegalArgumentException("Invalid file type: " + type);
   }
   this.type = type;
}

private boolean isValidType(String type) {
   // logic here
}

内部表現を簡単に変更する機能:

タイプの表現を変更するのStringは比較的簡単です。有効なタイプのファイルenum ValidFileTypeを実装して含むを持っていると想像してください。FileType

次のように、クラス内のファイルタイプの内部表現を簡単に変更できます。

public class File {
   // ...
   private FileType type;
   // ...
   public String getType() {
      return type.toString();
   }

   public void setType(String type) {
      FileType newType = ValidFileType.valueOf(type);

      if(newType == null) {
         throw new IllegalArgumentException("Invalid file type: " + type);
      }

      this.type = newType;
   }
}

クラスのクライアントはとにかく電話getType()をかけてきたのでsetType()、彼らの観点からは何も変わりません。他のクラスが使用しているインターフェースではなく、クラスの内部のみが変更されました。

于 2009-09-22T21:30:27.947 に答える
5

アイデアは、クライアントクラスがget / set関数を呼び出す場合、後でそれらが行うことを変更でき、呼び出し元が隔離されるということです。パブリック変数があり、私が直接アクセスする場合、後でアクセスまたは設定するときに動作を追加する方法はありません。

あなたの単純な例でさえ、あなたはそれをもっと利用することができます。

使用する代わりに:

milesPerGallon = miles / gallons;

calculateMileage()で

setMiles()とsetGallons()を変更して、呼び出されたときにmilesPerGallonを更新できます。次に、setMilesPerGallon()を削除して、読み取り専用プロパティであることを示します。

于 2009-09-22T18:06:57.793 に答える
3

重要なのは、これは実装固有であるため、クラスはそのフィールドへの直接アクセスを許可してはならないということです。別のデータストレージを使用するために後でクラスを変更したいが、その「ユーザー」に対してクラスを同じに保つか、フィールドを含めることができないインターフェイスを作成したい場合があります。

このテーマに関するウィキペディアの記事をご覧ください。

于 2009-09-22T18:04:59.420 に答える
2

これらは、クラスのパブリックインターフェイスと、カプセル化の手段を提供します。ゲッターとセッターなしで公開データにアクセスする方法を検討してください。

Mileage m = new Mileage();
m.miles = 5.0;
m.gallons = 10.0;
...

ここで、クラスに検証を追加する場合は、フィールドに直接アクセスしたすべての場所でコードを変更する必要があります。最初からゲッターとセッターを使用する場合(必要な場合のみ)、その作業を回避し、コードを1か所で変更するだけです。

于 2009-09-22T18:08:07.980 に答える
2

ゲッターとセッターを使用すると、後で実装を変更する柔軟性が得られます。あなたはそれが必要だとは思わないかもしれませんが、時々あなたはそうします。たとえば、プロキシパターンを使用して、使用に費用がかかるオブジェクトを遅延ロードしたい場合があります。

class ExpensiveObject {
    private int foo;

    public ExpensiveObject() {
       // Does something that takes a long time.
    }

    public int getFoo() { return foo; }
    public void setFoo(int i) { foo = i; }
}

class ExpensiveObjectProxy extends ExpensiveObject {
    private ExpensiveObject realObject;

    public ExpensiveObjectProxy() { ; }

    protected void Load() {
       if ( realObject == null ) realObject = new ExpensiveObject();
    }

    public int getFoo() { Load(); return realObject.getFoo(); }
    public void setFoo(int i) { Load(); realObject.setFoo(i); }
}

class Main {
    public static void main( string[] args ) {
         // This takes no time, since ExpensiveOjbect is not constructed yet.
         ExpensiveObject myObj = new ExpensiveObjectProxy();

         // ExpensiveObject is actually constructed here, when you first use it.
         int i = myObj.getFoo();
    }
}

これがよく発生するのは、ORMを介してオブジェクトをデータベースにマップする場合です。必要なものだけをロードしてから、データベースに戻って、実際に使用されている場合は残りをロードします。

于 2009-09-22T18:13:38.187 に答える
2

一般に、セッターとゲッターは、すべての変数がプライベートでなければならないという事実を回避するための初期のGUIビルダー(ボーランド)による悪いハックでした(本当に、これは絶対に必要です)

それらを抽象化と呼ぶ人もいますが、そうではありません。ボイラープレートセッター/ゲッターは、パブリックメンバーに勝るものはありません。クラスが制御できない場合でも変数へのフルアクセスを許可し、クラス内の変更を制限します(変数がintの場合は、setterとgetterを呼び出すすべてを変更して、変数を文字列に変更する必要があります)。

ゲッターとセッターは、クラスの外部からクラスのデータにアクセスすることをお勧めします。クラスのメンバーにアクセスするコードは、おそらく(設計で述べられているように)そのクラス内に存在する必要があるため、セッターやゲッターは必要ありません。それらは不要である必要があります。

また、すべてのクラスにSetterを強制することは恐ろしいことです。つまり、クラスを不変にすることはできませんが、クラスを可変にするための本当に良い理由が必要です。

とは言うものの、永続性エンジンやGUIビルダーなど、値を取得および設定でき、クラスが取得または変更されたものを監視し、それを変更または検証できる横断的関心事に役立ちます。

横断的な変数アクセスを必要とするシステムのより良いパターンは、リフレクションを介して変数に直接アクセスすることですが、セッターまたはゲッターが存在する場合はそれを呼び出し、可能であればセッターとゲッターをプライベートにします。

これにより、OO以外の横断的コードが正しく機能し、クラスが必要なときにセットと取得を変更し、必要に応じてゲッター(非常に便利な場合があります)を使用できるようになります。

于 2011-03-18T21:58:16.663 に答える
1

アクセサメソッドのポイント。ゲッターとセッターは、カプセル化AKA情報隠蔽を提供することです。これは、オブジェクト指向プログラミングの基本原則の1つです。

アクセサメソッド

情報の隠蔽/カプセル化

于 2009-09-22T18:04:12.627 に答える
1

一言で言えば、インターフェースです。

インターフェイスはフィールドではなくメソッドを許可するため、確立された規則では、この目的のためにgetXメソッドとsetXメソッドを使用します。

(そして、インターフェースは、Javaでの実装から機能を切り離す方法です)

于 2009-09-22T20:07:25.063 に答える
1

あなたの例は、ばかげているほど極端です。はい、それらすべてのゲッターとセッターはコードを肥大化し、その場合は何の価値も追加しません。ただし、カプセル化の基本的な考え方は、相互作用する多くのコンポーネントで構成される大規模なシステムを対象としており、小さな自己完結型のプログラムを対象とはしていません。

ゲッターとセッターの有用で賢明な使用法の特徴:

  • 他の多くのクラスで使用されるクラス(実装の詳細を非表示にすると、クライアントが簡単になります)
  • ゲッターとセッターは、実際に必要なフィールドのみを対象としています。可能な限り少なく、ほとんどのフィールドはプライベートで、クラス内でのみ使用する必要があります。
  • 一般にセッターはごくわずかです。可変フィールドを使用すると、読み取り専用フィールドよりもプログラムの状態を追跡するのがはるかに困難になります。
  • 無効な値の例外をスローしたり、「最終変更」タイムスタンプを更新したりするセッターや、基になるフィールドに依存するのではなくオンザフライで値を計算するゲッターなど、フィールドへのアクセス以外に実際に何かを行うゲッターとセッター
于 2009-09-22T20:39:31.823 に答える
0

数ヶ月早送りします。たぶんあなたの先生はあなたにMilageクラスのリモートバージョンを実装するように頼みます。多分ウェブサービスとして、多分何か他のもの。

ゲッター/セッターがなければ、マイレージにアクセスするすべてのコードを変更する必要があります。ゲッター/セッターを使用すると、(少なくとも完璧な世界では)マイレージタイプの作成を変更するだけで済みます。

于 2009-09-22T18:08:57.150 に答える
0

ゲッターとセッターを使用すると、オブジェクト内のデータにアクセスして変更するための便利なショートカットを作成できます。一般に、これは、次のように、値を取得および設定するために使用されるオブジェクトを使用して2つの関数を使用する代わりの方法と見なすことができます。

{
    getValue: function(){
        return this._value;
    },
    setValue: function(val){
        this._value = val;
    }
}

この方法でJavaScriptを作成することの明らかな利点は、ユーザーに直接アクセスさせたくないあいまいな値を使用できることです。次のような最終結果(クロージャを使用して、新しく構築されたフィールドの値を格納します):

function Field(val){
    var value = val;

    this.getValue = function(){
        return value;
    };

    this.setValue = function(val){
        value = val;
    };
}

セッターメソッドとゲッターメソッドの追加マネージドBeanの状態にアクセスできるようにするには、その状態のセッターメソッドとゲッターメソッドを追加する必要があります。createSalutationメソッドはBeanのsgreetメソッドを呼び出し、getSalutationメソッドは結果を取得します。セッターメソッドとゲッターメソッドが追加されると、Beanが完成します。最終的なコードは次のようになります。

import javax.inject.Inject;
import javax.enterprise.context.RequestScoped;
import javax.inject.Named;

@Named
@RequestScoped
public class Printer {

    @Inject @Informal Greeting greeting;

    private String name;
    private String salutation;

    public void createSalutation() {
        this.salutation = greeting.greet(name);
    }

    public String getSalutation() {
        return salutation;
    }
    public String setName(String name) {
       this.name = name;
    }

    public String getName() {
       return name;
    }
}
于 2010-09-15T02:10:39.267 に答える
0

カプセル化コードの再利用性は、オブジェクト指向プログラミングの美しさです。コードで機密データを処理している場合は、それをプライベートデータフィールドとして宣言します。つまり、誰も直接アクセスできないようにデータをカプセル化します。これらのデータフィールドにアクセスする場合は、セッターとゲッターを使用する必要があります。つまり、機密データフィールドを処理するための制御されたアクセスメカニズム。次の例は、セッターとゲッターの利点と重要性を理解するのに役立ちます。

  • 日変数を利用するクラスを実装しました。
  • 私のクラスでは、365日を超える日の値を設定することはできません。
  • 誰かが私のクラスから継承したいと思っています(コードの再利用性)。
  • 彼が365日を超える日数の値を入力すると、私のクラスのすべての機能が失敗します。
  • したがって、days変数をプライベートデータフィールドとして宣言する必要があります。
  • これで、日数データフィールドをプライベートとして宣言した場合、入力に関する前述の制限付きのセッター関数を実装したため、365日を超える日数の値を設定することはできませんでした。
于 2016-05-02T10:17:00.317 に答える