Лучший способ справиться с этим слишком сильно зависит от конкретного случая, чтобы обеспечить общее решение.Поэтому я приведу несколько примеров и способы их решения.
Пример 1: Виртуальная файловая система
Клиенты вашего кода реализуют виртуальные файловые системыкоторые позволяют им работать с любым ресурсом, который можно сделать похожим на файл.Они делают это путем реализации следующего интерфейса.
interface IFolder
{
IFolder subFolder(String Name);
void delete(String filename);
void removeFolder(); // must be empty
IFile openFile(String Name);
List<String> getFiles();
}
В следующей версии вашего программного обеспечения вы хотите добавить возможность удаления каталога и всего его содержимого.Назовите это removeTree.Вы не можете просто добавить removeTree в IFolder, потому что это сломает всех пользователей IFolder.Вместо этого:
interface IFolder2 implements IFolder
{
void removeTree();
}
Всякий раз, когда клиент регистрирует IFolder (а не IFolder2), вместо этого регистрируйте
new IFolder2Adapter(folder)
и используйте IFolder2 во всем приложении.Большая часть вашего кода не должна беспокоиться о разнице в том, какие старые версии IFolder поддерживаются.
Случай 2: улучшенные строки
У вас есть класс строк, который поддерживает различныефункциональность.
class String
{
String substring(int start, end);
}
Вы решили добавить поиск строки в новой версии и таким образом реализовать:
class SearchableString extends String
{
int find(String);
}
Это просто глупо, SearchableString следует объединить в строку.
Случай 3: Фигуры
У вас есть симулятор формы, который позволяет вам получать области фигур.
class Shape
{
double Area();
static List<Shape> allShapes; // forgive evil staticness
}
Теперь вы вводите новый вид фигуры:
class DrawableShape extends Shape
{
void Draw(Painter paint);
}
Мы могли бы добавить пустой метод Draw по умолчанию в Shape.Но кажется неправильным, чтобы у Shape был метод Draw, потому что фигуры вообще не предназначены для рисования.Чертежу действительно нужен список DrawableShapes, а не список Shapes, который предоставляется.На самом деле, возможно, DrawableShape вообще не должна быть Shape.
Случай 4: Детали
Предположим, что у нас есть Автомобиль:
class Car
{
Motor getMotor();
Wheels getWheels();
}
void maintain(Car car)
{
car.getMotor().changeOil();
car.getWheels().rotate();
}
Конечно, вы знаете, что где-то в будущем кто-то сделает лучшую машину.
class BetterCar extends Car
{
Highbeams getHighBeams();
}
Здесь мы можем использовать шаблон посетителей.
void maintain(Car car)
{
car.visit( new Maintainer() );
}
Автомобиль передает все свои составные части на вызовы в интерфейс ICarVisitor, позволяя классу Maintainer поддерживать каждый компонент.
Случай 5: игровые объекты У нас естьигра с множеством объектов, которые можно увидеть на экране
class GameObject
{
void Draw(Painter painter);
void Destroy();
void Move(Point point);
}
Некоторые из наших игровых объектов нуждаются в способности выполнять логику через регулярные промежутки времени, поэтому мы создаем:
class LogicGameObject extends GameObject
{
void Logic();
}
Как мы вызываем Logic () для всех объектов LogicGameObject?В этом случае добавление пустого метода Logic () в GameObject кажется лучшим вариантом.В описании работы GameObject вполне можно ожидать, что он сможет знать, что делать с обновлением логики, даже если это ничего не значит.
Заключение
Лучший способ справиться с этой ситуацией зависит от конкретной ситуации.Вот почему я поставил вопрос о том, почему вы не хотите добавлять функциональность в Foo.Лучший способ расширения Foo зависит от того, что именно вы делаете.То, что вы видите с помощью instanceof /, - это признак того, что вы не расширили объект наилучшим образом.