Массив методов: шаблон адаптера?
Описание проблемы:
Я хочу, чтобы иметь возможность передавать список методов другим классам, где методы были определены только в одном классе. Если методы, некоторые из которых имеют входные параметры и типы невоичного возврата, определены в одном классе, я хочу иметь возможность передавать список некоторых из них с возможными дубликатами в качестве параметра для какого-либо другого конструктора классов.
Описание кода:
Приведенный ниже код является грубым примером и может быть проигнорирован, если он отвлекает от основной цели. Другим примером, в дополнение к приведенному ниже, будет случай, когда методы являются 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;
}
}
Ответы
Ответ 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 (кешировать результаты, чтобы их не нужно было повторно запускать).