Java中List、Set、Map区别--小记

三者异同

1.List,Set都是继承自Collection接口;
2.List特点:元素有放入顺序,元素可重复;
Set特点:元素无放入顺序,元素不可重复(注意:元素虽然无放入顺序,但是元素在set中的位置是有该元素的HashCode决定的,其位置其实是固定的);
Map特点:元素按键值对存储,无放入顺序 (应该知道什么是键值对吧!) ;
3.List接口有三个实现类:LinkedList,ArrayList,Vector;
LinkedList:底层基于链表实现,链表内存是散乱的,每一个元素存储本身内存地址的同时还存储下一个元素的地址。链表增删快,查找慢;
ArrayList和Vector的区别:ArrayList是非线程安全的,效率高;Vector是基于线程安全的,效率低;
Set接口有两个实现类:HashSet(底层由HashMap实现),LinkedHashSet;
SortedSet接口有一个实现类:TreeSet(底层由平衡二叉树实现)
Query接口有一个实现类:LinkList
Map接口有三个实现类:HashMap,HashTable,LinkeHashMap
HashMap非线程安全,高效,支持null;HashTable线程安全,低效,不支持null
SortedMap有一个实现类:TreeMap
其实最主要的是,list是用来处理序列的,而set是用来处理集的。Map是知道的,存储的是键值对
set 一般无序不重复.map kv 结构 list 有序
4.Map相当于和Collection一个级别的;Map该集合存储键值对,且要求保持键的唯一性;
Map—-(HashMap,Hashtable,TreeMap)。
其中,Hashtable:底层是哈希表数据结构,不可以存null键、null值,且线程同步
HashMap:底层数据结构是哈希表,允许使用null键和值,其余于HashMap的用法相同,且线程不同步;
TreeMap:底层是数据机构是二叉树,可以用于给map中的键排序,且线程不同步。

去重的问题

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
package yn;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* 类描述:set集合针对String类型和8大基础数据类型过滤掉重复数据,如果存放的是其他类型对象,
* 则需要重写hashCode方法和equals方法,当equals比较相等时,
* 则会去比较hashCode值 hashCode的值 如果一致的话,则不会存进set
* @author yxx
*/
public class ListSetMap {
public static void main(String[] args) {
Set<String> nameSet = new HashSet<String>();
nameSet.add("张三");
nameSet.add("李四");
nameSet.add("王五");
nameSet.add("张三");
// 输出结果 张三 李四 王五
for(String name : nameSet){
System.out.print(name + "\t");
}
System.out.println();
// List集合去除重复基础数据
List<String> nameList = new ArrayList<String>();
nameList.add("张三");
nameList.add("李四");
nameList.add("王五");
nameList.add("赵六");
nameSet.addAll(nameList);
// 输出结果 张三 李四 王五 赵六
for(String name : nameSet){
System.out.print(name + "\n");
}
// 去除编号和用户名一样的 对象,需要重写 equals 方法 和 hashCode方法
User admin = new User(1, "admin");
User user = new User(2, "user");
User user1 = new User(2, "user");
User admin1 = new User(3, "admin");
Set<User> userSet = new HashSet<User>();
userSet.add(admin);
userSet.add(user);
userSet.add(admin1);
userSet.add(user1);
// 输入结果 admin1 admin3 user2
for(User u : userSet){
System.out.print(u.username + u.id + "\t");
}
System.out.println(user.equals(null));
}
}
class User{
protected Integer id;
protected String username;
public User(Integer id, String username){
this.id = id;
this.username = username;
}
/**
* 如果对象类型是User 的话 则返回true 去比较hashCode值
*/
@Override
public boolean equals(Object obj) {
if(obj == null) return false;
if(this == obj) return true;
if(obj instanceof User){
User user =(User)obj;
// if(user.id = this.id) return true; // 只比较id
// 比较id和username 一致时才返回true 之后再去比较 hashCode
if(user.id == this.id && user.username.equals(this.username)) return true;
}
return false;
}
/**
* 重写hashcode 方法,返回的hashCode 不一样才认定为不同的对象
*/
@Override
public int hashCode() {
// return id.hashCode(); // 只比较id,id一样就不添加进集合
return id.hashCode() * username.hashCode();
}
}

运行截图

这样一来基本就明白了重写equal是干什么的,也知道了set集合针对String类型和8大基础数据类型过滤掉重复数据,如果存放的是其他类型对象,则需要重写hashCode方法和equals方法,当equals比较相等时,则会去比较hashCode值 hashCode的值 如果一致的话,则不会存进set

Java设计模式--总结

设计模式是从许多优秀的软件系统中总结出成功的可复用的设计方案。设计模式一定是将简单的东西复杂化,哈哈哈,是不是矛盾,其实不然,扩展性大大增强。

什么是框架

框架不是模式,框架是针对某个领域,提供用于开发应用系统的类的集合,程序设计者可以使用框架提供的类设计一个应用程序,而且在设计应用程序时可以针对特定的问题使用某个模式。
1.层次不同。模式比框架更抽象,模式是在某种特定环境中,针对一个软件设计出现的问题而给出的可复用的解决方案,不能向使用者提供可以直接使用的类,设计模式只有在被设计人员使用时才能表示为代码。框架和模式不同,它不是一种可复用的设计方案,它是由可用于设计解决某个问题的一些类组成的集合,程序设计人员通过使用框架提供的类或扩展框架提供的类进行应用程序的设计。
2.范围不同。模式本质上是逻辑概念,以概念的形式而存在,模式所描述的方案独立于编程语言。框架的应用很具体,它们不是以概念的形式而存在,而是以具体的软件组织而存在。
3.相互关系。一个框架往往包括很多个设计模式,它们是面向对象系统获得最大复用的方式,较大的面向对象应用会由多层彼此合作的框架组成。

面向对象的基本原则

面向抽象原则

抽象类和接口

1.抽象类
抽象类具有如下特点:

  • 抽象类中可以有abstract方法,也可以有非abstract方法。
  • 抽象类不能用new运算符创建对象。
  • 如果一个非抽象类是某个抽象类的子类,那么它必须重写父类的abstract方法。
  • 作为上转型对象。其实就是子类对象指向父类引用。

2.接口
接口具有如下特点:

  • 接口中只可以有public权限的abstract方法,不能有非abstract方法。
  • 接口由类去实现,实现全部方法。
  • 接口回调。其实就是子类对象指向父类引用。

面向抽象

所谓面向抽象编程,是指当设计一个类时,不让该类面向具体的类,而是面向抽象类或接口,即所设计类中的重要数据是抽象类或接口声明的变量。

开-闭原则

所谓“开-闭原则”(Open-Closed Principle)就是让设计对扩展开放,对修改关闭。实际上这句话的本质就是指当一个设计中增加新的模块时,不需要修改现有的模块。

多用组合少用继承原则

之所以提倡多用组合,少用继承,是因为在许多设计中,人们希望系统的类之间尽量是低耦合的关系,而不希望是强耦合关系。即在许多情况下需要避开继承的缺点,而需要组合的优点。在装饰模式,策略模式以及中介者模式中都有体现。

高内聚-低耦合原则

如果类中的方法是一组相关的行为,则称该类时高内聚的,反之称为低内聚的。高内聚便于维护,而低内聚不利于类的维护。

创建型模式

创建型模式涉及对象的实例化,这类模式的特点是,不让用户代码依赖于对象的创建或排列方式,避免用户直接使用new运算符创建对象。

GOF的23中模式中的下列5种模式是属于创建型的模式

  • 工厂方法模式:定义了一个用于创建对象的接口,让子类决定实例化哪一个类。Factory Method使一个类的实例化延迟到其子类。
  • 抽象工厂模式:提供一个创建一系列或相互依赖对象的接口,而无须制定它们具体的类。
  • 生成器模式:将一个复杂对象的创建于它的表示分离,使得同样的构建过程可以创建不同的表示。
  • 原型模式:用原型实例制定创建对象的种类,并且通过复制这些原型创建新的对象。
  • 单件模式:保证一个类仅有一个实例,并提供一个访问它的全局访问点。

行为型模式

行为型模式涉及怎样合理地涉及对象之间的交互通信,以及怎样合理为对象分配职责,让设计富有弹性,易维护,易复用。

GOF的23中模式中的下列11种模式是属于创建型模式。

  • 责任链模式:使多个对象都有机会处理请求,从而避免请求的发送者和接受者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。
  • 命令模式:将一个请求封装为一个对象,从而可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。
  • 解释器模式:给定一个语言,定义它文法的一种表示,并定义了一个解释器,这个解释器使用该表示来解释语言中的句子。
  • 迭代器模式:提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示。
  • 中介者模式:用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显示地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。
  • 备忘录模式:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样以后就可将该对象恢复到原先保存的状态。
  • 观察者模式:定义对象间的一种一对多的依赖关系,当一个对象的状态发生变化时,所有依赖于它的对象都得到通知并被自动更新。
  • 状态模式:允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它的类。
  • 策略模式:定义了一系列算法,把它们一个个封装起来,并且使它们可以相互替换。本模式使算法可独立于使用它的客户而变化。
  • 模板方法模式:定义一个操作中算法的骨架,而将一些步骤延迟到子类中。模板方法使子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
  • 访问者模式:表示一个作用于某对象结构中的各个元素的操作。它可以在不改变各个元素的类的前提下定义作用于这些元素的新操作。

结构型模式

结构型模式涉及如何组合类和对象以形成更大的结构,和类有关的结构型模式涉及如何合理地使用继承机制;和对象有关的结构型模式涉及如何合理地使用对象组合机制。

GOF的23种设计模式中的下列7种模式是属于创建型模式。

  • 适配器模式:将一个类的接口装换成客户希望的另一个接口。Adapter模式使原来由于接口不兼容而不能在一起工作的那些类可以一起工作。
  • 组合模式:将对象组合成树形结构以表示“部分-整体”的层次结构。Composite使用户对单个对象和组合对象的使用具有一致性。
  • 代理模式:为其他对象提供一种代理以控制对这个对象的访问。
  • 享元模式:运用共享技术有效地支持大量细粒度的对象。
  • 外观模式:为系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使这一子系统更加容易使用。
  • 桥接模式:将抽象部分与它的实现部分分离,使它们都可以独立地变化。
  • 装饰模式:动态地给对象添加一些额外的职责,就功能来说装饰模式相比生成子类更为灵活。

简单的介绍下UML:

  • 接口
  • 泛化关系 就是继承
  • 关联关系
  • 依赖关系
  • 实现关系

Java设计模式--命令模式

命令模式(别名:动作,事物)

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

Command Pattern(Another name:Action,Transaction)

Encapsulate a request as an object, thereby letting you parameterize clients with different requests, queue or log requests, and support undoable operations.

类图

模式的结构与使用

命令模式的结构中包四种角色。

  • 接受者(Receiver):接受者是一个类的实例,该实例负责执行与请求相关的操作。
  • 命令(Command):命令是一个接口,规定了用来封装“请求”的若干个方法,比如:execute()、undo()等方法。
  • 具体命令(Concrete command):具体命令是实现命令接口的类的实例。具体命令必须实现接口中的方法,比如execute(),使该方法封装一个“请求”。
  • 请求者(Invoker):请求者是一个包含Command接口变量的类的实例。请求者中的Command接口的变量可以存放任何具体命令的引用。请求者负责调用具体命令,让具体命令执行那些封装“请求”的方法,比如execute()方法。

简单的例子

接受者Receiver的类CompanyArmy.java

1
2
3
4
5
6
7
package Command;
public class CompanyArmy {
public void sneakAttack() {
System.out.println("我们知道了如何偷袭敌人,保证完成任务");
}
}

Command接口Command.java

1
2
3
4
5
package Command;
public interface Command {
public abstract void execute();
}

ConcreteCommand的实现类ConcreteCommand.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package Command;
public class ConcreteCommand implements Command {
CompanyArmy army;
public ConcreteCommand(CompanyArmy army) {
this.army = army;
}
@Override
public void execute() {
army.sneakAttack();
}
}

请求者类ArmySuperior.java

1
2
3
4
5
6
7
8
9
10
11
package Command;
public class ArmySuperior {
Command command;
public void setCommand(Command command) {
this.command = command;
}
public void startExecuteCommand() {
command.execute();
}
}

测试类Application.java

1
2
3
4
5
6
7
8
9
10
11
12
package Command;
public class Application {
public static void main(String[] args) {
CompanyArmy 三连 = new CompanyArmy();
Command command = new ConcreteCommand(三连);
ArmySuperior 指挥官 = new ArmySuperior();
指挥官.setCommand(command);
指挥官.startExecuteCommand();
}
}

运行截图

命令撤销的例子

接受者Receiver的类MakeDir.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package UndoCommand;
import java.io.File;
public class MakeDir {
public void createDir(String name) {
File dir = new File(name);
dir.mkdir();
}
public void deleteDir(String name) {
File dir = new File(name);
dir.delete();
}
}

Command接口Command.java

1
2
3
4
5
6
package UndoCommand;
public interface Command {
public abstract void execute(String name);
public abstract void undo();
}

ConcreteCommand的实现类ConcreteCommand.java

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
package UndoCommand;
import java.util.ArrayList;
public class ConcreteCommand implements Command {
ArrayList<String> dirNameList;
MakeDir makeDir;
public ConcreteCommand(MakeDir makeDir) {
dirNameList = new ArrayList<String>();
this.makeDir = makeDir;
}
@Override
public void execute(String name) {
makeDir.createDir(name);
dirNameList.add(name);
}
@Override
public void undo() {
if (dirNameList.size() > 0) {
int m = dirNameList.size();
String str = dirNameList.get(m - 1);
makeDir.deleteDir(str);
dirNameList.remove(m - 1);
} else
System.out.println("没有需要撤销的操作");
}
}

请求者类RequestMakedir.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package UndoCommand;
public class RequestMakedir {
Command command;
public void setCommand(Command command) {
this.command = command;
}
public void startExecuteCommand(String name) {
command.execute(name);
}
public void undoCommand() {
command.undo();
}
}

测试类Application.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package UndoCommand;
public class Application {
public static void main(String[] args) {
MakeDir makeDir = new MakeDir();
Command command = new ConcreteCommand(makeDir);
RequestMakedir requestMakedir = new RequestMakedir();
requestMakedir.setCommand(command);
requestMakedir.startExecuteCommand("yxx");
requestMakedir.startExecuteCommand("yn");
requestMakedir.undoCommand();
}
}

运行截图

命令模式的优点

  • 在命令模式中,请求者(Invoker)不直接与接受者(Receiver)交互,即请求者(Invoker)不包含接受者(Receiver)的引用,因此彻底消除了彼此之间的耦合。
  • 命令模式满足“开-闭原则”。如果增加新的具体命令和该命令的接受者,不必修改调用者的代码,调用者就可以使用新的命令对象;反之,如果增加新的调用者,不必修改现有的具体命令和接受者,新增加的调用者就可以使用已有的具体命令。
  • 由于请求者的请求被封装到了具体命令中,那么就可以将具体命令保存到持久化的媒介中,在需要的时候,重新执行这个具体命令。因此,使用命令模式可以记录日志。
  • 使用命令模式可以对请求者的“请求”进行排队。每个请求都各自对应一个具体命令,因此可以按一定顺序执行这些命令。

适用命令模式的情景

  • 程序需要在不同的时刻制定、排列和执行请求。
  • 程序需要提供撤销操作。
  • 程序需要支持宏操作。

下载源码请到

MyGitHub

Java设计模式--外观模式

外观模式

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

Facade Pattern

Provide a unified interface to a set of interfaces in a subsystem. Facade defines a higher-level interface that makes the subsystem easier to use.

类图

模式的结构与使用

外观模式的结构中包两种角色。

  • 子系统(Subsystem):子系统是若干个类的集合,这些类的实例协同合作为用户提供所需要的功能,子系统中任何类都不包含外观类的实例引用。
  • 外观(Facade):外观是一个类,该类包含子系统中全部或部分类的实例引用,当用户想要和子系统中的若干个类的实例打交道时,可以代替地和子系统的外观类的实例打交道。

简单的例子

子系统CheckWord.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package Facade;
public class CheckWord {
public final int basicAmount = 85;
String advertisement;
int amount;
public CheckWord(String advertisement) {
this.advertisement = advertisement;
}
public void setAmount() {
this.amount = this.advertisement.length() + basicAmount;
}
public int getAmount() {
return this.amount;
}
}

子系统Charge.java

1
2
3
4
5
6
7
8
9
10
11
12
13
package Facade;
public class Charge {
public final int basicCharge = 12;
CheckWord checkWord;
Charge(CheckWord checkWord) {
this.checkWord = checkWord;
}
public void giveCharge() {
int charge = checkWord.getAmount() * basicCharge;
System.out.println("广告费用:" + charge + "元");
}
}

子系统TypeSeting.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
ppackage Facade;
public class TypeSeting {
String advertisement;
public TypeSeting(String advertisement) {
this.advertisement = advertisement;
}
public void typeSeting() {
System.out.println("广告排版格式:");
System.out.println("*****************");
System.out.println(this.advertisement);
System.out.println("*****************");
}
}

外观ClientServerFacade.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package Facade;
public class ClientServerFacade {
private CheckWord checkWord;
private Charge charge;
private TypeSeting typeSeting;
String advertisement;
public ClientServerFacade(String advertisement) {
this.advertisement = advertisement;
checkWord = new CheckWord(advertisement);
charge = new Charge(checkWord);
typeSeting = new TypeSeting(advertisement);
}
public void doAdvertisement() {
checkWord.setAmount();
charge.giveCharge();
typeSeting.typeSeting();
}
}

测试类Application.java

1
2
3
4
5
6
7
8
9
10
package Facade;
public class Application {
public static void main(String[] args) {
String clientAdvertisement = "电脑,价格63377,联系电话5059676";
ClientServerFacade csf = new ClientServerFacade(clientAdvertisement);
csf.doAdvertisement();
}
}

运行截图

外观模式的优点

  • 使客户和子系统中的类无耦合,并且使子系统使用起来更加方便。
  • 外观只是提供了一个更加简洁的界面,并不影响用户直接使用子系统中的类。
  • 子系统中任何类对方法的内容进行修改,不影响外观类的代码。

适用外观模式的情景

  • 对于一个复杂的子系统,需要为用户提供一个简单的交互操作。
  • 不希望客户代码和子系统中的类有耦合,以便提高子系统的独立性和可移植性。
  • 当整个系统需要构建一个层次结构的子系统,不希望这些子系统相互直接的交互。

下载源码请到

MyGitHub

Java设计模式--中介者模式

中介者模式

用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显示地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。

Mediator Pattern

Define an object that encapsulates how a set of objects interact. Mediator promotes loose coupling by keeping objects from refering to each other explicitly, and it lets you vary their interaction independently.

类图

模式的结构与使用

中介者模式的结构中包括四种角色。

  • 中介者(Mediator):中介者使一个接口,该接口定义了用于同事(Colleague)对象之间进行通信的方法。
  • 具体中介者(Concrete Mediator):具体中介者是实现中介者接口的类。具体中介者需要包含所有具体同事(Concrete Colleague)的引用,并通过实现中介者接口中的方法来满足具体同事之间的通信请求。
  • 同事(Colleague):一个接口,规定了具体同事需要实现的方法。
  • 具体同事(Concrete Colleague):实现同事接口的类。具体同事需要包含具体中介者的引用,一个具体同事需要和其他同事交互时,只需将自己的请求通知给它所包含的具体中介者即可。

简单的例子

同事接口类Colleague.java

1
2
3
4
5
6
7
8
package Mediator;
public interface Colleague {
public void giveMess(String[] mess);
public void receiverMess(String mess);
public void setName(String name);
public String getName();
}

本问题直接需要一个具体中介者ConcreteMediator.java

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
package Mediator;
public class ConcreteMediator {
ColleagueA colleagueA;
ColleagueB colleagueB;
ColleagueC colleagueC;
public void registerColleagueA(ColleagueA colleagueA) {
this.colleagueA = colleagueA;
}
public void registerColleagueB(ColleagueB colleagueB) {
this.colleagueB = colleagueB;
}
public void registerColleagueC(ColleagueC colleagueC) {
this.colleagueC = colleagueC;
}
public void deliverMess(Colleague colleague, String[] mess) {
if (colleague == colleagueA) {
if (mess.length >= 2) {
colleagueB.receiverMess(colleague.getName() + mess[0]);
colleagueC.receiverMess(colleague.getName() + mess[1]);
}
}
if (colleague == colleagueB) {
if (mess.length >= 2) {
colleagueA.receiverMess(colleague.getName() + mess[0]);
colleagueC.receiverMess(colleague.getName() + mess[1]);
}
}
if (colleague == colleagueC) {
if (mess.length >= 2) {
colleagueA.receiverMess(colleague.getName() + mess[0]);
colleagueB.receiverMess(colleague.getName() + mess[1]);
}
}
}
}

ConcreteColleague的实现类ColleagueA.java

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
package Mediator;
public class ColleagueA implements Colleague {
ConcreteMediator cm;
String name;
public ColleagueA(ConcreteMediator cm) {
this.cm = cm;
cm.registerColleagueA(this);
}
@Override
public void giveMess(String[] mess) {
cm.deliverMess(this, mess);
}
@Override
public void receiverMess(String mess) {
System.out.println(name + "收到的信息");
System.out.println("\t" + mess);
}
@Override
public void setName(String name) {
this.name = name;
}
@Override
public String getName() {
return this.name;
}
}

ConcreteColleague的实现类ColleagueB.java

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
package Mediator;
public class ColleagueB implements Colleague {
ConcreteMediator cm;
String name;
public ColleagueB(ConcreteMediator cm) {
this.cm = cm;
cm.registerColleagueB(this);
}
@Override
public void giveMess(String[] mess) {
cm.deliverMess(this, mess);
}
@Override
public void receiverMess(String mess) {
System.out.println(name + "收到的信息");
System.out.println("\t" + mess);
}
@Override
public void setName(String name) {
this.name = name;
}
@Override
public String getName() {
return this.name;
}
}

ConcreteColleague的实现类ColleagueC.java

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
package Mediator;
public class ColleagueC implements Colleague {
ConcreteMediator cm;
String name;
public ColleagueC(ConcreteMediator cm) {
this.cm = cm;
cm.registerColleagueC(this);
}
@Override
public void giveMess(String[] mess) {
cm.deliverMess(this, mess);
}
@Override
public void receiverMess(String mess) {
System.out.println(name + "收到的信息");
System.out.println("\t" + mess);
}
@Override
public void setName(String name) {
this.name = name;
}
@Override
public String getName() {
return this.name;
}
}

测试类Application.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package Mediator;
public class Appication {
public static void main(String[] args) {
ConcreteMediator cm = new ConcreteMediator();
ColleagueA colleagueA = new ColleagueA(cm);
ColleagueB colleagueB = new ColleagueB(cm);
ColleagueC colleagueC = new ColleagueC(cm);
colleagueA.setName("A国");
colleagueB.setName("B国");
colleagueC.setName("C国");
String[] messA = {"要求归还曾抢夺的100斤土豆", "要求归还曾抢夺的20头牛"};
colleagueA.giveMess(messA);
String[] messB = {"要求归还曾抢夺的10只公鸡", "要求归还曾抢夺的15匹马"};
colleagueB.giveMess(messB);
String[] messC = {"要求归还曾抢夺的300斤小麦", "要求归还曾抢夺的50头驴"};
colleagueC.giveMess(messC);
}
}

运行截图

中介者模式的优点

  • 可以避免许多的对象为了之间的通信而相互显示引用,否则,不仅系统难于维护,而且也使其他系统难以复用这些对象。
  • 可以通过中介者将原本分布于多个对象之间的交互行为集中在一起。当这些对象之间需要改变之间的通信行为时,只需使用一个具体中介者即可,不必修改各个具体同事的代码,即这些同事可被重用。
  • 具体中介者使得各个具体同事完全解耦,修改任何一个具体同事的代码不会影响到其他同事。
  • 具体中介者集中了同事之间是如何交互的细节,使系统比较清楚地知道整个系统中同事是如何交互的。
  • 当一些对象想相互通信,但又无法相互包含对方的引用,那么使用中介者模式就可以使这些对象互相通信。
    注:由于具体中介者集中了同事之间是如何交互的细节,可能使具体具体中介者变得非常复杂,增加了维护的难度。

适用中介者模式的情景

  • 许多对象以复杂的方式交,所导致的依赖关系使系统难以理解和维护。
  • 一个对象引用其他很多对象,导致难以复用该对象。

下载源码请到

MyGitHub

Java设计模式--生成器模式

生成器模式

将一个复杂对象的构建与它的表示分离,使同样的构建过程可以创建不同的表示。

Builder Pattern

Separate the construction of a complex object from its representation so that the same construction process can create different representations.

类图

模式的结构与使用

生成器模式的结构中包括四个角色。

  • 产品(Product):具体生成器要构造的复杂对象。
  • 抽象生成器(Abstract Builder):抽象生成器是一个接口,该接口除了为创建一个Product对象的各个组件定义了若干个方法外,还定义返回Product对象的方法。
  • 具体生成器(Concrete Builder):实现Builder接口的类,具体生成器将实现Builder接口所定义的方法。
  • 指挥者(Director):指挥者是一个类,该类需含有Builder接口声明的变量。指挥者的职责是负责向用户提供具体生成器,即指挥者将请求具体生成器来构造用户所需要的Product对象,如果所请求的具体生成器成功地构造出Product对象,指挥者就可以让该具体生成器返回所构造的Product对象。

简单的例子

Product的抽象类PanelProduct.java

1
2
3
4
5
6
7
8
9
package Builder;
import javax.swing.*;
public class PanelProduct extends JPanel {
JButton button;
JLabel label;
JTextField textField;
}

抽象生成器Builder接口类Builder.java

1
2
3
4
5
6
7
8
9
10
package Builder;
import javax.swing.JPanel;
public interface Builder {
public abstract void buildButton();
public abstract void buildLabel();
public abstract void buildTextField();
public abstract JPanel getPanel();
}

ConcreteBuilder的实现类ConcreteBuilderOne.java

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
package Builder;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
public class ConcreteBuilderOne implements Builder {
private PanelProduct panel;
public ConcreteBuilderOne() {
panel = new PanelProduct();
}
@Override
public void buildButton() {
panel.button = new JButton("按钮");
}
@Override
public void buildLabel() {
panel.label = new JLabel("标签");
}
@Override
public void buildTextField() {
panel.textField = new JTextField("文本框");
}
@Override
public JPanel getPanel() {
panel.add(panel.button);
panel.add(panel.label);
panel.add(panel.textField);
return panel;
}
}

ConcreteBuilder的实现类ConcreteBuilderTwo.java

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
package Builder;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
public class ConcreteBuilderTwo implements Builder {
private PanelProduct panel;
public ConcreteBuilderTwo() {
panel = new PanelProduct();
}
@Override
public void buildButton() {
panel.button = new JButton("button");
}
@Override
public void buildLabel() {
panel.label = new JLabel("label");
}
@Override
public void buildTextField() {
panel.textField = new JTextField("My文本框");
}
@Override
public JPanel getPanel() {
panel.add(panel.textField);
panel.add(panel.label);
panel.add(panel.button);
return panel;
}
}

指挥类Director.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package Builder;
import javax.swing.JPanel;
public class Director {
private Builder builder;
public Director(Builder builder) {
this.builder = builder;
}
public JPanel constructProduct() {
builder.buildButton();
builder.buildLabel();
builder.buildTextField();
JPanel product = builder.getPanel();
return product;
}
}

测试类Application.java

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
ppackage Builder;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Application {
public static void main(String[] args) {
Builder builder = new ConcreteBuilderOne();
Director director = new Director(builder);
JPanel panel = director.constructProduct();
JFrame frameOne = new JFrame();
frameOne.add(panel);
frameOne.setBounds(12, 12, 200, 120);
frameOne.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frameOne.setVisible(true);
builder = new ConcreteBuilderTwo();
director = new Director(builder);
panel = director.constructProduct();
JFrame frameTwo = new JFrame();
frameTwo.add(panel);
frameTwo.setBounds(212, 12, 200, 120);
frameTwo.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frameTwo.setVisible(true);
}
}

运行截图

生成器模式的优点

  • 生成器模式将对象的构造过程封装在具体生成器中,用户使用不同的具体生成器就可以得到该对象的不同表示。
  • 生成器模式将对象的构造过程从创建对象的类中分离出来,使用户无需了解该对象的具体组件。
  • 可以更加精细有效地控制对象的构造过程。生成器将对象的构造过程分解成若干步骤,这就使程序可以更加精细,有效地控制整个对象的构造。
  • 生成器模式将对象的构造过程与创建该对象类解耦,使对象的创建更加灵活有弹性。
  • 当增加新的具体生成器时,不必修改指挥者的代码,即该模式满足开-闭原则。

适用生成器模式的情景

  • 当系统准备为用户提供一个内部结构复杂的对象,而且在构造方法中编写创建该对象的代码无法满足用户需求时,就可以使用生成器模式来构造这样的对象。
  • 当某些系统要求对象的构造过程必须独立于创建该对象的类时。

下载源码请到

MyGitHub

Java设计模式--策略模式

策略模式(别名:政策)

定义一系列算法,把它们一个个封装起来,并且使它们可相互替换。本模式使得算法可独立于使用它的客户而变化。

Strategy Pattern(Another Name: Policy)

Define a family of algorithms, encapsulate each one, and make them inter changeable. Strategy lets the algorithm vary independently from clients that use it.

类图

模式的结构与使用

策略方法模式的结构中包括三种角色。

  • 策略(Strategy):策略是一个接口,该接口定义若干个算法标识,即定义了若干个抽象方法。
  • 具体策略(Concrete Strategy):具体策略是实现策略接口的类。具体策略实现策略接口所定义的抽象方法,即给出算法标识的具体算法。
  • 上下文(Context):上下文是依赖于策略接口的类,即上下文包含有策略声明的变量。上下文中提供一个方法,该方法委托策略变量调用具体策略所实现的策略接口中的方法。

简单的例子

Abstract Strategy的接口类ComputableStrategy.java

1
2
3
4
5
package Strategy;
public interface ComputableStrategy {
public abstract double computableStrategy(double[] a);
}

Strategy的实现类StrategyOne.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package Strategy;
public class StrategyOne implements ComputableStrategy {
@Override
public double computableStrategy(double[] a) {
double score = 0, sum = 0;
for (int i = 0; i < a.length; i++) {
sum = sum + a[i];
}
score = sum/a.length;
return score;
}
}

Strategy的实现类StrategyTwo.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package Strategy;
public class StrategyTwo implements ComputableStrategy {
@Override
public double computableStrategy(double[] a) {
double score = 0, multi = 1;
int n = a.length;
for (int i = 0; i < a.length; i++) {
multi = multi * a[i];
}
score = Math.pow(multi, 1.0/n);
return score;
}
}

Strategy的实现类StrategyThree.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package Strategy;
import java.util.Arrays;
public class StrategyThree implements ComputableStrategy {
@Override
public double computableStrategy(double[] a) {
if (a.length <= 2)
return 0;
double score = 0, sum = 0;
Arrays.sort(a);
for (int i = 1; i < a.length - 1; i++) {
sum = sum + a[i];
}
score = sum/(a.length-2);
return score;
}
}

Context类GymnasticsGame.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package Strategy;
public class GymnasticsGame {
ComputableStrategy strategy;
public void setStrategy(ComputableStrategy strategy) {
this.strategy = strategy;
}
public double getPersonScore(double a[]) {
if (strategy != null) {
return strategy.computableStrategy(a);
} else
return 0;
}
}

测试类Application.java

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
package Strategy;
public class Application {
public static void main(String[] args) {
GymnasticsGame game = new GymnasticsGame();
game.setStrategy(new StrategyOne());
Person zhang = new Person();
zhang.setName("张三");
double[] a = {9.12, 9.25, 8.87, 9.99, 6.99, 7.88};
Person li = new Person();
li.setName("李四");
zhang.setScore(game.getPersonScore(a));
li.setScore(game.getPersonScore(a));
System.out.println(zhang.getScore());
System.out.println(li.getScore());
game.setStrategy(new StrategyThree());
zhang.setScore(game.getPersonScore(a));
li.setScore(game.getPersonScore(a));
System.out.println(zhang.getScore());
System.out.println(li.getScore());
}
}
class Person {
String name;
double score;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getScore() {
return score;
}
public void setScore(double score) {
this.score = score;
}
}

执行效果图

策略模式的优点

  • 上下文(Context)和具体策略(ConcreteStrategy)是松耦合关系。因此上下文只知道它要使用某一个实现Strategy接口类的实例,但不需要知道具体是哪一个类。
  • 策略模式满足“开-闭原则”。当增加新的具体策略时,不需要修改上下文类的代码,上下文就可以引用新的具体策略的实例。

适用策略模式的情景

  • 一个类定义了多种行为,并且这些行为在这个类的方法中以多个条件语句的形式出现,那么可以使用策略模式避免在类中使用大量的条件语句。
  • 程序不希望暴露复杂的、与算法有关的数据结构,那么可以使用策略模式封装算法。
  • 需要使用一个算法的不同变体。

下载源码请到

MyGitHub

Java设计模式--原型模式

原型模式

用原型实例制定创建对象的种类,并且通过复制这些原型创建新的对象。

Prototype Pattern

Specify the kinds of objects to create using a prototypical instance, and create new objects by copying this prototype.

类图

模式的结构与使用

原型方法模式的结构中包括两种角色。

  • 抽象原型(Abstract Prototype):是一个接口,负责定义对象复制自身的方法。
  • 具体原型(Concrete Prototype):实现Prototype接口的类。具体原型实现抽象原型中的方法,以便所创建的对象调用该方法复制自己。

克隆方法

理解深克隆和浅克隆

  • 1:被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。换言之,浅复制仅仅复制所考虑的对象,而不复制它所引用的对象。
  • 2:被复制对象的所有变量都含有与原来的对象相同的值,除去那些引用其他对象的变量。那些引用其他对象的变量将指向被复制过的新对象,而不再是原有的那些被引用的对象。换言之,深复制把要复制的对象所引用的对象都复制了一遍。

clone()方法

java.lang包中的Object类提供了一个权限是protected的用于复制对象的clone()的方法。我们知道Java中所有的类都是java.lang包中Object类的子类或者间接子类,因此Java中所有的类都继承这个clone()方法,但是由于clone()方法的访问权限是protected的,这就意味着,如果一个对象想使用该方法得到自己的一个复制品,就必须保证自己所在的类与Object类在同一个包中,这显然是不可能的,因为Java不允许用户编写的类拥有java.lang这样的包名(尽管可以编译拥有java.lang包名的类,但允许时JVM拒绝加载这样的类)。

clone()方法的重写与Cloneable接口

为了能让一个对象使用clone()方法,创建该对象的类可以重写(覆盖)clone()方法,并将访问权限提高为public权限,为了能使用被覆盖的clone()方法,只需在重写的clone()方法中使用关键字super调用Object类的clone()方法即可。也可以在创建对象的类中新定义一个复制对象的方法,将访问权限定义为public,并在该方法中调用Object类的clone()方法。
另外,当对象调用Object类中的clone()方法时,JVM将会逐个复制该对象的成员变量,然后创建一个新对象返回,所以JVM要求调用clone()方法的对象必须实现Cloneable接口。Cloneable接口中没有任何方法,该接口的唯一作用就是让JVM知道实现该接口的对象是可以被克隆的。

下面Circle类的对象使用clone方法克隆自己的CloneExample.java

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 Prototype;
class Circle implements Cloneable {
private double radius;
public double getRadius() {
return radius;
}
public void setRadius(double radius) {
this.radius = radius;
}
public Object clone() throws CloneNotSupportedException {
Object object = super.clone();
return object;
}
}
public class CloneExample {
public static void main(String[] args) {
Circle circle = new Circle();
circle.setRadius(198.99);
try {
Circle circleCopy = (Circle) circle.clone();
System.out.println("circle对象中的数据:" + circle.getRadius());
System.out.println("circleCopy对象中的数据:" + circleCopy.getRadius());
circle.setRadius(200);
System.out.println("circle对象中的数据:" + circle.getRadius());
System.out.println("circleCopy对象中的数据:" + circleCopy.getRadius());
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
}

深度克隆

Object类中的clone()方法将复制当前对象变量中的值来创建一个新对象,例如,一个对象Object有两个int型的成员变量x、y,那么该对象a与它调用clone()方法返回的cloneObject对象如图所示。

需要注意的是,如果调用clone()方法的当前对象拥有的成员变量是一个对象,那么clone()方法仅仅复制了当前对象所拥有的对象的引用,并没有复制这个对象所拥有的变量,这就是使clone()方法返回的新对象和当前对象拥有一个相同的对象,未能实现完全意义的复制。例如一个对象Object有两个变量rectangle和height,其中height是int型变量,但是变量rectangle是一个对象,这个对象rectangle有两个double型的变量m、n,那么对象object与它调用clone()方法返回的cloneObject对象如图所示。

这样一来就涉及一个深度克隆问题,因为,当前对象的成员变量中可能还会有其他对象。所以使用clone()方法复制对象有许多细节问题需要考虑,比如,在重写clone()方法时,必须也要对当前对象中的rectangle对象进行复制。下面DeepCloneExample使用clone()方法克隆自己并处理了深度克隆的问题。

注释掉了处理深度克隆的代码

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
package Prototype;
class Geometry implements Cloneable {
int height;
Rectangle rectangle;
public Geometry(int height, Rectangle rectangle) {
this.height = height;
this.rectangle = rectangle;
}
@Override
protected Object clone() throws CloneNotSupportedException {
Geometry object = (Geometry) super.clone();
//object.rectangle = (Rectangle) rectangle.clone();
return object;
}
}
class Rectangle implements Cloneable {
double m, n;
public Rectangle(double m, double n) {
this.m = m;
this.n = n;
}
// @Override
// protected Object clone() throws CloneNotSupportedException {
// return super.clone();
// }
}
public class DeepCloneExample {
public static void main(String[] args) {
Geometry geometry = new Geometry(100, new Rectangle(10, 20));
try {
Geometry geometryCopy = (Geometry) geometry.clone();
System.out.println("geometry对象中的rectangle矩形的长和宽:");
System.out.println(geometry.rectangle.m + "," + geometry.rectangle.n);
System.out.println("geometryCopy对象中的rectangle矩形的长和宽:");
System.out.println(geometryCopy.rectangle.m + "," + geometryCopy.rectangle.n);
System.out.println("geometry对象修改其中的rectangle矩形的长和宽:");
geometry.rectangle.m = 100;
geometry.rectangle.n = 200;
System.out.println("geometry对象中的rectangle矩形的长和宽:");
System.out.println(geometry.rectangle.m + "," + geometry.rectangle.n);
System.out.println("geometryCopy对象中的rectangle矩形的长和宽:");
System.out.println(geometryCopy.rectangle.m + "," + geometryCopy.rectangle.n);
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
}

注释掉了处理深度克隆的代码的程序运行效果图

把注释去掉之后处理了深克隆的运行效果图

序列化 Serializable接口与克隆对象

相对于clone()方法,Java又提供了一种较简单的解决方案,这个方案就是使用Serializable接口和对象流来复制对象。
如果希望得到对象object的复制品,必须保证该对象是序列化的,即创建object对象的类必须实现Serializable接口。Serializable接口中的方法对程序是不可见的,因此实现该接口的类不需要实现额外的方法。
为了得到object的复制品,首先需要将object写入ObjectOutputStream流中,当把一个序列化的对象写入ObjectInputStream输出流时,JVM就会实现Serializable接口中的方法,将一定格式的文本–对象的序列化信息,写入ObjectOutputStream输出流的目的地。然后使用ObjectInputStream对象输入流从ObjectOutputStream输出流的目的地读取对象,这时ObjectInputStream对象流就读回object对象的序列化信息,并根据序列化信息创建一个新的对象,这个新对象就是object对象的一个复制品。
需要注意的是,使用对象流把一个对象写入文件时不仅要保证该对象是序列化的,而且该对象的成员对象也必须是序列化的。

简单的例子

Abstract Prototype的接口类MilitaryPerson.java

1
2
3
4
5
package Prototype;
public interface Prototype {
public Object cloneMe() throws CloneNotSupportedException;
}

Abstract Prototype的实现类Cubic.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package Prototype;
public class Cubic implements Prototype {
double length,width,heigth;
public Cubic(double length, double width, double heigth) {
this.length = length;
this.width = width;
this.heigth = heigth;
}
@Override
public Object cloneMe() throws CloneNotSupportedException {
Object object = (Cubic)clone(); //调用从Object类继承的clone方法
return object;
}
}

Abstract Prototype的实现类Goat.java

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 Prototype;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class Goat implements Prototype, Serializable {
StringBuffer color;
public StringBuffer getColor() {
return color;
}
public void setColor(StringBuffer color) {
this.color = color;
}
@Override
public Object cloneMe() throws CloneNotSupportedException {
Object object = null;
try {
ByteArrayOutputStream outOne = new ByteArrayOutputStream();
ObjectOutputStream outTwo = new ObjectOutputStream(outOne);
outTwo.writeObject(this); //将原型对象写入对象输出流
ByteArrayInputStream inOne = new ByteArrayInputStream(outOne.toByteArray());
ObjectInputStream inTwo = new ObjectInputStream(inOne);
object = inTwo.readObject(); //创建新的对象,原型的复制品
} catch (Exception e) {
e.printStackTrace();
}
return object;
}
}

测试类Application.java

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
package Prototype;
public class Application {
public static void main(String[] args) {
Cubic cubic = new Cubic(12, 20, 66);
System.out.println("cubic的长、宽、高:");
System.out.println(cubic.length + "," + cubic.width + ","
+ cubic.heigth);
try {
Cubic cubicCopy = (Cubic) cubic.cloneMe();
System.out.println("cubicCopy的长、宽、高:");
System.out.println(cubicCopy.length + "," + cubicCopy.width + ","
+ cubicCopy.heigth);
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
Goat goat = new Goat();
goat.setColor(new StringBuffer("白颜色的山羊"));
System.out.println("goat是" + goat.getColor());
try {
Goat goatCopy = (Goat) goat.cloneMe();
System.out.println("goatCopy是" + goatCopy.getColor());
System.out.println("goatCopy将自己的颜色改变成黑色");
goatCopy.setColor(new StringBuffer("黑颜色的山羊"));
System.out.println("goat仍然是" + goat.getColor());
System.out.println("goatCopy是" + goatCopy.getColor());
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
}

执行效果图

原型模式的优点

  • 当创建类的新实例的代价更大时,使用原型模式复制一个已有的实例可以提高创建新实例的效率。
  • 可以动态地保存当前对象的状态。在运行时,可以随时使用对象流保存当前对象的一个复制品。
  • 可以在运行时创建新的对象,而无须创建一系列类和继承结构。
  • 可以动态地添加、删除原型的复制品。

适用原型模式的情景

  • 程序需要从一个对象出发,得到若干个和其状态相同,并可独立变化其状态的对象时。
  • 当对象的创建需要独立于它的构造过程和表示时。
  • 一个类创建实例状态不是很多,那么就可以将这个类的一个实例定义为原型,那么通过复制该原型得到新的实例可能比重新使用类的构造方法创建新实例更方便。

下载源码请到

MyGitHub

Java设计模式--单例模式

单例模式

保证一个类仅有一个实例,并提供一个访问它的全局访问点。

Singleton Pattern

Ensure a class only has one instance, and provide a global point of access to it.

类图

模式的结构与使用

单例方法模式的结构中包括一种角色。

  • 单件类(Singleton):单件类只可以创建出一个实例。

java中单例模式是一种常见的设计模式,单例模式的写法有好几种,这里主要介绍三种:

  • 懒汉式单例
  • 饿汉式单例
  • 登记式单例

简单的例子

懒汉式单例.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package Singleton;
/**
* @author yxx
* 懒汉式单例类,在第一次调用的时候实例化自己
*/
public class Singleton {
private Singleton(){}
private static Singleton single = null;
//静态工厂方法
public static Singleton getInstance() {
if (single == null) {
single = new Singleton();
}
return single;
}
}

Singleton通过将构造方法限定为private避免了类在外部被实例化,在同一个虚拟机范围内,Singleton的唯一实例只能通过getInstance()方法访问。
(事实上,通过Java反射机制是能够实例化构造方法为private的类的,那基本上会使所有的Java单例实现失效。此问题在此处不做讨论,姑且掩耳盗铃地认为反射机制不存在。)

但是以上懒汉式单例的实现没有考虑线程安全问题,它是线程不安全的,并发环境下很可能出现多个Singleton实例,要实现线程安全,有以下三种方式,都是对getInstance这个方法改造,保证了懒汉式单例的线程安全,如果你第一次接触单例模式,对线程安全不是很了解,可以先跳过下面这三小条,去看饿汉式单例,等看完后面再回头考虑线程安全的问题。

在getInstance方法上加同步

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package Singleton;
/**
* @author yxx
* 懒汉式单例类,在第一次调用的时候实例化自己
* 在getInstance方法上加同步
*/
public class SingletonSynchronized {
private SingletonSynchronized(){}
private static SingletonSynchronized single = null;
//静态工厂方法
public static synchronized SingletonSynchronized getInstance() {
if (single == null) {
single = new SingletonSynchronized();
}
return single;
}
}

双重检查锁定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package Singleton;
/**
* @author yxx
* 懒汉式单例类,在第一次调用的时候实例化自己
* 双重检查锁定
*/
public class SingletonDoubleChecked {
private SingletonDoubleChecked(){}
private static SingletonDoubleChecked single = null;
//静态工厂方法
public static SingletonDoubleChecked getInstance() {
if (single == null) {
synchronized (SingletonDoubleChecked.class) {
if (single == null) {
single = new SingletonDoubleChecked();
}
}
}
return single;
}
}

静态内部类,既实现了线程安全,又避免了同步带来的性能影响

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package Singleton;
/**
* @author yxx
* 懒汉式单例类,在第一次调用的时候实例化自己
* 既实现了线程安全,又避免了同步带来的性能影响
*/
public class SingletonStatic {
private static class LazyHolder {
private static final SingletonStatic INSTANCE = new SingletonStatic();
}
private SingletonStatic (){}
public static final SingletonStatic getInstance() {
return LazyHolder.INSTANCE;
}
}

饿汉式单例模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package Singleton;
/**
* @author yxx
* 饿汉式单例类,在类初始化时,已经自行实例化
*/
public class Singleton2 {
private Singleton2() {}
private static final Singleton2 single = new Singleton2();
// 静态工厂方法
public static Singleton2 getInstance() {
return single;
}
}

饿汉式在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变,所以天生是线程安全的。

登记式单例实际上维护了一组单例类的实例,将这些实例存放在一个Map(登记薄)中,对于已经登记过的实例,则从Map直接返回,对于没有登记的,则先登记,然后返回。

这里我对登记式单例标记了可忽略,我的理解来说,首先它用的比较少,另外其实内部实现还是用的饿汉式单例,因为其中的static方法块,它的单例在类被装载的时候就被实例化了。

饿汉式和懒汉式区别

从名字上来说,饿汉和懒汉,饿汉就是类一旦加载,就把单例初始化完成,保证getInstance的时候,单例是已经存在的了,而懒汉比较懒,只有当调用getInstance的时候,才回去初始化这个单例。另外从以下两点再区分以下这两种方式:
1、线程安全:
饿汉式天生就是线程安全的,可以直接用于多线程而不会出现问题,懒汉式本身是非线程安全的,为了实现线程安全有几种写法,分别是上面的1、2、3,这三种实现在资源加载和性能方面有些区别。
2、资源加载和性能:
饿汉式在类创建的同时就实例化一个静态对象出来,不管之后会不会使用这个单例,都会占据一定的内存,但是相应的,在第一次调用时速度也会更快,因为其资源已经初始化完成,而懒汉式顾名思义,会延迟加载,在第一次使用该单例的时候才会实例化对象出来,第一次调用时要做初始化,如果要做的工作比较多,性能上会有些延迟,之后就和饿汉式一样了。

至于1、2、3这三种实现又有些区别,
第1种,在方法调用上加了同步,虽然线程安全了,但是每次都要同步,会影响性能,毕竟99%的情况下是不需要同步的,
第2种,在getInstance中做了两次null检查,确保了只有第一次调用单例的时候才会做同步,这样也是线程安全的,同时避免了每次都同步的性能损耗
第3种,利用了classloader的机制来保证初始化instance时只有一个线程,所以也是线程安全的,同时没有性能损耗,所以一般我倾向于使用这一种。

什么是线程安全?

如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。或者说:一个类或者程序所提供的接口对于线程来说是原子操作,或者多个线程之间的切换不会导致该接口的执行结果存在二义性,也就是说我们不用考虑同步的问题,那就是线程安全的。

单例模式的优点

  • 单例类的唯一实例由单例类本身控制,所以可以很好地控制用户何时访问它。

适用单例模式的情景

  • 当系统需要某个类只能有一个实例。

下载源码请到

MyGitHub