-
Статический полиморфизм
-
Замена виртуальных функций при компиляции
-
Подходит только там, где это возможно
template<class Derived>
class A
{
public:
void method()
{
static_cast<Derived*>(this)->some_method();
}
};
class B: A<B>
{
public:
void some_method()
{
lala;
}
};
-
Необходимость проинициализировать базовый класс сложным образом
-
Нет доступа к переменным класса, так как он ещё не проинициализирован
class A: public vector<int>
{
public:
A(): vector(init())
{}
private:
static vector<int> init()
{
return ...
}
};
-
Нужно переопределить порядок инициализации базового класса и наследника
class A: public vector<double>
{
public:
// Так не будет работать.
A(double y): y_(y * y - std::sin(y)), vector({{y_, y_ + 1, y_ + 2}})
{}
private:
double y_;
};
class B_helper
{
public:
B_helper(double y): y_(y * y - std::sqrt(y))
{}
private:
double y_;
};
class B: public B_helper, public vector<double>
{
public:
B(double y): B_helper(y), vector({{y_, y_ + 1, y_ + 2}})
{}
};
-
Изменение функциональности приводит к "порче" класса
-
Расширение — это просто декоратор или адаптор
-
Старый класс можно использовать для ещё одного расширения
-
В базовом классе сохраняется малое количество методов
struct A
{
void some_method();
void another_method();
}
struct B: A
{
void extra_method();
}
-
Неоднозначность в подстановке типа в конструкторе
-
Правило вывода
-
Make-функция
-
-
Реализация perfect forwarding
template<class V>
class A
{
public:
template<class B>
A(B x)
{ ... }
};
template<class B, V = deduce B>
A<V> make(B x)
{
return A<V>(x);
}
-
Почти все методы нужно делать константными
-
Можно было бы ввести ключевое слово
mutable
template<class T>
class iterator
{
public:
T& operator*() const
{
return *ptr_;
}
private:
T* ptr_;
};
-
Возможна только в тех случаях, когда точно известны все классы
-
Основано на утиной типизации
-
Используется
std::variant
-
Минусы: везде надо использовать
std::visit
, все функции должны быть шаблонизированы, возвращаемый тип должен быть одинаковым
class Lala
{
public:
void method();
};
class Lala2: public Lala
{
public:
void method()
{
// override
}
};
class Lala3
{
public:
void method()
{
// new implementation
}
};
std::variant<Lala, Lala2, Lala3> obj = ...;
std::visit([](auto& x) { x.method(); }, obj)
-
Класс должен отвечать за одну сущность
-
Изменение класса должно быть связано только с изменением этой сущности
Конвертер форматов:
-
Изменился один из выходных форматов — изменяется конвертер
-
Изменился один из входных форматов — изменяется конвертер
-
Изменилось имя для работы с сервером — изменился конвертер (wat??)
Подход pandoc
:
-
На каждый выходной формат свой конвертер
-
На каждый входной формат свой читатель
-
Введение внутреннего формата
-
Класс закрыт для изменений
-
Получить новую функциональность можно через расширение класса (наследования, перегрузки)
-
Принцип интерфейса и реализации
-
Модульное тестирование в таком подходе: наследование от класса для предоставления скрытых связей внутри класса
-
Полиморфизм в терминах наследования от интерфейса
-
Множественное наследование от интерфейсов
-
Старое поведение базового класса должно оставаться неизменным в наследнике
-
В программе можно вместо базового класса написать наследника — поведение должно остаться прежним
Список с дублированными элементами.
test<ListType>(): ListType l; l.add(44) CHECK(l.size() == 1) DoubleList(List): add(x): List::add(x) List::add(x) test<DoubleList>() // fail
Как быть?
-
Разобраться какой из интерфейсов должен остаться прежним
-
Ввести понятие дублированный элемент, а не список с дублированием
-
Много мелких интерфейсов лучше, чем один большой
-
Принцип избегания "божественного объекта"
-
Сущности не зависят от методов, которые не используют
Объединённый интерфейс | Разделённый интерфейс |
---|---|
driver: allocate(size): void* deallocate(void*) set_program(byte[]) read(ptr, count, offset): vector<byte> write(ptr, count, offset) enqueue() wait() |
allocator: allocate(size): void* deallocate(void*) driver_data: ptr count offset io_handler: enqueue(driver_data) wait() driver(allocator): set_program(byte[]) -> io_handler |
-
Модули верхних уровней не должны зависеть от модулей нижних уровней
-
Абстракции не должны зависеть от деталей
-
Разорвать связи можно введением дополнительного уровня
-
Проверка — написание модульного теста по типу белого ящика
Встроенная зависимость | Зависимость вне класса |
---|---|
tq: - tile: ptr count - vector<tile> push(ptr, count) begin(): vector<tile>::iterator end(): vector<tile>::iterator |
tq2<Tile>: - vector<Tile> push<Ptr>(ptr, count) begin(): vector<Tile>::iterator end(): vector<Tile>::iterator |