`
mmdev
  • 浏览: 12917030 次
  • 性别: Icon_minigender_1
  • 来自: 大连
文章分类
社区版块
存档分类
最新评论

Clean Code学习笔记の将系统的构造与使用分开

 
阅读更多

为了软件系统在系统层级上保持整洁,需要为系统演化出恰当的抽象等级和模块。其中一个有效的方法就是将系统的构造和使用分开,因为构造和使用是非常不一样的过程。

软件系统应将启始过程和启始过程之后的运行时逻辑分离开,如果在启始过程中构建应用对象,将会存在相互缠结的依赖关系。将关注的方面分离开,是软件技艺中最古老也是最重要的设计技巧。不幸的是,多数应用程序没能做到分离处理,启始过程代码很特殊,被混杂到运行时逻辑中,下面的代码片段是著名的延迟初始化/赋值,这种处理的好处是在真正用到对象之前,无需操心这种架空构造,启始时间也会缩短,而且能够保证永远不会返回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代码中请求特定的对象。


分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics