Фундаментальные принципы объектно-ориентированного программирования на javascript

Функции доступа (геттеры и сеттеры)

В зависимости от класса, может быть уместным (в контексте того, что делает класс) иметь возможность получать/устанавливать значения закрытым переменным-членам класса.


Функция доступа — это короткая открытая функция, задачей которой является получение или изменение значения закрытой переменной-члена класса. Например:

class MyString { private: char *m_string; // динамически выделяем строку int m_length; // используем переменную для отслеживания длины строки public: int getLength() { return m_length; } // функция доступа для получения значения m_length };

1 2 3 4 5 6 7 8 9

classMyString

{

private

char*m_string;// динамически выделяем строку

intm_length;// используем переменную для отслеживания длины строки

public

intgetLength(){returnm_length;}// функция доступа для получения значения m_length

};

Здесь getLength() является функцией доступа, которая просто возвращает значение .

Функции доступа обычно бывают двух типов:

   геттеры — это функции, которые возвращают значения закрытых переменных-членов класса;

   сеттеры — это функции, которые позволяют присваивать значения закрытым переменным-членам класса.

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

class Date { private: int m_day; int m_month; int m_year; public: int getDay() { return m_day; } // геттер для day void setDay(int day) { m_day = day; } // сеттер для day

int getMonth() { return m_month; } // геттер для month void setMonth(int month) { m_month = month; } // сеттер для month int getYear() { return m_year; } // геттер для year void setYear(int year) { m_year = year; } // сеттер для year };

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17

classDate

{

private

intm_day;

intm_month;

intm_year;

public

intgetDay(){returnm_day;}// геттер для day

voidsetDay(intday){m_day=day;}// сеттер для day

intgetMonth(){returnm_month;}// геттер для month

voidsetMonth(intmonth){m_month=month;}// сеттер для month

intgetYear(){returnm_year;}// геттер для year

voidsetYear(intyear){m_year=year;}// сеттер для year

};

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

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

Хотя иногда вы можете увидеть, что геттер возвращает неконстантную ссылку на переменную-член — этого следует избегать, так как в таком случае нарушается инкапсуляция, позволяя caller-у изменять внутреннее состояние класса вне этого же класса. Лучше, чтобы ваши геттеры использовали тип возврата по значению или по константной ссылке.

Правило: Геттеры должны использовать тип возврата по значению или по константной ссылке. Не используйте для геттеров тип возврата по неконстантной ссылке.

Примеры

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();
  }
 }
}

Программирование для интерфейса

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

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

Имеется в виду

вместо

Это также рекомендовано во многих книгах по Java, в том числе в Effective Java и Head First design pattern.

Ниже приведён пример для интерфейса в Java:

Курс по теме  Refactoring to Design Patterns».

Подробности

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

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

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

Принцип открытости/закрытости (OCP)


Соответствует букве O акронима SOLID. Принцип можно выразить так: «Классы, методы или функции должны быть открыты для расширения (добавления новой функциональности) и закрыты для модификации». Такой подход запрещает кому-либо изменять уже опробованный и протестированный код, а значит, он не ломается. В этом и состоит основное преимущество такого подхода.

Ниже приведён пример кода на Java, который нарушает этот принцип:

А вот пример после рефакторинга. Теперь соблюдается принцип открытости/закрытости: при добавлении новой реализации не нужно менять код .

Курс по теме SOLID Principles of Object-Oriented Design and Architecture.

Роль наследования

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

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

Если не пользоваться иерархиями, то для каждого объекта пришлось бы явно определять все его свойства. А если воспользоваться наследованием, то достаточно определить лишь те свойства, которые делают объект особенным в его классе. Он может также наследовать общие свойства своего родителя. Следовательно, благодаря механизму наследования один объект становится отдельным экземпляром более общего класса.

super ключевое слово

Что, если метод TreatPatient в классе Surgeon хочет выполнить функциональность, определенную в классе Doctor, а затем выполнить свою собственную специфическую функциональность? В этом случае ключевое слово может использоваться для доступа к методам родительского класса из дочернего класса. Метод TreatPatient в классе Surgeon может быть записан как:

treatPatient(){
   super.treatPatient();
     //add code specific to Surgeon
}

Ключевое слово super может использоваться для доступа к любому члену данных или методам суперкласса в подклассе.

Шаг 1) Скопируйте следующий код в редактор

public class Test{
     public static void main(String args[]){
        X x= new X();
       Y y = new  Y();
       y.m2();
      //x.m1();
      //y.m1();
     //x = y;// parent pointing to object of child
     //x.m1() ;
     //y.a=10;
   }

}
class X{
   private int a;
   int b;
      public void m1(){
       System.out.println("This is method m1 of class X");
     }
}

class Y extends X{
      int c; // new instance variable of class Y
         public void m1(){
            // overriden method
            System.out.println("This is method m1 of class Y");
        }
       public void m2(){
           super.m1();
           System.out.println("This is method m2 of class Y");
      }
}

Шаг 2) Сохранение, компиляция Запустите код. Соблюдайте вывод.

Шаг 3) Раскомментируйте строки № 6-9. Сохранить, скомпилировать Запустите код. Соблюдайте вывод.

Шаг 4) Раскомментируйте строку # 10. Сохраните и Скомпилируйте код.

Шаг 5) Ошибка =? Это потому, что подкласс не может получить доступ к закрытым членам суперкласса.

Интерфейсы

Общий интерфейс – неотъемлемая часть полиморфизма. Определить интерфейс в PHP можно двумя способами: interfaces и abstract classes. Оба имеют свою область применения, и их можно смешивать и сочетать для подгонки в свою иерархию классов так, как вы сочтете нужным.

Интерфейс

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

Интерфейс объявляется ключевым словом ‘interface‘:

PHP

interface MyInterface { // methods }

1 2 3

interfaceMyInterface{

// methods  

}

и присоединяется к классу, используя ключевое слово ‘implements‘ (множественные интерфейсы могут реализовываться путем перечисления и отделения их запятыми):

PHP

class MyClass implements MyInterface { // methods }

1 2 3

classMyClassimplementsMyInterface{

// methods  

}

Методы в интерфейсе можно определять так же, как в классе, за исключением того, что они не могут иметь содержимого (части между фигурными скобками):

PHP

interface MyInterface { public function doThis(); public function doThat(); public function setName($name); }

1 2 3 4 5

interfaceMyInterface{

publicfunctiondoThis();

publicfunctiondoThat();

publicfunctionsetName($name);

}

Все здесь определенные методы потребуется включить во все реализуемые классы точно так, как описано (прочтите внизу комментарии к коду):

PHP

//ПРАВИЛЬНО class MyClass implements MyInterface { protected $name; public function doThis() { // код, который выполняет this } public function doThat() { // код, который выполняет that } public function setName($name) { $this->name = $name; } } // НЕПРАВИЛЬНО class MyClass implements MyInterface { // не хватает doThis()! private function doThat() { // эта должна быть public! } public function setName() { // не хватает названия аргумента! } }

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25

//ПРАВИЛЬНО  

classMyClassimplementsMyInterface{

protected$name;

publicfunctiondoThis(){

// код, который выполняет this  

}

publicfunctiondoThat(){

// код, который выполняет that  

}

publicfunctionsetName($name){

$this->name=$name;

}

}

// НЕПРАВИЛЬНО  

classMyClassimplementsMyInterface{

// не хватает doThis()!  

privatefunctiondoThat(){

// эта должна быть public!  

}

publicfunctionsetName(){

// не хватает названия аргумента!  

}

}

Абстрактный класс


Абстрактный класс – это смесь интерфейса и класса. Он может определять как функциональность, так и интерфейс (в виде абстрактных методов). Классы, расширяющие абстрактный класс должны реализовывать все абстрактные методы, определенные в абстрактном классе.

Абстрактный класс объявляется таким же образом, как классы с добавлением ключевого слова ‘abstract‘:

PHP

abstract class MyAbstract { // methods }

1 2 3

abstractclassMyAbstract{

// methods  

}

и назначается классу, используя ключевое слово ‘extends‘:

PHP

class MyClass extends MyAbstract { // class methods }

1 2 3

classMyClassextendsMyAbstract{

// class methods  

}

Обычные методы могут определяться в абстрактном классе так же как в обычном классе, вместе с любыми абстрактными методами (использующими ключевое слово ‘abstract‘). Абстрактные методы ведут себя подобно методам, определяемым в интерфейсе, и должны аналогично образом реализовываться в дочерних классах.

PHP

abstract class MyAbstract { public $name; public function doThis() { // do this } abstract public function doThat(); abstract public function setName($name); }

1 2 3 4 5 6 7 8

abstractclassMyAbstract{

public$name;

publicfunctiondoThis(){

// do this  

}

abstractpublicfunctiondoThat();

abstractpublicfunctionsetName($name);

}

Классовое наследование

В классовом ООП классы являются чертежами для объектов. Объекты (или экземпляры) создаются на основе классов. Существует конструктор, который используется для создания экземпляра класса с заданными свойствами.

Например:

Здесь при помощи ключевого слова из ES6 мы создаём класс  со свойствами и , которые хранятся в . Значения свойств задаются в конструкторе, а доступ к ним осуществляется в методе .

Frontend Developer (React)

Xsolla, удалённо, 150 000 ₽

tproger.ru

Вакансии на tproger.ru

Мы создаём экземпляр класса с именем с помощью ключевого слова :

Объекты, созданные с помощью ключевого слова , изменяемы. Другими словами, изменения в классе повлияют на все объекты, являющиеся экземплярами этого класса, а также на дочерние классы, которые его расширяют ().

Для расширения класса мы можем создать другой класс. Расширим класс с помощью класса . — это с почтой и паролем:

Всё вроде бы хорошо работает, но использование классового подхода к наследованию привело к большому конструктивному недостатку: откуда пользователи класса (например, ) могут знать, что у этого класса есть свойства и и функция ? Одного взгляда на код класса недостаточно для того, чтобы сказать что-либо о его родительском классе. В итоге приходится копаться в документации или искать нужный код по всей иерархии классов.

Как говорит Дэн Абрамов:

Классовое наследование построено на создании связей через зависимости. На основе базовых классов (или суперклассов) создаются производные классы. Классовое наследование хорошо подходит для небольших и простых приложений, которые редко меняются и у которых не более одного уровня наследования (неглубокие деревья наследования позволяют избежать проблемы хрупкого базового класса) или совершенно разные сценарии использования. Однако по мере расширения иерархии такое наследование со временем будет невозможно поддерживать.

Эрик Эллиот описал, как классовое наследование может потенциально привести к провалу проекта, а в худшем случае — к провалу компании:

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

Принцип подстановки Барбары Лисков (LSP)

Соответствует букве L акронима SOLID. Согласно этому принципу подтипы должны быть заменяемыми для супертипа. Другими словами, методы или функции, работающие с суперклассом, должны иметь возможность без проблем работать также и с его подклассами.

Java-программист уровня middle-senior

Shells, удалённо, от 2000 $


tproger.ru

Вакансии на tproger.ru

LSP тесно связан с принципом единственной ответственности и принципом разделения интерфейса.

Если класс реализует больше функциональности, чем подкласс, то последний может не поддерживать некоторые функции и тем самым нарушает данный принцип.

Ниже приведён пример такого кода на Java:

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

Курс по теме SOLID Principles of Object-Oriented Design.

Подробности

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

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

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

Зачем делать переменные-члены класса закрытыми?

В качестве ответа, воспользуемся аналогией. В современной жизни мы имеем доступ ко многим электронным устройствам. К телевизору есть пульт дистанционного управления, с помощью которого можно включать/выключать телевизор. Управление автомобилем позволяет в разы быстрее передвигаться между двумя точками. С помощью фотоаппарата можно делать снимки.

Все из этих 3-х вещей используют общий шаблон: они предоставляют простой интерфейс для вас (кнопка, руль и т.д.) для выполнения определенного действия. Однако, то, как эти устройства фактически работают, скрыто от вас (как от пользователей). Для нажатия кнопки на пульте дистанционного управления вам не нужно знать, что выполняется «под капотом» пульта для взаимодействия с телевизором. Когда вы нажимаете на педаль газа в своем автомобиле, вам не нужно знать о том, как двигатель внутреннего сгорания приводит в движение колеса. Когда вы делаете снимок, вам не нужно знать, как датчики собирают свет в пиксельное изображение.

Такое разделение интерфейса и реализации чрезвычайно полезно, поскольку оно позволяет использовать объекты, без необходимости понимания их реализации. Это значительно снижает сложность использования этих устройств и значительно увеличивает их количество (устройства с которыми можно взаимодействовать).

По аналогичным причинам разделение реализации и интерфейса полезно и в программировании.

Подробности

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

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

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

Интерфейсы

Интерфейс похож на абстрактный класс: вы так же не можете объявить его экземпляр, дочерний класс должен реализовывать все члены интерфейса. Реализация членов может находиться как в интерфейсе, так и в дочернем классе.

Сходств очень много, но у интерфейсов есть особенность: один класс может наследовать несколько интерфейсов сразу.

Объявляется интерфейс следующим образом:

Также можно использовать индексаторы и события (это тема для отдельной статьи). Теперь рассмотрим применение этого интерфейса.

В отличие от абстрактных методов, методы интерфейса не нужно реализовывать с ключевым словом override.

Также есть одна особенность: метод, реализация которого находится внутри интерфейса, не может использовать этот метод — класс нужно привести к интерфейсу. Для примера добавим в класс Player следующий метод:

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

Подробности

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

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

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

Принцип разделения интерфейса (ISP)

Соответствует букве I акронима SOLID. Этот принцип подразумевает, что интерфейс, который не используется, не должен быть реализован.

В основном это происходит, когда один интерфейс содержит несколько функциональностей, и клиенту нужна только одна из них, а другие — нет.

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

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

Роль полиморфизма

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

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

Рассмотрим для примера стек, т.е. область памяти, функционирующую по принципу «последним пришел — первым обслужен». Допустим, что в программе требуются три разных типа стеков: один — для целых значений, другой — для значений с плавающей точкой, третий — для символьных значений. В данном примере алгоритм, реализующий все эти стеки, остается неизменным, несмотря на то, что в них сохраняются разнотипные данные. В языке, не являющемся объектно-ориентированным, для этой цели пришлось бы создать три разных набора стековых подпрограмм с разными именами. Но благодаря полиморфизму для реализации всех трех типов стеков в C# достаточно создать лишь один общий набор подпрограмм. Зная, как пользоваться одним стеком, вы сумеете воспользоваться и остальными.

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

Роль инкапсуляции

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

Т.е. инкапсуляция представляет собой способности языка скрывать излишние детали реализации от пользователя объекта. Например, предположим, что используется класс по имени DatabaseReader, который имеет два главных метода: Open() и Close().

Фиктивный класс DatabaseReader инкапсулирует внутренние детали нахождения, загрузки, манипуляций и закрытия файла данных. Программистам нравится инкапсуляция, поскольку этот принцип ООП упрощает кодирование. Нет необходимости беспокоиться о многочисленных строках кода, которые работают «за кулисами», чтобы реализовать функционирование класса DatabaseReader. Все, что потребуется — это создать экземпляр и отправлять ему соответствующие сообщения (например, «открыть файл по имени AutoLot.mdf, расположенный на диске С:»).

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

Основной единицей инкапсуляции в C# является класс, который определяет форму объекта. Он описывает данные, а также код, который будет ими оперировать. В C# описание класса служит для построения объектов, которые являются экземплярами класса. Следовательно, класс, по существу, представляет собой ряд схематических описаний способа построения объекта.

Код и данные, составляющие вместе класс, называют членами. Данные, определяемые классом, называют полями, или переменными экземпляра. А код, оперирующий данными, содержится в функциях-членах, самым типичным представителем которых является метод. В C# метод служит в качестве аналога подпрограммы. (К числу других функций-членов относятся свойства, события и конструкторы.) Таким образом, методы класса содержат код, воздействующий на поля, определяемые этим классом.

Используем Замыкание

const createCounter = () => {
  // Переменная, определенная в области действия конструктора
  // является приватной для этой функции. 

  let count = 0;
  return ({
    // Любые другие функции, определенные в той же области, являются привилегированными:
     // Они оба имеют доступ к закрытой переменной `count`
     // определяется в любом месте их цепочки областей видимости 
    click: () => count += 1,
    getCount: () => count.toLocaleString()
  });
};
const counter = createCounter();
counter.click();
counter.click();
counter.click();
console.log(
  counter.getCount()
);

Привилегированный метод — это метод, который имеет доступ к закрытым данным внутри области действия функции (также известной как лексическая среда). Привилегированные функции и методы имеют доступ на основе ссылок к переменным внутри функции, даже после того, как функция завершилась. Эти ссылки являются действующими, поэтому, если состояние изменяется во внутренней функции, изменения переносятся в каждую привилегированную функцию. Другими словами, когда мы вызываем counter.click(), она изменяет значение, которое видит counter.getCount().

Можно даже наследовать приватное состояние, используя функциональные миксины.

Принцип инверсии зависимостей (DIP)

Соответствует букве D акронима SOLID. Прелесть этого принципа проектирования в том, что любой класс легко тестируется с помощью фиктивного объекта и проще в обслуживании, потому что код создания объекта централизован, а клиентский код не перегружен им.

Ниже приведён пример кода Java, который нарушает принцип инверсии зависимости:

Пример демонстрирует, что зависит от . Если вам нужно использовать другой способ уведомления клиента (например push-уведомления, SMS или электронную почту), необходимо изменить класс .

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

Курс по теме Using SOLID Principles to Write Better Code — A Crash Course.

Теперь перейдём к принципам, которые не входят в пятёрку SOLID, но не менее важны.


С этим читают