代码重构之解耦合

最近在对业务代码进行重构,遇到了一些比较典型的“散发着难闻味道”的代码,可以用又臭又长来形容。

这部分的业务是发布动态,包括以下步骤:

  • 敏感词过滤
  • 话题提取
  • 动态数据入库
  • 敏感词记录
  • 话题及话题动态关联关系入库

之所以说重构前的代码“又臭又长”,首先最直观的一点,方法的行数超过了200行,其次上面四个步骤的逻辑全都写在了一起,没有按功能模块拆分,这也是方法过长的原因。

如果按功能划分,发布动态包含三个模块:

  • 敏感词模块
  • 动态模块
  • 话题模块

其中的敏感词模块话题模块都是为动态模块服务的。一个动态可以有敏感词,也可以没有敏感词,比如用户分享一个链接,这就不需要做敏感词过滤与记录了,所以敏感词模块与动态模块应该是“松耦合”的。同样,一个动态可以包含话题,也可能不包含,所以话题模块与动态模块也应该是松耦合的。

既然敏感词模块与话题模块都是为动态模块服务的,动态模块就有必要与这两个服务模块建立一个服务约定

  • 我需要什么样的服务
  • 我们之间如何建立耦合关系

我需要什么样的服务

在编程的世界里,约定可以抽象成接口

1
2
3
4
5
6
7
8
interface PublishContract
{
// Call this method before publishing
public function beforePublish();
// Call this method after publishing
public function afterPublish();
}

对于敏感词模块和话题模块,只需要实现该接口,动态模块就可以根据约定调用它们提供的服务。

1
2
3
4
5
6
7
8
9
10
11
12
class SensitiveWords implements PublishContract
{
public function beforePublish()
{
//
}
public function afterPublish()
{
//
}
}
1
2
3
4
5
6
7
8
9
10
11
12
class TopicObserver implements PublishContract
{
public function beforePublish()
{
//
}
public function afterPublish()
{
//
}
}

这样动态模块就知道,在动态入库前调用服务模块的beforePublish服务,入库后调用服务模块的afterPublish服务。

我们如何建立耦合

耦合关系的建立应该由动态发布的调用方决定,动态模块需提供相应的接口,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class Feeds
{
// PublishContract[]
protected $serviceModules = [];
public function addServiceModule(PublishContract $service)
{
$this->serviceModules[get_class($service)] = $service;
}
public function removeServiceModule($serviceClassName)
{
unset($this->serviceModules[$serviceClassName]);
}
public function publish()
{
foreach ($this->serviceModules as $service) {
$service->beforePublish();
}
// do something about publishment
foreach ($this->serviceModules as $service) {
$service->afterPublish();
}
}
}

这样,动态发布的调用方可以通过addServiceModule/removeServiceModule动态地添加/删除服务模块。

UML图

[from processon.com](https://www.processon.com/i/5429053c0cf2e6eabf125bb8 "")

重构带来的好处

松耦合

服务模块与核心模块是独立的,通过接口建立松耦合的关系;可动态添加/删除服务模块。

易于维护

服务模块的修改不会直接影响核心模块。

易于扩展

如果需要添加其它服务模块,只需实现服务约定的接口,并将新的服务模块动态添加到核心模块即可。