Изоляция данных между чистым абстрактным классом против его реализации и истинного значения чистого абстрактного класса? - PullRequest
2 голосов
/ 29 декабря 2011

В книге Бьярна Страуструпа "Язык программирования C ++" говорится, что производный класс от суперкласса в иерархии классов во многих случаях получает доступ к данным суперкласса.Книга предполагает, что это проблема, потому что совместное использование " двух связанных, но разных наборов данных вызывает проблемы. Рано или поздно кто-то получит их несинхронизированные. Кроме того, опыт показывает, что начинающие программисты склоннысвязываться с защищенными данными ненужными способами, которые вызывают проблемы с обслуживанием".Вопрос в том, как может иметь доступ проблема с частными данными из класса предков? Похоже, что наследование активов от вашего родителя - плохая сделка.

Как следствиеМеханизм чистого абстрактного класса позволяет разделить абстрактный класс и его реализацию.Обратите внимание, что реализация включает в себя члены данных, потому что, если члены данных определены в абстрактном классе, абстрактный класс содержит подробности реализации (определенные члены), таким образом, он не очень хорошо разделен между абстракцией и реализацией.Зачем нужно такое разделение?Одна из причин, как указано в здесь , заключается в том, что " является способом принудительного заключения контракта между разработчиком класса и пользователями этого класса. Пользователи этого класса должны объявить соответствующую функцию-член дляКласс для компиляции."Другая важная причина, как указано в книге, заключается в защите от изменений от реализации.Имея абстрактную иерархию классов, вы можете защитить от изменений, которые требуют компиляции всей иерархии классов.

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

Я думаю, что его пример (пример Ival_box ) в сочетании с паттерном Bridge Design Pattern является мощным, так как Bridge Pattern оставляетабстрактное дерево полностью, и есть другая иерархия классов для реализации. Чистый абстрактный класс может считаться эквивалентным Интерфейс в Java, за исключением того, что в Java объяснение обычно состоит в том, что абстрактный класс может быть унаследован один раз, в то время как интерфейс допускает множественное наследование, что позволяет инкапсулировать общие поведенияв общем интерфейсе между несколькими классами.Этот ответ, на мой взгляд, недопустим в этом контексте, поскольку C ++ допускает множественное наследование.

Последний вопрос: , как производный класс компилируется и существует в двоичном образе? компилятор заполняет унаследованную информацию в производном классе, рассматривает его как изолированный класс и затем компилирует?(Аналогично предварительной обработке)

tl; dr :

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

Является ли чистый абстрактный класс (он же Интерфейс в Java) способом защиты исходного кода от изменений с разделением дерева абстракций и дерева реализации?

Какабстрактный класс / супер класс скомпилирован в C ++?Превращает ли производный класс в один класс, заполняя информацию из суперкласса, а затем компилирует

Ответы [ 4 ]

3 голосов
/ 29 декабря 2011

Мета-примечание: Ваши вопросы можно было задавать в виде нескольких отдельных вопросов.

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

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

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

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

Кстати, "наследуется дюжиной классов с несколькими слояминиже корневого класса "обычно это признак плохого дизайна.Держите иерархии наследования плоскими и краткими.Предпочитайте членов базовым классам: если вы решили использовать функциональность класса Foo в классе Bar, и нет веских причин позволять Bar наследовать Foo, лучше используйте член типа Foo.

Этот ответ, на мой взгляд, недопустим в этом контексте, поскольку C ++ допускает множественное наследование.

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

как компилируется производный класс исуществует в двоичном образе?

Это зависит от компилятора.Пока не беспокойтесь об этом - сначала поймите язык, затем его реализацию.

1 голос
/ 29 декабря 2011

Работая с несколькими ОО-языками, я усвоил несколько вещей, которые помогают программировать на C ++. И это относится к вашему вопросу.

Это длинный, скучный вопрос, но, возможно, оно того стоит.

Конкретные классы, сначала

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

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

Итак, этот пример, включая данные и методы:


//  ....

#include <iostream>

class Employee {
  public:
    Employee();                         // constructor;

    void SayHello();
    void Work();
};

class Student {
  public:
    Student();                          // constructor;

    void SayHello();
    void Study();
};

//  ....

Становится так:


//  ....

#include <iostream>

class Person {
  public:
    Person();                         // constructor;

    virtual void SayHello() = 0;
};

class Employee {
  public:
    Employee();                         // constructor;

    virtual void SayHello();
    void Work();
};

class Student {
  public:
    Student();                         // constructor;

    virtual void SayHello();
    void Study();
};

//  ....

В этом предыдущем примере показаны только методы, а не данные, но применяется тот же принцип.

Избегайте закрытых полей или методов

Что-то, что я обычно делаю, чтобы избежать "частных" разделов.Если мне нужно спрятаться, я вместо этого использую «защищенный».

Итак, это:


//  ....

#include <iostream>
#include <cstring>

class Person {
  private:
    char[512] Name;

  public:
    Person();                         // constructor;

    virtual void SayHello() = 0;    
};

//  ....

Становится так:


//  ....

#include <iostream>
#include <cstring>

class Person {
  protected:
    char[512] Name;

  public:
    Person();                         // constructor;

    char[512] Name;

    virtual void SayHello() = 0;    
};

//  ....

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

Используйте свойства (с методами accesor) вместо простых полей данных

Что меня действительно беспокоит, так это отсутствие «реальных свойств» в «C ++» и «Java».

Один из способов доступа, изменения, управления или обновления данных из объекта, его свойств, то есть полей, принимаемых с использованием методов, называемых accesors.

Свойства могут быть реализованы в C ++, используяшаблоны или просто методы кодирования от руки.

Итак, пример прямого неизолированного поля:


//  ....

#include <iostream>
#include <cstring>

class Person {
  public:
    Person();                         // constructor;

    char[512] Name;

    virtual void SayHello() = 0;    
};

int main (...) {
  Person thisPerson = new Person();

  strcpy(thisPerson.Name, "John Doe");

  cout << thisPerson.Name << "\n";

  delete thisPerson;
}

//  ....

Становится примером изолированного свойства:


//  ....

#include <iostream>
#include <cstring>

class Person {
  protected:
    // isolated data field
    char[512] Name;

  public:
    Person();                         // constructor;

    virtual void SayHello() = 0;

    // public data field accesor reader method or "getter"
    const char* getName();

    // public data field accesor writer method or "setter"
    void setName(const char* AName);
};

const char Person::getName() {
  char char* Result = this.FName;

  return Result;
}   

void setName(const char* AName) {
  bool IsValid = false;

  // do some validation before actually modifing field
  // ...

  if (IsValid)
  {
    strcpy(this.FName, AName);
  }
}   

int main (...) {
  Person thisPerson = new Person();

      // should be read as "thisPerson.Name = "John Doe";"
  thisPerson.setName("John Doe");

      // should be read as "cout << thisPerson.Name;"
  cout << thisPerson.getName() << "\n";

  delete thisPerson;
}

//  ....

Многие разработчики на C ++ не любят "свойства" из-за отсутствияконтроля.Я думаю, что вы должны использовать для доступа к данным в качестве вашего вопроса, но, как и любая функция, должны быть «правильно использованы».

Свойства методов доступа должны быть виртуальными

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

Когда я использую свойства, я всегда использую виртуальные методы для доступа к ним.Есть скорость в памяти и скорости, но это того стоит.Свойства с виртуальными переопределенными методами позволяют контролировать и получать доступ к данным объектов.

Сводка

Ваш вопрос касается того, как реализовать данные, как изолировать данные и как получить доступданные в классе и как реализовать такие функции в дочерних классах.

Я читал аналогичные вопросы в Stackoverflow, но я пришел к выводу, что некоторые вопросы связаны с отсутствием «реальных свойств» вC ++.

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

И, возможно, захотите попытаться изучить другие ОО, где понятие «свойства»он реализован, например: VB (.NET), C #, Delphi, D или Vala.

И позже, при необходимости, попытайтесь реализовать эту концепцию в своих программах на C ++.У меня есть класс в C ++, в котором есть простые поля данных, поля, которые обрабатываются как «свойства», как в Java, с методами accesor, или смешивают оба, когда это необходимо.

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

1 голос
/ 29 декабря 2011

Как доступ к частным данным из класса-предка вызывает проблемы?

Представьте, есть ли у класса-предка определенные функции, состояние и методы, которые он поддерживает.Предположим, что определенные комбинации данных-членов недопустимы (иначе нарушают инварианты класса).Затем представьте, что дочерний класс может изменить данные предка без сохранения инвариантов.Вы попадаете в дело с недопустимым состоянием, и не только это, родитель даже не знает, что произошло, поэтому у него нет шансов вызвать исключение или каким-либо образом восстановиться.Не только это, но даже если он оказывается в действительном состоянии, класс предка может все еще быть «запутан» в отношении состояния, в котором он находится, если он был видоизменен за пределами интерфейса класса (публичный / защищенный).Является ли чистый абстрактный класс (он же Интерфейс в Java) способом защиты исходного кода от изменений с разделением дерева абстракций и дерева реализации?

Чистый абстрактный класс используется для представления концепции интерфейса.Он не защищает исходный код от изменений.Зачастую, пока интерфейс является подходящим и реализация может быть легко записана в интерфейс, клиентскому коду не нужно будет менять, хотя, возможно, придется пересобирать.Разделение интерфейса и реализации определенно желательно и может быть улучшено с помощью невиртуального интерфейса, который делегирует виртуальную реализацию pimpl (или с помощью невиртуального шаблона интерфейса).

Как абстрактный класс / супер класс компилируется в C ++?Превращает ли производный класс в один класс, заполняя информацию из суперкласса, а затем компилирует

Суперкласс рассматривается как часть производного класса.Точные детали этого могут незначительно отличаться в зависимости от реализации.

1 голос
/ 29 декабря 2011

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

Кроме того, ваше понимание того, как производный класс компилируется, является правильным.Фактически, первый компилятор C ++ был просто причудливым препроцессором C.

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

...