Templete Method是一个相对简单的模式,在父类中一个 多态地调用子类方法的方法 被称为Templete Method,在js中,因为缺少必要的接口检查和虚方法,不能够简单地使用C++或者Java的方式实现,但通过js的scope chain的灵活应用,可以更为优美地实现这一模式。
在js中,有一种非常有趣的继承方式:元类。尽管语言级别未提供支持,但是first class的函数和动态语言特性使得js能更为彻底地实现元类继承。首先,大概介绍下下元类继承的方式:
function parent(string){
var child=new Function("this.x=10;"+string);
return child;
}
var child=new parent("this.y=20;");
var childObj=new child();
alert(childObj.y);
以某种方式创建一个函数,并将之返回,这就是js版的元类继承。作为扩展,可以不使用Function创建函数,parent为子类提供参数
function parent(n){
return function(){
this.show=function(){
alert(n);
}
};
}
var child=new parent(20);
var childObj=new child();
childObj.show();
如果你认为这不能算作继承的话,那么下面一段代码可以展现这种继承的灵活性:
function parent(prototype){
return function(){
this.show=function(){
alert("show");
}
for(var p in prototype)this[p]=prototype[p];
};
}
这是一个类似原型继承的元类 且它可指定不论以什么对象为原型,创建的类都至少有一个可覆盖的show方法。如果调换一下顺序,则可变成,不论如何,都会有不可覆盖的show方法,原型当中的show方法会被忽略:
function parent(prototype){
return function(){
for(var p in o)this[p]=prototype[p];
this.show=function(){
alert("show");
}
};
}
实际上到此为止,Templete Method的实现方法已经是显然的了,将Templete Method需要用到的方法作为参数传递给元类,将Templete Method放在上例的show的位置即可。下面给出一个广度优先搜索的例子:(以后也许还会用它做策略模式的组成部分 hoho)
function BreadthFirstSearch(extend,beam,finish)
{
return function(){
this.finish=finish;
this.extend=extend;
this.beam=beam;
this.search=function(){
var queue=[this];
while(queue.length)
{
var current=queue.shift();
if(!current.beam()){
var extended=current.extend();
for(var i=0;i<extended.length;i++)
{
if(extended[i].finish())return extended[i];
queue.push(extended[i]);
}
}
}
return null;
}
}
}
按照广搜的算法extend返回一个数组,包含当前节点扩展出的所有子节点,beam是剪枝函数,它返回true表示当前节点可被剪除,否则该节点将被扩展,finish检查是否已搜索到最终结果,如果是则返回true则结束整个搜索。
这个例子过于抽象,我自己看起来都有些不知所云,所以我决定免费赠送一个用此搜索算法解决八皇后问题的例子的例子:P
首先定义八皇后问题的数据结构,用最传统的类和对象来解决:
function Queen(n){
var ret=new Array();
var depth=0;
for(var y=0;y<n;y++)
{
ret.push([]);
for(var x=0;x<n;x++)
ret[ret.length-1].push(0);
}
}
广搜算法是一个泛型算法,所以我们还可以把它设计成一个Prototype Patten,通过定义clone方法来使它不依赖具体类型,这里算是补充一个Prototype Patten的例子,综合使用的clone函数将大大提高clone的速度,我把这种clone方式称为"深原型clone",它的开销比创建深clone对象小得多,且对clone体的修改不会影响到母体和其他clone体,但母体的修改会影响到所有clone体,为了调试方便,我们还可以定义一个toString显示美观一点的棋盘结构,ok,下面是完整的数据结构
<script>
function Queen(n){
var ret=new Array();
ret.size=n;
ret.depth=0;//搜索的深度
ret.pos=0;//新皇后的水平位置
for(var y=0;y<n;y++)
{
ret.push([]);
for(var x=0;x<n;x++)
ret[ret.length-1].push(0);
}
function objectPrototypeClone()
{
var tmp=function(){};
tmp.prototype=this;
return new tmp;
}
ret.clone=function(){
var r=objectPrototypeClone.call(this);
for(var i=0;i<n;i++)
{
r[i]=objectPrototypeClone.call(this[i])
}
return r;
}
ret.toString=function(){
var str="";
for(var y=0;y<n;y++)
{
for(var x=0;x<n;x++)
str+=this[y][x]==0?"○":"★";
str+="/n";
}
return str;
}
return ret;
}
</script>
接下来进入正题,写出八皇后问题的extend beam finish三个函数
extend函数非常容易,八皇后问题每层搜索一行,extend函数即扩展一个新行
function extendQueen()
{
var ret=new Array();
//alert(this.depth);
for(var i=0;i<this.size;i++)
{
var current=this.clone();
//alert(current.depth);
current[current.depth][i]=1;
current.pos=i;
current.depth++;
ret.push(current);
}
return ret;
}
beam函数则是检查新的皇后能否吃到以前的皇后
function beamQueen()
{
var x,y;
if(this.depth==0)return false;
if(this.depth==this.size)return true;
x=this.pos;y=this.depth-1;
while(--x>=0&&--y>=0)
if(this[y][x]!=0)return true;
x=this.pos;y=this.depth-1;
while(--y>=0)
if(this[y][x]!=0)return true;
x=this.pos;y=this.depth-1;
while(--y>=0&&++x<this.size)
{
if(this[y][x]!=0)return true;
}
return false;
}
最后是finish函数 在beam的基础上修改一下就可以了 我们可以通过finish检查,来决定求一个可行解、求所有解、还是对解计数,下面的finish是求出并打印所有解。
function finishQueen(){
if(this.depth<this.size)return false;
x=this.pos;y=this.depth-1;
while(--x>=0&&--y>=0)
if(this[y][x]!=0)return false;
x=this.pos;y=this.depth-1;
while(--y>=0)
if(this[y][x]!=0)return false;
x=this.pos;y=this.depth-1;
while(--y>=0&&++x<this.size)
{
if(this[y][x]!=0)return false;
}
document.write(this+"/n");
return false;
}
有了这三个函数之后,就可以定义一个BFSQueen类,使它继承Queen类并实现BreadthFirstSearch接口
定义如下
function BFSQueen(n)
{
var ret=new Queen(n);
var BFS=new BreadthFirstSearch(extendQueen,beamQueen,finishQueen);
BFS.apply(ret);
return ret;
}
下面是完整的八皇后问题代码,在我的电脑上大约跑了30秒 用FF的话只需要一半的时间
<
pre
style
="font-family:宋体;"
>
<
script
>
function
Queen(n){
var
ret
=
new
Array();
ret.size
=
n;
//
皇后问题的规模
ret.depth
=
0
;
//
搜索的深度
ret.pos
=
0
;
//
新皇后的水平位置
for
(
var
y
=
0
;y
<
n;y
++
)
{
ret.push([]);
for
(
var
x
=
0
;x
<
n;x
++
)
ret[ret.length
-
1
].push(
0
);
}
function
objectPrototypeClone()
{
var
tmp
=
function
(){};
tmp.prototype
=
this
;
return
new
tmp;
}
ret.clone
=
function
(){
var
r
=
objectPrototypeClone.call(
this
);
for
(
var
i
=
0
;i
<
n;i
++
)
{
r[i]
=
objectPrototypeClone.call(
this
[i])
}
return
r;
}
ret.toString
=
function
(){
var
str
=
""
;
for
(
var
y
=
0
;y
<
n;y
++
)
{
for
(
var
x
=
0
;x
<
n;x
++
)
str
+=
this
[y][x]
==
0
?
"
○
"
:
"
★
"
;
str
+=
"
"
;
}
return
str;
}
return
ret;
}
function
extendQueen()
{
var
ret
=
new
Array();
if
(
this
.depth
==
this
.size)
return
ret;
for
(
var
i
=
0
;i
<
this
.size;i
++
)
{
var
current
=
this
.clone();
//
alert(current.depth);
current[current.depth][i]
=
1
;
current.pos
=
i;
current.depth
++
;
ret.push(current);
}
return
ret;
}
function
beamQueen()
{
var
x,y;
if
(
this
.depth
==
0
)
return
false
;
if
(
this
.depth
==
this
.size)
return
true
;
x
=
this
.pos;y
=
this
.depth
-
1
;
while
(
--
x
>=
0
&&--
y
>=
0
)
if
(
this
[y][x]
!=
0
)
return
true
;
x
=
this
.pos;y
=
this
.depth
-
1
;
while
(
--
y
>=
0
)
if
(
this
[y][x]
!=
0
)
return
true
;
x
=
this
.pos;y
=
this
.depth
-
1
;
while
(
--
y
>=
0
&&++
x
<
this
.size)
{
if
(
this
[y][x]
!=
0
)
return
true
;
}
return
false
;
}
function
finishQueen(){
if
(
this
.depth
<
this
.size)
return
false
;
x
=
this
.pos;y
=
this
.depth
-
1
;
while
(
--
x
>=
0
&&--
y
>=
0
)
if
(
this
[y][x]
!=
0
)
return
false
;
x
=
this
.pos;y
=
this
.depth
-
1
;
while
(
--
y
>=
0
)
if
(
this
[y][x]
!=
0
)
return
false
;
x
=
this
.pos;y
=
this
.depth
-
1
;
while
(
--
y
>=
0
&&++
x
<
this
.size)
{
if
(
this
[y][x]
!=
0
)
return
false
;
}
document.write(
++
count
+
"
.
"
+
this
);
return
false
;
}
function
BreadthFirstSearch(extend,beam,finish)
{
return
function
(){
this
.finish
=
finish;
this
.extend
=
extend;
this
.beam
=
beam;
this
.search
=
function
(){
var
queue
=
[
this
];
while
(queue.length)
{
var
current
=
queue.shift();
if
(
!
current.beam()){
var
extended
=
current.extend();
for
(
var
i
=
0
;i
<
extended.length;i
++
)
{
if
(extended[i].finish())
return
extended[i];
queue.push(extended[i]);
}
}
}
return
null
;
}
}
}
function
BFSQueen(n)
{
var
ret
=
new
Queen(n);
var
BFS
=
new
BreadthFirstSearch(extendQueen,beamQueen,finishQueen);
BFS.apply(ret);
return
ret;
}
var
queen
=
new
BFSQueen(
8
);
var
count
=
0
;
queen.search();
</
script
>
</
pre
>
附:八皇后问题
八皇后问题是一个古老而著名的问题,是回溯算法的典型例题。该问题是十九世纪著名的数学家高斯1850年提出:在8X8格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。
高斯认为有76种方案。1854年在柏林的象棋杂志上不同的作者发表了40种不同的解,后来有人用图论的方法解出92种结果。