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

Изучаю php, и в некоторых источниках авторы утверждают, что хорошим тоном является ограничение доступа к свойствам класса через private/protected, а затем создания метода для изменения этих самых свойств (например setValue()). Не проще ли напрямую указывать значение свойства класса, чем писать ненужный код с методами для задания значений к этим самым свойствам?
  • Вопрос задан
  • 500 просмотров
Решения вопроса 1
Maksclub
@Maksclub Куратор тега PHP
maksfedorov.ru
У вас очень логичный вопрос на счет сеттера: сеттер полностью уничтожает задумку с инкапсуляцией. Правильно это называется "семантическое нарушение инкапсуляции" — то есть как-бы мы инкапсулировали, но по факту назад открыли прямую запись в свойство напрямую. Все доводы "ну мы можем в сеттере валидировать" не корректны

Правильным способом будет не использовать сеттеры. Вот моя статья на эту тему:
https://habr.com/ru/post/469323/

Более подробно: каждый класс нужно проектирвовать так, чтобы данные внутри были максимально связаны. Например каждый метод работать должен в хорошем случае с максимальным числом полей класса, тогда у него высокий cohesion... В тоже время снаружи наужно работать с максимальн омалым числом метода, тогда будет низкая связанность (coupling). Это пара принципов из GRASP.
Когда вы делаете сеттеры и геттеры, то у вас данные внутри между собой почти никак не взаимодействут: с геттером работают снаружи, с сеттером работаю снаружи — весь класс нараспашку, а в нем в 100% случаев появляются данные, которые вместе не должны находиться и никак не связаны — анрушена и абстракция и инвариант и много чего еще...

Про инвариант отдельно: например есть платеж, у него есть значение, с которым платеж инициирвоан (initValue), есть значение холда (holdAmount) и есть значение чарджа на списание (chargeAmount)
Когда вы работаете с платежом, контролируя ивнариант в самом классе, то ваш каждый метод првоеряет др значения и позволяет перейти к др состоянию... методов будет 2-3, все инкапсулировано и безопасно.

Например:
class Payment {
     pub func charge(amount int) void {
          if (this.holdAmount < amount && this.initAmount < amount) {
               throw new PaymentException('Unavailable charge amount')
          }

           if (this.status === PaymentStatus:finish) {
               throw new PaymentException('Payment already fisnished')
          }

          this.chargeAmount = amount
          this.holdAmount -= amount
          this.status = PaymentStatus:finish
     }
}


Тут в одном методе полные проверки и класс сам контролирует все состояние внутри, также соблюдается закон Деметры. У даннго кода высокий cohesion (из GRASP), тк внутри идет плотная работа с внутр данными (значит они корректно тут закроекны) и низкий coupling ( с теми данными для данного кейса только один метод работы, все внутри)

Когда вы раскроете сеттерами и геттерами, то ваше состояние становится непредсказуемо и полагается только на то, что снаружи точно подумали об инварианте (нет)
Ответ написан
Пригласить эксперта
Ответы на вопрос 3
Adamos
@Adamos
class VeryOpenOne
{
public $property;
}
$voo = new VeryOpenOne();
$name = 'pro' . 'perty';
$voo->$name = 'Пытаясь отрефакторить тот класс, ' .
  'ты хрен найдешь, что в этой строчке меняется эта переменная. ' .
  'Никакое самое умное IDE не поможет';
Ответ написан
gzhegow
@gzhegow
aka "ОбнимиБизнесмена"
Я когда-то задал тот же самый вопрос и нашел на него ответ.

Для начала "private/protected/public" это больше история про наследование, чем про "доступ" или про "капсуляцию". Тут просто эти три слова несколькой обязанностей выполняют.

Сразу для тех кто "ненавидит наследование" и пока не знает "почему" он его ненавидит. Существует композиция, когда в один класс на вход конструктора передается другой. Так можно "наследовать" сколько хочешь функционала неограниченно раз, и хоть несколько классов, поэтому наследование это скорее про изменение написанного функционала в будущем (к тому же наследование делать немного быстрее чем композицию, и когда предполагается с десяток классов чего-то похожего, да ещё они простые - то почему нет), а НЕ про добавление нового путем "этот будет делать всё что и родитель, но ещё вот это". Так можно, но только если очень лень делать, как "нужно".

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

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

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

Теперь про свойства.

"Публичные" свойства как во всех языках нужны, чтобы просто к ним обращаться и вписывать туда значение. Плюсы как бы - обычная переменная, меньше кода писать. Минусы? Обьекты постоянны от функции к функции. Где-то на уровне 10м-20м кто-то всунет в этот обьект то, что ты бы не хотел там видеть и оно всунется, а ты потом офигеешь, что "а че, так можно было чтоли?" - да. То есть публичные свойства стоит использовать только в каких-то может одноразовых объектах, типа DTO/ValueObject паттерн, просто потому что от "защиты" там только число кода добавится, они просто как массивы только с именованными заранее полями. Удобно писать, редактор подсказки показывает. И все равно остается опасность того что выше написано, поэтому как бы предполагается что вот дто он такой, одноразовый, и пишут в него только в начале приложения, разбирая запрос на куски, запихнул что не надо - получай.

"Защищенные/Приватные" свойства здесь выполняют почти ту же задачу, что и защищенные методы (про наследование). Дополнительная фишка - в защищенное свойство ты можешь методами написать КАК ИМЕННО ты кладешь туда данные или всобачить небольшую проверку. Не стоит увлекаться тысячей проверок - в коде утонешь, но проверку Типа - можно, почему нет. Раньше типизированных свойств не было, так это был вообще единственный способ убедится что в поле лежит нужного класса другой объект. Я вот раньше писал set()/get()/delete()/add()/put() и кучу всего пока не понял, что можно обобщить до двух set(?$items) и add($items), причем set() - вызовет add() и если туда null передать, то оно либо поставит по умолчанию либо очистит, то есть ещё и delete() выполняет когда нужно.

Ещё кое-что про свойства. Стоит помнить, что в свойства можно закинуть любую информацию, в том числе скаляры. Скаляры это те типы, что копируются каждый раз, когда ты их присваиваешь в другую переменную потом. То есть увлекаться большим числом свойств никогда нельзя, это чревато тем, что при копировании обьектов ты скопируешь все данные внутри, внезапно увеличив потребление ресурсов компьютера. Конечно, на практике так очень редко бывает, что кто-то в свойство положит массив на 100 тысяч свойств, а потом клонирует обьект случайно в цикле десяток раз... Но где минус там и плюс - если объект не копировать - это пачка данных, которые хранятся один раз, а работают потом везде, так сказать общее хранилище.

Это не значит что есть норма "сколько", это значит что нужно понимать, что в свойствах большой обьем желательно не копить. Все поля имеющие "массив по-умолчанию" можно сделать методами, тогда вызов метода даст тебе данные в одной функции, а по завершению - их вычистит. А положить их в свойства и запомнить навсегда (это же удобно! а ещё быстро считывается!) - это печально.
Ответ написан
Комментировать
@Frayl
нет, не проще.
С помощью методов можно задать критерии для изменения этих самых свойств.
Допустим есть класс:

class Entity {

    private static array $ids = [];

    private static int $currentId = 0;
    
    private int $id;

    public function __construct() {
        $this->id = self::$currentId++;
        self::$ids[] = $this->id;
    }
    
    public function getId(): int {
        return $this->id;
    }

    public function setId(int $id): bool {
        if ($id < 0 || isset(self::$ids[$id])) {
            return false;
        }

        unset(self::$ids[$this->id]);
        self::$ids[] = $id;
        $this->id = $id;

        return true;
    }

}


Вы можете легко оперировать данными и делать им разные проверки.
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы