Что такое модификатор доступа protected в контексте ООП?

Ответ

Модификатор protected предоставляет доступ к членам класса (полям, методам) в рамках определенной иерархии наследования и пакета. Это промежуточный уровень между private и public.

Область видимости protected-члена:

  1. Внутри самого класса, где он объявлен.
  2. Внутри любого класса, находящегося в том же пакете (как package-private).
  3. Внутри классов-наследников (подклассов), даже если они объявлены в других пакетах.

Пример и его значение для тестирования: Допустим, мы разрабатываем фреймворк для тестирования и хотим позволить пользователям создавать свои кастомные проверки, расширяя наш базовый класс.

// Базовый класс в пакете 'com.testing.framework'
package com.testing.framework;

public abstract class BaseAssertion {
    protected String actualValue; // Protected поле видно наследникам
    protected String message; // Protected поле видно наследникам

    protected BaseAssertion(String actual) {
        this.actualValue = actual;
        this.message = "Assertion failed";
    }

    // Protected метод, который могут переопределить или использовать наследники
    protected void logComparison(String expected) {
        System.out.printf("[DEBUG] Comparing: actual='%s', expected='%s'%n", actualValue, expected);
    }

    public abstract void assertEquals(String expected);
}

// Пользовательский класс-наследник в другом пакете
package com.myapp.tests;
import com.testing.framework.BaseAssertion;

public class CustomStringAssertion extends BaseAssertion {
    public CustomStringAssertion(String actual) {
        super(actual);
    }

    @Override
    public void assertEquals(String expected) {
        // Наследник имеет прямой доступ к protected-полям и методам
        logComparison(expected); // Вызов protected метода
        if (!actualValue.equals(expected)) {
            throw new AssertionError(message + ": " + actualValue + " != " + expected);
        }
    }
}

Для QA-инженера: Понимание protected важно при анализе кодовой базы, построенной на наследовании (например, в тестовых фреймворках типа TestNG или при работе с Page Object Model). Вы будете видеть, какие методы предназначены для расширения в наследниках, а какие — полностью внутренние (private).

Ответ 18+ 🔞

Давай разберем эту штуку с protected, а то звучит как какой-то секретный уровень доступа для избранных. На самом деле, всё проще, чем кажется.

Представь, что у тебя есть дом (class). У него есть гостиная (public) — туда все заходят. Спальня (private) — только для своих, посторонним вход воспрещён. А protected — это типа закрытой веранды или мастерской. Твоя семья (классы в том же пакете) туда ходит свободно. И твои дети, даже если они уже выросли и живут в другом городе (подклассы в других пакетах), тоже имеют право туда зайти, потому что они кровные родственники (наследники). А вот случайные прохожие (классы из других пакетов без родства) — нет, им туда нельзя. Вот и вся магия, ёпта.

Где эта защищённая веранда видна:

  1. Внутри самого дома (в своём классе) — очевидно.
  2. У всех соседей по подъезду (в том же пакете) — как package-private.
  3. У детей, которые разъехались (подклассы, даже в чужих пакетах). Вот это ключевое отличие от просто соседского доступа!

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

// Базовый класс в пакете 'com.testing.framework' (наша фабрика)
package com.testing.framework;

public abstract class BaseAssertion {
    protected String actualValue; // Protected поле. Наследник его увидит и сможет трогать.
    protected String message;     // Аналогично. Это не публично, но для своих — пожалуйста.

    protected BaseAssertion(String actual) {
        this.actualValue = actual;
        this.message = "Assertion failed";
    }

    // Protected метод. Наследник может его вызывать или даже переопределить.
    protected void logComparison(String expected) {
        System.out.printf("[DEBUG] Comparing: actual='%s', expected='%s'%n", actualValue, expected);
    }

    public abstract void assertEquals(String expected); // А это публичный контракт, который все должны реализовать.
}

// А вот тестировщик в своём проекте делает свой кастомный класс. Другой пакет!
package com.myapp.tests;
import com.testing.framework.BaseAssertion;

public class CustomStringAssertion extends BaseAssertion {
    public CustomStringAssertion(String actual) {
        super(actual); // Всё ок, конструктор protected? Нет, public. Но даже если бы protected — наследник бы вызвал.
    }

    @Override
    public void assertEquals(String expected) {
        // И вот здесь — внимание! — наследник имеет прямой доступ к protected-штукам родителя.
        logComparison(expected); // Может вызвать protected-метод. Овердохуище удобства!
        if (!actualValue.equals(expected)) { // Может напрямую читать protected-поле actualValue.
            throw new AssertionError(message + ": " + actualValue + " != " + expected); // И поле message тоже.
        }
    }
}

Тебе, как QA-инженеру, это надо понимать, чтобы не охуеть при чтении кода: Когда видишь protected в каком-нибудь базовом PageObject или в хелпере фреймворка (в том же TestNG или JUnit), сразу ясно — разработчик намеренно оставил тут крючок для расширения. Эти методы и поля — не для общего пользования, а для тех, кто будет наследоваться и делать свою логику. Если же метод private — всё, тупик, это внутренняя кухня, и лезть туда не стоит. А protected — это как сказать: «Чувак, если очень надо — расширь меня и используй мои наработки, но просто так с улицы не дергай». Волнение ебать, но логично.