前言
在移动应用遗留系统重构(8)- 依赖注入篇
、移动应用遗留系统重构(9)- 路由篇章节中,我们已经完成了基础的注入和路由框架搭建。接着移动应用遗留系统重构(7)- 解耦重构演示篇(一)+视频演示
,本篇我们会把App中剩余的 platform 包、dynamic 包进行重构。文中会主要列出分析及解耦的思路及过程,并且会有详细的完整演示视频。
platform包重构代码演示: mp.weixin.qq.com/s/YJLBFBD9T…
dynamic 包重构代码演示:
mp.weixin.qq.com/s/ZcDDIrJwU…
安全重构演示
platform 包重构
- 依赖分析
platform 中存在对上层bundle中的反向依赖,该依赖需要解除后,便可将该包下沉到platform的模块中。
- 安全重构
重构前代码:
public class LoginActivity extends AppCompatActivity {
UserController userController = new UserController();
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
userController.login("", "", new CallBack() {
@Override
public void success(String message) {
}
@Override
public void filed(String message) {
}
});
}
}
复制代码
重构手法:提取代理类、内联、移动
- 由于UserControler中存在login及getUserInfo方法,我们不能简单将类一起下沉,需要抽取独立类将login方法和LoginActivity一起内聚下沉
重构后代码:
public class UserController {
public static boolean isLogin = false;
public final LoginController loginController = new LoginController();
public boolean login(String id, String password, CallBack callBack) {
//用户登录
return loginController.login(id, password, callBack);
}
public UserInfo getUserInfo() {
//获取用户信息
return loginController.getUserInfo();
}
}
复制代码
- 将LoginActivity对UserController的调用内联为对LoginController的调用
内联login方法:
内联loginController:
抽取成员变量:
重构后代码:
public class LoginActivity extends AppCompatActivity {
private LoginController loginController = new LoginController();
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
//用户登录
loginController.login("", "", new CallBack() {
@Override
public void success(String message) {
}
@Override
public void filed(String message) {
}
});
}
}
复制代码
- 代码移动
代码移动至独立的platform,加上对应的Gradle依赖:
- 功能验证
执行冒烟测试,验证功能
./gradlew app:testDeb
ug --tests SmokeTesting
复制代码
代码演示: mp.weixin.qq.com/s/YJLBFBD9T…
具体的代码:github链接
dynamic 包重构
- 依赖分析
dynamic包存在对fileBundle和userBundle之间的横向依赖。主要是因为动态中需要上传和下载文件,所以依赖了fileBundle,另外需要判断是否登录,所以也依赖了userBundle
- 安全重构
重构前代码:
public class DynamicFragment extends Fragment {
DynamicController dynamicController = new DynamicController();
FileController fileController = new FileController(new UserStateImpl());
Button btnShare;
public static DynamicFragment newInstance() {
DynamicFragment fragment = new DynamicFragment();
Bundle args = new Bundle();
fragment.setArguments(args);
return fragment;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
dynamicController.getDynamicList();
FileInfo fileInfo = fileController.upload("/data/data/user.png");
dynamicController.post(new Dynamic(), fileInfo);
}
}
public class DynamicController {
FileController fileController=new FileController(new UserStateImpl());
public boolean post(Dynamic dynamic, FileInfo fileInfo) {
//发送一条动态消息
if (!UserController.isLogin) {
return false;
}
HttpUtils.post("http://dynamic", LoginController.userId);
return true;
}
public List<Dynamic> getDynamicList() {
//通过网络获取动态信息,有些动态带有附件需要下载
fileController.download("");
return new ArrayList<>();
}
}
复制代码
重构手法:提前代理类、抽取接口、移动、内联、提取变量等
由于FileController中除了上传和下载还有获取文件的方案,我们不能简单粗暴就把整个FileController提前接口,希望抽取除了的接口职责更加单一。
- 抽取FileTransfer类,提取接口
- 将对FileController的依赖内联为FileTransfer
- 对UserBundle的依赖使用已经抽取的UserState接口
- 使用注入的方式进行依赖管理
由于步骤比较多,大家可以直接看视频的演示,这里就不一一截图说明。
重构后代码:
@AndroidEntryPoint
public class DynamicFragment extends Fragment {
@Inject
DynamicController dynamicController;
Button btnShare;
@Inject
TransferFile transferFile;
public static DynamicFragment newInstance() {
DynamicFragment fragment = new DynamicFragment();
Bundle args = new Bundle();
fragment.setArguments(args);
return fragment;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
dynamicController.getDynamicList();
//上传文件
FileInfo fileInfo = transferFile.upload("/data/data/user.png");
dynamicController.post(new Dynamic(), fileInfo);
}
}
public class DynamicController {
@Inject
TransferFile transferFile;
@Inject
UserState userState;
@Inject
public DynamicController() {
}
public boolean post(Dynamic dynamic, FileInfo fileInfo) {
//发送一条动态消息
if (!userState.isLogin()) {
return false;
}
HttpUtils.post("http://dynamic", LoginController.userId);
return true;
}
public List<Dynamic> getDynamicList() {
//通过网络获取动态信息,有些动态带有附件需要下载
//下载文件
transferFile.download("");
return new ArrayList<>();
}
}
复制代码
- 代码移动
代码移动至独立的dynamic Bundle,加上对应的Gradle依赖:
- 功能验证
执行冒烟测试,验证功能
./gradlew app:testDeb
ug --tests SmokeTesting
复制代码
代码演示:mp.weixin.qq.com/s/ZcDDIrJwU…
具体的代码:github链接
ArchUnit
在重构的过程中,我们补充了api的模块,我们需要调整ArchUnit的用例。
@RunWith(ArchUnitRunner.class)
@AnalyzeClasses(packages = "com.cloud.disk")
public class ArchRuleTest {
@ArchTest
public static final ArchRule architecture_layer_should_has_right_dependency =layeredArchitecture()
.layer("Library").definedBy("..cloud.disk.library..")
.layer("Api").definedBy("..cloud.disk.api..")
.layer("PlatForm").definedBy("..cloud.disk.platform..")
.layer("FileBundle").definedBy("..cloud.disk.bundle.file..")
.layer("DynamicBundle").definedBy("..cloud.disk.bundle.dynamic..")
.layer("UserBundle").definedBy("..cloud.disk.bundle.user..")
.layer("AllBundle").definedBy("..cloud.disk.bundle..")
.layer("App").definedBy("..cloud.disk.app..")
.whereLayer("App").mayOnlyBeAccessedByLayers()
.whereLayer("FileBundle").mayOnlyBeAccessedByLayers("App")
.whereLayer("DynamicBundle").mayOnlyBeAccessedByLayers("App")
.whereLayer("UserBundle").mayOnlyBeAccessedByLayers("App")
.whereLayer("PlatForm").mayOnlyBeAccessedByLayers("App","AllBundle")
.whereLayer("Api").mayOnlyBeAccessedByLayers("App","AllBundle","PlatForm")
.whereLayer("Library").mayOnlyBeAccessedByLayers("App","AllBundle","PlatForm");
}
复制代码
目前所有之前的架构设计约束已经完整解耦开,但由于测试文件和Hilt会生成一些编译的问题,所以我们还得新增配置文件进行过滤。
在resource文件夹中新增文件archunit_ignore_patterns.txt,新增过滤规则如下:
.*com.cloud.disk.SmokeTesting.*
.*com.cloud.disk.DaggerSmokeTesting_*.*
复制代码
运行架构守护测试命令如下:
./gradlew app:testDebug --tests ArchRuleTest
复制代码
我们可以看到已正常运行通过。
总结
经过不断的演进重构,目前我们终于把设计的架构守护测试通过,我们通过一张图来对比一下前面的区别。
随着第一阶段的解耦重构完成,CloudDisk团队决定将团队划分为5个团队,分别管理文件、动态、用户中心、平台及公共库。整个项目不再统一使用一个Git大仓进行代码管理,每个团队能独立维护自己的代码仓库。
下一篇,移动应用遗留系统重构(11)- 制品管理篇将分享如何将解耦的模块进行二进制发布,模块间不再使用源码依赖编译,能按设计在独立的git仓库中进行管理。
CloudDisk示例代码
系列链接
移动应用遗留系统重构(7)- 解耦重构演示篇(一)+视频演示
大纲
关于
欢迎关注CAC敏捷教练公众号。微信搜索:CAC敏捷教练。