0%

一、概述

词的定格的一些符号解释:

  • “+”:表示可平可仄
  • “-”:表示平声
  • “|”:表示仄声

二、平韵格

2.1 十六字令

十六字令《苍梧谣》、《归字谣》,十六字,三平韵,定格如下:

1
2
3
-(韵)
+ | - - | | -(韵)
- - |(句) + | | - -(韵)

示例1:
天!休使圆蟾照客眠。
人何在?桂影自婵娟。 —-蔡伸

示例2:
归!猎猎西风卷绣旗。
拦教住,重举送行杯。 —-张孝祥

2.2 南歌子

南歌子又名又名“南柯子”、“怕春归”“、”春宵曲”、“碧窗梦”、“风蝶令”等。南歌子有单调和双调之分,区别如下:

单调:正体单调二十三字,五句三平韵;另有单调二十六字,五句三平韵

双调:双调五十二字,前后段各四句三平韵;双调五十四字,前后段各四句三平韵等变体

1
2
3
4
5
6
7
8
9
10
11
12
正体单调(二十三字):
| | - - |(句)
- - | | -(韵)
- | | - -(韵)
| - - | |
| - -(韵)

变体单调(二十六字):
| | - - |(句)
- - | | -(韵)
+ - + | | - -(韵)
+ | + - + | | - -(韵)

2.3 渔歌子

渔歌子,词牌名,又名“渔父”“渔父乐”“渔父词”“秋日田父辞”等

二十七字,五句四平韵

1
2
3
4
5
6
+ | - - | | -(韵)
+ - - | | - -(韵)
- | |(句)
| - -(韵)
- - | | | - -(韵)

示例:
西塞山前白鹭飞,桃花流水鳜(guì)鱼肥。
青箬(ruò)笠(lì),绿蓑衣,
斜风细雨不须归。 —-张志和《渔歌子·西塞山前白鹭飞》

西塞山:在今浙江省湖州市西面或湖北省黄石市
箬笠:用竹篾、箬叶编的斗笠
蓑衣:用草或棕麻编织的雨衣

2.4 忆江南

忆江南,本为唐教坊曲名,后用作词牌名。又名“望江南”、“梦江南”、“江南好”、“望江梅”、“春去也”、“梦游仙”、“安阳好”、“步虚声”、“壶山好”、“望蓬莱”、“江南柳”等。

忆江南,二十七字,五句三平韵

1
2
3
4
5
- + |(句)
+ | | - -(韵)
+ | + - - | |(句)
+ - + | | - -(韵)
+ | | - -(韵)

春去也,多谢洛城人。弱柳从风疑举袂,丛兰裛(yì)露似沾巾。独坐亦含嚬(pín)。
春去也。共惜艳阳年。犹有桃花流水上,无辞竹叶醉尊前。惟待见青天。 —-刘禹锡《忆江南·春去也》

春未老,风细柳斜斜。试上超然台上看(看 一作:望),半壕(háo)春水一城花。烟雨暗千家。
寒食后,酒醒却咨(zī)嗟(jiē)。休对故人思故国,且将新火试新茶。诗酒趁年华。 —-苏轼《望江南 超然台作》

一、概述

二、模板方法模式

模板方法角色:

  • 抽象类:负责给出一个算法的轮廓和骨架,它由一个模板方法的若干个基本方法构成
  • 具体子类:实现抽象类中所定义的抽象方法和钩子方法

抽象类中的方法有以下几类:

  • 模板方法:定义算法的股价,按照某种顺序调用其包含的基本方法
  • 基本方法:整个算法中的一个步骤,由具体子类实现
    • 抽象方法:在抽象类中声明,由具体子类实现
    • 具体方法:在抽象类中已经实现,在具体子类中可以继承或重写
    • 钩子方法:在抽象类中已经实现,包括用于判断的逻辑方法和需要子类重写的空方法两种

2.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
public class Clent{
public static void main(String[] args){
AbstractClass tm = new ConcreteClass();
tm.templateMethod();
}
}

abstract class AbstractClass{
public void templateMethod(){
specificMethod();
abstractMehtod1();
abstractMehtod2();
}

public void specificMethod(){
System.out.println("抽象类中的具体方法被调用...");
}

public abstract void abstractMethod1();
public abstract void abstractMethod2();
}

class ConcreteClass extends AbstractClass{
public void abstractMethod1(){
System.out.println("抽象方法1的实现被调用...");
}

public void abstractMethod1(){
System.out.println("抽象方法2的实现被调用...");
}
}

/*
抽象类中的具体方法被调用...
抽象方法1的实现被调用...
抽象方法1的实现被调用...
*/

2.2 应用

三、策略模式

该模式定义了一系列算法并将每个算法封装起来,使它们可以相互替换,且算法的变化不会影响使用算法的客户。

策略模式属于对象行为模式,它通过对算法进行封装,把使用算法的责任和算法的实现分割开来,并委派给不同的对象对这些算法进行管理。

策略模式的主要角色:

抽象策略(Strategy)类:定义了一个公共接口,各种不同的算法以不同的方式实现这个接口,环境角色使用这个接口调用不同的算法,一般使用接口或抽象类实现

具体策略(Concrete Strategy)类:实现了抽象策略定义的接口,提供具体的算法实现。

环境(Context)类:持有一个策略类的引用,最终给客户端调用

3.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
// 访问类
public class StrategyPattern {
public static void main(String[] args) {
Context c = new Context();
Strategy s = new ConcreteStrategyA();
c.setStrategy(s);
c.strategyMethod();
System.out.println("------------------------");
s = new ConcreteStrategyB();
c.setStrategy(s);
c.strategyMethod();
}
}

interface Strategy{
public void strategyMethod();
}

// 具体策略A
class ConcreteStrategyA implements Strategy{
public void strategyMethod() {
System.out.println("具体策略A的策略方法被访问!");
}
}

// 具体策略B
class ConcreteStrategyB implements Strategy{
public void strategyMethod() {
System.out.println("具体策略B的策略方法被访问!");
}
}

// 环境类
class Context{
private Strategy strategy;

public Strategy getStrategy() {
return strategy;
}

public void setStrategy(Strategy strategy) {
this.strategy = strategy;
}

public void strategyMethod(){
strategy.strategyMethod();
}
}

3.2 应用

四、命令模式

命令( ommand )模式的定义如下:将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。这样两者之间通过命令对象进行沟通,这样方便将命令对象进行储存、传递、调用、增加与管理。

命令模式的主要角色:

  • 抽象命令类(Command)角色:声明执行命令的接口,拥有执行命令的抽象方法execute()
  • 具体命令角色(Concrete Command)角色:是抽象命令的具体实现类,它拥有接收者对象,并通过调用接收者的功能来完成命令执行的操作
  • 实现者/接收者(Receiver)角色:执行命令功能的相关操作,是具体命令对象业务的真正实现者
  • 调用者/请求者(Invoker)角色:是请求的发送者,它通常拥有很多的命令对象,并通过访问命令对象来执行相关请求,它不直接访问接收者

4.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
public class CommandPattern {
public static void main(String[] args) {
Command cmd = new ConcreteCommand();
Invoker ir = new Invoker(cmd);
System.out.println("客户访问调用者的call()方法...");
ir.call();
}
}

class Invoker{
private Command command;

public Invoker(Command command){
this.command = command;
}

public void setCommand(Command command){
this.command = command;
}

public void call(){
System.out.println("调用者执行命令command...");
command.execute();
}
}

interface Command{
public abstract void execute();
}

class ConcreteCommand implements Command{
private Receiver receiver;

ConcreteCommand(){
receiver = new Receiver();
}

public void execute(){
receiver.action();
}
}

class Receiver{
public void action(){
System.out.println("接收者的action()方法被调用...");
}
}
/*
客户访问调用者的call()方法...
调用者执行命令command...
接收者的action()方法被调用...
*/

4.2 应用

五、职责链模式

为了避免请求发送者与多个请求处理者糯合在 起,将所有请求的处理者通过前一对象记住其下个对象的引用而连成一条链当有求发生时,可将请求沿着这条链传递,直到有对象处理它为止

职责链的主要角色:

  • 抽象处理者角色(Handler):定义一个处理请求的接口,包含抽象处理方法和一个后继连接
  • 具体处理者角色(ConcreteHandler):实现抽象处理者的处理方法,判断能否处理本次请求,如果可以处理请求则处理,否则将该请求转给它的后继者
  • 客户类角色:创建处理链,并向链头的具体处理者对象提交请求,它不关心处理细节和请求的传递过程

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
public class ChainOfResponsibilityPattern {
public static void main(String[] args) {
Handler handler1 = new ConcreteHandler1();
Handler handler2 = new ConcreteHandler2();
handler1.setNext(handler2);
handler1.handleRequest("two");
}
}

// 抽象处理者角色
abstract class Handler{
private Handler next;

public Handler getNext() {
return next;
}

public void setNext(Handler next) {
this.next = next;
}

public abstract void handleRequest(String request);
}

class ConcreteHandler1 extends Handler{
@Override
public void handleRequest(String request) {
if (request.equals("one")){
System.out.println("具体处理1负责处理该请求!");
} else{
if (getNext() != null){
getNext().handleRequest(request);
}else {
System.out.println("没有人处理该请求!");
}
}
}
}

class ConcreteHandler2 extends Handler{
@Override
public void handleRequest(String request) {
if (request.equals("two")){
System.out.println("具体处理2负责处理该请求!");
} else{
if (getNext() != null){
getNext().handleRequest(request);
}else {
System.out.println("没有人处理该请求!");
}
}
}
}

5.2 应用

六、状态模式

状态模式:对有状态的对象,把复杂的“判断逻辑”提取到不同的状态对象中, 允许状态对象在其内部状态发生改变时改变其行为。

状态模式的主要角色:

  • 环境角色(Context):也称为上下文,它定义了客户感兴趣的接口,维护一个当前状态,并将与状态相关的操作委托给当前状态对象来处理
  • 抽象状态角色(State):定义一个接口,用以封装环境对象中的特定状态所对应的行为
  • 具体状态角色(Concrete State):实现抽象状态所对应的行为

6.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
public class StatePatternClient {
public static void main(String[] args) {
Context context = new Context(); // 创建环境
context.Handle(); // 处理请求
context.Handle();
context.Handle();
context.Handle();
}
}

// 环境类
class Context{
private State state;

public Context(){
this.state = ConcreteStateA();
}

public Handler getState() {
return state;
}

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

public void Handle(){
state.Handle(this);
}
}

// 抽象状态类
abstract class State{
public abstract void Handle(Context context);
}

// 具体状态类A
class ConcreteStateA extends State{
public void Handle(Context context){
System.out.println("当前状态是A.")
context.setState(new ConcreteStateB());
}
}

// 具体状态类B
class ConcreteStateA extends State{
public void Handle(Context context){
System.out.println("当前状态是B.")
context.setState(new ConcreteStateA());
}
}

/*
当前状态是A.
当前状态是B.
当前状态是A.
当前状态是B.
*/

6.2 应用

七、观察者模式

观察者模式:指多个对象存在一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。这种模式有时又称作发布-订阅模式、模型-视图模式 它是对象行为型模式。

观察者模式的主要角色:

  • 抽象主题Subject:也叫抽象目标类,它提供了一个用于保存观察者对象的聚集类和增加、删除观察者对象的方法,以及通知所有观察者的抽象方法
  • 具体主题Concrete Subject:也叫具体目标类,它实现抽象目标中的通知方法,当具体主题的内部状态发生改变时,通知所有注册过的观察者对象
  • 抽象观察者Observer:它是一个抽象类或接口,它包含了一个更新自己的抽象方法,当接到具体主题的更改通知时被调用
  • 具体观察者Concrete Observer:实现抽象观察者中定义的抽象方法,以便在得到目标的更改通知时更新自身的状态

7.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
public class ObserverPattern{
public static void main(String[] args){
Subject subject = new ConcreteSubject();
Observer obs1 = new ConcreteObserver1();
Observer obs2 = new ConcreteObserver2();
subject.add(obs1);
subject.add(obs2);
subject.notifyObserver();
}
}

abstract class Subject{
protected List<Object> observers = new ArrayList<Observer>();
// 添加观察者
public void add(Observer observer){
observers.add(observer);
}

// 删除观察者
public void remove(Observer observer){
observers.remove(observer);
}

public abstract void notifyObserver(); // 通知观察者方法
}

class ConcreteSubject extends Subject{
public void notifyObserver(){
System.out.println("具体目标发生改变...");
System.out.println("----------------");

for(Object obs:observers){
((Observer)obs).response();
}
}
}

interface Observer{
void response();
}

class ConcreteObserver1 implement Observer{
public void response(){
System.out.println("具体观察者1做出反应...");
}
}

class ConcreteObserver2 implement Observer{
public void response(){
System.out.println("具体观察者2做出反应...");
}
}

/*
具体目标发生改变...
----------------
具体观察者1做出反应...
具体观察者2做出反应...
*/

7.2 应用

八、中介者模式

中介模式:定义一个中介对象来封装一系列对象之间的交互,使原有对象之间的精合松散,且可以独立地改变它们之间的交互。中介者模式又叫调停模式,它是迪米特法则的典型应用。

中介模式的主要角色:

  • 抽象中介者Mediator:中介者的接口,提供了同事对象注册与转发同事对象信息的抽象方法
  • 具体中介者Concrete Mediator:实现中介者接口,定义一个List来管理同事对象,协调各个同事角色的交互关系,因此它们依赖于同事角色
  • 抽象同事类Colleague:定义同时类的接口,保存中介者对象,提供同事对象交互的抽象方法,实现所有相互影响的同事类的公共功能
  • 具体同事类Concrete Colleague:是抽象同事类的实现者,当需要与其他同事对象交互时,由中介者对象负责后续的交互

8.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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
public class MediatorPattern{
public static void main(String[] args){
Mediator md = new ConcreteMediator();
Colleague c1,c2;
c1 = new ConcreteColleague1();
c2 = new ConcreteColleague2();
md.register(c1);
md.register(c2);
c1.send();
System.out.println("--------------");
c2.send();
}
}

abstract class Mediator{
public abstract void register(Colleague colleague);
public abstract void relay(Colleague cl);
}

class ConcreteMediator extends Mediator{
private List<Colleague> colleagues = new ArrayList<Colleague>();

public void register(Colleague colleague){
if(!colleagues.contains(colleague)){
colleague.add(colleague);
colleague.setMedium(this);
}
}

public void relay(Colleague cl){
for(Colleague ob:colleagues){
if(!ob.equals(cl)){ // 传递请求给非cl的对象
((Colleague)ob).receive();
}
}
}
}

abstract class Colleague{
protected Mediator mediator;

public void setMedium(Mediator mediator){
this.mediator = mediator;
}
public abstract void receive();
public abstract void send();
}

class ConcreteColleague1 extends Colleague{
public abstract void receive(){
System.out.println("具体同事类1收到请求!");
}

public abstract void send(){
System.out.println("具体同事类1发出请求!");
mediator.relay(this) // 请中介者转发
}
}

class ConcreteColleague2 extends Colleague{
public abstract void receive(){
System.out.println("具体同事类2收到请求!");
}

public abstract void send(){
System.out.println("具体同事类2发出请求!");
mediator.relay(this) // 请中介者转发
}
}

/*
具体同事类1发出请求!
具体同事类2收到请求!
--------------
具体同事类2发出请求!
具体同事类1收到请求!
*/

8.2 应用

九、迭代器模式

迭代器Iterator模式:提供一个对象来 序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示。

迭代器模式的主要角色:

  • 抽象聚合Aggregate角色:定义存储、添加、删除聚合对象以及创建迭代器对象的接口
  • 具体聚合Concrete Aggregate角色:实现抽象聚合类,返回一个具体迭代器的实例
  • 抽象迭代器Iterator角色:定义访问和遍历聚合元素的接口,通常包含hasNext()、first()、next()等方法
  • 具体迭代器Concrete Iterator角色:实现抽象迭代器接口中所定义的方法,完成对聚合对象的遍历,记录遍历的当前位置

9.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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
public class IteratorPattern{
public static void main(String[] args){
Aggregate ag = new ConcreteAggregate();
ag.add("中山大学");
ag.add("华南理工");
ag.add("韶关学院");
System.out.println("聚合的内容有:");

Iterator it = ag.getIterator();
while(it.hasNext()){
Object ob = it.next();
System.out.println(ob.toString()+"\t");
}
Object ob = it.first();
System.out.println("\nFirst:"+ob.toString());
}
}

interface Aggregate{
public void add();
public void remove();
public Iterator getIterator();
}

class ConcreteAggregate implements Aggregate{
private List<Object> list = new ArrayList<>();

public void add(Object obj){
list.add(obj);
}

public void remove(Object obj){
list.remove(obj);
}

public Iterator getIterator(){
return new ConcreteIterator(list);
}
}

interface Iterator{
Object first();
Object next();
boolean hasNext();
}

class ConcreteIterator implements Iterator{
private List<Object> list = null;

private int index = 0;
public ConcreteIterator(List<Object> list){
this.list = list;
}

public boolean hasNext(){
if(index >= list.size()){
return false;
}else{
return true;
}
}

public Object first(){
index = 0;
Object obj = list.get(index);
return obj;
}

public Object next(){
Object obj = null;
if(this.hasNext()){
obj = list.get(index++);
}
return obj;
}
}

/*
聚合的内容有:中山大学 华南理工 韶关学院
First:中山大学
*/

9.2 应用

十、访问者模式

访问者模式:将作用于某种数据结构中的各元素的操作分离出来封装成独立的类,使其在不改变数据结构的前提下可以添加作用于这些元素的新的操作,为数据结构中的每个元素提供多种访问方式。

访问者模式的主要角色:

  • 抽象访问者Visitor:定义一个访问具体元素的接口,为每个具体元素类对应一个访问操作visit(),该操作中的参数类型标识了被访问的具体元素
  • 具体访问者Concrete Vistor:实现抽象访问者角色中声明的各个访问操作
  • 抽象元素角色Element:声明一个包含接受操作accept()的接口,被接受的访问者对象作为accept()方法的参数
  • 具体元素角色Concrete Element:实现抽象元素角色提供的accept()操作,其方法体通常都是visitor.visit(this),另外具体元素中可能还包含本身业务逻辑的相关操作
  • 对象结构角色Object Structure:是一个包含元素角色的容器,提供让访问者对象遍历容器中的所有元素的方法,通常由List、Set、Map等聚合类实现

10.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
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
public class VisitorPattern {
public static void main(String[] args) {
ObjectStructure os = new ObjectStructure();
os.add(new ConcreteElementA());
os.add(new ConcreteElementB());

Visitor visitor = new ConcreteVisitorA();
os.accept(visitor);
System.out.println("---------------------");
visitor = new ConcreteVisitorB();
os.accept(visitor);
}
}

interface Visitor{
void visit(ConcreteElementA element);
void visit(ConcreteElementB element);
}

class ConcreteVisitorA implements Visitor{

@Override
public void visit(ConcreteElementA element) {
System.out.println("具体访问者A访问-->"+element.operationA());
}

@Override
public void visit(ConcreteElementB element) {
System.out.println("具体访问者A访问-->"+element.operationB());
}
}

class ConcreteVisitorB implements Visitor{

@Override
public void visit(ConcreteElementA element) {
System.out.println("具体访问者B访问-->"+element.operationA());
}

@Override
public void visit(ConcreteElementB element) {
System.out.println("具体访问者B访问-->"+element.operationB());
}
}

interface Element{
void accept(Visitor visitor);
}

class ConcreteElementA implements Element{

@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}

public String operationA(){
return "具体元素A的操作。";
}
}

class ConcreteElementB implements Element{

@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}

public String operationB(){
return "具体元素B的操作。";
}
}

class ObjectStructure{
private final List<Element> list = new ArrayList<>();
public void accept(Visitor visitor){
Iterator<Element> i = list.iterator();
while (i.hasNext()){
((Element) i.next()).accept(visitor);
}
}

public void add(Element element){
list.add(element);
}

public void remove(Element element){
list.remove(element);
}
}

/*
具体访问者A访问-->具体元素A的操作
具体访问者A访问-->具体元素B的操作
---------------------
具体访问者B访问-->具体元素A的操作
具体访问者B访问-->具体元素B的操作
*/

10.2 应用

十一、备忘录模式

备忘录模式:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便以后当需要时能将该对象恢复到原先保存的状态。

备忘录模式的主要角色:

  • 发起人Originator角色:记录当前时刻的内部状态信息,提供创建备忘录和回复备忘录数据的功能,实现其他业务功能,它可以访问备忘录里的所有信息
  • 备忘录Memento角色:负责存储发起人的内部状态,在需要的时候提供这些内部状态给发起人
  • 管理者Caretaker角色:对备忘录进行管理,提供保存与获取备忘录的功能,但其不能对备忘录的内容进行访问和修改

11.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
63
64
65
66
67
68
69
70
71
public class MementoPattern {
public static void main(String[] args) {
Originator or = new Originator();
Caretaker cr = new Caretaker();
or.setState("S0");
System.out.println("初始状态:"+or.getState());

// 保存状态
cr.setMemento(or.createMemento());

or.setState("S1");
System.out.println("新的状态:"+or.getState());

// 恢复状态
or.restoreMemento(cr.getMemento());
System.out.println("恢复状态:"+or.getState());
}
}

class Memento{
private String state;

public Memento(String state){
this.state = state;
}

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

public String getState(){
return state;
}
}

class Originator{
private String state;

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

public String getState(){
return state;
}

public Memento createMemento(){
return new Memento(state);
}

public void restoreMemento(Memento m){
this.setState(m.getState());
}
}

class Caretaker{
private Memento memento;

public void setMemento(Memento m){
memento = m;
}

public Memento getMemento(){
return memento;
}
}
/*
初始状态:S0
新的状态:S1
恢复状态:S0
*/

11.2 应用

十二、解释器模式

解释器Interpreter模式的定义:给分析对象定义一个语言,并定义该语言的文法表示,再设计一个解析器来解释语言中的句子。也就是说,用编译语言的方式来分析应用中的实例。这种模式实现了文法表达式处理的接口。

解释器模式的主要角色:

  • 抽象表达式Abstract Expression:定义解释器的接口,约定解释器的解释操作,主要包含解释方法interpret()
  • 终结符表达式Terminal Expression:是抽象表达式的子类,用来实现文法中与终结符相关的操作,文法中的每一个终结符都有一个具体终结表达式与之相对应
  • 非终结符表达式Nonterminal Expression:也是抽象表达式的子类,用来实现文法中与非终结符相关的操作,文法中的每条规则都对应于一个非终结符表达式
  • 环境角色Context:通常包含各个解释器需要的数据或是公共的功能,一般用来传递被所有解释器共享的数据,后面的解释器可以从这里获取这些值
  • 客户端Client:主要任务是将需要分析的句子或表达式转换成使用解释器对象描述的抽象语法树,然后调用解释器的解释方法,当然也可以通过环境角色间接访问解释器的解释方法

12.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
63
64
65
66
67
68
69
70
71
72
73
74
/*文法规则
<expression>::=<city>的<person>
<city> ::= 韶关 | 广州
<person> ::= 老人 | 妇女 | 儿童
*/
public class InterpreterPatternDemo {
public static void main(String[] args) {
Context bus = new Context();
bus.freeRide("韶关的老人");
bus.freeRide("韶关的年轻人");
bus.freeRide("广州的妇女");
bus.freeRide("广州的儿童");
bus.freeRide("山东的儿童");
}
}

// 抽象表达类
interface Expression{
public boolean interpret(String info);
}

// 终结符表达式类
class TerminalExpression implements Expression{
private Set<String> set = new HashSet<>();

public TerminalExpression(String[] data){
for (int i=0;i<data.length;i++)
set.add(data[i]);
}

@Override
public boolean interpret(String info) {
if (set.contains(info)){
return true;
}
return false;
}
}

// 非终结表达式类
class NonTerminalExpression implements Expression{
private Expression city = null;
private Expression person = null;

public NonTerminalExpression(Expression city,Expression person){
this.city = city;
this.person = person;
}

@Override
public boolean interpret(String info) {
String s[] = info.split("的");
return city.interpret(s[0])&&person.interpret(s[1]);
}
}

// 环境类
class Context{
private String[] citys = {"韶关", "广州"};
private String[] persons = {"老人", "妇女", "儿童"};
private Expression cityPerson;

public Context(){
Expression city = new TerminalExpression(citys);
Expression person = new TerminalExpression(persons);
cityPerson = new NonTerminalExpression(city,person);
}

public void freeRide(String info){
boolean ok = cityPerson.interpret(info);
if (ok) System.out.println("您是"+info+",您本次乘车免费!");
else System.out.println(info+",您不是免费人员,本次乘车扣费20元!");
}
}

12.2 应用

一、概述

1.1 基本术语

莫扎特《A大调奏鸣曲》的每小节就是一个乐汇,两个乐汇组成一个乐节,两个乐节构成一个句,两个乐句构成一个乐段

  • 音乐材料:不同的乐器音响效果
  • 乐汇:是音乐语言的基本结构单位,至少需要一个逻辑强拍和一个逻辑弱拍,因此最少需要两个音,可以理解为语言中的词汇
  • 乐节:是音乐语言的基本组织单位。一般由两个乐汇构成,是乐段内部介于乐句与乐汇之间的结构段落。
  • 乐句:是音乐语言的基本表达单位,是构成一首乐曲的一个具有特性的基本结构单位
  • 乐段:是音乐形象的具体表现单位
  • 乐思:乐思就是在音乐曲式分析中整体表达内容的最小单位
  • 主题:音乐主题是一首(部)音乐作品(段落)中的最主要的乐思,即音乐的核心部分。它是音乐思维的“种子”
  • 动机:是音乐主题最具代表性的小单位,是主题或乐曲发展的胚芽,具有一定的独立表现意义,可能是一个节奏型,一个旋律走向,肯定很短,可以衔接不同的乐段作为同一个曲子
  • 发展:音乐律动的进行过程

现在着重讲下乐句和乐段,乐段是由乐句构成,一般为8小节,前4节为先行部,后4节为后续部

先行部和后续部的前半段都是相同的,只是后半段的发展是不同的,先行部的后半段为一个终止,但终止感相对与后续部的后半段的终止感要弱

1.2 音乐材料与发展手法

  • 重复:对前一小节的重复演奏
  • 变奏
  • 展开
  • 派生对比
  • 并置对比
  • 模仿
  • 摸进:模仿进行,将前一小节的音符移高或者移低
  • 分裂
  • 平行乐句:两个乐句开始部分是相同的,只有结尾不同,形成一种呼应,比如《欢乐颂》
  • 时值的压缩与扩大

二、曲式

2.1 一部曲式

一部曲式:一部曲式是完整的曲式中规模最小的结构,结构为A。可以由一个乐句、两个乐句、三个及三个以上的乐句组成。一般有比较明显的终止式,能够表达一个完整或相对完整的乐思。

  • 这种曲式结构最简单,没有明显的重复段落,没有第二主题,也没有副歌。
  • 用这种结构写出来的乐曲通常较短

2.2 单二部曲式

单二部曲式:由两个乐段构成的曲式,叫做单二部曲式,结构为A+B,通常情况下A段和B段会形成鲜明对比

再继续细分的话又分为两种情况:

  • 带再现的单二部曲式,这种曲式里通常B段的前一半为对比部分,后一半为再现部分
  • 不带再现的单二部曲式,这种曲式A段和B段由完全不同的材料构成,形成鲜明对比

2.3 单三部曲式

由三个乐段构成,其中第一段和第三段是一样的材料,结构为A+B+A。这种曲式的中段B会与前后两段A形成鲜明对比,通常在速度、力度、感情变化方面都完全不一样。有些作品的B段主题也可能由A段的主题变化而来。

第三段为第一段的再现,有些作品完全一样,有些作品可能会产生一些微小的变化。

2.4 复三部曲式

这种曲式的大结构与单三部曲式完全一样,只是这里的三个段落中,每个段落还包含一个独立的曲式结构,可能为单二部曲式。可能为单三部曲式

在古典作品中,中间的段落叫做“三声中部”

2.5 变奏曲式

变奏曲一开始会有一个完整的主题段落,在后面的段落中,都是这个主题的发展变化。这个发展变化会改变其中的音型、节奏、调性、速度、音区等等,但是主题中的某些材料会保留下来,例如和声、旋律走向、句子逻辑关系等等。变奏曲式的变奏部分数量可以有很多,例如拉赫玛尼诺夫的《帕格尼尼主题狂想曲》有24个变奏,巴赫的《哥德堡变奏曲》有30个变奏。

2.6 回旋曲式

这种曲式由两种部分构成,为主部与副部。在段落中,第1、3、5…奇数段落为主部,第2、4、6…偶数段落为副部。主部的段落主题是一致的,而副部的主题一直在不断变化。回旋曲式主部和副部的数量可以很多

这样的曲式听起来会感觉到不断有新主题出现,然后回到原来的主题

回旋曲式的产生源于声乐性的轮舞曲,在轮舞曲中有分节歌与副歌,分节歌通常是独唱,副歌是合唱,每次独唱完成后都会回到合唱,每次合唱部分都是相同的。

2.7 奏鸣曲式

奏鸣曲式在大结构上和三部曲式一样,但是其内部结构更为复杂。第一段的呈示部和第三段的再现部用相同的主题,而中间的展开部会把这个主题发展变化。

呈示部中往往还包含第一主题、第二主题、第一结尾、第二结尾,这些片段之间还有一些连接段,在调性安排上,第一主题为主调,第二主题转到属调。

呈示部中往往还包含第一主题、第二主题、第一结尾、第二结尾,这些片段之间还有一些连接段,在调性安排上,第一主题为主调,第二主题转到属调。

再现部包含了呈示部中的所有部分,区别在于这里第一主题和第二主题都为主调。

有很多的奏鸣曲、协奏曲、交响乐、室内乐的乐章都用到了奏鸣曲式,尤其是第一乐章用得最多,但不是所有的作品都是用奏鸣曲式来写的,也可以用其他的结构来写

三、旋律

3.1 音程旋律

音高进行方式:

  • 同音重复:相同音重复进行
  • 极进进行:两个音的进行相差二度关系
  • 小跳跳进:两个音的进行相差三度关系
  • 大跳进行:两个音的进行相差三度以上关系

对于大跳进行,四度以上的进行不能太频繁,六度及以上的跳进必须在之后要反向进行

规范:

  • 音域在12度以内:音域太广就容易不好演奏、演唱困难、音乐缺少连贯性
  • 极进跳进交错:连续的级进音乐容易单调,连续的跳进音乐容易不稳定,所以交错进行可以保持音乐的活力和稳定

音程旋律的优缺点:

  • 无需灵感、无需创意
  • 旋律随机,重复概率小
  • 旋律要进行多次调整,否则可能很难听

3.2 旋律发展

音乐的张力可以推动音乐的发展

  • 上升进行:音高向上的进行
  • 下降进行:音高向下的进行
  • 交错进行:上升和下降交错
  • 静止进行:音高不发生变化

通过音高控制张力:

  • 音高上升:积累张力
  • 音高下降:释放张力

通过音长控制张力:

  • 短促音符:积累张力

  • 悠长音符:释放张力

四、流行音乐曲式

4.1 结构

一般来说副歌的和声要比正歌、前奏要丰富许多,是整首歌感情最饱满的部分,如果正歌后直接接副歌,就会感觉很突兀,所以流行歌曲的正歌和副歌之间会有一个预副歌的部分,这一部分的乐器会比正歌丰富一些

  • Intro:前奏、间奏
  • Verse:正歌
  • Pre-Chorus:预副歌
  • Chorus:副歌
  • Bridge:桥段
  • Outro:尾奏

一、适配器模式

适配器模式(Adapter Pattern):结构型模式之一,将一个类的接口转换成客户希望的另一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的哪些类可以一起工作

适配器模式结构

  • 目标抽象类:该角色把其他类转换为我们期望的接口,可以是一个抽象类或接口,也可以是具体类。
  • 被适配者:原有的接口,也是希望被适配的接口。
  • 适配者:将被适配者和目标抽象类组合到一起的类。

适配器的实现又分为:类适配器、对象适配器、接口适配器

1.1 实现

1.1.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
// 被适配者
public class Adaptee {
public void adapterRequest(){
System.out.println("被适配者的方法");
}
}

// 目标接口
public interface Target {
void request();
}

// 适配器
public class Adapter extends Adaptee implements Target {
@Override
public void request() {
super.adapterRequest();
}
}

// 测试类
public class Test {
public static void main(String[] args) {
Target adapterTarget = new Adapter();
adapterTarget.request();
}
}

1.1.2 对象适配器

调整适配器的实现方式,不再以继承的方式调用被适配者的信息,而是通过对象的方式获取信息

1
2
3
4
5
6
7
8
public class Adapter implements Target {
// 获取被适配者对象
private Adaptee adaptee = new Adaptee();

public void request() {
adaptee.adapterRequest();
}
}

1.1.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
// 目标接口
public interface Target {
void request();
void getTypeA();
void getTypeB();
}

// 适配器类
public abstract class Adapter extends Adaptee implements Target {
public void request() {
return null;
}

public void getTypeA(){
return null;
}
public void getTypeB(){
return null;
}
}

// 测试类
public class AdapterTest {
public static void main(String[] args) {
Target adapterTarget= new Adapter() {
@Override
public void request() {
return super.adapterRequest();
}
};

adapterTarget.request();
}
}

二、代理模式

代理模式的角色:

  • 抽象主题(Subject)类:通过接口或抽象类声明真实主题和代理对象实现的业务方法

  • 真实主题(Real Subject)类:实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。

  • 代理(Proxy)类:提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。

代理一般理解为代码增强,即在不修改源代码的逻辑上增加一些逻辑代码

代理模式分为静态代理和动态代理:

  • 静态:由程序员创建代理类或特定工具自动生成源代码再对其编译,在程序运行前代理类的 .class 文件就已经存在了。
  • 动态:在程序运行时,运用反射机制动态创建而成

2.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
package proxy;

// 测试类
public class ProxyTest {
public static void main(String[] args) {
Proxy proxy = new Proxy();
proxy.Request();
}
}

//抽象主题
interface Subject {
void Request();
}

//真实主题
class RealSubject implements Subject {
public void Request() {
System.out.println("访问真实主题方法...");
}
}

//代理类
class Proxy implements Subject {
private RealSubject realSubject;

public void Request() {
if (realSubject == null) {
realSubject = new RealSubject();
}
preRequest();
realSubject.Request();
postRequest();
}

public void preRequest() {
System.out.println("访问真实主题之前的预处理。");
}

public void postRequest() {
System.out.println("访问真实主题之后的后续处理。");
}
}

2.2 应用

  • Spring框架的的AOP(面向切面编程)概念

三、桥接模式

桥接模式的角色:

  • 抽象化角色Abstraction:定义抽象类,并包含一个对实现化对象的引用
  • 扩展抽象化角色Refined Abstraction:抽象化角色的子类,实现父类中的业务方法
  • 实现化角色Implementor:实现化角色的接口,供扩展抽象化角色调用
  • 具体实现化角色Concrete Implementor:给出实现化角色接口的具体实现

简单来说就是通过抽象化角色完成对实现化角色方法的调用

桥接模式是将抽象与实现分离,这里的抽象是抽象化角色,实现是实现化角色,两者以组合关系完成抽象化角色实现化角色方法的调用

3.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
package bridge;
public class BridgeTest {
public static void main(String[] args) {
Implementor imple = new ConcreteImplementorA();
Abstraction abs = new RefinedAbstraction(imple);
abs.Operation();
}
}
//实现化角色
interface Implementor {
public void OperationImpl();
}
//具体实现化角色
class ConcreteImplementorA implements Implementor {
public void OperationImpl() {
System.out.println("具体实现化(Concrete Implementor)角色被访问");
}
}
//抽象化角色
abstract class Abstraction {
protected Implementor imple;
protected Abstraction(Implementor imple) {
this.imple = imple;
}
public abstract void Operation();
}
//扩展抽象化角色
class RefinedAbstraction extends Abstraction {
protected RefinedAbstraction(Implementor imple) {
super(imple);
}
public void Operation() {
System.out.println("扩展抽象化(Refined Abstraction)角色被访问");
imple.OperationImpl();
}
}

3.2 应用

四、装饰模式

装饰模式角色:

  • Component(抽象构件类):是具体构件以及抽象装饰类的父类,声明了在具体构件中实现的业务方法,它的引入可以使客户端以一致的方式处理未被装饰之后的对象,以实现客户端的透明操作
  • ConcreteComponent(具体构件类):是抽象构件类的子类,用于定义具体的构件对象,实现了在抽象构件中声明的方法,装饰器可以给它增加额外的职责
  • Decorator(抽象装饰类)用于给具体构件类增加职责,但是具体职责在子类实现。抽象装饰类维护一个指向抽象构件的引用,通过该引用可以调用装饰之前构件对象的方法,并通过子类扩展该方法以达到装饰的目的
  • ConcreteDecorator(具体装饰类)负责向构件中添加新的职责,每一个具体装饰类都定义了一些新的行为,可以调用抽象装饰类中定义的方法,并可以增加新的职责用以扩充对象的行为

4.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
// 抽象构建类
abstract class Component
{
abstract void operation();
}

// 具体构建类
class ConcreteComponent extends Component
{
public void operation()
{
System.out.println("具体构件方法");
}
}

// 抽象构建类
class Decorator extends Component
{
private Component component;

public Decorator(Component component)
{
this.component = component;
}

public void operation()
{
component.operation();
}
}

// 具体装饰类
class ConcreteDecorator extends Decorator
{
public ConcreteDecorator(Component component)
{
super(component);
}

public void operation()
{
super.operation();
newBehavior();
}

public void newBehavior()
{
System.out.println("装饰方法");
}
}

// 测试类
public static void main(String[] args)
{
Component component = new ConcreteComponent();
Component decorator = new ConcreteDecorator(component);
decorator.operation();
}

五、外观模式

外观模式:为多个复杂子系统提供一个一致的接口,而使这些子系统更加容易被访问

外观模式角色:

  • 外观角色Facade:为多个子系统对外提供一个共同的接口
  • 子系统角色SubSystem:实现系统的部分功能,客户可以通过外观角色访问它
  • 客户角色Client:通过一个外观角色访问各个子系统的功能

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
// 测试类
public class FacadePattern{
public static void main(String[] args){
Facade f = new Facade();
f.method();
}
}

// 外观类
class Facade{
private SubSystemA obj1 = new SubSystemA();
private SubSystemB obj2 = new SubSystemB();
private SubSystemC obj3 = new SubSystemC();

public void method(){
obj1.method1();
obj2.method2();
obj3.method3();
}
}

// 子系统A
class SubSystemA{
public void method1(){
System.out.println("子系统A的method1()方法被调用");
}
}

// 子系统B
class SubSystemB{
public void method2(){
System.out.println("子系统B的method2()方法被调用");
}
}

// 子系统C
class SubSystemC{
public void method3(){
System.out.println("子系统C的method3()方法被调用");
}
}

5.2 应用

六、享元模式

意图:运用共享技术有效地支持大量细粒度的对象。

主要解决:在有大量对象时,有可能会造成内存溢出,我们把其中共同的部分抽象出来,如果有相同的业务请求,直接返回在内存中已有的对象,避免重新创建。

何时使用: 1、系统中有大量对象。 2、这些对象消耗大量内存。 3、这些对象的状态大部分可以外部化。 4、这些对象可以按照内蕴状态分为很多组,当把外蕴对象从对象中剔除出来时,每一组对象都可以用一个对象来代替。 5、系统不依赖于这些对象身份,这些对象是不可分辨的。

主要角色

  • 抽象享元角色Flyweight:是所有的具体享元类的基类,为具体享元规范需要实现的公共接口,非享元的外部状态以参数的形式通过方法传入。
  • 具体享元角色ConcreteFlyweight:实现抽象享元角色中所规定的接口
  • 非享元角色UnsharedConcreteFlyweight:是不可以共享的外部状态 ,它以参数的形式注入具体享元的相关方法中
  • 享元工厂角色FlyweightFactory负责创建和管理享元角色。当客户对象请求享元对象时享元工厂检查系统中是否存在符合要求的享元对象如果存在提供给客户;如果不存在的话,则建一个新的享元对象。

6.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
63
64
65
// 测试类
public class Client{
public static void main(String[] args){
FlyweightFactory factory = new FlyweightFactory();
Flyweight f01 = factory.getFlyweight("a");
Flyweight f02 = factory.getFlyweight("a");
Flyweight f03 = factory.getFlyweight("a");
Flyweight f11 = factory.getFlyweight("b");
Flyweight f12 = factory.getFlyweight("b");

f01.operation(new UnsharedConcreteFlyweight("第1次调用a"));
f02.operation(new UnsharedConcreteFlyweight("第2次调用a"));
f03.operation(new UnsharedConcreteFlyweight("第3次调用a"));
f11.operation(new UnsharedConcreteFlyweight("第1次调用b"));
f12.operation(new UnsharedConcreteFlyweight("第2次调用b"));
}
}

// 抽象享元角色
interface Flyweight{
void operation(UnsharedConcreteFlyweight state);
}

// 非享元角色
class UnsharedConcreteFlyweight state{
private String info;
UnsharedConcreteFlyweight(String info){
this.info = info;
}
public String getInfo(){
return this.info;
}
public void setInfo(String info){
this.info = info;
}
}

// 具体享元角色
class ConcreteFlyweight implement Flyweight{
private String key;
ConcreteFlyweight(String key){
this.key = key;
System.out.println("具体享元"+key+"被创建!");
}

public void operation(UnsharedConcreteFlyweight outState){
System.out.println("具体享元"+key+"被调用!");
System.out.println("非享元信息是:"+outState.getInfo());
}
}

// 享元工厂
class FlyweightFactory{
private HashMap<String,Flyweight> flyweights = new HashMap<String,Flyweight>();
public Flyweight getFlyweight(String key){
Flyweight flyweight = (Flyweight)flyweights.get(key);
if(flyweight != null){
System.out.println("具体享元"+key+"已经存在,被成功获取");
}else{
flyweight = new ConcreteFlyweight(key);
flyweights.put(key,flyweight);
}
return flyweight;
}
}

6.2 应用

七、组合模式

组合模式角色:有时又叫作部分整体模式它是一种将对象组合成树状的层次结构的模式 用来表示部分整体”的关系,使用户对单个对象和组合对象具有一致的访问性。

组合模式的角色:

  • 抽象构建角色Component:它的主要作用是为树叶构件和树枝构件声明公共接口
  • 树叶构建角色Leaf::是组合中的叶节点对象,它没有子节点,用于实现抽象构件角色中声明的公共接口。
  • 树枝构件角色Composite:是组合中的分支节点对象,它有子节点。,它的主要作用是存储和管理子部件

组合模式分为透明式的组合模式安全式的组合模式

  • 透明方式:抽象构件声明了所有子类中的全部方法,所以客户端无须区别树叶对象和树枝对象
  • 安全方式:将管理子构件的方法移到树枝构件中

7.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
63
public class Client{
public static void main(String[] args){
Component c0 = new Composite();
Component c1 = new Composite();
Component leaf1 = new Leaf("1");
Component leaf2 = new Leaf("2");
Component leaf3 = new Leaf("3");
c0.add(leaf1);
c0.add(c1);
c1.add(leaf2);
c1.add(leaf3);
c0.operation();
}
}

// 抽象构件
interface Component{
public void add(Component c);
public void remove(Component c);
public Component getChild(int i);
public void operation();
}


// 树叶构建
class Leaf implements Component{
private String name;
public Leaf(String name){
this.name = name;
}
public void add(Component c){}
public void remove(Component c){}
public Component getChild(int i){return null;}
public void operation(){
System.out.println("树叶"+name+":被访问!");
}
}

// 树枝构件
class Composite implements Component{
private ArrayList<Component> children = new ArrayList<Component>();
public void add(Component c){
children.add(c);
}
public void remove(Component c){
children.remove(c);
}
public Component getChild(int i){
return children.get(i);
}
public void operation(){
for(Object obj:children){
((Component)obj).operation();
}
}
}

/*
执行结果:
树叶1:被访问!
树叶2:被访问!
树叶3:被访问!
*/

7.2 应用

一、五十音

1.1 清音

1.2 浊音、半浊音

1.3 拗音

1.4 拨音、促音和长音

拨音、促音和长音不做单独音节发音

拨音:用”ん”表示,鼻音,作用如拼音的”ng”

促音:用”つ”表示,某一音节突然停顿后发音

长音:用”あ、い、う、え、お”表示,延长音节

二、基础语法

2.1 判断句

【名】は 【名】です,表示~是~,类似于古汉语中的~乃~是也,です表示判断、断定的含义,可以翻译成“是…”

【名】は 【名】では ありません,表示~不是~,ではありません表示否定,或者使用じゃありません也是表示否定,值得注意的是ではありません是一个整体,不能拆开使用

2.2 疑问句

一般疑问句:【名】は 【名】ですか,其中“か”为疑问助词,置于句末,表示疑问,对应的回答方式如下:

  • はい,そえです:是的
  • いいえ,ちがいます或者いいえ,そうではありません:两个都表示否定
  • 分(わ)かりません:不知道

特殊疑问句:对于特殊疑问句,不能用是或者不是来进行回答,特殊疑问词有以下几种:

  • 何(なん):什么
  • どなた:哪位、谁
  • だれ:谁
  • いつ:什么时候
  • どこ:哪里
  • どれ:哪个
  • どう:如何

特殊疑问词一般跟在“は”之后,以以下句子为例:

  • 日本語の先生はどなたですか(日语老师是哪位)
    • 日本語の先生は李先生です(日语老师是李先生)
  • 先生のかばんはどれですか(老师的包是哪个)
    • 先生のかばんは黒いかばんです(老师的包是黑色的那个)

2.3 の

の用来衔接事物的所属关系,表示“~的~”,参考以下例句:

  • 私のカメラは日本のカメラです(我的电脑是日本产的)
  • この本(ほん)は日本語の本です(这本书是日语书)
  • この本は何の本ですか(这本书是什么样的书呢?)

2.4 も

も:提示助词,表示类推,表示主题内容与之前主题内容相同,相当于汉语中的“也”,一般格式如下:

  • Aも Bです(A也是B)
  • Aも Bでは ありません(A也不是B)

用例如下:

  • 私は中国人です(我是中国人)
    • あの人も 中国人です(那个人也是中国人)

2.5 存在句

2.5.1 A在B处

  • Aは Bに あります(A在B处)
    • A为人和动物之外的事物,B为表示地点的名词或代词,“あります”表示“在”
    • 如果表示不在,则“あります”应改为“ありません”

用例如下:

  • 日本語(ご)科(か)の教室(きょうしつ) 五階(ごかい)に あります(日语专业教室在第五层)
  • 先生の本は どこに ありますか(老师的书在哪里呢)

2.5.2 在A处 有B

  • Aに Bがあります
    • “が”用于提示主语
    • 如果表示没有,则“あります”应改为“ありません”

用例如下:

  • 机(つくえ)の上(うえ) 日本語の本があります(在桌子上有日语书)
  • 机(つくえ)の上 日本語の本がありません(在桌子上没有日语书)
  • 机(つくえ)の上 何(なに)がありますか(在桌子上有什么?)

2.5.3 在A处 有B和C之类的东西

  • Aに BやC などが あります

や:并列助词,前后连接两个有代表性的事物,暗示还有其他。

など:副词,可以解释为等等,之类的

用例如下:

  • 机の上 本ノート などが あります(桌子上有书和本子等一些东西)

2.5.4 在A处 有(几个)B

  • Aに Bが (数量) あります
    • 在日语中,句子的补语(数量、状态、方向等)往往出现在谓语动词之前
    • 补语的位置可以使用特殊疑问词来对B的数量进行题问

用例如下:

  • 机の上に 鍵(かぎ)が 六つ(むっつ) あります(桌子上有六把钥匙)
  • 机の上に 鍵(かぎ)が いくつ あります(桌子上有多少把钥匙?)

针对具体量词的疑问词有:何個(なんこ,多少个)、何台(なんだい,多少台)、何本(なんほん,多少本)、何~(なん~)

2.5.5 存在句的强调形式

  • Aには B あります
  • Aには B ありません(否定形式)
    • は表示对比,或加强否定语气
    • には为助词に、は的重叠,加强了对前项地点的突出

用例如下:

  • 寮(りょう)には クーラー あります(宿舍里啊【强调】,有空调的)
  • いいえ、教室(きょうしつ)には クーラーは ありません(不,教室里啊【强调】,没有空调的【否定强调】)

2.6 形容词与形容动词

2.6.1 形容词的构成

形容词 = 词干 + 词尾(い),比如:寒(さむ,冷)い、暑(あつ,热)い、広(ひろ,宽)い、古(ふる,旧)い

2.6.2 形容词的使用方法

  • 判断句:Aは 【形容词】です(A是~的)
    • 用于对事物的状态和性质进行肯定描述
  • 定语用法:形容词 + 名词/代词(~的~)
    • 形容词对后项事物进行修饰

用例如下:

  • 先生の本は 古い(ふるい)です(老师的书是旧的)
  • 毎日(まいにち)は 忙(いそが)しい です(每天都很忙)
  • これは 広(ひろ)い 教室(きょうしつ)です(这是个宽敞的教室)
  • 先生の本は あの古い(ふるい)です(老师的书是旧的那本)
  • それは 美味(おい)しい日本料理(りょうり)です

2.6.3 形容动词的构成

  • 形容动词 = 词干 + 词尾(),在课本或辞典中其词尾多省略
  • 比如:便利だ、静か(しずか,安静) だ、立派(りっぱ,漂亮,华丽,优秀,丰盛)だ

2.6.4 形容动词的使用方法

  • 作谓语:Aは (形容动词词干)です
  • 作定语:形容动词词干 + な + 名词/代词

用例如下:

  • あの教室は 静(しず )かです(那个教室是安静的)
  • 日本語の教室は あの綺麗(きれい,干净)な 教室です(日语教室是那间干净的教室)

2.7 场所

  • 地点 + へ + 方向
    • へ作为助词使用时,读音与え相同,表示移动的方向
  • 行きます(いきます,去,前往)、来ます(きます)、帰ります(かえります)、出かけます(でかけます)、 案内します(あんないします)

用例如下:

  • これからは肇慶(ちょうけい) へ案内します(我带你去参观下肇庆)

一、Eureka

1.1 概述

服务提供者越多就需要用到C/S架构来对服务消费者和服务提供者进行统一管理

服务消费者:可以通过eureka注册中心获取到服务提供者的信息,经过负载均衡选择后即可进行远程调用

服务注册中心:存放服务消费者和服务提供者的信息,并进行管理,注册中心也可以有多个

1.2 环境搭建

1.2.1 服务端

创建Maven项目作为Eureka服务注册中心(服务端),命名为cloud-eureka-server7001

pom文件依赖配置如下:

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
<dependencies>
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-netflix-eureka-client -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
<version>3.1.2</version>
</dependency>

<dependency>
<groupId>org.example</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-actuator -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-devtools -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
</dependencies>

yml配置如下:

1
2
3
4
5
6
7
8
9
10
11
server:
port: 7001
eureka:
instance:
hostname: localhost #eureka服务端的实例名称
client:
register-with-eureka: false #false表示不向注册中心注册自己。
fetch-registry: false #false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
service-url:
#设置与Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址。
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/

主启动类中需要添加EnableEurekaServer注解如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.atguigu.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@SpringBootApplication
@EnableEurekaServer
public class EurekaMain7001 {
public static void main(String[] args) {
SpringApplication.run(EurekaMain7001.class,args);
}
}

1.2.2 客户端

客户端要做的就是将微服务注册进eureka中

以cloud-provider-payment8001工程为例,eureka依赖包导入如下:

1
2
3
4
5
6
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-netflix-eureka-client -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
<version>3.1.2</version>
</dependency>

yml配置文件如下:

1
2
3
4
5
6
7
8
9
10
11
eureka:
client:
register-with-eureka: true #false表示不向注册中心注册自己。
fetch-registry: true #false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
service-url:
#设置与Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址。
defaultZone: http://localhost:7001/eureka/

spring:
application:
name: cloud-order-service #注册进eureka的服务名称

主启动类添加注解@EnableEurekaClient

1
2
3
4
5
6
7
8
@SpringBootApplication
@EnableEurekaClient
public class PaymentMain8001 {
public static void main(String[] args) {
SpringApplication.run(OrderMain8080.class,args);
}

}

这些配置完成之后即可运行,并访问http://localhost:7001/,访问成功后就可以在页面中看到服务的注册信息

同理,其他服务的注册步骤也是一样的,导包->yml配置->主启动类注解

1.3 Eureka集群构建

如果Eureka注册中心只有一个,并且发生故障的话,会导致整个服务环境不可用,所以要构建Eureka集群

多个注册中心要满足规定:互相注册,相互守望。但对外是一个整体

注:负载均衡是对某一个微服务中的不同端口进行一种选择调用的机制,默认的是轮询,即在通过消费者端8080访问支付服务时会先访问某一端口,如8001,那么下次再进行访问时就会访问8002端口,依次循环下去

1.3.1 注册中心集群构建

  • 构建多个注册中心maven项目,取不同端口号,如7001、7002、7003
  • 修改yml配置,满足互相注册,相互守望的规则
1
2
3
4
5
6
7
8
9
10
11
server:
port: 7001 # 端口号为7001的注册中心
eureka:
instance:
hostname: eureka7001.com #eureka服务端的实例名称,集群要保证每个注册中心名字不同
client:
register-with-eureka: false #false表示不向注册中心注册自己。
fetch-registry: false #false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
service-url:
# 互相注册,注意下面的地址端口
defaultZone: http://eureka7002.com:7002/eureka/
  • 同时微服务的yml配置也需要修改
1
2
3
4
5
6
7
8
9
10
11
12
server:
port: 8001
servlet:
context-path: /

eureka:
client:
register-with-eureka: true #false表示不向注册中心注册自己。
fetch-registry: true #false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
service-url:
# 多个注册中心地址都要写上
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/

1.3.2 微服务集群构建

  • 构建多个相同模块的微服务项目,取不同的端口号,如8001、8002、8003
  • 消费者端的RestTemplate服务调用地址修改为微服务的名称
1
2
3
4
5
6
7
8
9
10
11
@RestController
@Slf4j
public class OrderController {
// private static final String PAYMENT_URL = "http://localhost:8001";
private static final String PAYMENT_URL = "http://CLOUD-PAYMENT-SERVICE";

@Resource
private RestTemplate restTemplate;

// ...
}
  • 在消费者端的配置类中,为RestTemplate添加负载均衡注解@LoadBalanced
1
2
3
4
5
6
7
8
@Configuration
public class ApplicationContextConfig {
@Bean
@LoadBalanced // 为服务调用提供负载均衡的能力
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}

1.4 actuator微服务信息

打开eureka注册中心后,可以看到微服务的链接状态,可以通过修改yml来对链接信息进行修改

修改微服务的yml配置,如8001的yml配置如下:

1
2
3
4
eureka:
instance:
instance-id: payment8001
prefer-ip-address: true # 访问路径可以显示IP地址

修改完成后,访问地址可以显示ip信息,显示效果如下:

1.5 服务发现Discovery

服务发现可以通过Discovery对象来获取微服务的信息,如微服务名称、访问地址、端口等

以8001为例,先修改其controller

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
@RestController
@Slf4j
public class PaymentController {
// 注入DiscoveryClient用于获取微服务信息,注意DiscoveryClient导入的是Spring的包
@Resource
private DiscoveryClient discoveryClient;

// ...
// 微服务信息获取接口
@GetMapping("/payment/discovery")
public Object discovery(){
// 获取所有微服务名称,并打印到终端
List<String> services = discoveryClient.getServices();
for (String service: services) {
log.info(service);
}

// 获取支付微服务的所有主机信息,并打印到终端
List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
for (ServiceInstance instance:instances) {
log.info(instance.getServiceId()+"\t"+instance.getHost()+"\t"+instance.getPort()+"\t"+instance.getUri());
}

/*返回结果:
{"services":["cloud-payment-service","cloud-order-service"],"order":0}
*/
return this.discoveryClient;
}
}

再修改8001的启动类,添加注解@EnableDiscoveryClient,开启服务发现功能

1
2
3
4
5
6
7
8
@SpringBootApplication
@EnableEurekaClient
@EnableDiscoveryClient
public class PaymentMain8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentMain8001.class,args);
}
}

1.6 Eureka自我保护理论

Eureka自我保护:一旦某个微服务不可用了,Eureka会启动保护机制,将该微服务信息保留一段时间,默认是开启的,注册中心会有以下提示信息:

可以通过修改注册中心,比如7001的yml配置关闭Eureka自我保护

1
2
3
4
5
6
eureka:
server:
# 关闭Eureka自我保护机制
enable-self-preservation: false
# 让服务端每隔2秒扫描一次,是服务能尽快的剔除,单位为ms
eviction-interval-timer-in-ms: 2000

除此之外,每个客户端微服务都会定时向服务端注册中心发送心跳,类似物业费的概念,即续费服务。服务发送心跳的时间间隔也可以通过服务的yml进行配置,以8001服务的yml配置如下:

1
2
3
4
5
6
eureka:
instance:
# Eureka客户端向服务端发送心跳的时间间隔,单位为s(默认为30s)
lease-renewal-interval-in-seconds: 1
# Eureka服务端在收到最后一次心跳后等待时间上限,单位为s(默认为90s),超时将剔除服务
lease-expiration-duration-in-seconds: 2

1.7 注意

  • Eureka自发布2.x版本后就停止维护了
  • 后面会进入到其它服务注册架构的学习

二、Zookeeper

zookeeper需要先在Linux下进行安装,再在微服务中进行配置才能使用

2.1 安装

2.1.1 单机版

  • zookeeper下载地址
  • 通过tar命令解压后,需要做两件事情
    • 重命名文件夹为zk,进入conf文件夹,复制其中的zoo_sample.cfg为zoo_cfg
    • 修改zoo.cfg中的数据文件夹地址,最后就可以进入zk/bin中启动zookeeper了
  • zookeeper启动命令:./zkServer.sh start
  • zookeeper客户端启动命令:./zkCli.sh

2.1.2 集群版

2.2 微服务构建

2.2.1 服务提供者

  • 首先肯定需要创建maven工程,命名为cloud-provider-payment8004
  • 接着就是配置yml,如下所示
1
2
3
4
5
6
7
8
9
10
11
server:
port: 8004
servlet:
context-path: /

spring:
application:
name: cloud-payment-service
cloud:
zookeeper:
connect-string: 192.168.41.199:2181 #配置单机版的zookeeper服务器地址
  • 然后就是pom文件的配置
1
2
3
4
5
<!-- zookeeper依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
</dependency>
  • 启动类注意添加@EnableDiscoveryClient注解
  • 最后就是创建类,一个启动类和一个controller分别用于启动和测试
1
2
3
4
5
6
7
8
9
10
11
@RestController
@Slf4j
public class PaymentController {
@Value("${server.port}")
private String serverPort;

@RequestMapping(value = "/payment/zk")
public String paymentzk(){
return "springcloud with zookeeper:"+serverPort+"\t"+ UUID.randomUUID().toString();
}
}
  • 最后直接运行启动类,如果配置zookeeper的信息没有问题,就可以打开zookeeper客户端,输入ls /命令查看到对应的服务节点

2.2.2 临时与持久节点

三、Consul

3.1 安装

  • Consul下载地址:Consul官网下载
  • 解压安装包后,直接可以通过cmd运行
  • 开发模式启动:consul agent -dev
  • 服务管理地址:localhost:8500

3.2 配置

  • 和之前几个服务注册中心一样,新建一个maven模块cloud-providerconsul-payment

  • pom引入依赖

1
2
3
4
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
  • yml配置
1
2
3
4
5
6
7
8
9
server:
port: 8006

cloud:
consul:
host: localhost
port: 8500
discovery:
service-name: ${spring.application.name}

3.3 三个服务注册中心的区别

组件名 语言 CAP 服务健康检查 对外暴露接口
Eureka Java AP 可配支持 Http
Consul Go CP 支持 Http/DNS
Zookeeper Java CP 支持 客户端
  • C:Consistency强一致性
  • A:Availability可用性
  • P:Partition tolerance分区容错性

一、概述

1.1 SpringCloud与Springboot版本约束

SpringCloud官网

1.2 环境搭建

1.2.1 创建Maven工程

先创建一个Maven工程,选择好对应的模板后,修改文件名最后完成工程项目创建

为简化项目结构,使一些不重要的配置文件不显示出来,可以再setting->Editor中进行一下配置:

1.2.2 父工程pom文件

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
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>org.example</groupId>
<artifactId>cloud2022</artifactId>
<version>1.0-SNAPSHOT</version>
<modules>
<module>cloud-provider-payment8001</module>
</modules>


<packaging>pom</packaging>

<!--统一管理jar包版本-->
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>12</maven.compiler.source>
<maven.compiler.target>12</maven.compiler.target>
<junit.version>4.13.2</junit.version>
<lombok.version>1.18.24</lombok.version>
<log4j.version>1.2.17</log4j.version>
<mysql.version>8.0.29</mysql.version>
<druid.version>1.2.11</druid.version>
<mybatis.spring.boot.version>2.2.2</mybatis.spring.boot.version>
</properties>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-project-info-reports-plugin</artifactId>
<version>3.0.0</version>
</dependency>
<!--spring boot 2.2.2-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.6.8</version>
<type>pom</type>
<scope>import</scope>
</dependency>

<!--spring cloud Hoxton.SR1-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>2021.0.3</version>
<type>pom</type>
<scope>import</scope>
</dependency>

<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2021.1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
<scope>runtime</scope>
</dependency>
<!-- druid-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>${druid.version}</version>
</dependency>

<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis.spring.boot.version}</version>
</dependency>
<!--junit-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
</dependency>
<!--log4j-->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>${log4j.version}</version>
</dependency>

<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<scope>provided</scope>
</dependency>

<!-- https://mvnrepository.com/artifact/com.alibaba/druid-spring-boot-starter -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>${druid.version}</version>
</dependency>

</dependencies>
</dependencyManagement>

<!--bulid是这样的用springboot默认的build方式-->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<fork>true</fork>
<addResources>true</addResources>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>

1.2.3 构建子模块

右击父工程创建一个maven子模块(不需要用maven模板创建)

子模块的pom文件如下:

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
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>cloud2022</artifactId>
<groupId>org.example</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>cloud-provider-payment8001</artifactId>
<dependencies>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-actuator -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

<!-- https://mvnrepository.com/artifact/org.mybatis.spring.boot/mybatis-spring-boot-starter -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>

<!-- https://mvnrepository.com/artifact/com.alibaba/druid-spring-boot-starter -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
</dependency>

<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>

<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-jdbc -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>


<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>

<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>

</dependencies>

</project>

pom文件配置好后,再在子模块中创建springboot的配置文件application.yml

yml配置内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
server:
port: 8001
servlet:
context-path: /
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
url: jdbc:mysql://localhost:3306/personalsystem?useUnicode=true&characterEncoding=utf8&allowMultiQueries=true&serverTimezone=GMT%2B8&useSSL=false
username: root
password: FLzxSQC1998.Com
driver-class-name: com.mysql.cj.jdbc.Driver

mybatis:
type-aliases-package: com.company.bufan.pojo
mapper-locations: classpath:mapper/*.xml
configuration:
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

配置文件的问题解决完了,接下来就可以创建Springboot的主启动类了,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
package com.atguigu.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class PaymentMain8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentMain8001.class,args);
}
}

1.2.4 注意事项

要注意该maven的Java启动版本,不然会报错,如下:

遇到此问题需要进行以下修改:Setting->Build,Execution,Deployment->Compiler->Java Compiler

此外,还需要检查下项目的File->Project Structure,看项目的Java版本是否正确

检查完成之后,应该就不会报Java版本的错误了

1.2.5 热部署

为避免每次修改代码都要手动重启项目,启动热部署可以在修改代码后自动重启项目

在子模块中添加以下依赖:

1
2
3
4
5
6
7
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-devtools -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>

添加以下配置:

再ctrl+alt+shift+/,进入Registry,启动项目两个选项

最后重启IDEA即可

二、支付模块构建

此支付模块用于记录消费者的支付记录,模块代码同一般的Springboot项目,即:

  • controller
  • mapper
  • pojo
  • service/service.impl

2.1 配置文件

2.1.1 yml配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
server:
port: 8001
servlet:
context-path: /
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
url: jdbc:mysql://localhost:3306/learn?useUnicode=true&characterEncoding=utf8&allowMultiQueries=true&serverTimezone=GMT%2B8&useSSL=false
username: root
password: FLzxSQC1998.Com
driver-class-name: com.mysql.cj.jdbc.Driver

mybatis:
type-aliases-package: com.atguigu.springcloud.pojo
mapper-locations: classpath:mapper/*.xml
configuration:
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

2.1.2 mapper配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<!--绑定命名空间,绑定自己的dao层接口-->
<mapper namespace="com.atguigu.springcloud.mapper.PaymentMapper">

<resultMap id="BaseResultMap" type="com.atguigu.springcloud.pojo.Payment">
<id column="id" property="id" jdbcType="BIGINT"/>
<id column="serial" property="serial" jdbcType="VARCHAR"/>
</resultMap>

<!-- 根据用户名查找用户 -->
<select id="getPaymentById" resultType="payment" parameterType="Long" resultMap="BaseResultMap">
SELECT * FROM learn.payment WHERE id = #{id};
</select>

<!-- 添加一名用户 -->
<insert id="addPayment" parameterType="payment" useGeneratedKeys="true" keyProperty="id">
INSERT INTO learn.payment(serial) VALUE (#{serial});
</insert>

</mapper>

2.2 controller

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
package com.atguigu.springcloud.controller;

import com.atguigu.springcloud.pojo.CommentResult;
import com.atguigu.springcloud.pojo.Payment;
import com.atguigu.springcloud.service.PaymentService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

@RestController
@Slf4j
public class PaymentController {
@Resource
private PaymentService paymentService;

@PostMapping("/payment/create")
public CommentResult create(Payment payment){
int res = paymentService.addPayment(payment);
if(res > 0){
return new CommentResult(res,200,"success");
}
else{
return new CommentResult(null,444,"fail");
}
}

@GetMapping("/payment/get/{id}")
public CommentResult<Payment> getPaymentById(@PathVariable("id") Long id){
Payment payment = paymentService.getPayment(id);

if(payment != null){
return new CommentResult(payment,200,"success");
}else{
return new CommentResult(null,444,"fail");
}
}
}

三、消费者订单模块构建

消费者模块用于调用支付模块服务请求,在spring框架中有RestTemplate框架用于调用服务请求

消费者服务架构如下:

  • config
  • controller
  • pojo

此模块不需要数据库的依赖包,需要在pom中去除这些数据库依赖包,否则会报错

3.1 config

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.atguigu.springcloud.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

@Configuration
public class ApplicationContextConfig {
// 注入RestTemplate对象
@Bean
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}

3.2 controller

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
package com.atguigu.springcloud.controller;

import com.atguigu.springcloud.pojo.CommentResult;
import com.atguigu.springcloud.pojo.Payment;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;

@RestController
@Slf4j
public class OrderController {
private static final String PAYMENT_URL = "http://localhost:8001";

@Resource
private RestTemplate restTemplate;

@PostMapping("/consumer/payment/create")
public CommentResult create(Payment payment){
// 调用RestTemplate中的方法进行调用支付模块请求
return restTemplate.postForObject(PAYMENT_URL+"/payment/create",payment,CommentResult.class);
}

@GetMapping("/consumer/payment/get/{id}")
public CommentResult getPaymentById(@PathVariable("id") Long id){
return restTemplate.getForObject(PAYMENT_URL+"/payment/get/"+id,CommentResult.class);
}
}

3.3 pojo

pojo层直接复制支付模块的pojo层即可

3.4 其他配置

1
2
server:
port: 8080

配置完成后运行即可,在idea下方有Services窗口,可以管理所有的服务模块:

四、工程重构

4.1 公共Maven模块

观察上述项目结构,两个服务在pojo部分存在相同的代码,因此可以提取这些代码,将其放到一个新的公共的maven模块中,其他服务模块可以在pom中引入这个maven项目即可

公共maven模块的依赖包:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
<optional>true</optional>
</dependency>

<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.4</version>
</dependency>
</dependencies>

复制pojo到公共的Maven模块中,注意包地址要保持一致

复制完成后->Maven clean/install打包公共模块->其他模块即可引入此依赖包:

1
2
3
4
5
6
7
<dependency>
<!-- 这个坐标是公共Maven模块的项目坐标 -->
<groupId>com.atguigu.springcloud</groupId>
<!-- 这个是公共Maven模块的项目名称 -->
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>

一、直接插入排序

  • 插入排序基本思想是每次将一个待排序的记录按其关键字大小插入到前面已排好序的子序列中,直到全部记录插入完成
  • 比较次数和移动次数取决于待排序表的初始状态
  • 适用于顺序存储和链式存储的线性表
1
2
3
4
5
6
7
8
9
10
11
12
void insertSort(int[] arr){
int i,j;
int n = arr.length;
for(i=2;i<=n;i++){ // 依次将A[2]~A[n]插入到前面已排序序列
if(A[i]<A[i-1]){ // 若A[i]关键码小于其前驱,将A[i]插入有序表中
A[0]=A[i]; // 复制为哨兵,A[0]不存放元素
for(j=i-1;A[0]<A[j];--j) // 从后往前查找待插入位置
A[j+1]=A[j]; // 向后挪位
A[j+1]=A[0]; // 复制到插入位置
}
}
}

二、折半插入排序

  • 设在待排序表中有一个记录序列V[1], …,v[n]。其中V[1], …, v[i-1]是已经排好序的记录。在插入v[i]时,利用折半搜索法寻找 v[i] 的插入位置,并插入,直到所有记录插入完成
  • 比较次数与待排序表的初始状态无关,仅取决于表中的元素个数n
  • 元素的移动次数未改变,依赖于待排序表的初始状态
  • 是一种稳定的排序算法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void InsertSort(ElemType A[],int n){
int i,j,low,high,mid;
for(i=2;i<=n;i++){ // 依次将A[2]~A[n]插入前面的已排序序列
A[0]=A[i]; // 将A[i]暂存到A[0]
low=1;high=i-1; // 设置折半查找的范围
while(low<=high){ // 折半查找(,默认递增有序)
mid=(low+high)/2; // 取中间点
if(A[mid]>A[0]) // 查找左半子表
high=mid-1; // 查找右半子表
else
low=mid+1;
}
for(j=i-1;j>=high+1;--j)
A[j+1]=A[j]; // 统一后移元素,空出插入位置
A[high+1]=A[0]; // 插入操作
}
}

三、希尔排序

  • 先将待排序表分割成若干形如L[i,i+d,i+2d,…,i+kd]的“特殊”子表,即把相隔某个“增量”的记录组成一个子表,对各个子表分别进行直接插入排序,当整个表中的元素已呈“基本有序”时,再对全体记录进行一次直接插入排序
  • 仅适用于线性表为顺序存储的情况
1
2
3
4
5
6
7
8
9
10
11
12
13
void ShellSort(ElemType A[],int n){
// A[0]只是暂存单元,不是哨兵,当j<=0时,插入位置已到
for(dk=n/2;dk>=1;dk=dk/2){ // 步长变化
for(i=dk+1;i<=n;++i){
if(A[i]<A[i-dk]){ // 需将A[i]插入有序增量子表
A[0]=A[i]; // 暂存在A[0]
for(j=i-dk;j>0&&A[0]<A[j];i-=dk)
A[j+dk]=A[j]; // 记录后移,查找插入的位置
A[j+dk]=A[0] // 插入
}
}
}
}

四、冒泡排序

  • 从后往前(或从前往后)两两比较相邻元素的值,若为逆序(即A[i-1]>A[i]),则交换它们,直到序列比较完。重复这一步骤,直到序列全部有序
  • 每一趟排序都能使一个元素放置在其最终的位置上
  • 比较次数和移动次数取决于待排序表的初始状态
1
2
3
4
5
6
7
8
9
10
11
12
13
void BubbleSort(ELemType A[],int n){
for(i=0;i<n-1;i++){
flag=false; // 表示本趟冒泡是否发生交换的标志
for(j=n-1;j>i;j--){ // 一趟冒泡国产
if(A[j-1]>A[j]){ // 若为逆序
swap(A[j-1],A[j]); // 交换
flag=true;
}
}
if(flag==false)
return; // 本趟遍历后没有发生交换,说明表已经有序
}
}

五、快速排序

  • 在待排序表L[1,2,…,n]中任取一个元素pivot作为基准,通过一趟排序将待排序表划分为独立的两个部分L[1,…,k-1]和L[k+1,…,n],使得L[1,…,k-1]中的所有元素小于pivot,L[k+1,…,n]中的所有元素大于等于pivot,则pivot放在了其最终位置L(k)上。然后分别递归地对两个子表重复上述过程,直到每个部分只有一个元素或为空为止,即所有元素放在了其最终位置上。
  • 每一趟排序都能使一个元素放置在其最终的位置上
  • 快速排序是所有内部排序算法中平均性能最优的排序算法
  • 初始不够对称,会使得快速排序效率降低;元素基本有序,无法发挥长处
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void QuickSort(ElemType A[],int low,int high){
if(low<high){ // 递归跳出的条件
int pivotpos=Partition(A,low,high); // 划分
QuickSort(A,low,pivotpos-1); // 依次对两个子表进行递归排序
QuickSort(A,pivot+1,high);
}
}

int Partition(ElemType A[],int low,int high){ // 一趟划分
ElemType pivot=A[low]; // 将当前表中第一个元素设为枢轴,对表进行划分
while(low<high){ // 循环跳出条件
while(low<high&&A[high]>=pivot) --high;
A[low]=A[high]; // 将比枢轴小的元素移动到左端
while(low<high&&A[low]<pivot) ++low;
A[high]=A[low]; // 将比枢轴大的元素移动到右端
}
A[low]=pivot; // 枢轴元素存放到最终位置
return low; // 返回存放枢轴的最终位置
}

六、简单选择排序

  • 每一趟(如第i趟)在后面n-i+1(i=1,2…,n-1)个待排序元素中选取关键字最小的元素,作为有序子序列的第i个元素,直到第n-1趟做完,待排序元素只剩下一个,就不用再选了
  • 移动次数与序列的初始状态有关;比较次数与序列的初始状态无关
1
2
3
4
5
6
7
8
void SelectSort(ElemType A[],int n){
for(i=0;i<n-1;i++){ // 一共进行n-1趟
min=i; // 记录最小元素位置
for(j=i+1;j<n;j++) // 在A[i,...,n-1]中选择最小的元素
if(A[j]<A[min]) min=j; // 更新最小元素位置
if(min!=i) swap(A[i],A[min]); // 封装的swap()函数共移动元素3次
}
}

七、堆排序

7.1 大根堆/小根堆的定义

  • n个关键字序列L[1…n]成为堆,当且仅当该序列满足:
    • (1)L(i)>=L(2i)且L(i)>=L(2i+1)或
    • (2)L(i)<=L(2i)且L(i)<=L(2i+1) (1<=i<=n/2取上界)
  • 可以将一维数组视为一颗完全二叉树,满足条件(1)的称为大根堆,大根堆的最大元素放在根结点,且其任意非根结点值小于等于其双亲结点值
  • 满足条件(2)的堆称为小根堆,小根堆的定义刚好相反,根结点是最小元素

7.2 堆排序的思路

  • 首先将存放L[1,…,n]中的n个元素建成初始堆,由于堆本身的特点(以大根堆为例),堆顶元素就是最大值。输出堆顶元素后,通常将堆底元素送入堆顶,此时根结点已不满足大根堆的形式,堆被破坏,将堆顶元素向下调整使其继续保持大根堆的性质,再输出堆顶元素。如此重复,知道堆中仅剩一个元素为止

八、归并排序

8.1 二路归并排序

  • 假定待排序表含有n个记录,则可将其视为n个有序的子表,每个子表的长度为1,然后两两归并,得到n/2(取上限)个长度为2或1的有序表;继续两个归并,如此重复,直到合并为一个长度为n的有序表为止
    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
    ElemType *B=(ElemType *)malloc((n+1)*sizeof(ElemType));     // 辅助数组

    void MergeSort(ElemType A[],int low,int high){
    if(low<high){
    int mid=(low+high)/2; // 从中间划分两个子序列
    MergeSort(A,low,mid); // 从左侧子序列进行递归排序
    MergeSort(A,mid+1,high); // 对右侧子序列进行递归排序
    Merge(A,low,mid,high); // 归并
    }
    }

    void Merge(ELemType A[],int low,int mid,int high){
    // 表A的两段A[low...mid]和A[mid+1...high]各自有序,将它们合并成一个有序表
    for(int k=low;k<=high;k++)
    B[k]=A[k]; // 将A中所有元素复制到B中
    for(i=low,j=mid+1,k=i;i<mid&&j<high;k++){
    if(B[i]<=B[j]) // 比较B的左右两段中的元素
    A[k]=B[i++]; // 将较小值复制到A中
    else
    A[k]=B[j++];
    }
    while(i<=mid)
    A[k++]=B[i++]; // 若第一个表未检测完,复制
    while(j<=high)
    A[k++]=B[j++]; // 若第二个表未检测完,复制
    }

九、基数排序

一、循环与网格图

1.1 音的循环

在物理上声音是通过震动产生的,而振动必然存在频率,频率越高,振动越快,声音听起来越高,相反声音听起来就越低

往往一个纯八度关系,比如$1$到$\dot{1}$,它们的频率关系为两倍关系,听感上两个音会完全融合

有了频率的概念,人们就逐渐发现了声音的规律,即十二平均律。十二平均律将一个八度分成十二份,一共有十二个音。在频率上,相邻两个音的频率之比为$\sqrt[12]{2}$,相邻两个音之间的距离称为半音,即半音阶。

从此,人们开始对音进行形式化表示,即C/D/E/F/G/A/B系统,音名循环图如下:

1.2 音程

  • 根音:和弦的主音,不一定是最低音
  • 冠音:和弦中的最高音
  • 度数:衡量音程的单位
  • 复音程:超过八度的音程关系

1.3 和声学

和声学解决的问题是

  • 用什么和弦
  • 怎么用和弦

1.4 五度圈

从C音后每隔纯五度取一个音,就会得到一个音阶的循环

观察1.1部分的音阶图可以发现,G->C->G,顺时针C到G是纯五度关系,逆时针C到G是纯四度关系

1.5 三和弦标记法

在五度圈的基础上,使用平面图来快速进行三和弦推导

观察上图,上三角均为大三和弦,下三角均为小三和弦

将上图进行一下扩展,如下图,根据平面图可以快速得到五度圈、大三度循环和小三度循环

二、大调和声

2.1 大调的三和弦

调内三和弦:使用调内音组成的和弦

调外和弦:包含调外音的和弦

2.2 和弦功能属性

以自然大调为例,每个大调的$I$级和弦都是主和弦T(Tonic chord),是和声的中心,其作用叫做主功能

在调性体系下,主和弦的组成音也是所有音中最稳定的,而其他和弦根据组成音的具体情况,产生各自不同的趋向性,这便是和声功能的根源所在。

大调的V级和弦,称为属和弦D(Dominant chord),代表属功能(Dominant chord),大调五级音又称属音,位于主音上方纯五度(下方纯四度),属和弦三个音都有朝向主音的倾向,地位仅次于主和弦。

大调的IV级和弦,称为下属和弦S(Subdominant chord),IV级音在主音下纯五度(上纯四度),所以叫下属音(Subdominant)。下属和弦具有下属功能。与属和弦相比,下属和弦的趋向性较弱,稳定性略高。

主和弦T、属和弦D、下属和弦S合称为正三和弦

正三和弦以外的和弦称为副三和弦,即ii、iii、vi、vii四个和弦,在C大调中就是Dm、Em、Am、B

  • 注:和弦是如何分功能组的?考虑主功能组的一级和弦135,其中三级和弦357包含了两个一级和弦的构成音35,六级和弦包含了两个一级和弦的构成音61,所以在和弦色彩上是比较接近的,因此可以分为相同的功能组中

2.3 和声进行的基本规律

大调进行一般会从I级开始,经过下属和弦、属和弦之后再回归到主和弦

  • 主和弦I所代表的主功能,关键词是“稳定”。它作为调的核心,产生一种基础的安稳、静止感。如果音乐没有结束在主和弦,通常会给人没有结束的感觉。
  • 下属和弦IV代表下属功能,关键词是“运动”。它的稳定性介于主、属之间,通常充当二者的中间环节,使音乐的张力逐渐积累。I—IV的进行常有一种启动、上升感
  • 以属和弦V为代表的属功能,关键词是“紧张”,在三个功能组中最不稳定,音乐的张力达到最大,制造迫切进行到主功能的感觉。V—I常被形容为有制动、下降感,最后到达主和弦,张力得到释放,产生音乐独有的美感。

和声第一定律:和声的变化是起承转合的,即稳定->不稳定->稳定

2.4 和声进行格式

2.5 和弦编配

和声第二定律:在为旋律编配和弦时,和弦要包含旋律中的一部分或全部的音,不能完全包含时,和声一般也要照顾到尽量多的旋律音,优先考虑强拍与长音

和弦外音(外音):不包含在和弦音中的旋律音

2.5.1 和弦转换时机

在流行歌曲中,和弦发生转换的时机一般有以下几种情况

  • 重音
  • 乐句的结尾
  • 前后音程对比强烈的地方
  • 一个乐句到另一个乐句之间

一般的流行歌曲,一个乐句中最多会进行一次和弦转换,换言之就是一个乐句一般包含一到两个和弦

2.5.2 判断节奏、调式调性

2.5.3 确定开头、解位和断句

  • 半终止
  • 终止式

2.5.4 填充乐句

2.6 常用和声套路

  • 15634125:忽然之间

  • 4536251:可惜不是你

  • 4536
  • 1625
  • 162536251:突然的自我
  • 1645:花
  • 1564

2.7 终止式的种类

2.7.1 收拢性终止

收拢性终止:落在主和弦的终止式

  • 正格终止:“属->主”的进行格式,典型的如$V-I$,V可以换成导三和弦、导七和弦等其他属功能和弦,这种终止式的结束感比较强烈。

    • 前面再加上下属功能组和弦,如$IV-V-I、II-V-I$,这种结构用到了调内所有的音和所有功能组和弦,称为完全正格终止复式正格终止
  • 变格终止:从下属功能组进行主和弦的终止式,即$IV-I$,使用不如正格终止频繁,教会中唱诗班较多使用,变格终止有一种平静、安详、甜美的感觉

  • 补充终止/扩充终止:在乐句、乐段或全曲末尾,于明确终止之后,再额外添加一段短小的终止作为“尾巴”。补充的终止可以是$VI-I、VII-I、IV-V-I、II-V-I$

2.7.2 开放性终止

开放性终止:不落在主和弦上的终止式,或称为半终止

  • 半终止:不稳定音级收束,如$V-IV$,音响效果上给人一种戛然而止、意犹未尽的感觉,经常用在上半句或开放性乐段的末尾
  • 阻碍终止/伪终止:以不稳定和弦代替主和弦,典型的有$V-VI$,它是把预期的“终止”打断,然音乐延续下去。作用是推迟终止、扩展乐段

2.7.3 其他

  • PAC:完全正格终止,V->I,和弦都是原位和弦,且旋律结束在主音上
  • IAC:不完满正格终止,可以是VII->I,或者使用转位和弦、或者旋律音没有结束在主音上,通过这些可以降低终止感
  • HC:半终止,停止在V级和弦上音乐不再发展
  • 终止感:PAC>IAC>HC

2.8 大调七和弦

2.8.1 大七和弦

2.8.2 属七和弦(大小七和弦)

2.8.3 四度圈

三、离调与转调

3.1 转调

3.2 离调

离调可以理解为转调的一种特殊形式,但要注意区分它和转调的关系。离调具体是指音乐作品中出现了某一临时变化音或临时带变音的和弦而引起了调性色彩的改变,而且这种改变只作了短暂停留之后又回到了原来的调性上,这样的调性变化就称作离调。