Разделение больших классов на внутренние классы в Java

Я работаю над проектом Android. Я искал повсюду, но не могу найти хорошую стратегию для разделения и упаковки моего кода.

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

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

Я хочу сохранить максимальное количество строк кода на класс до 150. В настоящее время это 278. Я ищу идеи, чтобы разделить их, в частности, как реструктурировать классы, чтобы сохранить абстракцию (private переменные). Каковы Java передовые методы для этого?

Например, вот один из моих основных классов, MainActivity, ~300 строк.


person xyz    schedule 05.06.2015    source источник
comment
если вы не хотите передавать тонны переменных через конструкторы (хороший принцип проектирования заключается в передаче не более 5 переменных в методы/конструкторы), просто инкапсулируйте все эти необходимые переменные в собственный класс (см. Value-Object или Transfer-Object шаблон дизайна). Тогда вам не нужно раскрывать какие-либо переменные в вашей MainActivity и нужно только передать один объект значения соответствующим конструкторам или методам ваших рефакторинговых внутренних классов.   -  person Andy    schedule 05.06.2015
comment
Оба почти похожи, и я, вероятно, передам 4 переменные. Однако новому классу также потребуется модифицировать переменные в основном классе, а это требует раскрытия или дополнительного кода только для проверки этого. В идеале я ищу тип C++ friend или что-то подобное, поэтому я могу передать только объект класса MainActivity.this и все готово.   -  person xyz    schedule 05.06.2015
comment
Вы думаете, как я могу структурно разделить это? Вместо этого попробуйте подумать, как я могу концептуально разделить это? Огромные классы значений с кучей служебных методов (например, String) — это нормально. Другие огромные классы, вероятно, имеют слишком много обязанностей. Каждый класс должен делать одну вещь по отношению к другой.   -  person Kevin Krumwiede    schedule 07.06.2015
comment
@slartidan Спасибо. На самом деле это new Bob().getSomething(). Я писал простой пример, чтобы людям не приходилось читать так много строк, чтобы понять суть. Фиксированный!   -  person xyz    schedule 08.06.2015
comment
@KevinKrumwiede Проблема в том, что концептуально MainActivity абстрагирует именно то, что он должен абстрагировать, и, насколько я могу судить, придерживается OOP. Я считаю, что концептуально не имеет смысла разделять их. Я просто ищу реализацию, которая позволит мне реализовать это в структуре, которая поддерживает эти отношения, но сохраняет управляемость кода.   -  person xyz    schedule 08.06.2015


Ответы (5)


Изменить:

После добавления фактического кода для MainActivivty я бы предложил следующее:

  1. Следуйте архитектурному шаблону MVC/MVP. Вы можете найти ссылку на шаблон, который я написал в конце, но шаблонов гораздо больше - просто выберите тот, который вам нравится. Как только вы поймете, как получить весь код, связанный с пользовательским интерфейсом, за пределами MainActivity, метод addButtons() исчезнет, ​​как и класс CategoriesListener.
  2. На самом деле нет необходимости, чтобы AllPostsFetchAsyncTask был внутренним классом. Реализуйте его как обычный класс вне активности. Чтобы передать данные из этого класса обратно в MainActivity, просто определите интерфейс прослушивателя, который будет реализовывать ваш MainActivity, и передайте MainActivity.this конструктору — когда эта задача будет завершена, он вызовет метод обратного вызова для MainActivity, который в свою очередь, будет обрабатывать привязку данных к Adapter. На самом деле, вы применяете здесь очень плохую практику — сообщая AllPostsFetchAsyncTask о деталях реализации MainActivity, вы создаете ненужную связь между ними, тем самым нарушая принципы инкапсуляции, единой ответственности и открытого и закрытого ООП.

Просто выполнив два описанных выше шага, вы сделаете этот конкретный MainActivity способ короче, чем 150 строк кода.

Сказал, что ваше намерение сохранить активность в 150 строк слишком ограничительно. Это сводится к тому, что если ваши Activity или Fragment нетривиальны, то после того, как вы реализуете onCreate(), onPause(), onResume(), onPrepareOptionsMenu(), onBackStackChanged() и другие стандартные методы жизненного цикла, у вас, вероятно, будет более 150 строк кода еще до того, как вы добавьте логику вашего пользовательского контроллера.

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

  • Никогда не манипулируйте элементами пользовательского интерфейса в контроллерах/презентаторах (Activities, Fragments и Adapters) — инкапсулируйте эти манипуляции в отдельные классы. Эти классы являются представлениями MVC/MVP (в отличие от Android View). и я кладу их в пакеты views или mvcviews. Мои Activities и Fragments обычно не имеют вызовов findViewById() в исходном коде.
  • Поместите все Adapters в отдельный пакет (даже если они длиной 30 строк). Я называю этот пакет controllers.adapters или controllers.listadapters
  • Если вам когда-либо понадобится передать набор связанных данных в ваше приложение, определите POJO (также известный как объект значения) и использовать его для инкапсуляции этих данных. Обычно у меня есть пакет с именем pojos, даже если он содержит только один класс.
  • Определите абстрактные классы AbstractActivity и AbstractFragment и поместите туда любую удобную логику, которая используется вашими контроллерами. Например: у меня всегда есть следующий метод (или аналогичный) в моих AbstractActivity и AbstractFragment:

    public void replaceFragment(Class <? extends Fragment> claz, boolean addToBackStack, Bundle args) { 
        // Code to replace the currently shown fragment with another one 
    }
    
  • Проверьте, есть ли какие-либо сторонние библиотеки, которые могут быть полезны в контексте вашего приложения, и используйте их.

Моя упаковка обычно следует такой схеме:

введите здесь описание изображения

Я знаю, что вы писали, что уже видели некоторые обсуждения MVC, но я все же рекомендую вам попробовать реализацию, которую я предлагаю в этом шаблоне/учебном проекте: https://github.com/techyourchance/android_mvc_template

Надеюсь это поможет

person Vasiliy    schedule 07.06.2015
comment
Согласен насчет строк кода. Java печально известна тем, что требует большого количества шаблонов, а Android в этом отношении еще хуже. Я счастлив, когда активность занимает менее 300 строк. (Конечно, около половины моих строк обычно являются комментариями Javadoc.) - person Kevin Krumwiede; 07.06.2015
comment
@Vasiliy Я добавил свой настоящий MainActivity для справки. Я уже держу адаптеры снаружи. Но AsyncTasks, onClickListeners, каждый требует доступа к MainActivity, поэтому я написал их как внутренние классы. @slartidan Это один из способов, но он больше похож на взлом. Я могу попробовать это, но я надеюсь на более элегантное решение. - person xyz; 08.06.2015
comment
@prakharsingh95, на основе кода для MainActivity я добавил два конкретных подхода, которые вы можете использовать. Смотрите отредактированный ответ - person Vasiliy; 08.06.2015
comment
Спасибо! Я добавил свою новую MainActivity на случай, если кому-то будет интересно. - person xyz; 12.06.2015
comment
ИМХО часто лучше упаковывать по функциям, чем по слоям. Как указано здесь, например, medium.com/mindorks/ - person dagi12; 26.05.2018

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

<сильный>1. Используйте только статические внутренние классы или автономные классы для AsyncTasks: см. any-100-working/3821998#3821998">Фоновая задача, диалоговое окно прогресса, изменение ориентации - есть ли какое-либо 100% рабочее решение?

Важно вот что:

Шаг 2. Пусть AsyncTask удерживает действие с помощью члена данных, устанавливаемого с помощью конструктора и установщика.

Шаг 5. В onCreate(), если getLastNonConfigurationInstance() не равен null, приведите его к классу AsyncTask и вызовите установщик, чтобы связать новую активность с задачей.

Вы заметите, что вам придется регистрировать и отменять регистрацию ваших компонентов на основе методов жизненного цикла Android. Это важно знать, всегда следите за жизненным циклом Android!

Помня об этом, вы всегда будете приходить к правильным ответам, касающимся отделения Android.

<сильный>2. При необходимости используйте классы хранения данных.

Здесь это не относится к Activity:

// Stores the fetched dataMap
ArrayList<HashMap<String, String>> arrayList;

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

Доступ к данным и их хранение можно осуществлять разными способами: http://developer.android.com/guide/faq/framework.html#3

В вашем случае это может быть применимо:

  • Общедоступное статическое поле/метод

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

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

<сильный>3. Связь с вашей активностью может осуществляться следующим образом: http://developer.android.com/guide/components/fragments.html#CommunicatingWithActivity

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

Как сказано в 1., жизненный цикл Android — это ключ ко всему.

<сильный>4. Внедрение зависимостей — очень важная тема, и вы можете либо использовать для этого фреймворк (например, Dagger 2 или RoboGuice), либо сделать это по-своему. Убедитесь, что ваш инжектор знает зависимости (например, каким кнопкам нужны какие ClickListeners и информация или какие данные нужны вашему адаптеру) и свяжите их вместе. Всегда рассматривая жизненный цикл, вы увидите, какие интерфейсы и какие методы вам нужны и когда их вызывать.

<сильный>5. Не беспокойтесь о количестве строк кода. Если ваш дизайн последователен и имеет смысл, у вас не будет проблем с читабельностью даже при 500 строках. Кстати. при правильном документировании вашего кода он легко превышает 150 строк кода. Итак, снова беспокоиться об этом.

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

person einschnaehkeee    schedule 08.06.2015
comment
Я не могу полностью согласиться с № 5: не беспокойтесь о количестве строк кода. Хотя 500 строк кода на класс (включая javadoc) для Android в определенных случаях вполне достаточно, всегда следует стараться минимизировать объем кода. В некоторых случаях, если у меня есть выбор между использованием вызова API в одну строку и его собственной реализацией в 20 строк (более эффективно), я выбираю более короткий вариант для удобочитаемости кода. - person Vasiliy; 10.06.2015
comment
Кроме того: никто не начинает программировать со знанием всех концепций, поэтому должны быть какие-то простые метрики, указывающие на то, что вы, вероятно, делаете это неправильно, чувак. Количество строк — одна из таких метрик, и опытные разработчики слишком легко отбрасывают ее. Я могу сказать это после прочтения некоторых частей AOSP - во многих случаях его можно было бы сделать намного более читабельным... - person Vasiliy; 10.06.2015
comment
Ну, вы, безусловно, правы, что с большим опытом приходит лучшее понимание концепций и структуры. Но на самом деле существуют огромные различия в зависимости от платформ, для которых вы разрабатываете; особенно с ограниченными ресурсами, такими как мобильные или встроенные системы. Производительность - действительно важная тема, например. в контексте Android вам абсолютно необходимо убедиться, что каждое вычисление выполняется менее чем за 16 мс, чтобы поддерживать плавные 60 кадров. Если вы этого не сделаете, он станет более изменчивым, и пользователь удалит ваше приложение. Вот почему вы иногда не можете написать самый элегантный код.... - person einschnaehkeee; 10.06.2015
comment
... Вы не можете выделить слишком много памяти для своего приложения, не можете создавать экземпляры многих объектов, иметь уродливые статические поля и т. д. Тем не менее, есть еще много способов отделить ваши компоненты для большей читабельности, но в случае с Android я не стал бы устанавливать планку ниже 150, но я думаю, что это зависит от многих факторов, таких как время, сложность, опыт и т. д. - person einschnaehkeee; 10.06.2015
comment
Ну, единственные приложения, критически важные для производительности, о которых я знаю, это игры. Все остальные задержки обычно вызваны запуском кода, который должен выполняться в фоновом режиме в потоке пользовательского интерфейса. В любом случае писать длинный код совершенно нормально, если вы уверены, что лучшего варианта нет. Однако это должно быть взвешенное решение, основанное на понимании того, что длинные файлы — это зло ) - person Vasiliy; 10.06.2015
comment
Что ж, существует огромное разнообразие приложений, таких как требовательные к графике вещи, такие как игры или визуализация, симуляция (с большой нагрузкой на ЦП), сенсорно-зависимая (с большим объемом ресурсов данных), сетевое общение в реальном времени. И для каждого из них нужны очень разные стратегии. Некоторым даже нужны более монолитные подходы. - person einschnaehkeee; 10.06.2015

Это ответ на часть проблемы. Как указано в вопросе

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

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

class A {
  public class B {
     public B(int x, int y, int z, int m, int n, int o){

     }
  }
}

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

class A {
   public class B{
     int a, int b, int c, int m, int n, int p = 0;
     public B(){
     }
     public B setA(int x){
       a = x;
       return this;
     }     
     public B setB(int x){
       b = x;
       return this;
     }
     ... and similar methods for other properties.     
   }
}

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

class A {
   public class B{
     int a, int b, int c, int m, int n, int p = 0; // key for int a == "a" and for b is "b" and so on... this is our assumption.
     public B(){
     }
     public B setProperty(String key, int value){
       if(key.equals("a")){
           a = value;
       }else if(key.equals("b")){
           b = value;
       } ... and so on for other properties.
       return this;
     }     

   }
}
person Gaurav Gupta    schedule 08.06.2015
comment
Я тоже к этому изначально стремился. Проблема в том, что внутренние классы связаны. Мне нужно не только передавать переменные, но и иметь возможность возвращать переменные. Если я перемещаю внутренние классы, привилегии уровня пакета недостаточны для изменения частных переменных. - person xyz; 08.06.2015
comment
можете ли вы указать на некоторые экземпляры в вашем коде, которые могут вызвать проблемы, если я вслепую перенесу классы «CategoriesListener» и «AllPostsFetchAsyncTask» за пределы класса «MainActivity». Будет ли удаление обоих классов решением для вас? - person Gaurav Gupta; 08.06.2015
comment
Этим двум классам нужен доступ к context, current, pageCount, adapter и т. д. MainActivity. Эти классы получают доступ и изменяют их. Я также хотел бы сохранить Listeners, AsyncTasks в отдельных пакетах, поэтому мне нужно работать с уровнем привилегий private. Спасибо за просмотр всего кода, кстати! - person xyz; 08.06.2015
comment
1. Для разрешения этих зависимостей вам необходимо внедрить context в AsyncTask/Listener. Либо передайте его в конструкторе, либо в обычном методе. 2. Другие зависимости (pageCount, current) можно устранить, создав геттеры и сеттеры для каждого свойства. 3. Что касается adapter, если у вас есть доступ к представлению (как в onClick), вы можете перемещаться по иерархии представлений и обращаться к Listview, а затем вызывать getAdapter() для получения подключенного адаптера. Или вы также можете выбрать подход № 2. - person Gaurav Gupta; 08.06.2015

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

Затем ваш пример может быть изменен на это:

/** new container class */
class FooBar {
    public Foo foo;
    public Bar bar;
}

/** nice, easy and SHORT! */
class MainActivity {

    private FooBar fooBar;

    public MainActivity() {
        new Ping(fooBar);
        new Pong(fooBar).someMethod();
    }
}

/** successfully converted from inner class to class */
class Ping {

    public Ping(FooBar fooBar) {
        fooBar.foo = new Foo(); // Ping modifies Foo
    }
}

/** successfully converted from inner class to class */
class Pong {

    private Bob bob;
    private FooBar fooBar;

    public Pong (FooBar fooBar) {
        this.fooBar = fooBar;
        fooBar.bar = new Bar(); // Pong modifies bar
        bob = new Bob();
    }

    public void someMethod () {
        fooBar.bar.setSomethingTo(Bob.getSomething()); // Pong modifies bar of Main class
        fooBar.foo = new Foo(fooBar.bar); // Pong assignes something to bar
    }
}

Я использовал эти заглушки классов для компиляции кода:

class Foo {
    public Foo() {}
    public Foo(Bar bar) {}
}
class Bar {
    public void setSomethingTo(String something) {}
}
class Bob {
    static String getSomething() {return "Something";}
}

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

person slartidan    schedule 07.06.2015

Выньте свои внутренние классы, передайте им экземпляр самого MainActivity в их конструкторах.

MainActivity mainActivity;
DownloadJSON(MainActivity mainActivity) {
        super();
        mProgressDialog = new ProgressDialog(MainActivity.this);
        mProgressDialog.setCancelable(false);
        this.mainActivity=mainActivity;
    }

Сделайте переменные в mainActivity общедоступными, и вы сможете получить к ним доступ следующим образом:

          // Extract the metadata
         mainActivity.pageCount =Integer.parseInt(metaData.get("PAGE_COUNT"));
person Ai Hao    schedule 05.06.2015
comment
он не хочет предоставлять переменные в MainActivity другим классам (что делается путем объявления их общедоступными), так что это не желаемое решение - person Andy; 05.06.2015
comment
Это означает, что мне нужно будет объявить pageCount как public или использовать взломанную реализацию friend. В Android очень важно защитить Activities. - person xyz; 05.06.2015
comment
О, я вижу. Если вы не хотите объявлять их общедоступными, я думаю, что то, что @Andy сказал в своем комментарии, - хороший способ. - person Ai Hao; 05.06.2015
comment
Я думаю, вы можете ввести некоторые методы геттеров и сеттеров в свою основную активность, вывести свой внутренний класс и передать экземпляр mainActivity конструктору внутреннего класса, а с помощью этого экземпляра вы можете вызвать метод геттеров. - person RajSharma; 05.06.2015
comment
@Raj Sharma Геттеры и сеттеры не ограничивали экспозицию, особенно при работе с объектами. - person xyz; 05.06.2015
comment
извините, я думал, что он просто хочет использовать переменные mainActivity, не делая их общедоступными... - person RajSharma; 05.06.2015
comment
Это не желаемое решение. - person Anis LOUNIS; 07.06.2015