定义

确保一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。

类型

创建类模式

类图

Singleton

要素

  • 私有的构造方法
  • 指向自己实例的私有静态引用
  • 以自己实例为返回值的静态的公有的方法

饿汉式单例

1
2
3
4
5
6
7
public class Singleton {  
private static Singleton singleton = new Singleton();
private Singleton(){}
public static Singleton getInstance(){
return singleton;
}
}

懒汉式单例

1
2
3
4
5
6
7
8
9
10
11
public class Singleton {  
private static Singleton singleton;
private Singleton(){}

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

单例模式的优点

  • 在内存中只有一个对象,节省内存空间。
  • 避免频繁的创建销毁对象,可以提高性能。
  • 避免对共享资源的多重占用。
  • 可以全局访问。

适用场景

由于单例模式的以上优点,所以是编程中用的比较多的一种设计模式。我总结了一下我所知道的适合使用单例模式的场景:

  • 需要频繁实例化然后销毁的对象。
  • 创建对象时耗时过多或者耗资源过多,但又经常用到的对象。
  • 有状态的工具类对象。
  • 频繁访问数据库或文件的对象。
  • 以及其他我没用过的所有要求只有一个对象的场景。

单例模式注意事项

  • 只能使用单例类提供的方法得到单例对象,不要使用反射,否则将会实例化一个新对象。
  • 不要做断开单例类对象与类中静态引用的危险操作。
  • 多线程使用单例使用共享资源时,注意线程安全问题。

评论和共享

开闭原则

开闭原则(Open Closed Principle)简称OCP

解释

一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。

开闭原则无非就是想表达这样一层意思:用抽象构建框架,用实现扩展细节。因为抽象灵活性好,适应性广,只要抽象的合理,可以基本保持软件架构的稳定。而软件中易变的细节,我们用从抽象派生的实现类来进行扩展,当软件需要发生变化时,我们只需要根据需求重新派生一个实现类来扩展就可以了。当然前提是我们的抽象要合理,要对需求的变更有前瞻性和预见性才行。

总结

当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化。

评论和共享

迪米特法则

迪米特法则(Law of Demeter)简称LoD

解释

一个对象应该对其他对象有最少的了解。

总结

迪米特法则的初衷是降低类之间的耦合,由于每个类都减少了不必要的依赖,因此的确可以降低耦合关系。但是凡事都有度,虽然可以避免与非直接的类通信,但是要通信,必然会通过一个“中介”来发生联系,过分的使用迪米特原则,会产生大量这样的中介和传递类,导致系统复杂度变大。所以在采用迪米特法则时要反复权衡,既做到结构清晰,又要高内聚低耦合。

评论和共享

依赖倒置原则

依赖倒置原则(Dependence Inversion Principle)简称DIP

## 解释
程序要依赖于抽象接口,不要依赖于具体实现。简单的说就是要求对抽象进行编程,不要对实现进行编程,这样就降低了客户与实现模块间的耦合。

  • 高层次的模块不应该依赖于低层次的模块,他们都应该依赖于抽象。
  • 抽象不应该依赖于具体实现,具体实现应该依赖于抽象。

例如:
母亲给孩子讲故事,只要给她一本书,她就可以照着书给孩子讲故事了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Book{  
public String getContent(){
return "很久很久以前有一个阿拉伯的故事……";
}
}

class Mother{
public void narrate(Book book){
System.out.println("妈妈开始讲故事");
System.out.println(book.getContent());
}
}

public class Client{
public static void main(String[] args){
Mother mother = new Mother();
mother.narrate(new Book());
}
}

假如有一天,需求变成这样:不是给书而是给一份报纸,让这位母亲讲一下报纸上的故事,报纸的代码如下:

1
2
3
4
5
class Newspaper{  
public String getContent(){
return "林书豪38+7领导尼克斯击败湖人……";
}
}

这位母亲却办不到,因为她居然不会读报纸上的故事,这太荒唐了,只是将书换成报纸,居然必须要修改Mother才能读。假如以后需求换成杂志呢?换成网页呢?还要不断地修改Mother,这显然不是好的设计。原因就是Mother与Book之间的耦合性太高了,必须降低他们之间的耦合度才行。
我们引入一个抽象的接口IReader。读物,只要是带字的都属于读物:

1
2
3
interface IReader{  
public String getContent();
}

Mother类与接口IReader发生依赖关系,而Book和Newspaper都属于读物的范畴,他们各自都去实现IReader接口,这样就符合依赖倒置原则了,代码修改为:

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
class Newspaper implements IReader {  
public String getContent(){
return "林书豪17+9助尼克斯击败老鹰……";
}
}
class Book implements IReader{
public String getContent(){
return "很久很久以前有一个阿拉伯的故事……";
}
}

class Mother{
public void narrate(IReader reader){
System.out.println("妈妈开始讲故事");
System.out.println(reader.getContent());
}
}

public class Client {
public static void main(String[] args) {
Mother mother = new Mother();
mother.narrate(new Book());
mother.narrate(new Newspaper());
}
}

总结

依赖倒置原则的核心思想是面向接口编程

评论和共享

接口隔离原则

接口隔离原则(Interface Segregation Principle)简称ISP

解释

客户端不应该依赖它不需要的接口;一个类对另一个类的依赖应该建立在最小的接口上。

例如:

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
interface I {  
public void method1();
public void method2();
public void method3();
public void method4();
public void method5();
}

class A{
public void depend1(I i){
i.method1();
}
public void depend2(I i){
i.method2();
}
public void depend3(I i){
i.method3();
}
}

class B implements I{
public void method1() {
System.out.println("类B实现接口I的方法1");
}
public void method2() {
System.out.println("类B实现接口I的方法2");
}
public void method3() {
System.out.println("类B实现接口I的方法3");
}
//对于类B来说,method4和method5不是必需的,但是由于接口A中有这两个方法,
//所以在实现过程中即使这两个方法的方法体为空,也要将这两个没有作用的方法进行实现。
public void method4() {}
public void method5() {}
}

class C{
public void depend1(I i){
i.method1();
}
public void depend2(I i){
i.method4();
}
public void depend3(I i){
i.method5();
}
}

class D implements I{
public void method1() {
System.out.println("类D实现接口I的方法1");
}
//对于类D来说,method2和method3不是必需的,但是由于接口A中有这两个方法,
//所以在实现过程中即使这两个方法的方法体为空,也要将这两个没有作用的方法进行实现。
public void method2() {}
public void method3() {}

public void method4() {
System.out.println("类D实现接口I的方法4");
}
public void method5() {
System.out.println("类D实现接口I的方法5");
}
}

public class Client{
public static void main(String[] args){
A a = new A();
a.depend1(new B());
a.depend2(new B());
a.depend3(new B());

C c = new C();
c.depend1(new D());
c.depend2(new D());
c.depend3(new D());
}
}

可以看到,如果接口过于臃肿,只要接口中出现的方法,不管对依赖于它的类有没有用处,实现类中都必须去实现这些方法,这显然不是好的设计。如果将这个设计修改为符合接口隔离原则,就必须对接口I进行拆分。在这里我们将原有的接口I拆分为三个接口,拆分后的代码如下:

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
interface I1 {  
public void method1();
}

interface I2 {
public void method2();
public void method3();
}

interface I3 {
public void method4();
public void method5();
}

class A{
public void depend1(I1 i){
i.method1();
}
public void depend2(I2 i){
i.method2();
}
public void depend3(I2 i){
i.method3();
}
}

class B implements I1, I2{
public void method1() {
System.out.println("类B实现接口I1的方法1");
}
public void method2() {
System.out.println("类B实现接口I2的方法2");
}
public void method3() {
System.out.println("类B实现接口I2的方法3");
}
}

class C{
public void depend1(I1 i){
i.method1();
}
public void depend2(I3 i){
i.method4();
}
public void depend3(I3 i){
i.method5();
}
}

class D implements I1, I3{
public void method1() {
System.out.println("类D实现接口I1的方法1");
}
public void method4() {
System.out.println("类D实现接口I3的方法4");
}
public void method5() {
System.out.println("类D实现接口I3的方法5");
}
}

总结

  • 接口尽量小,但是要有限度。对接口进行细化可以提高程序设计灵活性是不争的事实,但是如果过小,则会造成接口数量过多,使设计复杂化。所以一定要适度。
  • 为依赖接口的类定制服务,只暴露给调用的类它需要的方法,它不需要的方法则隐藏起来。只有专注地为一个模块提供定制服务,才能建立最小的依赖关系。
  • 提高内聚,减少对外交互。使接口用最少的方法去完成最多的事情。

评论和共享

里氏替换原则

里氏替换原则(Liskov Substitution Principle)简称 SRP

解释

所有引用基类的地方必须能透明地使用其子类的对象。

  • 子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。
  • 子类中可以增加自己特有的方法。
  • 当子类的方法重载父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松。
  • 当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格。

总结

子类可以扩展父类的功能,但不能改变父类原有的功能。

评论和共享

单一职责原则

单一职责原则( Single Responsibility Principle )简称 SRP

解释

规定一个类应该只有一个发生变化的原因

比如这个类:

1
2
3
4
5
6
7
8
public interface IPhone {
//拨通电话
public void dial(String phoneNumber);
//通话
public void chat(Object o);
//挂电话
public void hangup();
}

上面这段代码(IPhone),拥有两个职责,dialhangup 是负责协议管理,chat 负责数据传输。其中只要有一个需要改变就会导致实现类发生改变,所以不符合 单一职责原则

应该将这个接口分成两个,让实现类实现这两个接口。

总结

单一职责原则提出了一个编写程序的标准,用“职责”或“变化原因”来衡量接口或类的设计是否优良,但是“职责”和“变化原因”确实不可度量的,因项目而异,因环境而异

在实际使用中应该灵活使用

评论和共享

Hexo 支持 Emoji

Hexo 默认是采用 hexo-renderer-marked ,这个渲染器不支持插件扩展,当然就不行了,还有一个支持插件扩展的是 hexo-renderer-markdown-it ,所以我们可以使用这个渲染引擎来支持 emoji表情,具体实现过程如下:

更换渲染器进入blog跟目录,执行如下命令

1
2
npm un hexo-renderer-marked --save
npm i hexo-renderer-markdown-it --save

安装emoji插件,执行如下命令

1
npm install markdown-it-emoji --save

编辑 _config.yml 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
markdown:
render:
html: true
xhtmlOut: false
breaks: true
linkify: true
typographer: true
quotes: '“”‘’'
plugins:
- markdown-it-abbr
- markdown-it-footnote
- markdown-it-ins
- markdown-it-sub
- markdown-it-sup
- markdown-it-emoji # add emoji
anchors:
level: 2
collisionSuffix: 'v'
permalink: true
permalinkClass: header-anchor
permalinkSymbol: ¶

添加emoji表情
先安装emoji

1
2
npm install emoji --save
npm install twemoji --save

编辑node_modules/markdown-it-emoji/index.js文件,最终文件像:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
'use strict';
var emojies_defs = require('./lib/data/full.json');
var emojies_shortcuts = require('./lib/data/shortcuts');
var emoji_html = require('./lib/render');
var emoji_replace = require('./lib/replace');
var normalize_opts = require('./lib/normalize_opts');
var twemoji = require('twemoji') //添加twemoji
module.exports = function emoji_plugin(md, options) {
var defaults = {
defs: emojies_defs,
shortcuts: emojies_shortcuts,
enabled: []
};

var opts = normalize_opts(md.utils.assign({}, defaults, options || {}));

md.renderer.rules.emoji = emoji_html;
//使用 twemoji 渲染
md.renderer.rules.emoji = function(token, idx) {
return twemoji.parse(token[idx].content);
};
md.core.ruler.push('emoji', emoji_replace(md, opts.defs, opts.shortcuts, opts.scanRE, opts.replaceRE));
};

在主题CSS中添加你的CSS代码就行了

1
2
3
.emoji{
width: 20px;
}

评论和共享

An emoji guide for your commit messages: gitmoji

Code Emoji 描述
:art: :art: 改进代码的结构/格式
:zap: :zap: 提高性能
:fire: :fire: 删除代码或文件
:bug: :bug: 修复bug
:ambulance: :ambulance: 关键修补程序
:sparkles: :sparkles: 引入新功能
:memo: :memo: 写文档
:rocket: :rocket: 部署项目
:lipstick: :lipstick: 更新UI和样式文件
:tada: :tada: 初始提交
:white_check_mark: :white_check_mark: 添加测试
:lock: :lock: 解决安全问题
:apple: :apple: 修改 macOS 下的一些问题
:penguin: :penguin: 修改 Linux 下的一些问题
:checkered_flag: :checkered_flag: 修改 Windows 下的一些问题
:robot: :robot: 修改 Android 下的一些问题
:green_apple: :green_apple: 修改 IOS 下的一些问题
:bookmark: :bookmark: 发布版本标签
:rotating_light: :rotating_light: 移除 linter 警告
:construction: :construction: 工作正在进行中
:green_heart: :green_heart: 修复CI构建
:arrow_down: :arrow_down: 降级依赖
:arrow_up: :arrow_up: 更新依赖
:construction_worker: :construction_worker: 添加CI构建系统
:chart_with_upwards_trend: :chart_with_upwards_trend: 添加分析或跟踪代码
:hammer: :hammer: 重构代码
:heavy_minus_sign: :heavy_minus_sign: 删除依赖关系
:whale: :whale: 关于Docker的工作
:heavy_plus_sign: :heavy_plus_sign: 添加依赖关系
:wrench: :wrench: 更改配置文件
:globe_with_meridians: :globe_with_meridians: 国际化和本地化
:pencil2: :pencil2: 修正打字错误
:hankey: :hankey: 编写不好的代码,需要改进
:rewind: :rewind: 还原更改
:twisted_rightwards_arrows: :twisted_rightwards_arrows: 合并分支
:package: :package: 更新已编译的文件或包
:alien: :alien: 由于外部API更改而更新代码
:truck: :truck: 移动或重命名文件
:page_facing_up: :page_facing_up: 添加或更新许可证
:boom: :boom: 引入爆炸改变
:bento: :bento: 添加或更新资源
:ok_hand: :ok_hand: 由于代码审查更改而更新代码
:wheelchair: :wheelchair: 改善无障碍
:bulb: :bulb: 文档化源代码
:beers: :beers: 沉迷写代码
:speech_balloon: :speech_balloon: 更新文本和常量
:card_file_box: :card_file_box: 执行数据库相关更改

评论和共享

这种写法并不能解决问题

1
Access-Control-Allow-Origin: https://www.google.com,https://www.baidu.com

应该写成下面这种

1
2
3
4
5
6
7
8
9
app.all('*', function(req, res, next) {
if( req.headers.origin == 'https://www.google.com' || req.headers.origin == 'https://www.baidu.com' ){
res.header("Access-Control-Allow-Origin", req.headers.origin);
res.header('Access-Control-Allow-Methods', 'POST, GET');
res.header('Access-Control-Allow-Headers', 'X-Requested-With');
res.header('Access-Control-Allow-Headers', 'Content-Type');
}
next();
});

如果想不限制域名

1
2
3
4
5
6
7
app.all('*', function(req, res, next) {
res.header("Access-Control-Allow-Origin", req.headers.origin);
res.header('Access-Control-Allow-Methods', 'POST, GET');
res.header('Access-Control-Allow-Headers', 'X-Requested-With');
res.header('Access-Control-Allow-Headers', 'Content-Type');
next();
});

评论和共享

作者的图片

Archie Shi

Nothing to say


Front-End Development Engineer