Что такое инкапсуляция?

Программирование

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

Я хочу, чтобы Вы кое о чем подумали:

Это два варианта реализации класса TimeInterval:

public class TimeInterval {
    public Date start;
    public Date end;

    public TimeInterval(Date start, Date end) {
        this.start = start;
        this.end = end;
    }
}


public class TimeInterval {
    public Date start;
    public Date end;

    public TimeInterval(Date start, Date end) {
        this.start = start;
        this.end = end;
    }
}


public class TimeInterval {
    private Date start;
    private Date end;

    public TimeInterval(Date start, Date end) {
        this.start = start;
        this.end = end;
    }

    public Date getStart() {
        return start;
    }

    public void setStart(Date start) {
        this.start = start;
    }

    public Date getEnd() {
        return end;
    }

    public void setEnd(Date end) {
        this.end = end;
    }
}

public class TimeInterval {
    private Date start;
    private Date end;

    public TimeInterval(Date start, Date end) {
        this.start = start;
        this.end = end;
    }

    public Date getStart() {
        return start;
    }

    public void setStart(Date start) {
        this.start = start;
    }

    public Date getEnd() {
        return end;
    }

    public void setEnd(Date end) {
        this.end = end;
    }
}

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

А вот фиг вам! Ни черта это не инкапсуляция!
Вот подумайте. Если для нас важно чтобы start <= end, то какая вообще разница, меняем мы значение переменной start через сеттер или напрямую?

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

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

 

Так что такое инкапсуляция?

Это механизм позволяющий нам обеспечить согласованность данных объекта или модуля, от приведения их в некорректное состояние. Обычно для этого прячут все поля и предоставляют только простой набор методов. Как я уже говорил, нам не обязательно даже прятать всё и вся. Достаточно просто запретить “контрактом” или документацией использовать такую возможность. Существуют языки в которых нет спецификаторов доступа (private, protected, public), но в них успешно используют инкапсуляцию.

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

 

Чем плох данный пример (Код 2)?

 

Просто мы можем очень легко привести TimeInterval в некорректное состояние (рассогласовать start и end). Для этого достаточно в любой сеттер передать дату которая не будет соответствовать другой. Мы могли бы, конечно, прикрутить валидацию в сеттеры и это немного улучшило бы ситуацию. Но стало бы неудобно использовать данный класс.

Я бы сделал так:

public class TimeInterval {
    private Date start;
    private Date end;

    public TimeInterval(Date start, Date end) {
        setInterval(start, end);
    }

    public TimeInterval(Date start, long durationInMillis) {
        setInterval(start, durationInMillis);
    }

    private void setInterval(Date start, long durationInMillis) {
        // validate start and durationInMillis
        this.start = start;
        this.end = new Date(start.getTime() + durationInMillis);
    }

    private void setInterval(Date start, Date end) {
        // validate start and end
        this.start = start;
        this.end = end;
    }

    public Date getStart() {
        return start;
    }

    public Date getEnd() {
        return end;
    }
}

public class TimeInterval {
    private Date start;
    private Date end;

    public TimeInterval(Date start, Date end) {
        setInterval(start, end);
    }

    public TimeInterval(Date start, long durationInMillis) {
        setInterval(start, durationInMillis);
    }

    private void setInterval(Date start, long durationInMillis) {
        // validate start and durationInMillis
        this.start = start;
        this.end = new Date(start.getTime() + durationInMillis);
    }

    private void setInterval(Date start, Date end) {
        // validate start and end
        this.start = start;
        this.end = end;
    }

    public Date getStart() {
        return start;
    }

    public Date getEnd() {
        return end;
    }
}

Теперь нам гораздо сложнее рассогласовать переменные start и end. Приведу еще один пример из жизни:

public class Car {
    public void startMoving() {
        turnKey();
        startEngine();
        warmEngine();
        removeParkingBrake();
        pressGas();
    }

    private void pressGas() {}

    public void removeParkingBrake() {}

    private void warmEngine() {}

    private void startEngine() {}

    private void turnKey() {}
}

public class Car {
    public void startMoving() {
        turnKey();
        startEngine();
        warmEngine();
        removeParkingBrake();
        pressGas();
    }

    private void pressGas() {}

    public void removeParkingBrake() {}

    private void warmEngine() {}

    private void startEngine() {}

    private void turnKey() {}
}

В классе Car нельзя делать метод removeParkingBrake публичным, потому что машина покатится и врежется в столб, и выйдет из строя. Нельзя разрешать использовать этим методом. А если у пользователя будет возможность вызывать те методы, что находятся в startMoving, но напрямую, то это тоже плохо. Он может забыть прогреть двигатель и в итоге испортит его. Это все утечка внутренней логики работы класса. Нарушение инкапсуляции.

Смотрите метод startMoving. Первоначально задумывалось, что пользователь может использовать только startMoving. В таком случае ничего плохого не произойдет.

 

Зачем же нам нужна инкапсуляция?

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

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

Когда Вы работаете один на проекте, то Вы свой код обычно держите в голове. И, скорее всего, знаете все нюансы использования Ваших компонентов. Но в большой команде, где каждый занимается своим модулем, для Вас другие модули как черный ящик. Если на Ваш вопрос о инкапсуляции к таким разработчикам, слышите “Инкапсуляция?! Не не слышал”, то будьте уверены – прикрутив новый функционал, Вы сломаете все приложение.

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

 

Преимущества и недостатки использования инкапсуляции?

Преимущества:

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

 

Недостатки:

  1. Если Вы нашли ошибку в библиотеке которую используете, то Вам будет трудно ее исправить.
  2. Снижается скорость работы приложения.

Но последний пункт, думаю, вообще брать нет смысла, с учетом мощностей которые теперь доступны. И плюсов которые она предоставляет.

 

Почему же задают такой простой вопрос на собеседовании?

 

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

Это как лакмусовая бумажка. И это лично мое мнение.

На этом все.

Оцените статью
0 0 голоса
Рейтинг статьи
Подписаться
Уведомить о
guest

3 комментариев
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии
Алиса
Алиса
3 лет назад

Инкапсуляция ( англ. encapsulation, от лат. in capsula ) — в информатике размещение в одном компоненте данных и методов, которые с ними работают. Также может означать скрытие внутренней реализации от других компонентов. Например, доступ к скрытой переменной может предоставляться не напрямую, а с помощью методов для чтения ( геттер ) и изменения ( сеттер ) её значения.

Благоустройство могил
Благоустройство могил
2 лет назад

Слово “инкапсуляция” происходит от латинского in capsula — “размещение в оболочке”. Таким образом, инкапсуляцию можно интуитивно понимать как изоляцию, закрытие чего-либо инородного с целью исключения влияния на окружающее, обеспечение доступности главного, выделение основного содержания путём помещения всего мешающего, второстепенного в некую условную капсулу ( чёрный ящик ).

вавада казино сайт
вавада казино сайт
1 год назад

Подлинное назначение инкапсуляции — собрать в одном месте знания, относящиеся к устройству некой сущности, правилам обращения и операциям с ней. Инкапсуляция появилась гораздо раньше, чем принято думать. Модули в программах на C — это инкапсуляция. Подпрограммы на ассемблере — это инкапсуляция. Противоположность инкапсуляции — размазывание знаний о функционировании чего-либо по всей программе. При реализации поведения мы иногда выбираем из нескольких равноценных вариантов. В будущем вес одного из отвергнутых вариантов может возрасти под влиянием изменившихся обстоятельств, выбранный и реализованный ранее вариант станет неудачным. Изменить принятое решение будет гораздо легче, если абсолютно все детали реализации выбранного варианта сосредоточены в одном месте. Пример: работа с денежными величинами. Не секрет, что во многих e-commerce системах денежные величины реализованы в виде чисел с плавающей запятой. Думаю, все из нас в курсе, что при простом сложении двух «целых» чисел, представленных в виде переменных с плавающих запятой, может образоваться «немного не целое число». Поэтому при такой реализации там и тут приходится вставлять вызов функции округления. Это и есть размазывание знаний об устройстве сущности по всей программе. Инкапсуляция в данном случае — собрать (спрятать) в одном месте знание о том, что деньги представлены в виде величины с плавающей запятой, и что её постоянно приходится округлять при самых невинных операциях. Спрятать так, чтобы при использовании сущности «деньги» речь об округлении даже не заходила. При инкапсуляции не будет никаких проблем заменить реализацию «денег» с числа с плавающей на число с фиксированной запятой. Яндекс открывает датасеты Беспилотных автомобилей, Погоды и Переводчика, чтобы помочь решить проблему сдвига данных в ML

3
0
Оставьте комментарий! Напишите, что думаете по поводу статьи.x