Merkle树

1、在分布式系统、P2P应用中或者是区块链中,会经常使用一种数据结构Merkle tree(默克尔树),这里我们将详细讨论一下这个常用数据结构。Merkle treeMerkle树看起来非常像二叉树,其叶子节点上的值通常为数据块的哈希值,而非叶子节点上的值,所以有时候Merkle tree也表示为Hash tree,如下图所示:
merkle1

2、在构造Merkle树时,首先要对数据块计算哈希值,通常,选用SHA-256等哈希算法。但如果仅仅防止数据不是蓄意的损坏或篡改,可以改用一些安全性低但效率高的校验和算法,如CRC。

3、然后将数据块计算的哈希值两两配对(如果是奇数个数,最后一个自己与自己配对),计算上一层哈希,再重复这个步骤,一直到计算出根哈希值。Merkle树大多用来进行完整性验证,比如分布式环境下,从多台主机获取数据,怎么验证获取的数据是否正确呢,只要验证Merkle树根哈希一致,即可。例如,下图中L3数据块发生错误(比如数据被修改了),错误会传导到计算hash(L3),接着传导到计算hash(Hash1-0+Hash1-1),最后传导到根哈希,导致根哈希的不一致,可以说,任何底层数据块的变化,最终都会传导到根哈希。另外如果根哈希不一致,也可以通过Merkle树快速定位到导致不一致的数据。

4、Merkle树还可以用来对数据进行快速比对,快速定位到不一致的数据。比如分布式存储中,一份数据会有多个副本,并且分布在不同的机器上。为了保持数据一致性,需要进行副本同步,而首要的就是比对当前副本是否一致,如一致,则无需同步,如不一致,还需找出不一致的地方,然后进行同步。很明显,如果采用直接传输数据进行比对,非常低效,一般采用对数据进行哈希,传输哈希值进行对比的方法。为此,可以对每台机器需要比对的数据构造Merkle树,如果根哈希一致,则数据相同,如果根哈希不一致,则通过Merkle树快速检索到不一致的数据。下面举例说明快速检索的过程,如上图蓝色标注所示。假设两台机器中L3数据块不一致,我们对比根哈希,发现根哈希不一致,即,数据不一致,此时需要找出是那一块不一致,分别对比Hash0和Hash1,发现是Hash1不一致,接着向下发现是Hash1-0不一致,这样就定位到是L3数据块不一致。定位过程的算法复杂度为O(log(n))。

还有一种数据结构,在一定程度上可以看做是Merkle树的子树,但又不完全一样,这个数据结构是Hash list(为了避免中文哈希列表与哈希表的误解,这里使用英文名称),我们下面看一下这个Hash list。
在点对点网络中数据传输的时候,为了提高效率往往会同时从多个机器下载数据的不同部分,即,不是从一台机器下载整个数据,而是将完整数据分成不同的部分,分别同时从不同的机器获取完整数据的不同组成部分。这样分块传输不但可以同时从多台机器下载数据,另一个好处是如果这一小块数据传输过程中损坏了,只要重新下载这一小数据块就可以了,不用重新下载整个数据。但这种分布式环境下,很多机器应该认为是不稳定或者不可信的,如何校验整个数据的完整性及每一小数据块的完整性呢?
merkle2

5、为了校验每一个数据块,我们需要对每个数据块做哈希,形成一个哈希列表,这样进行下载前,我们先要获取一个哈希列表,下载后,我们就能够通过哈希列表,来验证每一个数据块。哪怎么保证这个哈希列表是正确的呢,或者说怎么校验完整数据呢?只要每一个数据块哈希是正确的,最终获取的完整数据就一定是正确的,所以,我们需要对哈希列表进行哈希得到根哈希,将此根哈希放到一个可信源中,在下载数据前,先从可信数据源哪里获取到数据的跟哈希,然后从任意机器获取哈希列表,再下载数据块。这样,数据完整性可以通过根哈希来保证。

Merkle tree 对比 Hash list

两种数据结构都有验证数据完整性的功能,都可以通过根哈希保证整体数据完整性。所不同的是,在数据庞大,数据块非常多的情况下,当根哈希检测到数据不一致时,Merkle tree可以快速的定位到导致不一致的数据块,复杂度为O(log(n)),而Hash list只能遍历庞大的哈希列表定位到导致不一致的数据块,复杂度为O(n),很显然,此时Merkle tree的效率要高很多。

区块链小白书

区块链小白书

1、交易过程

假设网络中有如下四个节点,且每个节点拥有的如下币值
link1

1、没有可信任的第三方后,每个人互相之间无法信任,如果要创建一个交易订单(转账),需要:
1、发广播,让所有人都知道网络里每一个人每一笔钱的来龙去脉
例:B向C转账100BTC
1)则B需要发广播通知网络中的每一个人,“大家好,我是B,我要向C转账100BTC,我的电子签名是xxxxxxx”
link2

2)然后网络中的每一个人都收到这个广播
link3

3)网络中的每个节点会通过电子签名验证这个交易是否是我发起的
link4

4)验证通过后网络中每个节点把这笔交易记在账本上,B就少100,C多100
link5

5)网络中的每个节点怎么知道我真的有100BTC呢?
每个节点的账本会帮助确认,这个账本即是区块,把区块连起来就是区块链

6)在比特币系统中,区块链记录了比特币从创立至今所有的交易记录,现在大概有60万个区块,每个区块里记录了两三千笔交易,包括网络中每一个节点有多少钱,从哪里来,花到哪儿去,都记录的一清二楚,透明公开。

7)在区块链网络里,每个节点都拿着一份相同且实时更新的账本。当B广播说要给C转账100BTC时,每个节点手里的账本就开始回溯,检查B是否有100BTC。如果没有,转账无效。

8)账本的可靠性是数字货币的基石,如果账本出了问题,什么币都不好使。这就引出两个新问题:

  • 谁来给大家记账
  • 怎么保证账本不被造假

9)如果每个人都能记账,那每个区块里包含的交易和交易顺序可能都不一样,如果有故意记假账的,那就更乱了,不可能得到一个大家都接受的账本,所以,记账的人得让所有人都能接受,这样大家的账本才能统一,这被称为共识机制

10)今天各种区块链有各种不同的共识机制。

  • GateChain:Pos
  • EOS:dPos
  • NEO:dBFT
  • 中本聪提出的比特币网络使用的是做题,谁先把答案算出来谁就有权力记账,这一机制被称为PoW(Proof-of-Work),即工作量证明。

11)工作量证明的本质是穷举,你的设备算力越强,算出答案的可能性就越高,为了做到这一点,需要用到哈希加密。以SHA256算法为例

12)任何一串字符用它加密后,都能得到一串独一无二的256位的二进制数
sha5
而原输入只要有任何一点轻微改动,哈希加密后的数字都会完全不同。

13)打开一个区块,我们可以看到一个区块的区块头,交易数量,交易详情等信息
link6
14)区块头是一个区块的标签,包含了时间戳,Merkle根数树根哈希值,随机数和上一个区块的哈希值等信息
link7

1
2
3
4
5
6
1、随机数:为了找到满足难度目标所设定的随机数。
为了解决32位随机数在算力飞升的情况下不够用的问题。规定时间戳和 COINBASE交易信息均可更改,一次扩展nonce的位数

2、时间戳:该区块产生的近似时间,精确到秒的UNIX时间戳,必须严格大于前11个区块时间的中值,同时全节点也会拒绝那些超出自己2个小时时间戳的区块。

3、Merkle根:该区块交易的Merkle树根的哈希值。同样采用SHA256(SHA256())计算。

15)而把区块头做二次SHA256计算,就能得到这个区块的哈希值
link8

16)想要记账,就得把区块里的各种信息打包好,
link9

在修改区块头里的这个随机数,让输入值能够在哈希计算后,得到一个前n个数都是0的哈希值
link10

17)每一位有0和1两种可能,每改变一次随机数成功的概率为1/2的n次方(1/2^n),比如你 n=1,则第一位为0就行,成功率为1/2。
link11

18)而网络里参与的算力越强,要算的0就越多,工作量证明的难度越大,今天比特币网络的n大概是76,成功率为1/2^76,差不多是1/756万亿亿,
link12

用一块8000元的RTX2080Ti显卡,大概要算1407年。

19)要算对确实不容易,但只要你算出来了,所有人就可以瞬间验证你算得是否正确。如果确实没问题,大家就会把这个区块连接在账本上,开始打包计算下一个区块。这样,网络里的所有人就有一份相同且实时更新的账本了。
link13

20)而为了让大家用动力做题记账,第一个完成区块打包的节点,会获得系统奖励,现在是12.5个比特币,差不多是60万人民币,这个过程也被称为挖矿
link14

21)另一方面,为了防止账本被篡改,每一个新加入的区块,都需要在区块头里,记录上一个区块的哈希值,也被称为哈希指针
link15

这样一个不断向前的指针,最终会指向第一个创始区块,把所有的区块紧紧连接在一起。

22)如果你修改任何一个区块里的任何一个字符,都会改变这个区块的哈希值,让下一个区块的哈希指针失效,所以你必须修改下一个区块的哈希指针,但这又会影响这个区块的哈希值,所以还需要重新计算随机数。完成计算后,还得接着计算这个区块的下一个区块,直到修改完这个区块后的所有区块,非常麻烦。
link16

23)这样即使记账人想造假,也是做不到的,因为有电子签名,记账人不能伪造别人给自己的转账。又因为历史账本的存在,也不能凭空变出一笔钱来。
link17

24)但这就引出一个新问题,如果两个人同时完成了计算,打包出了一个新区快,那该听谁的呢?答案是,谁长听谁的(最长链原则)。现在所有人都可以在这两个区块后面接着打包,比如下一轮先完成计算的人选择接在B上,则B链就更长了,其他人也会愿意接在B后面。一般情况下,打包6块之内就能分出胜负。被废弃的链上的交易会被撤回,重新放到交易池等待打包。
link18

25)但既然谁长听谁的,只要你比大家都能算,算力大于51%,就能一个人算出最长链,进而控制账本。
link19

所以比特币世界里的矿工算力越强,大家要算的0就越多,保证谁都不能控制记账权。
link20

26)但参与者不多的其他区块链就不好说了,比如2018年5月15日,一个叫比特黄金的数字货币就遭遇了51%攻击。攻击者先是把自己价值1000万美金的比特黄金转给交易所,这笔转账被记录在区块A上,
link21

同时攻击者秘密准备了一个这笔转账没有发生的区块B,
link22

同时在区块B之后计算新的区块,等A链上的转账确认后,攻击者就可以把在交易所的的比特黄金提现,但因为攻击者的算力大于全网的51%,B链的长度终将大于A链,这是只要向全网发布更长的B链,
link23

历史就会被改写,B链会替代A链成为真正主链,而区块A里转给交易所转账也会被撤回,攻击者白赚1000万。
link24

27)今天,对于没有算力的普通人,获得数字货币最简单的办法就是去交易所购买,再提现到钱包地址,
link25

这个地址来自于你的私钥,私钥加密后会得到公钥,公钥加密后会得到地址。
link26

28)在区块链这样的匿网络里,只有私钥才能证明你是你,只要转账时附上你私钥生成的电子签名,大家就能确认这笔转账有效。
link27

如果私钥泄露,谁都可以冒充你把钱转走。比如2013年一位 叫Adam的男士在电视直播里收到了相当于今天1500元的比特币,在他开心的向镜头展示私钥后,这笔钱就被当场偷走了。

注:整理自 回形针 区块链科普视频

链队列

参考:https://www.cnblogs.com/lixiaolun/p/4646312.html

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
60
61
62
package Queue;

public class LinkQueue {

Node front;
Node rear;
Node head;

public void init() {

head=new Node();
front=new Node();
rear=new Node();

front=head;
rear=head;

}

//入队
public void enQueue(Object e) {
Node node=new Node();
node.data=e;

//第一次入队
if(rear==head) {
head.next=node;
front.next=node;
rear=node;
}else {
rear.next=node;
rear=node;
}
}

public void deQueue() {
if(rear==head) {
//队列为空
return;
}else {
//队中只有一个元素
if(front.next==rear) {
rear=front;
}else {
front.next=front.next.next;
}
}
}

public void printQueue() {
Node temp=head;
while(temp!=rear) {
System.out.println(temp.next.data);
temp=temp.next;
}
}

class Node{
Object data;
Node next;
}
}

2、测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package Queue;

public class TestQueue {

public static void main(String[] args) {
// TODO Auto-generated method stub

LinkQueue qu=new LinkQueue();
qu.init();
qu.enQueue("ddd");
qu.enQueue(8);
qu.enQueue("ct");
qu.enQueue(99);
//qu.deQueue();
qu.printQueue();
}

}

链栈

参考:https://www.cnblogs.com/lixiaolun/p/4644141.html

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
package linkStack;

public class LinkStack {

Node base;
Node top;

public void init() {
base=new Node();
top=new Node();

base.data=null;
base.next=null;

top.data=null;
top.next=base;
}

//入栈
public void push(String e) {
Node node=new Node();
node.data=e;

//第一次入栈
if(top.next==base) {
node.next=base;
top.next=node;
}else {
node.next=top.next;
top.next=node;
}
}

//出栈
public void pop() {
if(top.next==base)
{
System.out.println("栈中没有元素!");
}else
{
//System.out.println("出栈操作"+top.next.data);
top.next=top.next.next;
}
}

public void printNode() {
Node temp=top;
while(temp.next!=base) {
temp=temp.next;
System.out.println(temp.data);
}
}

class Node{
String data;
Node next;
}
}

2、测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package linkStack;

public class TestStack {

public static void main(String[] args) {
// TODO Auto-generated method stub

LinkStack stack=new LinkStack();
stack.init();
stack.push("abcd");
stack.push("fff");
stack.push("gggg");
stack.push("eeee");
stack.pop();
stack.printNode();
}

}

选择排序

1、选择排序

表现最稳定的排序算法之一,因为无论什么数据进去都是O(n2)的时间复杂度,所以用到它的时候,数据规模越小越好。唯一的好处可能就是不占用额外的内存空间了吧。理论上讲,选择排序可能也是平时排序一般人想到的最多的排序方法了吧。
选择排序(Selection-sort)是一种简单直观的排序算法。它的工作原理:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。


2、时间复杂度

最佳情况:T(n) = O(n) 最差情况:T(n) = O(n^2) 平均情况:T(n) = O(n^2)


3、动态演示

Image text

4、算法实现

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
package sort;

public class SelectionSort {

public static void main(String[] args) {
// TODO Auto-generated method stub
int [] a= {99,45,67,12,84,59,32};
selectionSort(a);
printArr(a);
}

public static void selectionSort(int [] arr) {
//选择小的,从小排到大
int min;//min记录最小值的下标

for(int i=0;i<arr.length-1;i++) {//比arr.length-1趟
min=i;
for(int j=i;j<arr.length;j++) {
if(arr[j]<arr[min]) {
min=j;//记录下标
}
}

//把最小的排到数组前
int temp=arr[i];
arr[i]=arr[min];
arr[min]=temp;
}
}

public static void printArr(int [] a) {
for(int i=0;i<a.length;i++) {
System.out.print(a[i]+" ");
}
}
}

设计模式-适配器模式

适配器模式

适配器模式(Adapter Pattern)是作为两个不兼容的接口之间的桥梁。这种类型的设计模式属于结构型模式,它结合了两个独立接口的功能。
这种模式涉及到一个单一的类,该类负责加入独立的或不兼容的接口功能。

介绍

意图:

将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。

主要解决:

主要解决在软件系统中,常常要将一些”现存的对象”放到新的环境中,而新环境要求的接口是现对象不能满足的

关键代码:

适配器继承或依赖已有的对象,实现想要的目标接口。

应用实例:

1、读卡器是作为内存卡和笔记本之间的适配器。您将内存卡插入读卡器,再将读卡器插入笔记本,这样就可以通过笔记本来读取内存卡。
2、美国电器 110V,中国 220V,就要有一个适配器将 110V 转化为 220V。
3、在 LINUX 上运行 WINDOWS 程序。
4、JAVA 中的 jdbc。

优点:

1、可以让任何两个没有关联的类一起运行。
2、提高了类的复用。
3、增加了类的透明度。
4、灵活性好。

缺点:

1、过多地使用适配器,会让系统非常零乱,不易整体进行把握。比如,明明看到调用的是 A 接口,其实内部被适配成了 B接口的实现,一个系统如果太多出现这种情况,
无异于一场灾难。因此如果不是很有必要,可以不使用适配器,而是直接对系统进行重构。
2.由于 JAVA 至多继承一个类,所以至多只能适配一个适配者类,而且目标类必须是抽象类。

使用场景:

有动机地修改一个正常运行的系统的接口,这时应该考虑使用适配器模式。

注意事项:

适配器不是在详细设计时添加的,而是解决正在服役的项目的问题。

实现

1)有一个 MediaPlayer 接口

2)一个实现了 MediaPlayer 接口的实体类AudioPlayer。
默认情况下,AudioPlayer 可以播放 mp3 格式的音频文件。

3)另一个接口 AdvancedMediaPlayer

4)实现了 AdvancedMediaPlayer 接口的实体类VlcPlayer和Mp4Player,可以播放 vlc 和 mp4 格式的文件。

想要让 AudioPlayer 播放其他格式的音频文件。为了实现这个功能,
5)需要创建一个实现了 MediaPlayer 接口的适配器类 MediaAdapter,
并使用 AdvancedMediaPlayer 对象来播放所需的格式。AudioPlayer 使用适配器类 MediaAdapter 传递所需的音频类型,不需要知道能播放所需格式音频的实际类。

6)AdapterPatternDemo,演示类,使用 AudioPlayer 类来播放各种格式。

Image text

1、创建接口MediaPlayer和AdvancedMediaPlayer。

1
2
3
public interface MediaPlayer {
public void play(String audioType, String fileName);
}

1
2
3
4
public interface AdvancedMediaPlayer { 
public void playVlc(String fileName);
public void playMp4(String fileName);
}

2、创建实现了 AdvancedMediaPlayer 接口的实体类VlcPlayer和Mp4Player。

1
2
3
4
5
6
7
8
9
10
11
public class VlcPlayer implements AdvancedMediaPlayer{
@Override
public void playVlc(String fileName) {
System.out.println("Playing vlc file. Name: "+ fileName);
}

@Override
public void playMp4(String fileName) {
//什么也不做
}
}

1
2
3
4
5
6
7
8
9
10
11
12
public class Mp4Player implements AdvancedMediaPlayer{

@Override
public void playVlc(String fileName) {
//什么也不做
}

@Override
public void playMp4(String fileName) {
System.out.println("Playing mp4 file. Name: "+ fileName);
}
}

3、创建实现了 MediaPlayer 接口的适配器类MediaAdapter。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class MediaAdapter implements MediaPlayer {

AdvancedMediaPlayer advancedMusicPlayer;

public MediaAdapter(String audioType){
if(audioType.equalsIgnoreCase("vlc") ){
advancedMusicPlayer = new VlcPlayer();
} else if (audioType.equalsIgnoreCase("mp4")){
advancedMusicPlayer = new Mp4Player();
}
}

@Override
public void play(String audioType, String fileName) {
if(audioType.equalsIgnoreCase("vlc")){
advancedMusicPlayer.playVlc(fileName);
}else if(audioType.equalsIgnoreCase("mp4")){
advancedMusicPlayer.playMp4(fileName);
}
}
}

4、创建实现了 MediaPlayer 接口的实体类AudioPlayer。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class AudioPlayer implements MediaPlayer {
MediaAdapter mediaAdapter;

@Override
public void play(String audioType, String fileName) {

//播放 mp3 音乐文件的内置支持
if(audioType.equalsIgnoreCase("mp3")){
System.out.println("Playing mp3 file. Name: "+ fileName);
}
//mediaAdapter 提供了播放其他文件格式的支持
else if(audioType.equalsIgnoreCase("vlc")
|| audioType.equalsIgnoreCase("mp4")){
mediaAdapter = new MediaAdapter(audioType);
mediaAdapter.play(audioType, fileName);
}
else{
System.out.println("Invalid media. "+
audioType + " format not supported");
}
}
}

5、在测试类AdapterPatternDemo中使用 AudioPlayer 来播放不同类型的音频格式。

1
2
3
4
5
6
7
8
9
10
public class AdapterPatternDemo {
public static void main(String[] args) {
AudioPlayer audioPlayer = new AudioPlayer();

audioPlayer.play("mp3", "beyond the horizon.mp3");
audioPlayer.play("mp4", "alone.mp4");
audioPlayer.play("vlc", "far far away.vlc");
audioPlayer.play("avi", "mind me.avi");
}
}

6、输出

1
2
3
4
Playing mp3 file. Name: beyond the horizon.mp3
Playing mp4 file. Name: alone.mp4
Playing vlc file. Name: far far away.vlc
Invalid media. avi format not supported

参考:http://www.runoob.com/design-pattern/adapter-pattern.html

设计模式-观察者模式

观察者模式

当对象间存在一对多关系时,则使用观察者模式(Observer Pattern)。比如,当一个对象被修改时,则会自动通知它的依赖对象。观察者模式属于行为型模式。

介绍

意图:

定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。

主要解决:

一个对象状态改变给其他对象通知的问题,而且要考虑到易用和低耦合,保证高度的协作。

关键代码:

在抽象类里有一个 ArrayList 存放观察者们。

应用实例:

1、拍卖的时候,拍卖师观察最高标价,然后通知给其他竞价者竞价。

优点:

1、观察者和被观察者是抽象耦合的。
2、建立一套触发机制。

缺点:

1、如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。
2、如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。
3、观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。

使用场景:

1、一个抽象模型有两个方面,其中一个方面依赖于另一个方面。将这些方面封装在独立的对象中使它们可以各自独立地改变和复用。
2、一个对象的改变将导致其他一个或多个对象也发生改变,而不知道具体有多少对象将发生改变,可以降低对象之间的耦合度。
3、一个对象必须通知其他对象,而并不知道这些对象是谁。
4、需要在系统中创建一个触发链,A对象的行为将影响B对象,B对象的行为将影响C对象……,可以使用观察者模式创建一种链式触发机制。

注意事项:

1、JAVA 中已经有了对观察者模式的支持类。
2、避免循环引用。
3、如果顺序执行,某一观察者错误会导致系统卡壳,一般采用异步方式。

实现

1)创建 Subject 类

2)创建Observer 抽象类

3)扩展了抽象类 Observer 的实体类。

4)ObserverPatternDemo,演示类,使用 Subject 和实体类对象来演示观察者模式。

Image text

1、创建 Subject 类。

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
import java.util.ArrayList;
import java.util.List;

public class Subject {

private List<Observer> observers
= new ArrayList<Observer>();
private int state;

public int getState() {
return state;
}

public void setState(int state) {
this.state = state;
notifyAllObservers();
}

public void attach(Observer observer){
observers.add(observer);
}

public void notifyAllObservers(){
for (Observer observer : observers) {
observer.update();
}
}
}

2、创建 Observer 抽象类。

1
2
3
4
public abstract class Observer {
protected Subject subject;
public abstract void update();
}

3、创建实体观察者类BinaryObserver、OctalObserver和HexaObserver。

1
2
3
4
5
6
7
8
9
10
11
12
13
public class BinaryObserver extends Observer{

public BinaryObserver(Subject subject){
this.subject = subject;
this.subject.attach(this);
}

@Override
public void update() {
System.out.println( "Binary String: "
+ Integer.toBinaryString( subject.getState() ) );
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
public class OctalObserver extends Observer{

public OctalObserver(Subject subject){
this.subject = subject;
this.subject.attach(this);
}

@Override
public void update() {
System.out.println( "Octal String: "
+ Integer.toOctalString( subject.getState() ) );
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
public class HexaObserver extends Observer{

public HexaObserver(Subject subject){
this.subject = subject;
this.subject.attach(this);
}

@Override
public void update() {
System.out.println( "Hex String: "
+ Integer.toHexString( subject.getState() ).toUpperCase() );
}
}

4、在测试类ObserverPatternDemo中使用 Subject 和实体观察者对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class ObserverPatternDemo {
public static void main(String[] args) {
Subject subject = new Subject();

new HexaObserver(subject);
new OctalObserver(subject);
new BinaryObserver(subject);

System.out.println("First state change: 15");
subject.setState(15);
System.out.println("Second state change: 10");
subject.setState(10);
}
}

5、输出

1
2
3
4
5
6
7
8
First state change: 15
Hex String: F
Octal String: 17
Binary String: 1111
Second state change: 10
Hex String: A
Octal String: 12
Binary String: 1010

参考:http://www.runoob.com/design-pattern/observer-pattern.html

设计模式-策略模式

策略模式

分别封装行为接口,实现算法族,超类里放行为接口对象,在子类里具体设定行为对象。在策略模式(Strategy Pattern)中,一个类的行为或其算法可以在运行时更改。这种类型的设计模式属于行为型模式。

原则就是:

分离变化部分,封装接口,基于接口编程各种功能。
该模式让行为算法的变化独立于算法的使用者。

介绍

意图:

定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换。

关键代码:

实现同一个接口。

应用实例:

1、诸葛亮的锦囊妙计,每一个锦囊就是一个策略。 2、旅行的出游方式,选择骑自行车、坐汽车,每一种旅行方式都是一个策略。
3、JAVA AWT 中的 LayoutManager。

优点:

1、算法可以自由切换。
2、避免使用多重条件判断。
3、扩展性良好。

缺点:

1、策略类会增多。
2、所有策略类都需要对外暴露。

使用场景:

1、如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为。 2、一个系统需要动态地在几种算法中选择一种。
3、如果一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重的条件选择语句来实现。

注意事项:

如果一个系统的策略多于四个,就需要考虑使用混合模式,解决策略类膨胀的问题。

实现

创建一个定义活动的 Strategy 接口和实现了 Strategy 接口的实体策略类。
Context 是一个使用了某种策略的类。
StrategyPatternDemo,演示类使用 Context 和策略对象来演示 Context在它所配置或使用的策略改变时的行为变化。
Image text

1、创建一个接口Strategy。

1
2
3
public interface Strategy {
public int doOperation(int num1, int num2);
}

2、创建实现接口的实体类OperationAdd、OperationSubstract、OperationMultiply。

1
2
3
4
5
6
public class OperationAdd implements Strategy{
@Override
public int doOperation(int num1, int num2) {
return num1 + num2;
}
}

1
2
3
4
5
6
public class OperationSubstract implements Strategy{
@Override
public int doOperation(int num1, int num2) {
return num1 - num2;
}
}
1
2
3
4
5
6
public class OperationMultiply implements Strategy{
@Override
public int doOperation(int num1, int num2) {
return num1 * num2;
}
}

3、创建 Context 类。

1
2
3
4
5
6
7
8
9
10
11
public class Context {
private Strategy strategy;

public Context(Strategy strategy){
this.strategy = strategy;
}

public int executeStrategy(int num1, int num2){
return strategy.doOperation(num1, num2);
}
}

4、使用 Context 来查看当它改变策略 Strategy 时的行为变化。

1
2
3
4
5
6
7
8
9
10
11
12
public class StrategyPatternDemo {
public static void main(String[] args) {
Context context = new Context(new OperationAdd());
System.out.println("10 + 5 = " + context.executeStrategy(10, 5));

context = new Context(new OperationSubstract());
System.out.println("10 - 5 = " + context.executeStrategy(10, 5));

context = new Context(new OperationMultiply());
System.out.println("10 * 5 = " + context.executeStrategy(10, 5));
}
}

5、输出

1
2
3
10 + 5 = 15
10 - 5 = 5
10 * 5 = 50

注意点:

  • 分析项目中变化与不变部分
  • 多用组合少用继承,用行为类组合,不用行为的继承,更有弹性

与状态模式的比较

状态模式的类图和策略模式类似,并且都是能够动态改变对象的行为。但是状态模式是通过状态转移来改变 Context 所组合的 State 对象,而策略模式是通过 Context 本身的决策来改变组合的 Strategy 对象。所谓的状态转移,是指 Context 在运行过程中由于一些条件发生改变而使得 State 对象发生改变,注意必须要是在运行过程中。
状态模式主要是用来解决状态转移的问题,当状态发生转移了,那么 Context 对象就会改变它的行为;而策略模式主要是用来封装一组可以互相替代的算法族,并且可以根据需要动态地去替换 Context 使用的算法。

参考:http://www.runoob.com/design-pattern/strategy-pattern.html

设计模式-装饰器模式

装饰器模式

装饰器模式(Decorator Pattern)
允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装。
这种模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。

介绍

意图:

动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式相比生成子类更为灵活

主要解决:

一般的,我们为了扩展一个类经常使用继承方式实现,由于继承为类引入静态特征,并且随着扩展功能的增多,子类会很膨胀。

关键代码:

1、Component 类充当抽象角色,不应该具体实现。
2、修饰类引用和继承 Component 类,具体扩展类重写父类方法。

应用实例:

1、孙悟空有 72 变,当他变成”庙宇”后,他的根本还是一只猴子,但是他又有了庙宇的功能
2、不论一幅画有没有画框都可以挂在墙上,但是通常都是有画框的,并且实际上是画框被挂在墙上。在挂在墙上之前,画可以被蒙上玻璃,装到框子里;这时画、玻璃和画框形成了一个物体。

优点:

装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。

缺点:

多层装饰比较复杂。

使用场景:

1、扩展一个类的功能。
2、动态增加功能,动态撤销。

注意事项:

可代替继承。

实现

1)创建一个 Shape 接口

2)创建实现了Shape 接口的实现类Rectangle和Circle。

3)创建一个实现了 Shape 接口的抽象装饰类 ShapeDecorator,并把 Shape 对象作为它的实例变量。

4)RedShapeDecorator 是实现了 ShapeDecorator 的实体类。

5)DecoratorPatternDemo,演示类,使用 RedShapeDecorator 来装饰 Shape 对象。

Image text

1、创建接口Shape

1
2
3
public interface Shape {
void draw();
}

2、创建接口的实现类Rectangle和Circle

1
2
3
4
5
6
7
public class Rectangle implements Shape {

@Override
public void draw() {
System.out.println("Shape: Rectangle");
}
}

1
2
3
4
5
6
7
public class Circle implements Shape {

@Override
public void draw() {
System.out.println("Shape: Circle");
}
}

3、创建实现了 Shape 接口的抽象装饰类ShapeDecorator。

1
2
3
4
5
6
7
8
9
10
11
public abstract class ShapeDecorator implements Shape {
protected Shape decoratedShape;

public ShapeDecorator(Shape decoratedShape){
this.decoratedShape = decoratedShape;
}

public void draw(){
decoratedShape.draw();
}
}

4、创建扩展了 ShapeDecorator 类的实体装饰类RedShapeDecorator。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class RedShapeDecorator extends ShapeDecorator {

public RedShapeDecorator(Shape decoratedShape) {
super(decoratedShape);
}

@Override
public void draw() {
decoratedShape.draw();
setRedBorder(decoratedShape);
}

private void setRedBorder(Shape decoratedShape){
System.out.println("Border Color: Red");
}
}

5、在测试类DecoratorPatternDemo中使用 RedShapeDecorator 来装饰 Shape 对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class DecoratorPatternDemo {
public static void main(String[] args) {

Shape circle = new Circle();

Shape redCircle = new RedShapeDecorator(new Circle());

Shape redRectangle = new RedShapeDecorator(new Rectangle());
System.out.println("Circle with normal border");
circle.draw();

System.out.println("\nCircle of red border");
redCircle.draw();

System.out.println("\nRectangle of red border");
redRectangle.draw();
}
}

6、输出

1
2
3
4
5
6
7
8
9
10
Circle with normal border
Shape: Circle

Circle of red border
Shape: Circle
Border Color: Red

Rectangle of red border
Shape: Rectangle
Border Color: Red

参考:https://www.zybuluo.com/mdeditor#1212370

设计模式-模板模式

模板模式

在模板模式(Template Pattern)中,一个抽象类公开定义了执行它的方法的方式/模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。这种类型的设计模式属于行为型模式。

介绍

意图:

定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。

主要解决:

一些方法通用,却在每一个子类都重新写了这一方法。

关键代码:

在抽象类实现,其他步骤在子类实现。

应用实例:

1、在造房子的时候,地基、走线、水管都一样,只有在建筑的后期才有加壁橱加栅栏等差异。
2、spring 中对 Hibernate 的支持,将一些已经定好的方法封装起来,比如开启事务、获取 Session、关闭 Session 等,程序员不重复写那些已经规范好的代码,直接丢一个实体就可以保存。

优点:

1、封装不变部分,扩展可变部分。
2、提取公共代码,便于维护。
3、行为由父类控制,子类实现。

缺点:

每一个不同的实现都需要一个子类来实现,导致类的个数增加,使得系统更加庞大。

使用场景:

1、有多个子类共有的方法,且逻辑相同。
2、重要的、复杂的方法,可以考虑作为模板方法。

注意事项:

为防止恶意操作,一般模板方法都加上 final 关键词。

实现

1)创建一个定义操作的 Game 抽象类,其中,模板方法设置为 final,这样它就不会被重写。
2)Cricket 和 Football 是扩展了 Game 的实体类,它们重写了抽象类的方法。
3)TemplatePatternDemo,我们的演示类使用 Game 来演示模板模式的用法。

Image text

1、创建一个抽象类Game,它的模板方法被设置为 final。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public abstract class Game {
abstract void initialize();
abstract void startPlay();
abstract void endPlay();

//模板
public final void play(){

//初始化游戏
initialize();

//开始游戏
startPlay();

//结束游戏
endPlay();
}
}

2、创建扩展了Game类的实体类Cricket和 Football。

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

@Override
void endPlay() {
System.out.println("Cricket Game Finished!");
}

@Override
void initialize() {
System.out.println("Cricket Game Initialized! Start playing.");
}

@Override
void startPlay() {
System.out.println("Cricket Game Started. Enjoy the game!");
}
}

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

@Override
void endPlay() {
System.out.println("Football Game Finished!");
}

@Override
void initialize() {
System.out.println("Football Game Initialized! Start playing.");
}

@Override
void startPlay() {
System.out.println("Football Game Started. Enjoy the game!");
}
}

3、在测试类TemplatePatternDemo使用 Game 的模板方法 play() 来演示游戏的定义方式。

1
2
3
4
5
6
7
8
9
10
public class TemplatePatternDemo {
public static void main(String[] args) {

Game game = new Cricket();
game.play();
System.out.println();
game = new Football();
game.play();
}
}

4、输出

1
2
3
4
5
6
7
Cricket Game Initialized! Start playing.
Cricket Game Started. Enjoy the game!
Cricket Game Finished!

Football Game Initialized! Start playing.
Football Game Started. Enjoy the game!
Football Game Finished!

参考:http://www.runoob.com/design-pattern/template-pattern.html