写简单的代码,是每个程序猴最为重要的事情之一。 本章提出了几点建议:
同一个方法中,它们的细节应该保持一致。这样将更容易理解。 把方法分解成命名良好的、处在细节的同一层面上的行为模块,以此来简化方法。 上层调用将不再关心细微的处理。而是业务逻辑。 例如:
def calc(type):
if type==0:
# 一大段非常多细节的代码
pass
dosth() # 对细节的封装
# 重构后
def calc(type):
if type==0:
dosth1()
else:
dosth2()
把算法的每个变体搬移到新类或子类通常是更好的选择。这实际上就是对象组合和继承之间的选择。
讲白了。就是抽象出算法的部分,建立新的类。然后将它组合进原来的类中。
值得注意的是。switch case通常不能被简单的消除,而只是换了一种形式。比如以下示例:
def capital(type):
if type==1:
pass
if type==2:
pass
...
return result
# 重构后
loan = new loan()
loan.inner_capital = new Type1Capital()
loan.capital() # 这里消除了type。因为依赖type的选择分支被拆分到了不同的Capital类中
但是,如何根据实际选择组合Type1Capital,还是Type2Capital 呢?恐怕,除了反射也没有很好的办法消除这一层逻辑了。
如果只是需要对原有的类进行边角功能的扩充,则可以考虑使用装饰器。 但是:
如果需要搬移装饰功能的类包含许多的公共方法,那么Decorator模式不应该是重构的选择。
因为装饰器应该是透明的。即,被包装的对象不应该感知到它被包装。 在理想情况下,装饰器也不应该互相影响。否则,它们之间就会有顺序的依赖问题。 示例: 对一个Html的node在tostring的时候是否应该解码
class StringNode():
def __init__(self):
self.should_decode=False
def get_str(self):
...
if self.should_decode:
return decode(result)
return result
重构为:
# 将decode(result)的逻辑抽离出StringNode类
string_node = new StringNode()
decode_node = new DecodeNode(string_node)
decode_node.get_str()
把状态改变条件逻辑从类中去除,并搬移到表示不同状态的一系列类中,可以产生更简单的设计。
如果把所有状态的迁移都放在一个地方处理(不使用状态模式)则很可能该方法的责任过大。方法体也会很长。 状态模式实质上,是把在一个类中不断的state变换,分解成一个个子状态。子状态类中,只关心状态的迁移。也就是上下文。
示例:
class Task(object):
def __init__(self):
self.state = 'nostart'
def run(self):
if self.state=='nostart':
self.state = 'doing'
elif self.state == 'doing':
if self.check():
self.state = 'done'
else:
self.state = 'undone'
self.update()
def check(self):
# ...
return True
def update(self):
# ...
pass
task = Task()
print(task.state)
task.run()
print(task.state)
task.run()
print(task.state)
在这个例子中,所有状态的转化都依赖于主类的run。且,check操作只有在state==doing的时候才用的上。此时,可以进行重构。 重构后:
class Task(object):
def __init__(self):
self.state = NoStart()
def run(self):
self.state.run(self)
self.update()
def set_state(self, state):
self.state = state
def update(self):
pass
class State(object):
def run(self, task):
pass
def get_str(self):
pass
class NoStart(State):
def run(self, task):
new_state = Doing()
task.set_state(new_state)
def get_str(self):
return 'nostart'
class Doing(State):
def run(self, task):
if self.check():
task.set_state(Done())
else:
task.set_state(UnDone())
def get_str(self):
return 'doing'
def check(self):
# ...
return True
class Done(State):
def run(self, task):
pass
def get_str(self):
return 'done'
class UnDone(State):
def run(self, task):
pass
def get_str(self):
return 'undone'
task = Task()
print(task.state.get_str())
task.run()
print(task.state.get_str())
task.run()
print(task.state.get_str())
此时,在主task中,run被抽象为执行当前的state的run。这样做的好处在于:
1. 流程的控制下移到了state。此时在state中,只需要关系当前状态下的转移。而不需要再次对当前状态进行判断。if self.state=='nostart':
这样的句子被移除。
2. 不通用的check
函数从主类移除。
这个重构其实很常见,即,将隐含的树形结构用组合模式来表示。 实际上,系统提供的xml包就是一个很不错的例子。
command模式,实质上是将执行的一行行命令原子化和抽象化。 比如:
if xxx:
# dosth1
elif xxx:
# dosth2
重构为
if xxx:
task = Dosth1()
task.exec(args)
elif xxx:
task = Dosth2()
task.exec(args)
将子任务抽象为相同的exec
接口,会使得系统更为明晰。虽然上面的重构没有包含command_list。但是仍然是一种不错的思路。
当走出这一步,就会有意想不到的收获。