#28 Модификаторы доступа элементов класса: public, private, protected. Инкапсуляция

Модификаторы доступа

Для элементов класса во многих языках существуют три уровня доступности:

  • public -- публичные (общедоступные элементы) элементы, напр. только с такими элементами мы имели дело в примерах первого урока по ООП.

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

  • protected - т.н. "защищенные" элементы, к ним можно обращаться из методов класса и из методов его потомков (т.е. из методов классов унаследованных от данного), но нельзя из внешнего кода
  • private -- "приватные/закрытые", доступ к ним может быть только из собственных методов класса, но ни классы-потомки ни внешний код, не имеют доступа к этим элементам.

Далее рассмотрим использование этих модификторов на примерах.

ПРИМАЧЕНИЕ:
в примерах далее мы будем использовать для protected и private т.н. строгие версии: strict protected и strict private. Это связано с тем, что в отличие от ряда других языков Паскаль "закрывает" доступ без "strict" не на уровне класса , а на уровне модуля (файла, т.е. без strict доступ даже для внешнего кода в том же файле будет открыт), но т.к. все наши примеры располагаются в одом файле, то мы используем дополнительное слово "strict", чтобы закрыть доступ даже на уровне одного файла.

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

Инкапсуляция

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

Далее мы будем рассматривать способы сокрытия элементов класса от другого кода.

Пример №1 - private: доступ для кода класса

Рассмотрим пример кода:

type
  Cat = class  // родительский класс
  public
    name: string; // поле name
    constructor create(nameValue: string);
    procedure sayHello();
  end;

constructor Cat.create(nameValue: string);
begin
 self.name := nameValue;
end;

procedure Cat.sayHello();
begin
 writeln('Привет, я '  + self.name + '!');
end;

var
  CatItem: Cat;
begin
  CatItem := Cat.create('Мурка');
  CatItem.name := 'Мурка22'; // можем заменить имя снаружи
  CatItem.sayHello();
end.

-- здесь поле name может быть изменено во внешнем, коде, как это и показано в теле программы.
Давай закроем доступ к полю name для внешенего кода, сделать это можно перенеся поле name в секцию private:

type
  Cat = class  // родительский класс
  public  // секция публичных элементов
    constructor create(nameValue: string);
    procedure sayHello();
  strict private // секция закрытых элементов
    name: string; // приватное поле
  end;

constructor Cat.create(nameValue: string);
begin
 self.name := nameValue;
end;

procedure Cat.sayHello();
begin
 writeln('Привет, я '  + self.name + '!');
end;

var
  CatItem: Cat;
begin
  CatItem := Cat.create('Мурка');
  // CatItem.name := 'Мурка22'; // не сработает!
  CatItem.sayHello();
end.

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

Пример №2 - protected для защищенных полей. Доступ только классу и наследникам

Если мы хотим дать доступ к какому-либо элементу класса только методам этого класса и еще и классам-наследникам (но при этом не давать доступ коду из других областей видимости), то мы можем использовать секцию protected, рассмотрим пример:

type
  Cat = class  // родительский класс
  public
    constructor create(nameValue: string);
    procedure sayHello();

  strict protected
    name: string;
  end;

  Tiger = class(Cat) // класс-потомок
    procedure roar(); // метод рычания
  end;

constructor Cat.create(nameValue: string);
begin
 self.name := nameValue;
end;

procedure Cat.sayHello();
begin
 writeln('Привет, я '  + self.name + '!');
end;

procedure Tiger.roar();
begin
 writeln(self.name + ': rrrrrrrrrr!');
end;

var
  CatItem: Cat;
  TigerItem: Tiger;
begin
  CatItem := Cat.create('Мурка');
  CatItem.sayHello();

  TigerItem := Tiger.create('Шархан');
  TigerItem.sayHello();
  TigerItem.roar();
//  TigerItem.name = '123'; // не сработает для защищенного поля!
end.   

Принцип минимальных необходимых прав

Когда вы выбираете какой именно доступ дать для метода или поля класса, руководствуйтесь популярным и в других областях принципом минимальных необходимых прав:

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

--т.е.:

  1. если какие-то элементы не должны использоваться наследниками или еще более отдаленным кодом - используйте private
  2. если же наследникам нужен доступ -то protected.
  3. Если же доступ к элементу необходим и из внешнего кода - тогда единственный вариант это объявлять его в открытой секции public

Решения конфликта имен с помощью модификаторов доступа

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

Видео-материалы

Задачи для самостоятельного решения

  1. Есть код:

    type
      Cat = class  // родительский класс
      public
        constructor create(nameValue: string);
        procedure sayHello();
        function getName(): string;
    
      strict protected
        name: string;
      end;
    
      Tiger = class(Cat)
      public
        function getRoarStart(): string; // вернет строку для начала рыка рыка
        procedure roar(); // само рычание
      end;
    
    constructor Cat.create(nameValue: string);
    begin
     self.name := nameValue;
    end;
    
    procedure Cat.sayHello();
    begin
     writeln('Привет, я '  + self.getName() + '!');
    end;
    
    function Cat.getName(): string;
    begin
     result := self.name;
    end;
    
    function Tiger.getRoarStart(): string;
    begin
     result := self.getName() + ':';
    end;
    
    procedure Tiger.roar();
    begin
     writeln(self.getRoarStart() + ' rrrrrrrrrr!');
    end;
    
    var
      CatItem: Cat;
      TigerItem: Tiger;
    begin
      CatItem := Cat.create('Мурка');
      CatItem.sayHello();
    
      TigerItem := Tiger.create('Шархан');
      TigerItem.sayHello();
      TigerItem.roar(); // рычит
    end.  
    

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

vedro-compota's picture

type
Cat = class // родительский класс
public
constructor create(nameValue: string);
procedure sayHello();

strict protected
name: string;
end;

Tiger = class(Cat) // класс-потомок
procedure roar();
end;

constructor Cat.create(nameValue: string);
begin
self.name := nameValue;
end;

procedure Cat.sayHello();
begin
writeln('Привет, я ' + self.name + '!');
end;

procedure Tiger.roar();
begin
writeln(self.name + ': rrrrrrrrrr!');
end;

var
CatItem: Cat;
TigerItem: Tiger;
begin
CatItem := Cat.create('Мурка');
CatItem.sayHello();

TigerItem := Tiger.create('Шархан');
TigerItem.sayHello();
TigerItem.roar();
// TigerItem.name = '123'; // не сработает для защищенного поля!
end.

_____________
матфак вгу и остальная классика =)