设计模式系列之十迭代器模式

迭代器模式:提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露其内部的表示。

  有这样的一个需求。一家集团公司在北京和上海分别有一家子公司,每家公司内部有自己的部门,且自己提供了打印部门的方法。其中一个子公司以数组来保存部门列表,另外一个以ArrayList来保存。现在需要打印所有的部门。

一、原始实现

1
2
3
4
5
6
7
8
9
10
11
12
//部门对象
public class Dept {
private String name;

public Dept(String name){
this.name= name;
}

public String getName(){
return this.name;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//北京分公司
public class BJBranch {
ArrayList<Dept> depts;

public BJBranch(){
depts = new ArrayList<Dept>();
//添加部门
depts.add(new Dept("北京-财务部"));
depts.add(new Dept("北京-研发部"));
depts.add(new Dept("北京-开发部"));
}

public ArrayList<Dept> getDepts(){
return depts;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//上海分公司
public class SHBranch {
Dept[] depts;

public SHBranch(){
depts = new Dept[3];
//添加部门
depts[0] = new Dept("上海-财务部");
depts[1] = new Dept("上海-研发部");
depts[2] = new Dept("上海-开发部");
}

public Dept[] getDepts(){
return depts;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class TestOld {
public static void main(String[] args) {
// TODO Auto-generated method stub
BJBranch BJ = new BJBranch();
ArrayList<Dept> BJDepts = BJ.getDepts();
SHBranch SH = new SHBranch();
Dept[] SHDepts = SH.getDepts();
//遍历两家子公司的所有部门
/** 由于类型不同,需要两次循环 **/
for(int i=0;i<BJDepts.size();i++){
System.out.println(BJDepts.get(i).getName());
}
for(int i=0;i<SHDepts.length;i++){
System.out.println(SHDepts[0].getName());
}
}
}

  从上面的代码中可以看出,由于两家子公司的实现方式不一样,造成循环遍历时自能使用对应的方式来遍历,造成相当大的不便。

二、使用迭代器模式来解决问题

1
2
3
4
5
//迭代器接口
public interface Iterator {
boolean hasNext();
Object next();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//北京分公司的部门迭代器
public class BJBranchIterator implements Iterator{
ArrayList<Dept> depts;
int position = 0;
public BJBranchIterator(ArrayList<Dept> depts){
this.depts = depts;
}
@Override
public boolean hasNext() {
if(position>=depts.size() || depts.get(position)==null){
return false;
}else{
return true;
}
}

@Override
public Object next() {
Dept dept = depts.get(position);
position = position + 1;
return dept;
}

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//改造后的北京分公司
public class BJBranch {
ArrayList<Dept> depts;

public BJBranch(){
depts = new ArrayList<Dept>();
//添加部门
depts.add(new Dept("北京-财务部"));
depts.add(new Dept("北京-研发部"));
depts.add(new Dept("北京-开发部"));
}

/*public ArrayList<Dept> getDepts(){
return depts;
}*/

//改造为返回Iterator对象,而不是ArrayList
public Iterator createrIterator(){
return new BJBranchIterator(depts);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//上海分公司的部门迭代器
public class SHBranchIterator implements Iterator{
Dept[] depts;
int position = 0;
public SHBranchIterator(Dept[] depts){
this.depts = depts;
}
@Override
public boolean hasNext() {
if(position>=depts.length|| depts[position]==null){
return false;
}else{
return true;
}
}

@Override
public Object next() {
Dept dept = depts[position];
position = position + 1;
return dept;
}

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//改造后的上海分公司
public class SHBranch {
Dept[] depts;

public SHBranch(){
depts = new Dept[3];
//添加部门
depts[0] = new Dept("上海-财务部");
depts[1] = new Dept("上海-研发部");
depts[2] = new Dept("上海-开发部");
}

/*public Dept[] getDepts(){
return depts;
}*/

//改造为返回Iterator对象,而不是数组
public Iterator createrIterator(){
return new SHBranchIterator(depts);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//使用迭代器模式后的代码实现
public class TestNew {

public static void main(String[] args) {
Iterator BJ = new BJBranch().createrIterator();
Iterator SH = new SHBranch().createrIterator();

printDeptName(BJ);
printDeptName(SH);
}

private static void printDeptName(Iterator iterator){
while(iterator.hasNext()){
Dept dept = (Dept) iterator.next();
System.out.println(dept.getName());
}
}
}

  从改造后的代码中可以看出,使用迭代器模式改造后,成功的屏蔽了北京与上海分公司遍历的差异。

  在Java中使用Iterator去遍历ArrayList应该大部分人都知道,实际上这个Iterator就是迭代器模式的实现。如果不使用迭代器模式,可以试想一下HasTableHashSetHashMap等类型,每一个都得按对应的方式去循环遍历,相当的不方便。

上一篇:设计模式系列之九模板方法模式
下一篇:设计模式系列之十一组合模式

留言

欢迎交流想法。留言会通过 GitHub Issues 保存,首次使用需要登录 GitHub。

坚持一年背英语单词的总结

  上图为前几天百词斩给我推送的本年度的年终总结。看完这个总结,感觉有必要把在这个过程中的所思所想整理分享出来。

01 为什么要学英语

  作为一个已经离开大学很多年的人,平时工作中也是地道的中文环境。年近三十,不禁要问,在这个年纪为什么还要学英文?

  在知乎上看到过这样一个问题:“知乎跟Quora的区别是怎样的?”,有一个答案是这样回答的:“如果把知乎比喻为一个小水潭,那么Quora就是一片汪洋大海”。在这里解释一下,知乎是国内的一个问答社区,用户基本为国内用户。Quora是国外的一个问答社区,他们的模式基本一样,唯一的区别是Quora的用户分布在世界各地,相互之间以英文沟通交流,在Quora上经常可以看到不同地区、不同语言、不同文化环境之间的语言交锋。

  我希望通过对英语的掌握,开启另外一扇窗。世界那么大,我想去看看

02 关于英语学习的感悟

  英语一向是我的弱项,上学时的英语成绩也一向是在及格线上挣扎。坚持完一年后,关于英语的学习有一些自己的感受。

  关于单词的拼写。曾经记不住几个单词,正确的拼写出完整的单词相当的困难。现在,发现拼写单词相当的简单。很多复杂的单词往往都是由简单的单词构成,只需要记住简单单词的拼写即可。另外,记住一个单词的正确发音后,通过正确的发音也能帮助单词的拼写。

  关于单词的发音。曾经见到生僻的单词基本不知道怎么念,怕念。现在,基本上能根据单词的拼写知道单词该如何念,虽然经常出错,但是已经有了念的底气。

  当单词量到一定程度,发现语感、语法变得尤为的重要。双十一打折在一堆技术书籍中夹杂了一本《英语语法实践指南》第九版,这是一本高中的复习用书,当把到货的书单发到朋友圈后,很多人感到很诧异……

  对于这一块,我觉得量变引起了质变。当认识的单词数量上来了之后,所有的一切在不知不觉间似乎变得容易了许多。

03 关于坚持

  今年一整年大概背了200多天的单词,每天15个。可能有人认为我是一个有毅力的人,可惜,错了。

  曾经看到过一个故事,说的是一个大学生在大学里生活相当有计划,每天都准时准点的按照计划执行,相当的有毅力。很多人都以为他生活中也是这样一个人。可是一到暑假,他的节奏完全被打乱了,跟在学校时的他判若两人。可能很多人在上学时都有过这样的经历,每个周末放假前就计划好周末要看什么书,要复习哪些功课。然后,就带了很厚一摞书回家。到了周一上学的路上,一顿悔恨,感觉这一堆书都白带了,因为根本就没有看!~

  从打卡日历中可以看到,没有圈的部分大多集中在非工作日。所以,周末魔咒对于我也是适用的。

  在这里,我不想引出周末魔咒这一个话题。恰恰相反,我想分享的是为什么在工作日的时候能一直坚持下去?

  **原因在于”习惯”**。从家到公司大概一个多小时,每天到地铁准备上地铁时戴上耳塞准备背单词,到下地铁时刚好背完。我觉得在工作日能一直不间断坚持下去的原因在于每天都有一个上班的一小时,而且养成了在这一小时里背单词的习惯。

  所以,我认为对于坚持干一件事毅力固然重要,而习惯的养成也起很大的作用。每天上地铁时固定的提醒,每天雷打不动的地铁一小时为坚持营造了良好的条件。

  最后,我还想说的是这一条件是可以创造的。比如下班回家的一小时可以用来在kindle中看书;比如中饭到下午上班之间的时间可以用来扫订阅的RSS;比如晚饭后到睡觉这一段时间可以用来看电影;开吃晚饭就同时触发电视的开关,这样一种习惯相比于毅力来得更加的轻松自然。

留言

欢迎交流想法。留言会通过 GitHub Issues 保存,首次使用需要登录 GitHub。

设计模式系列之九模板方法模式

模板方法模式:在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。

  有一个泡茶和冲咖啡的业务,他们都有差不多相似的流程。

一、不使用设计模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class Tea {
//泡茶流程
void process() {
boilWater();
steepTeaBag();
pourInCup();
addLemon();
}

public void boilWater() {
System.out.println("烧水");
}

public void steepTeaBag() {
System.out.println("泡茶");
}

public void pourInCup() {
System.out.println("倒进杯子");
}

public void addLemon() {
System.out.println("加柠檬");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class Coffee {
//冲咖啡流程
void process() {
boilWater();
brewCoffeeGrinds();
pourInCup();
addSugarAndMilk();
}

public void boilWater() {
System.out.println("烧水");
}

public void brewCoffeeGrinds() {
System.out.println("冲咖啡");
}

public void pourInCup() {
System.out.println("倒进杯子");
}

public void addSugarAndMilk() {
System.out.println("加牛奶和糖");
}
}

二、模板方法模式的使用

  下面以一个代码样例来说明模板方法模式的使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//定义模板方法
public abstract class TemplateMethod {
//冲咖啡和泡茶的模板,注意这里的final,意味着该方法不可修改,即模板中方法的先后顺序被固定
final void template() {
//烧水
boilWater();
//冲泡:用沸水"浸泡"茶叶/用沸水"冲泡"咖啡
brew();
//把泡好的饮料倒进杯子
pourInCup();
//加调料
addCondiments();
}
/**这两个方法交给子类去实现**/
//茶应该"浸泡",咖啡应该"冲泡"
abstract void brew();
//茶加柠檬,咖啡加牛奶和糖
abstract void addCondiments();

/**这两个方法属于公用方法**/
void boilWater() {
System.out.println("烧水");
}
void pourInCup() {
System.out.println("倒进杯子");
}
}
1
2
3
4
5
6
7
8
public class Tea extends TemplateMethod {
public void brew() {
System.out.println("浸泡");
}
public void addCondiments() {
System.out.println("加柠檬");
}
}
1
2
3
4
5
6
7
8
public class Coffee extends TemplateMethod {
public void brew() {
System.out.println("冲泡");
}
public void addCondiments() {
System.out.println("加糖和牛奶");
}
}
1
2
3
4
5
6
7
8
9
10
public class Test {
public static void main(String[] args) {
Tea tea = new Tea();
Coffee coffee = new Coffee();
//泡茶
tea.template();
//冲咖啡
coffee.template();
}
}

三、使用钩子(hook)来做一些其它操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
//定义模板方法
public abstract class TemplateMethodWithHook {
//冲咖啡和泡茶的模板,注意这里的final,意味着该方法不可修改,即模板中方法的先后顺序被固定
final void template() {
//烧水
boilWater();
//冲泡:用沸水"浸泡"茶叶/用沸水"冲泡"咖啡
brew();
//把泡好的饮料倒进杯子
pourInCup();
//加调料
if (customerWantsCondiments()) {
addCondiments();
}
}
/**这两个方法交给子类去实现**/
//茶应该"浸泡",咖啡应该"冲泡"
abstract void brew();
//茶加柠檬,咖啡加牛奶和糖
abstract void addCondiments();

/**这两个方法属于公用方法**/
void boilWater() {
System.out.println("烧水");
}
void pourInCup() {
System.out.println("倒进杯子");
}

//这里是个钩子,子类可决定是否需要加调料。也就是说子类可通过这个hook控制模板定义的逻辑
boolean customerWantsCondiments() {
return true;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class TeaWithHook extends TemplateMethod {

public void brew() {
System.out.println("浸泡");
}
public void addCondiments() {
System.out.println("加柠檬");
}

public boolean customerWantsCondiments() {
//不需要加柠檬
return false;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class CoffeeWithHook extends TemplateMethod {

public void brew() {
System.out.println("冲泡");
}
public void addCondiments() {
System.out.println("加糖和牛奶");
}

public boolean customerWantsCondiments() {
//需要加糖和牛奶
return true;
}
}
1
2
3
4
5
6
7
8
9
10
public class TestHook {
public static void main(String[] args) {
TeaWithHook teaHook = new TeaWithHook();
CoffeeWithHook coffeeHook = new CoffeeWithHook();
//泡茶,不加柠檬
teaHook.template();
//冲咖啡,加糖和牛奶
coffeeHook.template();
}
}

四、比较

不使用设计模式 使用模板方法模式
Coffee和Tea主导一切;它们控制了算法 TemplateMethod控制了一切,它拥有算法,并且保护这个算法
Coffee和Tea之间存在着重复的代码 通过TemplateMethod类实现了代码的复用
如果算法变了,需要修改Coffee和Tea类 新增算法或调整顺序只需要修改TemplateMethod类
由于类的组织不具有弹性,新加入第三种类型时需要完全重写一份 由于有模板存在,只需要实现差异化的逻辑即可
算法的知识和它的实现会分散在许多类中 TemplateMethod类专注在算法本身,而由子类提供完整的实现

上一篇:设计模式系列之八外观模式
下一篇:设计模式系列之十迭代器模式

留言

欢迎交流想法。留言会通过 GitHub Issues 保存,首次使用需要登录 GitHub。

设计模式系列之八外观模式

外观模式:提供了一个统一的接口,用来访问子系统的一群接口。外观定义了一个高层接口,让子系统更容易使用。

  下面通过一个图来说明外观模式的使用,此图来自于《Head First设计模式》一书。

  从上图中可以看到,通过一个统一的Facade来调用复杂的子系统的类。

  按我的理解,外观模式实际上就是抽取一些公共的业务逻辑封装起来,以统一的方法来调用。使代码结构更加清晰,易于维护,避免流水代码。

  举一个简单的例子,下班回家三件事:开门、开灯、开电视。上班出门要干三件事:关电视、关灯、关门。

流水线似的写法

1
2
3
4
5
6
7
8
9
10
public class Door {
//开门
public void open(){

}
//关门
public void close(){

}
}
1
2
3
4
5
6
7
8
9
10
public class Light {
//开灯
public void on(){

}
//关灯
public void off(){

}
}
1
2
3
4
5
6
7
8
9
10
public class TV {
//开电视
public void on(){

}
//关电视
public void off(){

}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class TestOld {
public static void main(String[] args) {
// TODO Auto-generated method stub
Light light = new Light();
Door door = new Door();
TV tv = new TV();
//回家的业务逻辑
door.open();
light.on();
tv.on();
//出门的业务逻辑
tv.off();
light.off();
door.close();
}
}

使用外观模式进行封装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//外观模式的实现
public class Facade {
TV tv;
Light light;
Door door;
public Facade(Door door,Light light,TV tv){
this.tv = tv;
this.light = light;
this.door = door;
}
//封装回家的业务逻辑
public void goHome(){
door.open();
light.on();
tv.on();
}
//封装出门的业务逻辑
public void goOut(){
tv.off();
light.off();
door.close();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class TestNew {
public static void main(String[] args) {
// TODO Auto-generated method stub
Light light = new Light();
Door door = new Door();
TV tv = new TV();
Facade facade = new Facade(door,light,tv);
/* 1.对于后期的运维人员,只需要知道这两个方法要干的事情即可,而不需要点开看里面的具体实现逻辑
* 2.如果是流水线的写法,则需要完完整整的把所有逻辑从头看到尾
* 3.这样的写法让代码结构更清晰
* 4.如果业务逻辑有变更,只需要修改对应的封装即可,而不需要去修改散落着各处的代码
* */
//回家逻辑
facade.goHome();
//出门逻辑
facade.goOut();
}
}

上一篇:设计模式系列之七适配器模式
下一篇:设计模式系列之九模板方法模式

留言

欢迎交流想法。留言会通过 GitHub Issues 保存,首次使用需要登录 GitHub。

文章推荐:《独家回应:陈天桥与加州理工的1亿美元》

  在微信公众号知识分子上看到了这篇文章,对文中的这一段印象深刻。

  在一次访谈中陈天桥被问到“为什么会产生支持脑科学研究的想法?

他给出的答案如下:

  十七年前我们创立第一个游戏式虚拟社区,五年后就已经有千万人同时在线,十七年的商业运营让我们积累了财富,积累了对世界的认知,也积累了迷思,比如为什么虚拟世界的种种虚拟成就或痛苦会如同真实世界一样深刻地影响每个参与者,甚至促使其做出种种匪夷所思的决定等等。对于这些迷思,过去这么多年我们试图从哲学、宗教、科学多个角度来解释,但思考的结果往往是带来更多的迷惑。

  这些问题既重大、严肃又充满无尽的想象空间。它吸引我们如同回到十七年前创业之初一样,从零开始研究各个可投入的社会领域,从零开始构画各种发展策略。所幸的是,我们比起十七年前拥有更多智慧而坦诚的朋友和师长。他们鼓励并支持我们以最大的勇气和最坚定的信念来继续探寻上述我们包括所有人都迷惑的问题。因此我们把这个理想定义为——全力支持人类在对大脑认知领域的无尽探索。

  脑科学作为人类现有知识体系里最为复杂而困难的课题之一,既充满魅力又让人望而却步。我们深知支持脑科学研究的决定将带来两个必然的挑战:巨大的付出和漫长的等待。对于前者,我们的回答是:全力以赴,对于后者,我们的回答是:永不止步。

  看完这个回答,给我也带来了无尽的迷思……另外,有益于全人类的理想以及面对困难的全力以赴与永不止步使人由.衷的敬佩。

留言

欢迎交流想法。留言会通过 GitHub Issues 保存,首次使用需要登录 GitHub。

设计模式系列之七适配器模式

适配器模式:将一个类的接口,转换成客户期望的另一个接口。适配器让原本接口不兼容的类可以合作无间。

  对于适配器模式,实际上就是一个转接口的概念。比如iphone7的耳塞必须通过转接才能适配,比如水货笔记本的插头必须通过一个转接口才能适配国内的插座等。下面通过代码来具体认识一下适配器模式。

原始接口

1
2
3
4
5
//原始接口
public interface OldInterface {
//原始方法
public void oldWay();
}
1
2
3
4
5
6
7
8
public class OldWay implements OldInterface{
@Override
public void oldWay() {
// TODO Auto-generated method stub
System.out.println("This is old way.");
}
}

新接口

1
2
3
4
5
//目前的新接口
public interface NewInterface {
//以新方法去干一件事情
public void newWay();
}

使用适配器适配原始接口

1
2
3
4
5
6
7
8
9
10
11
12
//通过适配器,使用新接口去调用老接口的方法
public class Adapter implements NewInterface{
OldInterface old;
public Adapter(OldInterface old){
this.old = old;
}
@Override
public void newWay() {
// TODO Auto-generated method stub
old.oldWay();
}
}

测试

1
2
3
4
5
6
7
8
9
public class Test {
public static void main(String[] args) {
OldWay old = new OldWay();
//通过适配,使用新接口去调用老接口
Adapter adapter = new Adapter(old);
//打印:This is old way.
adapter.newWay();
}
}

  通过上述代码,可以很清晰的理解适配器模式的核心思想。适配器模式大量存在与新旧代码兼容,以及如今的前后端分离中的数据接口对接部分。

上一篇:设计模式系列之六命令模式
下一篇:设计模式系列之八外观模式

留言

欢迎交流想法。留言会通过 GitHub Issues 保存,首次使用需要登录 GitHub。

读《巨流河》有感

  这本书是一位八十多岁的老太太所写的自传。从小学、初中、高中一直到大学的抗战时期到横渡海峡后的台湾时期。以一个历史见证人的角度,徐徐讲述了在那波澜壮阔的历史背景下,她所遇见的人与经历的事。通过本书,可以深切体会到作为一个解放后迁往台湾的异乡人那种复杂的感情。本书十分值得一读,尤其是台版

关于国家

  《巨流河》的开篇写道“献给──所有为国家献身的人。”,看了作者讲述的张大飞以及作者的父亲的一生,算是真正理解了这句话的含义。

  父母被日本人所杀的张大飞刚满十八岁即参军,为了保卫国家成为一名飞行员。抗战胜利前夕壮烈牺牲,二十多岁正值青春年华即走完自己的一生。对比如今的和平时代,跟他年龄相仿的人估计才刚刚大学毕业,懵懵懂懂的进入社会,一些人可能还在啃老。而张大飞为了保家卫国,此时已在蓝天中与日本人的战斗机战斗了无数个回合。而这一切,是真实发生在距今六七十年前的事。

  齐邦媛的父亲留学德国,回国后参加郭松龄的改革兵变失败而被迫流亡。而后,抗日战争爆发,积极与日本人做斗争。在这之前,只知道那时的国民党腐败横行,各种无能。看完了齐邦媛讲述父亲的事迹,发现不能一概而论。她的父亲为了整个国家民族,为了东北,为了沦陷区的父老乡亲而呕心沥血。在那个时代背景下的人们,同仇敌忾,把民族大义放在首位,不计个人得失共同抵御外敌。

  站在国家的角度,应当摒弃政党的偏见,像这些为了国家而牺牲的人不应被遗忘,值得永远铭记。

关于文学

  大学时,齐邦媛原本选择的是哲学,后来转为英语文学,师从朱光潜。书中有两个关于朱光潜的轶事。

  其一:“一日,一学生见朱光潜的院子里满地落叶,相帮老师打扫一下,朱老师立刻阻止他说,我等了好久才存了这么多层落叶,晚上在书房看书,可以听见雨落下来,风卷起的声音。这个记忆,比赞许多秋天境界的诗更为生动、深刻。”;

  其二:“朱老师读到”the fowls of heaven have wings,……chains tie us down by land and sea”(天上的鸟儿有翅膀,链紧我们的是大地和海洋),说中国古诗有相似的风云有鸟路,江汉限无梁”之句,此时竟然语带便咽,稍微停顿又继续念下去,念“if any chance to heave a sign”(若有人为我叹息,)“they pity me,and not my grief.”(他们怜悯的是我,不是我的悲苦。)老师取下了眼镜,眼泪流下双颊,突然把书合上,快步走出教室,留下满室愕然,却无人开口说话。也许,在那样一个艰困的时代,坦率表现感情是一件奢侈的事,对于仍然崇拜偶的大学三年级学生来说,这是一件难于评论的意外,甚至是感到荣幸的事,能看到文学名师至情的眼泪。”

  上面的轶事中,文学的美让一位老师在众目睽睽之下而流泪。有这样一位老师,沉浸在文学的海洋中应当有无穷的乐趣。

  书中讲到了英诗,知道了雪莱的《云雀之歌》、济慈的《夜莺颂》,通过作者的介绍而真切感受到了英诗的美妙。后来作者到美国进修,那种对知识的渴望,对文学的追求令人心生敬佩。

关于人生

  看完了《巨流河》,只觉得人生是何其的短暂。

  作者在八十岁高龄时回忆起少年往事恍若隔世。两岸开放探亲后,相隔半个世纪回到大陆。见到曾经的大学同学,印象中个个花枝招展,此时此刻都变成了老太太。大家都变了模样,各自经历了自己的人生而到了暮年。慢慢的,收到曾经大学同学一个个去逝的消息,半个世纪相隔两岸没有交集,只依稀记得同学时期的故事,给人一种快速来到这个世界上然后又快速离开的错觉。

  人生短暂,更显得每一天、每时每刻都更加珍贵。

留言

欢迎交流想法。留言会通过 GitHub Issues 保存,首次使用需要登录 GitHub。

jQuery validator addMethod 动态提示信息

关于jQuery validator addMethod自定义验证规则网络上大部分都是这样写的

1
2
3
4
5
6
7
8
9
10
$.validator.addMethod('PD_password', function (value, element) {
var len = value.length;
if(len<6){
return false;
}
if(len>15){
return false;
}
return true;
}, "密码必须在6-15位之间");

  现在,我想要更具体的提示信息,即能准确的提示到底是大于15位还是小于6位。在中文网页搜了一圈都没找到答案,最后在stackoverflow上找到了答案。想了想,感觉有必要把如何实现该需求分享一下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$.validator.addMethod('PD_password', function (value, element) {
var len = value.length;
if(len<6){
$(element).data('error-msg','长度不能少于6位');
return false;
}
if(len>15){
$(element).data('error-msg','长度不能大于15位');
return false;
}
return true;
}, function(params, element) {
return $(element).data('error-msg');
});

留言

欢迎交流想法。留言会通过 GitHub Issues 保存,首次使用需要登录 GitHub。

Gulp合并requirejs并MD5文件

项目目录结构

可以从https://github.com/muchstudy/GulpDemo 这里下载样例代码,求Star!

说明:

  1. js的的依赖关系为main.build.js–>three.js–>two.js–>one.js
  2. main-build.js为构建的入口
  3. 从github上拿下来的项目是可以直接运行的。可执行gulp cleangulp观察结果。

文件合并

  1. 在requirejs目录下运行npm init,初始化package.json
  2. 运行npm install gulp --save-devnpm install gulp-requirejs-optimize --save-dev、``npm install gulp-rename –save-dev`

文件合并代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var gulp            = require('gulp');
var reqOptimize = require('gulp-requirejs-optimize'); //- requireJs文件合并所需模块,选择该模块的原因为相对于其它模块活跃度较高
var rename = require("gulp-rename"); //- 文件重命名

gulp.task("optimize",function () {
gulp.src("app/main-build.js")
.pipe(reqOptimize({
optimize:"none", //- none为不压缩资源
//findNestedDependencies: true, //- 解析嵌套中的require
paths:{
"PDAppDir":"", //- 所有文件的路径都相对于main-build.js,所以这里为空即可
"jquery":"empty:"
}
}))
.pipe(rename("main.min.js"))
.pipe(gulp.dest('app')); //- 映射文件输出目录
});

说明:

  1. 运行gulp optimize即可把main-build.js所依赖的文件合并到main.min.js中。
  2. 在代码中有一个findNestedDependencies参数,意思是代码中如果是使用require方式而不是define方式依赖文件,默认不解析
  3. :empty代表忽略该文件
  4. reqOptimize的详细参数见requirejs的官方文档

文件md5

运行:npm install gulp-rev --save-dev

MD5

文件MD5代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
gulp.task("md5",function (cb) {
gulp.src("app/main-build.js")
.pipe(reqOptimize({
optimize:"none", //- none为不压缩资源
//findNestedDependencies: true, //- 解析嵌套中的require
paths:{
"PDAppDir":"", //- 所有文件的路径都相对于main-build.js,所以这里为空即可
"jquery":"empty:"
}
}))
.pipe(rev()) //- 文件名加MD5后缀
.pipe(gulp.dest("app")) //- 生成MD5后的文件
.pipe(rev.manifest({merge:true})) //- 生成一个rev-manifest.json
.pipe(gulp.dest('')) //- 映射文件输出目录
.on('end', cb);
});

运行gulp md5,此时就会生成类似于这样的文件main-min-c144065c18.js。现在问题就来了,文件名变了,就需要动态替换首页上的引用文件名。

路径替换

运行:mpn install gulp-rev-collector --save-devnpm install through2 --save-dev
代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
function modify(modifier) {
return through2.obj(function(file, encoding, done) {
var content = modifier(String(file.contents));
file.contents = new Buffer(content);
this.push(file);
done();
});
}
function replaceSuffix(data) {
return data.replace(/\.js/gmi, "");
}
//去掉.js后缀,因为requirejs的引用一般都不带后缀
gulp.task("replaceSuffix",function (cb) {
gulp.src(['rev-manifest.json'])
.pipe(modify(replaceSuffix)) //- 去掉.js后缀
.pipe(gulp.dest(''))
.on('end', cb);
});

gulp.task("replaceHomePath",function (cb) {
gulp.src(['rev-manifest.json', 'index-build.html'])
.pipe(revCollector()) //- 替换为MD5后的文件名
.pipe(rename("index.html"))
.pipe(gulp.dest(''))
.on('end', cb);
});

依次运行gulp replaceSuffixgulp replaceHomePath即可生成可运行的首页。相对于原始首页,依赖的文件路径为md5后的路径。

整合

从上面可以看到,这一套下来需要运行一系列的task,而且这些task的运行还有先后顺序。下面尝试使用一个task来完成整个任务。由于gulp的task都是异步运行的,所以需要使用到run-sequence
安装npm install gulp-clean --save-devnpm install run-sequence --save-dev

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//删除掉上一次构建时创建的资源
gulp.task("clean",function () {
return gulp.src([
'rev-manifest.json',
'**/*-build-*.js',
'index.html'
]).pipe(clean());
});

//构建总入口
gulp.task('default', function(callback) {
runSequence(
"clean", //- 上一次构建的结果清空
"md5", //- 文件合并与md5
"replaceSuffix", //- 替换.js后缀
"replaceHomePath", //- 首页路径替换为md5后的路径
callback);
});

说明:

  1. 可以看到上面的task中最后都有一句.on('end', cb)这个是为了解决task任务异步运行的问题
  2. 此时,只需要运行gulp即可完成所有的构建任务。

留言

欢迎交流想法。留言会通过 GitHub Issues 保存,首次使用需要登录 GitHub。

设计模式系列之六命令模式

命令模式:将“请求”封装成对象,以便使用不同的请求、队列或者日志来参数化其他对象。命令模式也可支持可撤销的操作。

  有一个这样的业务逻辑,在客厅的进门处有一个开关面板,该面板上有两个开关,第一个开关是打开客厅的灯,第二个开关是打开客厅的电视。下面就来具体实现一下。

首先实现电视与灯:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Light {
String location = "";

public Light(String location) {
this.location = location;
}

public void on() {
System.out.println("打开"+location+"的灯");
}

public void off() {
System.out.println("关闭"+location+"的灯");
}
}
1
2
3
4
5
6
7
8
9
10
public class Television {

public void open() {
System.out.println("打开电视");
}

public void close() {
System.out.println("关闭电视");
}
}

硬编码的方式实现开关面板功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//开关控制器
public class SwitchControlOld {
Light light = new Light("客厅");
Television television = new Television();

public void onButtonWasPressed(int solt) {
//打开第一个开关
if(solt==0){
light.on();
}else if(solt==1){
television.open();
}
}

public void offButtonWasPressed(int solt) {
//关闭第一个开关
if(solt==0){
light.off();
}else if(solt==1){
television.close();
}
}
}

  从上面可以看到,实现得十分的不友好,如果开关面板上再加个开关按钮就必须得再加一组判断。而且,开关控制面板跟具体的电器开关逻辑强耦合。

使用命令模式重新实现该逻辑

1
2
3
4
5
6
7
//可以理解为这里为转接口的定义
public interface Command {
//运行
public void execute();
//回退
public void undo();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//开灯命令
public class LightOnCommand implements Command {
Light light;

public LightOnCommand(Light light) {
this.light = light;
}

public void execute() {
light.on();
}

@Override
public void undo() {
// TODO Auto-generated method stub
light.off();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//关灯命令
public class LightOffCommand implements Command {
Light light;

public LightOffCommand(Light light) {
this.light = light;
}

public void execute() {
light.off();
}

@Override
public void undo() {
// TODO Auto-generated method stub
light.on();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//打开电视
public class TelevisionOnCommand implements Command {
Television television;

public TelevisionOnCommand(Television television) {
this.television = television;
}
@Override
public void execute() {
television.open();
}
@Override
public void undo() {
television.close();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//关闭电视
public class TelevisionOffCommand implements Command {
Television television;

public TelevisionOffCommand(Television television) {
this.television = television;
}
@Override
public void execute() {
television.close();
}
@Override
public void undo() {
television.open();
}
}

使用命令模式实现的开关控制器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//开关控制器
public class SwitchControl {
Command[] onCommands = new Command[2];
Command[] offCommands = new Command[2];
//把开关与具体要干的事情关联起来
public void setCommand(int solt,Command onCommand,Command offCommand) {
onCommands[solt] = onCommand;
offCommands[solt] = offCommand;
}
//按下开关
public void onButtonWasPressed(int solt) {
onCommands[solt].execute();
}
//回退
public void offButtonWasPressed(int solt) {
onCommands[solt].undo();
}
}

测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Test {
public static void main(String[] args) {
//有两个开关的开关控制器
SwitchControl control = new SwitchControl();
//把电灯逻辑绑定到第一个开关上
Light light = new Light("客厅");
control.setCommand(0,new LightOnCommand(light),new LightOffCommand(light));
control.onButtonWasPressed(0); //- 打印:打开客厅的灯

//把电视逻辑绑定到第二个开关上
Television television = new Television();
control.setCommand(1,new TelevisionOnCommand(television),new TelevisionOffCommand(television));
control.onButtonWasPressed(1); //- 打印:打开电视
}
}

  从上面的代码中可以看到,对于开关控制面板来讲,它只需要按下开关并执行命令的execute方法,不需要知道每个电器的具体实现。

  通过使用命令模式对代码的改造,让开关控制器跟具体电器的开/关彻底的解耦。如果想要在控制器上增加新的按钮,可以很方便的增加上去。使得代码更加清晰,易于扩展与维护。

  从命令模式的特性中可以看出,如果此时的需求变为按下一个开关需要同时打开灯与电视,那么只需要一个command数组即可。把LightOnCommandTelevisionOnCommand放入其中,遍历调用execute

  另外,在队列请求中大量的用到了命令模式。一个个的请求依次而来,接收端不需要知道每个请求的具体逻辑,只需要依次调用请求的execute方法即可完成请求的处理。

上一篇:设计模式系列之五工厂模式
下一篇:设计模式系列之七适配器模式

留言

欢迎交流想法。留言会通过 GitHub Issues 保存,首次使用需要登录 GitHub。