Подтвердить что ты не робот

Массив методов: шаблон адаптера?

Описание проблемы: Я хочу, чтобы иметь возможность передавать список методов другим классам, где методы были определены только в одном классе. Если методы, некоторые из которых имеют входные параметры и типы невоичного возврата, определены в одном классе, я хочу иметь возможность передавать список некоторых из них с возможными дубликатами в качестве параметра для какого-либо другого конструктора классов.

Описание кода: Приведенный ниже код является грубым примером и может быть проигнорирован, если он отвлекает от основной цели. Другим примером, в дополнение к приведенному ниже, будет случай, когда методы являются int Add (int n1, int n2), int Subtract (int n1, int n2), Multiply и т.д., А интерфейс имеет метод, называемый int MathOperation (int n1, int n2).

Попытка решить проблему: У шаблона адаптера есть функциональность, которую я ищу, но я видел только примеры, когда методы в интерфейсе не имеют входных или выходных параметров. Примерная реализация, которую я написал только для этого вопроса, размещена ниже.

Проблема Аналогия: У вас есть веб-служба генератора случайных изображений. Есть 30 мутаций, которые могут быть применены к изображению. Клиент подключается и нажимает кнопку "сгенерировать", и случайный список некоторых из этих функций передается другому классу в веб-службе, который затем запускает эти функции с собственными данными, одновременно собирая и, возможно, повторно используя возврат значения для генерации некоторого мутированного изображения кошки. Он не может просто явно вызвать методы в другом классе, потому что этот процесс должен выполняться случайным образом во время выполнения. Вот почему я склоняюсь к идее создания случайного списка методов, которые выполняются в порядке, когда нажата кнопка "сгенерировать".

Надеюсь, я поняла.

public class SomeClass {
    ...
    public double UseWrench(double torque, boolean clockwise) { ... }
    public double UsePliers(double torque, boolean clockwise) { ... }
    public double UseScrewDriver(double torque, boolean clockwise) { ... }
    public boolean UseWireCutters(double torque) { ... }

    interface IToolActions {
        double TurnFastener(double torque, boolean clockwise);
        boolean CutWire(double torque);
    }

    private IToolActions[] toolActions = new IToolActions[] {
        new IToolActions() { public double TurnFastener(double torque, boolean clockwise) { double UseWrench(double torque, boolean clockwise); } },
        new IToolActions() { public double TurnFastener(double torque, boolean clockwise) { double UsePliers(double torque, boolean clockwise); } },
        new IToolActions() { public double TurnFastener(double torque, boolean clockwise) { double UseScrewDriver(double torque, boolean clockwise); } },
        new IToolActions() { public boolean CutWire(double torque) { boolean UseWireCutters(double torque); } },
    };
}

public class Worker<T> {

    public List<? extends IToolActions> toolActions;

    public Worker(List<? extends IToolActions> initialToolSet){
        toolActions = initialToolActions;
    }
}
4b9b3361

Ответ 1

@John вот как я подошел к решению вашей проблемы.

Я использовал случай MathOperations, чтобы сделать его более простым. Сначала я думаю, что лучше иметь интерфейс вне SomeClass, например:

public interface MathOperable {

    public int mathOperation(int n1, int n2);

}

Я создал два примера классов, реализующих этот интерфейс, и одну анонимную реализацию внутри SomeClass (я сделал Add, Multiply и анонимный "Substract" )

public class Add implements MathOperable {

    public int mathOperation(int n1, int n2) {

        return n1 + n2;
    }

    public String toString() {
        return "<addition>";
    }

}

Превращение toString() просто для того, чтобы предоставить более читабельность примерам, которые я покажу в конце моего сообщения.

public class Multiply implements MathOperable {

    public int mathOperation(int n1, int n2) {
        // TODO Auto-generated method stub
        return n1 * n2;
    }

    public String toString() {
        return "<multiplication>";
    }

}

Вот мой класс SomeClass, он ссылается на getRandomListOfOperations, где я имитирую, что происходит, когда клик по кнопке выполнен

public class SomeClass {

    private static MathOperable addition = new Add();
    private static MathOperable multiplication = new Multiply();

    // Anonymous substraction  
    private static MathOperable substraction = new MathOperable() {

        public int mathOperation(int n1, int n2) {
            // TODO Auto-generated method stub
            return n1-n2;
        }

        public String toString() {
            return "<substraction>";
        }

    };


    public List<MathOperable> getRandomListOfOperations() {

        // We put the methods in an array so that we can pick them up later     randomly
        MathOperable[] methods = new MathOperable[] {addition,     multiplication, substraction};
        Random r = new Random();

        // Since duplication is possible whe randomly generate the number of     methods to send
        // among three so if numberOfMethods > 3 we are sure there will be     duplicates
        int numberOfMethods = r.nextInt(10);
        List<MathOperable> methodsList = new ArrayList<MathOperable>();

        // We pick randomly the methods with duplicates
        for (int i = 0; i < numberOfMethods; i++) {
            methodsList.add(methods[r.nextInt(3)]);

        }

        return methodsList;     
    }

    public void contactSomeOtherClass() {
        new SomeOtherClass(getRandomListOfOperations());
    }
}

Теперь вот мой SomeOtherClass (который может соответствовать вашему классу Worker)

public class SomeOtherClass<T extends MathOperable> {

    Random r = new Random();

    List<T> operations;

    public SomeOtherClass(List<T> operations) {
        this.operations = operations;

        runIt();
    }

    public void runIt() {

        if (null == operations) {
            return;
        }

        // Let imagine for example that the new result is taken as     operand1 for the next operation
        int result = 0;

        // Here are examples of the web service own datas
        int n10 = r.nextInt(100);
        int n20 = r.nextInt(100);

        for (int i = 0; i < operations.size(); i++) {

            if (i == 0) {
                result = operations.get(i).mathOperation(n10, n20);
                System.out.println("Result for operation N "  + i + " = " +     result);
            } else {

                // Now let imagine another data from the web service     operated with the previous result
                int n2 = r.nextInt(100);
                result = operations.get(i).mathOperation(result, n2);
                System.out.println("Current result for operation N " + i + "     which is " + operations.get(i) +" = " + result);

            }
        }
    }

}

У меня есть простой тестовый класс, который содержит основную ссылку для подключения двух классов

public class SomeTestClass {

    public static void main(String[] args) {
        SomeClass classe = new SomeClass();
        classe.contactSomeOtherClass();
    }

}

Теперь несколько примеров исполнений:

example1

И еще одна иллюстрация!

example 2

Надеюсь, это может быть полезно!

Ответ 2

В то время как у @alainlompo есть общая идея, Java 8 упрощает это, используя что-то вроде BiConsumer (для парных) или даже просто Consumer для объекта класса. На самом деле, вы можете пойти действительно сумасшедшим и принять метод varargs lambdas:

public class SomeClass

    public double useWrench(double torque, boolean clockwise) { ... }
    public double usePliers(double torque, boolean clockwise) { ... }
    public double useScrewDriver(double torque, boolean clockwise) { ... }
    public boolean useWireCutters(double torque) { ... }

}

public class Worker {

    @SafeVarargs
    public Worker(SomeClass example, Consumer<? extends SomeClass>... operations) {
        for (Consumer bc : operations) {
            bc.accept(example);
        }
    }
}

Тогда это легко упрощается:

SomeClass c = new SomeClass();
new Worker(c, SomeClass::useWrench, SomeClass:usePliers, SomeClass::useScrewDriver, SomeClass::useWireCutters);

Хотя кажется немного неудобным, применяя его так (из-за того, что он является шаблоном адаптера), вы можете легко увидеть, как это применимо к телу класса:

public class SomeClass

    public double useWrench(double torque, boolean clockwise) { ... }
    public double usePliers(double torque, boolean clockwise) { ... }
    public double useScrewDriver(double torque, boolean clockwise) { ... }
    public boolean useWireCutters(double torque) { ... }

    @SafeVarargs
    public void operate(Consumer<? extends SomeClass>... operations) {
        for (Consumer<? extends SomeClass> bc : operations) {
            bc.accept(example);
        }
    }

}

//Elsewheres
SomeClass c = new SomeClass();
c.operate(SomeClass::useWrench, SomeClass:usePliers, SomeClass::useScrewDriver, SomeClass::useWireCutters);

Конечно, вам не нужны varargs, он будет работать так же просто, пройдя Collection

Но подождите там больше!!!

Если вы хотите получить результат, вы можете использовать метод самовозвращения через Function, например:

public class SomeClass {

    public double chanceOfSuccess(Function<? super SomeClass, ? extends Double> modifier) {
        double back = /* some pre-determined result */;
        return modifier.apply(back); //apply our external modifier
    }

}

//With our old 'c'
double odds = c.chanceOfSuccess(d -> d * 2); //twice as likely!

В API-интерфейсе Java существует гораздо больше гибкости, что создает сложные проблемы, такие как невероятно упрощенные для записи.

Ответ 3

Хорошо, я собираюсь быть "этим парнем"... тем, кто понимает этот вопрос, но все равно просит повторить проблему, потому что я думаю, что вы ошибаетесь. Итак, медведь со мной: если вам нравится то, что вы видите, здорово; если нет, я понимаю.

В принципе, у вас другое намерение/мотивация/цель, чем подходит для "адаптера". Шаблон команды лучше подходит.

Но сначала, в общем, одна из целей разработки "элементов многоразового программного обеспечения" (из названия оригинальной книги шаблонов GOF) заключается в том, что вы не хотите изменять код при добавлении функциональности; скорее, вы хотите добавить код, не касаясь существующей функциональности. Итак, когда у вас есть:

public class Toolbox {
    public void hammer() { ... }
}

и вы хотите добавить отвертку к панели инструментов, это плохо:

public class Toolbox {
    public void hammer() { ... }
    public void screwdriver() { ... }
}

Скорее, в идеале, весь существующий код останется неизменным, и вы просто добавите новый блок компиляции Screwdriver (т.е. добавьте новый файл) и unit test, а затем протестируете существующий код для регрессии (который должен маловероятно, поскольку ни один из существующих кодов не изменился). Например:

public class Toolbox {
    public void useTool(Tool t) { t.execute(); ...etc... }
}

public interface Tool { // this is the Command interface
     public void execute() // no args (see ctors)
}

pubic Hammer implements Tool {
     public Hammer(Nail nail, Thing t) // args!
     public void execute() { nail.into(t); ... }
}

pubic Screwdriver implements Tool {
     public Screwdriver(Screw s, Thing t)
     public void execute() { screw.into(t); ... }
}

Надеюсь, нам станет ясно, как расширить это на ваш пример. Работник становится прямым списком инструментов (или, для ясности, вместо "Инструмент", просто назовите его "Командой" ).

public class Worker {
    public List<Command> actionList;
     ....
    public void work() {
      for(...) {
         action.execute();
      }
    }
}

Этот шаблон также позволяет легко "отменить" функциональность и "повторить попытку", а также memoization (кешировать результаты, чтобы их не нужно было повторно запускать).