TLDR, выделенный жирным шрифтом
Как уже отмечал Джеффри Коффин, нет единственно правильного способа сделать то, что вы стремитесь достичь. В программном обеспечении не существует «одного размера для всех», поэтому ответьте на все эти ответы с небольшим количеством соли и используйте ваше лучшее решение для вашего проекта и обстоятельств. Как говорится, вот одна потенциальная альтернатива:
Остерегайтесь насмешливого ада:
Подход, который вы обрисовали в общих чертах, будет работать: но он может быть не лучшим (или может быть, только вы можете решить). Обычно причина, по которой вы склонны использовать макеты, заключается в том, что есть некоторая зависимость, которую вы хотите сломать. Извлечение интерфейса - это нормальный шаблон, но он, вероятно, не решает основную проблему. В прошлом я сильно полагался на насмешки, и были ситуации, когда я действительно сожалел об этом. У них есть свое место, но я стараюсь использовать их как можно реже и с наименьшим уровнем и наименьшим возможным классом. Вы можете попасть в насмешливый ад, в который вы собираетесь войти, так как вы должны рассуждать о том, что ваши издевательства имеют насмешки. Обычно, когда это происходит, это происходит потому, что существует структура наследования / композиции, а base / children разделяют зависимость. Если возможно, вы хотите провести рефакторинг, чтобы зависимость не была так сильно укоренилась в ваших классах.
Выделение "реальной" зависимости:
Лучшим шаблоном может быть Parameterize Constructor (еще один паттерн Michael Feathers WEWLC).
WLOG, допустим, что ваша мошенническая зависимость - это база данных (возможно, это не база данных, но идея все еще верна). Возможно, MyClass
и MyOtherClass
оба нуждаются в доступе к нему. Вместо извлечения интерфейса для обоих этих классов, попытается изолировать зависимость и передать ее конструкторам для каждого класса.
Пример:
class MyClass {
public:
MyClass(...) : ..., db(new ProdDatabase()) {}; // Old constructor, but give it a "default" database now
MyClass(..., Database* db) : ..., db(db) {}; // New constructor
...
private:
Database* db; // Decide on semantics about owning a database object, maybe you want to have the destructor of this class handle it, or maybe not
// MyOtherClass* moc; //Maybe, depends on what you're trying to do
};
и
class MyOtherClass {
public:
// similar to above, but you might want to disallow this constructor if it's too risky to have two different dependency objects floating around.
MyOtherClass(...) : ..., db(new ProdDatabase());
MyOtherClass(..., Database* db) : ..., db(db);
private:
Database* db; // Ownership?
};
И теперь, когда мы видим этот макет, это заставляет нас понять, что вы могли бы даже хотеть, чтобы MyOtherClass
просто был членом из MyClass
(зависит от того, что вы делаете и как они связаны). Это позволит избежать ошибок при создании экземпляра MyOtherClass
и облегчит бремя владения зависимостями.
Другая альтернатива - сделать Database
синглтоном, чтобы облегчить бремя владения. Это будет хорошо работать для Database
, но в общем случае шаблон синглтона не подходит для всех зависимостей.
Плюсы:
- Разрешает чистое (стандартное) внедрение зависимостей, а решает основную проблему выделения истинной зависимости.
- Изоляция реальной зависимости делает так, что вы избегаете насмешливого ада и можете просто обойти зависимость.
- Улучшенный дизайн, пригодный для будущего, высокая возможность повторного использования шаблона и, вероятно, менее сложный. Следующему классу, который нуждается в зависимости, не придется издеваться над собой, вместо этого они просто привязывают зависимость в качестве параметра.
Минусы:
- Этот шаблон, вероятно, займет больше времени / усилий, чем Extract Interface. В устаревших системах иногда это не срабатывает. Я совершил все виды грехов, потому что нам нужно было удалить особенность ... вчера. Это нормально, так бывает. Просто будьте в курсе проблем с дизайном и технической задолженности, которую вы накапливаете ...
- Это также более подвержено ошибкам.
Некоторые общие унаследованные советы, которые я использую (о чем WEWLC не говорит):
Не зацикливайтесь на том, чтобы избежать зависимости, если вам не нужно ее избегать . Это особенно верно при работе с устаревшими системами, в которых рефакторинг рискован в генеральный. Вместо этого вы можете сделать так, чтобы ваши тесты вызывали фактическую базу данных (или любую другую зависимость), но набор тестов мог подключаться к небольшой «тестовой» базе данных вместо базы данных «prod». Стоимость выдерживания небольшой тестовой БД обычно довольно мала. Стоимость сбойного продукта, потому что вы обманули насмешку или издевались не так, как обычно, намного выше. Это также сэкономит вам лот кодирования.
Избегайте издевательств (особенно сильных издевательств), где это возможно. Я становлюсь все более и более убежденным, когда становлюсь инженером-программистом, что издевательства - это запах мини-дизайна. Они быстрые и грязные, но обычно иллюстрируют большую проблему.
Представьте идеальный API и попытайтесь создать то, что вы видите. Вы не можете на самом деле создать идеальный API, но представьте, что вы можете мгновенно реорганизовать все и получить тот API, который вам нужен. желание. Это хорошая отправная точка для улучшения унаследованной системы и компромиссов / жертв с вашим текущим дизайном / реализацией.
HTH, удачи!