在 Java 中将大 类 与内部 类 拆分

Splitting big classes with Inner classes in Java

我正在做一个 Android 项目。我到处搜索,但找不到拆分和打包代码的好策略。

我的问题是我有内部 类 使用主要 class 变量,我无法弄清楚 如何解耦它们.

我尝试创建辅助程序 classes,但是要么我通过构造函数传递了很多变量,要么公开了我的主要 class,但我没有我不想做任何一个

我想将每个 class 的最大代码行数保持在 150。目前是 278。我正在寻找 解耦这些 的想法,具体来说,如何重构 classes 以保留抽象(private 变量)。 Java 这方面的最佳做法是什么?

例如,here 是我的主要 classes 之一,MainActivity,约 300 行。

把你内心的类拿出来,在它们的构造函数中将MainActivity本身的实例传递给它们。

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

在mainActivity中创建变量public,你可以这样访问它们:

          // Extract the metadata
         mainActivity.pageCount =Integer.parseInt(metaData.get("PAGE_COUNT"));

编辑:

在为 MainActivivty 添加实际代码后,我建议如下:

  1. 遵循MVC/MVP 架构模式。你可以在我最后写的模板中找到一个 link,但还有更多的模板 - 只需选择一个你喜欢的。一旦你了解了如何在 MainActivity 之外获取所有 UI 相关代码,方法 addButtons() 将消失,以及 CategoriesListener class.
  2. AllPostsFetchAsyncTask真的没必要做内推class。在 activity 之外将其作为常规 class 实施。为了将数据从这个 class 传递回 MainActivity,只需定义一个监听器接口,你的 MainActivity 将实现,并将 MainActivity.this 传递给构造函数 - 当这个任务完成后,它将调用 MainActivity 上的回调方法,该回调方法将处理与 Adapter 的数据绑定。事实上,你在这里采用了一个非常糟糕的做法 - 通过让 AllPostsFetchAsyncTask 了解 MainActivity 的实现细节,你在两者之间创建了不必要的耦合,从而违反了 OOP 的封装、单一职责和开放封闭原则。

只需执行上面的两个步骤,您就可以使这个特定的 MainActivity 方法少于 150 行代码。

您说的是,您打算让活动保持 150 行的长度限制太多。这归结为这样一个事实,即如果您的 ActivityFragment 不是微不足道的,那么一旦您实施了 onCreate()onPause()onResume()onPrepareOptionsMenu()onBackStackChanged() 和其他标准生命周期方法,那么在添加自定义控制器的逻辑之前,您的代码可能会超过 150 行。

现在,我完全讨厌内心的 classes 并且我想不惜一切代价避免它们。以下清单可以作为指导,但无论如何都不完整:

  • 永远不要操作 controllers/presenters 中的 UI 元素(ActivitiesFragmentsAdapters)——将这些操作封装在单独的 class 中.这些 class 是 MVC/MVP 视图(相对于 Android View),我将它们放在 viewsmvcviews 包中。我的 ActivitiesFragments 在它们的源代码中往往有零个 findViewById() 调用。
  • 将所有 Adapters 放在一个单独的包中(即使它们有 30 行长)。我称这个包为 controllers.adapterscontrollers.listadapters
  • 如果您需要在您的应用程序中传递一组相关数据 - 定义一个 POJO(也称为值对象)并使用它来封装这些数据。我通常有一个名为 pojos 的包,即使它只包含一个 class.
  • 定义抽象 classes AbstractActivityAbstractFragment 并将您的控制器使用的任何便利逻辑放在那里。例如:我的 AbstractActivityAbstractFragment 中总是有以下方法(或类似方法):

    public void replaceFragment(Class <? extends Fragment> claz, boolean addToBackStack, Bundle args) { 
        // Code to replace the currently shown fragment with another one 
    }
    
  • 检查是否有任何第三方库可能对您的应用上下文有用并使用它们。

我的包装通常遵循这种模式:

我知道你写道你已经看到了一些关于 MVC 的讨论,但我仍然鼓励你尝试我在这个 template/tutorial 项目中提出的实现:https://github.com/techyourchance/android_mvc_template

希望对您有所帮助

如果内部classes只是访问字段,那么引入一个新的Container class你的MainActivity class的所有相关字段(你当然可以也可以制作两个或三个小容器,而不是一个大容器。

然后您的示例可以修改为:

/** 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 存根来编译代码:

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

如果内部 classes 也在访问方法,那么您可以在接口中指定那些,由 MainActivity 实现。仅使用接口将 MainActivity 的实例交给其他 classes。这样您就不必公开完整的 MainActivity 并且可以避免循环依赖。

这是部分问题的答案。如问题所述

I have tried to create helper classes, but then either I pass a lot of variables through constructors

这与 Telescoping constructor 非常相似。所以,为了解决这个问题,我个人会使用类似于 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 看起来冗长。因此,为此我想对上述模式稍作修改。为每个 属性 分配密钥也会让 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;
     }     

   }
}

首先,根据您的 Activity 实施,您错过了一些关于活动的重要事项。

1.仅 使用静态内部 classes 或独立 classes 用于 AsyncTasks:参见 Background task, progress dialog, orientation change - is there any 100% working solution?

重要的是:

步骤 #2:让 AsyncTask 通过数据成员保持 Activity,通过构造函数和 setter.[=14 进行设置=]

步骤 #5:在 onCreate() 中,如果 getLastNonConfigurationInstance() 不为 null,则将其投射到您的 AsyncTask class 并调用您的 setter将您的新 activity 与任务相关联。

您会注意到,您必须根据 Android 的生命周期方法注册和注销您的组件。了解这一点很重要,始终遵循 Android 生命周期!

记住这一点将始终引导您找到有关解耦 Android 方式的正确答案。

2。在需要时使用 数据保持 classes。

这里不属于 Activity:

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

当你的 Activity 被摧毁时,例如在配置更改期间,您的所有数据都消失了,您需要重新加载所有内容。

可以通过多种不同的方式访问和存储您的数据:http://developer.android.com/guide/faq/framework.html#3

在您的情况下,这可能适用:

  • A public静态field/method

    另一种跨 Activities/Services 访问数据的方法是使用 public 静态字段 and/or 方法。 您可以从您的任何其他 class 访问这些静态字段 应用。要共享对象,创建您的 activity object 设置一个静态字段指向这个对象和任何其他 activity 想要使用这个对象只是访问这个静态 字段.

还要考虑将您的数据存储在数据库中或通过其他方式,这样即使您的应用程序被销毁,您的数据也不会消失。

3。与您的 Activity 的交流 可以这样完成:http://developer.android.com/guide/components/fragments.html#CommunicatingWithActivity

以相同的方式将它用于您的视图和视图侦听器。有一个组件管理您的视图(就像片段一样),将其注册到您的 Activity,使用它,在不需要时或生命周期需要时注销它。

正如 1. 中所说,Android 生命周期是一切的关键。

4.依赖注入 是一个非常重要的主题,您可以使用它的框架(如 Dagger 2 或 RoboGuice)或按照您自己的方式进行。确保你的 Injector 知道依赖关系(比如哪些 Buttons 需要哪些 ClickListeners 和 Information 或者你的 Adapter 需要哪些数据)并将它们绑定在一起。当总是考虑生命周期时,你会看到你需要哪些接口和方法以及何时调用它们。

5.不用担心代码行数。如果你的设计是一致的并且有意义,那么即使有 500 行你也不会有可读性问题。顺便提一句。当正确记录您的代码时,它很容易超过 150 行代码。所以,又要担心了。

如果您对实施细节有任何具体问题,请提出具体问题,否则您会得到一个臃肿的答案。