NanYin的博客

记录生活点滴


  • Home

  • About

  • Tags

  • Categories

  • Archives

  • Search

设计模式之装饰者模式

Posted on 2019-06-10 | In 设计模式
Words count in article: 607 | Reading time ≈ 2

设计模式之装饰者模式

可以动态地将附加职责附加给对象。装饰器为子类化的方式提供了灵活的替代扩展功能。比如做梦梦到了交个女朋友,她的发型,发色,穿着都是根据场景做梦梦到的时候附加上去的。这时,就应该对“女朋友”这个对象实行装饰者模式来动态的、灵活的拓展。

应用场景

  1. 动态且透明地向各个对象添加职责的同时不影响其他对象。
  2. 灵活的可撤回职责。
  3. 有时候需要很多子类来用于支持每种功能的组合,这样每次增加一种功能拓展就要增加很多子类的情况。显然这样是不符合要求的。所以遇到这种多功能拓展时,需要使用装饰者模式来解决。

装饰者模式结构

装饰者模式可以大致分为四种结构(本例子中的抽象构件和抽象装饰类为一个)抽象构件,具体构件,抽象装饰类,实际装饰类。

装饰者模式

代码

第一部分:抽象构件与抽象装饰类

1
2
3
4
5
6
7
8
9
10
11
12
// 制造一个假想的“女朋友”
public abstract class AbstractGirl {

public void dyeHair() {
}

public abstract void wearClothes();

public void pack() {
}

}

第二部分:实现组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 基本的功能实现
public class NormalGirl extends AbstractGirl {
@Override
public void dyeHair() {
System.out.println("normal hair");
}

@Override
public void wearClothes() {
System.out.println("normal clothes");
}

@Override
public void pack() {
}
}

第三部分:附加装饰类

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
// 工作中的女孩 穿正装
public class WorkGirl extends AbstractGirl {

AbstractGirl abstractGirl;

public WorkGirl(AbstractGirl abstractGirl) {
this.abstractGirl = abstractGirl;
}

@Override
public void wearClothes() {
abstractGirl.wearClothes();
System.out.println("wearClothes after : ");
System.out.println("wear formal clothes");
}
}

//实际出去逛街的女孩应该是这样的
public class FashionGirl extends AbstractGirl {

AbstractGirl abstractGirl;

public FashionGirl(AbstractGirl abstractGirl) {
this.abstractGirl = abstractGirl;
}

@Override
public void dyeHair() {
abstractGirl.dyeHair();
System.out.println("dyeHair after : ");
System.out.println("dye blue hair");
}

@Override
public void wearClothes() {
abstractGirl.wearClothes();
System.out.println("wearClothes after : " );
System.out.println("wear rock type clothes");
}

@Override
public void pack() {
abstractGirl.pack();
System.out.println("wearClothes after : " );
System.out.println("take fashion pack");
}
}

最后客户端的调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14

public class App {
public static void main(String[] args) {
//先制造一个整体组件
AbstractGirl normalGirl = new NormalGirl();
normalGirl.dyeHair();
// 为组件添加装饰,比如这里加了个时尚的包包
AbstractGirl fashionHair = new FashionGirl(normalGirl);
fashionHair.pack();
//穿一套正装
AbstractGirl workClose = new WorkGirl(fashionHair);
workClose.wearClothes();
}
}

设计模式之桥接模式

Posted on 2019-06-03 | In 设计模式
Words count in article: 600 | Reading time ≈ 2

设计模式之桥接模式

目的在于将抽象与其实现分离,以便两者可以独立变化。独立变化的同时能够根据抽象类的对象关联从而能够将两个继承结构联动起来。就像在两个结构之间建立个桥梁一样进行通信,所以叫桥接模式。

应用场景

  1. 如果想避免抽象类与实现的永久绑定,可以在运行时间选择和切换实现类。
  2. 抽象类和接口都应该通过子类来进行拓展,在桥接模式中,可以使用子类来进行组合的同时能够独立拓展他们。
  3. 接口的实现的变化对客户端无影响。
  4. 如果想要在多个对象类中共享实现,并且避免让客户端感知到。

桥接模式结构图

桥接模式

代码

主要分为四部分,1.抽象类 2.抽象实现类 3.接口类 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
37
38
39

// 抽象类与抽象实现类

public abstract class Shape {

Colors colors;

Shape(Colors colors) {
this.colors = colors;
}

public abstract void buildShape();
}

// 圆形实现类
public class ShapeCircle extends Shape {

public ShapeCircle(Colors colors) {
super(colors);
}

@Override
public void buildShape() {
System.out.println("\n first step : build circle\n and second step:");
colors.paint();
}
}
//方形实现类
public class ShapeSquare extends Shape {
public ShapeSquare (Colors colors) {
super(colors);
}

@Override
public void buildShape() {
System.out.println("\n first step : build Square\n and second step:");
colors.paint();
}
}

抽象方法中引用了Colors类变量,使用实现类中的buildShape方法实现具体功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public interface Colors {
public void paint();
}

public class ColorBlue implements Colors {
@Override
public void paint() {
System.out.println(" print blue !!");
}
}

public class ColorRed implements Colors {
@Override
public void paint() {
System.out.println(" paint inner with red !!");
}
}

通过使用Color接口,实现类实现Colors中的paint方法实现Color

1
2
3
4
5
6
7
8
public class App {
public static void main(String[] args) {
Shape circle = new ShapeCircle(new ColorBlue());
circle.buildShape();
Shape square = new ShapeSquare(new ColorRed());
square.buildShape();
}
}
1
2
3
4
5
6
7
8
9
结果:

first step : build circle
and second step:
print blue !!

first step : build Square
and second step:
paint red !!

上面的例子中,颜色和图形是两个独立不同的维度,两个可以分别变化。将两个维度设计为两个不同的继承的结构,在两个结构之间使用在抽象类中的关联来达到链接的目的,这个链接成为两个继承结构通信的桥梁。所以为桥接模式。

设计模式之原型模式

Posted on 2019-06-02 | In 设计模式
Words count in article: 328 | Reading time ≈ 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
/**
* 一个细胞
* @Author nanyin
* @Date 23:49 2019-06-01
**/
public class Cell implements Cloneable{

private String name;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public Cell(){
}

private Cell(String name) {
this.name = name;
}

@Override
protected Cell clone() throws CloneNotSupportedException {
super.clone();
return new Cell(name);
}
}

客户端程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class App {
@Test
public void testPrototype(){
Cell cell = new Cell();
cell.setName("org");
try{
Cell cloneCell = cell.clone();
Assert.assertEquals("org",cloneCell.getName());//pass
Assert.assertEquals(cell,cloneCell);//not pass
}catch (CloneNotSupportedException e){
e.printStackTrace();
}
}
}

设计模式之适配器模式

Posted on 2019-06-02 | In 设计模式
Words count in article: 593 | Reading time ≈ 2

设计模式之适配器模式

我们实际中会用到两个不同接口的类的通信,在不修改两个类的前提下,使用新的中间类来完成衔接的过程,这个中间件就是适配器.可以让两个完全不同的接口相互转化.

适配器模式允许在适配器中包装其他不兼容的对象,以使其与另一个类兼容。

就如同生活中买了港版的手机,但是附赠的是三脚插头,而大陆上用的是两脚的,需要一个适配器来将三角插头转换为两脚的。其中这个中间件就是适配器。

应用场景

  • 当你想要这个类,但是类中的接口与想用的接口并不匹配
  • 当想要去创建一个可重用的类,让它与不相关或不可预见的类合作,这个类不必要有兼容的接口
  • 当你需要一系列的子类的时候,如果想要调整这些子类的接口方法的时候,对每一个接口子类化的这种方法是不可行的。这时就可使用适配器适配这些子类的夫类。

结构图

适配器模式图

使用client调用适配器,适配器继承与三脚插头,依赖于二脚插头。这样就可以使用三脚插头的接口调用二脚插头的方法。

代码

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
/**
* 三角插头
* @Author nanyin
* @Date 11:13 2019-06-02
**/
public class ThreeLeggedPlug {
public void charge(){
System.out.println("use three legged plug");
}
}

/**
* 二脚插头
* @Author nanyin
* @Date 11:13 2019-06-02
**/
public class TwoLeggedPlug {
public void fastCharge(){
System.out.println("use two legged plug for fast charge !");
}
}
//适配器 继承三角插头
public class TwoLeggedPlugAdapter extends ThreeLeggedPlug {
TwoLeggedPlug twoLeggedPlug;
// 依赖于二脚插头
public TwoLeggedPlugAdapter() {
twoLeggedPlug = new TwoLeggedPlug();
}
//使用三角插头的charge方法,调用二脚插头的fastCharge方法
@Override
public void charge() {
twoLeggedPlug.fastCharge();
}
}

//某S9港版手机
public class S9Plus extends ThreeLeggedPlug {
ThreeLeggedPlug threeLeggedPlug;

public S9Plus(ThreeLeggedPlug threeLeggedPlug) {
this.threeLeggedPlug = threeLeggedPlug;
}

@Override
public void charge() {
threeLeggedPlug.charge();
}
}

//客户端方法
public class App {
@Test
public void testCharge() {
// 不使用适配器 使用类三角插头
ThreeLeggedPlug s9Plus = new S9Plus(new ThreeLeggedPlug());
s9Plus.charge();
// 使用二脚插头的适配器
ThreeLeggedPlug s9PlusUseTwoLeggedPlug = new S9Plus(new TwoLeggedPlugAdapter());
s9PlusUseTwoLeggedPlug.charge();
}
}
1
2
3
4
5
6
结果:

use three legged plug
use two legged plug for fast charge !

Process finished with exit code 0

设计模式之组合模式

Posted on 2019-06-01 | In 设计模式
Words count in article: 767 | Reading time ≈ 3

设计模式之组合模式

将对象组合成树结构以表示部分整体层次结构。当使用树结构的上层和下层组件属性可能差别很大,所以说一般情况下需要对这两类对象进行分别处理, 但Composite允许客户端统一处理单个对象和对象组合。也就是说可以将叶子组件与整体进行一致性处理,实现的方法是组件和整体实现相同接口。

应用场景

  1. 想要表示对象的部分与整体之间的层次结构
  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
// 这个抽象组件即完成了抽象组件的功能,又完成了“树枝”的功能,能够链接各个叶子节点
public abstract class Component {
String name;
Integer areaLevel;
List<Component> list = new LinkedList<>();

public Component(String name, Integer areaLevel) {
this.name = name;
this.areaLevel = areaLevel;
}

public void printName(){
System.out.println("Name : "+name);
};

public void printLevel(){
System.out.println("Area Level :" + areaLevel);
};
// 这里在抽象类的方法中直接打印子组件(ps:应统一递归输出子组件的子组件)
public void printAll(){
for (Component p:list
) {
System.out.println("子组件:"+ p.name);
p.printName();
p.printLevel();
}
}
public void add(Component component){
list.add(component);
};
}

第二部分:树叶构件

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 Country extends Component {
List<Province> provinceList = new LinkedList<>();

public Country(String name, Integer areaLevel) {
super(name, areaLevel);
}

@Override
public void printName() {
System.out.println("Country Name :" + this.name);;
}

public void add(Province province){
provinceList.add(province);
}

public void printAll(){
System.out.println("当前组件:"+name + "等级:"+ areaLevel);
for (Province p:provinceList
) {
p.printAll();
}
}
@Override
public void printLevel() {
System.out.println("Country Level :" + this.areaLevel);
}
}
// 省级别树叶节点
public class Province extends Component{

List<City> cityList = new ArrayList<>();

public Province(String name, Integer areaLevel) {
super(name, areaLevel);
}

@Override
public void printName() {
System.out.println("Province Name :" +this.name);
}

public void add(City city){
cityList.add(city);
}

public void printAll(){
System.out.println("当前组件:"+name + "等级:"+ areaLevel);
for (City p:cityList
) {
p.printAll();
}
}
@Override
public void printLevel() {
System.out.println("");
}
}
// 市级别树叶节点

public class City extends Component {

public City(String name, Integer areaLevel) {
super(name, areaLevel);
}

@Override
public void printName() {
System.out.println("City Name :" + this.name);
}

@Override
public void printLevel() {
System.out.println("City Level " + this.areaLevel);
}

public void printAll(){
System.out.println("组件:"+ this.name);
}
}

第三部分:客户端

1
2
3
4
5
6
7
8
9
10
public class App {
public static void main(String[] args) {
Country zhongguo = new Country("china", 0);
City langfang = new City("langfang", 2);
Province hebei = new Province("hebei", 1);
zhongguo.add(hebei);
hebei.add(langfang);
zhongguo.printAll();
}
}
1
2
3
当前组件:china 等级:0
当前组件:hebei 等级:1
组件:langfang

设计模式之建造者模式

Posted on 2019-06-01 | In 设计模式
Words count in article: 878 | Reading time ≈ 3

设计模式之建造者模式

建造者模式是创建型设计模式。将复杂对象的构造与其表示分开,以便相同的构造过程可以创建不同的表示。

在玩游戏创建角色的时候,系统默认一个初始人物,开始创建时,需要输入角色的名称,选择人物的性别。之后可以自定义武器,技能等等。拥有的人物可能各不相同,但是生成人物的流程是相同的。这个流程就是”建造过程“,这种构建建造过程的模式,就是建造者模式。

使用建造者模式:

  • 可以对用户屏蔽构建的具体细节

  • 用户只需给出复杂对象的内容和类型可以创建出对象

  • 依据构造流程构建出复杂对象

解决了什么问题

建造者模式避免了在构建相同的对象,对象内具有不同的属性时,造成的构造器污染(constructor pollution)。方便的创建一系列不同的相同类型的对象。解决了可伸缩性的构造器反设计模式 (telescoping constructor anti-pattern

应用场景

  • 对象足够复杂,创建这个对象的方法应与创建这个对象的组件和组装方式分开.
  • 当构造对象时要求对这个对象具有不同的行为.

建造者模式结构

  1. 抽象Builder接口,定义构造流程结构(非必须)
  2. 实现Builder实现类,实现复杂对象构造结构
  3. Product 具体复杂实体类,也就是需要构建的结构
  4. Director 指挥者,调用Builer进行构建的指挥者(非必须)

建造者模式类图

上图为建造者模式类图:其中CharacterBuilder 和 Characters 中的属性相同,CharacterBuilder 存在的目的就是多种构造不同行为的Characters .

具体可以看下面代码实例

代码

角色实体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Characters {
// 角色构造器内有多个角色属性
private String name;
private int age;
private String sex;
private String skill;
private String weapon;
// 使用构造器来构造实体 其中实体属性有builder中来
public Characters(CharacterBuilder builder) {
this.name = builder.getName();
this.age = builder.getAge();
this.sex = builder.getSex();
this.skill = builder.getSkill();
this.weapon = builder.getWeapon();
}

public void showCharacterBoard(){
System.out.println("人物 : " + this.name + " 年龄 :" + this.age + " 性别:" + this.sex + " 技能:" +this.skill + " 武器:" + this.weapon);
}
}

角色构建器

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
public class CharacterBuilder {
// 与角色实体保持相同的属性
private String name;
private int age;
private String sex;
private String skill;
private String weapon;

public String getName() {
return name;
}

public int getAge() {
return age;
}

public String getSex() {
return sex;
}

public String getSkill() {
return skill;
}

public String getWeapon() {
return weapon;
}
//构造基本属性的构造器
public CharacterBuilder buildBasicAttributes(String name , int age, String sex){
//保证姓名不能为空
if(name == null || "".equals(name)){
throw new IllegalArgumentException("name can not be empty ");
}else {
this.name = name;
this.age = age;
this.sex = sex;
}
return this;
}

public CharacterBuilder buildSkill(String skill){
this.skill = skill;
return this;
}

public CharacterBuilder billdWeapon(String weapon){
this.weapon = weapon;
return this;
}

//生成角色实体
public Characters build(){
return new Characters(this);
}

}

客户端类:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class App {
@Test
public void testBuildCharacter(){
// 链式调用,最后使用build方法新建一个角色实体
Characters characters = new CharacterBuilder()
.buildBasicAttributes("jack",18,"man")
.buildSkill("Emission laser")
.billdWeapon("Laser Cannon")
.build();
//打印角色属性面板
characters.showCharacterBoard();
}
}

输出结果:

1
2
3
Connected to the target VM, address: '127.0.0.1:57347', transport: 'socket'
人物 : jack 年龄 :18 性别:man 技能:Emission laser 武器:Laser Cannon
Disconnected from the target VM, address: '127.0.0.1:57347', transport: 'socket'

设计模式之单例模式

Posted on 2019-05-30 | In 设计模式
Words count in article: 861 | Reading time ≈ 3

单例模式

意在保证一个类只有一个实例,并提供一个全局访问点.

通过将类的构造器私有化,只向外提供一个单独的创建唯一对象的方法来实现一个类只创建一个对象.主要有三种实现 1. 饿汉模式 2. 懒汉模式 3.使用枚举实现单例.下面通过具体实例来分析如何在多线程的情况下仍能满足单例模式创建对象.

饿汉模式

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Singleton {
private Singleton(){};
//1. 使用静态变量
// private static Singleton singleton = new Singleton();
//2.使用静态代码块 未使用时造成内存空间浪费
private static Singleton singleton;
static {
singleton = new Singleton();
}
public Singleton newInstance(){
return singleton;
}
}

这种方式不会影响到多线程的线程安全问题,因为类的装载机制在初始化对象的时候是保证不会有第二个线程进入的。但是有个很大的弊端是他不是lazy-loading的,这回产生资源的浪费,比如创建完对象后,自始至终没用过。所以不推荐使用

懒汉模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Singleton {
private void Singleton(){}
private static Singleton singleton;

// 普通的线程不安全的
public Singleton newInstance(){
if(singleton == null){
singleton = new Singleton();
}
return singleton;
}

//2.静态内部类 一方面能够达到lazy-loading的效果,另一方面能够保证线程安全,因为jvm保证初始化的时候别的线程是不能进入的
private static class newSingleton{
private static final Singleton INSTANCE = new Singleton();
}

public Singleton newInstanceInnerClass(){
return newSingleton.INSTANCE;
}
}

上面代码的第一种是不能保证线程安全的,多线程下会导致失效。而第二种使用静态内部类的方式实现能够实现线程安全得宜于类的加载机制,类似饿汉模式。但同时具有lazy-Loading的特性。是常用的单例模式用法。

double-check双重锁

双重锁结构只是为了满足多线程安全而建立的,是一种特殊的懒汉模式,思想同懒汉模式相同.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class DoubleCheck {
private void DoubleCheck(){}
private volatile DoubleCheck doubleCheck; //使用volatile保证原子性,防止重排序
public DoubleCheck newInstance(){
if(doubleCheck == null){
// 当两个线程同时到这步 a先进同步块,在a进入后获得instance后,b获得锁,进入同步块,
// 这时候下一个判断就起到作用了,这时候的doubleCheck不为空,
// 直接return,否则又️新建了一个对象
synchronized (DoubleCheck.class){
if(doubleCheck == null){
doubleCheck = new DoubleCheck();
}
}
}
return doubleCheck;
}
}

实际上double-check也是懒汉模式的一种,能够保证线程安全。很完美。。

使用枚举创建单例

在stackoverflow中有一个问题What is an efficient way to implement a singleton pattern in Java?

这里的回答引用到了Effective Java 作者 Joshua Bloch对于单例的处理方式:使用枚举

1
2
3
4
5
6
7
8
public enum SingletonEnum {
INSTANCE;
private final String[] favoriteSongs =
{ "Hound Dog", "Heartbreak Hotel" };
public void printFavorites() {
System.out.println(Arrays.toString(favoriteSongs));
}
}

这种方法等同与使用public的构造方法,并且比上面的更加简洁,并且即使面对复杂的反射与序列化也能够保证单一的实例.虽然现实代码中应用的较少,但是这种使用枚举类型是创建单例的最佳方法.

1
2
3
4
5
public void testEnum() {
SingletonEnum singletonEnum = SingletonEnum.INSTANCE;
SingletonEnum singletonEnumCopy = SingletonEnum.INSTANCE;
Assert.assertEquals(singletonEnum, singletonEnumCopy); //通过
}

使用idea对spring boot应用进行热部署

Posted on 2019-05-29 | In 工具
Words count in article: 209 | Reading time ≈ 1

使用idea对spring boot应用进行热部署

POM

在pom.xml中添加maven依赖

1
2
3
4
5
6
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional> <!-- 这行必填 -->
<scope>runtime</scope>
</dependency>

并且在后面的插件配置中配置dev

1
2
3
4
5
6
7
8
9
10
11
12
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<!--如果没有该项配置,devtools不会起作用,即应用不会restart -->
<fork>true</fork>
</configuration>
</plugin>
</plugins>
</build>

IDEA

在IDEA中添加配置如下

  1. 打开 setting ,搜索 compiler 后,把 Build Project automaticial 选项勾上

compiler

  1. 使用 ctrl+shift+A 快捷键打开窗口,并搜索 Registry... 。勾选 compiler.automake.allow.when.app.running

running

利用springboot构建jpa+springdata+rest应用的基本配置过程

Posted on 2019-05-29 | In Spring
Words count in article: 1k | Reading time ≈ 5

利用springboot构建jpa+springdata+rest应用的基本配置过程

前述

利用SpringBoot整合springData+JPA应用非常方便,所以本着学习的目的来构建一个基本的web应用,配置起来非常简单。下面来说说配置过程和踩过的坑。

过程

后端配置

因为我使用的是IDEA的spring initalizer,勾选如下:

  • Rest Repositories

  • Thymeleaf

  • JPA

  • H2

  • Lombok 需要ide下载插件使用

以前在配置spring MVC接口时,往往会浪费很长时间配置rest地址,springdata解决了这个一遍又一遍麻烦的过程。

首先配置实体类

任何基于Spring Data REST的应用程序的基石都是域对象。其中的@Id等都是JPA语法,具体用法可以看上一篇文章。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Entity
@Getter
@Setter
@Table(name = "user")
public class User {

@Id
private Long id;

@Column(name = "username",nullable = true,length = 255)
private String username;

@Column(name = "password",nullable = true,length = 255)
private String password;

@OneToOne()
@JoinColumn(name = "person_id",nullable = false)
private Person person;
}

定义一个存储库

Spring Data REST应用程序的另一个关键部分是创建相应的存储库定义

1
2
public interface UserRepository extends JpaRepository<User,Long> {
}

配置service和controller

1
2
3
4
5
6
7
8
9
public class UserServiceImpl implements UserService {
@Autowired
UserRepository userRepository;
@Override
public void save(User user) {
user.setPassword(...); //定义代码
userRepository.save(user);
}
}

配置数据库和application.properties

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 驱动配置信息
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.url = jdbc:mysql://127.0.0.1:3306/jreact?useUnicode=true&characterEncoding=utf-8
spring.datasource.username = root
spring.datasource.password = 123456
spring.datasource.driverClassName = com.mysql.jdbc.Driver
#非严格的html解析
spring.thymeleaf.mode =LEGACYHTML5
# 彩色输出
spring.output.ansi.enabled=DETECT
# 设置默认引擎
spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect
#配置自动建表:updata:没有表新建,有表更新操作,控制台显示建表语句
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
#配置spring data rest的基地址
spring.data.rest.base-path=/api

启动应用

访问 localhost:8080/api/users 可以查看到如下信息,说明配置成功了。只是表中还没有数据,另外表已经由自动建好,以后如果更新,直接更新程序就好了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
{
"_embedded" : {
"users" : [ ]
},
"_links" : {
"self" : {
"href" : "http://localhost:8080/api/users{?page,size,sort}",
"templated" : true
},
"profile" : {
"href" : "http://localhost:8080/api/profile/users"
}
},
"page" : {
"size" : 20,
"totalElements" : 0,
"totalPages" : 0,
"number" : 0
}
}

前端配置

使用react框架作为前端框架,首先下载create-react-app作为开发的脚手架。这里我选用的是meterial-UI的create-react-app,按照文档中的说明来下载并安装。

配置proxy

在src目录下新建名称为setupProxy.js的文件,并在文件中写入如下内容:

1
2
3
4
5
6
const proxy = require('http-proxy-middleware');
module.exports = function(app) {
app.use(proxy('/api/',
{ target: 'http://localhost:8080/' }
));
}

其中/api/为访问后端的基地址,target为访问后端的主机地址和端口号

配置router

使用npm下载 react-router-dom后,在src目录下新建router文件夹,并创建baseRouter.js文件,引入刚下载的包,基本的文件内容如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import React from "react";
import { BrowserRouter as Router, Route, Link,Switch } from "react-router-dom";
import Index from "../pages/index"
import SignIn from "../pages/login/signIn"
import Error404 from "../pages/error/404"
const About = () => <h2>About</h2>;
const Users = () => <h2>Users</h2>;

const AppRouter = () => (
<Router>
<div>
<Switch>
<Route exact path="/" component={Index} />
<Route path="/SignIn" component={SignIn} />
<Route path="/About" component={About} />
{/* when none of the above match, <NoMatch> will be rendered */}
<Route component={Error404} />
</Switch>
</div>
</Router>
);
export default AppRouter;

并最后在index.js入口文件中写入

1
ReactDOM.render(<AppRouter />, document.getElementById('root'));

这样可以访问localhost:3000的时候可以访问到Index文件内容。

设计模式之工厂模式

Posted on 2019-05-29 | In 设计模式
Words count in article: 2k | Reading time ≈ 7

设计模式之工厂模式

工厂模式分为简单工厂模式,工厂方法模式,抽象工厂方法模式三种,但是其中简单工厂方法其实不在23中设计模式之中,都属于创建型的设计模式。

简单工厂模式

简单工厂方法其实不属于工厂模式的,他只是一种设计思想,是一种对接口的编程的习惯。简单工厂抽象出一个公共的接口,不同的对象实现这个公共的接口。最后定义一个工厂类,根据不同的条件来返回不同类型的实例.

简单工厂的使用场景

  1. 创建对象类型相同,但实现不同,最重要的一点就是创建对象的类型数目较小
  2. 创建过程,对象细节实现客户端不关心

举个简单的例子,就拿在游戏中准备武器材料合成武器这个场景来展开,我需要刀、剑、斧等武器。我需要准备武器的材料和具体的设计图,交给武器制造者。具体怎么打造武器,我并不用关心,最后有一个成果出来就没问题。

这里的制造者就是一个工厂,根据一个设计图就能够制造很多相同的武器。不同的设计图制造不同的武器类型。理解这个,来看简单工厂的组成结构。

简单工厂结构

简单工厂可以分为大致三个部分:

  1. 抽象产品接口,定义产品公共方法,比如武器的攻击方式
  2. 产品接口实现类,实现产品接口中的方法,给出具体行为,比如刀就是砍,剑就是刺
  3. 工厂类,制造出具体产品,比如工匠依据图纸打造具体的武器

%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E4%B9%8B%E5%B7%A5%E5%8E%82%E6%A8%A1%E5%BC%8F%20dc400f3d7f7e48dc9bff810a46308fd6/Untitled.png

代码

武器接口和武器实现类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public interface Weapon {
public String attack();
}
public class Sword implements Weapon{
@Override
public String attack() {
System.out.println("使用刀用来砍向敌人!!");
return "攻击方式: 砍";
}
}
public class Axe implements Weapon{
@Override
public String attack() {
System.out.println("使用斧子用来劈向敌人!!");
return "攻击方式: 劈";
}
}

工厂类

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

public static Weapon build(WeaponType weaponType){
if(WeaponType.AXE.equals(weaponType)){
return new Axe();
}else if(WeaponType.SWORD.equals(weaponType)){
return new Sword();
}
return null;
}
}
// 枚举武器类型
public enum WeaponType {
SWORD,AXE
}

客户端测试

1
2
3
4
5
6
7
8
9
10
public class Gamer {
public static void main(String[] args) {
// 使用工厂制造一个斧子
Weapon axe = WeaponFactory.build(WeaponType.AXE);
axe.attack();
// 使用工厂制造一个刀
Weapon sword = WeaponFactory.build(WeaponType.SWORD);
sword.attack();
}
}

上面举的一个小栗子,主要想体现两点

  • 客户端不再自己创建具体事例,而是根据特定类型要求工厂创建
  • 相同的行为封装接口的编程习惯

工厂方法模式

通过查看前面的简单工厂代码可以发现,在工厂内是通过if..else结构进行判断需要创建哪个类型的武器,工厂方法模式可以通过抽象工厂,创建一个公共的工厂接口类,通过实现类去实现结构,在调用的时候通过使用实现类来实例化特定的工厂。也就是说,工厂方法类同样需要通过子类去实例化建造对象.

工厂方法的应用场景

  1. 和简单工厂相同,创建对象类型相同,但实现不同
  2. 和简单工厂不同的是,工厂方法模式不再让客户端面对对象类型,而是面对工厂类型,并且可以通过工厂指定不同的属性的对象,可以看下面的实例。

还是在打造武器的例子的基础上,但是现在制造者进行了分门别派,有专门制造斧子的制造者,有专门制造刀的制造者。

  • 在原来,需要对制造者说:”给我来一把斧子!“,那么制造者会拿起斧子的制造模板,开始制造
  • 在现在,只需要走到专门制造斧子的制造者前面,对制造者说:”给我来把武器/附魔的武器“,那么制造者自然会给你一把对应的斧子。

工厂方法结构

工厂方法大致分为四个部分:

  1. 抽象产品接口,定义公共产品方法,如下图的weapon方法
  2. 产品实现类和特殊属性实现类,如下图中的Axe和EnchanteAxe
  3. 抽象工厂,定义生产产品接口
  4. 工厂实现类,实现具体的生产方法工厂方法模

%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E4%B9%8B%E5%B7%A5%E5%8E%82%E6%A8%A1%E5%BC%8F%20dc400f3d7f7e48dc9bff810a46308fd6/Untitled%201.png

代码

  • 抽象产品和产品实现类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public interface Weapon {
public String attack();
}
public class Axe implements Weapon {
@Override
public String attack() {
System.out.println("使用斧子用来劈向敌人!!");
return "攻击方式: 劈";
}
}
public class EnchantedAxe extends Axe{

@Override
public String attack() {
System.out.println("使用一把附魔的斧子用来劈向敌人!!");
return "攻击方式: 劈";
}
}
  • 抽象工厂和工厂实现类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public interface WeaponFactory {
public Weapon build(boolean isEnchanted);
}
public class AxeFactory implements WeaponFactory {

@Override
public Weapon build(boolean isEnchanted) {
// 如果是需要附魔的武器,则返回 附魔的斧子
// 否则返回正常的斧子
if(isEnchanted){
return new EnchantedAxe();
}else{
return new Axe();
}
}
}
  • 客户端调用
1
2
3
4
5
6
7
8
9
10
11
public class Gamer {
public static void main(String[] args) {
// 声明一个工厂
WeaponFactory axeFactory = new AxeFactory();
// 创建一个附魔的斧子
Weapon enchantedAxe = axeFactory.build(true);
enchantedAxe.attack();
Weapon axe = axeFactory.build(false);
axe.attack();
}
}

抽象工厂模式

提供用于创建相关或从属的对象族(多个组件,并且组件之间是由必要联系的)的接口,而无需指定其具体类.是对工厂方法的再次延展。需要注意的是,这里的相关或从属的对象组指的是相关联的一系列的对象。

如在游戏时,不光需要武器,还要需要防具,这时的武器和防具就属于”一个对象族“,他们相关联,需要同时创建。

抽象工厂应用场景

抽象工厂方法是工厂方法的进阶,是对工厂方法中的生成的对象的再次包装。

抽象方法结构

抽象方法结构可以分为四部分:

  1. 分离的产品组件接口
  2. 产品组件实现接口
  3. 抽象工厂接口。声明一组方法,用来创建一组对象
  4. 具体工厂。针对抽象工厂接口中的创建方法,进行具体实现

%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E4%B9%8B%E5%B7%A5%E5%8E%82%E6%A8%A1%E5%BC%8F%20dc400f3d7f7e48dc9bff810a46308fd6/Untitled%202.png

实现代码

  • 产品组件,分别定义了接口和相对应的实现
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
public interface Weapon {
public String attack();
}
public interface Armor {
String defence();
}

public class Axe implements Weapon {
@Override
public String attack() {
System.out.println("使用斧子用来劈向敌人!!");
return "攻击方式: 劈";
}
}
public class EnchantedAxe extends Axe {
@Override
public String attack() {
System.out.println("使用一把附魔的斧子用来劈向敌人!!");
return "攻击方式: 劈";
}
}
public class IronArmor implements Armor{
@Override
public String defence() {
System.out.println("使用铁制防具防御");
return "铁质防具";
}
}
public class SteelArmor implements Armor {
@Override
public String defence() {
System.out.println("使用精钢制防具防御");
return "精钢质防具";
}
}
  • 工厂接口和实现,不同类型的工厂制造不同的一组组件
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 interface WarFactory {
public Weapon buildWeapon();
public Armor buildArmor();
}
public class LowerFactory implements WarFactory{
// 低级的制造工厂
@Override
public Weapon buildWeapon() {
return new Axe();
}

@Override
public Armor buildArmor() {
return new IronArmor();
}
}
public class SeniorFactory implements WarFactory {
@Override
public Weapon buildWeapon() {
return new EnchantedAxe();
}

@Override
public Armor buildArmor() {
return new SteelArmor();
}
}
  • 客户端实现
1
2
3
4
5
6
7
8
9
10
public class Gamer {

public static void main(String[] args) {
WarFactory senior = new SeniorFactory();
Armor armor = senior.buildArmor();
armor.defence();
Weapon weapon = senior.buildWeapon();
weapon.attack();
}
}

可以看到,抽象工厂模式中,如果还需定义更多的组件,那么需要在每个抽象工厂和实现工厂中添加对应方法,这样不满足开闭原则,所以在实际应用中局限性很大。

<i class="fa fa-angle-left"></i>1…567…12<i class="fa fa-angle-right"></i>
NanYin

NanYin

Was mich nicht umbringt, macht mich starker.

111 posts
16 categories
21 tags
RSS
GitHub E-Mail
近期文章
  • ThreadLocal
  • Java的四种引用类型
  • Markdown语法入门
  • IDEA设置代码注释模板和JavaDoc文档生成
  • 基于Java的WebService实践
0%
© 2023 NanYin
|
本站访客数:
|
博客全站共140.1k字
|