Вопрос

На этот вопрос уже есть ответ здесь:

Простите за длину, но вот две программы, обе одинаковые, но одна с сеттерами, геттерами и конструкторами, а другая без них.

Раньше я посещал базовый курс C++ и не помню ничего из этого, и на данный момент я не вижу в них смысла, если бы кто-нибудь мог объяснить их в терминах ламена, я был бы очень признателен.. .на данный момент они кажутся не чем иным, как пустой тратой места, чтобы мой код выглядел длиннее, но учитель говорит, что они важны (и пока все).

Заранее спасибо!А теперь вот код:Пробег.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);
    }
}

Пробегtest.java:

package gasMileage;

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

А теперь тот, у кого нет сеттеров и геттеров:

Тестпробег.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();
    }

}

Еще раз спасибо!

Это было полезно?

Решение

Точка геттеров и сеттеров, независимо от языка, заключается в том, чтобы скрыть базовую переменную.Это позволяет вам добавить логику проверки при попытке установить значение — например, если у вас есть поле для даты рождения, вы можете разрешить установку этого поля только на какое-то время в прошлом.Это невозможно обеспечить, если поле общедоступно и может быть изменено — вам нужны геттеры и сеттеры.

Даже если вам пока не нужна никакая проверка, она может понадобиться вам в будущем.Написание геттеров и сеттеров теперь означает, что интерфейс остается согласованным, поэтому существующий код не сломается при его изменении.

Другие советы

Другие ответы обычно дают хорошее представление о некоторых причинах использования геттеров и сеттеров, но я хочу дать несколько полный пример того, почему они полезны.

Возьмем, к примеру, файл (игнорируя существование File класс в Java).Этот 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, что позволяет вам связать определенное поведение с разными типами файлов.Однако многие клиенты вашего класса уже получают и устанавливают типы файлов как Stringс.Так что у вас там проблемы...вы сломаете много кода (даже код в других проектах, который вы не можете исправить самостоятельно, если это библиотека), если вы просто изменили поле с String к 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() в любом случае, с их точки зрения, ничего не меняется.Изменилось только внутреннее устройство класса, а не интерфейс, который используют другие классы.

Инкапсуляция

Методы доступа («сеттеры и геттеры») пытаются скрыть подробности о том, как хранятся данные в объекте.На практике они являются прославленным средством хранения и извлечения данных необъектно-ориентированным способом.Аксессоры ничего эффективно не инкапсулируют, поскольку практическая разница между следующими двумя фрагментами кода невелика:

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

И это:

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

Оба фрагмента кода раскрывают идею о том, что Person тесно связан с Hair.Эта тесная связь затем проявляется во всей базе кода, что приводит к нестабильности программного обеспечения.То есть становится сложно изменить способ хранения волос Человека.

Вместо:

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

Это следует за предпосылкой «Скажи, не спрашивай». Другими словами, объекты должны быть проинструктированы (другими объектами) выполнять определенную задачу.В этом вся суть объектно-ориентированного программирования.И, похоже, очень немногие люди это понимают.

Разница между этими двумя сценариями заключается в следующем:

  • В первой ситуации Боб не мог контролировать, какого цвета станут его волосы.Отлично подходит для парикмахера, который любит рыжих, но не очень подходит для Боба, который презирает этот цвет.
  • Во второй ситуации Боб имеет полный контроль над тем, какого цвета будут его волосы, поскольку ни один другой объект в системе не может изменить этот цвет без разрешения Боба.

Другой способ избежать этой проблемы — вернуть копию цвета волос Боба (как новый экземпляр), которая больше не связана с Бобом.Я считаю это неэлегантным решением, потому что это означает, что существует поведение, которого желает другой класс, используя волосы Человека, которое больше не связано с самим Человеком.Это уменьшает возможность повторного использования кода, что приводит к дублированию кода.

Скрытие типов данных

В 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, JPEG, TIFF, EPS) к общему (IMAGE, DOCUMENT, SPREADSHEET).
  • Представляет ошибки. Изменение подразумеваемого протокола не приведет к ошибке компилятора, которая может привести к ошибкам.

Полностью избегайте этой проблемы, запретив другим классам спрашивать для данных:

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 class для проверки типа.

Идея состоит в том, что если ваши клиентские классы вызывают функции get/set, вы можете изменить то, что они делают позже, и вызывающие объекты будут изолированы.Если у вас есть общедоступная переменная, и я обращаюсь к ней напрямую, у вас не будет возможности добавить поведение позже, когда она будет доступна или установлена.

Даже в вашем простом примере вы могли бы извлечь из этого больше пользы.

Вместо использования:

milesPerGallon = miles / gallons;

в расчете пробега()

Вы можете изменить setMiles() и setGallons(), чтобы обновлять мильPerGallon при их вызове.Затем удалите setMilesPerGallon(), чтобы указать, что это свойство доступно только для чтения.

Дело в том, что класс не должен разрешать прямой доступ к своим полям, поскольку это зависит от реализации.Возможно, вы захотите изменить класс позже, чтобы использовать другое хранилище данных, но оставить класс тем же для его «пользователей», или вы можете захотеть создать интерфейс, который также не может включать поля.

Взгляните на Статья в Википедии на предмет.

Они предоставляют общедоступный интерфейс для вашего класса и некоторую степень инкапсуляции.Подумайте, как вы будете получать доступ к общедоступным данным без геттеров и сеттеров.

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

Теперь, если вы решите добавить в свой класс некоторую проверку, вам придется изменить свой код везде, где к полям был прямой доступ.Если вы просто используете геттеры и сеттеры с самого начала (только там, где они нужны) вы можете избежать этих усилий и изменить свой код только в одном месте.

Использование геттеров и сеттеров дает вам возможность изменить реализацию позже.Возможно, вы думаете, что вам это не нужно, но иногда это так.Например, вы можете использовать шаблон Proxy для отложенной загрузки объекта, использование которого требует больших затрат:

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.Вы загружаете только то, что вам нужно, а затем возвращаетесь в базу данных, чтобы загрузить остальное, если/когда оно действительно используется.

В общем, сеттеры и геттеры были плохим приемом со стороны первых разработчиков графического интерфейса (borland), чтобы обойти тот факт, что все переменные должны быть частными (действительно, это абсолютно необходимо).

Некоторые люди называют их абстракцией, но это не так.Шаблонный сеттер/геттер не лучше публичного члена.Они по-прежнему разрешают полный доступ к переменной в тех случаях, когда класс не может ее контролировать, и по-прежнему ограничивают изменения внутри класса (если ваша переменная является целым числом, вам все равно придется изменить все, что вызывает методы установки и получения, чтобы изменить переменную на строку. )

Геттеры и сеттеры поощряют доступ к данным класса извне класса.Любой код, который обращается к члену класса, вероятно, должен существовать внутри этого класса (как указано в вашем проекте) и, следовательно, не должен нуждаться в методах установки или получения.Они ДОЛЖНЫ быть ненужными.

Кроме того, принудительное использование сеттера во всех ваших классах ужасно, это означает, что ваши классы просто не могут быть неизменяемыми, тогда как на самом деле у вас должна быть действительно веская причина, чтобы сделать класс изменяемым.

Тем не менее, они в некоторой степени полезны для решения сквозных задач, таких как механизмы сохранения состояния и разработчики графического интерфейса, где они могут получать и устанавливать значения, а класс может отслеживать, что было получено или изменено, а также изменять или проверять это.

Лучшим шаблоном для тех систем, которым необходим сквозной доступ к переменным, будет доступ к переменной напрямую через отражение, НО вызывать метод установки или получения, если таковой существует, - делая методы установки и получения частными, если это возможно.

Это позволит корректно работать сквозному коду, не являющемуся объектно-ориентированным, позволит вашему классу изменять наборы и методы получения, когда это необходимо, и разрешать методы получения (которые иногда действительно полезны), где это необходимо.

Точка методов доступа, т.е.геттеры и сеттеры предназначены для обеспечения инкапсуляции, сокрытия информации.Это один из основных принципов объектно-ориентированного программирования.

Методы доступа

Сокрытие/инкапсуляция информации

Ответ одним словом интерфейсы.

Интерфейсы допускают использование методов, а не полей, поэтому общепринятым соглашением является использование для этой цели методов getX и setX.

(А интерфейсы — это способ отделить функциональность от реализации на Java)

Ваш пример крайний до абсурда.Да, все эти геттеры и сеттеры раздувают код и в этом случае не добавляют никакой ценности.Но основная идея инкапсуляции предназначена для более крупных систем, состоящих из множества взаимодействующих компонентов, а не для небольших автономных программ.

Характеристики полезного и разумного использования геттеров и сеттеров:

  • Класс, который используется многими другими классами (скрытие деталей реализации упрощает работу клиентов).
  • Геттеры и сеттеры только для полей, для которых они действительно нужны — как можно меньше, большинство полей должны быть закрытыми и использоваться только внутри своего класса.
  • Очень мало сеттеров в целом:Изменяемые поля значительно усложняют отслеживание состояния программы, чем поля, доступные только для чтения.
  • Геттеры и сеттеры, которые на самом деле делать что-то помимо доступа к файлу, например.установщики, которые создают исключения для недопустимых значений или обновляют временную метку «последнего изменения», или геттер, который вычисляет значение «на лету», а не полагается на базовое поле

Перенесемся на несколько месяцев вперед.Возможно, ваш учитель просит вас реализовать удаленную версию класса Milage.Может быть, как веб-сервис, может быть, что-то еще.

Без сеттеров/сеттеров вам придется изменить каждый код повсюду, везде, который достигает сорта, с Getter/Setters, которые вы в значительной степени (по крайней мере, в идеальном мире) просто должны изменить создание типа Molate.

Геттеры и сеттеры позволяют создавать полезные ярлыки для доступа к данным внутри объекта и их изменения.Как правило, это можно рассматривать как альтернативу двум функциям с объектом, которые используются для получения и установки значения, например:

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

Добавление методов сеттера и получения, чтобы сделать состояние управляемого боба доступным, вам необходимо добавить методы сеттера и получения для этого состояния.Метод createSalutation вызывает метод bean’sgreet, а метод getSalutation получает результат.Как только методы установки и получения будут добавлены, компонент будет завершен.Окончательный код выглядит так:пакет поздравлений;

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

Инкапсуляция и возможность повторного использования кода в этом красота объектно-ориентированного программирования.Если мы имеем дело с некоторыми конфиденциальными данными в нашем коде, мы объявляем их как поля частных данных, т.е.мы инкапсулируем наши данные, чтобы никто не мог получить к ним прямой доступ. Теперь любой, кто хочет получить доступ к этим полям данных, должен использовать методы установки и получения, т.е.механизм контролируемого доступа для работы с конфиденциальными полями данных.Следующий пример может быть полезен для понимания преимуществ и важности сеттеров и геттеров.

  • Я реализовал класс, в котором использую переменную дней.
  • В моем классе никто не может установить значение дней больше 365.
  • Кто-то хочет наследовать мой класс (повторное использование кода).
  • Теперь, когда он вводит значение дней больше 365, вся функциональность моего класса будет работать со сбоем.
  • Следовательно, я должен был объявить переменную дней как личное поле данных.
  • Теперь, если бы я объявил поле данных дней как личное, никто не смог бы установить значение дней больше 365, поскольку я бы реализовал функции установки с упомянутыми ограничениями на ввод.
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top