Что является лучшей практикой - вспомогательные методы как экземпляры или статические?


48

Этот вопрос субъективен, но мне было просто любопытно, как к этому подходит большинство программистов. Пример ниже приведен на псевдо-C #, но это также должно относиться к Java, C ++ и другим языкам ООП.

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

class Foo
{
  Bar _bar;

  public void DoSomethingWithBar()
  {
    // Method Call #1.
    DoSomethingWithBarImpl();

    // Method Call #2.
    DoSomethingWithBarImpl(_bar);
  }

  private void DoSomethingWithBarImpl()
  {
    _bar.DoSomething();
  }

  private static void DoSomethingWithBarImpl(Bar bar)
  {
    bar.DoSomething();
  }
}

Моя причина для этого заключается в том, что он проясняет (по крайней мере, на мой взгляд), что вспомогательный метод имеет возможный побочный эффект для других объектов - даже без чтения его реализации. Я обнаружил, что могу быстро использовать методы, которые используют эту практику и, таким образом, помогают мне в отладке вещей.

Что вы предпочитаете делать в своем собственном коде и каковы ваши причины для этого?


3
Я бы убрал C ++ из этого вопроса: в C ++, очевидно, вы бы сделали его бесплатным методом, потому что нет смысла произвольно встраивать его в объект: он отключает ADL.
Матье М.

1
@MatthieuM .: Бесплатный метод или нет, это не имеет значения. Цель моего вопроса состояла в том, чтобы спросить, есть ли какие-либо преимущества в объявлении вспомогательных методов в качестве экземпляров. Таким образом, в C ++ выбор может быть методом экземпляра против свободного метода / функции. У вас есть хорошее замечание по поводу ADL. Так вы говорите, что предпочитаете использовать бесплатные методы? Спасибо!
Илиан

2
для C ++ рекомендуются бесплатные методы. Они увеличивают инкапсуляцию (опосредованно, так как имеют возможность доступа только к общедоступному интерфейсу) и по-прежнему хорошо воспроизводятся из-за ADL (особенно в шаблонах). Однако я не знаю, применимо ли это к Java / C #, C ++ сильно отличается от Java / C #, поэтому я ожидаю, что ответы будут отличаться (и, следовательно, я не думаю, что просить 3 языка вместе иметь смысл).
Матье М.


1
Не пытаясь никого обострить, я никогда не слышал веских причин не использовать статические методы. Я использую их везде, где могу, так как они делают более очевидным, что делает метод.
Шридхар Сарнобат

Ответы:


23

Это действительно зависит. Если значения, с которыми работают ваши помощники, являются примитивами, то статические методы - хороший выбор, как отметил Петер.

Если они являются сложными, то SOLID применяется, более конкретно, S , в I и D .

Пример:

class CookieJar {
      function takeCookies(count:Int):Array<Cookie> { ... }
      function countCookies():Int { ... }
      function ressuplyCookies(cookies:Array<Cookie>
      ... // lot of stuff we don't care about now
}

class CookieFan {
      function getHunger():Float;
      function eatCookies(cookies:Array<Cookie>):Smile { ... }
}

class OurHouse {
      var jake:CookieFan;
      var jane:CookieFan;
      var cookies:CookieJar;
      function makeEveryBodyAsHappyAsPossible():Void {
           //perform a lot of operations on jake, jane and the cookies
      }
      public function cookieTime():Void {
           makeEveryBodyAsHappyAsPossible();
      }
}

Это будет о вашей проблеме. Вы можете сделать makeEveryBodyAsHappyAsPossibleстатический метод, который будет принимать необходимые параметры. Другой вариант:

interface CookieDistributor {
    function distributeCookies(to:Array<CookieFan>):Array<Smile>;
}
class HappynessMaximizingDistributor implements CookieDistributor {
    var jar:CookieJar;
    function distributeCookies(to:Array<CookieFan>):Array<Smile> {
         //put the logic of makeEveryBodyAsHappyAsPossible here
    }
}
//and make a change here
class OurHouse {
      var jake:CookieFan;
      var jane:CookieFan;
      var cookies:CookieDistributor;

      public function cookieTime():Void {
           cookies.distributeCookies([jake, jane]);
      }
}

Теперь OurHouseне нужно знать о тонкостях правил распространения файлов cookie. Это должен только сейчас объект, который реализует правило. Реализация абстрагируется от объекта, единственной обязанностью которого является применение правила. Этот объект может быть проверен в изоляции. OurHouseможет быть проверено с помощью простого макета CookieDistributor. И вы можете легко решить изменить правила распространения файлов cookie.

Однако позаботьтесь, чтобы вы не переусердствовали. Например, наличие сложной системы из 30 классов действует как реализация CookieDistributor, где каждый класс просто выполняет крошечную задачу, на самом деле не имеет смысла. Моя интерпретация SRP заключается в том, что он не только диктует, что каждый класс может иметь только одну ответственность, но также и то, что один класс должен нести одну ответственность.

В случае примитивов или объектов, которые вы используете как примитивы (например, объекты, представляющие точки в пространстве, матрицы или что-то еще), статические вспомогательные классы имеют большой смысл. Если у вас есть выбор, и он действительно имеет смысл, тогда вы можете подумать о добавлении метода в класс, представляющий данные, например, целесообразно Pointиметь addметод. Опять же, не переусердствуйте.

Таким образом, в зависимости от вашей проблемы, есть разные способы решения этой проблемы.


18

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

Однако у этого подхода есть серьезное ограничение: такие методы / классы не могут быть легко смоделированы (хотя AFAIK, по крайней мере, для C #, есть платформы для имитации, которые могут достичь даже этого, но они не являются обычным явлением, и, по крайней мере, некоторые из них коммерческий). Поэтому, если у вспомогательного метода есть какая-либо внешняя зависимость (например, БД), которая делает его - то есть его вызывающих - сложным для модульного тестирования, лучше объявить его нестатичным . Это позволяет вводить зависимости, тем самым упрощая вызовы метода для модульного тестирования.

Обновить

Пояснение: выше говорится о служебных классах, которые содержат только вспомогательные методы низкого уровня и (как правило) без состояния. Вспомогательные методы внутри не зависящих от состояния классов являются другой проблемой; извинения за неправильное толкование ФП.

В этом случае я чувствую запах кода: метод класса A, который работает в основном с экземпляром класса B, может на самом деле иметь лучшее место в классе B. Но если я решу оставить его там, где он есть, я предпочитаю вариант 1, как это проще и легче читать.


Разве вы не можете просто передать зависимость статическому вспомогательному методу?
Илиан

1
@JackOfAllTrades, если единственная цель метода состоит в том, чтобы иметь дело с внешним ресурсом, вряд ли есть смысл вводить эту зависимость. В противном случае это может быть решением (хотя в последнем случае метод, вероятно, нарушает принцип единой ответственности и, следовательно, является кандидатом на рефакторинг).
Петер Тёрёк

1
Статика легко «подделывается» - Microsoft Moles или Fakes (в зависимости от VS2010 или VS2012 +) позволяет вам заменить статический метод своей собственной реализацией в ваших тестах. Делает старые насмешливые рамки устаревшими. (это также делает традиционные насмешки, и может также издеваться над конкретными классами). Это бесплатно от MS через менеджер расширений.
gbjbaanb

4

Что вы предпочитаете делать в своем собственном коде

Я предпочитаю # 1 ( this->DoSomethingWithBarImpl();), если, конечно, вспомогательный метод не нуждается в доступе к данным / реализации экземпляра static t_image OpenDefaultImage().

и каковы ваши причины для этого?

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

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

static void ValidateImage(const t_image& image);
void validateImages() const {
    ValidateImage(this->innerImage());
    ValidateImage(this->outerImage());
}

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

наконец, # 2 кажется излишним, если вы используете ОО-языки, где поддерживается мутация (например, constметод).

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