代码重构:如何充实你的设计工具箱

用户头像
NORTH
关注
发布于: 2020 年 06 月 23 日
代码重构:如何充实你的设计工具箱

做开发这么多年,越来越害怕自己不知道。不自知有两点体现:

  • 确实不知

  • 思维固化



先说第一点,其实就是经验太少且眼界太窄。当你需要设计或者开发某个系统的时候,其实业界已经有成熟的方案,但我不知道,只能抓耳挠腮凭自己已有的经验做出一个漏洞百出的系统。又比如,如果你没有了解积累足够多的设计模式,怎么重构?用相同的方式再写一遍代码吗?



再说第二点,其实就是要时常更新你的知识库,同样的知识或者框架,它现如今的样子与你脑袋中的也许已经相去甚远。比如,提起Spring Web,脑袋中只有Spring MVC的话,也许就应当多关注一下Spring的新特性了。再比如,提起MongoDB,脑袋中可能会出现“不支持事务”、“不能Join”、“不能用于核心业务”等固有印象,但实际上,MongoDB早已经支持分布式事务,支持左右连接,也早用于很多的核心业务。



这两点对一个人或者技术团队的影响真的挺大的,如果想要成为一个好的架构师,更要努力规避这两点。比如让你来设计一个系统,单就数据库层面,我想大多数人还是会延续先单库 ( MySQL ),然后通过外部的中间件整合成集群来应对大数据和大规模扩展的场景。但团队是否有足够的能力来掌控数据库集群和中间件,公司是否愿意投入足够的时间和人力成本来折腾这些?答案还真不一定。很多创业公司或者小团队的核心价值,真不在折腾技术上,而在于快速交付,那为何不尝试一下其他的数据库呢?比如,新的业务可以尝试MongoDB,天生支持集群和分布式;如果一定要兼容MySQL协议,那也可以试试TiDB呢,它是纯分布式架构,支持弹性扩容,支持事务和高可用。



所以,架构师的天花板也许就是很多团队的天花板,特别是一些小的团队,表现尤甚。那如何让这个天花板高一些呢?对此,我有两点建议。



一是开阔眼界,常规的方式比如多看看最近的技术书籍,可以参加一些社群或者论坛。另外我觉得一个比较好的方式是订阅极客时间的每日一课 ( 能不能给点广告费 ),看看别人的经验总结,也看看其他语言有趣的特性。我个人经常看,不是说一定要有多大收获,至少对于开阔眼界来说,已经挺不错了,



二是充实自己的工具箱,对于一些基础的知识,要努力去学习去掌握。诸如语言的基础特性、数据库的原理、设计模式、分布式系统设计等等,对于自己薄弱的方面,要努力去补齐。



本周课程的主要内容是设计模式,那就以设计模式为例,来说说如何掌握设计模式吧。



对于设计模式,首先要掌握的就是每种设计模式主要解决的问题是什么以及何时该使用这种设计模式。只要是一本讲设计模式的书,应该都会讲这个,多看看,实在不行,背几个常用的也行。



其次,在平时的开发中,多重构自己的代码。就如同前面所说的那样,如果发现自己的代码没办法扩展,代码冗余,甚至连自己都看不下去的时候,那么就想想是不是可以使用合适的设计模式来重构一下。大多数情况下,应该都是可以的,就看我们愿不愿意花时间去做。



最后,在学习设计模式的时候,对于一些相似的设计模式,多多对比它们的使用场景和优缺点,不仅可以加深自己的理解,对自己的设计能力也是一种很好的锻炼。



这里以模版方法模式和策略模式为例,聊聊它们之间的异同,以下的示例来自于「敏捷软件开发」一书,详情可点击参考资料查看。



在模版方法模式中,通常会定义一个抽象类作为基类来封装通用算法,用来串联一些基础操作。比如,下面是冒泡排序的算法:



public abstract class BubbleSorter {
// 数组长度
protected int lengh = 0;
// 排序
public void doSort() {
if (lengh > 1) {
for (int i = lengh - 2; i >= 0; i--) {
for (int j = 0; j <= i; j++) {
if (outOfOrder(j)) {
swap(j);
}
}
}
}
}
// 交换位置
public abstract void swap(int index);
// 比较
public abstract boolean outOfOrder(int index);
}



不同数据类型的数组排序,只需要实现这个抽象类即可,比如:



public class IntBubbleSorter extends BubbleSorter {
private int[] array;
public IntBubbleSort(int[] array) {
this.array = array;
this.lengh = array.length;
}
@Override
public void swap(int index) {
int temp = array[index];
array[index] = array[index + 1];
array[index + 1] = temp;
}
@Override
public boolean outOfOrder(int index) {
return array[index] > array[index + 1];
}
}



如果要对Double类型的数据排序,只需要再实现一个DoubleBubbleSort类即可。



模板方法模式是一个使用非常广泛的设计模式,常见的比如Spring中的各种Template类、Servelet中的HttpServlet抽象类等。但模板方法模式的实现是有代价的,由于基类和子类是继承关系,这就导致子类与它们的基类紧紧的绑定在一起。如果要实现其他类型的排序,比如快速排序,也需要使用IntBubbleSorter中的swap和outOfOrder方法,但却没有办法重用它们。由于继承了BubbleSorter,就注定要把IntBubbleSorter和BubbleSorter永远绑在一起。不过,策略模式提供了另一种可选方案。



策略模式则使用了一种非常不同的方法来倒置通用算法和具体实现之间的依赖关系。它将通用算法放进一个具体的Runner类中,把接口的实现类传给Runner类,这样,Runner类就可以把具体的工作委托给这个接口去完成。



来改写上面的例子,让不同的排序方法可以重用swap和outOfOrder等方法。首先定义处理排序的公共接口:



public interface SortHandler {
// 交换位置
void swap(int index);
// 比较
boolean outOfOrder(int index);
// 获取数组长度
int length();
// 设置数组
void setArray(Object array);
}



接下来把通用的算法放到一个Runner类中,比如,前面的冒泡排序算法的实现如下:



public class BubbleSorter {
private SortHandler handler;
public BubbleSort(SortHandler handler) {
this.handler = handler;
}
// 排序
public void doSort(Object array) {
handler.setArray(array);
int lengh = handler.length();
if (lengh > 1) {
for (int i = lengh - 2; i >= 0; i--) {
for (int j = 0; j <= i; j++) {
if (handler.outOfOrder(j)) {
handler.swap(j);
}
}
}
}
}
}



在使用时,只需要将SortHandler的具体实现传给BubbleSorter即可。比如,要给整型数组使用冒泡算法排序,只需要实现SortHandler即可:



public class IntSortHandler implements SortHandler {
private int[] array = null;
@Override
public void swap(int index) {
int tmp = array[index];
array[index] = array[index + 1];
array[index + 1] = tmp;
}
@Override
public boolean outOfOrder(int index) {
return array[index] > array[index + 1];
}
@Override
public int length() {
return array.length;
}
@Override
public void setArray(Object array) {
this.array = (int[]) array;
}
}



请注意,这里IntSortHandler类对BubbleSorter类一无所知,它不依赖于任何排序算法的实现。再回顾一下模板方法模式,其中IntBubbleSorter直接依赖于BubbleSorter,BubbleSorter类中包含冒泡排序算法,而策略模式则不包含这样的依赖,我们可以很容易的实现另外一种算法:



public class QuickSorter {
private SortHandler handler;
public QuickSorter(SortHandler handler) {
this.handler = handler;
}
// 快排算法实现
public void doSort(Object array) {
}
}



客户端在使用时也非常简单,可以很容易的切换不同的排序算法:



public static void main(String[] args) {
int[] array = new int[]{1,3,5,6,7,2,3};
BubbleSorter bubbleSorter = new BubbleSorter(new IntSortHandler());
bubbleSorter.doSort(array);
QuickSorter quickSorter = = new QuickSorter(new IntSortHandler());
quickSorter.doSort(array);
}



这两个模式都可以用来分离高层算法和低层的具体实现细节,模板方法模式允许一个通用算法操纵多个具体实现,而策略模式则允许每个具体实现可以被多个不同的算法操纵,因此策略模式完全遵循依赖倒置原则,而模板方法模式则部分违反这一原则。



其实,这两个设计模式也印证在 软件开发:软件设计的基本原则 所说,软件的设计原则只是用于指导开发,并不是要我们机械的全盘遵守。



最后,设计模式是你工具箱里面的利器,但也不要乱用,毕竟做出来永远在做好的前面。



极客大学架构师训练营第三周学习总结

封面图来自于 Katie Rodriguez



参考资料

敏捷软件开发(原则模式与实践)



发布于: 2020 年 06 月 23 日 阅读数: 1312
用户头像

NORTH

关注

Because, I love. 2017.10.16 加入

这里本来应该有简介的,但我还没想好 ( 另外,所有文章会同步更新到公众号:时光虚度指南,欢迎关注 ) 。

评论 (3 条评论)

发布
用户头像
策略方法中提到的“冒泡排序”和“快速排序”实现于一个接口类更好吧
2020 年 06 月 30 日 09:14
回复
用户头像
边学习边总结
2020 年 06 月 23 日 16:10
回复
用户头像
写得挺好得~
2020 年 06 月 23 日 15:47
回复
没有更多了
代码重构:如何充实你的设计工具箱