Как принудительно использовать метод setter без использования private ? (общедоступный для чтения, закрытый для записи)

У меня есть тип объекта класса Circle, для которого у меня есть член 'r' для хранения радиуса. Теперь, поскольку я часто использую эти объекты, я не хочу делать радиус приватным, потому что другим классам пришлось бы использовать метод getR(), и это затруднило бы чтение кода. Например, намного легче читать такой код: double alpha = b1.r + b2.r -2*b1.r*b2.r; чем читать это: двойная альфа = b1.getR() + b2.getR() - 2*b1.getR()*b2.getR();

Тем не менее, поскольку это радиус, и его квадрат также часто используется, и я хочу вычислить его один раз, мне бы хотелось иметь установщик для радиуса, чтобы я мог обновлять как r, так и его квадрат одновременно. Поэтому я хочу, чтобы r был закрытым, чтобы принудительно использовать setR(), но я хочу, чтобы он был общедоступным, чтобы на него можно было ссылаться для чтения. Кто-нибудь знает, как это сделать на Java?


person jdbertron    schedule 10.03.2013    source источник
comment
Я бы предложил пойти с геттером/сеттером и включить комментарий сверху, который дает предполагаемый расчет, т.е. b1.r + b2.r -2*b1.r*b2.r Таким образом, вы не пожертвуете читабельностью. Хорошей практикой является сохранение конфиденциальности ваших атрибутов и использование средств доступа для чтения и записи.   -  person Rohit    schedule 10.03.2013
comment
Вы можете перейти на Scala   -  person Antimony    schedule 10.03.2013


Ответы (4)


В Java нет понятия свойств, доступных только для чтения.

Если вы позволите r быть читаемым, кто-то может и изменит его случайным образом. Кроме того, это вредная привычка. Просто не надо. Длинный пример: что, если вам нужно, чтобы это функционировало в каком-то странном неевклидовом пространстве? Теперь вам нужно развернуть беспорядок, чтобы исправить это.

Реалистичные варианты:

Вы можете отклониться от нормы и назвать геттер r(). Почти такой же короткий.

Или вы можете поместить такую ​​математику в класс Circle, где она может напрямую обращаться к своим членам. Вы получаете лучшую часть инкапсуляции с более короткими именами.

class Circle() {
   static double alpha(Circle c1, Circle c2) {
      return c1.r + c2.r -2*c1.r*c2.r
   }
   ...
}
person James    schedule 10.03.2013
comment
ЯГНИ. В том редком случае, когда вам нужно неевклидово пространство, вам, вероятно, все равно придется внести гораздо большие изменения в кодовую базу. - person Antimony; 10.03.2013
comment
Согласитесь, ужасный пример. Дело в том, что нарушать инкапсуляцию — плохая привычка, потому что вы чувствуете лень и хотите сохранить несколько символов. Особенно с учетом явного требования не допускать произвольных изменений r. Я недостаточно знаю, что делает jdberton, чтобы привести реальный пример того, как это может привести к тому, что позже они укусят их за задницу. Можете ли вы избежать наказания за тривиальные вещи, к которым вы никогда не собираетесь добавлять? Наверное. Не вернется ли она к вам позже, когда вы привнесете эту вредную привычку в более крупный проект? Наверное также. - person James; 10.03.2013
comment
Это действительно проблема, только если это часть общедоступного API. В противном случае вы можете легко провести рефакторинг по мере необходимости. - person Antimony; 10.03.2013
comment
Как только вы сделаете r общедоступным и позволите кому-то еще прикасаться к нему, вы фактически сделаете его частью своего API. Если у вас есть контроль над всем касающимся его кодом, конечно, вы можете изменить его по мере необходимости, но чем дальше вы позволите утечке, тем больше кода вам придется затронуть для рефакторинга. - person James; 10.03.2013
comment
Я думаю, что Джеймс прав. Это определенно плохой выбор, но на самом деле это не спасение персонажей. Это сделано для того, чтобы сделать несколько других математических строк намного проще для чтения и понимания, сейчас и через 5 лет, когда я снова посмотрю на это. По той же причине я использую греческие переменные (вы можете сделать это с помощью eclipse), чтобы это было понятнее и соответствовало исследовательской работе. Это, я думаю, делает гораздо больше для защиты моего проекта от коварных ошибок. Итак, сейчас я воспользуюсь геттером r() и сделаю r закрытым. - person jdbertron; 20.03.2013

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

б). Если удобочитаемость является вашей проблемой, я предлагаю получить радиус с помощью метода getR() и сохранить в локальных переменных. например - int firstCircleRadius = first.getR() и аналогично для других кругов, и сделайте формулу легко читаемой, используя ваши локальные переменные.

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

person John Jai    schedule 10.03.2013

Лично я бы использовал геттеры и сеттеры. Однако вы можете сохранить реальный радиус в частном члене, а затем использовать метод setR() для обновления фиктивного радиуса r. Это не решает проблему возможности изменить r вручную.

private int radius;
public int r;
public void setR(int r) {
    radius = r;
    square(); //do you squaring and other math
    this.r = r;
}
person Breavyn    schedule 10.03.2013

Недавно я был обожжен этой проблемой, и, рискуя получить кучу отрицательных голосов и гнев сообщества Java, я бы посоветовал НЕ полагаться на доступ к Java - в переменной класса или методе (или то, что я раньше думал, что "private" для инкапсуляции в объектно-ориентированном языке программирования. Просто используйте public для всего и спокойно спать по ночам. Вот объяснение. Рассмотрим следующий класс:

package hiddenscope;

public class Privacy {
    private int privateInt = 12;
    private double oneBy2pi = 0.5 / Math.PI;
    private String myClassName;

    public Privacy () { 
        privateInt = 12;
        myClassName = this.getClass().getName();
        print ("Constructor");
    }

    // Given the circumference of a circle, get its radius
    public double c2r (double c) {
        return c * oneBy2pi;
    }

    // Do not let any other class call this method
    private void print (String caller) {
        System.out.println ("["+caller+"] Current value of privateInt in class " + 
                                                myClassName + " is " + privateInt);
    }
}

Вот класс, который изменяет приватную переменную privateInt вышеприведенного класса и вызывает ее приватным методом «печати» даже из-за пределов пакета (защищено, кто-нибудь?):

package userpackage;
import hiddenscope.Privacy;

import java.lang.reflect.*;


public class EditPrivate {

    private int i2set;
    private String fieldNameToModify, myClassName;
    private Object p;
    private Method printer;

    public EditPrivate (Object object, String fName, int value) {
        fieldNameToModify = fName;
        i2set = value;
        p = object;
        myClassName = this.getClass().getName();
        Method[] methods = p.getClass().getDeclaredMethods();
        for (Method m : methods) {
            if (m.getName().equals("print")) {
                printer = m;
                printer.setAccessible(true);
                break;
        }}

        if (printer == null)
            return;

        try {
            printer.invoke (p, myClassName);
        } catch (IllegalAccessException ex1)        {   ex1.printStackTrace();
        } catch (InvocationTargetException ex2)     {   ex2.printStackTrace();
    }}

    public void invadePrivacy () throws Exception {
        Field[] fields = p.getClass().getDeclaredFields();
        for (Field field : fields) {
            String fieldName = field.getName();
            if (fieldName.equals(fieldNameToModify)) {
                field.setAccessible(true);
                if ("int".equals(field.getType().toString())) {
                    try {
                        field.setInt(p, i2set);
                        if (printer != null)
                            printer.invoke (p, myClassName);
                        return;
                    } catch (IllegalAccessException ex) {
                        ex.printStackTrace();
        }}}}
        throw new Exception ("No such int field: " + fieldNameToModify);
    }

    public static void main(String[] args) {
        try {
            Privacy p = new Privacy();
            new EditPrivate(p, "privateInt", 15).invadePrivacy();
        } catch (Exception ex) {
            ex.printStackTrace();
}}}

Чтобы усугубить эту травму, вот что происходит, когда несколько потоков искажают внутренние и частные переменные класса (предполагается, что oneBy2pi является закрытым!)

package userpackage;
import java.lang.reflect.Field;
import java.util.concurrent.*;
import java.util.*;

import hiddenscope.*;

public class ThreadedPi implements Runnable {

    Privacy p = new Privacy();
    private int nSample = 2000, bins[] = new int[4]; // 94814117 999065 0003379, 2028859
    private Field c2dField;
    private boolean mutate;

    public ThreadedPi () {
        Field[] fields = p.getClass().getDeclaredFields();
        for (Field field : fields) {
            String fieldName = field.getName();
            if (fieldName.equals("oneBy2pi")) {
                field.setAccessible(true);
                c2dField = field;
    }}}

    synchronized private boolean setMutationOfField (boolean master, boolean value) {
        if (master)
            mutate = value;
        return mutate;
    }

    public void run () {
        Random rng = new Random();
        for ( ; ; ) {
            if (setMutationOfField (false, true)) {
                int nDigit = 2 + rng.nextInt(4);
                double doublePi = 4.0 * Math.atan(1.0),
                        decimal = Math.pow(10, nDigit),
                        apprxPi = Math.round(decimal * doublePi) / decimal;
                try {
                    c2dField.setDouble (p, 0.5/apprxPi);
                } catch (IllegalAccessException ex) {
                    ex.printStackTrace();
            }} else {
                return;
    }}}

    public void execute () {
        setMutationOfField (true, true);
        new Thread (this).start();
        Executed[] class2x = new Executed[nSample];
        ExecutorService executor = Executors.newCachedThreadPool();
        for (int i = 0 ; i < 4 ; i++)
            bins[i] = 0;
        for (int i = 0 ; i < nSample ; i++) {
            class2x[i] = new Executed();
            executor.execute(class2x[i]);
            double d = class2x[i].getDiameter(-1);
            if (d < 399.99)         bins[0]++;
            else if (d < 400)       bins[1]++;
            else if (d < 400.01)    bins[2]++;
            else                    bins[3]++;
            //System.out.println ("d: " + d);
        }
        setMutationOfField (true, false);
        for (int i = 0 ; i < 4 ; i++)
            System.out.print("\t\t[" + (i+1) + "] " + bins[i]);
        System.out.println ();
    }

    public static void main(String[] args) {
        ThreadedPi tp = new ThreadedPi();
        for (int k = 0 ; k < 5 ; k++)
            tp.execute();
    }

    private class Executed implements Runnable {
        private double d=-1, c = 2513.274123;

        synchronized double getDiameter (double setter) {
            if (setter < 0) {
                while (d < 0) {
                    try {
                        wait();
                    } catch (InterruptedException ex) {
            }}} else {
                d = setter;
                notify();
            }
            return d;
        }

        public void run () {
            getDiameter (p.c2d(c));
}}}

Все вышеперечисленные классы прекрасно компилируются и работают, что объясняет, почему «приватный» может не иметь никакого значения в java. Подними щиты, Скотти!

person Manidip Sengupta    schedule 10.03.2013
comment
Ну, конечно, вы можете делать что угодно с SetAccessible. Вы можете сделать то же самое с собственным кодом. Не следует ожидать, что языковые инварианты будут применяться к механизмам, специально разработанным для их обхода. Если вы беспокоитесь о безопасности, вам нужно использовать SecurityManagers. - person Antimony; 10.03.2013
comment
Дело не в моих ожиданиях, и я не использовал ничего низкоуровневого в приведенном выше коде — эти функции либо являются частью Java, либо нет. К сожалению, похоже, что они есть, и вокруг есть фреймворки, которые активно это используют. Согласитесь, это явное нарушение базовой ООП-парадигмы инкапсуляции, и нам приходится с этим смириться. - person Manidip Sengupta; 11.03.2013
comment
Вы вызываете функции Java, но эти функции в конечном итоге вызывают собственный код. Следовательно, они вольны делать все, что хотят (в данном случае нарушать инкапсуляцию). - person Antimony; 11.03.2013