Можно ли классу использовать собственный публичный метод?


23

Задний план

В настоящее время у меня есть ситуация, когда у меня есть объект, который передается и принимается устройством. Это сообщение имеет несколько конструкций, а именно:

public void ReverseData()
public void ScheduleTransmission()

ScheduleTransmissionМетод необходимо вызвать ReverseDataметод всякий раз , когда он вызывается. Тем не менее, бывают случаи, когда мне нужно вызывать ReverseDataизвне (и я должен добавить вне пространства имен полностью ), из которого создается объект в приложении.

Что касается «получения», я имею в виду, что ReverseDataон будет вызываться извне в object_receivedобработчике событий для отмены обращения данных.

Вопрос

В целом допустимо ли для объекта вызывать свои публичные методы?


5
Нет ничего плохого в вызове публичного метода из его собственного. Но, основываясь на именах ваших методов, ReverseData () звучит для меня немного опасно, если он является публичным методом, если он обращает внутренние данные. Что если ReverseData () вызывается вне объекта, а затем вызывается снова с помощью ScheduleTransmission ().
Каан

@Kaan Это не настоящие имена моих методов, а тесно связанные. Фактически, «обратные данные» обращают только 8 бит всего слова и выполняются, когда мы принимаем и передаем.
Снуп

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

2
@ StevieV Я считаю, что это только усиливает беспокойство, которое вызывает Каан. Звучит так, как будто вы представляете метод, который изменяет состояние объекта, и это состояние зависит, прежде всего, от того, сколько раз вызывается метод. Это создает кошмар при попытке отследить, каково состояние объекта в вашем коде. Для меня это больше похоже на то, что вы бы извлекли выгоду из отдельных типов данных из этих концептуальных состояний, так что вы можете сказать, что находится в любом конкретном фрагменте кода, не беспокоясь об этом.
jpmc26

2
@StevieV что-то вроде Data и SerializedData. Данные - это то, что видит код, а SerializedData - это то, что отправляется через сеть. Затем выполните обратное преобразование данных (или, вернее, сериализацию / десериализацию данных) из одного типа в другой.
csiz

Ответы:


33

Я бы сказал, что это не только приемлемо, но и поощряется, особенно если вы планируете разрешать расширения. Чтобы поддерживать расширения класса в C #, вам необходимо пометить метод как виртуальный в соответствии с комментариями ниже. Однако вы можете задокументировать это, чтобы кто-то не удивился, когда переопределение ReverseData () изменит способ работы ScheduleTransmission ().

Это действительно сводится к дизайну класса. ReverseData () звучит как фундаментальное поведение вашего класса. Если вам нужно использовать это поведение в других местах, вы, вероятно, не хотите иметь другие версии этого. Вам просто нужно быть осторожным, чтобы не допустить попадания деталей, специфичных для ScheduleTransmission (), в ReverseData (). Это создаст проблемы. Но так как вы уже используете это за пределами класса, вы, вероятно, уже продумали это.


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

2
«чтобы кто-то не удивился, когда переопределение ReverseData () изменяет способ работы ScheduleTransmission ()» : нет, не совсем. По крайней мере, не в C # (обратите внимание, что вопрос имеет тег C #). Если не ReverseData()является абстрактным, независимо от того, что вы делаете в дочернем классе, всегда будет вызываться оригинальный метод.
Арсений Мурзенко

2
@MainMa Или virtual... И если вы переопределяете метод, то по определению он должен быть virtualили abstract.
Pokechu22

1
@ Pokechu22: действительно, virtualтоже работает. Во всех случаях, согласно OP, подпись такова public void ReverseData(), что часть ответа о переопределении вещи немного вводит в заблуждение.
Арсений Мурзенко

@MainMa вы говорите, что если метод базового класса вызывает виртуальный метод, он не ведет себя обычным виртуальным способом, если это не так abstract? Я посмотрел ответы на abstractvs virtualи не увидел, что упомянуто: довольно абстрактные просто означает, что в этой базе не дано никакой версии.
JDługosz

20

Абсолютно.

Видимость метода имеет единственную цель - разрешить или запретить доступ к методу вне класса или внутри дочернего класса; публичные, защищенные и приватные методы всегда могут быть вызваны внутри самого класса.

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

Тем не менее, вы можете внимательно следить за следующими шаблонами:

  1. Метод Hello()вызывает World(string, int, int)так:

    Hello()
    {
        this.World("Some magic value here", 0, 100);
    }

    Избегайте этой картины. Вместо этого используйте необязательные аргументы . Необязательные аргументы облегчают обнаружение: вызывающий пользователь Hello(не обязательно будет знать, что существует метод, позволяющий вызывать метод со значениями по умолчанию. Необязательные аргументы также самодокументированы. World()не показывает вызывающему абоненту, каковы фактические значения по умолчанию.

  2. Метод Hello(ComplexEntity)вызывает World(string, int, int)так:

    Hello(ComplexEntity entity)
    {
        this.World(entity.Name, entity.Start, entity.Finish);
    }

    Вместо этого используйте перегрузки . Причина та же: лучшая открытость. Вызывающий может сразу увидеть все перегрузки через IntelliSense и выбрать правильный.

  3. Метод просто вызывает другие общедоступные методы, не добавляя существенного значения.

    Подумай дважды. Вам действительно нужен этот метод? Или вы должны удалить его и позволить вызывающей стороне вызывать различные методы? Подсказка: если название метода кажется неправильным или его сложно найти, вам, вероятно, следует удалить его.

  4. Метод проверяет ввод и затем вызывает другой метод:

    Hello(string name, int start, int end)
    {
        if (name == null) throw new ArgumentNullException(...);
        if (start < 0) throw new OutOfRangeException(...);
        ...
        if (end < start) throw new ArgumentException(...);
    
        this.World(name, start, end);
    }

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


Подходят ли те же мысли, если ReverseData на самом деле является внутренним и используется во всем пространстве имен, содержащем экземпляры «объекта»?
Снуп

1
@ StevieV: да, конечно.
Арсений Мурзенко

@MainMa Я не понимаю, в чем причина пунктов 1 и 2
Kapol

@Kapol: спасибо за ваш отзыв. Я отредактировал свой ответ, добавив пояснения к первым двум пунктам.
Арсений Мурзенко

Даже в случае 1 я обычно предпочитаю перегрузки, а не необязательные аргументы. Если параметры таковы, что использование перегрузок не является опцией или создает особенно большое количество перегрузок, я бы создал собственный тип и использовал бы его в качестве аргумента (как в предложении 2), или реорганизовал код.
Брайан

8

Если что-то общедоступно, оно может быть вызвано в любое время любой системой. Там нет причин, вы не можете быть одной из этих систем тоже!

В высоко оптимизированных библиотеках вы хотите скопировать идиому, изложенную в java.util.ArrayList#ensureCapacity(int).

ensureCapacity

  • ensureCapacity является публичным
  • ensureCapacity имеет все необходимые проверки границ и значения по умолчанию и т. д.
  • ensureCapacity звонки ensureExplicitCapacity

ensureCapacityInternal

  • ensureCapacityInternal является частным
  • ensureCapacityInternal имеет минимальную проверку ошибок, потому что все входные данные поступают из класса
  • ensureCapacityInternal ТАКЖЕ звонит ensureExplicitCapacity

ensureExplicitCapacity

  • ensureExplicitCapacity ТАКЖЕ частный
  • ensureExplicitCapacityне имеет проверки ошибок
  • ensureExplicitCapacity делает реальную работу
  • ensureExplicitCapacityне вызывается нигде, кроме как ensureCapacityиensureCapacityInternal

Таким образом, код, которому вы доверяете, получает привилегированный (и более быстрый!) Доступ, потому что вы знаете, что его входные данные хороши. Код, которому вы не доверяете, проходит определенную строгость, чтобы проверить его целостность и взломать или предоставить значения по умолчанию или иным образом обработать неверные данные. Оба они направляются в то место, которое действительно работает.

Однако это используется в ArrayList, одном из наиболее часто используемых классов в JDK. Вполне возможно, что ваше дело не требует такого уровня сложности и строгости. Короче говоря, если бы все ensureCapacityInternalзвонки были заменены ensureCapacityзвонками, производительность все равно была бы действительно очень хорошей. Это микрооптимизация, вероятно, только после тщательного рассмотрения.


3

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

Публичные методы обычно имеют контракт: «возьмите объект в согласованном состоянии, сделайте что-нибудь разумное, оставьте объект в (возможно, другом) согласованном состоянии». Здесь «непротиворечивый» может означать, например, что Lengtha List<T>не больше, чем его Capacityи что ссылка на элементы с индексами от 0to Length-1не сгенерирует.

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


1

Другим примером, когда вызов публичного метода внутри другого публичного метода совершенно нормально, является подход CanExecute / Execute. Я использую его, когда мне нужно и подтверждение и сохранение инварианта .

Но в целом я всегда осторожен с этим. Если метод a()вызывается внутри метода b(), это означает, что метод a()является деталью реализации b(). Очень часто это указывает на то, что они принадлежат к разным уровням абстракции. И тот факт, что они оба публичны, заставляет меня задуматься, является ли это нарушением принципа единой ответственности или нет.


-5

Извините, но мне придется не согласиться с большинством других ответов «да, можете» и сказать, что:

Я бы не рекомендовал классу вызывать один публичный метод из другого

Есть несколько потенциальных проблем с этой практикой.

1: бесконечный цикл в унаследованном классе

Таким образом, ваш базовый класс вызывает method1 из method2, но затем вы или кто-то другой наследует его и скрывает method1 новым методом, который вызывает method2.

2: события, ведение журнала и т. Д.

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

3: многопоточность и другие тупики

Например, InsertComplexData открывает соединение с БД, запускает транзакцию, блокирует таблицу, затем вызывает InsertSimpleData, открывает соединение, запускает транзакцию и ожидает разблокировки таблицы ....

Я уверен, что есть и другие причины, один из ответов касался «вы редактируете method1 и удивляетесь, что method2 начинает вести себя по-другому»

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

Редактировать ----

Давайте рассмотрим конкретный случай в ОП.

у нас не так много деталей, но мы знаем, что ReverseData вызывается каким-либо обработчиком событий, а также методом ScheduleTransmission.

Я полагаю, что обратные данные также изменяют внутреннее состояние объекта

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

Чтобы сделать поток ReverseData безопасным, вы можете добавить блокировку. Но если ScheduleTransmission также должен быть потокобезопасным, вы захотите использовать такую ​​же блокировку.

Самый простой способ сделать это - переместить код ReverseData в закрытый метод и вызвать его для обоих открытых методов. Затем вы можете поместить оператор блокировки в Public Methods и совместно использовать объект блокировки.

Очевидно, вы можете утверждать, что "этого никогда не случится!" или «Я мог бы запрограммировать блокировку другим способом», но суть хорошей практики кодирования заключается в том, чтобы сначала хорошо структурировать ваш код.

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

Вот еще один: вы также можете нарушить DDD. Если ваш объект является объектом домена, его открытые методы должны быть терминами домена, которые что-то значат для бизнеса. В этом случае очень маловероятно, что «купить дюжину яиц» - это то же самое, что «купить 1 яйцо 12 раз», даже если оно начинается таким образом.


3
Эти вопросы больше напоминают плохо продуманную архитектуру, чем общее осуждение принципа дизайна. (Возможно, каждый раз вызывать «1 добавленный» нормально. Если это не так, мы должны программировать по-разному. Может быть, если два метода пытаются монопольно заблокировать один и тот же ресурс, они не должны вызывать друг друга, или мы написали их плохо . Вместо того, чтобы представлять плохие реализации, вы можете сосредоточиться на более веских аргументах, которые рассматривают доступ к публичным методам как универсальный принцип. Например, учитывая хороший аккуратный код, почему это плохо?
двойник

Точно, если это не так, у вас нет места для проведения мероприятия. Точно так же с # 3 все в порядке, пока один из ваших методов в цепочке не потребует транзакции, а затем вы облажались
Ewan

Все эти проблемы в большей степени отражают то, что сам код имеет низкое качество, а не общий принцип. Все три случая просто плохой код. Они могут быть решены с помощью некоторой версии «не делают этого, сделать это вместо того, чтобы » (возможно , с просьбой «что же вы хотите точно?»), И не ставят под сомнение общий принцип класса называя свою собственную общественность методы. Неопределенный потенциал для бесконечной петли - единственный здесь, который касается вопроса в принципе, и даже тогда не решен полностью.
двойник

единственное, что «код» в моем примере - это один открытый метод, вызывающий другой. Если вы думаете, что «плохой код» хорош! Это мое утверждение
Ewan

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