为了软件系统在系统层级上保持整洁,需要为系统演化出恰当的抽象等级和模块。其中一个有效的方法就是将系统的构造和使用分开,因为构造和使用是非常不一样的过程。
软件系统应将启始过程和启始过程之后的运行时逻辑分离开,如果在启始过程中构建应用对象,将会存在相互缠结的依赖关系。将关注的方面分离开,是软件技艺中最古老也是最重要的设计技巧。不幸的是,多数应用程序没能做到分离处理,启始过程代码很特殊,被混杂到运行时逻辑中,下面的代码片段是著名的延迟初始化/赋值,这种处理的好处是在真正用到对象之前,无需操心这种架空构造,启始时间也会缩短,而且能够保证永远不会返回null值。
public Service getService() {
if (null == service) {
service = new MyServiceImpl(...);
}
return service;
}
但是存在一个问题,我们在得到service的同时,必须硬编码依赖于MyServiceImpl及其构造器所需一切。不分解这些依赖关系就无法编译,即使在运行时永不使用这种类型的对象。
如果MyServiceImpl是个重型对象,那么进行单元测试前,需要给service指派恰当的测试替身(TestDouble)或者仿制对象(MockObject)。
为了解决上面的依赖问题,我们有三种方法:
1)分解Main
将构造和使用分开的方法之一是将全部构造过程移到main或被称为main的模块中,设计系统的其余部分时,假设所有对象都已正确构造和设置。如图,main函数负责调用Builder创建系统所需的对象,再传递给应用程序application,应用程序只负责使用。从箭头的方向可知,应用程序对main或者构造过程一无所知,它只是简单地指望一切已就绪。
简单实现的代码如下:
/**
* 某配置对象,供Applicaton使用,由Builder创建
* @author ASCE1885
*
*/
public class ConfiguredObject {
private String majorVersion;
private String minorVersion;
public String getMajorVersion() {
return majorVersion;
}
public void setMajorVersion(String majorVersion) {
this.majorVersion = majorVersion;
}
public String getMinorVersion() {
return minorVersion;
}
public void setMinorVersion(String minorVersion) {
this.minorVersion = minorVersion;
}
public ConfiguredObject(String majorVersion, String minorVersion) {
this.majorVersion = majorVersion;
this.minorVersion = minorVersion;
}
public ConfiguredObject() {
}
}
/**
* 负责创建ConfiguredObject
* @author ASCE1885
*
*/
public class Builder {
private ConfiguredObject configuredObject = null;
//延迟初始化
public ConfiguredObject createConfiguredObject(String majorVersion, String minorVersion) {
if (null == configuredObject) {
configuredObject = new ConfiguredObject(majorVersion, minorVersion);
}
return configuredObject;
}
}
/**
* 具体某个应用程序模块,需要ConfiguredObject对象实例才能运行
* @author ASCE1885
*
*/
public class Application {
private ConfiguredObject configuredObject;
public void run(ConfiguredObject co) {
configuredObject = co;
System.out.println("MajorVersion is : " + co.getMajorVersion());
System.out.println("MinorVersion is : " + co.getMinorVersion());
}
}
/**
* Main模块
* @author ASCE1885
*
*/
public class Main {
public static void main(String[] args) {
//创建Builder对象
Builder builder = new Builder();
ConfiguredObject co = builder.createConfiguredObject("R3","C02");
//创建Application对象
Application app = new Application();
app.run(co);
}
}
2)引入抽象工厂方法
上面的方法中,应用程序并不知道对象co何时被创建,但是有些情况下,应用程序也要负责确定创建对象的时机,例如在某订单处理系统中,应用程序必须创建LineItem实体并添加到Order对象。这种情况下,我们可以应用抽象工厂模式让应用程序自行控制何时创建LineItem,同时构造的细节隔离于应用程序代码之外。如图所示,从箭头的方向可知,应用程序OrderProcessing与如何构建LineItem的细节是分离开的,它只拥有抽象工厂方法的接口,具体细节是由main这边的LineItemFactoryImplementation实现的。但应用程序能完全控制LineItem实体何时创建,甚至能传递应用特定的构造器参数。
简单实现代码如下:
/**
* LineItem实体
* @author ASCE1885
*
*/
public class LineItem {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public LineItem(String name) {
this.name = name;
}
}
/**
* 抽象工厂类
* @author ASCE1885
*
*/
public interface LineItemFactory {
public LineItem makeLineItem(String name);
}
/**
* 具体工厂类
* @author ASCE1885
*
*/
public class LineItemFactoryImpl implements LineItemFactory {
private LineItem lineItem;
@Override
public LineItem makeLineItem(String name) {
if (null == lineItem) {
lineItem = new LineItem(name);
}
return lineItem;
}
}
/**
* 订单处理类,自行控制何时创建LineItem
* @author ASCE1885
*
*/
public class OrderProcessing {
private LineItem lineItem;
private LineItemFactory lineItemFactory;
public OrderProcessing(LineItemFactory lif) {
lineItemFactory = lif;
}
public void run() {
lineItem = lineItemFactory.makeLineItem("ASCE1885");
System.out.println("The name of lineItem is : " + lineItem.getName());
}
}
/**
* Main模块
* @author ASCE1885
*
*/
public class Main {
public static void main(String[] args) {
LineItemFactory lif = new LineItemFactoryImpl();
OrderProcessing op = new OrderProcessing(lif);
op.run();
}
}
3)依赖注入DI
依赖注入是控制反转IoC在依赖管理中的一种应用手段,控制反转是将第二权责从对象中分离出来,转移到另一个专注与此的对象中,从而遵循单一权责原则SRP。因为初始设置是一种全局问题,这种授权机制通常要么是main例程,要么是有特定目的的容器。
JNDI查找是DI的一种“部分”实现,如下代码片段是在JNDI中,对象请求目录服务器提供一种符合某个特定名称的“服务”:
MyService myService = (MyService)(jndiContext.lookup("NameOfMyService"));
上面代码中,调用对象jndiContext并没有控制真正返回对象的类别,但是它仍然主动分解了依赖(NameOfMyService)。
在真正的依赖注入中,类并不直接分解其依赖,而是完全被动的。类只提供可用于注入依赖的赋值器方法或者构造器参数(或两者皆有),然后在构造过程中,由DI容器来实体化所需的对象,并使用类提供的构造器参数或者赋值器方法将依赖连接到一起。至于哪个依赖对象真正得到使用,是通过配置文件或在一个有特殊目的的构造模块中编程实现的。详见Spring框架中的JavaDI容器,用户在XML配置文件中定义相互关联的对象,然后在Java代码中请求特定的对象。
分享到:
相关推荐
微软书籍Write Clean Code 微软书籍Write Clean Code 微软书籍Write Clean Code 微软书籍Write Clean Code
Clean Code A Handbook of Agile Software Craftsmanship 英文无水印pdf pdf所有页面使用FoxitReader和PDF-XChangeViewer测试都可以打开 本资源转载自网络,如有侵权,请联系上传者或csdn删除 本资源转载自...
Writing Clean Code.rar aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
Clean Code Summary 英文无水印pdf pdf所有页面使用FoxitReader和PDF-XChangeViewer测试都可以打开 本资源转载自网络,如有侵权,请联系上传者或csdn删除 本资源转载自网络,如有侵权,请联系上传者或csdn删除
Writing Clean Code中文版 好东西大家分享
CleanCode 简要说明
Writing Clean Code资料,对学习编码很有帮助。
编程精粹(Writing Clean Code)中文pdf版
24 Patterns for Clean Code 英文mobi 本资源转载自网络,如有侵权,请联系上传者或csdn删除 本资源转载自网络,如有侵权,请联系上传者或csdn删除
完美模式设计指南(Write Clean Code) CHM版 繁体中文
24 Patterns for Clean Code 英文azw3 本资源转载自网络,如有侵权,请联系上传者或csdn删除 本资源转载自网络,如有侵权,请联系上传者或csdn删除
《Clean Code(评注版)》提出一种观念:代码质量与其整洁度成正比。干净的代码,既在质量上较为可靠,也为后期维护、升级奠定了良好的基础。《Clean Code(评注版)》作者给出了一系列行之有效的整洁代码操作实践。这些...
Clean-Code-A-Handbook-of-Agile-Software-Craftsmanship-Robert-C-Martin-Series Robert C. Martin Series The mission of this series is to improve the state of the art of software craftsmanship. The books ...
clean_code(中文完整版)clean_code(中文完整版)clean_code(中文完整版)clean_code(中文完整版)
clean code英文版,作者Robert C. Martin
24 Patterns for Clean Code 英文无水印pdf pdf所有页面使用FoxitReader和PDF-XChangeViewer测试都可以打开 本资源转载自网络,如有侵权,请联系上传者或csdn删除 本资源转载自网络,如有侵权,请联系上传者或...
Clean Code Summary Agile Software Craftmanship Guidelines Developer Deconstructed 英文无水印pdf pdf所有页面使用FoxitReader和PDF-XChangeViewer测试都可以打开 本资源转载自网络,如有侵权,请联系上传者...
代码审查、类/包设计、TDD、持续集成速查表 Urs Enzler花了大约一年半的时间整理了这份速查表,旨在帮助开发者检查代码是否洁净 •清洁代码 •类/包设计 •TDD——测试驱动开发 •ATDD——检验测试驱动开发 ...