【问题标题】:Splitting big classes with Inner classes in Java在 Java 中使用内部类拆分大类
【发布时间】:2015-08-20 02:01:29
【问题描述】:

我正在开发一个 Android 项目。我搜索了高低,但我无法找到一个好的策略来拆分和打包我的代码。

我的问题是我有使用主类变量的内部类,我不知道如何将它们解耦

我曾尝试创建辅助类,但要么我通过构造函数传递了很多变量,要么我暴露了我的主类,我都不想这样做

我想将每个类的最大代码行数保持在 150 行。目前是 278 行。我正在寻找解耦这些的想法,特别是如何重组类以保留抽象 (private变量)。 Java 的最佳做法是什么?

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

【问题讨论】:

  • 如果您不想通过构造函数传递“大量”变量(好的设计原则是向方法/构造函数传递不超过 5 个变量),只需将所有需要的变量封装在一个自己的类(参见 Value-Object 或 Transfer-Object 设计模式)。这样您就不必在 MainActivity 中公开任何变量,只需将一个值对象传递给重构的内部类的相应构造函数或方法。
  • 两者几乎相似,我可能会传递 4 个变量。但是,新类还需要修改主类中的变量,这需要暴露,或者更多代码来检查这一点。理想情况下,我正在寻找 C++ 类型 friend 或类似的东西,所以我只能通过传递类对象 MainActivity.this 并完成。
  • 您在想,“我怎样才能在结构上拆分它?”尝试改为思考“我如何在概念上将其拆分?”具有大量实用方法的巨大值类型类(例如,String)完全可以。其他巨大的类可能有太多的责任。每个班级应该对另一件事做一件事。
  • @slartidan 谢谢。实际上是new Bob().getSomething()。我正在写一个简单的例子,这样人们就不必阅读那么多行来获得要点。固定!
  • @KevinKrumwiede 问题是概念上MainActivity 正在抽象它应该抽象的东西,据我所知,坚持OOP。我相信从概念上拆分这些是没有意义的。我只是在寻找一种实现,它允许我在 结构实现它,它维护这些关系但保持代码可管理。

标签: java android inner-classes packaging object-oriented-analysis


【解决方案1】:

取出你的内部类,将 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"));

【讨论】:

  • 他不想将 MainActivity 中的变量暴露给其他类(这是通过将它们声明为 public 来完成的),所以这不是理想的解决方案
  • 这意味着我需要将pageCount 声明为public 或使用被黑的friend 实现。在android中保护Activities非常重要。
  • 哦,我明白了。如果您不想将它们宣布为公开,我认为@Andy 在他的评论中所说的是一个好方法。
  • 我认为你可以在你的 mainactivty 中引入一些 getter 和 setetrs 方法,并将你的内部分类出来并将 mainActivity 的实例传递给 inne 类的构造函数,然后使用该实例你可以调用 getters 方法。跨度>
  • @Raj Sharma Getter 和 setter 不会限制曝光,尤其是在处理对象时。
【解决方案2】:

编辑:

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

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

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

也就是说,您将 Activity 保持 150 行长的意图过于严格。这归结为一个事实,如果你的ActivityFragment 不是微不足道的,那么一旦你实现了onCreate()onPause()onResume()onPrepareOptionsMenu()onBackStackChanged() 和其他标准生命周期方法,那么甚至在您添加自定义控制器的逻辑之前,您可能会有超过 150 行代码。

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

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

    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

希望对你有帮助

【讨论】:

  • 同意代码行数。 Java 因需要大量样板而臭名昭著,而 Android 在这方面更糟糕。当一项活动少于 300 行时,我很高兴。 (当然,我的大约一半的行通常是 Javadoc cmets。)
  • @Vasiliy 我添加了我的实际MainActivity 以供参考。我已经把 Adapters 放在外面了。但是AsyncTasksonClickListeners,每个都需要访问MainActivity,因此我将它们编写为内部类。 @slartidan这是一种方式,但它似乎更像是一种黑客行为。我可以尝试一下,但我希望有一个更优雅的解决方案。
  • @prakharsingh95,基于MainActivity 的代码,我添加了两种您可以使用的特定方法。查看编辑后的答案
  • 谢谢!我已经添加了我的新 MainActivity,以防有人感兴趣。
  • 恕我直言,按功能打包通常比按层打包要好。如此处所述,例如medium.com/mindorks/…
【解决方案3】:

如果内部类只访问字段,则引入一个新的 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,并且可以避免循环依赖。

【讨论】:

    【解决方案4】:

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

    我曾尝试创建辅助类,但是我通过构造函数传递了很多变量

    这与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 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;
         }     
    
       }
    }
    

    【讨论】:

    • 这也是我最初接触的。问题在于内部类耦合的。我不仅需要传递变量,还需要能够返回变量。如果我将内部类移出,包级权限不足以修改私有变量。
    • 如果我盲目地将 'CategoriesListener' 和 'AllPostsFetchAsyncTask' 类移到 'MainActivity' 类之外,您能否指出代码中的某些实例可能会导致问题。把这两个班级都搬出去会是你的解决方案吗?
    • 这两个类需要访问MainActivitycontextcurrentpageCountadapter等,由这些类访问和修改。我还想将 Listeners, AsyncTasks 保存在单独的包中,因此我需要使用 private 权限级别。感谢您浏览整个代码,顺便说一句!
    • 1.要解决这些依赖关系,您需要将 context 注入 AsyncTask/Listener。在构造函数或普通方法中传递它。 2. 其他依赖(pageCount,current)可以通过为每个属性做一个getter和setter来解决。 3. 就adapter 而言,如果您有权访问视图(如在onClick 中),则可以在视图层次结构中导航并到达Listview,然后调用getAdapter() 以获取附加的适配器。或者,您也可以选择方法#2。
    【解决方案5】:

    首先,基于你的Activity的实现,你错过了一些关于Activity的重要事情。

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

    重要的是:

    第 2 步:让 AsyncTask 通过数据成员持有 Activity,通过构造函数和 setter 进行设置。

    第 5 步:在 onCreate() 中,如果 getLastNonConfigurationInstance() 不为 null,则将其强制转换为 AsyncTask 类并调用 setter 以将新活动与任务相关联。

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

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

    2。在需要时使用数据保存类。

    这里并不真正属于 Activity:

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

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

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

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

    • 公共静态字段/方法

      使数据可跨活动/服务访问的另一种方法是使用公共静态字段和/或方法。 您可以从您的任何其他类访问这些静态字段 应用。共享一个对象,创建你的活动 object 设置一个静态字段来指向这个对象和任何其他 想要使用这个对象的活动只是访问这个静态 字段。

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

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

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

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

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

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

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

    【讨论】:

    • 我不能完全同意#5:“不要担心代码行数”。虽然每个类 500 行代码(包括 javadoc)在特定情况下对 Android 来说是可以的,但应始终尽量减少代码量。在某些情况下,如果我可以选择使用单行 API 调用还是自己在 20 行中实现它(更有效),我会选择更短的选项以提高代码的可读性。
    • 另外:没有人从所有概念的知识开始编程,因此应该有一些简单的指标表明“你可能做错了,伙计”。行数是这些指标之一,经验丰富的开发人员很容易将其丢弃。在阅读了 AOSP 的某些部分后,我可以告诉这一点——在许多情况下,它本可以变得更具可读性......
    • 嗯,您肯定有一个观点,即随着经验的增加,您可以更好地理解概念和结构。但是根据您正在开发的平台,确实存在巨大差异。特别是在移动或嵌入式系统等资源有限的情况下。性能是一个非常重要的主题,例如在 Android 的上下文中,您绝对需要确保在 16 毫秒内完成每个计算,以保持 60 帧的流畅。如果您不这样做,它将变得更加不稳定,并且用户会卸载您的应用程序。这就是为什么你有时不能写出最优雅的代码......
    • ...你不能为你的应用分配太多的内存,不能实例化很多对象,有丑陋的静态字段等等。尽管如此,仍然有很多方法可以解耦你的组件以提高可读性,但在 Android 的情况下,我不会将标准设置为 150,但我想这取决于许多因素,如时间、复杂性、经验等。
    • 嗯,我知道的唯一性能关键应用是游戏。所有其他“滞后”通常是由运行应该在 UI 线程的后台运行的代码引起的。无论如何,只要您确定没有更好的选择,编写冗长的代码是完全可以的。然而,这应该是一个经过深思熟虑的决定,基于对冗长文件是邪恶的理解)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-09-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-08-17
    相关资源
    最近更新 更多