软件设计模式-单例模式-命令模式
单例模式(singleton model)
单例模式可以保证所实例化的对象只有一个
例如在Windows中 我们希望在不同时刻打开的注册表 我们从操作系统获得的注册表应该是一致的 所以这就必须程序保证仅且仅一次实例化注册表对象
实现单例的方式主要有三种
常规单例模式实现
常见的实现单例模式(这种 不具有多线程安全)
这种实现单例的做法是对我们想要实现单例的类私有化其构造方法 ** 然后在单例类中单独开出一个方法用于外界使用该单例对象 一般而言是getInstance**
示例代码如下
/**
* @Title: SingletonModelTest.java
* @Package
* @Description: TODO(用一句话描述该文件做什么)
* @author Lustre
* @date 2019年12月9日
* @version V1.0
*/
/**
* @author Nigel
* @version 创建文件时间:2019年12月9日 下午8:30:21
*/
/**
*
* @ClassName: SingletonClass
* @Description: 单例模式演示类
* @author Nigel
* @date 2019年12月9日
*
*/
class SingletonClass {
private static SingletonClass singletonClass;
/**
* 私有化其构造方法
*
*/
private SingletonClass() {
}
public static SingletonClass getInstance() {
if (singletonClass == null) {
singletonClass = new SingletonClass();
}
return singletonClass;
}
}
/**
* @ClassName: SingletonModelTest
* @Description: TODO(这里用一句话描述这个类的作用)
* @author Nigel
* @date 2019年12月9日
*
*/
public class SingletonModelTest {
/**
* @Title: main
* @Description: TODO
* @param args
* @return void
* @throws
*/
public static void main(String[] args) {
//如果我们此时强行使用其单例类的构造方法则会报错
//报错信息:The constructor SingletonClass() is not visible
//实例化两个对象 看他们是否指向同一对象
SingletonClass test1 = SingletonClass.getInstance();
SingletonClass test2 = SingletonClass.getInstance();
if (test1 == test2) {
System.out.println("指向同一地址!");
} else {
System.out.println("不指向同一地址!");
}
}
}
运行结果如图所示
这样大体上让人觉得它确实可能保证类有且仅有一个实例化对象 但是这种方法是不具备多线程安全的 有可能在判断的时候条件竞争
示例代码如下
/**
* @Title: SingletonModelTest.java
* @Package
* @Description: TODO(用一句话描述该文件做什么)
* @author Lustre
* @date 2019年12月9日
* @version V1.0
*/
/**
* @author Nigel
* @version 创建文件时间:2019年12月9日 下午8:30:21
*/
/**
*
* @ClassName: SingletonClass
* @Description: 单例模式演示类
* @author Nigel
* @date 2019年12月9日
*
*/
class SingletonClass {
private static SingletonClass singletonClass;
/**
* 私有化其构造方法
*
*/
private SingletonClass() {
}
public static SingletonClass getInstance() {
if (singletonClass == null) {
singletonClass = new SingletonClass();
}
return singletonClass;
}
}
/**
*
* @ClassName: ThreadTest
* @Description: 测试是否具有多线程安全
* @author Nigel
* @date 2019年12月9日
*
*/
class ThreadTest extends Thread {
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println(System.identityHashCode(SingletonClass.getInstance()));
return ;
}
}
/**
* @ClassName: SingletonModelTest
* @Description: TODO(这里用一句话描述这个类的作用)
* @author Nigel
* @date 2019年12月9日
*
*/
public class SingletonModelTest {
/**
* @Title: main
* @Description: TODO
* @param args
* @return void
* @throws
*/
public static void main(String[] args) {
Thread[] threads = new Thread[100];
//初始化线程
for (int i = 0; i < 100; i++) {
threads[i] = new ThreadTest();
}
//启动线程
for (int i = 0; i < 100; i++) {
threads[i].start();
}
}
}
运行结果 可以看到发生了条件竞争
多线程安全的单例模式实现
使用Synchronized关键字修饰要进行同步的方法
示例如下
class SingletonClass {
private static SingletonClass singletonClass;
/**
* 私有化其构造方法
*
*/
private SingletonClass() {
}
public static synchronized SingletonClass getInstance() {
if (singletonClass == null) {
singletonClass = new SingletonClass();
}
return singletonClass;
}
}
通过上述的多线程测试 我们发现将不会再出现条件竞争的状态
但我们的条件竞争 实际上是只会发生在第一次 一旦我们在第一次获取成功获取而没有因为多线程的问题受到影响 那么第二次到最后就不会发生问题 而像我们这样进行修饰之后 在异步操作之后 就会浪费一部分额外的锁资源 这是没有必要的 但是这种方式是很简单且非常有效的
eagerly 多线程安全单例实现
当然 我们可以通过一些特殊的操作实现既能多线程安全而且满足单例的情况 如下所示
我们在单例类中直接在一开始就实例化一次 这样我们就不用判断 这样的话显然就没有多线程冲突的情况了
class SingletonClass {
private static SingletonClass singletonClass = new SingletonClass();
/**
* 私有化其构造方法
*
*/
private SingletonClass() {
}
public static SingletonClass getInstance() {
return singletonClass;
}
}
当然 除了上面的那种方式以外 我们还可以通过double-checked locking这种方式实现获取单例实例化对象的安全方法
双检锁/双重校验锁(double-checked lockin)
这种方式是只会在第一次的时候实现加锁目的 第二次之后就不会再浪费锁资源
这次我们只在第一次判断的时候加锁
示例代码如下所示
class SingletonClass {
private static volatile SingletonClass singletonClass;
/**
* 私有化其构造方法
*
*/
private SingletonClass() {
}
public static SingletonClass getInstance() {
//这次并没有直接在该方法上加上synchronized关键字
if (singletonClass == null) {
synchronized (SingletonClass.class) {
if (singletonClass == null) {
singletonClass = new SingletonClass();
}
}
}
return singletonClass;
}
}
命令模式
命令模式的应用场景是主要体现在命令的执行 例如进入餐馆 我们 作为客户 仅仅需要把想吃的事物分装成一个菜单 然后交给服务员 由服务员去传递给真正做饭的后厨 同样的 服务员也不需要关心该如何做出正确的事物 服务员起到的作用是传递即通知后厨。 所以命令模式最常用的场景就是底层和高层应该解耦 即底层应该专心实现它的细节 而不需要关心高层什么时候 以什么方式去调用它 而作为高层 应该只关心命令的发送 即只关心传递什么样的指令动作序列给底层 让底层去实现我们想要的服务 获得底层服务后的结果。
命令模式的一个简单示例如下所示 这里仅演示遥控器只有一个插槽的情形
对于多个插槽的情况 我们使用数组或者列表即可实现 实现多插槽 我们每次调用命令的时候就得额外指定一个int数来表明我们想要的服务号
/**
* @Title: CommandModelTest.java
* @Package
* @Description: TODO(用一句话描述该文件做什么)
* @author Lustre
* @date 2019年12月10日
* @version V1.0
*/
/**
* @author Nigel
* @version 创建文件时间:2019年12月10日 下午1:35:29
*/
/**
*
* @ClassName: Light
* @Description: 电灯类
* @author Nigel
* @date 2019年12月10日
*
*/
class Light {
public void on() {
System.out.println("now light on!");
}
public void off() {
System.out.println("now light off!");
}
}
class GarageDoor {
public void on() {
System.out.println("now garageDoor on!");
}
public void off() {
System.out.println("now garageDoor off!");
}
}
/**
*
* @ClassName: Command
* @Description: 首先需要一个Command对象的接口 作为一个抽象的Command接口 然后具体化为具体的执行动作 例如打开电灯 关闭电灯等
* @author Nigel
* @date 2019年12月10日
*
*/
interface Command {
//对于子类 需要实现这个抽象接口 执行具体的动作指令
void execute();
}
/**
*
* @ClassName: LightOnCommand
* @Description: 实现这个具体的命令类
* @author Nigel
* @date 2019年12月10日
*
*/
class LightOnCommand implements Command{
Light light;
/**
* 创建一个新的实例 LightOnCommand.
*
*/
public LightOnCommand(Light light) {
//获得一个电灯对象用于打开
this.light = light;
}
public void LightOnAction() {
//打开电灯
light.on();
}
@Override
public void execute() {
LightOnAction();
}
}
/**
*
* @ClassName: GarageDoorOpenCommand
* @Description:车库开门类
* @author Nigel
* @date 2019年12月10日
*
*/
class GarageDoorOpenCommand implements Command{
GarageDoor garageDoor;
/**
* 创建一个新的实例 GarageDoorOpenCommand. 需要指定哪一个车库门开开
*
*/
public GarageDoorOpenCommand(GarageDoor garageDoor) {
// TODO Auto-generated constructor stub
this.garageDoor = garageDoor;
}
private void garageDoorOpen() {
if (garageDoor == null) {
System.out.println("you have not target a garageDoor");
} else {
garageDoor.on();
}
}
@Override
public void execute() {
garageDoorOpen();
}
}
/**
*
* @ClassName: SimpleRemoteControl
* @Description: 模拟遥控器 即餐厅的顾客 Client
* @author Nigel
* @date 2019年12月10日
*
*/
class SimpleRemoteControl {
//现在只是简单的测试只放置一个命令时候的情况
Command slot;
public void setCommand( Command clientCommand ) {
System.out.println("设定" + clientCommand.getClass().getName() + "的操作给" + this.getClass().getName());
slot = clientCommand;
}
public void buttonPressed() {
//我们这里调用了这个抽象接口 遥控器现在确实不需要关注底层的细节 只需要调用这个统一的抽象接口即可
System.out.println(this.getClass().getName() + "执行" + slot.getClass().getName() + "的操作");
slot.execute();
}
}
/**
* @ClassName: CommandModelTest
* @Description: TODO(这里用一句话描述这个类的作用)
* @author Nigel
* @date 2019年12月10日
*
*/
public class CommandModelTest {
/**
* @Title: main
* @Description: TODO
* @param args
* @return void
* @throws
*/
public static void main(String[] args) {
//给车库门发出指令
SimpleRemoteControl remoteControl = new SimpleRemoteControl();
GarageDoor garageDoor = new GarageDoor();
Command clientCommand = new GarageDoorOpenCommand(garageDoor);
remoteControl.setCommand(clientCommand);
remoteControl.buttonPressed();
}
}