我正在使用MVP模式来开发大规模应用程序。在开发过程中,我提出了一个问题,是应该使用组合还是继承。例如:假设我有一个名为Foo的表单,其中包含字段a和B。在应用程序的其他部分,我有一个Bar表单,它具有相同的字段a和B,但有一个附加字段C。
目前,该代码是用继承方法编写的,其中窗体Bar的视图继承自窗体Foo。然后,演示者处理的数据与模型略有不同。这很简单,但无论if是否遵循"是A"的经验法则,我都很惊讶,因为即使形式不同,它们也处理常见的输入(A和B)。
然而,在这里,我一直在思考"组合而非继承"和利斯科夫替代原则,并认为我应该使用组合而不是继承。然而,由于我使用MVP,它比预期的要复杂,因为我必须为具有字段a和B的窗体Foo设置一个演示者,然后为具有字段C并引用Fa演示者的Bar创建一个演示器,以便它可以注入字段a进入。
问题是,它已经被证明是更多的代码,因为我必须在Foo的演示程序中添加一些排序getter和setter,以便它能够将数据传递到Bar。这感觉就像我打破MVP是为了提供构图。
所以我的问题是:
对我的案例来说,使用组合而不是继承真的更好吗?为什么?
使用作文会"打破"MVP吗?
对于我的案例来说,使用组合而不是继承真的更好吗?为什么?
是。因为组合在更大的应用程序中更可靠、更安全、更可维护、更可发现、更可文档化、更易于理解。IMHO.:)
使用组合会"破坏"MVP吗?
是。它打破了你现在做的那种简单的MVP。组合可以让你选择如何耦合你的代码,这对大型应用程序来说非常好。它确实使用了更多的代码,因为你必须明确你是如何耦合的。
对于一个简单的应用程序来说,成长是非常合理的,并成为从简单的MVP继承过渡到更复杂的组合的良好候选者。这是一个解耦步骤,能够以新的方式重新耦合。
这类似于许多简单的web应用程序正在转变为前/后API驱动的应用程序。这本质上是前端用户视图与后端存储模型的解耦。
当表单不同时,它们处理公共输入(A和B)。
这意味着Foo演示程序在概念上与Bar演示程序不同,而且只是碰巧共享一些公共输入,所以它们不应该通过继承来关联。将处理公共输入的代码提取到实用程序类中,并在Foo-prepreser和Bar-presenter中重用该代码。
如果Foo的概念发生变化,它不会影响Bar(反之亦然:如果Bar的概念不能在不改变Foo概念的情况下发生变化,那么就是"是A"关系,继承确实可以使用)
当有疑问时,总是更喜欢合成
一个更干净的组合应该有类:
模型:A、B、C、Foo、Bar
视图;AView、BView、CView、FooView、BarView
呈现器:A呈现器、B呈现器、C呈现器、FooPresentor、BarPresentor
在FooView包含AView和BView的情况下,BarView包含一个AView、BView和CView并且呈现者具有相似的组成。
这种组合使A、B和C(以及它们的视图和呈现器)模块化,这样您就可以随心所欲地混合和匹配,复合类(Foo和Bar)也可以处理集成。
这可以与继承一起使用:如果Bar是Foo的特定情况,那么Bar应该继承自Foor,BarPresentor可以继承自FooPresentor。然而,我会以更符合具体情况的方式考虑视图的继承,因为视图可能适合继承,也可能不适合继承,这取决于它们的行为。
让我们从基本知识开始,关于类,你必须知道的最重要的一点是,子类总是超类的完整实例。因此,如果你在超类中定义了一个字段变量,那么如果你创建了子类的实例,这个字段总是会被创建的。您可以使用super.getVariable()在子类中获取该变量以重用字段(类变量、字段、标志,这在OO编程中是一样的)。但您也可以从外部调用subclassInstance.getVariable(),您将获得相同的字段(无需通过子类对其进行任何更改)。因此,您通常根本不需要在子类中调用"super",因为您通常只想从外部获取/设置它的超类(包括抽象类!)的字段。由于您应该始终将字段变量设置为私有,我总是建议永远不要调用"super"来访问任何字段变量(因为即使使用super,你也不能访问超类的私有方法/字段……实际上是Java中最大的错误之一,因为它不能向其他类提供类树的完全封装……所以你通常需要调用像super.getField()这样的protected
方法,这很烦人,但很必要)。
现在让我们从数据模型开始:您有模型A和模型B。您可以从超类继承它们,也可以仅从Object继承它们。但是,如果您仅从Object继承,则可以定义一个简单的(Java!)接口,并将该接口实现到modelA和modelB中。然后,您只需通过接口来处理这两个类(它们都是"接口对象",可以通用地处理)。如果你有一个由模型A和模型B组成的模型C,你只需要在模型C中使用这两个模型的一个实例(有时也称为"解引用"),就不需要更多的代码了。因此,您可以选择尽可能小而简单的型号("beans")。基于组件的实现,这是数据结构的常用方式。
如果您有GUI或表单,它看起来会有所不同。您可能有很多共同的代码,并且不希望将这些代码拆分为几十个不同的类,然后将这些组件拉到控制器/演示器类中。因此,您可以定义一个抽象类,它包含所有共享字段/标志以及访问和更改它们的几个方法。然后,从formA和formB调用这些常用方法,并重用代码。
集合论对你有什么启示吗?你有两个圆,A和B,以及A和B的交集。抽象类是交集,子类formA和formB是集合差。学习编写正确的程序。。。正在理解集合论。;-)
用你的话来说:Foo形式的大多数代码都将在一个抽象的超类Foobar中,这个类将能够处理A和B。然后,您从中继承形式Foo和形式Bar,虽然C可能主要保留为Foobar的子集,但您添加了Bar的能力,这就是集合差异。
最终,Bar在任何时候都不会是Foo,它们都将仅是Foobar。你有一些新的共享字段/标志吗?没问题,您可以将他们的代码迁移到Foobar中,并且可以在这两个子类中使用它!
但是,如果有一天你需要第三个组件FooToo,它与Foo略有不同,该怎么办?没问题,让FooBarFoo成为一个抽象类,扩展FooBar,然后创建Fao和FooToooo更改任何其他代码)。
您说过要在表单(或其演示者)中实现setter/getter吗?然后您还必须使用modelA和modelB模型(但没有模型C,因为C只在Bar中使用,而不是Foo)。这些模型用作包装器,用于在Foo和Bar之间传输数据。该数据流应由演示者控制,而不是由Foo或Bar控制。
因此,你的问题最终是:演讲者的全部内容是什么?实际上,演示者是运行GUI组件和数据模型的代码。它一方面使用GUI组件,另一方面使用数据模型的getter/setter。它是GUI层和数据层这两层之间的中间件,甚至是不同GUI组件和不同数据模型之间的中间件。
因此,通常只有两种方法可以做到这一点:没有演示器/控制器或使用它。如果没有,你需要将大量swing组件代码复制粘贴到演示器类中。那又怎样?是的,当你使用摆动组件时,你总是已经使用(M)VP模式,这是不可能的!
也就是说,要创建一个框架,你需要使用组件设计,因为你想为使用你的框架的程序员提供最大限度的灵活性。但是,一个高效的系统与一个框架不同,这是许多框架程序员认为的错误。所以,如果一个框架程序员告诉你"基于组件的实现就是一切",那么他可能错了。仅仅因为他正在为他的框架编程组件,这并不意味着你需要为你的演示者做同样的事情!
这就是我们开始讨论GUI组件与GUI表示的时候。创建"尽可能多的演示者组件"是可能的,因为你可以使用"include(…)"方法来创建一个简单的HTML网站并用它创建几十个PHP网站。但我可以向你保证,基于组件的设计并不总是能提高代码的可维护性!如果我只用一节课就能做一些事情,而且我能做得清晰易读,我宁愿用一节课,而不是十节课。一个演示者=一个类,或者更具体地说:一个GUI框架/选项卡=一个类别。
再说一遍,如果你有两个相似的框架/选项卡,但它们不一样,该怎么办?将共享代码迁移到一个抽象类中,并创建2个子类,对吗不,您首先需要考虑这些GUI的共享内容。他们共享旗帜了吗?因此,将标志移到一个抽象超类中。但他们只是行为不同吗?好吧,您只需要在同一个类中实现两个不同的方法,并在需要时调用它们这是最重要的。
用你的话来说:GUIPres1总共使用Foo、Bar和A,B以及C。如果GUI具有不同的标志,则它Pres2在另一个类中仅。否则,您将设置一个标志Pres1和一个标志Pres2,并在方法中检查此标志。由if(flag="Pres1"){} else if(flag="Pres2"){}
。像这样,您将获得最大的灵活性和代码可重用性。
不要把Java类看作是不可编辑、不可使用、不可更改的东西。如果你是一个好的程序员,一旦需要,你就会直观地改变程序的结构。你不需要考虑人为的概念,你只需要理解面向对象的编程模式。
"组件"总是指"带有构造函数的东西"。但是,有时你只需要使用一个方法而不是一个组件来做一些事情!所以,如果有人告诉你"组件设计就是一切",他会告诉你"基于构造函数的设计就是一切。"。但是要创建构造函数,您需要有字段变量/标志!如果没有字段变量,仅仅为了创建一个新类是完全没有意义的
注意:"组件"不是一种方法。很明显,您将在GUI类中使用许多方法来非常容易地处理事情,所以最后您只需要调用一些方法。所以不要混合成分和方法!我总是建议采用一种强大的面向方法的设计,因为这一切都是为了使用更少的代码行。因此,尽可能多地定义方法。。。而且尽可能减少类/组件
当然,当Foo不扩展Bar时,您需要添加更多的代码,因为您有额外的getter和setter。但最大的好处是Foo不再依赖Bar。这看起来可能是一个很小的好处,但想象一下,如果你在50多个类中使用inhenritance会是什么样子。。。如果你必须更改一个由其他几个类扩展的类中使用的组件,那么这将是非常复杂的。
出于维护原因,请避免使用继承。正如你所说,"酒吧不是Foo",所以酒吧不应该扩展Foo。根据我的经验,继承从来都不是一个好的解决方案,应该只用于类家族(例如,当使用复合模式时)。