При разработке своего программного обеспечения я начал с интерфейсов, так как это кажется «стандартом». Затем я переключился на абстрактные классы, потому что они кажутся более подходящими для решения поставленной задачи. Однако я не уверен, что упустил некоторые соображения при выборе этого дизайна. Помимо проблем, связанных с предметной областью, о которых я думал, какие более общие факторы следует учитывать при выборе между интерфейсами и абстрактными классами?
Какие факторы следует учитывать при выборе между интерфейсами и абстрактными классами?
Ответы (5)
Я считаю, что лучший вариант в большинстве случаев - это делать и то, и другое. Это не всегда возможно, когда вы полагаетесь на то, что что-то происходит в базовом классе. Предоставление как абстрактного базового класса, так и интерфейса предоставляет наибольшую свободу действий разработчикам вашей абстракции, опять же, если вы не требуете, чтобы разработчик интерфейса что-то делал. Если вы требуете, чтобы что-то происходило, то вы не вообще хотите предоставлять интерфейс — и вам также нужно убедиться, что ваш базовый класс обеспечивает это требование. действие ВСЕГДА происходит...
Минус в обоих случаях: больше слизи.
пример псевдокода:
interface IVehicle
{
void PutCarInGear(Int speed);
}
abstract class Vehicle : IVehicle
{
public void PutCarInGear(Int speed)
{
//base implementation
}
}
Обратите внимание, что в этом примере кто-то может создать свой собственный класс, реализующий IVehicle
, а затем передать его всему, что принимает IVehicle
. Этот объект НЕ должен быть дочерним элементом абстрактного класса Vehicle
. Поэтому, если вы ожидаете, что в методе PutCarInGear()
произойдет что-то конкретное, вполне вероятно, что этот новый класс НЕ оправдает это ожидание. Однако до тех пор, пока не имеет значения, что делают реализации IVehicle
, наиболее гибкой абстракцией является наличие как абстрактного базового класса, так и интерфейса.
Наследование определяет сходство состава. Интерфейсы определяют сходство поведения. Оттуда вы должны решить, достаточно ли важна композиция, чтобы переопределить поведение.
Если ваш интерфейс также имеет разумное поведение по умолчанию по крайней мере для некоторых функций, вы можете захотеть использовать абстрактный класс, чтобы избежать использования шаблонного кода во всех конкретных реализациях. В противном случае используйте интерфейс.
Используйте абстрактный класс, когда у вас есть несколько экземпляров, которые конкретизируют шаблон метода шаблона.
Скажем, у вас есть три парсера. Один разграничивает данные файла с помощью ",", другой с помощью " ", а третий с помощью "|"
Затем вы можете иметь CommaParser, SpaceParser и PipeParser, все из которых являются подклассами Abstract Parser и переопределяют абстрактный метод, getDelimiter()
Используйте интерфейс, когда классы не имеют общей реализации, а просто отвечают на одни и те же вызовы методов.
Сохраните его как абстрактный класс, если это отношение «является» и должно выполнять подмножество/все функции. Сохраняйте его как интерфейс, если это отношение «Должен делать».