界面爆炸问题

时间:2010-03-31 13:31:47

标签: language-agnostic design-patterns interface

我正在使用MVP模式实现一个屏幕,屏幕上添加了更多功能,我在IScreen / IPresenter接口上添加了越来越多的方法,因此,IScreen / IPresenter界面变得越来越大,我该怎么办?要应对这种情况吗?

4 个答案:

答案 0 :(得分:4)

在接口中对工件(方法,常量,枚举等)的数量没有具体限制 - 比如说 N - 这样我们可以说如果接口X有多个N个文物,它很臃肿

至少没有上下文。在这种情况下,上下文是应该提供什么接口?,或者更好的是,这个接口的实现应该做什么? 什么是实现接口的类的预期行为或角色?

我强烈建议您熟悉某些指标,例如内聚和耦合(一般情况和OO的细节)。特别是,我建议你看一下LCOM。一旦你理解了它,它将帮助你观察你现在遇到的情况。

http://javaboutique.internet.com/tutorials/coupcoh/

你想要用接口或类(或者甚至包或模块,如果你正在进行程序编程)的最后一件事就是将它们变成方法和函数的包,除了厨房水槽之外你扔掉所有东西。这导致内聚力差或紧密耦合(或两者兼而有之。)

接口的一个问题是我们无法像实际的类那样轻松地计算或估计他们的LCOM, 可以指导 你决定何时r- efactor。因此,你必须使用一点直觉。

我们假设您的界面为参数而命名为 A 。然后,

第1步: 考虑通过参数对接口方法进行分组:是否存在对相同类型的参数进行操作的方法子集?如果是这样,它们与其他方法组有显着差异吗?

interface A
{
  void method1();
  void method2(someArgType x);
  someOtherType y method3();

  ...

  void doSomethingOn( someType t );
  boolean isUnderSomeCondition( someType t )
  someType replaceAndGetPrev( someType t, someFields ... )
}

在这种情况下,请考虑将该组拆分为自己的界面 B

第2步:

提取界面B后,它看起来像这样吗?

interface B
{
  void doSomethingOn( someType t );
  ...
  boolean isUnderSomeCondition( someType t )
  ...
  someType replaceAndGetPrev( someType t, someFields ... )
}

也就是说,它表示在某种类型上执行事物的方法?

如果是这样,你的界面正在模仿ADT上的程序模块操作(在这种情况下, someType ) - 如果你使用的是程序或多范式语言没有错误< / em>的。

在理性和务实的情况下,在OO中,您可以最大限度地减少在其他对象上执行操作的过程。您可以调用这些对象中的方法 代表您自己做事。或者更确切地说,你发出信号要求他们在内部做一些事情。

在这种情况下,考虑将B转换为封装类型的类(并且,它使用相同的签名扩展接口,但只有在它有意义的情况下,如果您期望封装/管理工件的不同实现该类型的元素。)

class Bclass
{
  someType t;

  Bclass(){ t=new someType();} 
  ...
  void doSomethingOn();
  ...
  boolean isUnderSomeCondition()
  ...
  someType replaceAndGetPrev( someFields ... )
}

第3步: 确定从A重新分解的接口和类之间的关系。

如果B表示只有在 A 时才存在的事物(A是B的上下文,例如Java EE术语中的servlet上下文中存在servlet请求),然后让B定义一个返回A的方法(例如A B.getContext()或类似的东西。)

如果B表示由A管理的事物(A是事物的组合,包括B),那么让A定义一个返回B的方法(B A.getBThingie())

如果A和B之间没有这样的关系,除了它们被组合在一起之外没有任何共同点,那么很可能原始界面的内聚性很差。

如果你不能在不破坏大量设计的情况下解开彼此,那么这就表明你的系统部分边界很差并且紧密耦合。

希望它有所帮助。

PS。此外,我还会避免尝试将您的界面和类适合传统模式 UNLESS 这样做是为了应用程序/业务特定目的。为了以防万一我必须把它丢在那里。太多的人厌倦了GoF书,试图让他们的班级融入模式,而不是问“我用这个解决了什么问题?”

答案 1 :(得分:1)

你能否将界面分解为代表屏幕各个部分的子界面?例如,如果您的屏幕分为多个组,例如导航部分,表单部分或工具栏部分,那么您的IPresenter / IScreen可能包含这些部分的接口的getter,以及那些部分部分可以包含每个部分的相关方法。您的主IPresenter / IScreen仍然拥有与整个界面相关的方法。

如果屏幕的某些部分不适合您的应用程序的逻辑类别,请考虑可能提供逻辑分解的其他内容。工作流程将是一个。

编辑例如:

例如,对于我所做的大型UI,我实际上不仅分解了我的演示者,还分析了我的模型和视图代码。整个屏幕整齐地分成一棵树(在这种情况下),主要演示者将工作委托给儿童节目主持人和链子。当我不得不稍后返回并添加到此UI时,我发现在层次结构中适合相当简单和可维护。

在一个像这样工作的示例中,MainPresenter IMainPresenter实现知道它的模型,它的视图和它的子演示者。每个SubPresenter控制自己的视图和模型。对该子节中逻辑上属于的内容的任何操作都应该在SubPresenter中。如果您的屏幕布局方式是这样的逻辑单元,那么这样的设置应该可以正常工作。每个SubPresenter应该能够为SubView返回MainPresenter,以便根据需要插入MainView

答案 2 :(得分:1)

在我看来,“完美的程序世界”包含公共接口和内部实现。

每个界面都严格“负责”只有一件事

我试图查看这些实体是“小”的人类,彼此互动以完成某项任务。

(对不起,如果这有点哲学化)

答案 3 :(得分:1)

您使用的是什么样的Model-View-Presenter?我发现Passive View很少涉及视图和演示者界面之间的重叠 - 通常它们会在不同的时间发生变化。

通常视图的界面本质上是一个视图模型,可能是这样的(C#风格):

public interface IEditCustomerView {
    string FirstName { get; set; }
    string LastName  { get; set; }
    string Country   { get; set; }
    List<Country> AvailableCountries { get; set; }
    // etc.
}

视图实现通常具有用户手势的处理程序,通常是调用到演示者的瘦包装器:

public class EditCustomerView {
    // The save button's 'click' observer
    protected void SaveCustomer() {
       this.presenter.SaveCustomer();
    }
}

演示者通常有一个方法用于每个用户手势,但没有数据,因为它直接从视图中获取(通常在构造函数中传递给演示者,但是如果你可以在每个方法调用上传递它它更合适):<​​/ p>

public interface IEditCustomerPresenter {
    void Load();
    void SaveCustomer();
}