Инкапсуляция, полиморфизм, наследование

Краткое введение

На мой взгляд главная проблема ООП в том, что его понимают не с той стороны.

К сожалению, в XXI веке каждый день возрастает гребаная тенденция к воITи и информационное пространство просто наполняется однотипными историями, как вчера он на токарном станке вытачивал железки, а сегодня я успешный 23-летний сеньор с ПТУшным образованием на своей галере клепает убогие сайтики на Битриксе, получая золотые горы. Таким не объясняют, что такое ООП, им объясняют, что такое инкапсуляция, наследование и полиморфизм, и то, если повезет. А что за зверь такой ООП — ну и хер с ним собственно.

Регулярно видел в вакансиях требование к пониманию принципов ООП. При этом работодатель сам не особо понимает что такое ООП. Ни разу на собеседовании вообще меня не спросили, что такое ООП, если дело доходило до него, но спросили про инкапсуляцию, наследование и полиморфизм, атрибуты доступа, как это работает с наследованием и прочие технические детали целевой технологии. Всё бы ничего, но вакансии достаточно серьезные, требующие глубокого понимания программирования и алгоритмизации, а не вот это вот всё это.

Я отучился в одном из лучших факультетов моей страны и там курс по ООП заключался в том, как его использовать в рамках платформы .NET. Инкапсуляция, наследование, полиморфизм. Но ни капли настоящего ООП. Это, кстати, одна из серьезнейших проблем современного IT образования. Между прочим, до сих пор ребята, с которыми я учился на потоке, не вдупляют, как его применять этот ООП, а потом у тебя в кодяре тонны бесполезных фабрик и какие-то жуткие зависимости.

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

Собственно ООП

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

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

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

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

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

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

Этот пример не очень аккуратный и продуманный, зато полностью передает то, как из концепции ООП вытекает всё то, что так требуют на собеседованиях. Именно это понимание и позволяет следовать SOLID принципам, создавать красивую архитектуру и отделять бизнес-логику от контроллера. По сути в понимании этих простейших вещей лежит вся суть современной разработки. И даже, несмотря на то, что это просто как валенок, всё равно 60-70% разработчиков не могут прийти к этому. И причина в том, что разработчику не предлагают понимание процесса, ему предлагают инструмент.

Приватные функции

Функции, будучи внешними () по умолчанию, видимы во всей так называемой единице трансляции (). Другими словами, если несколько файлов скомпилированы вместе в один объектный файл, любой из этих файлов сможет получить доступ к любой функции из любого другого файла. Использование ключевого слова “статический” () при создании функции ограничит ее видимость до файла в котором она была определена.Следовательно, для обеспечения приватности функции необходимо выполнить несколько шагов:

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

В данном примере, в файле “private_funct.c”, была определена статическая функция . К слову, функция успешно вызывает поскольку они находятся в одном файле.

В соответствующем заголовочном файле «private_funct.h», была декларирована как статическая функция.

Основная программа, “main.c”, успешно вызывает опосредовательно через , поскольку обе функции находятся в одном документе. Тем не менее, попытка вызвать из основной программы вызовет ошибку.

Подробности

В общем случае в разных языках программирования термин «инкапсуляция» относится к одной или обеим одновременно следующим нотациям:

  • механизм языка, позволяющий ограничить доступ одних компонентов программы к другим;
  • языковая конструкция, позволяющая связать данные с методами, предназначенными для обработки этих данных.

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

Рефлексия

Я об это больно стукнулся, когда попытался найти через рефлексию конструктор, который, естественно, был помечен, как internal, у internal-класса. И оказалось, что рефлексия не выдаст ничего, что не было бы public. И это, в принципе, логично.

Во-первых, рефлексия, если я правильно помню то, что писали умные люди в умных книгах, это про поиск информации в метаданных сборки. Которые, по идее, не должны выдавать лишнего (я так думал, по крайней мере). Во-вторых, основное применение рефлексии — сделать вашу программу расширяемой. Вы предоставляете посторонним какой-то интерфейс (возможно, даже в виде интерфейсов, фить-ха!). А они его реализуют и предоставляют плагины, моды, расширения в виде загружаемой на ходу сборки, из которой рефлексия их и достаёт. И само собой, ваше API будет public. То есть, смотреть на internal через рефлексию невозможно технически и бессмысленно с практической точки зрения.

На этом можно было бы и закончить с рефлексией, но давайте вернёмся к предыдущему примеру, где A, I, B сидели на трубе:

Автор класса A решил, что ничего страшного не случится, если метод internal-класса пометить как public, чтобы компилятор не ныл, и чтобы не пришлось городить ещё кода. Интерфейс отмечен, как internal, класс, его реализующий, отмечен как internal, снаружи до метода, помеченного как public, вроде бы никак не добраться.

И тут открывает дверь и тихонько крадётся рефлексия:

Изучите этот код, вбейте его в студию, если вам так хочется. Тут мы пытаемся с помощью рефлексии найти все методы из всех типов нашей трубы (namespace Pipe). И вот какие результаты нам это даёт:

Сразу скажу, что используя объект типа MethodInfo, найденный метод можно вызвать. То есть, если рефлексия что-то нашла, то нарушить инкапсуляцию чисто теоретически можно. И у нас кое-что найдено. Во-первых, тот самый public void SomeMethod() из класса A. Это было ожидаемо, что тут ещё сказать. У этой поблажки всё-таки могут быть последствия. Во-вторых, void SomeMethod() из интерфейса I. Это уже интереснее. Как бы мы не запирались, но абстрактные методы, размещённые в интерфейсе (или что на самом деле там размещает CLR) на самом деле являются открытыми. Отсюда вывод, вынесенный в отдельный абзац:

Смотрите внимательно кому и какие объекты типа System.Type вы отдаёте.

Но тут ещё один нюанс с этими двумя найденными методами, который я хотел бы рассмотреть. Методы internal-интерфейсов и открытые методы internal-классов можно найти с помощью рефлексии. Как человек разумный, я сделаю вывод, что они попадают в метаданные. Как человек опытный, я этот вывод проверю. И в этом нам поможет ILDasm.

Беглый осмотр показывает, что в метаданные попадает всё, как бы оно ни было помечено. Рефлексия ещё заботливо от нас прячет то, что посторонним видеть не положено. Так что вполне может быть, что лишние пять строк кода на каждый метод internal-интерфейса не такое уж и большое зло. Тем не менее, главный вывод остаётся прежним:

Смотрите внимательно кому и какие объекты типа System.Type вы отдаёте.

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

Абстракция

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

  • Применяя абстракцию, мы можем отделить то, что может быть сгруппировано по какому-либо типу.
  • Часто изменяемые свойства и методы могут быть сгруппированы в отдельный тип, таким образом основной тип не будет подвергаться изменениям. Это усиливает принцип ООП: «Код должен быть открытым для Расширения, но закрытым для Изменений».
  • Абстракция упрощает представление доменных моделей.

Отличие между абстракцией и инкапсуляциейЧто такое абстрактный класс и абстрактный метод?Когда необходимо использовать абстрактный класс?Что такое интерфейс?

  1. Множественное наследование.
  2. Слабая связанность. Происходит абстракция операции, такая как разделение на уровни, а конкретной реализацией может быть что угодно: JDBC, JPA, JTA и т.д.
  3. Программа-интерфейс не реализуется.
  4. Полиморфизм с динамическим связыванием: раскрывается програмный интерфейс объекта без раскрытия его фактической реализации.
  5. Абстрактные уровни, разделение функциональностей.

Разница между интерфейсом и абстрактным классом.

  • Интерфейс — это договорные отношения с классами, которые этот интерфейс реализуют, о том, что реализация происходит путём, обозначенным интерфейсом. Это пустая оболочка с объявленными методами.
  • Абстрактный класс определяет некоторое общее поведение и просит свои подклассы определить нетипичное или конкретное поведение для своего класса.
  • Методы и члены абстрактного класса могут быть обозначены любым модификатором доступа, в свою очередь все методы интерфейса обязаны быть открытыми (public).
  • Когда происходит наследование абстрактного класса, класс-наследник должен определить абстрактные методы, в то время как интерфейс может наследовать другой интерфейс и при этом не обязательно определять его методы.
  • Класс-наследник может расширять только один абстрактный класс, а интерфейс может расширять или класс может реализовывать множество других интерфейсов.
  • Класс-наследник может определять абстрактные методы с тем же или менее ограниченным модификатором доступа, при этом класс, реализующий интерфейс, должен определять методы с тем же уровнем видимости.
  • Интерфейс не содержит конструкторы, в том время, как они есть в абстрактном классе.
  • Переменные, объявленные в Java-интерфейсе по умолчанию являются final. Абстрактный класс может содержать переменные, которые не являются final.
  • Все участники Java-интерфейса по умолчанию являются . Участники абстрактного класса могут позволить себе быть , и др.

Подробности

В общем случае в разных языках программирования термин «инкапсуляция» относится к одной или обеим одновременно следующим нотациям:

  • механизм языка, позволяющий ограничить доступ одних компонентов программы к другим;
  • языковая конструкция, позволяющая связать данные с методами, предназначенными для обработки этих данных.

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

Получение доступа к приватным функциям

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

Заключение

Инкапсуляция существует за пределами ООП языков. Современные ООП языки делают использование инкапсуляции удобным и естественным. Существует множество способов обойти инкапсуляцию и избежание сомнительных практик поможет ее сохранить как в Си, так и в Си++.

Примеры

Ada

package Stacks is
  
  type Stack_Type is private;
  
  procedure Push (Stack in out Stack_Type; Val Integer);
  
private

  type Stack_Data is array (1 .. 100) of Integer;
  
  type Stack_Type is record
    Max  Integer := 0.3;
    Data  Stack_Data;
  end record;
end Stacks;

C++

class A
{
 public
   int a, b; //данные открытого интерфейса
   int ReturnSomething(); //метод открытого интерфейса
 private
   int Aa, Ab; //скрытые данные
   void Do_Something(); //скрытый метод
};

Класс А инкапсулирует свойства Aa, Ab и метод Do_Something(), представляя внешний интерфейс ReturnSomething, a, b.

C#

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

    class NoEncapsulation
    {
        public double ValueDouble;
        public string ValueString;
    }

При этом мы можем отдельно изменять как само значение Value, так и его строковое представление, и в некоторый момент может возникнуть их несоответствие (например, в результате исключения). Пример реализации с использованием инкапсуляции:

    class EncapsulationExample
    {
        private double valueDouble;
        private string valueString;

        public double ValueDouble
        {
            get { return valueDouble; }
            set 
            {
                valueDouble = value;
                valueString = value.ToString();
            }
        }

        public string ValueString
        {
            get { return valueString; }
            set 
            {
                double tmp_value = Convert.ToDouble(value); //здесь может возникнуть исключение
                valueDouble = tmp_value;
                valueString = value;
            }
        }
    }

Здесь доступ к переменным valueDouble и valueString возможен только через свойства ValueDouble и ValueString. Если мы попытаемся присвоить свойству ValueString некорректную строку и возникнет исключение в момент конвертации, то внутренние переменные останутся в прежнем, согласованном состоянии, поскольку исключение вызывает выход из процедуры.

Delphi

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

  TMyClass = class
  private
    FMyField Integer;
    procedure SetMyField(const Value Integer);
    function GetMyField Integer;
  public
    property MyField Integer read GetMyField write SetMyField;
  end;

Для создания интерфейса доступа к скрытым полям в Delphi введены свойства.

PHP

class A
{
    private $a; // скрытое свойство
    private $b; // скрытое свойство

    private function doSomething() //скрытый метод
    {
        //actions
    }

    public function returnSomething() //открытый метод
    {
        //actions
    }
}

В этом примере закрыты свойства $a и $b для класса A с целью предотвращения повреждения этих свойств другим кодом, которому необходимо предоставить только права на чтение.

Java

class A {
 private int a;
 private int b;

 private void doSomething() { //скрытый метод
  //actions
 }

 public int getSomething() { //открытый метод
  return a;
 } 
}

JavaScript

var A = function() {
 // private
 var _property;
 var _privateMethod = function() { /* actions */ } // скрытый метод

 // public
 this.getProperty = function() { // открытый интерфейс
  return _property;
 }

 this.setProperty = function(value) { // открытый интерфейс
  _property = value;
  _privateMethod();
 }
}

или

var A = function() {
 // private
 var _property;
 var _privateMethod = function() { /* actions */ } // скрытый метод

 // public
 return {
  getProperty function() { // открытый интерфейс
   return _property;
  },
  setProperty function(value) { // открытый интерфейс
   _property = value;
   _privateMethod();
  }
 }
}

Юнит-тесты и дружественные сборки

В C++ была такая странноватая фича, как friendly classes. Классы можно было назначить друзьями, и тогда граница инкапсуляции между ними стиралась. Подозреваю, что это не самая странная фича в C++. Возможно, даже в десятку самых странных не входит. Но выстрелить себе в ногу, связав несколько классов намертво, как-то слишком уж легко, а подходящий случай под эту фичу придумать очень тяжело.

Тем удивительнее было узнать, что в .NET существуют дружественные сборки, этакое переосмысление. То есть, вы можете сделать так, чтобы одна сборка видела то, что спрятано за internal-замком в другой сборке. Я когда узнал об этом, был несколько удивлён. Ну, как бы, зачем? В чём смысл? Кто будет намертво связывать две сборки, занявшись их разделением? Случаи, когда в любой непонятной ситуации лепят public, мы в этой статье не рассматриваем.

А потом в том же проекте я начал познавать одну из ветвей пути настоящего самурая: юнит-тестирование. А по фэн-шую юнит-тесты должны лежать в отдельной сборке. По тому же фэн-шую всё, что можно спрятать внутри сборки, нужно спрятать внутри сборки. Я встал перед весьма и весьма неприятным выбором. Или тесты будут лежать рядышком и уходить клиенту вместе с полезным для него кодом, или всё покроется ключевым словом public, как долго лежавший в сырости хлебушек.

И вот тут откуда-то из закромов моей памяти было добыто что-то про дружественные сборки. Оказалось, что если у вас есть сборка «YourAssemblyName», то можно написать вот так:

И сборка «YourAssemblyName.Tests» будет видеть то, что помечено ключевым словом internal в «YourAssemblyName». Строчку эту можно вписать, чуть что, в AssemblyInfo.cs, который VS создаёт специально для хранения таких атрибутов.

Таким образом, волки остаются сыты, овцы целы, всё, что можно, по-прежнему прячется внутри сборки, юнит тесты живут в отдельной сборке, как и положено, а фича, про которую я едва вспомнил, обретает повод её использовать. Возможно, единственный существующий повод.

Чуть не забыл один важный момент. Действия атрибута InternalsVisibleTo одностороннее.

1.3 Контроль передаваемых аргументов

Иногда нужно контролировать аргументы, передаваемые в методы нашего класса. Например, наш класс описывает объект «человек» и позволяет задать дату его рождения. Мы должны проверять все передаваемые данные на их соответствие логике программы и логике нашего класса. Например, не допускать 13-й месяц, дату рождения 30 февраля и так далее.

Зачем же кому-то указывать в дате рождения 30 февраля? Во-первых, это может быть ошибка ввода данных от пользователя. Во-вторых, прежде чем программа будет работать как часы, в ней будет много ошибок. Например, возможна такая ситуация.

Программист пишет программу, которая определяет людей, у которых день рождения послезавтра. Например, сегодня 3 марта. Программа добавляет к текущему дню месяца число 2 и ищет всех, кто родился 5 марта. Вроде бы всё верно.

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

Помните, когда мы изучали и разбирали его код, и там была проверка индекса в методах и : больше или равен нулю и меньше длины массива. Там еще выбрасывалось исключение, если в массиве нет элемента с таким индексом. Это классический пример проверки входных данных.

Пролог: internal is new public

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

Случился такой проект и в моей жизни. Очередной. Причём делаю я его под добровольным надзором, где за каждой моей строчкой следят. Соответственно, уже не только хотелось, но и надо было делать всё правильно. Одним из «правильно» было «чти инкапсуляцию и закрывайся по максимуму, потому что открыться всегда успеешь, а закрыться обратно потом будет поздно». И поэтому я везде, где только мог, стал использовать для классов модификатор доступа internal вместо public. И, естественно, когда ты начинаешь активно использовать новую для тебя фичу языка, возникают некоторые нюансы. О них по порядку и хочу рассказать.

Инкапсуляция на примере

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

class Account {
    private int account_number;
    private int account_balance;

    public void show Data() {
        //code to show data 
    }

    public void deposit(int a) {
        if (a < 0) {
            //show error 
        } else
            account_balance = account_balance + a;
    }
}

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

Подход 1: Он пытается внести недействительную сумму (скажем, -100) на ваш банковский счет, манипулируя кодом.

Инкапсуляция, полиморфизм, наследование

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

Инкапсуляция, полиморфизм, наследование

Так что в нашем случае хакер не может внести сумму -100 на ваш счет.

Подход 2: Первый подход Хакера не сработал. Далее он пытается внести сумму -100, используя метод «депозит».

Но реализация метода имеет проверку на отрицательные значения. Таким образом, второй подход также терпит неудачу.

Инкапсуляция, полиморфизм, наследование

Таким образом, вы никогда не подвергаете свои данные внешней стороне. Что делает ваше приложение безопасным.

Инкапсуляция, полиморфизм, наследование

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

1.4 Минимизация ошибок при изменении кода классов

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

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

А когда мы меняем методы, объявленные как private, мы знаем, что нигде нет ни одного класса, который вызывал бы эти методы. Мы можем их переделать, поменять количество параметров и их типы, и зависимый код будет работать дальше. Ну или как минимум компилироваться.

Полиморфизм

Полиморфизм (polymorphism) (от
греческого polymorphos) — это свойство, которое
позволяет одно и то же имя использовать для
решения двух или более схожих, но технически
разных задач. Целью полиморфизма, применительно
к объектно-ориентированному программированию,
является использование одного имени для задания
общих для класса действий. Выполнение каждого
конкретного действия будет определяться типом
данных. Например для языка Си, в котором
полиморфизм поддерживается недостаточно,
нахождение абсолютной величины числа требует
трёх различных функций: abs(), labs() и fabs(). Эти
функции подсчитывают и возвращают абсолютную
величину целых, длинных целых и чисел с плавающей
точкой соответственно. В С++ каждая из этих
функций может быть названа abs().
Тип данных, который используется при вызове
функции, определяет, какая конкретная версия
функции действительно выполняется. В С++ можно
использовать одно имя функции для множества
различных действий. Это называется перегрузкой
функций (function overloading).

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

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

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

Примеры

Ada

package Stacks is
  
  type Stack_Type is private;
  
  procedure Push (Stack in out Stack_Type; Val Integer);
  
private

  type Stack_Data is array (1 .. 100) of Integer;
  
  type Stack_Type is record
    Max  Integer := 0.3;
    Data  Stack_Data;
  end record;
end Stacks;

C++

class A
{
 public
   int a, b; //данные открытого интерфейса
   int ReturnSomething(); //метод открытого интерфейса
 private
   int Aa, Ab; //скрытые данные
   void Do_Something(); //скрытый метод
};

Класс А инкапсулирует свойства Aa, Ab и метод Do_Something(), представляя внешний интерфейс ReturnSomething, a, b.

C#

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

    class NoEncapsulation
    {
        public double ValueDouble;
        public string ValueString;
    }

При этом мы можем отдельно изменять как само значение Value, так и его строковое представление, и в некоторый момент может возникнуть их несоответствие (например, в результате исключения). Пример реализации с использованием инкапсуляции:

    class EncapsulationExample
    {
        private double valueDouble;
        private string valueString;

        public double ValueDouble
        {
            get { return valueDouble; }
            set 
            {
                valueDouble = value;
                valueString = value.ToString();
            }
        }

        public string ValueString
        {
            get { return valueString; }
            set 
            {
                double tmp_value = Convert.ToDouble(value); //здесь может возникнуть исключение
                valueDouble = tmp_value;
                valueString = value;
            }
        }
    }

Здесь доступ к переменным valueDouble и valueString возможен только через свойства ValueDouble и ValueString. Если мы попытаемся присвоить свойству ValueString некорректную строку и возникнет исключение в момент конвертации, то внутренние переменные останутся в прежнем, согласованном состоянии, поскольку исключение вызывает выход из процедуры.

Delphi

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

  TMyClass = class
  private
    FMyField Integer;
    procedure SetMyField(const Value Integer);
    function GetMyField Integer;
  public
    property MyField Integer read GetMyField write SetMyField;
  end;

Для создания интерфейса доступа к скрытым полям в Delphi введены свойства.

PHP

class A
{
    private $a; // скрытое свойство
    private $b; // скрытое свойство

    private function doSomething() //скрытый метод
    {
        //actions
    }

    public function returnSomething() //открытый метод
    {
        //actions
    }
}

В этом примере закрыты свойства $a и $b для класса A с целью предотвращения повреждения этих свойств другим кодом, которому необходимо предоставить только права на чтение.

Java

class A {
 private int a;
 private int b;

 private void doSomething() { //скрытый метод
  //actions
 }

 public int getSomething() { //открытый метод
  return a;
 } 
}

JavaScript

var A = function() {
 // private
 var _property;
 var _privateMethod = function() { /* actions */ } // скрытый метод

 // public
 this.getProperty = function() { // открытый интерфейс
  return _property;
 }

 this.setProperty = function(value) { // открытый интерфейс
  _property = value;
  _privateMethod();
 }
}

или

var A = function() {
 // private
 var _property;
 var _privateMethod = function() { /* actions */ } // скрытый метод

 // public
 return {
  getProperty function() { // открытый интерфейс
   return _property;
  },
  setProperty function(value) { // открытый интерфейс
   _property = value;
   _privateMethod();
  }
 }
}