本文继续聊另外一个设计原则:开闭原则。在UI动画框架中,开闭原则在“动画策略”和“移动算法”这两个类体系中均有所体现。照旧,先看一下开闭原则的定义。
1. 开闭原则
一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。
有人说过“唯一不变的就是变化”,尤其是软件需求的变化。在不断支持新需求的时候,应尽量避免“修改”已有的代码,尽量通过“扩展”来实现变化。所谓的“修改”,指的是修改现有系统的关键抽象,比如在继承体系中修改类的设计;而“扩展”指的是在现有系统的基础(或框架)之上附加新的组件来实现新增的功能,比如新加入一个派生类,新注册一个回调函数等。
有人可能会说,新加一个派生类,总要在一个地方创建这个派生类的实例对象吧,这不算是“修改”吗?算!确实是修改了现有代码。但是这个修改只局限在“初始化”阶段(或者说“配置”阶段),而现有系统的核心抽象层并没有感知到这个修改,核心逻辑也不用做任何改动,这就是“对修改关闭”的含义。
2. 框架中的开闭原则
开闭原则在动画框架中有两处体现,一个是策略类体系:
在实际应用中,UI动画框架扩展的需求已经多达10个以上,在应用初期,也只有简单的几个动画策略,比如:帧率、移动、缩放等。某一天,客户提了一个“淡入淡出”的动画需求,框架怎么支持呢?步骤大概是这样的:
- 首先实现“淡入淡出”的功能类:FadeStrategy;
- 接着在初始化阶段,将该类纳入框架;
- 最后,客户在配置文件中用动画语言编写具体的动画。
第2步骤涉及到的初始化阶段的改动,在这里:
IStrategy* AnimationParser::BuildStrategy(const AttriMap& attributes) const
{
if (attributes["id"] == "frame"):
return new FrameStrategy(attributes["param"]);
else if (attributes["id"] == "move"):
return new MoveStrategy(attributes["param"]);
else if (attributes["id"] == "scale")
return new ScaleStrategy(attributes["param"]);
else if (attributes["id"] == "fade")
return new FadeStrategy(attributes["param"]);
...
else
throw ("invalid strategy type!") + attributes["id"]);
}
BuildStrategy
就是初始化阶段负责将具体策略类加入进框架的地方。我们对现有代码所作的修改仅此而已,框架的核心抽象:Window类、IStrategy基类以及现有的各派生类(FrameStrategy、MoveStrategy或ScaleStrategy)都没有做任何修改,甚至是使用该新增动画策略(FadeStrategy)的地方也没有改动,在框架中,你甚至不知道在哪里使用了这个新增动画策略,一切都隐藏在IStrategy背后。我们为框架新增了功能,但没有对核心做任何修改,我们轻而易举地做到了“对扩展开放,对修改关闭”!
另外一处体现开闭原则的地方是移动算法类体系:
窗口移动方式很快就由直线移动(水平、垂直、斜线平移),扩展到二次曲线移动(抛物线、反抛物线移动等)。比如又来了一个圆形转动的需求!实现的步骤如下:
- 首先实现“圆形”轨迹的功能类:Circle;
- 接着在初始化阶段,将该类纳入框架;
- 最后,客户在配置文件中用动画语言编写具体的动画。
第2步骤涉及到的初始化阶段的改动,在这里:
Locus* BuildLocus(const String& type, ...)
{
if (type == "horizontal")
return BuildHorizontal(...);
else if (type == "parabola")
return BuildParabola(...);
else if (type = "circle")
return BuildCircle(...);
...
else
return nullptr;
}
我们再一次看到了开闭原则的体现。
3. 结语
有关设计模式的几个原则,比如“依赖倒置”原则、“接口隔离”原则、“最少知识”原则以及本文讨论的“开闭”原则,这些原则有相当程度上的相通性,虽然它们的实现方式各不相同,但它们都有一个共同点:解耦!
就我个人的理解,还有一个“逻辑与数据分离”的原则(或者说算法与数据分离)基本上也是在为解耦而翻来覆去的捣腾!当你准备对你的业务实施“算法与数据”分离原则的时候,你得到的类图框架大抵是这样的:
只有最大限度的解耦,你才有足够的自由度,以足够低的代价修改你的系统,扩展你的框架,支持多变的需求!