设计模式系列之五工厂模式

Java实现

简单工厂

  简单工厂应该是最简单的工厂方法了,也是最常见的。下面,通过代码就知道为什么这么说了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class SimplePizzaFactory {

public Pizza createPizza(String type) {
Pizza pizza = null;

if (type.equals("cheese")) {
pizza = new CheesePizza();
} else if (type.equals("pepperoni")) {
pizza = new PepperoniPizza();
} else if (type.equals("clam")) {
pizza = new ClamPizza();
} else if (type.equals("veggie")) {
pizza = new VeggiePizza();
}
return pizza;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class PizzaStore {
SimplePizzaFactory factory;

public PizzaStore(SimplePizzaFactory factory) {
this.factory = factory;
}

public Pizza orderPizza(String type) {
Pizza pizza;
//通过简单工厂创建不同类型的披萨
pizza = factory.createPizza(type);

pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();

return pizza;
}
}

  通过简单工厂方法,有效的把new Object()解耦了出来。在实际的业务中,不用再去关心如何去创建对象。特别是对象的创建与初始化很复杂,且有很多地方都需要用到该对象时,就不用每个地方都去显示的new一下,要修改初始化条件时,也只需要修改工厂方法即可。

工厂方法

定义:

工厂方法模式:定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法让类把实例化推迟到子类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public abstract class PizzaStore {

//具体如何创建披萨交由子类去实现(注意对比这里跟简单工厂的区别)
abstract Pizza createPizza(String item);

public Pizza orderPizza(String type) {
Pizza pizza = createPizza(type);
System.out.println("--- Making a " + pizza.getName() + " ---");
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//纽约风格的披萨
public class NYPizzaStore extends PizzaStore {

Pizza createPizza(String item) {
if (item.equals("cheese")) {
return new NYStyleCheesePizza();
} else if (item.equals("veggie")) {
return new NYStyleVeggiePizza();
} else if (item.equals("clam")) {
return new NYStyleClamPizza();
} else if (item.equals("pepperoni")) {
return new NYStylePepperoniPizza();
} else return null;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//芝加哥风格的披萨
public class ChicagoPizzaStore extends PizzaStore {

Pizza createPizza(String item) {
if (item.equals("cheese")) {
return new ChicagoStyleCheesePizza();
} else if (item.equals("veggie")) {
return new ChicagoStyleVeggiePizza();
} else if (item.equals("clam")) {
return new ChicagoStyleClamPizza();
} else if (item.equals("pepperoni")) {
return new ChicagoStylePepperoniPizza();
} else return null;
}
}

抽象工厂

  在所有的工厂模式中,还有一种叫抽象工厂模式。不过一直无法理解抽象工厂模式的具体使用场景,所以这部分留着今后对抽象工厂模式理解深刻了再来总结吧。

Javascript实现

简单工厂

  举一个如下的业务场景:需要开发一个表单生成器,需要做到表单元素的自动生成。那么,可以采用简单工厂来创建表单元素对象。

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
35
36
37
38
39
40
41
(function () {
function SampleFormFactory() {

}
SampleFormFactory.fn = SampleFormFactory.prototype;
SampleFormFactory.fn.getFormElement = function (type) {
switch (type){
case "Input":
return new Input();
case "Select":
return new Select();
case "Textarea":
return new Textarea();
case "Radio":
return new Radio();
case "Checkbox":
return new Checkbox();
}
};

/*****************标准表单对象*****************/
function Input() {

}
function Select() {

}
function Textarea() {

}
function Radio() {

}
function Checkbox() {

}

//使用简单工厂创建表单元素
var factory = new SampleFormFactory();
var element = factory.getFormElement("Input");
})();

  至于在javascript中工厂方法模式的使用,一时想不到使用场景,感觉有点为了用设计模式而设计模式,所以这里就不再举例了。

上一篇:设计模式系列之四装饰者模式
下一篇:设计模式系列之六命令模式

留言

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

设计模式系列之四装饰者模式

装饰者模式的定义:

装饰者模式:动态的将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。

Java实现

  借用《Head First设计模式》 中的咖啡店卖咖啡的样例。便于说明,这里简洁一下。假如我要写一个咖啡点单计价的软件。业务逻辑是这样的,一杯咖啡10元,如果加糖另外加1元,如果加奶另外加两元。实际来讲,采用硬编码的方式我可以写三个类来实现该需求:1.咖啡单买;2.咖啡+糖;3.咖啡+奶。然后就完事儿,任务完成。但是如果哪一天,老板打算卖即加糖又加奶的咖啡的话,就得再增加一个类来实现该类型咖啡的计价。这还好说,假如我有多种类型的咖啡呢?而且每种类型的咖啡都可以加奶或者糖,这样排列组合下去,我估计要写吐血…..

下面,我们就使用装饰者模式来解决该问题。

样例

首先来一个咖啡的抽象类

1
2
3
4
5
6
7
8
9
10
 public abstract class AbsCoffee {
//咖啡描述
String description = "Unknown Type";

public String getDescription() {
return description;
}
//咖啡加个
public abstract double cost();
}

接下来两种咖啡的实现

1
2
3
4
5
6
7
8
9
10
 public class MochaCoffee extends AbsCoffee{
public MochaCoffee() {
description = "摩卡咖啡";
}
@Override
public double cost() {
//摩卡咖啡10元一杯
return 10;
}
}

1
2
3
4
5
6
7
8
9
10
public class LatteCoffee extends AbsCoffee{
public LatteCoffee() {
description = "拿铁咖啡";
}
@Override
public double cost() {
//拿铁咖啡15元一杯
return 15;
}
}

装饰器

1
2
3
4
5
//装饰器
public abstract class Decorator extends AbsCoffee {
//强制装饰者增加自己的描述信息
public abstract String getDescription();
}

两个装饰类,这里分别为咖啡的调调:糖、牛奶

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Sugar extends Decorator{
AbsCoffee coffee;
public Sugar(AbsCoffee coffee){
this.coffee = coffee;
}

@Override
public String getDescription() {
return coffee.getDescription()+"+糖";
}

@Override
public double cost() {
// TODO Auto-generated method stub
return coffee.cost()+2;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Milk extends Decorator{
AbsCoffee coffee;
public Milk(AbsCoffee coffee){
this.coffee = coffee;
}

@Override
public String getDescription() {
return coffee.getDescription()+"+牛奶";
}

@Override
public double cost() {
// TODO Auto-generated method stub
return coffee.cost()+1;
}
}

测试结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Test {
public static void main(String[] args) {
// TODO Auto-generated method stub
//摩卡咖啡
AbsCoffee coffee = new MochaCoffee();
//打印:摩卡咖啡=10.0
System.out.println(coffee.getDescription()+"="+coffee.cost());
//摩卡+糖+牛奶
coffee = new Milk(new Sugar(coffee));
//打印:摩卡咖啡+糖+牛奶=13.0
System.out.println(coffee.getDescription()+"="+coffee.cost());
/*同理,可以把不同类型的咖啡与不同类型的调料任意组合,解决了硬编码时一种组合就需要对应写一套逻辑的尴尬*/
}
}

java.io中的装饰者模式

这里的代码样例来自与《Head First设计模式》一书,演示了FileInputStream中装饰者模式的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//实现字符的大小写转换
public class LowerCaseInputStream extends FilterInputStream {

public LowerCaseInputStream(InputStream in) {
super(in);
}

public int read() throws IOException {
int c = in.read();
return (c == -1 ? c : Character.toLowerCase((char)c));
}

public int read(byte[] b, int offset, int len) throws IOException {
int result = in.read(b, offset, len);
for (int i = offset; i < offset+result; i++) {
b[i] = (byte)Character.toLowerCase((char)b[i]);
}
return result;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class InputTest {
public static void main(String[] args) throws IOException {
int c;

try {
//层层装饰,最终输入大写字符
InputStream in =
new LowerCaseInputStream(
new BufferedInputStream(
new FileInputStream("test.txt")));

while((c = in.read()) >= 0) {
System.out.print((char)c);
}

in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}

Javascript实现

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
35
36
37
38
39
40
41
42
43
(function () {
function MochaCoffee() {
this.description = "摩卡咖啡"
}
MochaCoffee.prototype.cost = function () {
return 10;
};
MochaCoffee.prototype.getDescription = function () {
return this.description;
};

//调料:糖
function Sugar(coffee) {
this.coffee = coffee;
this.description = "糖";
}
Sugar.prototype.cost = function () {
return this.coffee.cost()+1;
};
Sugar.prototype.getDescription = function () {
return this.coffee.getDescription()+"+"+this.description
};
//调料:牛奶
function Milk(coffee) {
this.coffee = coffee;
this.description = "牛奶";
}
Milk.prototype.cost = function () {
return this.coffee.cost()+2;
};
Milk.prototype.getDescription = function () {
return this.coffee.getDescription()+"+"+this.description
};

/*************************测试代码*************************/
var coffee = new MochaCoffee();
//这里将打印:摩卡咖啡=10
console.log(coffee.getDescription()+"="+coffee.cost());
coffee = new Milk(new Sugar(coffee));
//这里将打印:摩卡咖啡+糖+牛奶=13
console.log(coffee.getDescription()+"="+coffee.cost());
})();

上一篇:设计模式系列之三观察者模式
下一篇:设计模式系列之五工厂模式

留言

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

设计模式系列之三观察者模式

先来看看观察者模式的定义

观察者模式:定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。

Java实现

下面以消息中心的需求作为例子。有一个消息中心,当消息中心收到新消息时,动态通知所有的收听者。

消息中心部分

1
2
3
4
5
6
7
8
9
public interface Subject {
//- 注册收听者
public void registerObserver(Observer o);
//- 移除收听者
public void removeObserver(Observer o);
//- 通知收听者
public void notifyObservers();
}

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
35
36
37
38
39
40
public class MessageCenter implements Subject {
private ArrayList<Observer> observers;
private String message;

public MessageCenter() {
//- 保存所有的收听者
observers = new ArrayList<Observer>();
}

public void registerObserver(Observer o) {
observers.add(o);
}

public void removeObserver(Observer o) {
int i = observers.indexOf(o);
if (i >= 0) {
observers.remove(i);
}
}
/**
* 通知所有人
*/
public void notifyObservers() {
for (Observer observer : observers) {
observer.printMessage(message);
}
}
/**
* 收到新消息触发
*/
public void newMessage() {
notifyObservers();
}

//模拟收到消息
public void setMessage(String message) {
this.message = message;
newMessage();
}
}

收听者部分

1
2
3
4
public interface Observer {
public void printMessage(String message);
}

1
2
3
4
5
6
7
8
9
10
11
12
13
public class UserOne implements Observer {
private Subject messageCenter;

public UserOne(Subject messageCenter) {
this.messageCenter = messageCenter;
this.messageCenter.registerObserver(this);
}

public void printMessage(String message) {
System.out.println("UserOne:"+message);
}
}

1
2
3
4
5
6
7
8
9
10
11
12
public class UserTwo implements Observer {
private Subject messageCenter;

public UserTwo(Subject messageCenter) {
this.messageCenter = messageCenter;
this.messageCenter.registerObserver(this);
}

public void printMessage(String message) {
System.out.println("UserTwo:"+message);
}
}

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Test {
public static void main(String[] args) {
MessageCenter messageCenter = new MessageCenter();
UserOne one = new UserOne(messageCenter);
UserTwo two = new UserTwo(messageCenter);

//这里One和Two都能收到消息
messageCenter.setMessage("First Message!");
//移除用户One
messageCenter.removeObserver(one);
//这里就只有UserTwo能收到消息了
messageCenter.setMessage("Second Message!");
}
}

JavaScript实现

在javascript中,最常见的例子就是jquery中的自定义事件,当事件触发时,会通知所有的事件收听者。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Demo</title>
</head>
<body>
<button id="ButtonID">Button</button>
<script src="../jquery/jquery-2.1.3.min.js"></script>
<script>
var $btn = $("#ButtonID");
$btn.bind("CustomEvent",function (event,param) {
console.log("我是收听者One");
console.log(param);
});
$btn.bind("CustomEvent",function (event,param) {
console.log("我是收听者Two");
console.log(param);
});
//执行此段代码,触发事件。在控制台上可以看到One和Two同时被打印出来了
$btn.trigger("CustomEvent","给收听者的通知");
</script>
</body>
</html>

样例

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
(function () {
function MessageCenter() {
this.observers = {};
this.newMessage = "";
}
MessageCenter.fn = MessageCenter.prototype;
//注册收听者
MessageCenter.fn.registerObserver = function (id,observer) {
this.observers[id] = observer;
};
//移除收听者
MessageCenter.fn.removeObserver = function (id) {
delete this.observers[id];
};
//通知所有收听者
MessageCenter.fn.notifyObservers = function () {
for(var key in this.observers){
this.observers[key].apply(this,[this.newMessage]);
}
};
//模拟收到新消息
MessageCenter.fn.setMessage = function (msg) {
this.newMessage = msg;
this.notifyObservers();
};

/**
* 收听者UserOne
* @param subject
* @constructor
*/
function UserOne(subject) {
subject.registerObserver("UserOne",function (msg) {
console.log("我是UserOne,我收到的消息是:"+msg);
})
}

/**
* 收听者UserTwo
* @param subject
* @constructor
*/
function UserTwo(subject) {
subject.registerObserver("UserTwo",function (msg) {
console.log("我是UserTwo,我收到的消息是:"+msg);
})
}

//测试代码
var msgCenter = new MessageCenter();
new UserOne(msgCenter);
new UserTwo(msgCenter);
//这里One和Two都能收到消息
msgCenter.setMessage("First Message!");
//移除UserOne
msgCenter.removeObserver("UserOne");
//这里就只有UserTwo能收到消息了
msgCenter.setMessage("Second Message!");
})();

上一篇:设计模式系列之二策略模式
下一篇:设计模式系列之四装饰者模式

留言

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

设计模式系列之二策略模式

Java实现

有如下需求,明天周一,需要去公司上班,目前从家里到公司有两种策略,一种是公交,一种是地铁。下面就来实现一下

定义具体策略

1
2
3
4
public interface IStrategy {
//对策略的描述
public void description();
}
1
2
3
4
5
6
7
8
 public class Bus implements IStrategy{
@Override
public void description() {
// TODO Auto-generated method stub
System.out.println("乘公交去上班");
}
}

1
2
3
4
5
6
7
 public class Subway implements IStrategy {
@Override
public void description() {
// TODO Auto-generated method stub
System.out.println("乘地铁去上班");
}
}

最简单的策略调用

1
2
3
4
5
6
7
8
9
public class GoToWork {
public void myStrategy(String strategy){
if("bus".equals(strategy)){
new Bus().description();
}else if("subway".endsWith(strategy)){
new Subway().description();
}
}
}
1
2
3
4
5
6
7
public class Test {
public static void main(String[] args) {
GoToWork gotowork = new GoToWork();
gotowork.myStrategy("bus");
gotowork.myStrategy("subway");
}
}

从上面的代码中可以看出,通过if else的使用实现了简单的策略模式。但是,假如有一天我买车了,可以自己开车去上班了。这个时候就需要对GoToWork类做修改,需要再加一个else if判断来实现需求。这样就造成了代码的紧耦合。那么,有没有办法解耦,不但能让我开车,甚至开飞机上班都不用修改GoToWork类呢?

改造

1
2
3
4
5
6
7
8
9
10
11
12
public class GoToWork {
//保存策略的引用
private IStrategy strategy;
//可动态设置策略
public void setStrategy(IStrategy strategy){
this.strategy = strategy;
}
public void myStrategy(){
//这里实现了对策略的动态调用
this.strategy.description();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Test {
public static void main(String[] args) {
GoToWork work = new GoToWork();
//乘公交上班
work.setStrategy(new Bus());
work.myStrategy();
//乘地铁上班
work.setStrategy(new Subway());
work.myStrategy();
//开车上班
//work.setStrategy(new Car());
//work.myStrategy();
/*.....还可增加任意策略.....*/
}
}

可以看到,对GoToWork类做完改造后,即满足了我们的需求。

JavaScript实现

之前写表格组件,有一个表格行可编辑的需求。开发人员在使用表格组件时可选择单元格的编辑类型:文本框、下拉框、单选按钮等。并且,为了灵活性,还需要能够让开发人员自定义类型,比如在表格的单元格中日期选择、弹出框选择等。针对该需求,策略模式派上了用场。

实现

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
(function(W){
var singleton = null;
function Grid(){
this.config = {
pagination:true
};
init(this);
}
//表格初始化
var init = function (G) {
var $table = $("<table></table>");
G.$Container = $("#"+this.placeAt).empty().append($table);
};
//设置表格配置
Grid.prototype.setConfig = function (config) {
this.config = $.extend(this.config,config);
};
//添加行
Grid.prototype.addRow = function () {
var $tr = $("<tr></tr>");
var len = this.config.layout.length;
for(var i=0;i<len;i++){
var $td = $("<td></td>");
new Cell($td,this.config.layout[i]);
$tr.append($td);
}
this.$Container.append($tr);
};
//删除行
Grid.prototype.deleteRow = function () {

};
//单例模式使用
W.Grid.getInstance = function () {
if(singleton===null){
singleton = new Grid();
}
return singleton;
};

//单元格对象
function Cell($container,config) {
this.$Container = $container;
this.config = config;
this.init();
}
Cell.fn = Cell.prototype;

Cell.fn.init = function(){
if(this.config.editor){
this._editor = this.edit();
}else{
this._editor = this.read();
}
};
//单元格只读
Cell.fn.read = function(){
//使用默认策略渲染单元格
return new Forms["Span"](this.$Container,"");
};
//单元格自定义与已有表单对象渲染
Cell.fn.edit = function(){
var formObj = null;
var type = this.config.editor.type;
if(typeof(type)=="function"){
//开发人员自定义的策略
formObj = new type(this.$Container,"");
}else if(typeof(type)=="string"){
//首字母大写转换
type = type.substring(0,1).toUpperCase() + type.substring(1,type.length);
//动态选择已有策略
formObj = new Forms[type](this.$Container,"");
}
return formObj;
};


/*******************************策略定义*****************************/
var Forms = {};

/**
* 默认 只读
* @param $container
* @param value
* @constructor
*/
Forms.Span = function($container,value){
this.parent = $container;
this._value = value;
this.$Dom = '';
this._init();
};
Forms.Span.prototype._init = function(){
this.$Dom = $('<span></span>').append(this._value);
this.parent.append(this.$Dom);
};
Forms.Span.prototype.setValue = function(value){
this.$Dom.html(value);
};
Forms.Span.prototype.getValue = function(){
return this._value;
};

/**
* 文本框
* @param $container
* @param value
* @constructor
*/
Forms.Text = function($container,value){
this.parent = $container;
this._value = value;
this.$Dom = null;
this._init();
};
Forms.Text.prototype._init = function(){
this.$Dom = $('<input type="text" style="width:100%">');
this.parent.append(this.$Dom);
this.setValue(this._value);
};
Forms.Text.prototype.setValue = function(value){
this.$Dom.val(value);
};
Forms.Text.prototype.getValue = function(){
return this.$Dom.val();
};
/*自定义对象只需要同样实现set和get方法即可*/

})(window);
1
2
3
4
5
6
7
8
9
10
11
12
13
//使用策略模式动态渲染表格行中的单元格
var grid = Grid.getInstance();
grid.setConfig({
placeAt:"GridContainer",
layout:[
{name:"姓名",field:"Name",sort:true,locked:true,editor:{type:"Text"}},
{name:"性别",field:"Sex",sort:true,editor:{type:"radio",options:[{name:"男",value:"1"},{name:"女",value:"0"}]}},
{name:"电话",field:"Phone",editor:{type:"Text"}},
{name:"邮件",field:"Email"},
{name:"地址",field:"Address",format:function(obj){}}
]
});
grid.addRow();

上一篇:设计模式系列之一单例模式
下一篇:设计模式系列之三观察者模式

留言

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

设计模式系列之一单例模式

Java实现

不考虑线程安全的写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Singleton {
private static Singleton uniqueInstance;

private Singleton() {}

public static Singleton getInstance() {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
return uniqueInstance;
}

// other useful methods here
public String getDescription() {
return "I'm a classic Singleton!";
}
}

这种写法的问题在于非线程安全,当两个线程同时进入if判断,且新对象还未创建时,就会产生两个不同的对象。可以通过synchronized来解决此问题

方式一

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Singleton {
private static Singleton uniqueInstance;

// other useful instance variables here

private Singleton() {}

public static synchronized Singleton getInstance() {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
return uniqueInstance;
}

// other useful methods here
public String getDescription() {
return "I'm a thread safe Singleton!";
}
}

此种写法解决了线程安全问题,但是,由于多线程状态时,需要排队获取单例对象,所以存在性能问题。

方式二

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Singleton {
private static Singleton uniqueInstance = new Singleton();

private Singleton() {}

public static Singleton getInstance() {
return uniqueInstance;
}

// other useful methods here
public String getDescription() {
return "I'm a statically initialized Singleton!";
}
}

此种方式线程安全且不会出现多线程状态下的性能问题。但是,问题在于即使代码中不会用到该对象,在jvm中也会创建一个,还是不够完美。

方式三

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//
// Danger! This implementation of Singleton not
// guaranteed to work prior to Java 5
//

public class Singleton {
private volatile static Singleton uniqueInstance;

private Singleton() {}

public static Singleton getInstance() {
if (uniqueInstance == null) {
synchronized (Singleton.class) {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
}

此种方式完美解决了多线程问题且能根据需要动态创建单例对象。唯一缺点为volatile关键字在jdk1.5以上才支持。

两篇介绍volatile关键字的资料:

  1. http://www.infoq.com/cn/articles/ftf-java-volatile
  2. http://www.ibm.com/developerworks/cn/java/j-jtp06197.html

JavaScript实现

方式一

1
2
3
4
5
6
7
8
9
10
11
12
function User(){
this.name = "YiYing";
}

var Singleton = function(){
var user = new User();
this.getInstance = function(){
return user
}
}

var user = Singleton.getInstance();

此种方式跟上面Java实现的方式二很像,不管用不用,一上来直接创建一个对象,如果代码中不使用就会使得创建的对象冗余。

方式二

1
2
3
4
5
6
7
8
9
var Singleton = function(){
var user = null;
this.getInstance = function(){
if(user === null){
user = new User();
}
return user;
}
}

此种方式实现了动态创建单例对象,相对与上一种更优。

实际应用

假如一个表单上只能允许一个表格存在,现在需要提供一个表格组件给开发人员使用。此时,不希望开发人员创建多个表格对象,可以考虑这样实现

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
//表格组件实现部分
(function(W){
var singleton = null;
function Grid(){
this.pagination = true;
init();
}
var init = function () {

};
Grid.prototype.addRow = function (row) {

};
Grid.prototype.deleteRow = function () {

};

W.Grid.getInstance = function () {
if(singleton===null){
singleton = new Grid();
}
return singleton;
}
})(window);
//使用部分
var grid = Grid.getInstance();
grid.addRow();

下一篇:设计模式系列之二策略模式

留言

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

Gulp实践

环境准备

NodeJS安装

在官方网址下载下载安装包即可:https://nodejs.org。
windows环境下只需要step by step,完成后在CMD中输入node -v 查看是否安装成功

npm安装

https://github.com/nodejs-tw/nodejs-wiki-book/blob/master/zh-tw/node_npm.rst

Gulp安装

入门指南:http://www.gulpjs.com.cn/docs/getting-started/

使用Gulp构建项目

说明

在目前的项目中使用Gulp来对前端资源进行构建,在过程当中牵涉到如下几点:

  • 文件删除
  • RequireJS文件合并。API,需要过一遍原文,因为很多中文翻译内容翻译得不完全
  • 文件MD5
  • LESS文件编译
  • 文件内容中的字符替换。去掉requirejs中config.js里的.js后缀
  • 文件压缩
  • 文件重命名
  • Gulp任务顺序处理,解决task异步执行问题

代码样例

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
var gulp            = require('gulp');
var rename = require("gulp-rename"); //- 文件重命名
var concat = require('gulp-concat'); //- 多个文件合并为一个
var rev = require('gulp-rev'); //- 对文件名加MD5后缀
var revCollector = require('gulp-rev-collector'); //- 路径替换
var reqOptimize = require('gulp-requirejs-optimize'); //- requireJs文件合并
var through2 = require('through2'); //- 文件内容操作
var clean = require('gulp-clean'); //- 删除文件
var runSequence = require('run-sequence'); //- 同步执行任务

var less = require('gulp-less'); //- less文件编译
var minifycss = require('gulp-minify-css'); //- css文件压缩

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, "");
}


//删除掉上一次构建时创建的资源
gulp.task("clean",function () {
return gulp.src([
'rev-manifest.json',
'**/*-build-*.js',
'**/*-build-*.css',
'static/core/page/login/login.jsp',
'static/core/page/home/home.jsp',
'static/core/page/home/subHome.jsp'
]).pipe(clean());
});

/**
* 首页框架相关资源构建
* 合并了如下资源
* - home-build.js
* - PDCoreDir/corePath
* - PDGlobalDir/base
* - PDHomeDir/js/homeApp
* - PDUtilDir/tabs
* - PDCoreDir/directive
*/
gulp.task("home-build",function (cb) {
gulp.src("static/core/page/home/js/home-build.js")
.pipe(reqOptimize({
//optimize:"none", //- none为不压缩资源
//findNestedDependencies: true, //- 解析嵌套中的require
paths:{
"PDCoreDir":"../../../", //- 相对于home-build.js往上3级
"PDGlobalDir":"../../../../global",
"PDHomeDir":"../",
"PDUtilDir":"../../../../modules/util",
//"PDUtilDir/dialog":"empty:",
"Ace-build":"empty:", //- 指明需忽略的文件
//"jqValidate-build":"empty:",
/*"JQuery.validate":"empty:",
"JQuery.validate.message":"empty:",
"JQuery.validate.extra":"empty:",*/
"Angular":"empty:",
"Angular-ui-router":"empty:",
"PDAppDir/appPath":"empty:",
"PDAppDir/directive":"empty:",
"PDUtilDir/util":"empty:",
"Bootstrap":"empty:",
"jquery":"empty:"
}
}))
.pipe(rev()) //- 文件名加MD5后缀
.pipe(gulp.dest("static/core/page/home/js")) //- 生成MD5后的文件
.pipe(rev.manifest({merge:true})) //- 生成一个rev-manifest.json
.pipe(gulp.dest('')) //- 映射文件输出目录
.on('end', cb);
});


/**
* 首页框架相关资源构建
* 合并了如下资源
* - subHome-build.js
* - PDCoreDir/corePath
* - PDGlobalDir/base
* - PDHomeDir/js/subHomeApp
* - PDCoreDir/directive
*/
gulp.task("subHome-build",function (cb) {
gulp.src("static/core/page/home/js/subHome-build.js")
.pipe(reqOptimize({
//optimize:"none",
//findNestedDependencies: true, //- 解析嵌套中的require
paths:{
"PDCoreDir":"../../../", //相对于home-build.js往上3级
"PDGlobalDir":"../../../../global",
"PDHomeDir":"../",
"PDUtilDir":"../../../../modules/util",
"jqValidate-build":"empty:",
/*"JQuery.validate":"empty:",
"JQuery.validate.message":"empty:",
"JQuery.validate.extra":"empty:",*/
"Angular":"empty:",
"PDAppDir/appPath":"empty:",
"PDAppDir/directive":"empty:",
"PDUtilDir/util":"empty:",
"Bootstrap":"empty:",
"jquery":"empty:"
}
}))
.pipe(rev()) //- 文件名加MD5后缀
.pipe(gulp.dest("static/core/page/home/js")) //- 生成MD5后的文件
.pipe(rev.manifest({merge:true})) //- 生成一个rev-manifest.json
.pipe(gulp.dest('')) //- 映射文件输出目录
.on('end', cb);
});

//ACE文件合并
gulp.task("ace-build",function (cb) {
gulp.src("static/modules/ace/dist/js/ace-build.js")
.pipe(reqOptimize({
//optimize:"none",
//mainConfigFile: 'static/global/config-build.js',
paths:{
"Ace":"ace.min",
"Ace-extra":"ace-extra.min",
"Ace-element":"ace-elements.min",
"Bootstrap":"empty:",
"jquery":"empty:"
},
shim:{
"Ace":["Bootstrap", "jquery"],
"Ace-element":["Ace"],
"Ace-extra":["Ace"]
}
}))
.pipe(rev())
.pipe(gulp.dest('static/modules/ace/dist/js/'))
.pipe(rev.manifest({merge:true}))
.pipe(gulp.dest(''))
.on('end', cb);
});

/**
* jquery.validate.min.js、messages_zh.js、additional-methods.js合并为一个文件
*/
gulp.task("jqValidate-build",function (cb) {
var dir = "static/modules/jquery/plugins/validate";
gulp.src(dir+"/jqValidate-build.js")
.pipe(reqOptimize({
//optimize:"none",
//findNestedDependencies: true,
paths:{
"JQuery.validate":"jquery.validate.min",
"JQuery.validate.message":"localization/messages_zh",
"JQuery.validate.extra":"additional-methods",
"jquery":"empty:"
},
wrapShim:true,
shim:{
"JQuery.validate.message":["JQuery.validate"],
"JQuery.validate.extra":["JQuery.validate"]
}
}))
.pipe(rev())
.pipe(gulp.dest(dir))
.pipe(rev.manifest({merge:true}))
.pipe(gulp.dest(''))
.on('end', cb);
});


//util目录下util.js文件合并压缩
gulp.task("util-build",function (cb) {
gulp.src(['static/modules/util/util-build.js'])
.pipe(reqOptimize({
//optimize:"none",
//findNestedDependencies: true, //- 解析嵌套中的require
paths:{
"PDUtilDir":"",
//"PDUtilDir/dialog":"empty:",
//"Bootstrap":"empty:",
"jquery":"empty:"
}
}))
.pipe(rev()) //- 文件名加MD5后缀
.pipe(gulp.dest("static/modules/util"))
//.pipe(rename({extname: ""})) //- 映射文件中去掉后缀
.pipe(rev.manifest({merge:true})) //- 生成一个rev-manifest.json
.pipe(gulp.dest('')) //- 映射文件输出目录
.on('end', cb);
});

//config文件中路径替换
gulp.task("replaceConfigPathTemp",function (cb) {
gulp.src(['rev-manifest.json', 'static/global/config-build.js'])
.pipe(revCollector())
.pipe(rev())
.pipe(gulp.dest('static/global'))
.pipe(rev.manifest({merge:true}))
.pipe(gulp.dest(''))
.on('end', cb);
});

//替换掉config-build-md5.js文件中的所有js文件后缀
gulp.task("replaceConfigPath",["replaceConfigPathTemp"],function (cb) {
gulp.src(['static/global/config-build-*.js'])
.pipe(modify(replaceSuffix)) //- 去掉.js后缀
.pipe(gulp.dest("static/global"))
//.pipe(rename("config.js")) //- 生成一份不带MD5的config文件
//.pipe(gulp.dest("static/global"))
.on('end', cb);
});

/**
* - 首页路径替换
* - 替换config-build.js、home-build.js
*/
gulp.task("replaceHomePath",function () {
gulp.src(['rev-manifest.json', 'static/core/page/home/home-build.jsp'])
.pipe(revCollector())
.pipe(rename("home.jsp"))
.pipe(gulp.dest('static/core/page/home'));
});

/**
* - SubHome中的路径替换
* - 替换config-build.js、subHome-build.js
*/
gulp.task("replaceSubHomePath",function () {
gulp.src(['rev-manifest.json', 'static/core/page/home/subHome-build.jsp'])
.pipe(revCollector())
.pipe(rename("subHome.jsp"))
.pipe(gulp.dest('static/core/page/home'));
});

gulp.task("replaceLoginFormPath",function () {
gulp.src(['rev-manifest.json', 'static/core/page/login/login-build.jsp'])
.pipe(revCollector())
.pipe(rename("login.jsp"))
.pipe(gulp.dest('static/core/page/login'));
});


//构建总入口
gulp.task('default', function(callback) {
runSequence(
"clean", //- 上一次构建的结果清空
"home-build", //- 首页相关资源合并
"subHome-build", //- SubHome相关
"ace-build", //- ace相关资源合并
"jqValidate-build", //- 语言包与验证插件合并为一个
"util-build", //- 把loading、slidebar合并到util中
"replaceConfigPath", //- config.js中路径处理
//- 需先编译css再替换相关页面路径
"component-less",
"home-less",
"login-less",
//- 可并行处理
["replaceHomePath","replaceSubHomePath","replaceLoginFormPath"],
callback);
});

//文件内容变更动态构建
gulp.task("start", function () {
gulp.watch("static/**",["default"])
});


/***********************LESS文件编译***********************/

/**
* - 登录页面相关
*/
gulp.task("login-less",function (cb) {
var dir = "static/core/page/login/theme";
gulp.src(dir+"/login-build.less")
.pipe(less())
.pipe(minifycss())
.pipe(rev())
.pipe(gulp.dest(dir))
.pipe(rev.manifest({merge:true}))
.pipe(gulp.dest(''))
.on('end', cb);
});

/**
* - util下的所有产品组件
*/
gulp.task("component-less",function (cb) {
var dir = "static/modules/util/css";
gulp.src(dir+"/components-build.less")
.pipe(less())
.pipe(minifycss())
.pipe(rev())
.pipe(gulp.dest(dir))
.pipe(rev.manifest({merge:true}))
.pipe(gulp.dest(''))
.on('end', cb);
});

/**
* - 首页相关
*/
gulp.task("home-less",function (cb) {
var dir = "static/core/page/home/css";
gulp.src(dir+"/home-build.less")
.pipe(less())
.pipe(minifycss())
.pipe(rev())
.pipe(gulp.dest(dir))
.pipe(rev.manifest({merge:true}))
.pipe(gulp.dest(''))
.on('end', cb);


留言

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

登录模块的设计

  最近做完系统登录模块的重构,登录这块的核心在于安全的控制。下面通过本文来总结一下登录模块的设计实现,以及哪些方面需要注意。

用户创建

  • 密码保存需要做不可逆加密。即密码不能明文保存且即使是内部技术人员也无法得到真实密码,常见的加密方法有MD5,SHA系列算法,如果对加密算法不太了解的可以移步这里
  • 密码强度限制。不允许使用弱口令,比如跟用户生日相等。
  • 手机、邮箱验证。方便后续找回密码。

用户登录

需要注意的问题

  • 使用验证码。增加暴力破解的成本。
  • 登录错误次数限制,一定时间后自动解锁,彻底堵死暴力破解。
  • 关键cookie设置HTTPOnly属性,防止xss盗取用户cookie。
  • 保证密码在网络传输中的安全。启用HTTPS,或使用RAS加密。请求登录界面时,生成公钥与私钥,私钥放在服务器端,密码传输前,使用公钥加密,服务器端收到密文后使用私钥解密密文得到用户输入的真实密码。
  • 集群状态下,采用session共享的方式实现SSO时,在session上记录的登录状态信息需要可序列化。
  • 页面跳转时使用服务器端跳转,request.getRequestDispatcher().forward()。尽量不要使用response.sendRedirect(),因为每次redirect都会触发客户端304,重新再次发起一次HTTP请求。

记住密码

可以考虑这样去实现记住密码功能:cookie中需要保存如下3个值
username:用户的登录名
token:使用公钥加密过的用户密码
sequence:登录序列(防重放攻击。如果自动登录的该字段与服务器的不相符时,则证明用户在其他地方登录过。)

  当勾选记住密码登录时,在服务器端保存当前的私钥、生成一个随机的登录序列(UUID)保存起来。同时,把用户名、登录传输过来的已经用公钥加密的密文、新生成的sequence保存到cookie中。下次用户请求登录页面时,从cookie中取得用户名放到用户名的输入框中,任意字符串放到密码框中,继续勾选上记住密码框。这个时候只需要输入验证码即可点击登录按钮。服务器端,判断出本次登录是记住密码登录,首先验证cookie中的sequence是否跟服务保存的一致,如果不一致,则登录失败。一致时,取出私钥解密从cookie中拿到的token,得到真实密码,接着按照常规登录流程完成登录验证。

密码变更

  • 修改密码需要提供原始密码
  • 相关信息在网络传输中需保证加密传输
  • 找回密码功能可以考虑使用手机验证码,邮件链接实现。另外,还可考虑提供用户在本系统中的留痕供用户选择(比如什么时候注册、最近做了什么操作等),从而识别出真正的用户。

日志记录

  • 记录用户的登录日志。可记录下登录时间、退出时间、IP等信息,分析过往日志,甄别出异常登录并提醒用户。
  • 记录用户登录失败日志。限制登录失败重试次数,防止暴力破解。

服务器相关

  有时应用的安全部分做得再好,也抵不住服务器直接被攻破,所以运行应用的服务器的安全也需要特别注意。

  • 服务器访问日志(建议开启)
  • 可疑用户
  • 可疑进程
  • 可疑服务
  • 操作系统日志(日志最大大小) 定期备份

留言

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

每一个优秀的人,都有一段默默努力的时光

  “每一个优秀的人,都有一段默默努力的时光。”,在上班的地铁上,打开百词斩,这句话第一时间映入眼帘。

  还记得高中时,有一哥们儿经常被大家喷,什么原因呢?是这样的,这哥们儿各科成绩很好,另外,大家都知道这哥们儿很勤奋努力,每天晚自习回家都要背一下单词,复习一下第二天要学习的新知识点才睡觉。喜欢运动,一有空就叫人一起去篮球场打球。作业少,较轻松的时候,大家当然都很乐意跟他一起去投投篮什么的。当作业堆成小山的时候,这哥们儿每叫一次就会被喷一次,被喷的核心内容在于他自己的事情干完了就开始勾引其他人了……

  回想起当年高中时的日子,算得上是人生当中的一段努力时光。

  到了大学,高中时肩上那种沉重压力灰飞烟灭。放下担子,一路轻快的飞奔,沉醉在那自由自在的欢快时光中,无法自拔。然而,一个不经意间就到了毕业季……

  刚工作时,什么都不懂,觉得周围的人都是大神,感觉离他们之间有相当遥远的距离,可望而不可及。

  一次出差,到外地培训公司新招的实习生,在当地的讲师队伍中有一位喜欢吹牛逼讲师。他在给还没毕业的学弟学妹的讲课当中,常常吹嘘自己看了多少书,吹嘘自己看过市面上的所有心理学书籍,看过所有的关于超级记忆的书籍。听到“所有”这两字,我就呵呵了,不由得就跟“吹牛”划上了等号。在后来的慢慢接触当中,渐渐发现他思维开阔,懂的东西特别的多,不管什么内容不仅能扩展开了泛泛而谈,而且能深入直达本质。另外,他的确喜欢读书,经常看到他收快递,十有八九都是书。深入了解后,从他的口中了解到,他曾经喜欢研究心理学,所以才把市面上所有经典的心理学书籍都买来看,为了帮助小孩儿学英语,把市面上的所有关于超级记忆的书都买来看了一遍!了解到这些,不由得一阵惊叹!然后不理解的再问了一个问题,“也没必要把所有相关的书都买了吧?”,他给我的答案是:“在这个世界上,书是最廉价的东西,只要这本书中有一句话影响到你,对你有益。那么,这本书就没有白买,这种益处还是终生的!想想就觉得相当的赚。”。听到这个回答,再次被惊着,世界观被颠覆。

  在这次出差当中,正巧一位集团的副总裁在这边,顺便叫了过来给新招的实习生做一下分享,顺便让大家见见今后的领导。对于当时也属于新兵的我来说,这位领导如在云端,遥不可及。他从他的读书生涯讲起,讲他的高中时光,讲他的大学时光,讲他是怎么样从高中走进了清华的校园,然后怎么样从清华校园到出国学习,接着工作,然后一直到目前上市公司副总裁的位置。讲完他的人生履历,他总结道,其实,他并不聪明,只是比别人更努力而已。曾经由于期末成绩不好,在暑假发狠重学上学期的所有功课,周末一人泡在自习室,别人在尽情欢乐的时候他却泡在图书馆。因为没办法,没别人聪明,那就只有靠努力来补。最后,他希望大家都成为一个努力的人。听完他的分享,觉得他其实跟大家一样,都站在地上,并没有想象的那么遥远。

  在工作的这些年中,不乏一些名牌大学毕业、一些工作很多年头的同事。一些同事的确很聪明,经验很丰富。但另一些同事你会明显感觉到他过去的丰富履历跟目前工作能力不相符。而零星的一些过去履历不怎么闪耀的同事反而工作能力出众,他们的实际能力远远超出了过去平淡履历的客观印象。一个能够解释其中原因的逻辑为,一些人在很久以前即达到了人生的顶峰,然后慢慢的往下走。而另外一些人慢慢从波谷走出来,不断往顶峰攀登。大家上班的时间都是差不多的,在上班时间都需要为公司创造价值,很少有自己的时间。因此,可以肯定,在工作之外,那些不断向前的人应当远远不止一段默默努力的时光。

  曾经在知乎上回答过一个跟逆袭相关的问题,当时我的回答是,逆袭并不是一个偶然事件,而是一个量变产生质变的事件。本来就不聪明当然要比聪明的人下更多的功夫。聪明是天生优势,如果停止不前也是很容易被笨蛋超越的。知乎上面,还有一句广泛被大众所知的一句话:“以大多数人的努力程度,远远还没到拼天赋的时候!”

  最后,只想对自己说:“你一直在努力的路上,你的征途是知识的星辰大海。”

作者公众号:

留言

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

Servlet中Cookie增、删、改、查

  整理了一个在Servlet中对Cookie增删改查的工具类,首先要注意的是在服务器端是无法对Cookie做修改的,只能做到覆盖创建。

引用StackOverflow上James Sumners的回答

Per section 3.3.4 of RFC 2965, the user agent does not include the expiration information in the cookie header that is sent to the server. Therefore, there is no way to update an existing cookie’s value while retaining the expiration date that was initially set based solely on the information associated with the cookie.

So the answer to this question is: you can’t do that.

工具类如下:

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
package com.demo.util;

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class CookieUtils {

/**
* 新增Cookie
* @param response
* @param name
* @param value
*/
public static void addCookie(HttpServletResponse response, String name,String value) {
try {
//特殊字符需要编码
Cookie cookie = new Cookie(name,URLEncoder.encode(value, "UTF-8"));
cookie.setMaxAge(60*60*24*7); //- 单位为秒,7天有效
cookie.setPath("/"); //- 根路径
//JavaEE5兼容
try{
cookie.setHttpOnly(true); //- 防XSS
}catch (NoSuchMethodError e) {
e.printStackTrace();
}
response.addCookie(cookie);
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}

/**
* 删除Cookie
* @param request
* @param response
* @param name
* @return
*/
public static boolean deleteCookie(HttpServletRequest request,HttpServletResponse response, String name) {
if (request.getCookies() != null) {
for (Cookie cookie : request.getCookies()) {
if (cookie.getName().equals(name)) {
cookie.setMaxAge(0);
//经测试发现还需设置如下两个值,之所以这样,原理为覆盖掉Cookie,而不是常规意义中的删除
cookie.setValue("");
cookie.setPath("/");
response.addCookie(cookie);
return true;
}
}
}

return false;
}

/**
* 覆盖掉之前的cookie
* @param request
* @param name
* @param value
*/
public static void overrideCookie(HttpServletRequest request,HttpServletResponse response, String name,String value) {
if (request.getCookies() != null) {
for (Cookie cookie : request.getCookies()) {
if (cookie.getName().equals(name)) {
cookie.setValue(value);
cookie.setPath("/");
//cookie.getMaxAge()为-1,服务器端无法获取MaxAge
cookie.setMaxAge(60*60*24*7);
response.addCookie(cookie);
}
}
}
}

/**
* 获取Cookie
* @param request
* @param name
* @return
*/
public static Cookie getCookie(HttpServletRequest request, String name) {
if (request.getCookies() != null) {
for (Cookie cookie : request.getCookies()) {
if (cookie.getName().equals(name)) {
return cookie;
}
}
}

return null;
}
/**
* 获取Cookie对应的值
* @param request
* @param name
* @return
*/
public static String getCookieValue(HttpServletRequest request, String name) {
if (request.getCookies() != null) {
for (Cookie cookie : request.getCookies()) {
if (cookie.getName().equals(name)) {
try {
return URLDecoder.decode(cookie.getValue(), "UTF-8");
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return null;
}
}
}
}
return null;
}
}

留言

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

关于代码质量的一点思考

  最近在做基础开发框架登录模块的重构,所以来谈一谈关于代码质量的思考。
  进入正题之前,先吐槽一下,看看能不能引起一点共鸣。
  之所以要重构这部分的代码,原因很简单,公司的测试团队通过压力测试发现登录这块有性能问题。同时,基于另外一个使用产品基础开发框架的团队需要在登录这块进行一些业务上的扩展。怀着激动的心情打开这块的相关代码,首先第一感觉就是杂乱无章,排版糟糕。没办法,要解决问题,还是得硬着头皮看下去。接着,从前端UI界面的输入到提交看起,发现代码相当的绕,很难看懂其中的逻辑。另外,其中还有大片的冗余拖沓代码。虽然整体功能逻辑完整的实现了,但是实现细节一团糟。毫不夸张的说,很多部分的代码完全属于技术验证阶段的代码。遇到这样的代码,我想很少能有人能静下心来完完整整的看完……

下面说说我所认为的代码是怎么样的:

  1. 代码需要排版整洁干净、并且有清晰适当的注释,当打开代码源文件的瞬间,能给人一种赏心悦目的感觉。
  2. 代码要简洁,不拖沓,不能为了封装而封装,为了面向对象而面向对象。重复功能的代码尽量封装成方法,杜绝成段成段的相同代码、冗余代码。
  3. 代码的结构要清晰,让其他人能轻松的找到程序的入口,轻松摸清代码的整体脉络,并且不看每个方法中的具体内容,只需要看整个代码的骨骼即能了解程序的功能。
  4. 模块与模块的对接部分要简洁。大部分情况为沟通不彻底或者相互对对方的模块缺乏基本的了解造成。
  5. 注重代码的性能。这一块不仅需要从纯技术角度去考虑代码的性能,还需要深入的了解业务需求,往往一个灵光一闪的想法就能节约成片的代码、以简单的逻辑实现复杂的业务需求。

一些思考:

  1. 好的代码往往是经过不断的打磨产生的,一个精进的程序员应当具备一颗喜欢折腾代码的躁动的心。
  2. 对于框架级的代码,越早重构越好,越往后拖所带来的成本越大。
  3. 糟糕代码质量的代码后期维护成本不可估量,甚至会影响到后续新功能的开发,从这里真正能对比出一个优秀Programmer的价值。
  4. 写代码也是一门艺术。一段完美精致代码的创造过程不亚于一件艺术品的创作过程。

留言

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