Введение
С абстракцией всё непросто: это единственный пункт «большой четвёрки», о котором до сих пор спорят, входит ли он в неё вообще. Когда парадигма только складывалась, концепций было три – инкапсуляция, наследование, полиморфизм. Абстракцию к ним добавили позже, уже в учебниках и на собеседованиях.
Возражение у скептиков простое: абстракция – это не механизм языка, а способ мышления. Инкапсуляцию пишут модификатором доступа, наследование – двоеточием, полиморфизм – интерфейсом или виртуальным методом. А чем пишется абстракция? Ничем конкретным: она проявляется в том, какие классы вы завели и что решили не показывать наружу. Это, кстати, мы уже разбирали в уроке про инженера – там абстракция названа главным навыком проектировщика, и никакого ключевого слова для неё не нашлось.
Тем не менее разобрать её стоит: спрашивают на собеседованиях, встречается в литературе, да и понятие полезное само по себе. Просто держите в голове, что это скорее принцип проектирования, чем механизм языка.
Определение: Абстракция – фокусировка на существенных характеристиках объекта с игнорированием деталей.
-
Видимо: кнопки выбора кофе.
-
Скрыто: механизм помола, нагрев воды.
-
Абстракция: «Что делает?»
-
Инкапсуляция: «Как защищено?»
Пример
using System;
using System.Reflection;
public class CoffeeMachine
{
private bool _coffeeIsPrepared = false;
private bool _waterIsBoiled = false;
public bool MakeCoffee()
{
GrindCoffee();
BoilWater();
return _coffeeIsPrepared && _waterIsBoiled;
}
private void GrindCoffee()
{
_coffeeIsPrepared = true;
}
private void BoilWater()
{
_waterIsBoiled = true;
}
}
class Program
{
static void Main()
{
CoffeeMachine coffeeMaker = new CoffeeMachine();
Console.WriteLine(coffeeMaker.MakeCoffee()); // Выведет: True
}
}
#include <iostream>
class CoffeeMachine {
public:
// Наружу торчит только этот метод
bool MakeCoffee() {
GrindCoffee();
BoilWater();
return coffee_is_prepared_ && water_is_boiled_;
}
private:
bool coffee_is_prepared_ = false;
bool water_is_boiled_ = false;
void GrindCoffee() { coffee_is_prepared_ = true; }
void BoilWater() { water_is_boiled_ = true; }
};
int main() {
CoffeeMachine coffee_maker;
std::cout << std::boolalpha << coffee_maker.MakeCoffee() << std::endl; // true
return 0;
}
package main
import "fmt"
// Наружу торчит только MakeCoffee: методы с маленькой буквы
// не видны за пределами пакета.
type CoffeeMachine struct {
coffeeIsPrepared bool
waterIsBoiled bool
}
func (m *CoffeeMachine) MakeCoffee() bool {
m.grindCoffee()
m.boilWater()
return m.coffeeIsPrepared && m.waterIsBoiled
}
func (m *CoffeeMachine) grindCoffee() { m.coffeeIsPrepared = true }
func (m *CoffeeMachine) boilWater() { m.waterIsBoiled = true }
func main() {
coffeeMaker := &CoffeeMachine{}
fmt.Println(coffeeMaker.MakeCoffee()) // Выведет: true
}
class CoffeeMachine:
def __init__(self):
self.__coffee_is_prepared = False
self.__water_is_boiled = False
# Наружу торчит только этот метод
def make_coffee(self):
self.__grind_coffee()
self.__boil_water()
return self.__coffee_is_prepared and self.__water_is_boiled
def __grind_coffee(self):
self.__coffee_is_prepared = True
def __boil_water(self):
self.__water_is_boiled = True
if __name__ == "__main__":
coffee_maker = CoffeeMachine()
print(coffee_maker.make_coffee()) # Выведет: True
class CoffeeMachine {
#coffeeIsPrepared = false;
#waterIsBoiled = false;
makeCoffee(): boolean {
this.#grindCoffee();
this.#boilWater();
return this.#coffeeIsPrepared && this.#waterIsBoiled;
}
#grindCoffee(): void {
this.#coffeeIsPrepared = true;
}
#boilWater(): void {
this.#waterIsBoiled = true;
}
}
const coffeeMaker = new CoffeeMachine();
console.log(coffeeMaker.makeCoffee()); // Выведет: true
public class CoffeeMachine {
private boolean coffeeIsPrepared = false;
private boolean waterIsBoiled = false;
public boolean makeCoffee() {
grindCoffee();
boilWater();
return coffeeIsPrepared && waterIsBoiled;
}
private void grindCoffee() {
coffeeIsPrepared = true;
}
private void boilWater() {
waterIsBoiled = true;
}
}
public class Program {
public static void main(String[] args) {
CoffeeMachine coffeeMaker = new CoffeeMachine();
System.out.println(coffeeMaker.makeCoffee()); // Выведет: true
}
}
class CoffeeMachine {
private var coffeeIsPrepared = false
private var waterIsBoiled = false
fun makeCoffee(): Boolean {
grindCoffee()
boilWater()
return coffeeIsPrepared && waterIsBoiled
}
private fun grindCoffee() {
coffeeIsPrepared = true
}
private fun boilWater() {
waterIsBoiled = true
}
}
fun main() {
val coffeeMaker = CoffeeMachine()
println(coffeeMaker.makeCoffee()) // Выведет: true
}
Что здесь происходит. Снаружи у кофемашины ровно одна кнопка – метод MakeCoffee. Внутри
он делает две вещи: мелет зерно и кипятит воду, и обе спрятаны в закрытых методах.
Тому, кто варит кофе, важен только результат: нажал – получил напиток. Помол, температура воды, порядок шагов – детали, от которых мы сознательно отвернулись. Их можно менять, добавлять к ним подогрев чашки или очистку – и ни одна строчка вызывающего кода об этом не узнает.
В этом и абстракция: мы оставили снаружи что машина делает, а как она это делает, убрали с глаз. Ошибиться здесь можно в обе стороны – вынести наружу лишнее (тогда чужой код начнёт зависеть от помола) или спрятать нужное (тогда кофемашиной невозможно будет пользоваться).
Четыре принципа рядом
Принципы ООП создают основу для качественного кода:
| Принцип | Преимущество | Пример |
|---|---|---|
| Наследование | Иерархии классов, повторное использование | Автомобиль → Грузовик |
| Полиморфизм | Единый интерфейс для разных объектов | Кнопка «Включить» |
| Инкапсуляция | Защита данных | Банкомат |
| Абстракция | Упрощение сложных систем | Кофемашина |