Твърде много параметри в методите на Java, част 6: Връщане на метода

В текущата поредица от публикации, които пиша за намаляване на броя на параметрите, необходими за извикване на Java методи и конструктори, досега съм се фокусирал върху подходи, които пряко засягат самите параметри (персонализирани типове, обекти на параметри, модел на конструктор, претоварване на метода и именуване на метод). Като се има предвид това, за мен може да изглежда изненадващо да посветя публикация в тази поредица на това как методите на Java предоставят връщани стойности. Възвръщаемите стойности на методите обаче могат да повлияят на параметрите, които методите приемат, когато разработчиците избират да предоставят „връщащи“ стойности чрез задаване или промяна на предоставените параметри, вместо или в допълнение към по-традиционните механизми за връщане на методите.

„Традиционните начини“, по които неконструкторният метод връща стойност, могат да бъдат посочени и в сигнатурата на метода. Най-често разпознаваният подход за връщане на стойност от Java метод е чрез декларирания му тип връщане. Това често работи добре, но едно от най-често срещаните разочарования е позволено да връща само една стойност от метод на Java.

Механизмът за обработка на изключения на Java също е друг подход за запазване на "резултат" от метод за повикващите. По-специално отметнатите изключения се рекламират на повикващия чрез клаузата за хвърляне. Всъщност Джим Уолдо в книгата си Java: Добрите части заявява, че е по-лесно да се разберат изключенията на Java, когато човек мисли за изключенията на Java като друг тип връщане на метод, ограничен до тип Throwable.

Въпреки че типът на връщане на метода и изхвърлените изключения са предназначени като основни подходи за методите за връщане на информация на повикващите, понякога е изкушаващо да се върнат данни или състояние чрез параметрите, предадени в метода. Когато методът трябва да върне повече от една информация, връщането на единична стойност на Java методите може да изглежда ограничаващо. Въпреки че изключенията осигуряват друг начин за комуникация обратно на повикващия, изглежда почти общоприето, че изключенията трябва да се използват само за докладване на извънредни ситуации, а не за докладване на „нормални“ данни или да се използват в контролния поток. Като се има предвид, че само един обект или примитив може да бъде върнат от метод и че изключенията позволяват връщането само наThrowable и трябва да се използва само за отчитане на изключителни ситуации, става все по-привлекателно за разработчика на Java да отвлича параметри като алтернативен маршрут за връщане на данни на повикващия.

Техниката, която разработчикът може да използва, за да приложи параметри на метода като носители за връщане на данни, е да приеме параметри, които могат да се променят и да мутира състоянието на предадените обекти. Съдържанието на тези променящи се обекти може да бъде променено от метода и след това повикващият да има достъп до обекта, който е предоставил, за да определи новите му настройки на състоянието, които са приложени от извикания метод. Въпреки че това може да се направи с всеки изменяем обект, колекциите изглеждат особено привлекателни за разработчика, който се опитва да предаде стойности обратно на повикващия чрез параметри.

Има някои недостатъци при връщането на състоянието обратно към извиканото чрез предоставените параметри. Този подход често нарушава принципа на най-малкото учудване, тъй като повечето разработчици на Java вероятно очакват параметрите да бъдат Входящи, а не ИЗХОДНИ (а Java не предоставя никаква поддръжка на код за определяне на разликата). Боб Мартин го казва по този начин в книгата си „Чист код“, „Като цяло изходните аргументи трябва да се избягват“. Друг недостатък на използването на аргументи като средство за метод за предоставяне на състояние или изход за повикващия е, че това добавя към бъркотията от аргументи, предадени на метод. Имайки това предвид, останалата част от тази публикация се фокусира върху алтернативи на връщането на множество стойности чрез предадени параметри.

Въпреки че методите на Java могат да върнат само един обект или примитив, това наистина не е голямо ограничение, когато човек смята, че обектът може да бъде почти всичко, което ние искаме да бъде. Има няколко подхода, които съм виждал, но не препоръчвам. Едно от тях е връщането на масив или колекция от екземпляри на обекти с всекиObjectкато различно и различно и често несвързано „нещо“. Например методът може да върне три стойности като три елемента от масив или колекция. Вариант на този подход е да се използва двойка кортеж или n-размер кортеж за връщане на множество свързани стойности. Друг вариант на този подход е връщането на Java Map, която преобразува произволни ключове в свързаната с тях стойност. Както при другите решения, този подход поставя ненужно бреме върху клиента да знае какви са тези ключове и да получи достъп до стойностите на картата чрез тези ключове.

Следващият списък с кодове съдържа няколко от тези по-малко атрактивни подходи за връщане на множество стойности без отвличане на параметрите на метода за връщане на множество стойности.

Връщане на множество стойности чрез общи структури от данни

 // =============================================================== // NOTE: These examples are intended solely to illustrate a point // and are NOT recommended for production code. // =============================================================== /** * Provide movie information. * * @return Movie information in form of an array where details are mapped to * elements with the following indexes in the array: * 0 : Movie Title * 1 : Year Released * 2 : Director * 3 : Rating */ public Object[] getMovieInformation() { final Object[] movieDetails = {"World War Z", 2013, "Marc Forster", "PG-13"}; return movieDetails; } /** * Provide movie information. * * @return Movie information in form of a List where details are provided * in this order: Movie Title, Year Released, Director, Rating. */ public List getMovieDetails() { return Arrays.asList("Ender's Game", 2013, "Gavin Hood", "PG-13"); } /** * Provide movie information. * * @return Movie information in Map form. Characteristics of the movie can * be acquired by looking in the map for these key elements: "Title", "Year", * "Director", and "Rating"./ */ public Map getMovieDetailsMap() { final HashMap map = new HashMap(); map.put("Title", "Despicable Me 2"); map.put("Year", 2013); map.put("Director", "Pierre Coffin and Chris Renaud"); map.put("Rating", "PG"); return map; } 

Показаните по-горе подходи отговарят на намерението да не се предават данни обратно на повикващия чрез параметрите на извиканите методи, но все още има ненужна тежест върху повикващия, за да знае интимни подробности за върнатата структура на данните. Хубаво е да намалите броя на параметрите до метода и да не нарушавате принципа на най-малката изненада, но не е толкова хубаво да изисквате от клиента да знае тънкостите на сложна структура от данни.

Предпочитам да пиша персонализирани обекти за връщанията си, когато трябва да върна повече от една стойност. Това е малко повече работа, отколкото използването на масив, колекция или структура на кортеж, но много малкото допълнителна работа (обикновено няколко минути със съвременни Java IDE) се отплаща с четливост и плавност, които не са налични при тези по-общи подходи. Вместо да се налага да обяснявам с Javadoc или да изисквам от потребителите на кода ми да четат внимателно кода ми, за да знаят кои параметри се предоставят в какъв ред в масива или колекцията или коя стойност е коя в кортежа, моите персонализирани обекти за връщане могат да имат дефинирани методи на тези, които казват на клиента какво точно предоставят.

Следващите фрагменти на кода илюстрират прост Movieклас, до голяма степен генериран от NetBeans, който може да се използва като тип на връщане, заедно с кода, който може да върне екземпляр на този клас, а не по-обща и по-малко четима структура от данни.

Movie.java

package dustin.examples; import java.util.Objects; /** * Simple Movie class to demonstrate how easy it is to provide multiple values * in a single Java method return and provide readability to the client. * * @author Dustin */ public class Movie { private final String movieTitle; private final int yearReleased; private final String movieDirectorName; private final String movieRating; public Movie(String movieTitle, int yearReleased, String movieDirectorName, String movieRating) { this.movieTitle = movieTitle; this.yearReleased = yearReleased; this.movieDirectorName = movieDirectorName; this.movieRating = movieRating; } public String getMovieTitle() { return movieTitle; } public int getYearReleased() { return yearReleased; } public String getMovieDirectorName() { return movieDirectorName; } public String getMovieRating() { return movieRating; } @Override public int hashCode() { int hash = 3; hash = 89 * hash + Objects.hashCode(this.movieTitle); hash = 89 * hash + this.yearReleased; hash = 89 * hash + Objects.hashCode(this.movieDirectorName); hash = 89 * hash + Objects.hashCode(this.movieRating); return hash; } @Override public boolean equals(Object obj) { if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } final Movie other = (Movie) obj; if (!Objects.equals(this.movieTitle, other.movieTitle)) { return false; } if (this.yearReleased != other.yearReleased) { return false; } if (!Objects.equals(this.movieDirectorName, other.movieDirectorName)) { return false; } if (!Objects.equals(this.movieRating, other.movieRating)) { return false; } return true; } @Override public String toString() { return "Movie{" + "movieTitle=" + movieTitle + ", yearReleased=" + yearReleased + ", movieDirectorName=" + movieDirectorName + ", movieRating=" + movieRating + '}'; } } 

Връщане на множество подробности в един обект

 /** * Provide movie information. * * @return Movie information. */ public Movie getMovieInfo() { return new Movie("Oblivion", 2013, "Joseph Kosinski", "PG-13"); } 

Простото писане на Movieклас ми отне около 5 минути. Използвах съветника за създаване на клас NetBeans, за да избера името и пакета на класа и след това въведох четирите атрибута на класа. Оттам нататък просто използвах механизма "Вмъкване на код" на NetBeans, за да вмъкна методите за достъп "get" заедно с заменените toString (), hashCode () и equals (Object) методи. Ако не мислех, че имам нужда от малко от това, бих могъл да поддържам класа по-опростен, но наистина е лесно да се създаде такъв, какъвто е. Сега имам много по-използваем тип връщане и това е отразено от кода, който използва класа. Не се нуждае от почти толкова Javadoc коментари за типа на връщане, защото този тип говори сам за себе си и рекламира съдържанието си с методите си "get".Чувствам, че малкото допълнително усилие за създаването на тези прости класове за връщане на множество стойности се отплаща с огромни дивиденти в сравнение с алтернативи като връщане на състояние чрез параметри на метода или използване на по-общи и по-трудни за използване структури за връщане на данни.

Не е изненадващо, че персонализиран тип, който да съхранява множество стойности, които трябва да бъдат върнати на повикващия, е привлекателно решение. В края на краищата това концептуално е много подобно на концепциите, за които съм писал в блогове преди, свързани с използването на персонализирани типове и параметри на обекти за предаване на множество свързани параметри, вместо да ги предавам поотделно. Java е обектно-ориентиран език и затова ме изненадва, когато не виждам обекти, използвани по-често в Java кода за организиране на параметри И връщане на стойности в хубав пакет.

Предимства и предимства

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

Разходи и недостатъци

Виждам много малък недостатък при писането на персонализирани типове с множество стойности, които да се използват като типове на връщане от Java методите. Може би най-често претендираната цена е цената за писане и тестване на тези класове, но тази цена е доста малка, тъй като тези класове обикновено са прости и защото съвременните IDE правят по-голямата част от работата вместо нас. Тъй като IDE го правят автоматично, кодът обикновено е правилен. Класовете са толкова прости, че са лесно четими от рецензенти на код и са лесни за тестване.