这两天在工作中经常会提到spearation of concern这个概念。我想在这篇博客里讲一下我对这个概念的理解。实际上关注分离并不是一个专属于IT领域的概念。在各个领域我们都在不自觉的考虑如何将任务切割,分配。记得在我读书的时候,看一些开源代码,就是不理解为什么这些所谓的牛逼代码写得那么绕:明明一个简单的new就能创建一个实例,偏要用一个工厂类来生成;明明一个new就能解决的问题,偏偏要拆成allocator和constuctor两个步骤;明明只为实现一个简单的逻辑,偏要在前面,后面包很多层。当时不理解为什么要这样,只是觉得这样很高端大气上档次。后来自己代码写得多了,书也看了不少,才慢慢体会到这些牛逼代码的设计初衷,懂得欣赏它们。我们创造出那么多种模式,发明了那么多编程理念,实际上核心都围绕着一点:关注分离。下面我们从最简单的例子慢慢衍生。
例子1:想要实现一个逻辑,给每一个员工配一辆车。我们最直观的想法就是需要车,就new一个实例出来呗:
Employee * AllocateCar()
{
Employee *employee = new Employee();
...
employee->Car = new Car();
return employee;
}
例子2:我们的需求有一点小小的变化:显然不同的员工统一的配相同的车是不合适的。经理要有经理的车,HR要有HR的车。
Manager * AllocateCar()
{
Manager *manager = new Manager();
...
manager->Car = new Car1();
return manager;
}
HR * AllocateCar()
{
HR * hr = new HR();
hr->Car = new Car2();
...
return hr;
}
这样写代码显然是不好的:但凡有一个新的工种,需要配备一个新车,就得增加一个新的函数。考虑到经理和HR大多数属性及行为是一致的,车也是如此,我们很容易想到抽象出一个基类:
EmployeeBase * AllocateCar(EmployeeType type)
{
EmployeeBase *employee;
if (type == Type.Manager)
{
employee = new Manager();
...
employee->Car = new Car1();
}
else if (type == Type.HR)
{
employee = new HR();
...
employee->Car = new Car2();
}
...
return employee;
}
显然这样比刚才好很多的,至少统一了。但是还是不太好:这个函数本应该就负责分配小车,它并不需要关心不同工种配不同的车。它要做得太多了。为了解决这个,我们可以创建工厂类(如下):
EmployeeBase * AllocateCar(EmployeeType type)
{
EmployeeBase * employee = EmployeeFactory.Create(type);
...
employee->Car = EmployeeBase.SelectCar(type);
return employee;
}
引入工厂类,这个函数不会再变化了:所有变化的东西都转移到了另外两个函数中去了。这样的好处是显而易见的:关注分离了。 分车的只要管好你分车的逻辑就好了。
其他的事交给其他的模块。
现在还有问题么? 实际上这个函数只想做一件事, 给员工分配车。但事实上它做了两件事:1)配车前创建了员工的实例, 2)配车。显然通过函数名是无法预料到还有步骤1,这很有可能会导致内存泄漏。鉴于此,我们应该将创建员工上移:
EmployeeBase * AllocateCar(EmployeeBase *employee)
{
if (employee == NULL) return NULL;
employee->Car = EmployeeBase.SelectCar(employee->Type);
return employee;
}
EmployeeBase * CreateEmployee(EmployeeType *type)
{
EmployeeBase * employee = EmployeeFactory.Create(type);
...
return AllocateCar(employee);
}
这样关注点就继续分离了:每一个函数都只负责一个逻辑。
但是这样还是不够好。我们想想创建完employee实例后(CreateEmployee) 我们是需要使用它的,用完还需要释放该实例。于是我们还需要编写一个ReleaseEmployee函数,并且想着调用它。 这样并不好。使用它的人往往只关注关于employee的业务逻辑,他不应该管理employee的生命周期。如果employee的生命周期非常长,想管好它也是非常不容易的。于是我们可以引入了依赖注入(Dependency Injection)这个概念。依赖注入容器负责管理依赖关系,生命周期,让业务逻辑将关注点从全部放在“业务”上。当代码量庞大的时候这种关注分离的威力就能体现出来了。因为任何地方出错,或者需要更新,我们只需要分析,修改,替换一小块模块,而不影响其他模块。从代码层面上说,这样的代码更容易维护,更容易扩展。从公司层面上说,招人更容易:)