前端预处理技术(Less、Sass、CoffeeScript、TypeScript)

目录

一、Less

  1. 概要
  2. 变量
  3. 解析Less
    • 插件安装
    • 在线处理
    • 预处理
  4. 混入(Mixins)
  5. 嵌套   
  6. 运算
  7. 函数
  8. 继承    
  9. 作用域
  10. 注释
  11. 循环
  12. 示例代码

二、Sass

  1. 变量
  2. 嵌套
  3. 导入
  4. mixin 混入
  5. 扩展/继承
  6. 运算
  7. 函数
    • RGB函数
    • HSL函数简介(HSL用色轮表示颜色值)
    • Opacity函数简介(控制颜色的透明度)
  8. 流程控制

三、CoffeeScript

  1. 安装
  2. 使用

四、TypeScript

  1. 安装
  2. 使用typescript

五、ECMAScript

六、总结

CSS不像其它高级语言一样支持算术运算、变量、流程控制与面向对象特性,所以CSS样式较多时会引起一些问题,如修改复杂,冗余,某些别的语言很简单的功能实现不了等。而javascript则是一种半面向对象的动态语言,有java的影子,有C的味道,中间有比其它语言多的糟粕,使用预处理办法可以解决这些问题。其中Less[les]与Sass是CSS的预处理技术,而CoffeeScript、TypeScript则是javascript的预处理技术。

一、Less

1. 概要

Less是一种动态样式语言,Less 是一门 CSS 预处理语言,它扩展了 CSS 语言,增加了变量、Mixin、函数等特性,使 CSS 更易维护和扩展。

Less 将 CSS 赋予了动态语言的特性,如 变量, 继承, 运算, 函数。LESS 既可以在 客户端 上运行 (支持IE 6+, Webkit, Firefox),也可以借助Node.js或者Rhino在服务端运行。 Less是一个JS库,所以他可以在客户端运行,相对Sass则必须在服务端借助Ruby运行

中文网站:http://www.lesscss.net/

英文官网:http://lesscss.org

less源码:https://github.com/cloudhead/less.js

github地址:https://github.com/less/less.js

2. 变量

语法:@变量名:值;

  1. 以@作为变量的起始标识,变量名由字母、数字、_和-组成
  2. 没有先定义后使用的规定;
  3. 以最后定义的值为最终值;
  4. 可用于rule值、rule属性、rule属性部件、选择器、选择器部件、字符串拼接;
  5. 定义时 “@变量名: 变量值;” 的形式;引用时采用 “@变量名” 或 “@{变量名}” 的形式;
  6. 存在作用域,局部作用域优先级高于全局作用域。
1
2
3
4
5
6
7
@color: #4d926f; 
#header { color: @color; }
#header { color: #4d926f; }
@color: #253636;
@color: #ff3636; //覆盖第一次的定义
#header {color: @color;} //多次反复解析
#header {color: #ff3636;}

编译后:

1
2
3
4
5
6
7
8
9
10
11
12
#header {
color: #ff3636;
}
#header {
color: #4d926f;
}
#header {
color: #ff3636;
}
#header {
color: #ff3636;
}
3.解析Less

插件安装

  1. 先安装node.js(https://nodejs.org/en/)
  2. 安装less编译器 npm install less -g
  3. 安装插件

  4. 配置

默认是正常的,如果发现Hbuilder不能自动翻译则需要配置

在线处理

页面中直接引用less的源码,使用javascript动态翻译,这样在开发阶段非常方便,但是在运行阶段会影响效率,建议在开发阶段使用less.js在线处理,项目稳定运行时将less文件预处理。

步骤一:

下载到less.js动态处理.less文件的javascript脚本,下载地址: https://github.com/less/less.js

步骤二:

在页面中引入样式与less.js文件,如下:

1
2
<link rel="stylesheet/less" type="text/css" href="styles.less">
<script src="less.js" type="text/javascript"></script>

测试运行

示例代码:

style1.less

1
2
3
4
5
6
7
8
9
10
11
12
/*1定义变量*/
@color:red;
@bgColor:lightgreen; /*定义变量color,值为red*/
.cls11{
color: @color;
}
@color:lightblue; /*重新定义,覆盖前面的定义,后定义的起作用*/
.cls12
{
background: @bgColor;
border: 2px solid @color;
}

de2.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Less</title>
<link rel="stylesheet/less" type="text/css" href="css/style1.less">
<script src="js/less/less.min.js" type="text/javascript"></script>
</head>
<body>
<div id="div1" class="cls12">
Hello Less
</div>
</body>
</html>

运行效果:

从上图可以看出less.js将style1.less文件翻译后变成了一个标准的CSS内部样式表。

预处理

在线处理的效率低,预处理就是将less文件先翻译成标准的CSS文件,再引入到项目中,处理的办法有许多:

方法一:使用lessc

a)、请先在电脑上安装node.js,下载地址: https://nodejs.org/en/


a)、安装lessc

使用npm(node.js package management)node.js包管理器

在命令行模式下输入安装指令:npm install less -g

使用lessc翻译less文件为css文件:

lessc styles.less 显示

lessc styles.less > styles.css 生成文件

参数 –x 普通压缩

参数 -h 帮助

-x的压缩方法已经被弃用,建议使用清理插件。

方法二:使用工具软件

能够翻译Less的工具软件有不少,这里介绍:Koala

Koala是一个开源的预处理语言图形编译工具,目前已支持Less、Sass、Compass与CoffeeScript。
功能特性:
多语言支持: 支持Less、Sass、Compass与CoffeeScript。
实时监听与编译: 在后台监听文件的变动,检测到文件被修改后将自动进行编译。
编译选项支持: 可以设置与自定义你需要的编译选项。
压缩支持: Less、Sass可直接编译生成压缩后的css代码。
错误提示: 编译中如果遇到错误,Koala将在右下角提示并显示出具体的出错地方,方便开发者快速定位。
跨平台: Windows、Mac、Linux完美支持。
安装Koala
在Koala官网根据你的系统平台下载对应的版本。Linux系统要求已安装好ruby运行环境。

下载地址: http://koala-app.com/


注意:路径中不要使用中文,切记!

方法三:使用IDE插件

如果使用Eclipse,Hbuilder,Visual Studio等开发工具可以安装插件完成自动翻译功能,这里使用HBuilder,在工具->插件下可以选择安装,如下图所示:

使用方法:

新建Less文件,保存后会自己生成对应的CSS文件。

1.4、混入(Mixins)
类似函数或宏

定义函数,@radius是参数,3px是默认值

1
2
3
4
5
.borderRadius(@radius:3px){
-moz-border-radius: @radius;
-webkit-border-radius: @radius;
border-radius: @radius;
}

使用函数,带参数

#header { .borderRadius(10px); }

不带参数使用默认参数

.btn { .borderRadius}

注意:

  • 可以不使用参数 .wrap(){…} .pre{ .wrap },也可以使用多个参数
  • 内置变量@arguments代表所有参数(运行时)的值 .boxShadow(@x:0,@y:0,@blur:1px,@color:#000){ box-shadow: @arguments; }

注意,在参数没有默认值的前提下使用@arguments调用时必须赋值,否则会导致整个页面内的less语法出错而失效。

  • Mixins必须使用ID或者类,即#xx或.xx,否则无效。

Less示例代码:

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
/*混入(Mixins)*/
/*定义*/
.circle(@width:100px, @color:lightblue) {
width: @width;
height: @width;
background: @color;
border-radius: @width/2;
float: left;
}
.boxShadow(@x:0, @y:0, @blur:1px, @color:#000) {
box-shadow: @arguments;
}
/*调用*/
.cls21 {
.circle();
/*默认值*/
}
.cls22 {
.circle(200px,lightgreen);
/*带参数*/
.boxShadow(5px,5px);
}
.cls23 {
.circle(300px);
/*带一个参数*/
}

HTML页面:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Less</title>
<link rel="stylesheet" type="text/css" href="css/style2.css" />
</head>
<body>
<div id="div1" class="cls21">
</div>
<div id="div1" class="cls22">
</div>
<div id="div1" class="cls23">
</div>
</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
/*调用*/
.cls21 {
width: 100px;
height: 100px;
background: lightblue;
border-radius: 50px;
float: left;/*默认值*/
}
.cls22 {
width: 200px;
height: 200px;
background: lightgreen;
border-radius: 100px;
float: left;
/*带参数*/
box-shadow: 5px 5px 1px #000;
}
.cls23 {
width: 300px;
height: 300px;
background: lightblue;
border-radius: 150px;
float: left;/*带一个参数*/
}
/*# sourceMappingURL=style2.css.map */

运行效果:

1.5、嵌套   
允许将多个CSS选择器嵌套在一起,&表示当前选择器的父选择器

1
2
3
4
#header { 
&.fl{ float: left; }
.mln { margin-left: 0; }
}

生成

1
2
#header.fl{float: left;} 
#header .mln {margin-left: 0;}

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/*嵌套*/
#parent {
color: red;
.sub11 {
background: green;
}
&.sub12 {
width: 100px;
}
.sub13 {
height: 200px;
.sub131 {
font-size: 10px;
}
}
}

结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/*嵌套*/
#parent {
color: red;
}
#parent .sub11 {
background: green;
}
#parent.sub12 {
width: 100px;
}
#parent .sub13 {
height: 200px;
}
#parent .sub13 .sub131 {
font-size: 10px;
}

1.6、运算

运算主要是针对任何数字、颜色、变量的操作,支持加、减、乘、除、()或者更复杂的综合运算;

@init: #111111;

@transition: @init*2;

.switchColor { color: @transition; }

算术运算示例:

1
2
3
4
5
6
7
8
9
10
/*运算*/
@base: 5%;
@filler: @base * 2;
@other: @base + @filler;
@base-color:lightblue;
.cls41{
color: #888 / 4;
background-color: @base-color + #111;
height: 100% / 2 + @filler;
}

运行结果:

1
2
3
4
5
.cls41 {
color: #222222;
background-color: #bee9f7;
height: 60%;
}

1.7、函数

Less 提供了许多用于转换颜色,处理字符串和进行算术运算的函数

.lightColor{lighten(@color, 10%); }

更多函数: http://www.lesscss.net/functions/

示例:

1
2
3
4
5
6
7
8
9
10
/*函数*/
.cls51 {
/*将一个资源内嵌到样式文件,如果开启了ieCompat选项,而且资源文件的体积过大,或者是在浏览器中使用,则会使用url()进行回退。如果没有指定MIME,则Node.js会使用MIME包来决定正确的MIME。*/
background: data-uri('../img/yes.gif') no-repeat;
height: 20px;
}
.cls52 {
/*增加一定数值的颜色亮度。*/
background: lighten(blue,20%);
}

翻译结果:

1
2
3
4
5
6
7
8
9
10
/*函数*/
.cls51 {
/*将一个资源内嵌到样式文件,如果开启了ieCompat选项,而且资源文件的体积过大,或者是在浏览器中使用,则会使用url()进行回退。如果没有指定MIME,则Node.js会使用MIME包来决定正确的MIME。*/
background: url("data:null;base64,R0lGODlhDAAMAKIAAMznjyJ6Gu732TKGFq7ZTF+nDI7JBf///yH5BAAAAAAALAAAAAAMAAwAAAM8eCdAZgQItdy7RAlXyhidBhjdEAQD1ZDHGVDQUyivMlws1d6xR6EFyKi06xgkHA8oSJhscI8mhWGJTA4JADs=") no-repeat;
height: 20px;
}
.cls52 {
/*增加一定数值的颜色亮度。*/
background: #6666ff;
}

运行效果:

1.8、继承 
   
示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
/*继承*/
.animal {
background-color: black;
color: white;
}
.bear {
&:extend(.animal);
background-color: brown;
}
.mouse{
&:extend(.animal);
}

翻译结果:

1
2
3
4
5
6
7
8
9
10
/*继承*/
.animal,
.bear,
.mouse {
background-color: black;
color: white;
}
.bear {
background-color: brown;
}

1.9、作用域

同一级的变量后者覆盖前者,内部变量优先级高于外部变量,变量只在同一个文件中生效。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
/*作用域*/
@len:10px;
.cls61{
@len:20px;
height:@len;
}
.cls62{
width:@len;
}
@len:30px;
.cls63{
height: @len;
}

结果:

1
2
3
4
5
6
7
8
9
.cls61 {
height: 20px;
}
.cls62 {
width: 30px;
}
.cls63 {
height: 30px;
}

1.10、注释

示例:

1
2
3
4
5
/*注释*/
.cls71{
width: 100px; //单行注释,CSS中不允许单行注释,Less允许
height:100px; /* 多行注释,CSS与Less都允许 */
}

结果:

1
2
3
4
5
/*注释*/
.cls71 {
width: 100px;
height: 100px;/* 多行注释,CSS与Less都允许 */
}

1.11、循环

在Less中,混合可以调用它自身。这样,当一个混合递归调用自己,再结合Guard表达式和模式匹配这两个特性,就可以写出循环结构。

less源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
.loop(@counter) when (@counter > 0) {
.loop((@counter - 1)); // 递归调用自身
width: (10px * @counter); // 每次调用时产生的样式代码
}

div {
.loop(3); // 调用循环
}
```

生成css

``` css
div {
width: 30px;
width: 20px;
width: 10px;
}

less源码(生成栅格系统):

1
2
3
4
5
6
7
.generate-columns(5);
.generate-columns(@n, @i: 1) when (@i =< @n) {
.column-@{i} {
width: (@i * 100% / @n);
}
.generate-columns(@n, (@i + 1));
}

生成css:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
.column-1 {
width: 20%;
}
.column-2 {
width: 40%;
}
.column-3 {
width: 60%;
}
.column-4 {
width: 80%;
}
.column-5 {
width: 100%;
}

1.12、示例代码

style.less:

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
/*1.3、变量**/

@color: red;

/*1.4、混入(Mixins)**/

.circle(@length: 100px, @color: lightblue) {
width: @length;
height: @length;
background: @color;
border-radius: @length/3;
float: left;
}

.boxshadow(@x: 3px, @y: 3px, @blur: 3px, @color: #999) {
box-shadow: @arguments;
box-shadow: @x @y @blur @color;
}

.c1 {
.circle();
/**默认参数*/
}

.c2 {
.circle(200px, lightgreen);
/**指定所有参数*/
}

.c3 {
.circle(300px);
/**指定部分*/
}

.c4 {
.circle(400px);
/**指定部分*/
.boxshadow(5px, 5px, 5px, #00f);
}

.c5 {
.circle(500px);
/**指定部分*/
.boxshadow();
}


/*1.5、嵌套 **/

#main {
color: @color;
.sub1 {
width: 100px;
.sub11 {
background: #00f;
}
}
&.sub2 {
height: 100px;
}
}

@color: blue;

/*1.6、运算**/

@i: 10rem;
@pcolor: blue;
@color: #0000ff;
.cls16 {
width: @i+10/2;
background: #000111+#111000;
color: @pcolor+#00ff00;
}


/*1.7、函数**/

.lightColor {
width: floor(2.6)px;
}

.bg {
background: data-uri('dot2.gif');
}


/*1.8、继承 **/

.animal {
color: black;
background: white;
}

.duck {
&:extend(.animal);
color: yellow;
}

.tduck {
&:extend(.duck);
min-height: 100px;
}

.pig {
&:extend(.animal);
}


/*1.9、作用域*/

@len: 10px;
.cls61 {
@len: 20px;
height: @len;
}

@len: 30px;
.cls62 {
width: @len;
}

@len: 40px;
.cls63 {
height: @len;
}

@len: 50px;

/*1.10、注释**/

.c {
width: 100px; //宽100px
}


/*1.11、循环*/

.loop(@counter) when (@counter > 0) {
width: (10px * @counter); // 每次调用时产生的样式代码
.loop((@counter - 1)); // 递归调用自身
}

div {
.loop(3); // 调用循环
}

.generate-columns(5);
.generate-columns(@n, @i: 1) when (@i =< @n) {
.column-@{i} {
width: (@i * 100% / @n);
}
.generate-columns(@n, (@i + 1));
}

style.css:

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
/*1.3、变量**/


/*1.4、混入(Mixins)**/

.c1 {
width: 100px;
height: 100px;
background: lightblue;
border-radius: 33.33333333px;
float: left;
/**默认参数*/
}

.c2 {
width: 200px;
height: 200px;
background: lightgreen;
border-radius: 66.66666667px;
float: left;
/**指定所有参数*/
}

.c3 {
width: 300px;
height: 300px;
background: lightblue;
border-radius: 100px;
float: left;
/**指定部分*/
}

.c4 {
width: 400px;
height: 400px;
background: lightblue;
border-radius: 133.33333333px;
float: left;
/**指定部分*/
box-shadow: 5px 5px 5px #00f;
}

.c5 {
width: 500px;
height: 500px;
background: lightblue;
border-radius: 166.66666667px;
float: left;
/**指定部分*/
box-shadow: 3px 3px 3px #999;
}


/*1.5、嵌套 **/

#main {
color: #0000ff;
}

#main .sub1 {
width: 100px;
}

#main .sub1 .sub11 {
background: #00f;
}

#main.sub2 {
height: 100px;
}


/*1.6、运算**/

.cls16 {
width: 15rem;
background: #111111;
color: #00ffff;
}


/*1.7、函数**/

.lightColor {
width: 2 px;
}

.bg {
background: url("data:image/gif;base64,R0lGODlhCQALAJEAAAAAAP///wCO/////yH5BAEAAAMALAAAAAAJAAsAAAITnI8naeLsllq0vlhHnhJFpoFMAQA7");
}


/*1.8、继承 **/

.animal,
.duck,
.pig,
.tduck {
color: black;
background: white;
}

.duck,
.tduck {
color: yellow;
}

.tduck {
min-height: 100px;
}


/*1.9、作用域*/

.cls61 {
height: 20px;
}

.cls62 {
width: 50px;
}

.cls63 {
height: 50px;
}


/*1.10、注释**/

.c {
width: 100px;
}


/*1.11、循环*/

div {
width: 30px;
width: 20px;
width: 10px;
}

.column-1 {
width: 20%;
}

.column-2 {
width: 40%;
}

.column-3 {
width: 60%;
}

.column-4 {
width: 80%;
}

.column-5 {
width: 100%;
}

二、Sass

Sass与Less类似类似也是一种CSS的预编译语言,他出现的更晚,但功能更加强大,Sass 有两种语法。 第一种被称为 SCSS (Sassy CSS),是一个 CSS3 语法的扩充版本;第二种比较老的语法成为缩排语法(或者就称为 “Sass”), 提供了一种更简洁的 CSS 书写方式特点如下:

特点:

  1. 不能直接在页面中解析,需要使用ruby预先翻译成css文件,而Less可以在线动态翻译。
  2. Sass功能更加强大,拥有流控语句等Less不具备的功能
  3. 完全兼容 CSS3,在 CSS 语言基础上添加了扩展功能,比如变量、嵌套 (nesting)、混合 (mixin)

在使用时Sass的后缀名为scss,本文全部使用scss的语法,可以安装Koala直接解析,不需要去搭建ruby环境,Koala已封装好。

下载地址: http://koala-app.com/

2.1、变量

sass中可以定义变量,方便统一修改和维护

Sass代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/*变量*/
$width:1004px;
$color:blue;

.cls11
{
width: $width;
height: $width/2;
background: $color;
}
$width:100px;
$color:red;
.cls12
{
$color:green;
width: $width;
height: $width/2;
background: $color;
}

CSS代码:

1
2
3
4
5
6
7
8
9
.cls11 {
width: 1004px;
height: 502px;
background: blue; }

.cls12 {
width: 100px;
height: 50px;
background: green; }
2.2、嵌套

sass可以进行选择器的嵌套,表示层级关系,看起来很优雅整齐。

Sass代码:

1
2
3
4
5
6
7
8
9
10
11
.cls21 
{
width: 100px;
.cls22{
height: 200px;
}
.cls23
{
color:blue;
}
}

CSS代码:

1
2
3
4
5
6
7
8
9
10
11
.cls21 {
width: 100px;
}

.cls21 .cls22 {
height: 200px;
}

.cls21 .cls23 {
color: blue;
}

复杂的嵌套:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
.news{

//忽略root嵌套
@at-root
.news_title{
width: 10px;
}

// 合并嵌套
.news_content{
width: 20px;
}

//组合嵌套
&_content1{
width: 20px;
&_content2{
width: 20px;
}
}

}

结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
.news .news_content {
// 在根节点.news下
width: 20px;
}

.news_content1 {
// 组合了根节点.news成为当前标签的一部分
width: 20px;
}

.news_content1_content2 {
// 多层组合&值为上一层的标签名称
width: 20px;
}
2.3、导入

sass中如导入其他sass文件,最后编译为一个css文件,优于纯css的@import

reset.scss

1
2
3
4
5
6
7
8
9
10
$zero:0;
$PI:3.14;
*
{
margin: $zero;
padding: $zero;
}
body,html{
height: 100%;
}

Sass代码:

1
2
3
4
5
@import "reset";
.cls31 {
/*height: zero; */
/*error*/
}

CSS代码:

1
2
3
4
5
6
7
8
9
10
* {
margin: 0;
padding: 0; }

body, html {
height: 100%; }

.cls31 {
/*height: zero; */
/*error*/ }
2.4、mixin 混入

sass中可用mixin定义一些代码片段,且可传参数,方便日后根据需求调用。从此处理css3的前缀兼容轻松便捷。定义时使用关键字@mixin,调用时使用@include

SCSS样式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@mixin circle($size:100px,$color:lightblue){
width: $size;
height: $size;
border-radius: $size/2;
background: $color;
}

.cls41{
@include circle();
}

.cls42{
@include circle(150px);
}

.cls43{
@include circle(200px,lightgreen);
}

CSS样式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
.cls41 {
width: 100px;
height: 100px;
border-radius: 50px;
background: lightblue;
}

.cls42 {
width: 150px;
height: 150px;
border-radius: 75px;
background: lightblue;
}

.cls43 {
width: 200px;
height: 200px;
border-radius: 100px;
background: lightgreen;
}
2.5、扩展/继承

sass可通过@extend来实现代码组合声明,使代码更加优越简洁。

SCSS样式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
.state
{
background: blue;
border: 1px solid lightblue;
}

.success{
@extend .state;
background: green;
}

.error
{
@extend .state;
border: 2px solid red;
}

CSS样式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
.state,
.success,
.error {
background: blue;
border: 1px solid lightblue;
}

.success {
background: green;
}

.error {
border: 2px solid red;
}
2.6、运算

SCSS样式:

1
2
3
4
.cls61
{
width: (100px+10px)/2-20px%7px+1px*8;
}

CSS样式:

1
2
3
.cls61 {
width: 57px;
}
2.7、函数

sass中集成了大量的颜色函数,让变换颜色更加简单。

SCSS样式:

1
2
3
4
5
6
7
8
9
$pcolor: #999ccc;
.cls71 a {
color: $pcolor;
&:hover {
background: darken($pcolor,15%);
/*变暗15%*/
color: lighten($pcolor,5%);
/*变亮5%*/
}

CSS样式:

1
2
3
4
5
6
7
8
.cls71 a {
color: #999ccc;
}

.cls71 a:hover {
background: #666bb3;
color: #aaacd5;
}

2.7.1. RGB函数

rgb($red, $green, $blue) //根据RGB中的三个值计算出一个颜色;

rgba($red, $green, $blue, $alpha) //根据RGB中红、绿、蓝和透明度计算出一个颜色;

red($color) //获取RGB中的红色值;

green($color) //获取RGB中的绿色值;

blue($color) //获取RGB中的蓝色值;

mix($color1, $color2, [$weight]) //混合两种颜色;

2.7.2. HSL函数简介(HSL用色轮表示颜色值)

hsl(hue,saturation, $lightness): 根据色相、饱和度和亮度的值返回对应的HEX颜色

hsla(hue,saturation, lightness,alpha): 根据色相、饱和度、亮度和透明度的值返回对应的HEX颜色

hue($color):从HEX颜色值中取得色相值

saturation($color): 从一个HEX颜色值中取得饱和度值

lightness($color):从一个HEX颜色值中取得亮度值

ajust-hue(color,degrees):通过改变一个颜色的色相值,创建一个新的颜色

lighten(color,amount):通过改变颜色的亮度值,让颜色变亮,创建一个一个新的颜色

darken(color,amount):通过改变颜色的亮度值,让颜色变暗,创建一个一个新的颜色

saturate(color,amount):通过改变颜色的饱和度值,让颜色更饱和,从而创建一个新的颜色

desaturate(color,amount):通过改变颜色的饱和度值,让颜色更少的饱和,从而创建出一个新的颜色

grayscale(color):将一个颜色变成灰色,相当于desaturate(color,100%);

complement(color):返回一个补充色,相当于adjust?hue(color,180deg);

invert($color):反回一个反相色,红、绿、蓝色值倒过来,而透明度不变

2.7.3.Opacity函数简介(控制颜色的透明度)

alpha($color)/opacity($color):获得透明度值

rgba($color,alpha):改变颜色的透明度值

opacify($color,$amount)/fade-in($color,$amount):使颜色更不透明

transparentize($color,$amount)/fade-out($color,$amount):使颜色更加透明

2.8、流程控制

sass中和其它程序语言一样也拥有流程控制语句,如if,for,each,while,指令,函数等。

SCSS样式:

1
2
3
4
5
6
7
8
9
10
11
12
13
$blur: lightblue;
@for $i from 1 through 10 {
.font-#{$i} {
/*计算字体大小*/
font-size: 12px+$i*2px;
/*颜色变暗*/
color: darken($blur,$i*2);
/*如果i是3的倍数,则下划线*/
@if $i%3==0 {
text-decoration: underline;
}
}
}

CSS样式:

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
/*8*/
.font-1 {
font-size: 14px;
color: #a5d4e4; }

.font-2 {
font-size: 16px;
color: #9dd1e1; }

.font-3 {
font-size: 18px;
color: #96cddf;
text-decoration: underline; }

.font-4 {
font-size: 20px;
color: #8ec9dc; }

.font-5 {
font-size: 22px;
color: #86c5da; }

.font-6 {
font-size: 24px;
color: #7ec2d8;
text-decoration: underline; }

.font-7 {
font-size: 26px;
color: #76bed5; }

.font-8 {
font-size: 28px;
color: #6ebad3; }

.font-9 {
font-size: 30px;
color: #67b7d1;
text-decoration: underline; }

.font-10 {
font-size: 32px;
color: #5fb3ce; }

HTML页面:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
<link rel="stylesheet" type="text/css" href="css/style3.css" />
</head>
<body style="padding: 10px;">
<div class="font-1">Hello SASS!</div>
<div class="font-2">Hello SASS!</div>
<div class="font-3">Hello SASS!</div>
<div class="font-4">Hello SASS!</div>
<div class="font-5">Hello SASS!</div>
<div class="font-6">Hello SASS!</div>
<div class="font-7">Hello SASS!</div>
<div class="font-8">Hello SASS!</div>
<div class="font-9">Hello SASS!</div>
<div class="font-10">Hello SASS!</div>
</body>
</html>

运行效果:

更多内容请参考:https://www.sass.hk http://sass.bootcss.com/ http://sass-lang.com/

三、CoffeeScript

javascript变得日益重要,但有很多明显的缺点,借助一种中间语言转译出优雅的javascript是解决这些问题的方法。如CoffeeScript,TypeScript。

Coffee Script是JavaScript的转译语言。了解JavaScript的发展历程:https://news.cnblogs.com/n/558565/。

Coffee的特点:

  • CoffeeScript语法类似 Ruby ,可以被编译成 JavaScript
  • CoffeeScript取JavaScript之精华,而抛弃了诸如全局变量声明、with等容易出错的部分
  • CoffeeScript是JavaScript与程序员之间的桥梁,程序员看到的是优雅的CoffeeScript接口,使得编程更简洁,写法更随意
  • 更少,更紧凑,和更清晰的代码
  • 通过规避和改变对JavaScript中不良部分的使用,只留下精华,让代码减少出错率,更容易维护
  • 在很多常用模式的实现上采用了JavaScript中的最佳实践
  • CoffeeScript生成的JavaScript代码都可以完全通过JSLint的检测

中文网: http://coffee-script.org/

官网: http://coffeescript.org/

源码:https://github.com/coffee-js/coffee-script

3.1、安装

CoffeeScript 编译器本身是 CoffeeScript 写的, 使用了 Jison parser generator. 命令行版本的coffee是一个实用的 Node.js 工具。

安装前你需要最新稳定版 Node.js, 和 npm (Node Package Manager)。借助 npm 可以安装 CoffeeScript:

npm install -g coffee-script

安装之后, 你应该可以运行 coffee 命令以执行脚本, 编译 .coffee 文件到 .js 文件, 和提供一个交互式的 REPL. coffee 命令有下列参数:

-c, –compile 编译一个 .coffee 脚本到一个同名的 .js 文件.

-m, –map 随 JavaScript 文件一起生成 source maps. 并且在 JavaScript 里加上 sourceMappingURL 指令.

-i, –interactive 启动一个交互式的 CoffeeScript 会话用来尝试一些代码片段. 等同于执行 coffee 而不加参数.

-o, –output [DIR] 将所有编译后的 JavaScript 文件写到指定文件夹. 与 –compile 或 –watch 搭配使用.

-j, –join [FILE] 编译之前, 按参数传入顺序连接所有脚本到一起, 编译后写到指定的文件. 对于编译大型项目有用.

-w, –watch 监视文件改变, 任何文件更新时重新执行命令.

-p, –print JavaScript 直接打印到 stdout 而不是写到一个文件.

-s, –stdio 将 CoffeeScript 传递到 STDIN 后从 STDOUT 获取 JavaScript. 对其他语言写的进程有好处. 比如:cat src/cake.coffee | coffee -sc

-l, –literate 将代码作为 Literate CoffeeScript 解析. 只会在从 stdio 直接传入代码或者处理某些没有后缀的文件名需要写明这点.

-e, –eval 直接从命令行编译和打印一小段 CoffeeScript. 比如:coffee -e “console.log num for num in [10..1]”

-b, –bare 编译到 JavaScript 时去掉顶层函数的包裹.

-t, –tokens 不对 CoffeeScript 进行解析, 仅仅进行 lex, 打印出 token stream: [IDENTIFIER square] [ASSIGN =] [PARAM_START (] …

-n, –nodes 不对 CoffeeScript 进行编译, 仅仅 lex 和解析, 打印 parse tree:

–nodejs node 命令有一些实用的参数, 比如

–debug, –debug-brk, –max-stack-size, 和 –expose-gc. 用这个参数直接把参数转发到 Node.js. 重复使用 –nodejs 来传递多个参数.

3.2、使用

1、编辑coffee脚本,后缀为coffee,代码如下:

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
# 赋值:
number = 42
opposite = true

# 条件:
number = -42 if opposite

# 函数:
square = (x) -> x * x

# 数组:
list = [1, 2, 3, 4, 5]

# 对象:
math =
root: Math.sqrt
square: square
cube: (x) -> x * square x

# Splats:
race = (winner, runners...) ->
print winner, runners

# 存在性:
alert "I knew it!" if elvis?

# 数组 推导(comprehensions):
cubes = (math.cube num for num in list)

将coffeescript翻译成javascript的方法如下:

a)、使用IDE插件直接翻译

翻译成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
(function() {
var cubes, list, math, num, number, opposite, race, square,
slice = [].slice;

number = 42;

opposite = true;

if (opposite) {
number = -42;
}

square = function(x) {
return x * x;
};

list = [1, 2, 3, 4, 5];

math = {
root: Math.sqrt,
square: square,
cube: function(x) {
return x * square(x);
}
};

race = function() {
var runners, winner;
winner = arguments[0], runners = 2 <= arguments.length ? slice.call(arguments, 1) : [];
return print(winner, runners);
};

if (typeof elvis !== "undefined" && elvis !== null) {
alert("I knew it!");
}

cubes = (function() {
var i, len, results;
results = [];
for (i = 0, len = list.length; i < len; i++) {
num = list[i];
results.push(math.cube(num));
}
return results;
})();

}).call(this);

b)、命令行翻译

翻译后的结果与上文相同,-c是参数表示编译的意思,-w是监听文件的变化,文件发生变化后将立即编译。

面向对象示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Animal
constructor: (@name) ->

move: (meters) ->
alert @name + " moved #{meters}m."

class Snake extends Animal
move: ->
alert "Slithering..."
super 5

class Horse extends Animal
move: ->
alert "Galloping..."
super 45

sam = new Snake "Sammy the Python"
tom = new Horse "Tommy the Palomino"

sam.move()
tom.move()

翻译后的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
(function() {
var Animal, Horse, Snake, sam, tom,
extend = function(child, parent) {
for(var key in parent) {
if(hasProp.call(parent, key)) child[key] = parent[key];
}

function ctor() {
this.constructor = child;
}
ctor.prototype = parent.prototype;
child.prototype = new ctor();
child.__super__ = parent.prototype;
return child;
},
hasProp = {}.hasOwnProperty;

Animal = (function() {
function Animal(name) {
this.name = name;
}

Animal.prototype.move = function(meters) {
return alert(this.name + (" moved " + meters + "m."));
};

return Animal;

})();

Snake = (function(superClass) {
extend(Snake, superClass);

function Snake() {
return Snake.__super__.constructor.apply(this, arguments);
}

Snake.prototype.move = function() {
alert("Slithering...");
return Snake.__super__.move.call(this, 5);
};

return Snake;

})(Animal);

Horse = (function(superClass) {
extend(Horse, superClass);

function Horse() {
return Horse.__super__.constructor.apply(this, arguments);
}

Horse.prototype.move = function() {
alert("Galloping...");
return Horse.__super__.move.call(this, 45);
};

return Horse;

})(Animal);

sam = new Snake("Sammy the Python");

tom = new Horse("Tommy the Palomino");

sam.move();

tom.move();

}).call(this);

四、TypeScript

TypeScript是一种由微软开发的自由和开源的编程语言,它是JavaScript的一个超集,扩展了JavaScript的语法,而且本质上向这个语言添加了可选的静态类型和基于类的面向对象编程。安德斯·海尔斯伯格,C#的首席架构师,工作于TypeScript的开发。

官网:http://www.typescriptlang.org/

github:https://github.com/Microsoft/TypeScript

4.1、安装
  • 在node.js环境下安装typescript,npm install -g typescript
  • 使用Microsoft指定的编辑器或IDE如,VS与微软Visual Studio Code 免费跨平台代码编辑器,安装相应的插件。

4.2、使用typescript

编写typescript源代码,greeter.ts:

1
2
3
4
5
6
7
8
9
class Greeter {
constructor(public greeting: string) { }
greet() {
return "<h1>" + this.greeting + "</h1>";
}
};

var greeter = new Greeter("Hello, world!");
document.body.innerHTML = greeter.greet();

使用tsc greeter.ts编译生成javascript greeter.js脚本:

1
2
3
4
5
6
7
8
9
10
11
12
var Greeter = (function () {
function Greeter(greeting) {
this.greeting = greeting;
}
Greeter.prototype.greet = function () {
return "<h1>" + this.greeting + "</h1>";
};
return Greeter;
}());
;
var greeter = new Greeter("Hello, world!");
document.body.innerHTML = greeter.greet();

新建一个页面测试:

1
2
3
4
5
6
7
8
9
10
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>TypeScript Hello World!</title>
</head>
<body>
<script src="typescript/greeter.js" type="text/javascript" charset="utf-8"></script>
</body>
</html>

运行结果:

示例2,raytracer.ts代码:

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
class Vector {
constructor(public x: number,
public y: number,
public z: number) {
}
static times(k: number, v: Vector) { return new Vector(k * v.x, k * v.y, k * v.z); }
static minus(v1: Vector, v2: Vector) { return new Vector(v1.x - v2.x, v1.y - v2.y, v1.z - v2.z); }
static plus(v1: Vector, v2: Vector) { return new Vector(v1.x + v2.x, v1.y + v2.y, v1.z + v2.z); }
static dot(v1: Vector, v2: Vector) { return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z; }
static mag(v: Vector) { return Math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z); }
static norm(v: Vector) {
var mag = Vector.mag(v);
var div = (mag === 0) ? Infinity : 1.0 / mag;
return Vector.times(div, v);
}
static cross(v1: Vector, v2: Vector) {
return new Vector(v1.y * v2.z - v1.z * v2.y,
v1.z * v2.x - v1.x * v2.z,
v1.x * v2.y - v1.y * v2.x);
}
}

class Color {
constructor(public r: number,
public g: number,
public b: number) {
}
static scale(k: number, v: Color) { return new Color(k * v.r, k * v.g, k * v.b); }
static plus(v1: Color, v2: Color) { return new Color(v1.r + v2.r, v1.g + v2.g, v1.b + v2.b); }
static times(v1: Color, v2: Color) { return new Color(v1.r * v2.r, v1.g * v2.g, v1.b * v2.b); }
static white = new Color(1.0, 1.0, 1.0);
static grey = new Color(0.5, 0.5, 0.5);
static black = new Color(0.0, 0.0, 0.0);
static background = Color.black;
static defaultColor = Color.black;
static toDrawingColor(c: Color) {
var legalize = d => d > 1 ? 1 : d;
return {
r: Math.floor(legalize(c.r) * 255),
g: Math.floor(legalize(c.g) * 255),
b: Math.floor(legalize(c.b) * 255)
}
}
}

class Camera {
public forward: Vector;
public right: Vector;
public up: Vector;

constructor(public pos: Vector, lookAt: Vector) {
var down = new Vector(0.0, -1.0, 0.0);
this.forward = Vector.norm(Vector.minus(lookAt, this.pos));
this.right = Vector.times(1.5, Vector.norm(Vector.cross(this.forward, down)));
this.up = Vector.times(1.5, Vector.norm(Vector.cross(this.forward, this.right)));
}
}

interface Ray {
start: Vector;
dir: Vector;
}

interface Intersection {
thing: Thing;
ray: Ray;
dist: number;
}

interface Surface {
diffuse: (pos: Vector) => Color;
specular: (pos: Vector) => Color;
reflect: (pos: Vector) => number;
roughness: number;
}

interface Thing {
intersect: (ray: Ray) => Intersection;
normal: (pos: Vector) => Vector;
surface: Surface;
}

interface Light {
pos: Vector;
color: Color;
}

interface Scene {
things: Thing[];
lights: Light[];
camera: Camera;
}

class Sphere implements Thing {
public radius2: number;

constructor(public center: Vector, radius: number, public surface: Surface) {
this.radius2 = radius * radius;
}
normal(pos: Vector): Vector { return Vector.norm(Vector.minus(pos, this.center)); }
intersect(ray: Ray) {
var eo = Vector.minus(this.center, ray.start);
var v = Vector.dot(eo, ray.dir);
var dist = 0;
if (v >= 0) {
var disc = this.radius2 - (Vector.dot(eo, eo) - v * v);
if (disc >= 0) {
dist = v - Math.sqrt(disc);
}
}
if (dist === 0) {
return null;
} else {
return { thing: this, ray: ray, dist: dist };
}
}
}

class Plane implements Thing {
public normal: (pos: Vector) =>Vector;
public intersect: (ray: Ray) =>Intersection;
constructor(norm: Vector, offset: number, public surface: Surface) {
this.normal = function(pos: Vector) { return norm; }
this.intersect = function(ray: Ray): Intersection {
var denom = Vector.dot(norm, ray.dir);
if (denom > 0) {
return null;
} else {
var dist = (Vector.dot(norm, ray.start) + offset) / (-denom);
return { thing: this, ray: ray, dist: dist };
}
}
}
}

module Surfaces {
export var shiny: Surface = {
diffuse: function(pos) { return Color.white; },
specular: function(pos) { return Color.grey; },
reflect: function(pos) { return 0.7; },
roughness: 250
}
export var checkerboard: Surface = {
diffuse: function(pos) {
if ((Math.floor(pos.z) + Math.floor(pos.x)) % 2 !== 0) {
return Color.white;
} else {
return Color.black;
}
},
specular: function(pos) { return Color.white; },
reflect: function(pos) {
if ((Math.floor(pos.z) + Math.floor(pos.x)) % 2 !== 0) {
return 0.1;
} else {
return 0.7;
}
},
roughness: 150
}
}


class RayTracer {
private maxDepth = 5;

private intersections(ray: Ray, scene: Scene) {
var closest = +Infinity;
var closestInter: Intersection = undefined;
for (var i in scene.things) {
var inter = scene.things[i].intersect(ray);
if (inter != null && inter.dist < closest) {
closestInter = inter;
closest = inter.dist;
}
}
return closestInter;
}

private testRay(ray: Ray, scene: Scene) {
var isect = this.intersections(ray, scene);
if (isect != null) {
return isect.dist;
} else {
return undefined;
}
}

private traceRay(ray: Ray, scene: Scene, depth: number): Color {
var isect = this.intersections(ray, scene);
if (isect === undefined) {
return Color.background;
} else {
return this.shade(isect, scene, depth);
}
}

private shade(isect: Intersection, scene: Scene, depth: number) {
var d = isect.ray.dir;
var pos = Vector.plus(Vector.times(isect.dist, d), isect.ray.start);
var normal = isect.thing.normal(pos);
var reflectDir = Vector.minus(d, Vector.times(2, Vector.times(Vector.dot(normal, d), normal)));
var naturalColor = Color.plus(Color.background,
this.getNaturalColor(isect.thing, pos, normal, reflectDir, scene));
var reflectedColor = (depth >= this.maxDepth) ? Color.grey : this.getReflectionColor(isect.thing, pos, normal, reflectDir, scene, depth);
return Color.plus(naturalColor, reflectedColor);
}

private getReflectionColor(thing: Thing, pos: Vector, normal: Vector, rd: Vector, scene: Scene, depth: number) {
return Color.scale(thing.surface.reflect(pos), this.traceRay({ start: pos, dir: rd }, scene, depth + 1));
}

private getNaturalColor(thing: Thing, pos: Vector, norm: Vector, rd: Vector, scene: Scene) {
var addLight = (col, light) => {
var ldis = Vector.minus(light.pos, pos);
var livec = Vector.norm(ldis);
var neatIsect = this.testRay({ start: pos, dir: livec }, scene);
var isInShadow = (neatIsect === undefined) ? false : (neatIsect <= Vector.mag(ldis));
if (isInShadow) {
return col;
} else {
var illum = Vector.dot(livec, norm);
var lcolor = (illum > 0) ? Color.scale(illum, light.color)
: Color.defaultColor;
var specular = Vector.dot(livec, Vector.norm(rd));
var scolor = (specular > 0) ? Color.scale(Math.pow(specular, thing.surface.roughness), light.color)
: Color.defaultColor;
return Color.plus(col, Color.plus(Color.times(thing.surface.diffuse(pos), lcolor),
Color.times(thing.surface.specular(pos), scolor)));
}
}
return scene.lights.reduce(addLight, Color.defaultColor);
}

render(scene, ctx, screenWidth, screenHeight) {
var getPoint = (x, y, camera) => {
var recenterX = x =>(x - (screenWidth / 2.0)) / 2.0 / screenWidth;
var recenterY = y => - (y - (screenHeight / 2.0)) / 2.0 / screenHeight;
return Vector.norm(Vector.plus(camera.forward, Vector.plus(Vector.times(recenterX(x), camera.right), Vector.times(recenterY(y), camera.up))));
}
for (var y = 0; y < screenHeight; y++) {
for (var x = 0; x < screenWidth; x++) {
var color = this.traceRay({ start: scene.camera.pos, dir: getPoint(x, y, scene.camera) }, scene, 0);
var c = Color.toDrawingColor(color);
ctx.fillStyle = "rgb(" + String(c.r) + ", " + String(c.g) + ", " + String(c.b) + ")";
ctx.fillRect(x, y, x + 1, y + 1);
}
}
}
}


function defaultScene(): Scene {
return {
things: [new Plane(new Vector(0.0, 1.0, 0.0), 0.0, Surfaces.checkerboard),
new Sphere(new Vector(0.0, 1.0, -0.25), 1.0, Surfaces.shiny),
new Sphere(new Vector(-1.0, 0.5, 1.5), 0.5, Surfaces.shiny)],
lights: [{ pos: new Vector(-2.0, 2.5, 0.0), color: new Color(0.49, 0.07, 0.07) },
{ pos: new Vector(1.5, 2.5, 1.5), color: new Color(0.07, 0.07, 0.49) },
{ pos: new Vector(1.5, 2.5, -1.5), color: new Color(0.07, 0.49, 0.071) },
{ pos: new Vector(0.0, 3.5, 0.0), color: new Color(0.21, 0.21, 0.35) }],
camera: new Camera(new Vector(3.0, 2.0, 4.0), new Vector(-1.0, 0.5, 0.0))
};
}

function exec() {
var canv = document.createElement("canvas");
canv.width = 256;
canv.height = 256;
document.body.appendChild(canv);
var ctx = canv.getContext("2d");
var rayTracer = new RayTracer();
return rayTracer.render(defaultScene(), ctx, canv.width, canv.height);
}

exec();

编译生成后的raytracer.js代码:

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
var Vector = (function() {
function Vector(x, y, z) {
this.x = x;
this.y = y;
this.z = z;
}
Vector.times = function(k, v) {
return new Vector(k * v.x, k * v.y, k * v.z);
};
Vector.minus = function(v1, v2) {
return new Vector(v1.x - v2.x, v1.y - v2.y, v1.z - v2.z);
};
Vector.plus = function(v1, v2) {
return new Vector(v1.x + v2.x, v1.y + v2.y, v1.z + v2.z);
};
Vector.dot = function(v1, v2) {
return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z;
};
Vector.mag = function(v) {
return Math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z);
};
Vector.norm = function(v) {
var mag = Vector.mag(v);
var div = (mag === 0) ? Infinity : 1.0 / mag;
return Vector.times(div, v);
};
Vector.cross = function(v1, v2) {
return new Vector(v1.y * v2.z - v1.z * v2.y, v1.z * v2.x - v1.x * v2.z, v1.x * v2.y - v1.y * v2.x);
};
return Vector;
}());
var Color = (function() {
function Color(r, g, b) {
this.r = r;
this.g = g;
this.b = b;
}
Color.scale = function(k, v) {
return new Color(k * v.r, k * v.g, k * v.b);
};
Color.plus = function(v1, v2) {
return new Color(v1.r + v2.r, v1.g + v2.g, v1.b + v2.b);
};
Color.times = function(v1, v2) {
return new Color(v1.r * v2.r, v1.g * v2.g, v1.b * v2.b);
};
Color.toDrawingColor = function(c) {
var legalize = function(d) {
return d > 1 ? 1 : d;
};
return {
r: Math.floor(legalize(c.r) * 255),
g: Math.floor(legalize(c.g) * 255),
b: Math.floor(legalize(c.b) * 255)
};
};
return Color;
}());
Color.white = new Color(1.0, 1.0, 1.0);
Color.grey = new Color(0.5, 0.5, 0.5);
Color.black = new Color(0.0, 0.0, 0.0);
Color.background = Color.black;
Color.defaultColor = Color.black;
var Camera = (function() {
function Camera(pos, lookAt) {
this.pos = pos;
var down = new Vector(0.0, -1.0, 0.0);
this.forward = Vector.norm(Vector.minus(lookAt, this.pos));
this.right = Vector.times(1.5, Vector.norm(Vector.cross(this.forward, down)));
this.up = Vector.times(1.5, Vector.norm(Vector.cross(this.forward, this.right)));
}
return Camera;
}());
var Sphere = (function() {
function Sphere(center, radius, surface) {
this.center = center;
this.surface = surface;
this.radius2 = radius * radius;
}
Sphere.prototype.normal = function(pos) {
return Vector.norm(Vector.minus(pos, this.center));
};
Sphere.prototype.intersect = function(ray) {
var eo = Vector.minus(this.center, ray.start);
var v = Vector.dot(eo, ray.dir);
var dist = 0;
if(v >= 0) {
var disc = this.radius2 - (Vector.dot(eo, eo) - v * v);
if(disc >= 0) {
dist = v - Math.sqrt(disc);
}
}
if(dist === 0) {
return null;
} else {
return {
thing: this,
ray: ray,
dist: dist
};
}
};
return Sphere;
}());
var Plane = (function() {
function Plane(norm, offset, surface) {
this.surface = surface;
this.normal = function(pos) {
return norm;
};
this.intersect = function(ray) {
var denom = Vector.dot(norm, ray.dir);
if(denom > 0) {
return null;
} else {
var dist = (Vector.dot(norm, ray.start) + offset) / (-denom);
return {
thing: this,
ray: ray,
dist: dist
};
}
};
}
return Plane;
}());
var Surfaces;
(function(Surfaces) {
Surfaces.shiny = {
diffuse: function(pos) {
return Color.white;
},
specular: function(pos) {
return Color.grey;
},
reflect: function(pos) {
return 0.7;
},
roughness: 250
};
Surfaces.checkerboard = {
diffuse: function(pos) {
if((Math.floor(pos.z) + Math.floor(pos.x)) % 2 !== 0) {
return Color.white;
} else {
return Color.black;
}
},
specular: function(pos) {
return Color.white;
},
reflect: function(pos) {
if((Math.floor(pos.z) + Math.floor(pos.x)) % 2 !== 0) {
return 0.1;
} else {
return 0.7;
}
},
roughness: 150
};
})(Surfaces || (Surfaces = {}));
var RayTracer = (function() {
function RayTracer() {
this.maxDepth = 5;
}
RayTracer.prototype.intersections = function(ray, scene) {
var closest = +Infinity;
var closestInter = undefined;
for(var i in scene.things) {
var inter = scene.things[i].intersect(ray);
if(inter != null && inter.dist < closest) {
closestInter = inter;
closest = inter.dist;
}
}
return closestInter;
};
RayTracer.prototype.testRay = function(ray, scene) {
var isect = this.intersections(ray, scene);
if(isect != null) {
return isect.dist;
} else {
return undefined;
}
};
RayTracer.prototype.traceRay = function(ray, scene, depth) {
var isect = this.intersections(ray, scene);
if(isect === undefined) {
return Color.background;
} else {
return this.shade(isect, scene, depth);
}
};
RayTracer.prototype.shade = function(isect, scene, depth) {
var d = isect.ray.dir;
var pos = Vector.plus(Vector.times(isect.dist, d), isect.ray.start);
var normal = isect.thing.normal(pos);
var reflectDir = Vector.minus(d, Vector.times(2, Vector.times(Vector.dot(normal, d), normal)));
var naturalColor = Color.plus(Color.background, this.getNaturalColor(isect.thing, pos, normal, reflectDir, scene));
var reflectedColor = (depth >= this.maxDepth) ? Color.grey : this.getReflectionColor(isect.thing, pos, normal, reflectDir, scene, depth);
return Color.plus(naturalColor, reflectedColor);
};
RayTracer.prototype.getReflectionColor = function(thing, pos, normal, rd, scene, depth) {
return Color.scale(thing.surface.reflect(pos), this.traceRay({
start: pos,
dir: rd
}, scene, depth + 1));
};
RayTracer.prototype.getNaturalColor = function(thing, pos, norm, rd, scene) {
var _this = this;
var addLight = function(col, light) {
var ldis = Vector.minus(light.pos, pos);
var livec = Vector.norm(ldis);
var neatIsect = _this.testRay({
start: pos,
dir: livec
}, scene);
var isInShadow = (neatIsect === undefined) ? false : (neatIsect <= Vector.mag(ldis));
if(isInShadow) {
return col;
} else {
var illum = Vector.dot(livec, norm);
var lcolor = (illum > 0) ? Color.scale(illum, light.color) :
Color.defaultColor;
var specular = Vector.dot(livec, Vector.norm(rd));
var scolor = (specular > 0) ? Color.scale(Math.pow(specular, thing.surface.roughness), light.color) :
Color.defaultColor;
return Color.plus(col, Color.plus(Color.times(thing.surface.diffuse(pos), lcolor), Color.times(thing.surface.specular(pos), scolor)));
}
};
return scene.lights.reduce(addLight, Color.defaultColor);
};
RayTracer.prototype.render = function(scene, ctx, screenWidth, screenHeight) {
var getPoint = function(x, y, camera) {
var recenterX = function(x) {
return(x - (screenWidth / 2.0)) / 2.0 / screenWidth;
};
var recenterY = function(y) {
return -(y - (screenHeight / 2.0)) / 2.0 / screenHeight;
};
return Vector.norm(Vector.plus(camera.forward, Vector.plus(Vector.times(recenterX(x), camera.right), Vector.times(recenterY(y), camera.up))));
};
for(var y = 0; y < screenHeight; y++) {
for(var x = 0; x < screenWidth; x++) {
var color = this.traceRay({
start: scene.camera.pos,
dir: getPoint(x, y, scene.camera)
}, scene, 0);
var c = Color.toDrawingColor(color);
ctx.fillStyle = "rgb(" + String(c.r) + ", " + String(c.g) + ", " + String(c.b) + ")";
ctx.fillRect(x, y, x + 1, y + 1);
}
}
};
return RayTracer;
}());

function defaultScene() {
return {
things: [new Plane(new Vector(0.0, 1.0, 0.0), 0.0, Surfaces.checkerboard),
new Sphere(new Vector(0.0, 1.0, -0.25), 1.0, Surfaces.shiny),
new Sphere(new Vector(-1.0, 0.5, 1.5), 0.5, Surfaces.shiny)
],
lights: [{
pos: new Vector(-2.0, 2.5, 0.0),
color: new Color(0.49, 0.07, 0.07)
}, {
pos: new Vector(1.5, 2.5, 1.5),
color: new Color(0.07, 0.07, 0.49)
}, {
pos: new Vector(1.5, 2.5, -1.5),
color: new Color(0.07, 0.49, 0.071)
}, {
pos: new Vector(0.0, 3.5, 0.0),
color: new Color(0.21, 0.21, 0.35)
}],
camera: new Camera(new Vector(3.0, 2.0, 4.0), new Vector(-1.0, 0.5, 0.0))
};
}

function exec() {
var canv = document.createElement("canvas");
canv.width = 600;
canv.height = 600;
document.body.appendChild(canv);
var ctx = canv.getContext("2d");
var rayTracer = new RayTracer();
return rayTracer.render(defaultScene(), ctx, canv.width, canv.height);
}
exec();

运行效果:

五、ECMAScript

它是一种由ECMA国际(前身为欧洲计算机制造商协会)制定和发布的脚本语言规范,javascript在它基础上经行了自己的封装。但通常来说,术语ECMAScript和javascript指的是同一个。业界所说的ECMAScript其实是指一种规范,或者说是一个标准。具体点来说,它其实就是一份文档

JS包含三个部分:ECMAScript(核心)、DOM(文档对象模型)、BOM(浏览器对象模型),ECMAScript是js语言的基础。

  1. ECMAScript3新增了对正则表达式、新控制语句、try-catch异常处理的支持,修改了字符处理、错误定义和数值输出等内容。标志着ECMAScript成为了一门真正的编程语言。
  2. 第四版于2008年7月发布前被废弃。
  3. ECMAScript5力求澄清第3版中的歧义,并添加了新的功能。新功能包括:原生JSON对象、继承的方法、高级属性的定义以及引入严格模式。
  4. ECMAScript6是继ES5之后的一次主要改进,增添了许多必要的特性,例如:模块和类以及一些实用特性,Maps、Sets、Promises、生成器(Generators)等。

浏览器支持:

一般来说,除了针对个别特性的特殊说明,各大主流浏览器都支持es5,包括

Chrome 13+

Firefox 4+

Safari 5.1*

IE 9*

其中IE9不支持es的严格模式,从IE10开始支持。Safari 5.1不支持 Function.prototype.bind

IE8只支持defineProperty、getOwnPropertyDescriptor的部分特性和JSon的新特性,IE9支持除了严格模式以外的新特性,IE10和其他主流浏览器都支持了。
因此在PC端开发的时候,要注意IE9以下的兼容,移动端开发时,可以比较放心了。

版本:

1995年,网景浏览器发布,包含一种脚本语言叫LiveScript

1996年,网景为搭上热炒Java的顺风车,将LiveScript改名为Javascript, 并提供给ECMA International进行标准化

1997年,ECMAScript1发布

1998年,ECMAScript2发布

1999年,ECMAScript3发布

3.0版是一个巨大的成功,在业界得到广泛支持,成为通行标准,奠定了JavaScript语言的基本语法,以后的版本完全继承。直到今天,初学者一开始学习JavaScript,其实就是在学3.0版的语法。

2007年,ECMAScript4.0版草案发布,本来预计次年8月发布正式版本。但是,各方对于是否通过这个标准,发生了严重分歧。以Yahoo、Microsoft、Google为首的大公司,反对JavaScript的大幅升级,主张小幅改动;以JavaScript创造者Brendan Eich为首的Mozilla公司,则坚持当前的草案。

2008年, 由于对于下一个版本应该包括哪些功能,各方分歧太大,争论过于激烈,ECMA开会决定,中止ECMAScript 4.0的开发,将其中涉及现有功能改善的一小部分,发布为ECMAScript 3.1,而将其他激进的设想扩大范围,放入以后的版本,由于会议的气氛,该版本的项目代号起名为Harmony(和谐)。会后不久,ECMAScript 3.1就改名为ECMAScript 5。

2009年,ECMAScript5发布,Harmony项目则一分为二,一些较为可行的设想定名为JavaScript.next继续开发,后来演变成ECMAScript 6;一些不是很成熟的设想,则被视为JavaScript.next.next,在更远的将来再考虑推出。TC39委员会的总体考虑是,ES5与ES3基本保持兼容,较大的语法修正和新功能加入,将由JavaScript.next完成。当时,JavaScript.next指的是ES6,第六版发布以后,就指ES7。TC39的判断是,ES5会在2013年的年中成为JavaScript开发的主流标准,并在此后五年中一直保持这个位置。

2011年,ECMAScript5.1发布

2015年,ECMAScript6发布,同年决定以后将多年一次的完整的新版本发布改为一年一次的新特性版本的发布,因此ES6又叫ES2015

2017年,ECMAScript8(EcmaScript 2017)在6月底由TC39正式发布

六、总结

coffeescript已经过去了,除非你对它非常熟悉,否则建选择typescript。

因为ECMAScript6的出现,javascript比以前要完善一些,但浏览器的支持度还是不够,但是有一天当JavaScript变得足够完善时这些中间语言就没有太多市场了。

上面提到的4种预处理工具都可以加快开发速度,某些程度上可以提高代码质量。

至于学习的方法我认为官网有详细的帮助。

总的来说要选择:Coffeescript、TypeScript或ES6都有争议。

参考网站:http://www.peise.net/tools/web/



切换样式的参考代码:

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>

<head>
<meta charset="UTF-8">
<title></title>
</head>

<body>
<h1>Hello Sass!</h1>

<button type="button" class="color" data-color="css/red.css">红色</button>
<button type="button" class="color" data-color="css/blue.css">蓝色</button>
<link rel="stylesheet" type="text/css" href="#" id="css1"/>
<script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>
<script type="text/javascript">
$(".color").click(function(){
var file=$(this).data("color");
$("#css1").attr("href",file);
});
</script>
</body>

</html>

本文链接: https://netlover.cn/2018/09/05/qianduanyuchuli.html

十种常见的软件架构模式

有没有想过要设计多大的企业规模系统?在主要的软件开发开始之前,我们必须选择一个合适的体系结构,它将为我们提供所需的功能和质量属性。因此,在将它们应用到我们的设计之前,我们应该了解不同的体系结构。

什么是架构模式?

根据维基百科中的定义:

架构模式是一个通用的、可重用的解决方案,用于在给定上下文中的软件体系结构中经常出现的问题。架构模式与软件设计模式类似,但具有更广泛的范围。

在本文中,将简要地解释以下10种常见的体系架构模式,以及它们的用法、优缺点。

  1. 分层模式
  2. 客户端-服务器模式
  3. 主从设备模式
  4. 管道-过滤器模式
  5. 代理模式
  6. 点对点模式
  7. 事件总线模式
  8. 模型-视图-控制器模式
  9. 黑板模式
  10. 解释器模式

一. 分层模式

这种模式也称为多层体系架构模式。它可以用来构造可以分解为子任务组的程序,每个子任务都处于一个特定的抽象级别。每个层都为下一个提供更高层次服务。

一般信息系统中最常见的是如下所列的4层。

  • 表示层(也称为UI层)
  • 应用层(也称为服务层)
  • 业务逻辑层(也称为领域层)
  • 数据访问层(也称为持久化层)

使用场景:

  • 一般的桌面应用程序
  • 电子商务Web应用程序

二. 客户端-服务器模式

这种模式由两部分组成:一个服务器和多个客户端。服务器组件将为多个客户端组件提供服务。客户端从服务器请求服务,服务器为这些客户端提供相关服务。此外,服务器持续侦听客户机请求。

使用场景:

  • 电子邮件,文件共享和银行等在线应用程序

三. 主从设备模式

这种模式由两方组成;主设备和从设备。主设备组件在相同的从设备组件中分配工作,并计算最终结果,这些结果是由从设备返回的结果。

使用场景:

  • 在数据库复制中,主数据库被认为是权威的来源,并且要与之同步
  • 在计算机系统中与总线连接的外围设备(主和从驱动器)

四. 管道-过滤器模式

此模式可用于构造生成和处理数据流的系统。每个处理步骤都封装在一个过滤器组件内。要处理的数据是通过管道传递的。这些管道可以用于缓冲或用于同步。

使用场景:

  • 编译器。连续的过滤器执行词法分析、解析、语义分析和代码生成
  • 生物信息学的工作流

五. 代理模式

此模式用于构造具有解耦组件的分布式系统。这些组件可以通过远程服务调用彼此交互。代理组件负责组件之间的通信协调。

服务器将其功能(服务和特征)发布给代理。客户端从代理请求服务,然后代理将客户端重定向到其注册中心的适当服务。

使用场景:

  • 消息代理软件,如Apache ActiveMQ,Apache Kafka,RabbitMQ和JBoss Messaging

六. 点对点模式

在这种模式中,单个组件被称为对等点。对等点可以作为客户端,从其他对等点请求服务,作为服务器,为其他对等点提供服务。对等点可以充当客户端或服务器或两者的角色,并且可以随时间动态地更改其角色。

使用场景:

  • 像Gnutella和G2这样的文件共享网络
  • 多媒体协议,如P2PTV和PDTP
  • 像Spotify这样的专有多媒体应用程序

七. 事件总线模式

这种模式主要是处理事件,包括4个主要组件:事件源、事件监听器、通道和事件总线。消息源将消息发布到事件总线上的特定通道上。侦听器订阅特定的通道。侦听器会被通知消息,这些消息被发布到它们之前订阅的一个通道上。

使用场景:

  • 安卓开发
  • 通知服务

八. 模型-视图-控制器模式

这种模式,也称为MVC模式,把一个交互式应用程序划分为3个部分,

  • 模型:包含核心功能和数据
  • 视图:将信息显示给用户(可以定义多个视图)
  • 控制器:处理用户输入的信息

这样做是为了将信息的内部表示与信息的呈现方式分离开来,并接受用户的请求。它分离了组件,并允许有效的代码重用。

使用场景

  • 在主要编程语言中互联网应用程序的体系架构
  • 像Django和Rails这样的Web框架

九. 黑板模式

这种模式对于没有确定解决方案策略的问题是有用的。黑板模式由3个主要组成部分组成。

  • 黑板——包含来自解决方案空间的对象的结构化全局内存
  • 知识源——专门的模块和它们自己的表示
  • 控制组件——选择、配置和执行模块

所有的组件都可以访问黑板。组件可以生成添加到黑板上的新数据对象。组件在黑板上查找特定类型的数据,并通过与现有知识源的模式匹配来查找这些数据。

使用场景:

  • 语音识别
  • 车辆识别和跟踪
  • 蛋白质结构识别
  • 声纳信号的解释

十. 解释器模式

这个模式用于设计一个解释用专用语言编写的程序的组件。它主要指定如何评估程序的行数,即以特定的语言编写的句子或表达式。其基本思想是为每种语言的符号都有一个分类。

使用场景:

  • 数据库查询语言,比如SQL
  • 用于描述通信协议的语言

体系架构模式的比较

下面给出的表格总结了每种体系架构模式的优缺点。

名称 优点 缺点
分层模式 一个较低的层可以被不同的层所使用。层使标准化更容易,因为我们可以清楚地定义级别。可以在层内进行更改,而不会影响其他层。 不是普遍适用的。在某些情况下,某些层可能会被跳过。
客户端-服务器模式 很好地建立一组服务,用户可以请求他们的服务。 请求通常在服务器上的单独线程中处理。由于不同的客户端具有不同的表示,进程间通信会导致额外开销。
主从设备模式 准确性——将服务的执行委托给不同的从设备,具有不同的实现。 从设备是孤立的:没有共享的状态。主-从通信中的延迟可能是一个问题,例如在实时系统中。这种模式只能应用于可以分解的问题。
管道-过滤器模式 展示并发处理。当输入和输出由流组成时,过滤器在接收数据时开始计算。轻松添加过滤器,系统可以轻松扩展。过滤器可重复使用。 可以通过重新组合一组给定的过滤器来构建不同的管道。 效率受到最慢的过滤过程的限制。从一个过滤器移动到另一个过滤器时的数据转换开销。
代理模式 允许动态更改、添加、删除和重新定位对象,这使开发人员的发布变得透明。 要求对服务描述进行标准化。
点对点模式 支持分散式计算。对任何给定节点的故障处理具有强大的健壮性。在资源和计算能力方面具有很高的可扩展性。 服务质量没有保证,因为节点是自愿合作的。安全是很难得到保证的。性能取决于节点的数量。
事件总线模式 新的发布者、订阅者和连接可以很容易地添加。对高度分布式的应用程序有效。 可伸缩性可能是一个问题,因为所有消息都是通过同一事件总线进行的。
模型-视图-控制器模式 可以轻松地拥有同一个模型的多个视图,这些视图可以在运行时连接和断开。 增加复杂性。可能导致许多不必要的用户操作更新。
黑板模式 很容易添加新的应用程序。扩展数据空间的结构很简单。 修改数据空间的结构非常困难,因为所有应用程序都受到了影响。可能需要同步和访问控制。
解释器模式 高度动态的行为是可行的。对终端用户编程性提供好处。提高灵活性,因为替换一个解释程序很容易。 由于解释语言通常比编译后的语言慢,因此性能可能是一个问题。

本文链接: https://netlover.cn/2018/08/20/10pattern.html

C#设计模式(08) - 装饰模式(Decorator Pattern)

一、引言

今天我们要讲【结构型】设计模式的第三个模式,该模式是【装饰模式】,英文名称:Decorator Pattern。我第一次看到这个名称想到的是另外一个词语“装修”,我就说说我对“装修”的理解吧,大家一定要看清楚,是“装修”,不是“装饰”。我们长大了,就要结婚,要结婚就涉及到要买房子,买的精装修或者简单装修就可以住的,暂时不谈。我们就谈谈我们购买的是毛坯房。如果我想要房子的内饰是大理石风格的,我们只要在毛坯房的基础之上用大理石风格的材料装修就可以,我们当然不可能为了要一个装修风格,就把刚刚盖好的房子拆了在重新来过。房子装修好了,我们就住了进来,很开心。过了段时间,我们发现我们的房子在冬季比较冷,于是我就想给我们的房子增加保暖的功能,装修好的房子我们可以继续居住,我们只是在房子外面加一层保护层就可以了。又过了一段时间,总是有陌生人光顾,所以我们想让房子更安全,于是我们在外墙和房顶加装安全摄像头,同时门窗也增加安全系统。随着时间的流逝,我们可能会根据我们的需求增加相应的功能,期间,我们的房子可以正常使用,加上什么设施就有了相应的功能。从这一方面来讲,“装修”和“装饰”有类似的概念,接下来就让我们看看装饰模式具体是什么吧!

二、装饰模式的详细介绍

2.1、动机(Motivate)

在房子装修的过程中,各种功能可以相互组合,来增加房子的功用。类似的,如果我们在软件系统中,要给某个类型或者对象增加功能,如果使用“继承”的方案来写代码,就会出现子类暴涨的情况。比如:IMarbleStyle是大理石风格的一个功能,IKeepWarm是保温的一个接口定义,IHouseSecurity是房子安全的一个接口,就三个接口来说,House是我们房子,我们的房子要什么功能就实现什么接口,如果房子要的是复合功能,接口不同的组合就有不同的结果,这样就导致我们子类膨胀严重,如果需要在增加功能,子类会成指数增长。这个问题的根源在于我们“过度地使用了继承来扩展对象的功能”,由于继承为类型引入的静态特质(所谓静态特质,就是说如果想要某种功能,我们必须在编译的时候就要定义这个类,这也是强类型语言的特点。静态,就是指在编译的时候要确定的东西;动态,是指运行时确定的东西),使得这种扩展方式缺乏灵活性;并且随着子类的增多(扩展功能的增多),各种子类的组合(扩展功能的组合)会导致更多子类的膨胀(多继承)。如何使“对象功能的扩展”能够根据需要来动态(即运行时)地实现?同时避免“扩展功能的增多”带来的子类膨胀问题?从而使得任何“功能扩展变化”所导致的影响降为最低?

2.2、意图(Intent)

动态地给一个对象增加一些额外的职责。就增加功能而言,Decorator模式比生成子类更为灵活。   —— 《设计模式》GoF

2.3、结构图(Structure)

2.4、模式的组成

在装饰模式中的各个角色有:

  1. 抽象构件角色(Component):给出一个抽象接口,以规范准备接收附加责任的对象。
  2. 具体构件角色(Concrete Component):定义一个将要接收附加责任的类。
  3. 装饰角色(Decorator):持有一个构件(Component)对象的实例,并实现一个与抽象构件接口一致的接口。
  4. 具体装饰角色(Concrete Decorator):负责给构件对象添加上附加的责任。
2.5 、装饰模式的具体代码实现

刚开始一看这个“装饰模式”是有点不太好理解,既然这个模式是面向对象的设计模式,那在现实生活中一定有事例和其对应,其实这种例子也不少,大家好好的挖掘吧,也可以提高我们对面向对象的理解。我继续拿盖房子来说事吧。

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
namespace 装饰模式的实现
{
/// <summary>
/// 该抽象类就是房子抽象接口的定义,该类型就相当于是Component类型,是饺子馅,需要装饰的,需要包装的
/// </summary>
public abstract class House
{
//房子的装修方法--该操作相当于Component类型的Operation方法
public abstract void Renovation();
}

/// <summary>
/// 该抽象类就是装饰接口的定义,该类型就相当于是Decorator类型,如果需要具体的功能,可以子类化该类型
/// </summary>
public abstract class DecorationStrategy:House //关键点之二,体现关系为Is-a,有这这个关系,装饰的类也可以继续装饰了
{
//通过组合方式引用Decorator类型,该类型实施具体功能的增加
//这是关键点之一,包含关系,体现为Has-a
protected House _house;

//通过构造器注入,初始化平台实现
protected DecorationStrategy(House house)
{
this._house=house;
}

//该方法就相当于Decorator类型的Operation方法
public override void Renovation()
{
if(this._house!=null)
{
this._house.Renovation();
}
}
}

/// <summary>
/// PatrickLiu的房子,我要按我的要求做房子,相当于ConcreteComponent类型,这就是我们具体的饺子馅,我个人比较喜欢韭菜馅
/// </summary>
public sealed class PatrickLiuHouse:House
{
public override void Renovation()
{
Console.WriteLine("装修PatrickLiu的房子");
}
}


/// <summary>
/// 具有安全功能的设备,可以提供监视和报警功能,相当于ConcreteDecoratorA类型
/// </summary>
public sealed class HouseSecurityDecorator:DecorationStrategy
{
public HouseSecurityDecorator(House house):base(house){}

public override void Renovation()
{
base.Renovation();
Console.WriteLine("增加安全系统");
}
}

/// <summary>
/// 具有保温接口的材料,提供保温功能,相当于ConcreteDecoratorB类型
/// </summary>
public sealed class KeepWarmDecorator:DecorationStrategy
{
public KeepWarmDecorator(House house):base(house){}

public override void Renovation()
{
base.Renovation();
Console.WriteLine("增加保温的功能");
}
}

public class Program
{
static void Main()
{
//这就是我们的饺子馅,需要装饰的房子
House myselfHouse=new PatrickLiuHouse();

DecorationStrategy securityHouse=new HouseSecurityDecorator(myselfHouse);
securityHouse.Renovation();
//房子就有了安全系统了

//如果我既要安全系统又要保暖呢,继续装饰就行
DecorationStrategy securityAndWarmHouse=new HouseSecurityDecorator(securityHouse);
securityAndWarmHouse.Renovation();
}
}
}

写了很多备注,大家好好体会一下,里面有两个关键点,仔细把握。

三、装饰模式的实现要点:

  1. 通过采用组合、而非继承的手法,Decorator模式实现了在运行时动态地扩展对象功能的能力,而且可以根据需要扩展多个功能。避免了单独使用继承带来的“灵活性差”和“多子类衍生问题”。
  2. Component类在Decorator模式中充当抽象接口的角色,不应该去实现具体的行为。而且Decorator类对于Component类应该透明——换言之Component类无需知道Decorator类,Decorator类是从外部来扩展Component类的功能。
  3. Decorator类在接口上表现为is-a Component的继承关系,即Decorator类继承了Component类所具有的接口。但在实现上又表现为has-a Component的组合关系,即Decorator类又使用了另外一个Component类。我们可以使用一个或者多个Decorator对象来“装饰”一个Component对象,且装饰后的对象仍然是一个Component对象。
  4. Decorator模式并非解决“多子类衍生的多继承”问题,Decorator模式应用的要点在于解决“主体类在多个方向上的扩展功能”——是为“装饰”的含义。

装饰模式的优点:

  1. 把抽象接口与其实现解耦。
  2. 抽象和实现可以独立扩展,不会影响到对方。
  3. 实现细节对客户透明,对用于隐藏了具体实现细节。

装饰模式的缺点:

  1. 增加了系统的复杂度

在以下情况下应当使用桥接模式:

  1. 如果一个系统需要在构件的抽象化角色和具体化角色之间添加更多的灵活性,避免在两个层次之间建立静态的联系。
  2. 设计要求实现化角色的任何改变不应当影响客户端,或者实现化角色的改变对客户端是完全透明的。
  3. 需要跨越多个平台的图形和窗口系统上。
  4. 一个类存在两个独立变化的维度,且两个维度都需要进行扩展。

四、.NET 中装饰模式的实现

在Net框架中,有一个类型很明显的使用了“装饰模式”,这个类型就是Stream。Stream类型是一个抽象接口,它在System.IO命名空间里面,它其实就是Component。FileStream、NetworkStream、MemoryStream都是实体类ConcreteComponent。右边的BufferedStream、CryptoStream是装饰对象,它们都是继承了Stream接口的。

如图:

Stream就相当于Component,定义装饰的对象,FileStream就是要装饰的对象,BufferedStream是装饰对象。我们看看BufferedStream的定义,部分定义了。

1
2
3
4
5
6
public sealed class BufferedStream : Stream
{
private const int _DefaultBufferSize = 4096;

private Stream _stream;
}

结构很简单,对比结构图看吧,没什么可说的了。

五、总结

今天的文章就写到这里了,总结一下我对这个模式的看法,这个模式有点像包饺子,ConcreteComponent其实是饺子馅,Decorator就像饺子皮一样,包什么皮就有什么的样子,皮和皮也可以嵌套,当然我们生活中的饺子只是包一层。其实手机也是一个装饰模式使用的好例子,以前我们的手机只是接打电话,然后可以发短信和彩信,我在装饰一个就可以拍照了。我们现在的手机功能很丰富,其结果也类似装饰的结果。随着社会的进步,技术发展,模块化的手机也出现了,其设计原理也和“装饰模式”就更接近了。不光手机,还有我们身边其他很多家用电器也有类似的发展经历,我们努力发现生活中的真理吧,然后再在软件环境中慢慢体会吧。

本文链接: https://netlover.cn/2018/08/19/csharp-decorator-pattern.html

C#设计模式(06) - 适配器模式(Adapter Pattern)

一、引言

从今天开始我们开始讲【结构型】设计模式,【结构型】设计模式有如下几种:适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式。【创建型】的设计模式解决的是对象创建的问题,那【结构型】设计模式解决的是类和对象的组合关系的问题。今天我们就开始讲【结构型】设计模式里面的第一个设计模式,中文名称:适配器模式,英文名称:Adapter Pattern。说起这个模式其实很简单,在现实生活中也有很多实例,比如:我们手机的充电器,充电器的接头,有的是把两相电转换为三相电的,当然也有把三相电转换成两相电的。我们经常使用笔记本电脑,笔记本电脑的工作电压和我们家里照明电压是不一致的,当然也就需要充电器把照明电压转换成笔记本的工作电压,只有这样笔记本电脑才可以正常工作。太多了,就不一一列举了。我们只要记住一点,适配就是转换,把不能在一起工作的两样东西通过转换,让他们可以在一起工作。

二、适配器模式的详细介绍

2.1、动机(Motivate)

在软件系统中,由于应用环境的变化,常常需要将“一些现存的对象”放在新的环境中应用,但是新环境要求的接口是这些现存对象所不满足的。如何应对这种“迁移的变化”?如何既能利用现有对象的良好实现,同时又能满足新的应用环境所要求的接口?

2.2、意图(Intent)

将一个类的接口转换成客户希望的另一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。 –《设计模式》Gof

2.3、结构图(Structure)

适配器有两种结构

1、-对象适配器(更常用)

对象适配器使用的是对象组合的方案,它的Adapter核Adaptee的关系是组合关系。

OO中优先使用组合模式,组合模式不适用再考虑继承。因为组合模式更加松耦合,而继承是紧耦合的,父类的任何改动都要导致子类的改动。

2、-类适配器

2.4、模式的组成

可以看出,在适配器模式的结构图有以下角色:

  1. 目标角色(Target):定义Client使用的与特定领域相关的接口。
  2. 客户角色(Client):与符合Target接口的对象协同。
  3. 被适配角色(Adaptee):定义一个已经存在并已经使用的接口,这个接口需要适配。
  4. 适配器角色(Adapte) :适配器模式的核心。它将对被适配Adaptee角色已有的接口转换为目标角色Target匹配的接口。对Adaptee的接口与Target接口进行适配.
2.5 适配器模式的具体实现

由于适配器模式有两种实现结构,今天我们针对每种都实现了自己的方式。

1、对象的是适配器模式实现

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
namespace 对象的适配器模式
{
///<summary>
///家里只有两个孔的插座,也懒得买插线板了,还要花钱,但是我的手机是一个有3个小柱子的插头,明显直接搞不定,那就适配吧
///</summary>
class Client
{
static void Main(string[] args)
{
//好了,现在就可以给手机充电了
TwoHoleTarget homeTwoHole = new ThreeToTwoAdapter();
homeTwoHole.Request();
Console.ReadLine();
}
}

/// <summary>
/// 我家只有2个孔的插座,也就是适配器模式中的目标(Target)角色,这里可以写成抽象类或者接口
/// </summary>
public class TwoHoleTarget
{
// 客户端需要的方法
public virtual void Request()
{
Console.WriteLine("两孔的充电器可以使用");
}
}

/// <summary>
/// 手机充电器是有3个柱子的插头,源角色——需要适配的类(Adaptee)
/// </summary>
public class ThreeHoleAdaptee
{
public void SpecificRequest()
{
Console.WriteLine("我是3个孔的插头也可以使用了");
}
}

/// <summary>
/// 适配器类,TwoHole这个对象写成接口或者抽象类更好,面向接口编程嘛
/// </summary>
public class ThreeToTwoAdapter : TwoHoleTarget
{
// 引用两个孔插头的实例,从而将客户端与TwoHole联系起来
private ThreeHoleAdaptee threeHoleAdaptee = new ThreeHoleAdaptee();
//这里可以继续增加适配的对象。。

/// <summary>
/// 实现2个孔插头接口方法
/// </summary>
public override void Request()
{
//可以做具体的转换工作
threeHoleAdaptee.SpecificRequest();
//可以做具体的转换工作
}
}
}

2、类的适配器模式实现

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
namespace 设计模式之适配器模式
{
/// <summary>
/// 这里手机充电器为例,我们的家的插座是两相电的,但是手机的插座接头是三相电的
/// </summary>
class Client
{
static void Main(string[] args)
{
//好了,现在可以充电了
ITwoHoleTarget change = new ThreeToTwoAdapter();
change.Request();
Console.ReadLine();
}
}

/// <summary>
/// 我家只有2个孔的插座,也就是适配器模式中的目标角色(Target),这里只能是接口,也是类适配器的限制
/// </summary>
public interface ITwoHoleTarget
{
void Request();
}

/// <summary>
/// 3个孔的插头,源角色——需要适配的类(Adaptee)
/// </summary>
public abstract class ThreeHoleAdaptee
{
public void SpecificRequest()
{
Console.WriteLine("我是三个孔的插头");
}
}

/// <summary>
/// 适配器类,接口要放在类的后面,在此无法适配更多的对象,这是类适配器的不足
/// </summary>
public class ThreeToTwoAdapter:ThreeHoleAdaptee,ITwoHoleTarget
{
/// <summary>
/// 实现2个孔插头接口方法
/// </summary>
public void Request()
{
// 调用3个孔插头方法
this.SpecificRequest();
}
}
}

代码都很简答,谁都可以看得懂,也有详细的备注。

三、适配器模式的实现要点:

  1. Adapter模式主要应用于“希望复用一些现存的类,但是接口又与复用环境要求不一致的情况”,在遗留代码复用、类库迁移等方面非常有用。
  2. GoF23定义了两种Adapter模式的实现结构:对象适配器和类适配器。类适配器采用“多继承”的实现方式,在C#语言中,如果被适配角色是类,Target的实现只能是接口,因为C#语言只支持接口的多继承的特性。在C#语言中类适配器也很难支持适配多个对象的情况,同时也会带来了不良的高耦合和违反类的职责单一的原则,所以一般不推荐使用。对象适配器采用“对象组合”的方式,更符合松耦合精神,对适配的对象也没限制,可以一个,也可以多个,但是,使得重定义Adaptee的行为较困难,这就需要生成Adaptee的子类并且使得Adapter引用这个子类而不是引用Adaptee本身。Adapter模式可以实现的非常灵活,不必拘泥于GoF23中定义的两种结构。例如,完全可以将Adapter模式中的“现存对象”作为新的接口方法参数,来达到适配的目的。
  3. Adapter模式本身要求我们尽可能地使用“面向接口的编程”风格,这样才能在后期很方便地适配。

适配器模式用来解决现有对象与客户端期待接口不一致的问题,下面详细总结下适配器两种形式的优缺点。

类的适配器模式:

优点:

  1. 可以在不修改原有代码的基础上来复用现有类,很好地符合 “开闭原则”
  2. 可以重新定义Adaptee(被适配的类)的部分行为,因为在类适配器模式中,Adapter是Adaptee的子类
  3. 仅仅引入一个对象,并不需要额外的字段来引用Adaptee实例(这个即是优点也是缺点)。

缺点:

  1. 用一个具体的Adapter类对Adaptee和Target进行匹配,当如果想要匹配一个类以及所有它的子类时,类的适配器模式就不能胜任了。因为类的适配器模式中没有引入Adaptee的实例,光调用this.SpecificRequest方法并不能去调用它对应子类的SpecificRequest方法。
  2. 采用了 “多继承”的实现方式,带来了不良的高耦合。

对象的适配器模式

优点:

  1. 可以在不修改原有代码的基础上来复用现有类,很好地符合 “开闭原则”(这点是两种实现方式都具有的)
  2. 采用 “对象组合”的方式,更符合松耦合。

缺点:

  1. 使得重定义Adaptee的行为较困难,这就需要生成Adaptee的子类并且使得Adapter引用这个子类而不是引用Adaptee本身。

适配器模式使用的场景:

  1. 系统需要复用现有类,而该类的接口不符合系统的需求
  2. 想要建立一个可重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作。
  3. 对于对象适配器模式,在设计里需要改变多个已有子类的接口,如果使用类的适配器模式,就要针对每一个子类做一个适配器,而这不太实际。

四、.NET 中适配器模式的实现

说道适配器模式在Net中的实现就很多了,比如:System.IO里面的很多类都有适配器的影子,当我们操作文件的时候,其实里面调用了COM的接口实现。以下两点也是适配器使用的案例:

1.在.NET中复用COM对象:

COM对象不符合.NET对象的接口,使用tlbimp.exe来创建一个Runtime Callable Wrapper(RCW)以使其符合.NET对象的接口,COM Interop就好像是COM和.NET之间的一座桥梁。

2..NET数据访问类(Adapter变体):

各种数据库并没有提供DataSet接口,使用DbDataAdapter可以将任何个数据库访问/存取适配到一个DataSet对象上,DbDataAdapter在数据库和DataSet之间做了很好的适配。当然还有SqlDataAdapter类型了,针对微软SqlServer类型的数据库在和DataSet之间进行适配。

五、总结

今天的文章就写到这里了,在结束今天写作之前,有一句话还是要说的,虽然以前说过。每种设计模式都有自己的适用场景,它是为了解决一类问题,没有所谓的缺点,没有一种设计模式可以解决所有情况的。我们使用设计模式的态度是通过不断地重构来使用模式,不要一上来就使用设计模式,为了模式而模式。如果软件没有需求的变化,我们不使用模式都没有问题。遇到问题,我们就按着常规来写,有了需求变化,然后我们去抽象,了解使用的场景,然后在选择合适的设计模式。

本文链接: https://netlover.cn/2018/08/19/csharp-adapter-pattern.html

C#设计模式(10) - 外观模式(Facade Pattern)

一、引言

快12点半了,要开始今天的写作了。很快,转眼设计模式已经写了十个了,今天我们要讲【结构型】设计模式的第五个模式,该模式是【外观模式】,英文名称是:Facade Pattern。我们先从名字上来理解一下“外观模式”。我看到了“外观”这个词语,就想到了“外表”这个词语,两者有着很相近的意思。就拿谈恋爱来说,“外表”很重要,如果第一眼看着很舒服、有眼缘,那就有交往下去的可能。如果长的“三寸钉、枯树皮”,估计就够呛了。在这方面,“外观”和“外表”有着相同的作用。在软件系统中,要完成一个功能,需要很多接口调用,不仅增加了开发难度,也增加了调试成本和维护的复杂度。不如我们把这些接口再封装一次,给一个很好的“外观”,让使用者使用更方便,只需调用一个接口,就可以完成以前调用多个接口的来完成任务,这就方便了。这个模式很简单,大家很容易理解,可能大家在编码的过程中已经不止一次使用过该模式了,只是不知道名字罢了。现实生活中这样的例子很多,举不胜举,来一幅图,大家看看就明白了。

图一:

二、外观模式的详细介绍

2.1、动机(Motivate)

在软件系统开发的过程中,当组件的客户(即外部接口,或客户程序)和组件中各种复杂的子系统有了过多的耦合,随着外部客户程序和各子系统的演化,这种过多的耦合面临很多变化的挑战。如何简化外部客户程序和系统间的交互接口?如何将外部客户程序的演化和内部子系统的变化之间的依赖相互解耦?

2.2、意图(Intent)

为子系统中的一组接口提供一个一致的界面,Facade模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。      ——《设计模式》GoF

2.3、结构图(Structure)

2.4、模式的组成

外观模式包含如下两个角色:

  1. 外观角色(Facade):在客户端可以调用它的方法,在外观角色中可以知道相关的(一个或者多个)子系统的功能和责任;在正常情况下,它将所有从客户端发来的请求委派到相应的子系统去,传递给相应的子系统对象处理。
  2. 子系统角色(SubSystem):在软件系统中可以有一个或者多个子系统角色,每一个子系统可以不是一个单独的类,而是一个类的集合,它实现子系统的功能;每一个子系统都可以被客户端直接调用,或者被外观角色调用,它处理由外观类传过来的请求;子系统并不知道外观的存在,对于子系统而言,外观角色仅仅是另外一个客户端而已。
2.5、外观模式的具体实现

马上就到“双十一”了,人们又开始疯狂的购买了。其实购买的过程很复杂,但是我们在购买的过程只需要选择自己喜欢的商品,也可以加入购物车,最后点击付款就完成了。其实这个过程没有那么简单。我们下面就模仿一下购买的过程吧。

购买过程有几点必须要做的事情:

  1. 身份验证安全,没有认证是无效用户。
  2. 系统安全,检查系统环境,防止注入、跨站和伪造等攻击
  3. 网银安全,检查付款地址的有效性,检查网关是否正常
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
namespace 外观模式的实现
{
/// <summary>
/// 不使用外观模式的情况
/// 此时客户端与三个子系统都发送了耦合,使得客户端程序依赖与子系统
/// 为了解决这样的问题,我们可以使用外观模式来为所有子系统设计一个统一的接口
/// 客户端只需要调用外观类中的方法就可以了,简化了客户端的操作
/// 从而让客户和子系统之间避免了紧耦合
/// </summary>
class Client
{
static void Main(string[] args)
{
SystemFacade facade=new SystemFacade();
facade.Buy();
Console.Read();
}
}

// 身份认证子系统A
public class AuthoriationSystemA
{
public void MethodA()
{
Console.WriteLine("执行身份认证");
}
}

// 系统安全子系统B
public class SecuritySystemB
{
public void MethodB()
{
Console.WriteLine("执行系统安全检查");
}
}

// 网银安全子系统C
public class NetBankSystemC
{
public void MethodC()
{
Console.WriteLine("执行网银安全检测");
}
}

//更高层的Facade
public class SystemFacade
{
private AuthoriationSystemA auth;
private SecuritySystemB security;
private NetBankSystemC netbank;

public SystemFacade()
{
auth=new AuthoriationSystemA();
security=new SecuritySystemB();
netbank=new NetBankSystemC();
}

public void Buy()
{
auth.MethodA();//身份认证子系统
security.MethodB();//系统安全子系统
netbank.MethodC();//网银安全子系统

Console.WriteLine("我已经成功购买了!");
}
}
}

这个模式很简单,就话不多说了。

三、外观模式的实现要点:

1、一个系统可以有几个门面类

在门面模式中,通常只需要一个门面类,并且此门面类只有一个实例,换言之它是一个单例类。当然这并不意味着在整个系统里只有一个门面类,而仅仅是说对每一个子系统只有一个门面类。或者说,如果一个系统有好几个子系统的话,每一个子系统都有一个门面类,整个系统可以有数个门面类。

2、为子系统增加新行为

初学者往往以为通过继承一个门面类便可在子系统中加入新的行为,这是错误的。门面模式的用意是为子系统提供一个集中化和简化的沟通管道,而不能向子系统加入新的行为。比如医院中的接待员并不是医护人员,接待员并不能为病人提供医疗服务。

3、Facade有助于建立层次结构的系统,实现了子系统与客户之间的松耦合关系,子系统内部的功能组件往往是紧耦合的。松耦合关系使得子系统的组件变化不会影响到它的客户。Facade消除了复杂的循环依赖关系。这一点在客户程序与子系统分别实现的时候格外重要。

4、从客户程序的角度来看,Facade模式不仅简化了整个组件系统的接口,同时对于组件内部与外部客户程序来说,从某种程度上也达到了一种“解耦”的效果——内部子系统的任何变化不会影响到Facade接口的变化。

外观模式的优点:

  1. 外观模式对客户屏蔽了子系统组件,从而简化了接口,减少了客户处理的对象数目并使子系统的使用更加简单。
  2. 外观模式实现了子系统与客户之间的松耦合关系,而子系统内部的功能组件是紧耦合的。松耦合使得子系统的组件变化不会影响到它的客户。

外观模式的缺点:

  1. 如果增加新的子系统可能需要修改外观类或客户端的源代码,这样就违背了”开——闭原则“(不过这点也是不可避免)。

在以下情况下可以考虑使用外观模式:

  1. 外一个复杂的子系统提供一个简单的接口
  2. 提供子系统的独立性
  3. 在层次化结构中,可以使用外观模式定义系统中每一层的入口。其中三层架构就是这样的一个例子。

四、.NET 中外观模式的实现

外观模式在FCL里面运用还是很多的,多数情况是单个类的情况,在Asp.Net里面,有很多复合控件,比如:Login控件,可以登录,可以认证,可以保存登录用户信息。其实,外观模式更多的是应用在业务系统当中,效果更好。

五、总结

这个模式很简单,就不说了,就稍微做一下小结。Facade设计模式更注重从架构的层次去看整个系统,而不是单个类的层次。Facade很多时候更是一种架构设计模式。注意区分Facade模式、Adapter模式、Bridge模式与Decorator模式:

  • Facade模式注重简化接口
  • Adapter模式注重转换接口
  • Bridge模式注重分离接口(抽象)与其实现
  • Decorator模式注重稳定接口的前提下为对象扩展功能

本文链接: https://netlover.cn/2018/08/19/csharp-facade-pattern.html

C#设计模式(09) - 组合模式(Composite Pattern)

一、引言

今天我们要讲【结构型】设计模式的第四个模式,该模式是【组合模式】,英文名称是:Composite Pattern。当我们谈到这个模式的时候,有一个物件和这个模式很像,也符合这个模式要表达的意思,那就是“俄罗斯套娃”。“俄罗斯套娃”就是大的瓷器娃娃里面装着一个小的瓷器娃娃,小的瓷器娃娃里面再装着更小的瓷器娃娃,直到最后一个不能再装更小的瓷器娃娃的那个瓷器娃娃为止(有点绕,下面我会配图,一看就明白)。在我们的操作系统中有文件夹的概念,文件夹可以包含文件夹,可以嵌套多层,最里面包含的是文件,这个概念和“俄罗斯套娃”很像。当然还有很多的例子,例如我们使用系统的时候,会使用到“系统菜单”,这个东西是树形结构。这些例子包含的这些东西或者说是对象,可以分为两类,一类是:容器对象,可以包含其他的子对象;另一类是:叶子对象,这类对象是不能在包含其他对象的对象了。在软件设计中,我们该怎么处理这种情况呢?是每类对象分别对待,还是提供一个统一的操作方式呢。组合模式给我们提供了一种解决此类问题的一个途径,接下来我们就好好的介绍一下“组合模式”吧。

二、组合模式的详细介绍

2.1、动机(Motivate)

客户代码过多地依赖于对象容器(对象容器是对象的容器,细细评味)复杂的内部实现结构,对象容器内部实现结构(而非抽象接口)的变化将引起客户代码的频繁变化,带来了代码的维护性、扩展性等方面的弊端。如何将“客户代码与复杂的对象容器结构”解耦?如何让对象容器自己来实现自身的复杂结构,从而使得客户代码就像处理简单对象一样来处理复杂的对象容器?

2.2、意图(Intent)

将对象组合成树形结构以表示“部分-整体”的层次结构。Composite使得用户对单个对象和组合对象的使用具有一致性。   —— 《设计模式》GoF

2.3、结构图(Structure)

2.4、模式的组成

组合模式中涉及到三个角色:

  1. 抽象构件角色(Component):这是一个抽象角色,它给参加组合的对象定义出了公共的接口及默认行为,可以用来管理所有的子对象(在透明式的组合模式是这样的)。在安全式的组合模式里,构件角色并不定义出管理子对象的方法,这一定义由树枝结构对象给出。
  2. 树叶构件角色(Leaf):树叶对象是没有下级子对象的对象,定义出参加组合的原始对象的行为。(原始对象的行为可以理解为没有容器对象管理子对象的方法,或者 【原始对象行为】+【管理子对象的行为(Add,Remove等)】=面对客户代码的接口行为集合)
  3. 树枝构件角色(Composite):代表参加组合的有下级子对象的对象,树枝对象给出所有管理子对象的方法实现,如Add、Remove等。

组合模式实现的最关键的地方是——简单对象和复合对象必须实现相同的接口。这就是组合模式能够将组合对象和简单对象进行一致处理的原因。

2.5、组合模式的具体代码实现

组合模式有两种实现方式,一种是:透明式的组合模式,另外一种是:安全式的组合模式。在这里我就详细说一下何为“透明式”,何为“安全式”。所谓透明式是指“抽象构件角色”定义的接口行为集合包含两个部分,一部分是叶子对象本身所包含的行为(比如Operation),另外一部分是容器对象本身所包含的管理子对象的行为(Add,Remove)。这个抽象构件必须同时包含这两类对象所有的行为,客户端代码才会透明的使用,无论调用容器对象还是叶子对象,接口方法都是一样的,这就是透明,针对客户端代码的透明,但是也有他自己的问题,叶子对象不会包含自己的子对象,为什么要有Add,Remove等类似方法呢,调用叶子对象这样的方法可能(注意:我这里说的是可能,因为有些人会把这些方法实现为空,不做任何动作,当然也不会有异常抛出了,不要抬杠)会抛出异常,这样就不安全了,然后人们就提出了“安全式的组合模式”。所谓安全式是指“抽象构件角色”只定义叶子对象的方法,确切的说这个抽象构件只定义两类对象共有的行为,然后容器对象的方法定义在“树枝构件角色”上,这样叶子对象有叶子对象的方法,容器对象有容器对象的方法,这样责任很明确,当然调用肯定不会抛出异常了。大家可以根据自己的情况自行选择是实现为“透视式”还是“安全式”的,以下我们会针对这两种情况都有实现,具体实现如下:

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
namespace 透明式的组合模式的实现
{
/// <summary>
/// 该抽象类就是文件夹抽象接口的定义,该类型就相当于是抽象构件Component类型
/// </summary>
public abstract class Folder
{
//增加文件夹或文件
public abstract void Add(Folder folder);

//删除文件夹或者文件
public abstract void Remove(Folder folder);

//打开文件或者文件夹--该操作相当于Component类型的Operation方法
public abstract void Open();
}

/// <summary>
/// 该Word文档类就是叶子构件的定义,该类型就相当于是Leaf类型,不能在包含子对象
/// </summary>
public sealed class Word : Folder
{
//增加文件夹或文件
public override void Add(Folder folder)
{
throw new Exception("Word文档不具有该功能");
}

//删除文件夹或者文件
public override void Remove(Folder folder)
{
throw new Exception("Word文档不具有该功能");
}

//打开文件--该操作相当于Component类型的Operation方法
public override void Open()
{
Console.WriteLine("打开Word文档,开始进行编辑");
}
}

/// <summary>
/// SonFolder类型就是树枝构件,由于我们使用的是“透明式”,所以Add,Remove都是从Folder类型继承下来的
/// </summary>
public class SonFolder : Folder
{
//增加文件夹或文件
public override void Add(Folder folder)
{
Console.WriteLine("文件或者文件夹已经增加成功");
}

//删除文件夹或者文件
public override void Remove(Folder folder)
{
Console.WriteLine("文件或者文件夹已经删除成功");
}

//打开文件夹--该操作相当于Component类型的Operation方法
public override void Open()
{
Console.WriteLine("已经打开当前文件夹");
}
}

public class Program
{
static void Main()
{

Folder myword = new Word();

myword.Open();//打开文件,处理文件

myword.Add(new SonFolder());//抛出异常
myword.Remove(new SonFolder());//抛出异常


Folder myfolder = new SonFolder();
myfolder.Open();//打开文件夹

myfolder.Add(new SonFolder());//成功增加文件或者文件夹
myfolder.Remove(new SonFolder());//成功删除文件或者文件夹

Console.Read();
}
}
}

以上代码就是“透明式的组合模式”实现,以下代码就是“安全式的组合模式”实现:

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
namespace 安全式的组合模式的实现
{
/// <summary>
/// 该抽象类就是文件夹抽象接口的定义,该类型就相当于是抽象构件Component类型
/// </summary>
public abstract class Folder //该类型少了容器对象管理子对象的方法的定义,换了地方,在树枝构件也就是SonFolder类型
{
//打开文件或者文件夹--该操作相当于Component类型的Operation方法
public abstract void Open();
}

/// <summary>
/// 该Word文档类就是叶子构件的定义,该类型就相当于是Leaf类型,不能在包含子对象
/// </summary>
public sealed class Word : Folder //这类型现在很干净
{
//打开文件--该操作相当于Component类型的Operation方法
public override void Open()
{
Console.WriteLine("打开Word文档,开始进行编辑");
}
}

/// <summary>
/// SonFolder类型就是树枝构件,现在由于我们使用的是“安全式”,所以Add,Remove都是从此处开始定义的
/// </summary>
public abstract class SonFolder : Folder //这里可以是抽象接口,可以自己根据自己的情况而定
{
//增加文件夹或文件
public abstract void Add(Folder folder);

//删除文件夹或者文件
public abstract void Remove(Folder folder);

//打开文件夹--该操作相当于Component类型的Operation方法
public override void Open()
{
Console.WriteLine("已经打开当前文件夹");
}
}

/// <summary>
/// NextFolder类型就是树枝构件的实现类
/// </summary>
public sealed class NextFolder : SonFolder
{
//增加文件夹或文件
public override void Add(Folder folder)
{
Console.WriteLine("文件或者文件夹已经增加成功");
}

//删除文件夹或者文件
public override void Remove(Folder folder)
{
Console.WriteLine("文件或者文件夹已经删除成功");
}

//打开文件夹--该操作相当于Component类型的Operation方法
public override void Open()
{
Console.WriteLine("已经打开当前文件夹");
}
}

public class Program
{
static void Main()
{
//这是安全的组合模式
Folder myword = new Word();

myword.Open();//打开文件,处理文件


Folder myfolder = new NextFolder();
myfolder.Open();//打开文件夹

//此处要是用增加和删除功能,需要转型的操作,否则不能使用
((SonFolder)myfolder).Add(new NextFolder());//成功增加文件或者文件夹
((SonFolder)myfolder).Remove(new NextFolder());//成功删除文件或者文件夹

Console.Read();
}
}
}

这个模式不是很难,仔细体会实现关键点,最重要理解模式的意图,结合结构图,大家好好体会一下。

三、组合模式的实现要点:

  1. Composite模式采用树形结构来实现普遍存在的对象容器,从而将“一对多”的关系转化为“一对一”的关系,使得客户代码可以一致地处理对象和对象容器,无需关心处理的是单个的对象,还是组合的对象容器。
  2. 将“客户代码与复杂的对象容器结构”解耦是Composite模式的核心思想,解耦之后,客户代码将与纯粹的抽象接口——而非对象容器的复杂内部实现结构——发生依赖关系,从而更能“应对变化”。
  3. Composite模式中,是将“Add和Remove等和对象容器相关的方法”定义在“表示抽象对象的Component类”中,还是将其定义在“表示对象容器的Composite类”中,是一个关乎“透明性”和“安全性”的两难问题,需要仔细权衡。这里有可能违背面向对象的“单一职责原则”,但是对于这种特殊结构,这又是必须付出的代价。ASP.Net控件的实现在这方面为我们提供了一个很好的示范。
  4. Composite模式在具体实现中,可以让父对象中的子对象反向追朔;如果父对象有频繁的遍历需求,可使用缓存技巧来改善效率。

组合模式的优点:

  1. 组合模式使得客户端代码可以一致地处理对象和对象容器,无需关系处理的单个对象,还是组合的对象容器。
  2. 将”客户代码与复杂的对象容器结构“解耦。
  3. 可以更容易地往组合对象中加入新的构件。

组合模式的缺点:

  1. 使得设计更加复杂。客户端需要花更多时间理清类之间的层次关系。(这个是几乎所有设计模式所面临的问题)。

在以下情况下应该考虑使用组合模式:

  1. 需要表示一个对象整体或部分的层次结构。
  2. 希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象。

四、.NET 中组合模式的实现

其实组合模式在FCL里面运用还是很多的,不知道大家是不是有所感觉,这个模式大多数是运用在控件上或者是和界面操作、展示相关的操作上。这个模式在.NET 中最典型的应用就是应用与WinForms和Web的开发中,在.NET类库中,都为这两个平台提供了很多现有的控件,然而System.Windows.Forms.dll中System.Windows.Forms.Control类就应用了组合模式,因为控件包括Label、TextBox等这样的简单控件,这些控件可以理解为叶子对象,同时也包括GroupBox、DataGrid这样复合的控件或者叫容器控件,每个控件都需要调用OnPaint方法来进行控件显示,为了表示这种对象之间整体与部分的层次结构,微软把Control类的实现应用了组合模式(确切地说应用了透明式的组合模式)。

五、总结

我写文章,怎么也要3个小时,也要读上好几遍,防止有错字错句的出现。我也想把握的理解更好融进我写的文章中,但是能力有限,欢迎大家来批评指正,我也会从中收益。今天的文章就写到这里了,模式这个东西就像“独孤九剑”,不要死记硬背,要多看看别人的,多写写代码,要理解场景和意图,多写多练吧,你就有可能成为一代大侠。模式学无止境,我也是刚刚开始。

本文链接: https://netlover.cn/2018/08/19/csharp-composite-pattern.html

C#设计模式(05) - 原型模式(Prototype Pattern)

一、引言

在开始今天的文章之前先说明一点,欢迎大家来指正。很多人说原型设计模式会节省机器内存,他们说是拷贝出来的对象,这些对象其实都是原型的复制,不会使用内存。我认为这是不对的,因为拷贝出来的每一个对象都是实际存在的,每个对象都有自己的独立内存地址,都会被GC回收。如果就浅拷贝来说,可能会公用一些字段,深拷贝是不会的,所以说原型设计模式会提高内存使用率,不一定。具体还要看当时的设计,如果拷贝出来的对象缓存了,每次使用的是缓存的拷贝对象,那就另当别论了,再说该模式本身解决的不是内存使用率的问题。

现在说说原型模式的要解决的问题吧,在软件系统中,当创建一个类的实例的过程很昂贵或很复杂,并且我们需要创建多个这样类的实例时,如果我们用new操作符去创建这样的类实例,这就会增加创建类的复杂度和创建过程与客户代码复杂的耦合度。如果采用工厂模式来创建这样的实例对象的话,随着产品类的不断增加,导致子类的数量不断增多,也导致了相应工厂类的增加,维护的代码维度增加了,因为有产品和工厂两个维度了,反而增加了系统复杂程度,所以在这里使用工厂模式来封装类创建过程并不合适。由于每个类实例都是相同的,这个相同指的是类型相同,但是每个实例的状态参数会有不同,如果状态数值也相同就没意义了,有一个这样的对象就可以了。当我们需要多个相同的类实例时,可以通过对原来对象拷贝一份来完成创建,这个思路正是原型模式的实现方式。

二、原型模式的详细介绍

2.1、动机(Motivate)

在软件系统中,经常面临着“某些结构复杂的对象”的创建工作;由于需求的变化,这些对象经常面临着剧烈的变化,但是它们却拥有比较稳定一致的接口。如何应对这种变化?如何向“客户程序(使用这些对象的程序)”隔离出“这些易变对象”,从而使得“依赖这些易变对象的客户程序”不随着需求改变而改变?

2.2、意图(Intent)

使用原型实例指定创建对象的种类,然后通过拷贝这些原型来创建新的对象。 –《设计模式》Gof

2.3、结构图(Structure)

2.4、模式的组成

可以看出,在原型模式的结构图有以下角色:

  1. 原型类(Prototype):原型类,声明一个Clone自身的接口;
  2. 具体原型类(ConcretePrototype):实现一个Clone自身的操作。

在原型模式中,Prototype通常提供一个包含Clone方法的接口,具体的原型ConcretePrototype使用Clone方法完成对象的创建。

2.5 原型模式的具体实现

《大话西游之大圣娶亲》这部电影,没看过的人不多吧,里面有这样一个场景。牛魔王使用无敌牛虱大战至尊宝,至尊宝的应对之策就是,从脑后把下一撮猴毛,吹了口仙气,无数猴子猴孙现身,来大战牛魔王的无敌牛虱。至尊宝的猴子猴孙就是该原型模式的最好体现。至尊宝创建自己的一个副本,不用还要重新孕育五百年,然后出世,再学艺,最后来和老牛大战,估计黄花菜都凉了。他有3根救命猴毛,轻轻一吹,想要多少个自己就有多少个,方便,快捷。

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
/// <summary>
/// 原型设计模式,每个具体原型是一类对象的原始对象,通过每个原型对象克隆出来的对象也可以进行设置,在原型的基础之上丰富克隆出来的对象,所以要设计好抽象原型的接口
/// </summary>
namespace 设计模式之原型模式
{
/// <summary>
/// 客户类
/// </summary>
class Customer
{
static void Main(string[] args)
{
Prototype xingZheSun = new XingZheSunPrototype();
Prototype xingZheSun2 = xingZheSun.Clone();
Prototype xingZheSun3 = xingZheSun.Clone();

Prototype sunXingZhe = new SunXingZhePrototype();
Prototype sunXingZhe2 = sunXingZhe.Clone();
Prototype sunXingZhe3 = sunXingZhe.Clone();
Prototype sunXingZhe4 = sunXingZhe.Clone();
Prototype sunXingZhe5 = sunXingZhe.Clone();

//1号孙行者打妖怪
sunXingZhe.Fight();
//2号孙行者去化缘
sunXingZhe2.BegAlms();

//战斗和化缘也可以分类,比如化缘,可以分:水果类化缘,饭食类化缘;战斗可以分为:天界宠物下界成妖的战斗,自然修炼成妖的战斗,大家可以自己去想吧,原型模式还是很有用的

Console.Read();
}
}

/// <summary>
/// 抽象原型,定义了原型本身所具有特征和动作,该类型就是至尊宝
/// </summary>
public abstract class Prototype
{
// 战斗--保护师傅
public abstract void Fight();
// 化缘--不要饿着师傅
public abstract void BegAlms();

// 吹口仙气--变化一个自己出来
public abstract Prototype Clone();
}

/// <summary>
/// 具体原型,例如:行者孙,他只负责化斋饭食和与天界宠物下界的妖怪的战斗
/// </summary>
public sealed class XingZheSunPrototype:Prototype
{
// 战斗--保护师傅--与自然修炼成妖的战斗
public override void Fight()
{
Console.WriteLine("腾云驾雾,各种武艺");
}
// 化缘--不要饿着师傅--饭食类
public override void BegAlms()
{
Console.WriteLine("什么都能要来");
}

// 吹口仙气--变化一个自己出来
public override Prototype Clone()
{
return (XingZheSunPrototype)this.MemberwiseClone();
}
}

/// <summary>
/// 具体原型,例如:孙行者,他只负责与自然界修炼成妖的战斗和化斋水果
/// </summary>
public sealed class SunXingZhePrototype : Prototype
{
// 战斗--保护师傅-与天界宠物战斗
public override void Fight()
{
Console.WriteLine("腾云驾雾,各种武艺");
}
// 化缘--不要饿着师傅---水果类
public override void BegAlms()
{
Console.WriteLine("什么都能要来");
}

// 吹口仙气--变化一个自己出来
public override Prototype Clone()
{
return (SunXingZhePrototype)this.MemberwiseClone();
}
}
}

上面代码中都有详细的注释代码,这里就不过多解释。

三、原型模式的实现要点:

Prototype模式同样用于隔离类对象的使用者和具体类型(易变类)之间的耦合关系,它同样要求这些“易变类”拥有“稳定的接口”。

Prototype模式对于“如何创建易变类的实体对象”(创建型模式除了Singleton模式以外,都是用于解决创建易变类的实体对象的问题的)采用“原型克隆”的方法来做,它使得我们可以非常灵活地动态创建“拥有某些稳定接口”的新对象——所需工作仅仅是注册一个新类的对象(即原型),然后在任何需要的地方不断地Clone。

Prototype模式中的Clone方法可以利用.NET中的Object类的MemberwiseClone()方法或者序列化来实现深拷贝。

原型模式的优点:

  1. 原型模式向客户隐藏了创建新实例的复杂性
  2. 原型模式允许动态增加或较少产品类。
  3. 原型模式简化了实例的创建结构,工厂方法模式需要有一个与产品类等级结构相同的等级结构,而原型模式不需要这样。
  4. 产品类不需要事先确定产品的等级结构,因为原型模式适用于任何的等级结构

原型模式的缺点:

  1. 每个类必须配备一个克隆方法
  2. 配备克隆方法需要对类的功能进行通盘考虑,这对于全新的类不是很难,但对于已有的类不一定很容易,特别当一个类引用不支持串行化的间接对象,或者引用含有循环结构的时候。

原型模式使用的场景:

  1. 资源优化场景:类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等。
  2. 性能和安全要求的场景:通过new产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式。
  3. 一个对象多个修改者的场景:一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用。

在实际项目中,原型模式很少单独出现,一般是和工厂方法模式一起出现,通过clone的方法创建一个对象,然后由工厂方法提供给调用者。

四、.NET 中原型模式的实现

在.NET中,微软已经为我们提供了原型模式的接口实现,该接口就是ICloneable,其实这个接口就是抽象原型,提供克隆方法,相当于与上面代码中Prototype抽象类,其中的Clone()方法来实现原型模式,如果我们想我们自定义的类具有克隆的功能,首先定义类实现ICloneable接口的Clone方法。其实在.NET中实现了ICloneable接口的类有很多,如下图所示(图中只截取了部分,可以用ILSpy反编译工具进行查看):

1
2
3
4
5
6
7
8
namespace System
{
[ComVisible(true)]
public interface ICloneable
{
object Clone();
}
}

在Net的FCL里面实现ICloneable接口的类如图,自己可以去查看每个类自己的实现,在此就不贴出来了。

五、总结

到今天为止,所有的创建型设计模式就写完了。学习设计模式应该是有一个循序渐进的过程,当我们写代码的时候不要一上来就用什么设计模式,而是通过重构来使用设计模式。创建型的设计模式写完了,我们就总结一下。Singleton单件模式解决的是实体对象个数的问题。除了Singleton之外,其他创建型模式解决的都是new所带来的耦合关系。 Factory Method,Abstract Factory,Builder都需要一个额外的工厂类来负责实例化“易变对象”,而Prototype则是通过原型(一个特殊的工厂类)来克隆“易变对象”。(其实原型就是一个特殊的工厂类,它只是把工厂和实体对象耦合在一起了)。如果遇到“易变类”,起初的设计通常从Factory Method开始,当遇到更多的复杂变化时,再考虑重构为其他三种工厂模式(Abstract Factory,Builder,Prototype)。

一般来说,如果可以使用Factory Method,那么一定可以使用Prototype。但是Prototype的使用情况一般是在类比较容易克隆的条件之上,如果是每个类实现比较简单,都可以只用实现MemberwiseClone,没有引用类型的深拷贝,那么就更适合了。

本文链接: https://netlover.cn/2018/08/18/csharp-prototype-pattern.html

C#设计模式(04) - 建造者模式(Builder Pattern)

一、引言

今天我们要讲讲Builder模式,也就是建造者模式,当然也有叫生成器模式的,英文名称是Builder Pattern。在现实生活中,我们经常会遇到一些构成比较复杂的物品,比如:电脑,它就是一个复杂的物品,它主要是由CPU、主板、硬盘、显卡、机箱等组装而成的。手机当然也是复杂物品,由主板,各种芯片,RAM 和ROM 摄像头之类的东西组成。但是无论是电脑还是手机,他们的组装过程是固定的,就拿手机来说,组装流水线是固定的,不变的,但是把不同的主板和其他组件组装在一起就会生产出不同型号的手机。那么在软件系统中是不是也会存在这样的对象呢?答案是肯定的。在软件系统中我们也会遇到类似的复杂对象,并且这个复杂对象的各个部分按照一定的算法组合在一起,此时该对象的创建工作就可以使用Builder模式了,下面我就来详细看看这个模式吧。

二、建造者模式的详细介绍

2.1、动机(Motivate)

在软件系统中,有时候面临着“一个复杂对象”的创建工作,其通常由各个部分的子对象用一定的算法构成;由于需求的变化,这个复杂对象的各个部分经常面临着剧烈的变化,但是将它们组合在一起的算法却相对稳定。如何应对这种变化?如何提供一种“封装机制”来隔离出“复杂对象的各个部分”的变化,从而保持系统中的“稳定构建算法”不随着需求改变而改变?

2.2、意图(Intent)

将一个复杂对象的构建与其表示相分离,使得同样的构建过程可以创建不同的表示。 ——《设计模式》GoF

2.3、结构图(Structure)

2.4、模式的组成

1、抽象建造者角色(Builder):为创建一个Product对象的各个部件指定抽象接口,以规范产品对象的各个组成成分的建造。一般而言,此角色规定要实现复杂对象的哪些部分的创建,并不涉及具体的对象部件的创建。

2、具体建造者(ConcreteBuilder)

  • 实现Builder的接口以构造和装配该产品的各个部件。即实现抽象建造者角色Builder的方法。
  • 定义并明确它所创建的表示,即针对不同的商业逻辑,具体化复杂对象的各部分的创建
  • 提供一个检索产品的接口
  • 构造一个使用Builder接口的对象即在指导者的调用下创建产品实例

3、指导者(Director):调用具体建造者角色以创建产品对象的各个部分。指导者并没有涉及具体产品类的信息,真正拥有具体产品的信息是具体建造者对象。它只负责保证对象各部分完整创建或按某种顺序创建。

4、产品角色(Product):建造中的复杂对象。它要包含那些定义组件的类,包括将这些组件装配成产品的接口。

2.5 建筑者模式的具体实现

现在人们生活水平都提高了,家家都有了家庭轿车,那今天我们就以汽车组装为例来说明Builder模式的实现。

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
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;


/// <summary>
/// 现在人们的生活水平都提高了,有钱了,我今天就以汽车组装为例子
/// 每台汽车的组装过程都是一致的,所以我们使用同样的构建过程可以创建不同的表示(即可以组装成不同型号的汽车,不能像例子这样,一会别克,一会奥迪的)
/// 组装汽车、电脑、手机、电视等等负责对象的这些场景都可以应用建造者模式来设计
/// </summary>
namespace 设计模式之建造者模式
{
/// <summary>
/// 客户类
/// </summary>
class Customer
{
static void Main(string[] args)
{
Director director = new Director();
Builder buickCarBuilder = new BuickBuilder();
Builder aoDiCarBuilder = new AoDiBuilder();

director.Construct(buickCarBuilder);

//组装完成,我来驾驶别克了
Car buickCar = buickCarBuilder.GetCar();
buickCar.Show();

// 我老婆就要奥迪了,她比较喜欢大品牌
director.Construct(aoDiCarBuilder);
Car aoDiCar = aoDiCarBuilder.GetCar();
aoDiCar.Show();

Console.Read();
}
}

/// <summary>
/// 这个类型才是组装的,Construct方法里面的实现就是创建复杂对象固定算法的实现,该算法是固定的,或者说是相对稳定的
/// 这个人当然就是老板了,也就是建造者模式中的指挥者
/// </summary>
public class Director
{
// 组装汽车
public void Construct(Builder builder)
{
builder.BuildCarDoor();
builder.BuildCarWheel();
builder.BuildCarEngine();
}
}

/// <summary>
/// 汽车类
/// </summary>
public sealed class Car
{
// 汽车部件集合
private IList<string> parts = new List<string>();

// 把单个部件添加到汽车部件集合中
public void Add(string part)
{
parts.Add(part);
}

public void Show()
{
Console.WriteLine("汽车开始在组装.......");
foreach (string part in parts)
{
Console.WriteLine("组件" + part + "已装好");
}

Console.WriteLine("汽车组装好了");
}
}

/// <summary>
/// 抽象建造者,它定义了要创建什么部件和最后创建的结果,但是不是组装的的类型,切记
/// </summary>
public abstract class Builder
{
// 创建车门
public abstract void BuildCarDoor();
// 创建车轮
public abstract void BuildCarWheel();
//创建车引擎
public abstract void BuildCarEngine();
// 当然还有部件,大灯、方向盘等,这里就省略了

// 获得组装好的汽车
public abstract Car GetCar();
}

/// <summary>
/// 具体创建者,具体的车型的创建者,例如:别克
/// </summary>
public sealed class BuickBuilder : Builder
{
Car buickCar = new Car();
public override void BuildCarDoor()
{
buickCar.Add("Buick's Door");
}

public override void BuildCarWheel()
{
buickCar.Add("Buick's Wheel");
}

public override void BuildCarEngine()
{
buickCar.Add("Buick's Engine");
}

public override Car GetCar()
{
return buickCar;
}
}

/// <summary>
/// 具体创建者,具体的车型的创建者,例如:奥迪
/// </summary>
public sealed class AoDiBuilder : Builder
{
Car aoDiCar = new Car();
public override void BuildCarDoor()
{
aoDiCar.Add("Aodi's Door");
}

public override void BuildCarWheel()
{
aoDiCar.Add("Aodi's Wheel");
}

public override void BuildCarEngine()
{
aoDiCar.Add("Aodi's Engine");
}

public override Car GetCar()
{
return aoDiCar;
}
}
}

上面代码中都有详细的注释代码,这里就不过多解释。

三、建造者模式的实现要点

在建造者模式中,指挥者是直接与客户端打交道的,指挥者将客户端创建产品的请求划分为对各个部件的建造请求,再将这些请求委派到具体建造者角色,具体建造者角色是完成具体产品的构建工作的,却不为客户所知道。 建造者模式主要用于“分步骤来构建一个复杂的对象”,其中“分步骤”是一个固定的组合过程,而复杂对象的各个部分是经常变化的。 产品不需要抽象类,由于建造模式的创建出来的最终产品可能差异很大,所以不大可能提炼出一个抽象产品类。 在前面文章中介绍的抽象工厂模式解决了“系列产品”的需求变化,而建造者模式解决的是 “产品部分” 的需要变化。 由于建造者隐藏了具体产品的组装过程,所以要改变一个产品的内部表示,只需要再实现一个具体的建造者就可以了,从而能很好地应对产品组成组件的需求变化。

建造者模式的优点:

  1. 使用建造者模式可以使客户端不必知道产品内部组成的细节。
  2. 具体的建造者类之间是相互独立的,容易扩展。
  3. 由于具体的建造者是独立的,因此可以对建造过程逐步细化,而不对其他的模块产生任何影响。

建造者模式的缺点:

产生多余的Build对象以及Dirextor类。

创建者模式的使用场景:

  1. 当创建复杂对象的算法应该独立于该对象的组成部分以及它们的装配方式时。
  2. 相同的方法,不同的执行顺序,产生不同的事件结果时。
  3. 多个部件或零件,都可以装配到一个对象中,但是产生的运行结果又不相同时。
  4. 产品类非常复杂,或者产品类中的调用顺序不同产生了不同的效能。
  5. 创建一些复杂的对象时,这些对象的内部组成构件间的建造顺序是稳定的,但是对象的内部组成构件面临着复杂的变化。

四、.NET 中建造者模式的实现

在微软的类库里面大量使用了设计模式,如果要想学习设计模式,仔细看看微软的类库是很有帮助的。今天的设计模式在FCL里面也有实现,该类型的名字就是System.Text.StringBuilder(存在mscorlib.dll程序集中),它就是一个建造者模式的实现,从名称也可以看出来。不过它的实现属于建造者模式的演化,此时的建造者模式没有指挥者角色和抽象建造者角色,StringBuilder类即扮演着具体建造者的角色,也同时扮演了指挥者和抽象建造者的角色,StringBuilder类扮演着建造string对象的具体建造者角色,其中的ToString()方法用来返回具体产品给客户端(相当于上面代码中GetProduct方法)。其中Append方法用来创建产品的组件(相当于上面代码中BuildPartA和BuildPartB方法),因为string对象中每个组件都是字符,所以也就不需要指挥者的角色的代码(指的是Construct方法,用来调用创建每个组件的方法来完成整个产品的组装),因为string字符串对象中每个组件都是一样的,都是字符,所以Append方法也充当了指挥者Construct方法的作用。

五、总结

今天就到这里了,还需要重申的是,学习设计模式不能死学,就像StringBuilder一样,他和Gof23种设计模式中定义的情形有很大的不同,但是它也是Builder模式,因为它们要解决的问题和使用场景是吻合的。我们写代码的时候,不要太居于形式,要看使用的契机和模式是否吻合,根据具体的情况我们的模式也会发生变化。当我们看得越多,写的越多时候,你的变化就越自然了。

本文链接: https://netlover.cn/2018/08/18/csharp-builder-pattern.html

C#设计模式(03) - 抽象工厂模式(Abstract Factory Pattern)

一、引言

上一篇文章我们讲了【工厂方法】模式,它是为了解决【简单工厂】模式所面对的问题,它的问题就是:如果我们增加新的产品,工厂类的方法就要修改本身的代码,增加产品越多,其逻辑越复杂,同时这样的修改也是不符合【开放关闭原则OCP】,对修改代码关闭,对增加代码开放。为了解决【简单工厂】的问题,我们引出了【工厂方法】模式,通过子类化工厂类,解决了工厂类责任的划分,产品和相应的工厂一一对应,符合了OCP。如果我们要设计一套房子,当然我们知道房子是由房顶、地板、窗户、房门组成的,别的组件暂时省略,先设计一套古典风格的房子,再创建一套现代风格的房子,再创建一套欧式风格的房子,这么多套房子,我们该怎么办呢?今天我们要讲的【抽象工厂】模式可以很好的解决多套变化的问题。

二、抽象工厂详细介绍

2.1、动机(Motivate):

在软件系统中,经常面临着”一系统相互依赖的对象”的创建工作:同时,由于需求的变化,往往存在更多系列对象的创建工作。如何应对这种变化?如何绕过常规的对象创建方法(new),提供一种”封装机制”来避免客户程序和这种”多系列具体对象创建工作”的紧耦合?

2.2、意图(Intent):

提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。 ——《设计模式》GoF

2.3、结构图(Structure)

该图是抽象工厂的UML图,结合抽象工厂的意图、动机和图示来理解该模式,今天我们就以建设房子为例来说明抽象工厂的实现机理。

2.4、模式的组成

可以看出,在抽象工厂模式的结构图有以下角色:

  1. 抽象产品类角色(AbstractProduct):为抽象工厂中相互依赖的每种产品定义抽象接口对象,也可以这样说,有几种产品,就要声明几个抽象角色,每一个抽象产品角色和一种具体的产品相匹配。
  2. 具体产品类(ConcreteProduct):具体产品类实现了抽象产品类,是针对某个具体产品的实现的类型。
  3. 抽象工厂类角色(Abstract Factory):定义了创建一组相互依赖的产品对象的接口操作,每种操作和每种产品一一对应。
  4. 具体工厂类角色(ConcreteFactory):实现抽象类里面的所有抽象接口操作,可以创建某系列具体的产品,这些具体的产品是“抽象产品类角色”的子类。
2.5、抽象工厂的具体代码实现

随着我们年龄的增大,我们也到了结婚的年龄。结婚首要的问题就是房子的问题,假设我有一个很有钱的爸爸,哈哈,有钱可以解决很多问题。作为长子的我,希望能有一套欧式风格的房子,再加上田园风光,此生足矣。我弟弟就不一样了,他想要一套现代样式的房子,如果兄弟姊妹再多年一点,那就有更多的要求了。由于房子由房顶、地板、窗户和房门组成,其他组件暂时省略,有这么多套房子要建设,每套房子的房顶、地板、窗户和房门都是一个体系的,那就让我们看看如何使用【抽象工厂】模式来实现不同房屋的建造。

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
/// <summary>
/// 下面以不同系列房屋的建造为例子演示抽象工厂模式
/// 因为每个人的喜好不一样,我喜欢欧式的,我弟弟就喜欢现代的
/// 客户端调用
/// </summary>
class Client
{
static void Main(string[] args)
{
// 哥哥的欧式风格的房子
AbstractFactory europeanFactory= new EuropeanFactory();
europeanFactory.CreateRoof().Create();
europeanFactory.CreateFloor().Create();
europeanFactory.CreateWindow().Create();
europeanFactory.CreateDoor().Create();


//弟弟的现代风格的房子
AbstractFactory modernizationFactory = new ModernizationFactory();
modernizationFactory.CreateRoof().Create();
modernizationFactory.CreateFloor().Create();
modernizationFactory.CreateWindow().Create();
modernizationFactory.CreateDoor().Create();
Console.Read();
}
}

/// <summary>
/// 抽象工厂类,提供创建不同类型房子的接口
/// </summary>
public abstract class AbstractFactory
{
// 抽象工厂提供创建一系列产品的接口,这里作为例子,只给出了房顶、地板、窗户和房门创建接口
public abstract Roof CreateRoof();
public abstract Floor CreateFloor();
public abstract Window CreateWindow();
public abstract Door CreateDoor();
}

/// <summary>
/// 欧式风格房子的工厂,负责创建欧式风格的房子
/// </summary>
public class EuropeanFactory : AbstractFactory
{
// 制作欧式房顶
public override Roof CreateRoof()
{
return new EuropeanRoof();
}

// 制作欧式地板
public override Floor CreateFloor()
{
return new EuropeanFloor();
}

// 制作欧式窗户
public override Window CreateWindow()
{
return new EuropeanWindow();
}

// 制作欧式房门
public override Door CreateDoor()
{
return new EuropeanDoor();
}
}

/// <summary>
/// 现在风格房子的工厂,负责创建现代风格的房子
/// </summary>
public class ModernizationFactory : AbstractFactory
{
// 制作现代房顶
public override Roof CreateRoof()
{
return new ModernizationRoof();
}

// 制作现代地板
public override Floor CreateFloor()
{
return new ModernizationFloor();
}

// 制作现代窗户
public override Window CreateWindow()
{
return new ModernizationWindow();
}

// 制作现代房门
public override Door CreateDoor()
{
return new ModernizationDoor();
}
}

/// <summary>
/// 房顶抽象类,子类的房顶必须继承该类
/// </summary>
public abstract class Roof
{
/// <summary>
/// 创建房顶
/// </summary>
public abstract void Create();
}

/// <summary>
/// 地板抽象类,子类的地板必须继承该类
/// </summary>
public abstract class Floor
{
/// <summary>
/// 创建地板
/// </summary>
public abstract void Create();
}

/// <summary>
/// 窗户抽象类,子类的窗户必须继承该类
/// </summary>
public abstract class Window
{
/// <summary>
/// 创建窗户
/// </summary>
public abstract void Create();
}

/// <summary>
/// 房门抽象类,子类的房门必须继承该类
/// </summary>
public abstract class Door
{
/// <summary>
/// 创建房门
/// </summary>
public abstract void Create();
}

/// <summary>
/// 欧式地板类
/// </summary>
public class EuropeanFloor : Floor
{
public override void Create()
{
Console.WriteLine("创建欧式的地板");
}
}


/// <summary>
/// 欧式的房顶
/// </summary>
public class EuropeanRoof : Roof
{
public override void Create()
{
Console.WriteLine("创建欧式的房顶");
}
}


/// <summary>
///欧式的窗户
/// </summary>
public class EuropeanWindow : Window
{
public override void Create()
{
Console.WriteLine("创建欧式的窗户");
}
}


/// <summary>
/// 欧式的房门
/// </summary>
public class EuropeanDoor : Door
{
public override void Create()
{
Console.WriteLine("创建欧式的房门");
}
}

/// <summary>
/// 现代的房顶
/// </summary>
public class ModernizationRoof : Roof
{
public override void Create()
{
Console.WriteLine("创建现代的房顶");
}
}

/// <summary>
/// 现代的地板
/// </summary>
public class ModernizationFloor : Floor
{
public override void Create()
{
Console.WriteLine("创建现代的地板");
}
}

/// <summary>
/// 现代的窗户
/// </summary>
public class ModernizationWindow : Window
{
public override void Create()
{
Console.WriteLine("创建现代的窗户");
}
}

/// <summary>
/// 现代的房门
/// </summary>
public class ModernizationDoor : Door
{
public override void Create()
{
Console.WriteLine("创建现代的房门");
}
}
2.6、 抽象工厂应对需求变更

让我们看看该模式如何应对需求的变化,假设我的表弟一看我们的房子很好,他也想要一套古典风格的房子(哈哈,这个家伙事挺多的,有好事总是落不下他)。

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
/// <summary>
///先为表弟的房子来建立一个工厂类吧
/// </summary>
public class ClassicalFactory : AbstractFactory
{
//创建房顶
public override Roof CreateRoof()
{
return new ClassicalRoof();
}

// 创建地板
public override Floor CreateFloor()
{
return new ClassicalFloor();
}

// 创建窗户
public override Window CreateWindow()
{
return new ClassicalWindow();
}

// 创建房门
public override Door CreateDoor()
{
return new ClassicalDoor();
}
}

/// <summary>
///古典的房顶
/// </summary>
public class ClassicalRoof : Roof
{
public override void Create()
{
Console.WriteLine("创建古典的房顶");
}
}

/// <summary>
/// 古典的地板
/// </summary>
public class ClassicalFloor : Floor
{
public override void Create()
{
Console.WriteLine("创建古典的地板");
}
}

/// <summary>
/// 古典的窗户
/// </summary>
public class ClassicalWindow : Window
{
public override void Create()
{
Console.WriteLine("创建古典的窗户");
}
}

/// <summary>
/// 古典的房门
/// </summary>
public class ClassicalDoor: Door
{
public override void Create()
{
Console.WriteLine("创建古典的房门");
}
}

此时,只需要添加五个类:一个是古典风格工厂类,负责创建古典风格的房子,另外几个类是具有古典风格的房顶、地板、窗户和房门的具体产品。从上面代码看出,抽象工厂对于系列产品的变化支持 “开放——封闭”原则(指的是要求系统对扩展开放,对修改封闭),扩展起来非常简便,但是,抽象工厂对于增加新产品这种情况就不支持”开放——封闭 “原则,因为要修改创建系列产品的抽象基类AbstractFactory,增加相应产品的创建方法,这也是抽象工厂的缺点所在。

三、抽象工厂的实现要点

  1. 如果没有应对“多系列对象创建”的需求变化,则没有必要使用AbstractFactory模式,这时候使用简单的静态工厂完全可以。
  2. “系列对象”指的是这些对象之间有相互依赖、或作用的关系,例如游戏开发场景中“道路”与“房屋”的依赖,“道路”与“地道”的依赖。
  3. AbstractFactory模式主要在于应对“新系列”的需求变动。其缺点在于难以应对“新对象”的需求变动。
  4. AbstractFactory模式经常喝FactoryMethod模式共同组合来应对“对象创建”的需求变化。

抽象工厂模式的优点:

【抽象工厂】模式将系列产品的创建工作延迟到具体工厂的子类中,我们声明工厂类变量的时候是使用的抽象类型,同理,我们使用产品类型也是抽象类型,这样做就尽可能的可以减少客户端代码与具体产品类之间的依赖,从而降低了系统的耦合度。耦合度降低了,对于后期的维护和扩展就更有利,这也就是【抽象工厂】模式的优点所在。可能有人会说在Main方法里面(这里的代码就是客户端的使用方)还是会使用具体的工厂类,对的。这个其实我们通过Net的配置,把这部分移出去,最后把依赖关系放到配置文件中。如果有新的需求我们只需要修改配置文件,根本就不需要修改代码了,让客户代码更稳定。依赖关系肯定会存在,我们要做的就是降低依赖,想完全去除很难,也不现实。

抽象工厂模式的缺点:

有优点肯定就有缺点,因为每种模式都有他的使用范围,或者说要解决的问题,不能解决的问题就是缺点了,其实也不能叫缺点了。【抽象工厂】模式很难支持增加新产品的变化,这是因为抽象工厂接口中已经确定了可以被创建的产品集合,如果需要添加新产品,此时就必须去修改抽象工厂的接口,这样就涉及到抽象工厂类的以及所有子类的改变,这样也就违背了“开发——封闭”原则。

抽象工厂模式的使用场景:

如果系统需要多套的代码解决方案,并且每套的代码方案中又有很多相互关联的产品类型,并且在系统中我们可以相互替换的使用一套产品的时候可以使用该模式,客户端不需要依赖具体实现。

四、.NET中抽象工厂模式实现

微软的类库发展了这么多年,设计模式在里面有大量的应用,【抽象工厂】模式在.NET类库中也存在着大量的使用,比如和操作数据库有关的类型,这个类就是System.Data.Common.DbProviderFactory,这个类位于System.Data.dll程序集中。该类扮演抽象工厂模式中抽象工厂的角色,我们可以用ILSpy反编译工具查看该类的实现:

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
/// 扮演抽象工厂的角色
/// 创建连接数据库时所需要的对象集合,
/// 这个对象集合包括有 DbConnection对象(这个是抽象产品类,如绝味例子中的YaBo类)、DbCommand类、DbDataAdapter类,针对不同的具体工厂都需要实现该抽象类中方法,
public abstract class DbProviderFactory
{
public virtual bool CanCreateDataSourceEnumerator
{
get
{
return false;
}
}

public virtual DbCommand CreateCommand()
{
return null;
}

public virtual DbCommandBuilder CreateCommandBuilder()
{
return null;
}

public virtual DbConnection CreateConnection()
{
return null;
}

public virtual DbConnectionStringBuilder CreateConnectionStringBuilder()
{
return null;
}

public virtual DbDataAdapter CreateDataAdapter()
{
return null;
}

public virtual DbParameter CreateParameter()
{
return null;
}

public virtual CodeAccessPermission CreatePermission(PermissionState state)
{
return null;
}

public virtual DbDataSourceEnumerator CreateDataSourceEnumerator()
{
return null;
}
}

DbProviderFactory类是一个抽象工厂类,该类提供了创建数据库连接时所需要的对象集合的接口,实际创建的工作在其子类工厂中进行,微软使用的是SQL Server数据库,因此提供了连接SQL Server数据的具体工厂实现,具体代码可以用反编译工具查看,具体代码如下:

SqlClientFactory扮演着具体工厂的角色,用来创建连接SQL Server数据所需要的对象

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
public sealed class SqlClientFactory : DbProviderFactory, IServiceProvider
{
public static readonly SqlClientFactory Instance = new SqlClientFactory();

public override bool CanCreateDataSourceEnumerator
{
get
{
return true;
}
}

private SqlClientFactory()
{
}

public override DbCommand CreateCommand()
{
return new SqlCommand();
}

public override DbCommandBuilder CreateCommandBuilder()
{
return new SqlCommandBuilder();
}

public override DbConnection CreateConnection()
{
return new SqlConnection();
}

public override DbConnectionStringBuilder CreateConnectionStringBuilder()
{
return new SqlConnectionStringBuilder();
}

public override DbDataAdapter CreateDataAdapter()
{
return new SqlDataAdapter();
}

public override DbParameter CreateParameter()
{
return new SqlParameter();
}

public override CodeAccessPermission CreatePermission(PermissionState state)
{
return new SqlClientPermission(state);
}

public override DbDataSourceEnumerator CreateDataSourceEnumerator()
{
return SqlDataSourceEnumerator.Instance;
}

object IServiceProvider.GetService(Type serviceType)
{
object result = null;
if (serviceType == GreenMethods.SystemDataCommonDbProviderServices_Type)
{
result = GreenMethods.SystemDataSqlClientSqlProviderServices_Instance();
}
return result;
}
}

OdbcFactory也是具体工厂类

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
public sealed class OdbcFactory : DbProviderFactory
{
public static readonly OdbcFactory Instance = new OdbcFactory();

private OdbcFactory()
{
}

public override DbCommand CreateCommand()
{
return new OdbcCommand();
}

public override DbCommandBuilder CreateCommandBuilder()
{
return new OdbcCommandBuilder();
}

public override DbConnection CreateConnection()
{
return new OdbcConnection();
}

public override DbConnectionStringBuilder CreateConnectionStringBuilder()
{
return new OdbcConnectionStringBuilder();
}

public override DbDataAdapter CreateDataAdapter()
{
return new OdbcDataAdapter();
}

public override DbParameter CreateParameter()
{
return new OdbcParameter();
}

public override CodeAccessPermission CreatePermission(PermissionState state)
{
return new OdbcPermission(state);
}
}

当然,我们也有OleDbFactory 类型,都是负责具体的数据库操作。DbProviderFactory就是【抽象工厂】模式UML里面AbstractFactory类型。其他具体的工厂类型继承DbProviderFactory类型,这个结构很简单,我就不画图了。

五、总结

终于写完了,写了3个小时,学习设计模式不能死学,要把握核心点和使用场景。关键点第一是,面向对象设计模式的基本原则,有了原则,考虑问题就不会跑偏,然后再仔细把握每种模式的使用场景和要解决的问题,多写写代码,多看看Net的类库,它是最好的教材。

本文链接: https://netlover.cn/2018/08/18/csharp-abstract-factory-pattern.html

C#设计模式(02) - 工厂方法模式(Factory Method Pattern)

一、引言

在上一篇文章中我们讲解了过渡的一种模式叫做【简单工厂】,也有叫【静态工厂】的,通过对简单工厂模式得了解,我们也发现了它的缺点,就是随着需求的变化我们要不停地修改工厂里面的方法的代码,需求变化越多,里面的If–Else–也越多,这样就会造成简单工厂的实现逻辑过于复杂。设计模式是遵循一定原则而得来的,比如,我们要怎么增加代码,怎么修改代码,不是想怎么来就怎么来的,其中一个原则就是OCP原则,中文是【开放关闭原则】,对增加代码开发,对修改代码关闭,所以我们就不能总是这样修改简单工厂里面的方法。本章介绍的工厂方法模式可以解决简单工厂模式中存在的这个问题,下面就具体看看工厂方法模式是如何解决该问题的。

二、工厂方法模式的胡介绍

2.1、动机(Motivate)

在软件系统的构建过程中,经常面临着“某个对象”的创建工作:由于需求的变化,这个对象(的具体实现)经常面临着剧烈的变化,但是它却拥有比较稳定的接口。

如何应对这种变化?如何提供一种“封装机制”来隔离出“这个易变对象”的变化,从而保持系统中“其他依赖对象的对象”不随着需求改变而改变?

2.2、意图(Intent)

定义一个用于创建对象的接口,让子类决定实例化哪一个类。Factory Method使得一个类的实例化延迟到子类。 –《设计模式》GoF

2.3、结构图(Structure)

2.4、模式的组成

可以看出,在工厂方法模式的结构图有以下角色:

  1. 抽象工厂角色(Creator): 充当抽象工厂角色,定义工厂类所具有的基本的操作,任何具体工厂都必须继承该抽象类。
  2. 具体工厂角色(ConcreteCreator):充当具体工厂角色,该类必须继承抽象工厂角色,实现抽象工厂定义的方法,用来创建具体产品。
  3. 抽象产品角色(Product):充当抽象产品角色,定义了产品类型所有具有的基本操作,具体产品必须继承该抽象类。
  4. 具体产品角色(ConcreteProduct):充当具体产品角色,实现抽象产品类对定义的抽象方法,由具体工厂类创建,它们之间有一一对应的关系。
2.5、工厂方法模式的代码实现

【简单工厂模式】的问题是:如果有新的需求就需要修改工厂类里面创建产品对象实例的那个方法的实现代码,在面向对象设计一个原则就是哪里有变化,我就封装哪里。还有另外两个大的原则,其一是:面向抽象编程,细节和高层实现都要依赖抽象,第二个原则是:多组合,少继承。这三个原则是最根本的原则,学习设计模式必须以这三个原则为基点,否则都是枉然。根据这三大原则又衍生出来6个具体的原则,分别是【单一职责原则】,【里氏替换原则】,【依赖倒置原则】,【接口隔离原则】、【迪米特法则】和【开闭原则】,既然工厂类有变化,我们就封装它,面向抽象编程,我们先抽象出一个工厂基类,然后,每个需求就实现一个具体的工厂类,这样我们就符合了【开闭原则OCP】,让一个工厂生产一款产品,并一一对应。我们把具体产品的创建推迟到子类中,此时工厂类(这类是基类了)不再负责所有产品的创建,而只是给出具体工厂必须实现的接口,这样工厂方法模式就可以允许系统不修改工厂类逻辑的情况下来添加新产品,这样也就克服了简单工厂模式中缺点。下面就是工厂方法模式的实现代码:

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
namespace 设计模式之工厂方法模式
{
/// <summary>
/// 汽车抽象类
/// </summary>
public abstract class Car
{
// 开始行驶
public abstract void Go();
}

/// <summary>
/// 红旗汽车
/// </summary>
public class HongQiCar : Car
{
public override void Go()
{
Console.WriteLine("红旗汽车开始行驶了!");
}
}

/// <summary>
/// 奥迪汽车
/// </summary>
public class AoDiCar : Car
{
public override void Go()
{
Console.WriteLine("奥迪汽车开始行驶了");
}
}

/// <summary>
/// 抽象工厂类
/// </summary>
public abstract class Factory
{
// 工厂方法
public abstract Car CreateCar();
}

/// <summary>
/// 红旗汽车工厂类
/// </summary>
public class HongQiCarFactory:Factory
{
/// <summary>
/// 负责生产红旗汽车
/// </summary>
/// <returns></returns>
public override Car CreateCar()
{
return new HongQiCar();
}
}

/// <summary>
/// 奥迪汽车工厂类
/// </summary>
public class AoDiCarFactory:Factory
{
/// <summary>
/// 负责创建奥迪汽车
/// </summary>
/// <returns></returns>
public override Car CreateCar()
{
return new AoDiCar();
}
}

/// <summary>
/// 客户端调用
/// </summary>
class Client
{
static void Main(string[] args)
{
// 初始化创建汽车的两个工厂
Factory hongQiCarFactory = new HongQiCarFactory();
Factory aoDiCarFactory = new AoDiCarFactory();

// 生产一辆红旗汽车
Car hongQi = hongQiCarFactory.CreateCar();
hongQi.Go();

//生产一辆奥迪汽车
Car aoDi = aoDiCarFactory.CreateCar();
aoDi.Go();

Console.Read();
}
}
}

在【工厂方法模式】中,我们同样也把汽车的类抽象出来一个抽象的基类,这里正好符合了【面向抽象编程】,客户端在使用的时候不会依赖具体的什么汽车。使用工厂方法实现的系统,如果系统需要添加新产品时,我们可以利用多态性来完成系统的扩展,对于抽象工厂类和具体工厂中的代码都不需要做任何改动。例如,我们想增加一辆奔驰车,我们只需从Car抽象类下继承一个BenChiCar类,同时在从Factory抽象基类下继承一个“奔驰”的工厂类BenChinaCarFactory就可以了,这样扩展符合OCP的原则。具体代码为:

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
/// <summary>
/// 奔驰车
/// </summary>
public class BenChiCar : Car
{
/// <summary>
/// 重写抽象类中的方法
/// </summary>
public override void Go()
{
Console.WriteLine("奔驰车开始行驶了!");
}
}

/// <summary>
/// 奔驰车的工厂类
/// </summary>
public class BenChiCarFactory : Factory
{
/// <summary>
/// 负责生产奔驰车
/// </summary>
/// <returns></returns>
public override Car CreateCar()
{
return new BenChiCar();
}
}

/// <summary>
/// 客户端调用
/// </summary>
class Client
{
static void Main(string[] args)
{

// 如果客户又生产一辆奔驰车
// 再另外初始化一个奔驰车的工厂
Factory benChiCarFactory = new BenChiCarFactory();

// 利用奔驰车的工厂生产奔驰车
Car benChi = benChiCarFactory.CreateCar();
benChi.Go();

Console.Read();
}
}

三、Factory Method模式的几个要点

Factory Method模式主要用于隔离类对象的使用者和具体类型之间的耦合关系。面对一个经常变化的具体类型,紧耦合关系会导致软件的脆弱。

Factory Method模式通过面向对象的手法,将所要创建的具体对象工作延迟到子类,从而实现一种扩展(而非更改)的策略,较好地解决了这种紧耦合关系。

  • Factory Method模式解决“单个对象”的需求变化;
  • AbstractFactory模式解决“系列对象”的需求变化;
  • Builder模式解决“对象部分”的需求变化;

工厂方法模式的优点:

  1. 在工厂方法中,用户只需要知道所要产品的具体工厂,无须关系具体的创建过程,甚至不需要具体产品类的类名。
  2. 在系统增加新的产品时,我们只需要添加一个具体产品类和对应的实现工厂,无需对原工厂进行任何修改,很好地符合了“开闭原则”。

工厂方法模式的缺点:

每次增加一个产品时,都需要增加一个具体类和对象实现工厂,是的系统中类的个数成倍增加,在一定程度上增加了系统的复杂度,同时也增加了系统具体类的依赖。这并不是什么好事。

工厂方法模式使用的场景:

  1. 一个类不知道它所需要的对象的类。在工厂方法模式中,我们不需要具体产品的类名,我们只需要知道创建它的具体工厂即可。
  2. 一个类通过其子类来指定创建那个对象。在工厂方法模式中,对于抽象工厂类只需要提供一个创建产品的接口,而由其子类来确定具体要创建的对象,在程序运行时,子类对象将覆盖父类对象,从而使得系统更容易扩展。
  3. 将创建对象的任务委托给多个工厂子类中的某一个,客户端在使用时可以无须关心是哪一个工厂子类创建产品子类,需要时再动态指定。

四、.NET中实现了工厂方法的类

.NET 类库中也有很多实现了工厂方法的类,例如Asp.net中,处理程序对象是具体用来处理请求,当我们请求一个.aspx的文件时,此时会映射到System.Web.UI.PageHandlerFactory类上进行处理,而对.ashx的请求将映射到System.Web.UI.SimpleHandlerFactory类中(这两个类都是继承于IHttpHandlerFactory接口的),关于这点说明我们可以在“C:\Windows\Microsoft.NET\Framework\v4.0.30319\Config\Web.Config”文件中找到相关定义,具体定义如下:

1
2
3
<add path="*.axd" verb="*" type="System.Web.HttpNotFoundHandler" validate="True"/>
<add path="*.aspx" verb="*" type="System.Web.UI.PageHandlerFactory" validate="True"/>
<add path="*.ashx" verb="*" type="System.Web.UI.SimpleHandlerFactory" validate="True"/>

配置文件截图了一部分,有时间大家可以自己去研究一下。
下面我们就具体看下工厂方法模式在Asp.net中是如何实现的,如果对一个Index.aspx页面发出请求时,将会调用PageHandlerFactory中GetHandler方法来创建一个Index.aspx对象,它们之间的类图关系如下:

五、总结

每种模式都有自己的使用场合,切记,如果使用错误,还不如不用。工厂方法模式通过面向对象编程中的多态性来将对象的创建延迟到具体工厂中,从而解决了简单工厂模式中存在的问题,也很好地符合了开放封闭原则(即对扩展开发,对修改封闭)。

学习设计模式我们一定要谨记设计模式的几大原则,否则是徒劳无功的。就像学务工一样,我们要记心法。6大原则就像孤独求败的独孤九剑的剑诀,学会了,变化无穷。

本文链接: https://netlover.cn/2018/08/18/csharp-factory-method-pattern.html