This lesson is not translated yet – the Russian original is below.
TODO: отвалидировать – текст собран автоматически из материалов курса на Google Drive, проверьте формулировки и код.
Клиенты не должны зависеть от интерфейсов, которые они не используют.
Лучше несколько маленьких интерфейсов, чем один огромный, заставляющий реализовывать лишние методы.
// Толстый интерфейс: принтер обязан уметь то, чего не умеет
public interface IDevice
{
void Print(string document);
void Scan(string path);
void Fax(string number);
}
public class SimplePrinter : IDevice
{
public void Print(string document) { }
public void Scan(string path) => throw new NotSupportedException();
public void Fax(string number) => throw new NotSupportedException();
}
// Разделённые интерфейсы: каждый реализует только то, что умеет
public interface IPrinter { void Print(string document); }
public interface IScanner { void Scan(string path); }
public interface IFax { void Fax(string number); }
public class SimplePrinter : IPrinter
{
public void Print(string document) { }
}
// Толстый интерфейс: принтер обязан уметь то, чего не умеет
class IDevice {
public:
virtual void Print(const std::string& document) = 0;
virtual void Scan(const std::string& path) = 0;
virtual void Fax(const std::string& number) = 0;
virtual ~IDevice() = default;
};
class SimplePrinter : public IDevice {
public:
void Print(const std::string& document) override {}
void Scan(const std::string& path) override {
throw std::logic_error("не поддерживается");
}
void Fax(const std::string& number) override {
throw std::logic_error("не поддерживается");
}
};
// Разделённые интерфейсы: каждый реализует только то, что умеет
class IPrinter {
public:
virtual void Print(const std::string& document) = 0;
virtual ~IPrinter() = default;
};
class IScanner {
public:
virtual void Scan(const std::string& path) = 0;
virtual ~IScanner() = default;
};
class SimplePrinter : public IPrinter {
public:
void Print(const std::string& document) override {}
};
// Толстый интерфейс: принтер обязан уметь то, чего не умеет
type Device interface {
Print(document string)
Scan(path string)
Fax(number string)
}
// Разделённые интерфейсы. В Go это естественный стиль:
// интерфейсы объявляет тот, кто их потребляет, и они получаются мелкими.
type Printer interface{ Print(document string) }
type Scanner interface{ Scan(path string) }
type Faxer interface{ Fax(number string) }
type SimplePrinter struct{}
func (SimplePrinter) Print(document string) {}
from typing import Protocol
# Толстый протокол: принтер обязан уметь то, чего не умеет
class Device(Protocol):
def print(self, document: str) -> None: ...
def scan(self, path: str) -> None: ...
def fax(self, number: str) -> None: ...
# Разделённые протоколы: каждый реализует только то, что умеет
class Printer(Protocol):
def print(self, document: str) -> None: ...
class Scanner(Protocol):
def scan(self, path: str) -> None: ...
class SimplePrinter:
def print(self, document: str) -> None:
pass
// Толстый интерфейс: принтер обязан уметь то, чего не умеет
interface Device {
print(document: string): void;
scan(path: string): void;
fax(number: string): void;
}
// Разделённые интерфейсы: каждый реализует только то, что умеет
interface Printer {
print(document: string): void;
}
interface Scanner {
scan(path: string): void;
}
interface Faxer {
fax(number: string): void;
}
class SimplePrinter implements Printer {
print(document: string): void {}
}
// Толстый интерфейс: принтер обязан уметь то, чего не умеет
public interface Device {
void print(String document);
void scan(String path);
void fax(String number);
}
public class SimplePrinter implements Device {
public void print(String document) { }
public void scan(String path) {
throw new UnsupportedOperationException();
}
public void fax(String number) {
throw new UnsupportedOperationException();
}
}
// Разделённые интерфейсы: каждый реализует только то, что умеет
public interface Printer { void print(String document); }
public interface Scanner { void scan(String path); }
public interface Faxer { void fax(String number); }
public class SimplePrinter implements Printer {
public void print(String document) { }
}
// Толстый интерфейс: принтер обязан уметь то, чего не умеет
interface Device {
fun print(document: String)
fun scan(path: String)
fun fax(number: String)
}
// Разделённые интерфейсы: каждый реализует только то, что умеет
fun interface Printer {
fun print(document: String)
}
fun interface Scanner {
fun scan(path: String)
}
fun interface Faxer {
fun fax(number: String)
}
class SimplePrinter : Printer {
override fun print(document: String) = Unit
}
Что даёт соблюдение
- Реализация не тащит лишние зависимости. Простому принтеру не нужны библиотеки для факса только потому, что метод оказался в общем интерфейсе.
- Моки в тестах становятся маленькими. Подменить интерфейс из одного метода – одна строка, из десяти – отдельный класс-заглушка.
- Изменение интерфейса задевает только тех, кто им пользуется. Добавили метод сканеру – принтеры даже не перекомпилируются.
- Интерфейс становится документацией. По списку из одного-двух методов сразу видно, что требуется от реализации.
Признак нарушения
Если реализация интерфейса вынуждена выбрасывать «не поддерживается» или оставлять метод пустым – интерфейс слишком широкий. Заметьте, что это тот же симптом, что и у нарушения LSP: оба принципа охраняют честность контракта.