英语原文共 688 页
Expert one-on-one J2EE Design and Development ---- Rod Johnson
第四章部分段落翻译
由于J2EE应用程序往往是大型和复杂的,因此我们必须遵循良好的OO设计实践,采用一致的编码约定,并利用现有的投资(无论是我们自己的还是第三方的投资)。在本章中,我们将依次介绍这些重要领域。
前两个问题涉及对象设计和代码级别的代码质量。我们在努力实现什么?什么是好代码?这是它的一些特点:
好的代码是可扩展的,没有剧烈的修改。很容易添加功能而不将其拆开。
好的代码易于读取和维护。好的代码有很好的文档记录。
好的代码很难写坏的代码。例如,对象公开干净、易于使用的接口,以促进良好的使用。好代码和坏代码都会产生。
好的代码易于测试。
好的代码易于调试。记住,即使一段代码工作得很好,如果不利于调试,它仍然是一个问题。如果开发人员试图跟踪不完美代码中的错误,并且堆栈跟踪消失在完美但不清晰的代码中,该怎么办?
好的代码不包含代码重复。好的代码会被重用。
很难编写实现这些目标的代码,尽管Java可以提供比任何其他流行语言更多的帮助。
自从我在1996开始使用语言(而且之前有大量的C和C )我还在学习,我编写和调试了很多Java代码。我不认为这一章包含了所有的答案,并且有很多意见,但希望它能为思考提供一些指导和有用的食物。这是一个重要的领域。
我们不仅必须确保我们正确地编写代码,还必须确保我们编写正确的代码,并在适当的时候利用现有的解决方案。这意味着开发团队必须密切合作以避免重复工作,并且架构师和主要开发人员必须保持对第三方解决方案(如开源项目)的最新了解。
本章和本书一样,主要讨论J2EE1.3,因此是J2SE1.3。但是,在相关的地方讨论了J2SE1.4中的语言和API改进,因为J2SE1.4已经可用,甚至可以与一些J2EE1.3应用服务器一起使用。
面向J2EE应用程序的OO设计建议
可以很好地设计一个J2EE应用程序,即使它在一个单独的对象级中包含了漂亮的Java代码,它仍然会被认为是一个失败。一个总体设计优秀但实现代码糟糕的J2EE应用程序将是一个同样糟糕的失败。不幸的是,许多开发人员花费了太多的时间来处理J2EE API,而确保他们遵守良好的编码实践的时间太少。所有Sun的J2EE示例应用程序似乎都反映了这一点。
以我的经验来看,坚持良好的OO原则并不是一种学究式的做法:它能带来真正的好处。
OO设计比任何特定的实现技术更重要(例如J2EE,甚至Java)。良好的编程实践和良好的OO设计是良好的J2EE应用程序的基础。坏的Java代码是坏的J2EE代码。
一些“编码标准”问题,特别是与OO设计相关的问题,处于设计和实现之间的边界:例如,设计模式的使用。
实现与接口的松耦合
经典的四人帮设计模式书倡导的“可重用面向对象设计的第一原则”是:“程序到接口,而不是实现”。幸运的是,Java使遵循这一原则变得非常简单(自然)。
接口程序,而不是类别。这是从他们的执行中解开的界面。使用物体之间的软耦合促进柔性。为了获得最大的灵活性,申报实例变量和方法参数是最需要的特异类型。
使用基于接口的体系结构在J2EE应用程序中特别重要,因为它们的规模很大。编程到接口而不是具体的类会增加一点复杂性,但是回报远远大于投资。通过接口调用对象有轻微的性能损失,但在实践中这很少是一个问题。
基于接口的方法的许多优点包括:
能够在不影响调用代码的情况下更改任何应用程序对象的实现类。这使我们能够在不破坏其他组件的情况下参数化应用程序的任何部分。
实现接口的完全自由。不需要提交到继承层次结构。然而,在接口实现中使用具体的继承仍然可以实现代码重用。
能够根据需要提供简单的测试实现和应用程序接口的存根实现,促进其他类的测试,并使多个团队在同意接口后能够并行工作。
采用基于接口的体系结构也是确保J2EE应用程序可移植的最佳方法,同时能够利用特定于供应商的优化和增强。
基于接口的体系结构可以有效地结合使用反射进行配置。
更喜欢对象组合而不是具体的继承
GoF书中强调的面向对象设计的第二个基本原则是“优先对象组合而不是类继承”。很少有开发人员欣赏这种明智的建议。
与许多旧的语言不同,如C ,Java在语言继承层次上区别于具体继承(方法实现的继承和成员变量与超类)和接口继承(接口的实现)。Java允许只从一个超类继承具体的继承,但是Java类可以实现任意数量的接口(包括,当然是由其祖先在类层次结构中实现的接口)。虽然存在多个具体继承(如C 中允许的)是最好的设计方法的罕见情况,但是Java更好地避免了由于允许这些罕见的合法用途而产生的复杂性。
对于面向对象的大多数新开发人员来说,具体继承受到了热烈的欢迎,但也有许多缺点。类层次结构是刚性的。改变一个类的部分实现是不可能的;相反,如果这个部分被封装在一个接口中(使用委托和策略设计模式,我们将在下面讨论),这个问题是可以避免的。
对象组合(通过组装或组成对象获得新功能)比具体继承更灵活,Java接口使委托自然。对象组合允许在运行时更改对象的行为,方法是将对象的部分行为委托给接口,并允许调用方设置该接口的实现。策略和状态设计模式依赖于这种方法。
为了澄清区别,让我们考虑一下我们希望通过继承实现什么。
抽象继承支持多态性:在运行时具有相同接口的对象的可替换性。这提供了面向对象设计的大部分价值。
具体的继承既可以实现多态性,也可以更方便地实现。代码可以从超类继承。因此,具体的继承是一个实现,而不是纯粹的设计问题。具体继承是任何OO语言的一个有价值的特性,但是很容易被过度使用。具体继承的常见错误包括:
当我们可能需要实现一个简单的接口时,强制用户扩展一个抽象的或具体的类。这意味着我们剥夺了用户代码自己继承层次结构的权利。如果一个用户类通常不需要它自己的自定义超类,那么我们可以为该方法提供一个方便的抽象实现来进行子类化。因此,接口方法并不排除提供方便的超类。
通过子类在超类中调用helper方法,使用具体的继承来提供helper功能。如果继承层次结构之外的类需要帮助器功能怎么办?使用对象组合,这样辅助对象是一个单独的对象,可以共享。
使用抽象类代替接口。抽象类在正确使用时非常有用。模板方法设计模式(下面讨论)通常用抽象类实现。然而,抽象类不是接口的替代品。这通常是实现接口的一个方便步骤。不要使用抽象类来定义类型。这是一个解决Java缺乏多个具体继承的问题的方法。不幸的是,核心Java库在这方面是很差的例子,通常使用抽象的类,其中接口是优选的。
界面在保持简单时最有价值。接口越复杂,将其建模为接口的价值就越低,因为开发人员将被迫扩展抽象的或具体的实现,以避免编写过多的代码。在这种情况下,正确的接口粒度至关重要;接口层次结构可能与类层次结构分离,因此特定的类只需要实现它需要的确切接口。
接口继承(即接口的实现,而不是从具体类继承功能)比具体继承灵活得多。
这是否意味着具体的继承是一件坏事?绝对不是;具体的继承是在OO语言中实现代码重用的一种强大方法。但是,最好将其视为实现方法,而不是高级设计方法。它是我们应该选择使用的,而不是被应用程序的总体设计所强制使用的。
模板法设计模式
具体继承的一个好用途是实现模板方法设计模式。
模板方法设计模式(GOF)解决了一个常见的问题:我们知道算法的步骤和执行顺序,但不知道如何执行所有步骤。这个模板方法模式解决方案是封装我们不知道如何作为抽象方法执行的各个步骤,并提供一个以正确顺序调用它们的抽象超类。这个抽象超类的具体子类实现执行各个步骤的抽象方法。关键概念是控制工作流的抽象基类。公共超类方法通常是最终的:延迟到子类的抽象方法受到保护。这有助于减少bug的可能性:所有子类都需要这样做,就是履行一个明确的合同。
将工作流逻辑集中到抽象超类中是控制反转的一个例子。与传统类库中的用户代码调用库代码不同,在这种方法中,超类中的框架代码调用用户代码。这也被称为好莱坞原则:“不要打电话给我,我会打电话给你”。控制反转是框架的基础,框架倾向于大量使用模板方法模式(稍后我们将讨论框架)。
例如,考虑一个简单的订单处理系统。业务包括根据单个项目的价格计算采购价格,检查是否允许客户使用此金额,并在必要时应用任何折扣。一些持久性存储(如RDBMS)必须进行更新,以反映成功的购买,并查询以获取价格信息。但是,最好将其与业务逻辑的步骤分开。
AbstractOrderEJB超类实现了业务逻辑,其中包括检查客户是否试图超出其支出限制,以及对大订单应用折扣。public placeOrder()方法是最终的,因此子类不能修改(或损坏)此工作流:
public final Invoice placeOrder(int customerId, InvoiceItem[] items) throws NoSuchCustomerException, SpendingLimitViolation {
int total = 0;
for (int i = 0; i lt; items.length; i ) {
total = getItemPrice(items[i]) * items[i].getQuantity();
}
if (total gt; getSpendingLimit(customerId)) { getSessionContext().setRollbackOnly();
throw new SpendingLimitViolation(total, limit);
}
else if (total gt; DISCOUNT_THRESHOLD) { // Apply discount to total...
}
int invoiceId = placeOrder(customerId, total, items); return new InvoiceImpl(iid, total);
}
我强调了这个方法中的三行代码,它们调用了受保护的抽象“模板方法”,这些抽象“模板方法”必须由子类实现。这些将在AbstractOrderEJB中定义如下:
protected abstract int getItemPrice(InvoiceItem item);
protected abstract int getSpendingLimit(customerId) throws NoSuchCustomerException;
protected abstract int placeOrder(int customerId, int total, InvoiceItem[] items);
AbstractOrderEJB的子类只需要实现这三个方法。他们不需要关心业务逻辑。例如,一个子类可能使用JDBC实现这三个方法,而另一个子类可能使用SQLJ或JDO实现它们。
这种模板方法模式的使用提供了很好的关注点分离。在这里,超类集中于业务逻辑;子类集中于实现原始操作(例如,使用低级API,如JDBC)。由于模板方法是受保护的,而不是公共的,所以调用方不必了解类实现的细节。
由于通常最好在接口中定义类型而不是类,因此模板方法模式通常用作实现接口的策略。
抽象超类还经常用于实现接口的某些(而不是全部)方法。剩下的方法(具体实现不同)仍然没有实现。这不同于模板方法模式,因为抽象超类不处理工作流。
使用模板方法设计模式捕获抽象超类中的算法,但将单个步骤的实现推迟到子类。这有可能通过一次正确的操作和简化用户代码来阻止错误。在实现模板方法模式时,抽象超类必须考虑可能在子类之间发生更改的方法,并确保方法签名在实现中具有足够的灵活性。
始终使抽象父类实现接口。模板方法设计模式在框架设计中尤其有价值(本章最后讨论)。
模板方法设计模式在J2EE应用程序中非常有用,可以帮助我们在应用程序服务器和数据库之间实现尽可能多的可移植性,同时仍然利用专有特性。我们已经看到了如何有时将业务逻辑与上面的数据库操作分开。我们同样可以使用这个模式来实现对特定数据库的有效支持。例如,我们可以有一个oracleorderejb和一个db2orderejb,在各自的数据库中有效地实现抽象模板方法,而业务逻辑仍然没有专有代码。模板方法设计模式在J2EE应用程序中非常有用,可以帮助我们在应用程序服务器和数据库之间实现尽可能多的可移植性,同时仍然利用专有特性。我们已经看到了如何有时将业务逻辑与上面的数据库操作分开。我们同样可以使用这个模式来实现对特定数据库的有效支持。例如,我们可以有一个oracleorderejb和一个db2orderejb,在各自的数据库中有效地实现抽象模板方法,而业务逻辑仍然没有专有代码。
战略设计模式
模板方法的另一种选择是策略设计模式,它将变量行为影响到接口中。因此,知道算法的类不是抽象的基类,而是一个具体的类,它使用一个助手来实现定义各个步骤的接口。策略设计模式比模板方法模式需要更多的工作来实现,但是它更灵活。策略模
以上是毕业论文外文翻译,课题毕业论文、任务书、文献综述、开题报告、程序设计、图纸设计等资料可联系客服协助查找。