帮助看似两个概念的矛盾 - oop和n层开发

时间:2011-01-19 19:42:50

标签: oop n-tier-architecture

不是新手在这里,但仍然从结构化编程过渡。我正在尝试将自包含对象的概念与n层编程协调起来。在我看来,你可以有一个或另一个,但不是两个。

请告诉我这里的n-tier-architecture错误。说我想和一群人一起工作。我使用FN,LN,BDay等在实体层中创建一个“Person”类。我将使用此Person类在我的所有层中表示一个人-UI,Business和DataAccess。但我通常不能将任何有用的方法放入'人'中,因为那时我将越过层边界。

所以我最终创建了一个UiPersonClass,一个BusinessPersonClass和一个DataAccessPersonClass,每个都有一个'Person'类成员。然后我最终为每个层类创建一个构造函数,接受一个'Person'类参数(来自其他层)并将其设置为 this.Person 。我用 this.Person 作为参数新建了一个相邻的PersonClass图层。等。

这感觉真的错了,但你还应该怎么做呢?

如果我只使用一个图层,我可以拥有一个'Person'类,其中包含填充ui控件的方法,处理信息,以及从数据库中保存和检索数据,所有这些都集中在一个“对象”中

8 个答案:

答案 0 :(得分:6)

我第一次学习面向对象编程时遇到的一个问题是,用于说明概念的示例如此基本以至于没有意义

为什么在有更简单,更直接的方法来编写解决方案时,会增加一堆看似不必要的复杂性?为什么代码接口似乎没有添加任何东西,只有更多的编程工作和混乱?为什么在我所做的只是将未更改的值传递给链时创建多个图层?

当我开始做更复杂的事情时,很多事情开始变得有意义了。 在这里给出的情况下,数据似乎不会随着从一层传递到下一层而改变。因此,拥有多个层似乎没有意义。 但是,想象一下更常见的情况,即数据存储在数据库中的方式与UI中显示的方式不匹配。

然后你给出的例子并不那么简单。

我发现以这种方式思考是有帮助的: BusinessLayer在数据库中的数据和用户界面中的数据之间进行转换。

我通常在数据库中的数据与在DataLayer中创建的对象之间存在基本上一对一的对应关系,并且来回传递给BusinessLayer

我传回的数据和从BusinessLayer传输到用户界面的内容基本上是一对一的对应关系以及UI中显示的内容

这些对象通常非常不同。

BusinessLayer是翻译发生的地方。这是从数据库中检索数据以显示在UI中,还是收集在UI中输入的数据并将其存储在数据库中。

当事情开始变得复杂时,事实证明以这种方式做事情要容易得多。特别是在对应用程序进行更改时

答案 1 :(得分:5)

在我看来,关于在层之间共享Person类的第一个概念是正确的。您可以将它抽象一点,比如添加一个接口IPerson,以便代表Person的任何对象都将实现此接口。

至于层中的分离,我不认为你应该有一个BusinessPersonClass和一个DataAccessPersonClass。相反,我认为你应该针对以下内容:你的业务层将在IPerson类型上运行:它不关心对象是Person类型还是Salesman类型,它只是知道它需要一个IPerson类型的对象。此外,业务层是所有业务规则(自然地) - 验证等的地方。我认为这些业务规则不应该是Person对象的一部分。实际上,Person对象应该只关注一个人的原子操作和属性 - 换句话说,它不应该包含任何业务或数据访问逻辑。

数据访问层也是如此 - 它在IPerson上运行,返回IPerson,修改IPerson(s)。数据访问层仅关注CRUD操作,或者它如何从/向数据源添加,获取,更新,删除IPerson。

现在有了UI,我觉得它有点不同了。通常你会实现你的ViewModel(UIModels)和一些简单的验证规则(不涉及业务逻辑,但输入的有效性)。我可以在这里想到两种方法:

1)每个关于Person的UI视图模型都实现了IPerson。通过这种方式,您可以确保层的一致性,并且可以轻松处理这些数据。

2)每个UI视图模型都包含一个IPerson类型的属性(如上所述)。在我看来,这也是一个不错的方法,因为它使UI和业务层之间的通信变得更加容易。

总之,这就是我的想法:

UI图层 以某种方式引用IPerson。实现基本验证规则 - 输入的有效性(输入的电子邮件确实是电子邮件)。然后,UI将此IPerson对象传递给业务层,以用于业务规则和操作。

业务层 获取IPerson对象,使用业务规则验证它(此电子邮件是否已存在于系统中)。执行更多业务层操作,可能是,并调用数据访问层,例如,创建实体。在此示例中,业务层将调用Create函数并将其传递给IPerson类型的对象。

数据访问层 对原子实体的CRUD操作 - IPerson,IProduct等

希望这有帮助。

答案 2 :(得分:5)

我认为你的错误是你假设Person应该能够自己做所有事情(知道如何加载自己,如何在屏幕上呈现自己等等)。如果你采取这种想法的激进路线,你将最终Person包含屏幕驱动程序(因为它需要知道如何在屏幕上呈现自己!)。

由于您已经定义了图层(持久性,渲染等),因此您应该考虑这些图层的职责。例如。您可以GraphicsSystem接受Person并在屏幕上呈现它。或PersonDAO负责从数据库中检索Person。或PersonPrintService知道如何打印,等等。

一般来说,在建模数据对象(例如Person)时,您应该专注于跨层传输的数据,而不是功能本身。

您采用的方法(BusinessPersonClassDataAccessPersonClass等)称为Decorator pattern。应用它的情况有所不同,但对我而言,此模式的最佳示例是Java的OutputStream类,它可以包装到BufferedOutputStream中,然后再包装到FileOutputStream等等。

我不知道在BusinessPerson之上添加了什么逻辑Person,但对我而言,如果有些人是“商务人士”,那么你最好是{ {1}} 扩展 BusinessPerson

由于我不知道您的问题所在,我无法进一步发表评论。

答案 3 :(得分:3)

好吧,我可能在这里多余,但我认为还有一点要点:


抱歉,但我的英语很糟糕。 但我会尽我所能。 :(


您正在使用n层架构。这意味着您的应用程序很大(层和层仅在您的应用程序足够大时才有用)。 N层授予隔离某些东西的能力。示例:您的数据库更改。不管怎么样,它只会改变。您的DAO图层可能会更改,但您的商务图层保持不变。男孩,这是巨大的。如果你有一个非常大的应用程序,这是一个非常理想的观点。

这就是n层的意思。它将一层与另一层隔离开来。您的UI更改不会影响您的数据层等。记在心上。好的,下一步。

现在,您正在使用n层,每层/层实际上都是一个非常大的“应用程序”。您的业​​务层(或任何其他层)执行很多操作,并拥有自己的对象和域。在这里,在一个图层中,你完全使用oop。在层/层内,您的对象只关心您的层/层域,您可以将对象与其自身的责任相关联。 在业务层中,Person仅执行业务人员所执行的操作。在UI中,您的等效Person只知道如何呈现其数据或者您希望Person在UI中执行的任何操作。

好的,还有一点需要澄清。

在图层之间,您有一个界面。不是Java接口,像操作合同的接口。在这个边界你不要使用oop。您正在传递DATA,而不是对象,而不是逻辑,只有DATA。这是什么意思?这意味着无论你通过你的层/层,它不是一个真正的oop对象(或者,它不应该是)。为什么?因为您不希望对象具有多个负责任权吗?

你能在这里看到我的观点吗?

你可能有一个Person类。但是商业中的Person类在DAO层中是不同的,并且对于UI来说是不同的 UNLESS Person类只是在层之间传输的数据。

n层和oop并不矛盾或相反。 它们不是可以比较的相同类型。

答案 4 :(得分:3)

我倾向于用动词来思考; UI层填充了呈现,查看,滚动,单击和显示的类。数据层保存,加载,更新和删除。业务层完全是以某种方式反映现实世界的对象。所以你的应用可能包含;

UI:PersonView,PersonEditPage,NewPersonForm

业务:人,生日,地址,推销员,员工

数据:连接,数据库,表,列

因此,Web应用程序中的典型操作序列可能会出现;

(screen)      UI        Business        Data      (storage)

Form/Page  PersonView   Organization    Connection     DB
   |           |             |                |         |
   |--type-+   |             |                |         |
   |       |   |             |                |         |
   |<------+   |             |                |         |
   |           |             |                |         |
   |---click-->|             |                |         |
   |           |--find(#3)-->|                |         |
   |           |             |-LoadPerson(3)->X         |
   |           |             |                X-select->X
   |           |             |                X         X
   |           |             |                X<--------X
   |           |             |<-----Person----|         |
   |           |<---Person---|                |         |
   |<---HTML---|             |                |         |
   |           |             |                |         |

这里发生了什么?

首先,用户正在键入并单击浏览器。单击时,PersonView对象(表示Web上某人的外观)将拦截该调用。在伪代码中:

onLinkClick() {
  personView.FindPerson(id:3);
  rendertoHtml();
}

PersonView现在需要找一个人,所以它要求业务层;它要求一个名为“组织”的对象来找到一个特定的人。例如;

PersonView.FindPerson(id) {
  organization.FindPerson(id)
}

请注意,它在不知道数据库或XML或Web服务的情况下会询问;它只是直接询问业务层,“你能找到第三个人吗?”。因此,UI层与数据层隔离。

然后,业务层(组织)转到数据层,并要求它加载人员记录。

Organization.FindPerson(id) {
  connection.LoadPerson(id);
}

同样,它没有提出特定于数据的请求。现在我们进入连接,它知道数据层的具体细节;

Connection.LoadPerson(id) {
  connectDb();
  execute("SELECT name, dob FROM Person WHERE id =@id");
  return new Person(name, dob);
}

所以连接肯定 知道特定的存储机制(SQL),但不知道调用它的UI层。它返回一个Person对象,然后Organization将它传递回PersonView,然后PersonView知道如何生成HTML。

这种结构有何帮助?想象一下,您希望从SQL迁移到XML文件以进行数据存储。你可以使用xpath创建一个不同的Connection实现,就像这样;

Connection.LoadPerson(id) {
  LoadXml();
  SelectNode("//person[@id=$(ID)]");
  return new Person(xml['name'], xml['dob']);
}

整个系统将继续工作。您还可以更改UI层(例如,从Web到Windows),无需重写业务层和数据层。

答案 5 :(得分:3)

由于我刚刚在这里向所有人投了赞成票,我也将在此分享我的观点,并就测试方程进行更多讨论。 :)

我觉得有趣的是,我现在从0层(将所有JSP文件中的内容)放到2层(servlet-JSP)到众神知道多层。根据我的经验,我可以告诉你的是,如果你不需要额外的层,就不会太疯狂。您可能会阅读需要您拥有此图层或该图层的编程书籍,但这一切都归结为当前项目的复杂性。

拥有层的优点是提升抽象层...例如,如果您决定使用Spring MVC或JSF框架替换Struts的前端,那么它不会导致不必要的负面涟漪效应其余的实现(数据库,安全性,日志记录等)。有太多层级的缺点是你不必要地在项目中引入更多的复杂性。一切都太抽象了,以至于你失去了跟踪哪一层做了什么。此外,如果你没有正确实现这些层,你最终会在层之间进行紧密耦合,你会发现单元测试非常困难和令人沮丧。这是因为为了让你对单元测试组件A进行单元测试,你必须首先从其他层初始化10个其他组件......最后,你最终没有进行任何单元测试,因为每次你在代码中改变一些东西时,你都会必须修复所有的测试用例。我自己也经历过这个错误。

为了简单起见,您需要以下层: -

  • UI:这是你的观众。它可以是JSP,HTML,Freemarker等。
  • Model:这包含Person类。它包含person和getter / setters方法的所有属性。就是这样......没有任何商业逻辑。这是POJO。你可以用它在层之间传递。
  • Controller:这是您的“空中交通管制员”。它处理用户请求,它知道要调用哪些服务来完成工作,但这里的计算量很少。把控制器想象成你的老板。老板总是要求员工这样做,最后,老板拿出结果并通过UI向用户表示。
  • Service:这是您拥有PersonService的地方。它处理与此人相关的所有服务,例如创建新患者,按姓氏查找人等。您的服务是唯一可以与数据库通信的服务。它会将所有检索到的人员信息从数据库映射到Person模型,并将其返回给控制器。如果你的项目范围很大,也许你想引入一个DAO层来处理所有与数据库相关的通信,但是现在,除非你可以证明需要它,否则不需要DAO层。 / LI>

通过查看此...前3层是您的MVC,它在Web应用程序开发中非常常用。此处引入了Service层,以提升Controllers之间的代码可重用性。例如,您有许多可能希望利用PersonService对一个人执行CRUD操作的多个控制器。

从这里开始,单元测试变得非常简单。由于Service是一个独立的层,并且它不依赖于任何其他层,因此您可以轻松编写测试用例来测试整个Service类。一旦测试了所有服务API,那么测试Controller将会很简单,因为现在,您可以安全地模拟控制器中的Service类来执行“快乐路径”和“不 - 快乐路径“测试。 Model并不需要任何测试,因为它只是一个POJO。这样,您可以轻松实现95%的测试覆盖率。

有......所有的快乐和完成。 :)

答案 6 :(得分:1)

如果我可以在这里加上我的0.02美元,可能稍微偏离了这个问题,但与做错事情的感觉有关。

我坚信每项技术都能达到目的,而且物理学中没有统一的场论,也没有奇迹技术可以涵盖软件开发中的各种建筑或其他问题。

我还坚信,对象作为整个应用程序中真实世界实体的抽象化身的隐喻意义通常(尽管并非总是如此)具有误导性。

多层开发中的每一层都有其目的,因此可以从使用特定的结构或优化技术中受益。将它们混合在一起似乎只能创建一致的架构,实际上它通常不会。

我坚信数据库架构(具有存储数据的最佳结构)不需要匹配类/对象架构(具有对数据的操作的最佳结构 ),也不是表示逻辑架构(具有显示操作结果的最佳结构)。

他们都是不同的,应该这样对待。

答案 7 :(得分:1)

为不同的“层”建立不同的模型并在它们之间进行映射没有任何问题。事实上,许多模式都鼓励这一点。

以MVVM(Model - View - ViewModel)为例。我们的想法是有一个业务(或域)模型,其中包含对您的业务逻辑有用的对象状态。可以将其视为业务层的模型。绑定时,您可能必须组合模型或修剪特定视图不需要的信息模型。这是一个视图模型,对应于UI层。您还有一个持久性机制,它具有自己的表示形式,通常与数据来源的关系数据库相同或至少非常接近(尽管您可以持久保存到其他类型)。

在一个非常简单的实现中,你可以通过让一个模型描述所有层来混淆水域。这种类型的模型的主要问题是它最终非常关系而不是OO。此外,它侧重于持久性机制的需求,而不是您正在使用的实际域(即应用程序的业务需求)。

我个人已经将视图更改为代表“应用程序”的圆圈,这类似于典型的n层图中的业务层。然后,我可以在不影响核心“应用程序”代码的情况下交换持久性机制(数据库,XML文件等)。我还可以在不更改核心应用程序的情况下交换不同的UI。

回到你的问题。在某些情况下,UI“模型”需要与业务模型不同。在这些情况下,我创建了一种来回翻译模型的方法(映射是另一个好词)。持久性和业务“层”之间的基本模式相同。

现在,有人提到服务是一个单独的概念。从应用程序作为业务逻辑的传播,我发现这有问题。从业务流程的核心来看,服务是一种持久性机制。封装在服务中的是服务“应用程序”使用的域模型,但是从“应用程序”查看时,最常使用服务来持久化对象。回到业务“层”和数据/持久“层”的分离,您应该能够在没有大量工作的情况下为数据库交换服务。

这只是我的两美分,你的里程可能会有所不同。