设计模式, design pattern, 其实就是人们在使用OO进行构架系统的时候归纳出来de一些经验, 可以帮助你更科学的使用OO进行程序设计。
模式是归纳出来的, 或许你现在进行的OO编程里面就使用了一些模式,只不过你不知道它叫什么名字而已, 也或许有一天你能归纳出一个新的确实有用的设计模式。
使用OO编程有几大原则:
1. 尽量面对接口编程, 少对实现编程。
这句话有点拗口。 其实它还有这样一个意思,尽量在抽象类的层次上进行编码, 尽量少的直接编写跟具体类相关的代码。
因为抽象类相对来说变化比较小, 而具体类则可能经常需要改变, 那么少直接编写具体类相关的代码, 则可以让你承受更小的代码变更压力。
2. OO编程的一个基本原则就是封装变化。
把你觉得长时间内不变的东西提取到抽象类,把可能变化的东西封装到具体类, 以后发生了变化则只要修改某个具体类就可以了。
3. 少用继承,多用组合
一个类派生另外一个子类, 那么你这个子类在编译时就具有了父类的特性。 如果你不继承, 而通过跟另外一个抽象类组合的方式, 那么在程序运行时你可以指定你要组合的具体类,这样弹性更大一些。
4. 一个类,一个责任
我们知道, 类的功能需要变化时, 类就要进行修改。 如果一个类承担了多个责任,那么它发生变化的概率将成倍上升。 当然。或许我们应该找出一个平衡点。。
目前学到的一些设计模式:
1. 策略模式: strategy
策略这个词也比较拗口, 实际上它是这样的一种模式: 假设你的代码要用到某种算法, 而该算法有多种实现方式,或者以后还会有更号的实现方式,那么你先定义算法抽象类, 从该抽象类派生出各种具体实现类, 这些具体实现类合称算法族。你的代码里面只要调用抽象类的接口即可。 至于具体调用的是哪个具体实现类的算法, 可以由你在运行时指定。 OO语言一般都具有多态的特点,因此这个模式一般都能很简单的实现。
2. 观察者模式: Observer
这个模式其实就是针对接口编程的一个有力例子罢了。 针对接口,针对抽象编程,可以带来类之间的松耦合。
举例: 有主题和众多观察者, 主题状态发生改变时要更新所有观察者。 那么如果不是针对接口编程,写出来的代码可能就是:
function Subject::onChange {
Observer1.update();
Observer2.update();
...
}
为了以后容纳更多的Observer, 便定义一个Observer 抽象类(接口),Observer1,Observer2等都要继承该接口。 Suject只需要维护一个Observer列表。
function Subject::onChange {
for observer in ObserverList {
observer.update();
}
}
很简单,对吧? 为了维护ObserverList, Subect必须同时实现AddObserver, DeleteObserver等接口。
3. 装饰模式: Decorate
与其叫装饰,我更喜欢叫它包裹或者伪装。
对于一个已经写好的类A,要给它增加一些功能的话,那么在它外面再包裹一个类, 姑且叫它 DecorateClass, DecorateClass 跟 A都要从同一个父类继承, 来保证DecorateClass在类型上可以伪装成A, 同时为了让DecorateClass保持A具有的那些功能(行为), DecorateClass必须在内部组合一个A。
4. 简单工厂, 工厂方法, 抽象工厂
这几个词真拗口。。日
当你在代码里面需要根据条件实例化不一样的具体类时:
function MyClass::foo() {
//...
if (condition1) {
instance = new A();
} elsif (conditon2) {
instance = new B();
}
//...
}
...
以后新增一个具体类时,这段代码都可能发生改变,要增加一个分支或者怎么的。 那么把这段会变化的代码提取出来放到一个类里面的一个方法里面去: Factory.create_instance(); 这种做法只是一种编程习惯而已, 谈不上有很好的优点。它只是把改变移到了 Factory.create_instance里面去。 不过这种做法在一定程度上可以把变化的代码集中到一块里面去。 因为可能有很多地方都需要根据条件实例化不一样的具体类。 那么让它们都调用Factory.create_instance就可以了,以后发生改变只要修改一个地方就可以而无需多个地方都要改,还可能改漏。 这种方法就叫简单工厂.
工厂方法, 类似于简单工厂, 也是把这段变化的代码抽取出来放到一个方法里面去,不过它不是放到其他类的方法里,而是在本类里面增加一个方法create_instance
function MyClass::foo() {
//...
instance = create_instance();
//...
}
function MyClass::create_instance() {
//if (condition1) {
// instance = new A();
//} elsif (conditon2) {
// instance = new B();
//}
}
create_instance里面不要包含具体代码。 让它为空吧,因为我们要让MyClass的子类来重载实现这个方法。 有多少个分支就增加多少个子类. 这种方法叫为工厂方法。它充分体现了不要让抽象类包含具体实现代码的思想。
至于抽象工厂,我目前理解为,它只是工厂方法的进一步抽象而已。
举例:如果我们要根据不同条件实例化多个相关的具体类,使用工厂方法实现则是:
function MyClass::foo() {
//...
instance_a = create_a_instance();
instance_b = create_b_instance();
instance_c = create_b_instance();
//...
}
MyClass有N个子类: Child01, child02, Child03..
function Child01::create_a_instance() {
return new A1();
}
function Child01::create_b_instance() {
return new B1();
}
function Child01::create_c_instance() {
return new C1();
}
同理,
...child02, Child03.. 也要实现3个create_x_instance方法
如果以后出现instance_d要实例化, 那么MyClass要增加一个creaet_d_instance方法, Chilid01,02,03都要增加一个creaet_d_instance的方法。
追求美感的你于是把所有的creaet_x_instance再提取出来成为一个抽象工厂如下:
Factory {
function create_a_instance();
function create_b_instance();
function create_c_instance();
function create_d_instance();
}
Factory1: Factory {
function create_a_instance() { return new A1(); } ;
function create_b_instance() { return new B1(); } ;
function create_c_instance() { return new C1(); } ;
function create_d_instance() { return new D1(); } ;
}
MyClass {
function create_instance();
}
Child1 : MyClass {
function create_instance() {
instance_a = Factory1.create_a_instance();
instance_b = Factory1.create_b_instance();
instance_c = Factory1.create_c_instance();
instance_d = Factory1.create_d_instance();
}
}
...
5. 单件模式: Singleton
这个非常简单的模式,就是要保证类有唯一实例。 实现方法就是把类的构造方法设置为私有,重新添加类的方法: get_instance(), 在该方法里面判断类是否实例过,没有则实例化之,最后返回实例化过的对象。 一般使用static对象和static get_instance则可以实现该模式。
唯一要注意的就是,如果第一次实例化是程序运行时产生的,那要考虑线程安全的问题。
if (instance == null) {
instance = new A();
}
这个是线程不安全的。可以通过加锁的方法来实现线程安全,不过开销太大:
lock();
if (instance == null) {
instance = new A();
}
unlock();
因为通常只有第一次实例化时该锁才是有效的。 可以通过2次判断来减少一些开销:
if (instance == null) {
lock();
if (instance == null) {
instance = new A();
}
unlock();
}
还有另外一种跟语言特性有些相关的方法:
MyClass {
static instance = new A();
static get_instance();
}
在程序刚开始运行时就实例化A。
6. 命令模式 command
发起命令的对象把命令封装成对象, 把该对象投递给管理者(可以是队列或者是数组等), 执行者从管理者取得该对象,并调用该对象的execute方法来运行命令。该模式可以实现发起命令者和执行者的松耦合。
windows里面的消息机制有命令模式的味道, 用户发出各种操作,这些都被封装Message放入进程的消息队列里面,进程的主线程从消息队列取出Message并处理之。
7. 适配器模式 adapter, 外观模式Facade
已经有了某接口I实现了某功能, 当你的程序里面需要使用该功能,但是又由于某种原因不能直接使用接口I时,那么设计出一个adapter类,它通过继承或者组合的方式使用了接口I, 同时提供给你的程序需要的接口。 这个模式跟decorate不一样在于,decorate是为了扩展功能, 接口是不变的, 而adapter是为了改变接口,功能是不变的。
那么还有一个外观模式,它也是对接口的封装, 它的出现是为了简化接口。
如果你的程序需要调用多个接口时, 你使用外观模式封装这多个接口, 提供给你的程序一个简单干净的接口。
8. 模板方法 Template
这个模式很常用..
一个类的方法定义了一个算法的骨架大纲(就类似于定义了一个算法模板), 其实就是一堆子方法的调用, 然后这些子方法可以被子类重载,于是算法依据子类的不同实现而有些差别。
这个模式的应用前提是你可以确实可以提炼出一个算法大纲。
子类可以通过钩子的方法来控制算法的条件分支:
MyCalss {
function MyAlgorithm {
f1();
if (need_do_f2()) {
f2();
}
}
function f1();
function f2();
function need_do_f2() { return true;};
}
Chilid1 : Myclass {
function need_do_f2() {return false;};
function f1() {//this is chilid1 f1};
}
注意这个模式跟策略模式不一样咯,策略并没有定义算法的大纲,只是组合了一个算法接口,让客户选择使用某种算法。
9. 迭代器模式 Iterator, 组合模式 Composite
你的类里面有一堆聚集的对象, 如果你不想对外暴露你内部是使用什么方式来存储这些聚集对象,又想对外提供遍历对象的方法,那么,使用迭代器模式吧。
实现creaet_iterator()的接口, 并定义一个Iterator类,使之至少具有has_next和next 2个方法。
如同菜单里面含有菜单项和子菜单一样,当有一堆聚集的对象, 它们也是呈层次结构的时候,你想跟上面的迭代器模式一样,对外提供遍历接口。那么或许你可以使用组合模式,它可以使到调用接口的客户代码无需关心你的内部实现,无需关心它正在遍历的是菜单项还是子菜单。
一个不是很安全的实现方法就是:
定义类Component表示菜单项或者是子菜单, 类Item和类Menu从Component派生出来. 类Menu内部维护一个Component的列表。
如果使用内部迭代的方法迭代这些聚集的对象:
Component {
function visit();
}
Item : Component {
function visit() { print "visiting me\n"};
}
Menu : Component {
ARRAYLIST component_list;
function visit() {
for (component IN component_list) {
component.visit();
}
};
}
客户代码写: some_component.visit(); 便会递归的遍历所有的对象。
这是一种内迭代,你的客户代码无法控制遍历的过程。 通常情况你都不会满足,你想跟迭代器模式一样可以取到一个iterator自己进行外迭代,那么代码会复杂一些,因为你不得不使用堆栈来实现深度优先遍历:
Component {
function create_iterator();
}
Item : Component {
function create_iterator() { return null_iterator};
}
Menu : Component {
ARRAYLIST component_list;
function create_iterator() {
return new CompositeIterator(component_list.iterator())
};
}
CompositeIterator : Iterator {
Stack stack;
function CompositeIterator (Iterator iterator) {stack.push(iterator)};
//注意使用了递归
function has_next() {
if (stack is empty){
return false;
}
Iterator iterator = stack.top();
if (iterator.has_next()) {
return true;
}
stack.pop();
return has_next();}
}
function next() {
if (has_next) {
Iterator iterator = stack.top();
Component c = iterator.next();
if (c is menu) {
//深度优先遍历
stack.push( c.create_iterator() );
}
return c;
} else {
return null
}
}
}
客户端代码:
Iterator iterator = component.create_iterator();
while (iterator .has_next()) {
iterator.next();
}
CompositeIterator 的代码比较难搞懂,我是看了好几遍才看懂。
10. 状态模式
给对象附加几个状态对象, 并且把对象的某些方法委托给状态来实现,这样当对象的状态发生改变时, 对象的方法也发生了改变。 实现该模式一般是定义State抽象类, 从该类派生出几个具体状态类,State1,State2,State3, 然后修改你的类:
MyClass {
State1 s1;
State2 s2;
State3 s3;
State s;
function action1() {
//委托
s.action1();
}
function action2() {
//委托
s.action2();
}
function change_state(State st) { s = st};
}
State {
function action1();
function action2();
}
这个模式看起来跟策略模式很像,不同的是,策略模式中, 使用哪一种算法子类是客户主动指定的, 而状态模式中的状态是对象内部转换的,并非客户指定。
2个模式的意图也是不一样的, 策略模式的出现是为了使用组合代替继承以带来更多的弹性, 而状态模式的出现是为了去掉类里面的N多if分支判断。如果没有状态模式, 我们刚才的类将变成:
MyClass {
int state;
function action1() {
if(state == STATE1) { do sth}
elsif (state == STATE2) {do sth}
...
}
function action2() {
if(state == STATE1) { do sth}
elsif (state == STATE2) {do sth}
...
}
}
11. 代理模式Proxy
为一个对象提供一个替身, 让客户直接使用替身。
之所以要用到这个模式,一般是这样几种情况
a. 远程代理:当对象是远程时。 可能以前客户是直接跟本地对象打交道,后来为了扩展客户跟远程对象的功能, 又不想对客户代码做大修改,那么便使用一个替身, 它跟客户打交道,而跟远程对象的交流细节则被封装在替身里面。
b. 虚拟代理: 当对象的创建开销比较大, 而客户不能等待对象的创建完才开始使用对象。那么虚拟出一个对象,由它另外创建线程来建立实际对象, 而在实际对象建立完成之前客户仍然可以跟替身打交道,当实际对象创建完毕后, 替身再把客户的请求委托给实际对象。
虚拟代理跟decorate模式很像, 主要是目的不一样罢了,实现上也有些差别吧, decorate不会实例化它装饰的对象, 而虚拟代理会。
c. 安全代理: 当想控制客户对对象的访问权限时。
12. 复合模式compound
鼎鼎有名,如雷贯耳。。MVC要出场了,鉴于Catalyst也是基于MVC,我将对MVC做进一步的学习和研究。
待续。。。。
阅读(1392) | 评论(0) | 转发(0) |