有经验的Java开发者和架构师容易犯的10个错误Word格式.docx
- 文档编号:19838942
- 上传时间:2023-01-10
- 格式:DOCX
- 页数:9
- 大小:20.26KB
有经验的Java开发者和架构师容易犯的10个错误Word格式.docx
《有经验的Java开发者和架构师容易犯的10个错误Word格式.docx》由会员分享,可在线阅读,更多相关《有经验的Java开发者和架构师容易犯的10个错误Word格式.docx(9页珍藏版)》请在冰豆网上搜索。
文章分上篇和下篇,本文是上篇。
10、错误地使用或者误解了依赖式注入
对于一个企业级项目来说,依赖式注入通常被认为是好的概念。
存在一种误解——如果使用依赖注入就不会出现问题。
但是这是真的吗?
依赖式注入的一个基本思想是不通过对象本身去查看依赖关系,而是通过开发者以在创建对象之前预先定义并且初始化依赖关系(需要一个依赖式注入框架,譬如Spring)。
当对象真正被创建时,仅仅需要在构造函数中传入预先配置好的对象(构造函数注入)或者使用方法(方法注入)。
然而总的思想是指仅仅传递对象需要的依赖关系。
但是我依然可以在一些新的项目里发现如下的代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
publicclassCustomerBill{
//InjectedbytheDIframework
privateServerContextserverContext;
publicCustomerBill(ServerContextserverContext)
{
this.serverContext=serverContext;
}
publicvoidchargeCustomer(Customercustomer)
CreditCardProcessorcreditCardProcessor=serverContext.getServiceLocator().getCreditCardProcessor();
Discountdiscount
=serverContext.getServiceLocator().getActiveDiscounts().findDiscountFor(customer);
creditCardProcessor.bill(customer,discount);
当然,这不是真正的依赖注入。
因为这个对象始终需要由它自己进行初始化。
在上面的代码中
“serverContext.getServiceLocator().getCreditCardProcessor()”这一行代码更加体现了该问题。
译注:
作者指的是
creditCardProcessor、discount
这两个变量的初始化。
当然,最好的方式应该是只注入那些真正需要的变量(最好是标记为final):
19
20
21
publicclassCustomerBillCorrected{
privateActiveDiscountsactiveDiscounts;
privateCreditCardProcessorcreditCardProcessor;
publicCustomerBillCorrected(ActiveDiscountsactiveDiscounts,CreditCardProcessorcreditCardProcessor)
this.activeDiscounts=activeDiscounts;
this.creditCardProcessor=creditCardProcessor;
=activeDiscounts.findDiscountFor(customer);
请注意两段代码的区别在于对于代码中需要的资源的范围。
从使用依赖注入的角度来看,前一段代码中注入的范围很大,那就意味着有了更多的变化空间,但是容易造成代码的功能不单一,同时增加了代码测试的复杂度。
后一段代码中注入的范围就很精确,代码简单易懂测试起来也比较容易。
9、像使用perl一样来使用Java
(跟其它编程语言比较)Java提供了一个好的属性,就是它的类型安全性。
可能在一些小型项目中开发者只有你自己,你可以使用任何喜欢的编程风格。
但如果是一个代码量很大以及复杂系统的Java项目中,
在错误发生时你需要早一些得到警示。
大多数的错误应该在编译阶段而不是在到运行期就被发现(如果你对Java不甚了解,请阅读Java的相关资料)。
Java提供了许多特性去辅助产生这些编译器的警告。
但是如果你写出下面的代码编译器还是没有办法捕获到对应的警告:
publicclassAnimalFactory{
publicstaticAnimalcreateAnimal(Stringtype)
switch(type)
case"
cat"
:
returnnewCat();
cow"
returnnewCow();
dog"
returnnewDog();
default:
请注意这段代码只能工作在jdk
1.7
下。
JDK
1.7以下的版本编译不能通过。
这段代码是非常危险,而且编译器不会产生任何的警告帮到你。
一个开发者也许会调用工厂方法以一个错误拼写“dig”创建一个Cat对象。
但实际上,他需要的是一个Dog对象。
这段代码不但会编译通过,而且错误往往只能在运行期被发现。
更严重的是,这个错误的产生依赖于应用程序本身的特性,因而有可能在程序上线几个月以后才能发现它。
你是否希望Java编译器可以通过某种机制帮你提前捕获到这样错误呢?
这里提供一个更正确的方式来确保代码只有被正确的使用的情况下才能编译通过(当然还有其他的解决方案)。
publicclassAnimalFactoryCorrected{
publicenumAnimalType{DOG,CAT,COW,ANY};
publicstaticAnimalcreateAnimal(AnimalTypetype)
caseCAT:
caseCOW:
caseDOG:
caseANY:
1.这里使用了javaenum类型。
2.由于对Perl语言不慎了解,猜测作者隐含的意思是perl语言如果按照第一种写法,被错误调用的时候是否在编译器就会报错。
如果知道的人可以帮忙解释一下。
8、像C语言一样使用Java(即不理解面向对象编程的理念)
回到C语言编程的时代,C语言建议用过程化的形式来书写代码。
开发者使用结构体存储数据,通过函数来描述那些发生在数据上的操作。
这时数据是愚笨的,方法反而是聪明的。
作者估计是想说,数据和函数是分离的没有直接的上下文来描述之间的关系。
然而Java正好是反其道而行。
由于Java是一门面向对象的语言,在创建类的时候数据和函数被聪明地绑定在一起。
然而大多数的Java开发者要么不理解上述两门语言之间的区别,要么就是他们讨厌编写面向对象代码。
虽然他们知道过程化开发方式与Java有些格格不入。
一个在Java应用程序中,最显而易见的过程化编程就是使用instanceof,并在随后的代码中判断向上转换或向下转换。
instanceof有它合适使用的情况,但在企业级的代码中通常它是一个严重的反模式示例。
下面的示例代码描述了这种情况:
22
publicvoidbill(Customercustomer,Amountamount){
Discountdiscount=null;
if(customerinstanceofVipCustomer)
VipCustomervip=(VipCustomer)customer;
discount=vip.getVipDiscount();
elseif(customerinstanceofBonusCustomer)
BonusCustomervip=(BonusCustomer)customer;
discount=vip.getBonusDiscount();
elseif(customerinstanceofLoyalCustomer)
LoyalCustomervip=(LoyalCustomer)customer;
discount=vip.getLoyalDiscount();
paymentGateway.charge(customer,amount);
使用面向对象的来重构以后的代码如下:
Discountdiscount=customer.getAppropriateDiscount();
这里可以认为使用设计模式当中的Factory
method模式。
每个继承Customer(或者实现Customer接口)定义了一个返回折扣的方法。
这样做的好处在于你可以假如新的类型的Customer而不需要关系customer的管理逻辑。
而使用instanceof的判断每次添加一个新的类型的Customer意味着你需要修改customer打印代码、财务代码、联系代码等等,当然同时还需要添加一个If判断。
你可能也需要查看一下关于充血模型vs贫血模型的讨论。
7、滥用延迟初始化
(即不能真正的理解对象的生命周期)
我经常能发现如下的代码:
publicclassCreditCardProcessor{
privatePaymentGatewaypaymentGateway=null;
//Billingacustomeralwaysneedsapaymentgatewayanyway
getPaymentGateway().charge(customer.getCreditCart(),amount);
privatePaymentGatewaygetPaymentGateway()
if(paymentGateway==null)
paymentGateway=newPaymentGateway();
paymentGateway.init();
//NetworksideEffects
here
returnpaymentGateway;
延迟初始化初衷是好的,即如果你有个非常昂贵的对象(譬如对象需要网络连接或者连接Web
API等等),当然应该只在需要的时候创建它。
然而,在你的项目中使用这项技术的时候最好确认以下两点:
∙这个对象真的很“昂贵”(你是如何给出这样的结论或者定义?
)
∙存在这个对象不被使用的情况
(确实不需要创建这个对象)
在实际开发中,我不断发现延迟初始化被用在对象上。
但实际上,这样的对象要么不是真的那么“昂贵”,要么总是在运行期创建。
延迟初始化这种对象能得到什么好处呢?
过度使用延迟初始化的主要问题在于它隐藏了组件的生命周期。
一个经过良好搭建的应用程序应该对它主要部件的生命周期有清晰的了解。
应用程序需要非常清楚对象什么时候应该被创建、使用和销毁。
依赖注入可以帮助定义对象的生命周期。
但依赖注入在对象创建时也有副作用。
使用以来注入表明应用程序状态依赖于对象被创建的顺序(按照要求的类型顺序)。
由于涵盖了过多的用例,对应用程序调试就变成了一件不可能完成的事情。
复现生产环境也变成一项巨大的工程,因为你必须十分清楚场景执行的顺序。
相应的我们需要做的就是定义应用程序启动时需要的所有对象。
这也带来了一个额外的好处,可以在应用程序发布过程中捕获任何致命的错误。
6、把GOF(俗称四人帮)当作圣经
我十分羡慕设计模式的几位作者。
这本书籍以其他书籍所无可比拟的气势影响了整个IT界。
如果你没看过《设计模式》,没有记住模式的名字或者准则的话,那么在面试中就可能无法通过。
期望这样的错误可以慢慢改善。
不要误解我,这本书本身是没有问题的。
问题出在人们如何解释以及使用它。
下面是通常场景:
1.架构师马克,拿到这本书开始阅读。
他觉得这本书牛逼坏了!
2.马克趁热打铁开始阅读现在工作的代码。
3.马克选择了一种设计模式并应用到了代码当中。
4.随后马克把这本书推荐给了那些跟他重复同样步骤的资深开发者。
结果就是一团糟。
如何正确使用这本书实际上已经在导读中做了清晰的说明(提醒那些不看导读的人)——“在过去你有个问题,而且这个问题总是一遍又一遍地困扰着你”。
注意到其中的顺序了吗?
先有一个问题,然后查看这本书之后找到对应的解决方案。
不要掉进看这本书的陷阱当中——“找到一个方案然后尝试把它应用在自己的的代码中。
尤其要注意的是,一些书中描述的模式在现实当中已经不再正确。
”(请参见下篇第5条)。
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 经验 Java 开发者 架构 容易 10 错误