Android MVP模式包结构

12

我看到了许多关于在Android中使用MVP模式的优秀教程,但是作者们似乎都有不同的打包实践。

我看到的第一篇教程按功能进行打包。例如,“登录”,“加入”,“UI”包。

“UI”包只包含活动,“登录”包具有呈现器和具体呈现器的接口,并且此包包含一个名为“Model”的子包,该子包包含与登录模型(与服务器的通信)有关的所有内容。 “加入”包具有与“登录”包相同的组成。

但是另一个我看到的则按场景进行打包,例如“加入”,“登录”。

“Join”包含一个活动和三个名为“Model”,“View”和“Presenter”的子包。

什么是最佳实践?是否有任何处理此问题的文章?


1
这对你来说是一个真正的问题吗?这更多是关于风格/约定的问题。 - Lyubomyr Shaydariv
3
嗯,我只是在寻找最佳实践。 - March3April4
个人而言,我会按照服务、存储库、实用工具等方式将其分为模型、展示器和视图。我开发了一个宠物跨平台MVP框架,并且我更喜欢上述风格。例如,模型和展示器是跨平台的,但它们各自的视图使用不同的“更依赖于平台”的包(例如foo.app.core和foo.app.android或foo.app.gwt)。然而,其他人可能更喜欢另一种风格,这也是可以理解的。 - Lyubomyr Shaydariv
请查看以下文章和示例项目,这可能会有所帮助: https://medium.com/@m_mirhoseini/yet-another-mvp-article-part-1-lets-get-to-know-the-project-d3fd553b3e21#.6y9ze7e55 - Mohsen Mirhoseini
3个回答

16
应用程序应该根据功能 features 进行打包,而不是按照通用功能进行打包。开发人员不应该将活动、片段、适配器等通用目的的包放在同一组中,这是一个不好的实践!大多数开发人员这样做是因为他们想保持所有应用程序的相同的包结构。但这是一个非常错误的决定,因为只是因为它们共享相同的父类而将类分组时,要查找类是始终很困难的!如果我们正在制作一些 API,我们应该根据父类对类进行分组,但如果我们正在为客户制作定制产品,则这是非常糟糕的实践。例如,所有活动都被放在活动包中,因为所有活动类都扩展了 Activity 类。这意味着这仅是与活动相关的包,但访问这些包很困难。
假设我们有一个 OrderListActivity 类和一个 OrderListFragment 类,我们从服务器获取订单列表并在 OrderListFragment 类中显示它,显然为此我们需要 OrderListAdapter 来显示订单列表。所以当客户要求在该订单列表屏幕上进行某些修改或添加功能时,我们必须去许多包中满足客户需求。例如,我们必须进入活动包,在 OrderListActivity 中修改某些内容,然后转到 OrderListFragment,然后 OrderListAdapter,然后 OrderListViewHolder,等等。因此,这变得非常困难,我们可能在修改过程中创建问题!
因此,我们应该将一起被更改/修改的类分组在一起。这是最佳实践,因此我们应该将负责 OrderListing 功能的所有类分组到一个包中并称其为 orderdlist 包。
请查看我在 Medium 上的这篇文章,我已经在其中解释了包结构:-- https://medium.com/@kailash09dabhi/mvp-with-better-naming-of-implementation-classes-dry-principle-e8b6130bbd02

4

最佳实践是按功能(有时被视为模块)和层次分离内容,而不是按其角色。原因:类/接口名称已经说明了这一点,例如LoginView、LoginPresenter、LoginFragment、LoginActivity等。


2
我以前从未见过有关打包的任何理论。有没有我可以参考的参考资料? - March3April4

4
我只是转载我的答案,在这里
我经常将业务逻辑代码放在模型层(不要与数据库中的模型混淆)。我通常重新命名为XManager以避免混淆(例如ProductManagerMediaManager ...),因此Presenter类只用于保持工作流程。
经验法则是,在Presenter类中没有或至少限制导入android包。这种最佳实践支持您更容易地测试Presenter类,因为Presenter现在只是一个普通的Java类,所以我们不需要Android框架来测试这些东西。
例如,这是我的MVP工作流程。 View类:这是存储所有视图(如按钮、TextView等)并在该层上设置所有视图组件的侦听器的位置。在此View上,您为Presenter实现定义了一个Listener类。您的视图组件将调用此侦听器类上的方法。
class ViewImpl implements View {
   Button playButton;
   ViewListener listener;

   public ViewImpl(ViewListener listener) {
     // find all view

     this.listener = listener;

     playButton.setOnClickListener(new View.OnClickListener() {
       listener.playSong();
     });
   }

   public interface ViewListener {
     playSong();
   }
}

Presenter类:这是您存储视图和模型以供稍后调用的位置。此外,Presenter类将实现上面定义的ViewListener接口。Presenter的主要作用是控制逻辑工作流程。

class PresenterImpl extends Presenter implements ViewListener {
    private View view;
    private MediaManager mediaManager;

    public PresenterImpl(View, MediaManager manager) {
       this.view = view;
       this.manager = manager;
    }

    @Override
    public void playSong() {
       mediaManager.playMedia();
    }
}

管理器类:这里是核心业务逻辑代码。可能一个Presenter会有多个Manager(取决于视图的复杂程度)。通常我们通过一些注入框架(例如Dagger)获取Context类。

Class MediaManagerImpl extends MediaManager {
   // using Dagger for injection context if you want
   @Inject
   private Context context;
   private MediaPlayer mediaPlayer;

   // dagger solution
   public MediaPlayerManagerImpl() {
     this.mediaPlayer = new MediaPlayer(context);
   }

   // no dagger solution
   public MediaPlayerManagerImpl(Context context) {
     this.context = context;
     this.mediaPlayer = new MediaPlayer(context);
   }

   public void playMedia() {
     mediaPlayer.play();
   }

   public void stopMedia() {
      mediaPlayer.stop();
   }
}

最后: 将这些内容放在Activities、Fragments中...这是您初始化视图、管理器并将所有内容分配给Presenter的地方。

public class MyActivity extends Activity {

   Presenter presenter;

   @Override
   public void onCreate() {
      super.onCreate();

      IView view = new ViewImpl();
      MediaManager manager = new   MediaManagerImpl(this.getApplicationContext());
      // or this. if you use Dagger
      MediaManager manager = new   MediaManagerImpl();
      presenter = new PresenterImpl(view, manager);
   }   

   @Override
   public void onStop() {
     super.onStop();
     presenter.onStop();
   }
}

您可以看到每个 Presenter、Model、View 都被包装在一个接口中。这些组件将通过接口进行调用。这种设计使您的代码更加健壮,并且以后修改起来更容易。

谢谢你的优秀回答。它似乎和我提问时所见不同。在那种情况下,视图仅是一个接口,没有任何实现类,而活动或片段则不同。我发现你的回答更加有趣。但是,在执行一些线程操作后,您如何处理来自xManagers(模型类)的回调?如果Presenter只需将结果传递给View类怎么办?您如何处理这些情况? - March3April4
有关处理回调的内容。这取决于情况。如果回调很简单(仅更改状态变量...),我可以直接在xManager上调用。如果它很复杂,我将在xManager上创建一个Listener接口(类似于上面讨论的View),并且Presenter将实现此监听器。因此,当回调完成时,xManager将调用例如listener.loginSuccess()。(并且loginSuccess将通过接口在Presenter类上实现) - hqt
关于演示器向视图类传递结果的问题。视图类定义了“dump方法”,例如:setUsername(username)disableLoginButton()...演示器将调用这些方法。如果我想通过管理器类更改视图,则将通过演示器类进行。在某些情况下,这会使代码冗余,但从长远来看,它将使代码具有更好的结构。 - hqt
再次感谢。我喜欢你的视图实现的概念。我认为它会让我的活动代码更简单。 - March3April4

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接