Должны ли методы класса вызывать свои собственные методы получения и установки?


53

Где я работаю, я вижу много классов, которые делают такие вещи:

public class ClassThatCallsItsOwnGettersAndSetters {

    private String field;

    public String getField() {
        return field;
    }

    public void setField(String field) {
        this.field = field;
    }

    public void methodWithLogic() {
        setField("value");
        //do stuff
        String localField = getField();
        //do stuff with "localField"
    }
}

Если бы я написал это с нуля, я бы написал methodWithLogic()так:

public class ClassThatUsesItsOwnFields {

    private String field;

    public String getField() {
        return field;
    }

    public void setField(String field) {
        this.field = field;
    }

    public void methodWithLogic() {
        field = "value";
        //do stuff            
        //do stuff with "field"
    }
}

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

Есть ли преимущества у первого метода? Первый метод на самом деле лучше?


Когда я отлаживаю какой-то незнакомый код, кто скажет, что ошибка не является побочным эффектом в этом методе? Ваши юнит-тесты. :)
Джошуа Тейлор

1
Использование getters / setters во внутреннем коде позволяет вам создать точку останова в вашем коде, если вы проходите через отладчик. Это также позволяет вам убедиться, что если что-то не так с настройкой установки / получения значения, вы знаете, что это из-за этого метода, а не из-за какого-то недопустимого доступа. В целом, это обеспечивает большую согласованность кода.
Callyalater

Ответы:


46

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

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


Но что делать, когда вы инициализируете данные в бэкэнде и не хотите, чтобы это интерпретировалось как грязное. Если вы звонили setField()с самого начала, вы только что ввели ошибку.
Даниэль Каплан

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

3
@DanielKaplan: дело в том, что вызывать геттеры и сеттеры в большинстве случаев правильно, и только в определенных ситуациях нет. Однако в реальном коде, когда вы изменяете существующую реализацию getter / setter позже, чтобы ввести некоторые намеренные побочные эффекты, вам, скорее всего, придется проверять каждый вызов getter или setter внутри класса, а также каждый прямой доступ к поле. Вот почему вы должны стараться, чтобы ваши классы были как можно меньше.
Док Браун

33

Вызов сеттера напрямую сам по себе не является проблемой. Вопрос действительно должен быть: почему наш код везде имеет сеттеры?

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

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

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


1
Я полностью согласен / практикую эту линию мышления. Я также думаю, что вы затронули вопрос, который на самом деле важнее моего вопроса. Тем не менее, я не чувствую, что это прямо отвечает на мой первоначальный вопрос. Если вы не говорите, «в общем плане ваш вопрос не имеет значения». Что также может быть правдой :)
Даниэль Каплан

@DanielKaplan: Я говорю именно это. :) Я разрывался между ответом и комментарием с этим, но я искренне не чувствую, что есть правильный ответ, который не задает более важный вопрос.
pdr

9

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


1
Я не вижу связи между вашим первым предложением и остальной частью вашего абзаца. Первое предложение говорит, что класс должен вызывать свои собственные методы получения и установки. В оставшейся части параграфа объясняется, почему клиентский код (т. Е. Код, использующий класс) должен использовать методы получения и установки вместо прямого доступа к полям. Но я не понимаю, как по этой причине класс не должен напрямую обращаться к своим собственным полям.
Даниэль Каплан

1
@DanielKaplan Моя точка зрения заключается в том, что причины одни и те же. Что если вы добавите установочную логику позже, это повлияет на внутренний код так же (потенциально), как и на внешний.
Майкл

2
@ Майкл: У класса могут быть веские причины не использовать свои собственные методы получения / установки. Один из распространенных сценариев состоит в том, что существует множество установщиков формы. someField=newValue; refresh(); Если метод позволяет задавать несколько полей, вызов сеттеров для записи этих полей вызовет избыточные операции «обновления». Запись всех полей и последующий вызов refresh()может привести к более эффективной и гладкой работе.
суперкат

6

Чтобы ответить на ваши вопросы одним словом, да.

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

Скажем, у вас есть что-то вроде этого:

public class Vehicle
{
    private int year;
    private String make;

    public Vehicle(int year, String make)
    {
        setYear(year);
        setMake(make);
    }

    public void setYear(int year)
    {
        this.year = year;
    }

    public void setMake(String make)
    {
        this.make = make;
    }
}

Вызов сеттеров для year и make в настоящее время может не добавить никакой функциональности, но что если вы захотите добавить что-то вроде проверки входных данных для сеттеров?

public class Vehicle
{
    private int year;
    private String make;

    public Vehicle(int year, String make)
    {
        setYear(year);
        setMake(make);
    }

    public void setYear(int year)
    {
        if(year > 0)
        {
            this.year = year;
        }
        else
        {
            System.out.println(year + " is not a valid year!");
        }
    }

    public void setMake(String make)
    {
        this.make = make;
    }
}

5

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


3

Если вы пытаетесь выполнить что-то, предоставляемое общедоступным интерфейсом, используйте геттеры / сеттеры.

Тем не менее, как владелец / разработчик класса, вам разрешен доступ к закрытым разделам вашего кода (конечно, из класса), но вы также несете ответственность за уменьшение опасности.

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

public class MyClass
{
    private int i;
    private List<string> list;
    public string getNextString()
    {
        i++;
        return list[i];
    }

    private void getString()
    {
        // Do not increment
        string currentString = list[i];

        // Increment
        string nextString = getNextString();
    }
}

Можете ли вы дать обоснование для этого?
Даниэль Каплан

1

Да. Геттеры и сеттеры представляют состояние, поэтому переверните этот вопрос - хотите ли вы отслеживать несколько способов изменения состояния объектов одним и тем же способом, если нет необходимости?

Чем меньше вещей вы должны отследить, тем лучше - вот почему с неизменными объектами легче иметь дело.

Подумайте, ничто не мешает вам иметь как публичное поле, так и публичный метод получения / установки - но что бы вы получили?

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


Суть моего вопроса в том, что я спрашиваю только о прямом доступе к полям в одном классе. Я уже понимаю назначение геттеров и сеттеров для клиентского кода.
Даниэль Каплан

@DanielKaplan: Я понимаю это, и я говорю, что, насколько это практически возможно, вы должны относиться к ним одинаково.
Jmoreno

@DanielKaplan: У вас может быть один частный сеттер и один публичный сеттер, которые будут иметь одинаковый результат (все побочные эффекты одинаковы), по крайней мере, для момента, но что это даст вам, кроме возможности расхождения вещей? Разница между этим сценарием и то , что вы описываете, что вы не можете избежать в состоянии получить доступ к государственным вне геттеров / сеттеров, но вы можете избежать на самом деле сделать это. Не усложняйте свой код, если вам не нужно.
Jmoreno

1

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

Вы несете ответственность за поддержание последовательности. Сеттер делает это по определению, но не может охватывать сложные случаи.


1

Я бы сказал нет ..

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

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

Надеюсь это поможет..

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.