Почему комбинирование получателей, сеттеров и модификатора частного доступа недостаточно для скрытия реализации?

В Очистить код :

public class Point {
   public double x;
   public double y;
}

Автор написал об этом классе Point():

  

Эта предоставляет реализацию. Действительно, это приведет к   даже если переменные были private , и мы использовали одну переменную    getters и сеттеры .

Это означает, что я должен записать свои школьные тетради.

Почему не объединяет геттеры, сеттеры и модификатор частного доступа?

6 голосов | спросил Billal Begueradj 24 AMpMon, 24 Apr 2017 11:22:47 +030022Monday 2017, 11:22:47

8 ответов


6

Приведенный пример класса Point с двумя открытыми членами типа double с именем x и y, раскрывает его реализацию, позволяя любому, у кого есть объект типа Point, чтобы узнать о его реализации. В этом случае раскрывается вся реализация.

Если вы изменили публичные члены на частные и создали пару геттеров и сеттеров, вы все равно выставляете свою реализацию. Если вы посмотрите на интерфейс объекта и увидите, что он имеет такие методы, как double getX(), double getY(), setX(double x) и setY(double y), тогда вы не будете сидеть там, думая: «Ну, интересно, как они это реализовали. Я не могу сказать».

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

Если мы хотим изменить внутреннее представление Point в полярные координаты, мы столкнемся с проблемами. Мы все еще можем использовать старый интерфейс, но он неуклюж. Вызов setX() должен сделать внутреннее преобразование в прямоугольные координаты, изменить x, а затем преобразовать обратно в полярное, ввести ненужные ошибки округления и дополнительные вычисления.

Если бы мы начали с разработки интерфейса для Point без ссылки на реализацию, у нас не было бы такого типа проблема. Скажем, мы решили, что точка имеет координату x и y, а также угол и величину. Мы могли бы иметь double x(), double y(), ---- +: = 15 =: + ---- и double angle(). Мы могли бы сделать их неизменными, но предположим, что мы этого не сделали. Поэтому нам нужно double magnitude() и rectangular(double x, double y) для перемещения точка.

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

Итак, в этом случае реализация не отображается, так как мы можем только догадываться, как она была реализована.

ответил Michael Shaw 6 Mayam17 2017, 05:15:09
26

Объектно-ориентированная абстракция данных - это о поведенческой абстракции . Детали реализации скрыты за поведенческим интерфейсом, то есть интерфейсом, который делает что-то .

Переменная - это не поведение, это состояние. Геттер или сеттер - это просто переопределенная переменная.

Вопрос: «что делает Point do ", not «какое у него состояние».

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

Но что, если точка была не просто «мертвым» мешком данных? Что, если у этой точки было поведение? Что, если он знал сам , как построить новую точку из себя? Например, мы можем получить новый Point, добавив вектор перемещения. Итак, мы добавляем метод add к точке, которая принимает вектор перемещения в качестве параметра (пример в Scala, но это не имеет значения ):

 class Point(val x: Double, val y: Double) {
  def +(v: Vector) = ???
}

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

Если вы знаете немного Scala, возможно, вы заметили, что Scala генерировал автоматические getters для x и y. Я чувствую, что все в порядке, потому что вы иногда do хотите работать с ними отдельно. Однако я сделал not создание сеттеров!

Если мы возьмем что-то более сложное, как интерфейс коллекции, оно станет еще более очевидным. Как вы предпочитаете перемещаться по связанному списку:

 // without abstraction:
var node = list.head
while (node != null) {
  println(node)
  node = node.next
}

// with abstraction:
list.foreach(println)

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

Существует лучший пример этого принципа в действии в оригинальной статье О понимании абстракции данных, Revisited Уильяма Р. Кука , в котором Кук объясняет фундаментальную разницу между объектно-ориентированной абстракцией данных и абстракцией данных на основе абстрактных типов данных. К сожалению, многие учебники относятся к объектам как к небольшому изменению ADT (например, «Objects = ADTs + Inheritance») или даже к одному и тому же, но они фактически принципиально разные. О критериях, которые будут использоваться в системах разложения в модули Дэвида Парнаса также по-прежнему хорошо читается, даже спустя 45 лет.

ответил Jörg W Mittag 24 PMpMon, 24 Apr 2017 13:02:27 +030002Monday 2017, 13:02:27
7

Один простой ответ на вопрос: «Почему это раскрывает реализацию?» заключается в том, что если у вас есть два приемника /сеттера, каждый из которых возвращает double и называется X и Y, вы подвергаете себя тому, что вы реализовали класс Point, используя Картезианские координаты , а не, скажем, Полярные координаты .

Кроме того, предоставление людям доступа к этим значениям X и Y может привести к их манипулированию ими при расчетах. Например; вместо использования

Point->distanceTo(otherPoint);

Они могут сделать что-то вроде:

distance = sqrt(
    pow( PointA->getX() - PointB->getX(), 2 )
  + pow( PointA->getY() - PointB->getY(), 2 )  );

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

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

ответил Erik 1 Maypm17 2017, 13:03:58
2

Я просто не согласен с этим предложением. Это зависит от семантики объекта, который вы представляете.

Ваш Point должен быть объект значения (например, целое число, ...), что-то полностью определенное его состоянием. У вас может быть две разные переменные, содержащие номер 1, но номер 1 по своей сути уникален, как и код Point (0,1). Этот класс должен быть неизменным, поэтому с getters для него X И Y отлично, а также геттеры для полярных координат R и Thêta. Внутреннее представление может быть также.

Класс, представляющий Cup, может иметь свойство, представляющее его позицию. Поскольку я отлично умею перемещать Cup произвольно, наличие геттера и сеттера в порядке. Может быть какое-то поведение, например, автоматическая настройка его Height при его удалении (когда IsInMyHand становится истинным).

С другой стороны, класс, представляющий Car, не должен иметь установщика для его позиции (если вы не моделируете большой кран, например). Это положение должно зависеть от истории его GasPedal и SteeringWheel.

Мой ответ на ваш вопрос

За упрощением?

ответил Philippe 5 Maypm17 2017, 19:41:41
1

Существует идиома для скрытия частных данных. Для этого есть разные названия: Opaque Pointer, Pirom Idiom, Cheshire Cat и т. Д. См. https: //ru .wikipedia.org /wiki /Opaque_pointer для более подробной информации.

Я прочитал об этом в первый раз в крупномасштабном программном обеспечении на C ++ Джон Лакос. Он использовал термин «изоляция» для описания практики. Изоляция сильнее, чем инкапсуляция, насколько контроль над разработчиком должен изменить детали частных данных.

Re:

  

Почему автор говорит, что комбинирования геттеров, сеттеров и модификатора частного доступа недостаточно?

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

Re:

  

, то как это сделать (скрыть детали реализации объекта)?

Я не знаю языка вашего опубликованного фрагмента кода, но на C ++ это может быть что-то вроде:

Заголовочный файл, назовите его Point.h:

 
 class Point {
   public:

      // Constuctor
      Point();

      // Destuctor
      ~Point();

      // Getter functions.
      double get_x() const;
      double get_y() const;

      // Setter functions.
      void set_x(double x);
      void set_y(double y);

      // Need to add copy constructor and copy assignment operators too.
      // Need to take care of the Rule of Three.

      // Class to hold the data
      class Data;

  private:

      // Pointer to the object holding private data
      Data* dataPtr;
};

Файл реализации, назовите его Point.cpp:

 #include "Point.h"

class Point::Data
{
   public:

      Data() : x(0), y(0) {}

      double x;
      double y;
};

Point::Point() : dataPtr(new Data()) {}

Point::~Point() { delete dataPtr; }

double Point::get_x() const { return dataPtr->x; }
double Point::get_y() const { return dataPtr->y; }

void Point::set_x(double x) { dataPtr->x = x; }
void Point::set_y(double y) { dataPtr->y = y; }

Это полностью скрывает, как хранятся личные данные Point. Одна из интересных вещей, которая возникает в результате использования этой идиомы в C ++, состоит в том, что детали Data могут быть изменены по желанию, не требуя какого-либо из файлы, которые зависят от Point.h, перекомпилируются. Это дает реальные преимущества в больших приложениях с сотнями .cpp-файлов.

ответил R Sahu 1 Mayam17 2017, 08:35:51
1

Как уже упоминалось @Erik, интерфейс раскрывает тот факт, что он реализован с использованием декартовой системы координат.

Это только теоретический аргумент, поскольку иногда нецелесообразно not создавать точки просто (x, y) по таким причинам, как мгновенное знакомство и производительность. Но, расширяя теоретический аргумент, вы можете сказать, что это функция CoordinateSystem, чтобы дать вам числовое описание местоположения определенной точки , В качестве примера можно использовать двойную отправку для преобразования точки из одной системы координат в другую , Например. в здесь .

 interface Point {
    Point convertTo(CoordinateSystem cs);
}

class CartesianPoint implements Point {
    CartesianPoint(double x, double y) {
        this.x = x;
        this.y = y;
    }

    public Point convertTo(CoordinateSystem cs) {
        return cs.convert(this);
    }

    double x;
    double y;
}

class PolarPoint implements Point {
    public PolarPoint(double angle, double radius) {
        this.angle = angle;
        this.radius = radius;
    }

    public Point convertTo(CoordinateSystem cs) {
        return cs.convert(this);
    }

    double angle;
    double radius;
}

interface CoordinateSystem {
    Point convert(CartesianPoint p);
    Point convert(PolarPoint p);

    double distance(Point p1, Point p2);
}

class CartesianCoordinateSystem implements CoordinateSystem {
    public Point convert(CartesianPoint p) {
        return p;
    }

    public Point convert(PolarPoint p) {
        double x = p.radius * Math.cos(p.angle);
        double y = p.radius * Math.sin(p.angle);
        return new CartesianPoint(x, y);
    }

    public double distance(Point p1, Point p2) {
        CartesianPoint cp1 = (CartesianPoint)p1.convertTo(this);
        CartesianPoint cp2 = (CartesianPoint)p2.convertTo(this);

        double a = cp1.x - cp2.x;
        double b = cp1.y - cp2.y;
        return Math.sqrt((a * a) + (b * b));
    }
}

class Program {
    public static void main(String[] args) {
        PolarPoint p1 = new PolarPoint(1.23, 5);
        CartesianPoint p2 = new CartesianPoint(4, 6);
        CartesianCoordinateSystem cs = new CartesianCoordinateSystem();

        CartesianPoint cp1 = (CartesianPoint)cs.convert(p1);
        System.out.println("P1=(" + cp1.x + "," + cp1.y + ")");
        System.out.println("P2=(" + p2.x + "," + p2.y + ")");
        System.out.println("DISTANCE=" + cs.distance(p1, p2));
    }
}

Это основная точка (каламбур).

ответил Yam Marcovic 6 Mayam17 2017, 09:34:06
1

Создание атрибутов класса private делает скрытие реализации и предоставление публичных функций, таких как double getX() не раскрывать детали реализации как таковые.

Проблема в том, что все ожидают, что эта функция будет реализована следующим образом:

double getX() { return this.x; }

, а не, например, следующим образом:

double getX() { return this.radius * Math.cos(this.angle); }

Название функции поддерживает определенную реализацию.

По этой причине я стараюсь избегать имен функций, начиная с get или set как можно больше.

Если вам нужно извлечь декартову X-координату точки, вы можете назвать ее следующим:

double extractCartesianX() { ... }

Это имя не означает, что X фактически является атрибутом класса. Это может быть, но это также может быть рассчитанное значение.

Примечание. Я попытался дать краткий ответ, который напрямую касается вашего вопроса. Для получения дополнительной информации и справочной информации вы должны проверить отличный ответ на @CandiedOrange.

ответил Frank Puffer 7 Maypm17 2017, 19:58:16
1

Автор какой-то книги сказал: «Это раскрывает реализацию. В самом деле, она будет раскрывать реализацию, даже если переменные были частными, и мы использовали одноразовые геттеры и сеттеры». Автор абсолютно прав. Но в этом случае ответ: «И что?»

Класс Point представляет собой точку, состоящую из двух координат двойной точности. Пользователь класса Point может считывать и назначать каждую координату отдельно. Это полностью соответствующий интерфейс - класс, представляющий математическую точку. Конечно, реализация раскрывается, но в этом случае это абсолютно нормально.

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

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

ответил gnasher729 7 Maypm17 2017, 23:24:13

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132