Проектирование класса
От: Sm0ke Россия ksi
Дата: 17.10.23 18:11
Оценка:
Приветики
После некоторых занятий на белке и ответов в rsdn на тему других языков я решил написать немного своих размышлений про мой любимы си++

Чем отличается структура от класса — мы знаем.
Это по сути казалось бы одно и тоже, вот только в struct изначально доступ к мемберам public, а в class — private
Тему наследования пока опустим для упрощения.

Я одно время ленился выбирать что писать class или struct в том или ином конкретном случае, и тупо начал писать везде struct в своём коде.
Думал мне так проще. Даже бывало делал всё public.

Бывает хочется скрыть data member от внешнего изменения. Для readonly доступа приходится писать get метод.
Таких свойств для скрытия когда становится много — то для каждого писать свой геттер — рутина.

Как быть?

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

Теперь добавляем в исходный класс одно свойство, чтобы поместить его в private / protected секцию.
На всё это свойство достаточно одного метода геттера, выдающего данные по константной ссылке.

struct Registrator_data
{
  double
  max_speed{},
  max_weight{},
  max_damage{};
};

class Registrator
{
  protected:

  // data
  Registrator_data
  m_data;

  public:

  // actions
  Registrator_data const &
  data() const { return m_data; }
};


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

Про friend declaration ещё хотелось бы упомянуть.
Вот не нравится мне писать friend в си++ для предоставления доступа к мемберам.
А зачем это вообще нужно? Например когда передаём this всего класса во внешнюю функцию.
Так же проще, взял да написал friend. А как иначе?

Не полениться бы и порассуждать логически.
Если внешней функции нужны скрытые параметры класса, то можно их туда передать пачкой (вместо this / отдельно от this).
Для этого вообще не нужно писать friend declaration.
Более того можно передать по константной ссылке в случае readonly доступа — через метод data(), или для полного доступа — просто m_data.

Подытожу мысль.
class — для сокрытия и для виртуальности.
struct — это просто набор свойств.

Получилось подобие мини article, а не впорос.

--

Случаи с наследованием тоже бы рассмотреть в таком ключе, по которому реплаи прошу постить отдельно от реплаев на article выше.

Как вы относитесь к наследованию у struct, или используете в нём только инкапсуляцию?
Тот же вопрос: в class -ах наследование используете?

Наследование методов от базы не всегда есть хорошо.
Есть ли способ запретить в потомке вызов из public для отдельного метода из базы?
Например оператор копирующего присваивания в базе.
* Но чтобы некоторые остальные методы из базы остались как public
Отредактировано 17.10.2023 18:18 Sm0ke . Предыдущая версия . Еще …
Отредактировано 17.10.2023 18:17 Sm0ke . Предыдущая версия .
Отредактировано 17.10.2023 18:15 Sm0ke . Предыдущая версия .
Отредактировано 17.10.2023 18:13 Sm0ke . Предыдущая версия .
Re: Проектирование класса
От: velkin Удмуртия http://blogs.rsdn.org/effective/
Дата: 17.10.23 18:52
Оценка:
Здравствуйте, Sm0ke, Вы писали:

S>Чем отличается структура от класса — мы знаем.


Думаю вопрос нужно ставить не чем отличаются структуры и классы в C++, а чем отличаются структуры в Си и структуры в C++. И просветление наступит, когда один и тот же код будет компилироваться как в Си, так и в C++. Только вот Си и C++ не так чтобы совсем уж совместимы. Суть же в парадигмах, то есть стилю мышления программиста.
Re: Проектирование класса
От: sergii.p  
Дата: 19.10.23 08:27
Оценка:
Здравствуйте, Sm0ke, Вы писали:

S>Объединяем скрытые мемберы по смыслу в общую структуру.


мне кажется, получается немного косноязычно. Также смешение функций и обычных членов не выглядит красиво
registrator.data().max_speed

Ещё это добавляет проблем при обработке в функциональном стиле:
registrators | std::views::transform(RegistratorData::data) | std::views::transform(std::mem_fn(Registrator_data::max_speed))

вместо куда более понятного
registrators | std::views::transform(RegistratorData::max_speed)

И у меня вопросы в именовании. Вот например хотим так вынести из строки size и capacity. data уже не назовёшь.

S>Если внешней функции нужны скрытые параметры класса, то можно их туда передать пачкой (вместо this / отдельно от this).


разные случаи бывают. operator== так не сделаешь, ему лишний параметр помешает пролазить в алгоритмы. Писать отдельную лямбду тоже не добавляет простоты. Поддержки каррирования нет. В общем, нет счастья.
Re[2]: Проектирование класса
От: Sm0ke Россия ksi
Дата: 19.10.23 12:15
Оценка:
Здравствуйте, sergii.p, Вы писали:

SP>Здравствуйте, Sm0ke, Вы писали:


S>>Объединяем скрытые мемберы по смыслу в общую структуру.


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

SP>
SP>registrator.data().max_speed
SP>


Можно перегрузить в классе Registrator оператор стрелка ->
Чтобы он возвращал указатель на const Registrator_data
registrator->max_speed

registrator->car_owner.name


SP>Ещё это добавляет проблем при обработке в функциональном стиле:

SP>
SP>registrators | std::views::transform(RegistratorData::data) | std::views::transform(std::mem_fn(Registrator_data::max_speed))
SP>

SP>вместо куда более понятного
SP>
SP>registrators | std::views::transform(RegistratorData::max_speed)
SP>


Можно сделать свой arrow_transform_view, который будет сперва применять оператор стрелка

resistrators |arrow_transform_view( std::mem_fn(Registrator_data::max_speed) )


SP>И у меня вопросы в именовании. Вот например хотим так вынести из строки size и capacity. data уже не назовёшь.


Можно назвать как удобно. Не обязательно именно data.
Опять же с оператором стрелка можно обойтись и без метода геттера на выдачу пачки параметров.

S>>Если внешней функции нужны скрытые параметры класса, то можно их туда передать пачкой (вместо this / отдельно от this).


SP>разные случаи бывают. operator== так не сделаешь, ему лишний параметр помешает пролазить в алгоритмы. Писать отдельную лямбду тоже не добавляет простоты. Поддержки каррирования нет. В общем, нет счастья.


Тут сперва решить вопрос что именно нужно сравнить: Весь класс, или отдельно data
Но можно же оба варианта

Если надо сравнить только struct Registrator_data отдельно, есть такая возможность задать операторы сравнения для неё
Тогда для class Ragistrator можно вывести автоматически компилятором operator <=> (/* ... */) = default;

Конечно есть нюансы:
* Когда оператор стрелка уже занят
* Когда в классе есть обычные public свойства, не помещённые в m_data (придётся помнить: когда писать точка / когда стрелка)

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

SP>... Поддержки каррирования нет.

А можно пример как вы каррируете?

p/s
короче это всё обходные пути вокруг отсутствия public readonly в плюсах
Re[2]: Проектирование класса
От: Sm0ke Россия ksi
Дата: 19.10.23 12:29
Оценка:
Здравствуйте, velkin, Вы писали:

V>Здравствуйте, Sm0ke, Вы писали:


S>>Чем отличается структура от класса — мы знаем.


V>Думаю вопрос нужно ставить не чем отличаются структуры и классы в C++, а чем отличаются структуры в Си и структуры в C++. И просветление наступит, когда один и тот же код будет компилироваться как в Си, так и в C++. Только вот Си и C++ не так чтобы совсем уж совместимы. Суть же в парадигмах, то есть стилю мышления программиста.


название темы: Проектирование класса
рассмотрен случай со скрытием свойств класса в private / protected секциях
чтобы не пришлось писать много геттеров

про си я не писал, и смотреть отличия ключевых слов struct от class собственно не конечная цель этого топика
Похоже мне надо уметь лучше формулировать постановку вопроса)

Делее предлагаю рассмотреть случай, когда методов внутри класса добавлено очень много.
Как можно сделать исходник класса более наглядным?
С учётом скрытия и без.
Re: Проектирование класса
От: rg45 СССР  
Дата: 27.10.23 17:30
Оценка: +2
Здравствуйте, Sm0ke, Вы писали:

S>Бывает хочется скрыть data member от внешнего изменения. Для readonly доступа приходится писать get метод.

S>Таких свойств для скрытия когда становится много — то для каждого писать свой геттер — рутина.

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