Есть ли элегантный способ заставить каждый метод в классе начинаться с определенного блока кода?


142

У меня есть класс, в котором все методы запускаются одинаково:

class Foo {
  public void bar() {
    if (!fooIsEnabled) return;
    //...
  }
  public void baz() {
    if (!fooIsEnabled) return;
    //...
  }
  public void bat() {
    if (!fooIsEnabled) return;
    //...
  }
}

Есть ли хороший способ требовать (и, надеюсь, не писать каждый раз) fooIsEnabledчасть для каждого общедоступного метода в классе?


45
Изучите аспектно-ориентированное программирование (особенно перед советом ).
Сотириос Делиманолис,

15
Для скольких методов у вас есть этот шаблон? Прежде чем вы начнете внедрять АОП, вы можете подумать о том, чтобы просто иметь этот небольшой повторяющийся код. Иногда самое простое решение - это немного скопировать и вставить.
bhspencer

51
Я подозреваю, что ваш будущий сопровождающий был бы более доволен дополнительной схемой, чем изучением структуры АОП.
bhspencer

7
Если каждый метод класса должен делать одно и то же в своих первых строках кода, значит, у нас плохой дизайн.
Tulains Córdova,

7
@ user1598390: Вопрос здесь не не по теме, и в сфере возможностей программистов нет ничего, что делало бы вопрос особенно значимым.
Роберт Харви,

Ответы:


90

Я не знаю насчет элегантности, но вот рабочая реализация с использованием встроенного в Java, java.lang.reflect.Proxyкоторая обеспечивает, чтобы все вызовы методов Fooначинались с проверки enabledсостояния.

main метод:

public static void main(String[] args) {
    Foo foo = Foo.newFoo();
    foo.setEnabled(false);
    foo.bar(); // won't print anything.
    foo.setEnabled(true);
    foo.bar(); // prints "Executing method bar"
}

Foo интерфейс:

public interface Foo {
    boolean getEnabled();
    void setEnabled(boolean enable);

    void bar();
    void baz();
    void bat();

    // Needs Java 8 to have this convenience method here.
    static Foo newFoo() {
        FooFactory fooFactory = new FooFactory();
        return fooFactory.makeFoo();
    }
}

FooFactory класс:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class FooFactory {

    public Foo makeFoo() {
        return (Foo) Proxy.newProxyInstance(
                this.getClass().getClassLoader(),
                new Class[]{Foo.class},
                new FooInvocationHandler(new FooImpl()));
    }

    private static class FooImpl implements Foo {
        private boolean enabled = false;

        @Override
        public boolean getEnabled() {
            return this.enabled;
        }

        @Override
        public void setEnabled(boolean enable) {
            this.enabled = enable;
        }

        @Override
        public void bar() {
            System.out.println("Executing method bar");
        }

        @Override
        public void baz() {
            System.out.println("Executing method baz");
        }

        @Override
        public void bat() {
            System.out.println("Executing method bat");
        }

    }

    private static class FooInvocationHandler implements InvocationHandler {

        private FooImpl fooImpl;

        public FooInvocationHandler(FooImpl fooImpl) {
            this.fooImpl = fooImpl;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            if (method.getDeclaringClass() == Foo.class &&
                !method.getName().equals("getEnabled") &&
                !method.getName().equals("setEnabled")) {

                if (!this.fooImpl.getEnabled()) {
                    return null;
                }
            }

            return method.invoke(this.fooImpl, args);
        }
    }
}

Как отмечали другие, это действительно кажется излишним для того, что вам нужно, если у вас есть только несколько методов, о которых нужно беспокоиться.

Тем не менее, преимущества, безусловно, есть:

  • Достигается определенное разделение задач, поскольку Fooреализациям методов не нужно беспокоиться о enabledсквозных проверках. Вместо этого код метода должен беспокоиться только о том, какова основная цель метода, не более того.
  • Невинный разработчик не может добавить новый метод в Fooкласс и по ошибке «забыть» добавить enabledпроверку. enabledПоведение проверки автоматически наследуются любым добавляемого способом.
  • Если вам нужно добавить еще одну сквозную проблему или если вам нужно улучшить enabledпроверку, это очень легко сделать безопасно и в одном месте.
  • Приятно, что вы можете получить такое поведение, подобное АОП, с помощью встроенных функций Java. Вы не обязаны интегрировать какие-то другие фреймворки, например Spring, хотя они определенно могут быть хорошими вариантами.

Честно говоря, некоторые из недостатков:

  • Часть кода реализации, обрабатывающего вызовы прокси, уродлива. Некоторые также скажут, что наличие внутренних классов для предотвращения создания экземпляров FooImplкласса некрасиво.
  • Если вы хотите добавить новый метод Foo, вам нужно внести изменения в 2 места: класс реализации и интерфейс. Ничего страшного, но работы еще немного.
  • Вызов прокси платный. Есть определенные накладные расходы на производительность. Однако для обычного использования это не будет заметно. См. Здесь для получения дополнительной информации.

РЕДАКТИРОВАТЬ:

Комментарий Фабиана Штрайтеля заставил меня задуматься о двух неприятностях с моим вышеуказанным решением, которые, я признаю, я недоволен собой:

  1. Обработчик вызова использует магические строки, чтобы пропустить "enabled-check" для методов "getEnabled" и "setEnabled". Это может легко сломаться, если изменить имена методов.
  2. Если бы был случай, когда нужно было добавить новые методы, которые не должны наследовать поведение «enabled-check», тогда разработчику может быть довольно легко ошибиться, и, по крайней мере, это будет означать добавление большего количества магии. струны.

Чтобы разрешить точку №1 и хотя бы облегчить проблему с точкой №2, я бы создал аннотацию BypassCheck(или что-то подобное), которую я мог бы использовать, чтобы отметить методы в Fooинтерфейсе, для которых я не хочу выполнять операцию " включена проверка ". Таким образом, мне совсем не нужны волшебные строки, и разработчику становится намного проще правильно добавить новый метод в этом особом случае.

Используя решение для аннотаций, код будет выглядеть так:

main метод:

public static void main(String[] args) {
    Foo foo = Foo.newFoo();
    foo.setEnabled(false);
    foo.bar(); // won't print anything.
    foo.setEnabled(true);
    foo.bar(); // prints "Executing method bar"
}

BypassCheck аннотация:

import java.lang.annotation.*;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface BypassCheck {
}

Foo интерфейс:

public interface Foo {
    @BypassCheck boolean getEnabled();
    @BypassCheck void setEnabled(boolean enable);

    void bar();
    void baz();
    void bat();

    // Needs Java 8 to have this convenience method here.
    static Foo newFoo() {
        FooFactory fooFactory = new FooFactory();
        return fooFactory.makeFoo();
    }
}

FooFactory класс:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class FooFactory {

    public Foo makeFoo() {
        return (Foo) Proxy.newProxyInstance(
                this.getClass().getClassLoader(),
                new Class[]{Foo.class},
                new FooInvocationHandler(new FooImpl()));
    }

    private static class FooImpl implements Foo {

        private boolean enabled = false;

        @Override
        public boolean getEnabled() {
            return this.enabled;
        }

        @Override
        public void setEnabled(boolean enable) {
            this.enabled = enable;
        }

        @Override
        public void bar() {
            System.out.println("Executing method bar");
        }

        @Override
        public void baz() {
            System.out.println("Executing method baz");
        }

        @Override
        public void bat() {
            System.out.println("Executing method bat");
        }

    }

    private static class FooInvocationHandler implements InvocationHandler {

        private FooImpl fooImpl;

        public FooInvocationHandler(FooImpl fooImpl) {
            this.fooImpl = fooImpl;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            if (method.getDeclaringClass() == Foo.class
                    && !method.isAnnotationPresent(BypassCheck.class) // no magic strings
                    && !this.fooImpl.getEnabled()) {

                return null;
            }

            return method.invoke(this.fooImpl, args);
        }
    }
}

11
Я понимаю, что это умное решение, но вы действительно воспользуетесь им?
bhspencer

1
- хороший обходной путь, вы используете шаблон динамического прокси, чтобы украсить объект этим обычным поведением, обнаруженным в начале каждого метода.
Виктор

11
@bhspencer: вполне законный вопрос. Я на самом деле использовал его много раз для обработки исключений, ведения журнала, обработки транзакций и т. Д. Я признаю, что для небольших классов это кажется излишним, и вполне может быть. Но если я ожидаю, что класс станет намного сложнее, и хочу обеспечить единообразное поведение всех методов при этом, я не возражаю против этого решения.
sstan

1
Не быть частью 97% зла, но каковы последствия для производительности этого прокси-класса?
corsiKa

5
@corsiKa: Хороший вопрос. Нет сомнений в том, что использование динамических прокси медленнее, чем прямой вызов метода. Однако для обычного использования накладные расходы на производительность будут незаметными. Связанная тема SO, если вам интересно: Стоимость производительности динамического прокси Java
sstan

51

Есть много хороших предложений ... что вы можете сделать, чтобы решить свою проблему, это подумать о State Pattern и реализовать его.

Взгляните на этот фрагмент кода ... возможно, он поможет вам понять идею. В этом сценарии похоже, что вы хотите изменить всю реализацию методов на основе внутреннего состояния объекта. Напоминаем, что сумма методов в объекте называется поведением.

public class Foo {

      private FooBehaviour currentBehaviour = new FooEnabledBehaviour (); // or disabled, or use a static factory method for getting the default behaviour

      public void bar() {
        currentBehaviour.bar();
      }
      public void baz() {
        currentBehaviour.baz();
      }
      public void bat() {
        currentBehaviour.bat();
      }

      public void setFooEnabled (boolean fooEnabled) { // when you set fooEnabel, you are changing at runtime what implementation will be called.
        if (fooEnabled) {
          currentBehaviour = new FooEnabledBehaviour ();
        } else {
          currentBehaviour = new FooDisabledBehaviour ();
        }
      }

      private interface FooBehaviour {
        public void bar();
        public void baz();
        public void bat();
      }

      // RENEMBER THAT instance method of inner classes can refer directly to instance members defined in its enclosing class
      private class FooEnabledBehaviour implements FooBehaviour {
        public void bar() {
          // do what you want... when is enabled
        }
        public void baz() {}
        public void bat() {}

      }

      private class FooDisabledBehaviour implements FooBehaviour {
        public void bar() {
          // do what you want... when is desibled
        }
        public void baz() {}
        public void bat() {}

      }
}

Надеюсь, вам понравится!

PD: Реализация паттерна состояний (также известная как стратегия в зависимости от контекста ... но принципы те же).


1
OP не хочет повторять одну и ту же строку кода в начале каждого метода, и ваше решение включает повторение одной и той же строки кода в начале каждого метода.
Tulains Córdova,

2
@ user1598390 нет необходимости повторять оценку, внутри FooEnabledBehaviour вы предполагаете, что клиент этого объекта установил для fooEnabled значение true, поэтому нет необходимости проверять. То же самое и с классом FooDisabledBehaviour. Проверьте еще раз, код в нем.
Виктор

2
Спасибо @ bayou.io, подождем ответа OP. Я думаю, что сообщество здесь чертовски хорошо работает, здесь есть много хороших советов!
Виктор

2
Согласившись с @dyesdyes, я не могу представить, как реализовать это для чего-либо, кроме действительно тривиального класса. Это слишком проблематично, учитывая, что bar()in FooEnabledBehaviorи bar()in FooDisabledBehaviorмогут использовать один и тот же код, возможно, даже с одной строкой, отличающейся между ними. Вы могли бы очень легко, особенно если бы этот код поддерживался младшими разработчиками (такими как я), в конечном итоге получить гигантский мусор, который невозможно поддерживать и не тестировать. Это может произойти с любым кодом, но кажется, что это так легко сделать очень быстро. +1 правда ведь хорошее предложение.
Chris Cirefice 01

1
Ммммм ... я не ребята ... но сначала спасибо за комментарии. Для меня размер кода не проблема, если он «чистый» и «читаемый». В свою пользу я должен возразить, что я не использую никаких внешних классов ... это должно сделать вещи более доступными. И если есть какое-то общее поведение, давайте инкапсулируем его в CommonBehaviourClass и делегируем туда, где это необходимо. В книге GOF (не моя любимая книга для обучения, но в ней есть хорошие рецепты) вы нашли этот пример: en.wikipedia.org/wiki/… . Более или менее то же самое, что и я здесь.
Виктор

14

Да, но это немного работы, поэтому все зависит от того, насколько это важно для вас.

Вы можете определить класс как интерфейс, написать реализацию делегата, а затем использовать java.lang.reflect.Proxyдля реализации интерфейса с методами, которые выполняют общую часть, а затем условно вызвать делегат.

interface Foo {
    public void bar();
    public void baz();
    public void bat();
}

class FooImpl implements Foo {
    public void bar() {
      //... <-- your logic represented by this notation above
    }

    public void baz() {
      //... <-- your logic represented by this notation above
    }

    // and so forth
}

Foo underlying = new FooImpl();
InvocationHandler handler = new MyInvocationHandler(underlying);
Foo f = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(),
     new Class[] { Foo.class },
     handler);

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

public Object invoke(Object proxy, Method method, Object[] args) {
    if (!fooIsEnabled) return null;
    return method.invoke(underlying, args);
}

Это не очень красиво. Но в отличие от различных комментаторов, я бы сделал это, так как я думаю, что повторение - более важный риск, чем такая плотность, и вы сможете создать «ощущение» своего реального класса с помощью этой несколько непостижимой оболочки очень локально, всего в паре строк кода.

См. Документацию по Java для получения подробной информации о динамических прокси-классах.


14

Этот вопрос тесно связан с аспектно-ориентированным программированием . AspectJ - это AOP-расширение Java, и вы можете взглянуть на него, чтобы получить некоторое вдохновение.

Насколько мне известно, в Java нет прямой поддержки АОП. Есть несколько шаблонов GOF, которые относятся к нему, например, шаблонный метод и стратегия, но на самом деле это не спасет вам строки кода.

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

public void checkBalance() {
    checkSomePrecondition();
    ...
    checkSomePostcondition();
}

Однако это не подошло бы к вашему случаю, потому что вы хотели бы, чтобы вычеркнутый код мог вернуться из checkBalance. В языках, поддерживающих макросы (например, C / C ++), вы можете определить checkSomePreconditionи checkSomePostconditionкак макросы, и они будут просто заменены препроцессором до того, как компилятор даже будет вызван:

#define checkSomePrecondition \
    if (!fooIsEnabled) return;

В Java этого нет из коробки. Это может кого-то обидеть, но в прошлом я использовал автоматическое создание кода и механизмы шаблонов для автоматизации повторяющихся задач кодирования. Если вы обрабатываете свои файлы Java перед их компиляцией с помощью подходящего препроцессора, например Jinja2, вы можете сделать что-то похожее на то, что возможно в C.

Возможный подход на чистом Java

Если вы ищете решение на чистом Java, то, возможно, вы найдете не очень лаконичным. Но при этом можно исключить общие части вашей программы и избежать дублирования кода и ошибок. Вы могли бы сделать что-то вроде этого (это своего рода шаблон, вдохновленный Стратегией ). Обратите внимание, что в C # и Java 8, а также на других языках, в которых функции немного проще обрабатывать, этот подход может действительно хорошо выглядеть.

public interface Code {
    void execute();
}

...

public class Foo {
  private bool fooIsEnabled;

  private void protect(Code c) {
      if (!fooIsEnabled) return;
      c.execute();
  }

  public void bar() {
    protect(new Code {
      public void execute() {
        System.out.println("bar");
      }
    });
  }

  public void baz() {
    protect(new Code {
      public void execute() {
        System.out.println("baz");
      }
    });
  }

  public void bat() {
    protect(new Code {
      public void execute() {
        System.out.println("bat");
      }
    });
  }
}

Что-то вроде реального сценария

Вы разрабатываете класс для отправки фреймов данных промышленному роботу. Роботу требуется время, чтобы выполнить команду. Как только команда будет завершена, она отправит вам контрольный кадр обратно. Робот может быть поврежден, если он получит новую команду, когда предыдущая все еще выполняется. Ваша программа использует DataLinkкласс для отправки и получения кадров от робота. Вам необходимо защитить доступ к DataLinkэкземпляру.

Пользовательский интерфейс нить вызовы RobotController.left, right, upили downкогда пользователь нажимает на кнопки, но и вызовы BaseController.tickчерез регулярные промежутки времени, для того , чтобы экспедирования повторного включения команды в частном DataLinkслучае.

interface Code {
    void ready(DataLink dataLink);
}

class BaseController {
    private DataLink mDataLink;
    private boolean mReady = false;
    private Queue<Code> mEnqueued = new LinkedList<Code>();

    public BaseController(DataLink dl) {
        mDataLink = dl;
    }

    protected void protect(Code c) {
        if (mReady) {
            mReady = false;
            c.ready(mDataLink);
        }
        else {
            mEnqueue.add(c);
        }
    }

    public void tick() {
        byte[] frame = mDataLink.readWithTimeout(/* Not more than 50 ms */);

        if (frame != null && /* Check that it's an ACK frame */) {
          if (mEnqueued.isEmpty()) {
              mReady = true;
          }
          else {
              Code c = mEnqueued.remove();
              c.ready(mDataLink);
          }
        }
    }
}

class RobotController extends BaseController {
    public void left(float amount) {
        protect(new Code() { public void ready(DataLink dataLink) {
            dataLink.write(/* Create a byte[] that means 'left' by amount */);
        }});
    }

    public void right(float amount) {
        protect(new Code() { public void ready(DataLink dataLink) {
            dataLink.write(/* Create a byte[] that means 'right' by amount */);
        }});
    }

    public void up(float amount) {
        protect(new Code() { public void ready(DataLink dataLink) {
            dataLink.write(/* Create a byte[] that means 'up' by amount */);
        }});
    }

    public void down(float amount) {
        protect(new Code() { public void ready(DataLink dataLink) {
            dataLink.write(/* Create a byte[] that means 'down' by amount */);
        }});
    }
}

4
Разве это не просто ударит по дороге. То есть будущий сопровождающий должен был помнить, что if (! FooIsEnabled) return; в начале каждой функции, а теперь им нужно помнить о защите (новый код {... в начале каждой функции. Как это помогает?
bhspencer

1
Мне нравится ваш анализ и фоновый контекст damix911 ... я бы создал новые экземпляры кода во время компиляции (используя частные статические члены), предполагая, что код не будет меняться со временем, и переименую изменение в «executeIf», передав в качестве аргумента условие (Как класс Predicate) и Код. Но это больше личное ощущение и вкус.
Виктор

1
@bhspencer В Java это выглядит довольно неуклюже, и в большинстве случаев эта стратегия действительно представляет собой чрезмерную разработку простого кода. Не многие программы могут извлечь выгоду из такого шаблона. Однако круто то, что мы создали новый символ, protectкоторый легче повторно использовать и документировать. Если вы скажете будущему сопровождающему, что критический код должен быть защищен protect, вы уже говорите ему, что делать. Если правила защиты изменятся, новый код все равно будет защищен. Это как раз основная причина определения функций, но OP должен «возвращать return», чего функции не могут.
damix911

11

Я бы подумал о рефакторинге. Этот узор сильно нарушает СУХОЙ узор (не повторяйтесь). Я считаю, что это ломает классовую ответственность. Но это зависит от вашего контроля над кодом. Ваш вопрос очень открытый - куда вы вызываете Fooэкземпляр?

Я полагаю, у вас есть код вроде

foo.bar(); // does nothing if !fooEnabled
foo.baz(); // does also nothing
foo.bat(); // also

возможно, тебе стоит назвать это примерно так:

if (fooEnabled) {
   foo.bat();
   foo.baz();
   ...
}

И держите его в чистоте. Например, ведение журнала:

this.logger.debug(createResourceExpensiveDump())

a logger не спрашивает себя , включена ли отладка. Это просто журналы.

Вместо этого вызывающий класс должен это проверить:

if (this.logger.isDebugEnabled()) {
   this.logger.debug(createResourceExpensiveDump())
}

Если это библиотека, и вы не можете управлять вызовом этого класса, бросьте, IllegalStateExceptionкоторое объясняет, почему, если этот вызов является незаконным и вызывает проблемы.


6
Это определенно проще и приятнее для глаз. Но если цель OP - убедиться, что по мере добавления новых методов включенная логика никогда не обходится, то этот рефакторинг не облегчает выполнение этого.
sstan

4
Также для вашего примера журнала я бы сказал, что это связано с гораздо большим количеством повторений - каждый раз, когда вы хотите войти, вы должны проверять, включен ли регистратор. Я обычно записываю больше строк, чем количество методов в любом классе ...
Т. Кили

4
Это нарушает модульность, потому что теперь вызывающий должен кое-что знать о внутреннем устройстве foo (в данном случае, является ли это fooEnabled). Это классический пример, когда следование правилам передовой практики не решит проблему, потому что правила конфликтуют. (Я все еще надеюсь, что кто-нибудь ответит на этот вопрос: «Почему я не подумал об этом?».)
Ян Голдби

2
Что ж, это сильно зависит от контекста, имеет ли это смысл.
Ian Goldby

3
Ведение журнала - это именно тот пример, когда я не хочу повторения в моем коде. Я просто хочу написать LOG.debug ("...."); - И регистратор должен проверить, действительно ли я хочу отлаживать. - Другой пример - закрытие / очистка. - Если я использую AutoClosable, мне не нужно исключение, если оно уже закрыто, оно просто ничего не должно делать.
Falco

6

ИМХО, самое элегантное и лучшее решение для этого - иметь более одной реализации Foo вместе с фабричным методом для его создания:

class Foo {
  protected Foo() {
    // Prevent direct instantiation
  }

  public void bar() {
    // Do something
  }

  public static void getFoo() {
    return fooEnabled ? new Foo() : new NopFoo();
  }
}

class NopFoo extends Foo {
  public void bar() {
    // Do nothing
  }
}

Или вариант:

class Foo {
  protected Foo() {
    // Prevent direct instantiation
  }

  public void bar() {
    // Do something
  }

  public static void getFoo() {
    return fooEnabled ? new Foo() : NOP_FOO;
  }

  private static Foo NOP_FOO = new Foo() {
    public void bar() {
      // Do nothing
    }
  };
}

Как указывает sstan, еще лучше было бы использовать интерфейс:

public interface Foo {
  void bar();

  static Foo getFoo() {
    return fooEnabled ? new FooImpl() : new NopFoo();
  }
}

class FooImpl implements Foo {
  FooImpl() {
    // Prevent direct instantiation
  }

  public void bar() {
    // Do something
  }
}

class NopFoo implements Foo {
  NopFoo() {
    // Prevent direct instantiation
  }

  public void bar() {
    // Do nothing
  }
}

Адаптируйте это к остальным вашим обстоятельствам (создаете ли вы каждый раз новый Foo или повторно используете тот же экземпляр и т. Д.)


1
Это не то же самое, что ответ Конрада. Мне это нравится, но я думаю, было бы безопаснее, если бы вместо использования наследования классов вы использовали интерфейс, как предлагали другие в своих ответах. Причина проста: разработчику слишком легко добавить метод Fooи забыть добавить нерабочую версию метода в расширенный класс, таким образом обойдя желаемое поведение.
sstan

2
@sstan Ты прав, так бы лучше. Я сделал это так, чтобы как можно меньше модифицировать исходный пример Кристины, чтобы не отвлекаться, но это актуально. Я добавлю ваше предложение к своему ответу.
Pepijn Schmitz

1
Я думаю, вы упустили момент. Вы определяете, будет ли isFooEnabledпри создании Foo. Это слишком рано. В исходном коде это делается при запуске метода. При этом стоимость isFooEnabledможет измениться.
Николас Барбулеско

1
@NicolasBarbulesco Вы не знаете, что fooIsEnabled может быть константой. Или его можно было бы использовать так, чтобы оно было практически постоянным. Или он может быть установлен в нескольких четко определенных местах, чтобы было легко каждый раз получать новый экземпляр Foo. Или было бы приемлемо получать новый экземпляр Foo каждый раз, когда он используется. Вы не знаете, поэтому я написал: «адаптируйте это к остальным вашим обстоятельствам».
Pepijn Schmitz

@PepijnSchmitz - Конечно, fooIsEnabled может быть постоянным. Но ничто не говорит нам о том, что оно будет постоянным. Итак, я рассматриваю общий случай.
Николас Барбулеско,

5

У меня другой подход: есть

interface Foo {
  public void bar();
  public void baz();
  public void bat();
}

class FooImpl implements Foo {
  public void bar() {
    //...
  }
  public void baz() {
    //...
  }
  public void bat() {
    //...
  }
}

class NullFoo implements Foo {
  static NullFoo DEFAULT = new NullFoo();
  public void bar() {}
  public void baz() {}
  public void bat() {}
}

}

и тогда вы можете сделать

(isFooEnabled ? foo : NullFoo.DEFAULT).bar();

Может быть , вы даже можете заменить isFooEnabledс Fooпеременной , которая либо удерживает , FooImplчтобы использовать или NullFoo.DEFAULT. Тогда вызов снова проще:

Foo toBeUsed = isFooEnabled ? foo : NullFoo.DEFAULT;
toBeUsed.bar();
toBeUsed.baz();
toBeUsed.bat();

Кстати, это называется «нулевой шаблон».


Общий подход хорош, но использование этого выражения (isFooEnabled ? foo : NullFoo.DEFAULT).bar();кажется немного неуклюжим. Имейте третью реализацию, которая делегирует одну из существующих реализаций. Вместо изменения значения поля isFooEnabledможно изменить цель делегирования. Это уменьшает количество веток в коде
SpaceTrucker

1
Но вы депортируете внутреннюю кулинарию класса Fooв телефонный код! Как мы могли знать isFooEnabled? Это внутреннее поле в классе Foo.
Николас Барбулеско

3

В аналогичном функциональном подходе к ответу @Colin с лямбда-функциями Java 8 можно обернуть код включения / отключения условной функции в метод защиты ( executeIfEnabled), который принимает лямбда-действие действия, для которого можно выполнить условно выполняемый код. прошло.

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

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

Например:

class Foo {
    private Boolean _fooIsEnabled;

    public Foo(Boolean isEnabled) {
        _fooIsEnabled = isEnabled;
    }

    private void executeIfEnabled(java.util.function.Consumer someAction) {
        // Conditional toggle short circuit
        if (!_fooIsEnabled) return;

        // Invoke action
        someAction.accept(null);
    }

    // Wrap the conditionally executed code in a lambda
    public void bar() {
        executeIfEnabled((x) -> {
            System.out.println("Bar invoked");
        });
    }

    // Demo with closure arguments and locals
    public void baz(int y) {
        executeIfEnabled((x) -> {
            System.out.printf("Baz invoked %d \n", y);
        });
    }

    public void bat() {
        int z = 5;
        executeIfEnabled((x) -> {
            System.out.printf("Bat invoked %d \n", z);
        });
    }

С тестом:

public static void main(String args[]){
    Foo enabledFoo = new Foo(true);
    enabledFoo.bar();
    enabledFoo.baz(33);
    enabledFoo.bat();

    Foo disabledFoo = new Foo(false);
    disabledFoo.bar();
    disabledFoo.baz(66);
    disabledFoo.bat();
}

Также похож на подход Damix, без необходимости в реализации интерфейса и анонимных классов с переопределением метода.
StuartLC

2

Как указано в других ответах, шаблон разработки стратегии является подходящим шаблоном проектирования для упрощения этого кода. Я проиллюстрировал это здесь, используя вызов метода через отражение, но есть любое количество механизмов, которые вы можете использовать для получения того же эффекта.

class Foo {

  public static void main(String[] args) {
      Foo foo = new Foo();
      foo.fooIsEnabled = false;
      foo.execute("bar");
      foo.fooIsEnabled = true;
      foo.execute("baz");
  }

  boolean fooIsEnabled;

  public void execute(String method) {
    if(!fooIsEnabled) {return;}
    try {
       this.getClass().getDeclaredMethod(method, (Class<?>[])null).invoke(this, (Object[])null);
    }
    catch(Exception e) {
       // best to handle each exception type separately
       e.printStackTrace();
    }
  }

  // Changed methods to private to reinforce usage of execute method
  private void bar() {
    System.out.println("bar called");
    // bar stuff here...
  }
  private void baz() {
    System.out.println("baz called");
    // baz stuff here...
  }
  private void bat() {
    System.out.println("bat called");
    // bat stuff here...
  }
}

Имеется дело с отражением немного неудобно, если уже есть классы, которые делают это за вас, как уже упоминалось Proxy.
SpaceTrucker,

Как ты мог foo.fooIsEnabled ...? Априори это внутреннее поле объекта, мы не можем и не хотим видеть его снаружи.
Николас Барбулеско

2

Если бы только java была немного лучше функциональна. Думаю, самое лучшее решение - это создать класс, который обертывает одну функцию, поэтому он будет вызываться только тогда, когда foo включен.

abstract class FunctionWrapper {
    Foo owner;

    public FunctionWrapper(Foo f){
        this.owner = f;
    }

    public final void call(){
        if (!owner.isEnabled()){
            return;
        }
        innerCall();
    }

    protected abstract void innerCall();
}

а затем реализовать bar, bazи batкак анонимные классы , которые , которые проходят FunctionWrapper.

class Foo {
    public boolean fooIsEnabled;

    public boolean isEnabled(){
        return fooIsEnabled;
    }

    public final FunctionWrapper bar = new FunctionWrapper(this){
        @Override
        protected void innerCall() {
            // do whatever
        }
    };

    public final FunctionWrapper baz = new FunctionWrapper(this){
        @Override
        protected void innerCall() {
            // do whatever
        }
    };

    // you can pass in parms like so 
    public final FunctionWrapper bat = new FunctionWrapper(this){
        // some parms:
        int x,y;
        // a way to set them
        public void setParms(int x,int y){
            this.x=x;
            this.y=y;
        }

        @Override
        protected void innerCall() {
            // do whatever using x and y
        }
    };
}

Другая идея

Используйте решение glglgl с нулевым значением, но производите FooImplи NullFooвнутренние классы (с частными конструкторами) следующего класса:

class FooGateKeeper {

    public boolean enabled;

    private Foo myFooImpl;
    private Foo myNullFoo;

    public FooGateKeeper(){
        myFooImpl= new FooImpl();
        myNullFoo= new NullFoo();
    }

    public Foo getFoo(){
        if (enabled){
            return myFooImpl;
        }
        return myNullFoo;
    }  
}

таким образом вам не нужно беспокоиться о том, чтобы не забыть использовать (isFooEnabled ? foo : NullFoo.DEFAULT).


Скажем, у вас есть: Foo foo = new Foo()чтобы позвонить, barвы должны написатьfoo.bar.call()
Колин

1

Кажется, что класс ничего не делает, когда Foo не включен, так почему бы не выразить это на более высоком уровне, где вы создаете или получаете экземпляр Foo?

class FooFactory
{
 static public Foo getFoo()
 {
   return isFooEnabled ? new Foo() : null;
 }
}
 ...
 Foo foo = FooFactory.getFoo();
 if(foo!=null)
 {
   foo.bar();
   ....
 }     

Это работает, только если isFooEnabled является константой. В общем случае вы можете создать свою аннотацию.


Конрад, можно развить аннотацию?
Николас Барбулеско

Исходный код определяет, fooIsEnabledкогда вызывается метод. Вы делаете это до создания экземпляра Foo. Это слишком рано. Тем временем значение может измениться.
Николас Барбулеско,

Я думаю, вы упустили момент. Априори, isFooEnabledэто поле экземпляра Fooобъектов.
Николас Барбулеско,

1

Я не знаком с синтаксисом Java. Предположение, что в Java есть полиморфизм, статическое свойство, абстрактный класс и метод:

    public static void main(String[] args) {
    Foo.fooIsEnabled = true; // static property, not particular to a specific instance  

    Foo foo = new bar();
    foo.mainMethod();

    foo = new baz();
    foo.mainMethod();

    foo = new bat();
    foo.mainMethod();
}

    public abstract class Foo{
      static boolean fooIsEnabled;

      public void mainMethod()
      {
          if(!fooIsEnabled)
              return;

          baMethod();
      }     
      protected abstract void baMethod();
    }
    public class bar extends Foo {
        protected override baMethod()
        {
            // bar implementation
        }
    }
    public class bat extends Foo {
        protected override baMethod()
        {
            // bat implementation
        }
    }
    public class baz extends Foo {
        protected override baMethod()
        {
            // baz implementation
        }
    }

Кто сказал, что включение - это статическое свойство класса?
Николас Барбулеско

Что new bar()должно значить?
Николас Барбулеско

В Java мы пишем класс Nameс большой буквы.
Николас Барбулеско

Это требует слишком сильного изменения вызывающего кода. Мы называем метод обычно: bar(). Если вам нужно это изменить, вы обречены.
Николас Барбулеско,

1

Обычно у вас есть флаг, если он установлен, вызов функции следует пропустить. Поэтому я думаю, что мое решение было бы глупым, но вот оно.

Foo foo = new Foo();

if (foo.isEnabled())
{
    foo.doSomething();
}

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

class Proxy<T>
{
    private T obj;
    private Method<T> proxy;

    Proxy(Method<T> proxy)
    {
        this.ojb = new T();
        this.proxy = proxy;
    }

    Proxy(T obj, Method<T> proxy)
    {
        this.obj = obj;
        this.proxy = proxy;
    }

    public T object ()
    {
        this.proxy(this.obj);
        return this.obj;
    }
}

class Test
{
    public static void func (Foo foo)
    {
        // ..
    }

    public static void main (String [] args)
    {
        Proxy<Foo> p = new Proxy(Test.func);

        // how to use
        p.object().doSomething();
    }
}

class Foo
{
    public void doSomething ()
    {
        // ..
    }
}

Для вашего первого блока кода вам понадобится видимый метод isEnabled(). Априори включение - это внутреннее приготовление Foo, а не открытое.
Николас Барбулеско,

Вызывающий код не может и не хочет знать, включен ли объект .
Николас Барбулеско

0

Есть еще одно решение, использующее делегат (указатель на функцию). У вас может быть уникальный метод, который сначала выполняет проверку, а затем вызывает соответствующий метод в соответствии с вызываемой функцией (параметром). Код C #:

internal delegate void InvokeBaxxxDelegate();

class Test
{
    private bool fooIsEnabled;

    public Test(bool fooIsEnabled)
    {
        this.fooIsEnabled = fooIsEnabled;
    }

    public void Bar()
    {
        InvokeBaxxx(InvokeBar);
    }

    public void Baz()
    {
        InvokeBaxxx(InvokeBaz);
    }

    public void Bat()
    {
        InvokeBaxxx(InvokeBat);
    }

    private void InvokeBaxxx(InvokeBaxxxDelegate invoker)
    {
        if (!fooIsEnabled) return;
        invoker();
    }

    private void InvokeBar()
    {
        // do Invoke bar stuff
        Console.WriteLine("I am Bar");
    }

    private void InvokeBaz()
    {
        // do Invoke bar stuff
        Console.WriteLine("I am Baz");
    }

    private void InvokeBat()
    {
        // do Invoke bar stuff
        Console.WriteLine("I am Bat");
    }
}

2
Правильно, он помечен как Java, поэтому я подчеркиваю и написал «Код на C #», поскольку я не знаю Java. Поскольку это вопрос о шаблоне дизайна, язык не важен.
ehh

Ой! Я так понимаю, извините за это, только попытался помочь и найти решение. Спасибо
эхх
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.