Метод, принимающий два разных типа в качестве параметра
Я пишу метод, который должен принимать в качестве своего параметра объект одного из двух типов, которые не разделяют родительский тип, отличный от Object. Например, типы - это Мечты и Чеснок. Вы можете сделать как dreams.crush()
, так и garlic.crush()
. Я хочу иметь метод utterlyDestroy(parameter)
, который будет принимать в качестве параметра как Dreams, так и Garlic.
utterlyDestroy(parameter) {
parameter.crush()
}
Оба чеснока и сны являются частью некоторой библиотеки, поэтому, если они реализуют интерфейс ICrushable (чтобы я мог писать utterlyDestroy(ICrushable parameter)
), это не вариант.
Тело моего метода довольно длинное, поэтому перегрузка будет означать дублирование кода. Некрасиво.
Я уверен, что смогу использовать рефлексию и немного взломать класс. Некрасиво.
Я пробовал использовать generics, но, видимо, я не могу написать что-то вроде
utterlyDestroy(<T instanceof Dreams || T instanceof Garlic> parameter)
Можно ли вывести Чеснок в мечты?
utterlyDestroy(Object parameter) {
((Dreams)parameter).crush()
}
Это все равно было бы уродливо. Каковы мои другие варианты и каков предпочтительный метод решения этой проблемы?
Ответы
Ответ 1
Как насчет этого:
interface ICrushable {
void crush();
}
utterlyDestroy(ICrushable parameter) {
// Very long crushing process goes here
parameter.crush()
}
utterlyDestroy(Dreams parameter) {
utterlyDestroy(new ICrushable() { crush() {parameter.crush();});
}
utterlyDestroy(Garlic parameter) {
utterlyDestroy(new ICrushable() { crush() {parameter.crush();});
}
Новая разработка должна реализовывать интерфейс ICrushable, но для существующих классов параметр заверяется в ICrushable и передается в utterlyDestroy (ICrushable), который выполняет всю работу.
Ответ 2
Как насчет чего-то такого простого?
utterlyDestroy(Object parameter) {
if(parameter instanceOf Dreams){
Dream dream = (Dreams)parameter;
dream.crush();
//Here you can use a Dream
}
else if(parameter instanceOf Garlic){
Garlic garlic = (Garlic)parameter;
//Here you can use a Garlic
garlic.crush();
}
}
Если utterlyDestroy
слишком сложный и большой, и вы просто хотите вызвать crush
, тогда это сделает то, что вы хотите
Ответ 3
Вы можете реализовать Haskell-esque Either-class в Java; что-то вроде этого:
class Either<L,R>
{
private Object value;
public static enum Side {LEFT, RIGHT}
public Either(L left) {value = left;}
public Either(R right) {value = right;}
public Side getSide() {return value instanceof L ? Side.LEFT : Side.RIGHT;}
// Both return null if the correct side isn't contained.
public L getLeft() {return value instanceof L ? (L) value : null;}
public R getRight() {return value instanceof R ? (R) value : null;}
}
Затем вы позволяете этому методу взять что-то типа Either<Dreams, Garlic>
.
Ответ 4
Просто используйте перегрузку метода.
public void utterlyDestroy(Dreams parameter) {
parameter.crush();
}
public void utterlyDestroy(Garlic parameter) {
parameter.crush();
}
Если вы хотите поддерживать более двух этих типов таким же образом, вы можете определить общий интерфейс для всех и использовать дженерики.
Ответ 5
Вы можете использовать интерфейс и adapt ваши типы.
Интерфейс:
public interface Crushable {
public void crush();
}
Пример вызова:
public class Crusher {
public static void crush(Crushable crushable) {
crushable.crush();
}
}
Пример адаптера factory:
public final class Dreams {
public static Crushable asCrushable(final Dream dream) {
class DreamCrusher implements Crushable {
@Override
public void crush() {
dream.crush();
}
}
return new DreamCrusher();
}
private Dreams() {}
}
Код пользователя выглядит следующим образом:
Dream dream = new Dream();
Crushable crushable = Dreams.asCrushable(dream);
Crusher.crush(crushable);
Если у вас есть много типов для адаптации, вы можете подумать об этом. Вот (неоптимизированный) адаптер factory, который использует тип Proxy:
public final class Crushables {
private static final Class<?>[] INTERFACES = { Crushable.class };
public static Crushable adapt(final Object crushable) {
class Handler implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
return crushable.getClass()
.getMethod(method.getName(), method.getParameterTypes())
.invoke(crushable, args);
}
}
ClassLoader loader = Thread.currentThread()
.getContextClassLoader();
return (Crushable) Proxy.newProxyInstance(loader, INTERFACES, new Handler());
}
private Crushables() {}
}
Для потребителя API это не так уродливо:
Dream dream = new Dream();
Crushable crushable = Crushables.adapt(dream);
Crusher.crush(crushable);
Однако, как обычно с отражением, вы жертвуете проверкой типа времени компиляции.
Ответ 6
Создание интерфейса Crushable кажется самым чистым способом. Является ли подтипирование Garlic или Dreams опцией и добавление вашего интерфейса в подтип?
Запрет на то, что вы можете поместить общий код в частный метод и иметь две версии utterlyDestroy, сделать то, что они должны делать с отдельными объектами, прежде чем вызывать общий код. Если тело метода длинное, вероятно, необходимо разбить его на частные методы. Я предполагаю, что вы уже подумали об этом, хотя это еще более очевидно решение, чем добавление интерфейса.
Вы можете ввести параметр как объект и затем применить его. Это то, что вы подразумеваете под рефлексией? т.е.
public void utterlyCrush(Object crushable) {
if (crushable instanceOf Dream) {
...
}
if (curshable instanceOf Garlic) {
...
}
Но отбрасывание из Garlic to Dream не является опцией, учитывая, что он не является подтипом другого.
Ответ 7
Если вы собираетесь относиться к ним одинаково во многих местах вашего проекта, я предлагаю обернуть их в класс, что-то вроде адаптера.
Ответ 8
Как я использую:
void fooFunction(Object o){
Type1 foo=null;
if(o instanceof Type1) foo=(Type1)o;
if(o instanceof Type2) foo=((Type2)o).toType1();
// code
}
Но это работает только в том случае, если Type2 можно преобразовать в Type1