设计模式(二):单例模式、命令模式、适配器模式和外观模式

五、单例模式

1、概念

单例模式确保一个类只有一个实例,并且提供一个全局访问点

2、实现

单例模式有多种实现方式,不同的实现方式有不同的特点

//多线程不安全的单例模式
//!!!错误的单例,不建议使用
public class Singletion{
     
	private static Singletion instance;
    
    private Singletion(){
     }
    
    public static Singletion getInstance(){
     
        if(instance == null){
     
            instance = new Singletion();
        }
        return instance;
    }
}


//非延迟实例化的单例
//会在程序运行时就创建Singleton对象,占用空间
public class Singleton{
     
    private static Singletion instance = new Singleton();
    
    private Singletion(){
     }
    
    public static Singletion getInstance(){
     
        return instance;
    }
}

//方法同步的单例,效率低
//!!!不建议使用
public class Singleton{
     
    private static Singletion instance = new Singleton();
    
    private Singletion(){
     }
    
    public synchronized static Singletion getInstance(){
     
        if(instance == null){
     
            instance = new Singletion();
        }
        return instance;
    }
}

//"双重检查加锁"的单例模式
//推荐使用
public class Singleton{
     
    //volatile保证,某个线程改变instance的值时对其他线程可见
    private volatile static Singletion instance的值时对其他线程可见 = new Singleton();
    
    //私有的构造器保证,不能直接使用new进行创建
    private Singletion(){
     }

    public static Singleton getInstance(){
     
        //先判断instance是否为null,再进入同步块,减少同步时间
        if(instance == null){
     
            //进行同步
            sychronized(Singleton.class){
     
                //只有一个线程能进行该部分,如果此时instance仍然为空,则创建对象
                if(instance == null){
     
                    instance = new Singletion();
                }
            }
        }
        return instance;
    }
}

3、要点

  • 单件模式确保程序中一个类最多只有一个实例
  • 单件模式提供访问这个实例的全局点
  • 在Java种实现单例模式需要私有构造器
  • 确定在性能和资源上的限制,然后小心地选择适当的方案实现单例,以解决多线程问题
  • 如果使用使用多个类加载器,可能导致单例失效产生多个实例

六、命令模式

1、概念

命令模式将“请求”封装成对象,以便使用不同的请求、队列或者日志来参数化其他对象。命令模式也支持可撤销的操作。

  • 命令模式可将“动作的请求者”从“动作的执行者”对象中解耦。
  • 一个命令对象通过在特定接收者上绑定一组动作来封装一个请求
  • 命令对象将动作和接收者包进对象中,这个对象只暴露出一个execute()方法,当此方法被调用时,接受组合会进行这些动作

2、基本命令模式实现

实现带一个插槽的遥控器对电灯进行控制

//命令接口
public interface Command {
     

    public void execute();

}

//电灯类,有开和关两个方法
public class Light {
     

    public void on() {
     
        System.out.println("Light on");
    }

    public void off() {
     
        System.out.println("Light off");
    }
}

//电灯控制类,继承自Command
public class LightOnCommand implements Command{
     

    private Light light;

    //构造器中传入某个电灯,并记录在实例变量中
    //一旦调用execute(),就由这个电灯对象成为接收者,负责接受请求
    public LightOnCommand(Light light){
     
        this.light = light;
    }

    @Override
    public void execute() {
     
        light.on();
    }
}

//简单遥控器(调用者)
public class SimpleRemoteControl {
     

    //有一个插槽持有命令,而这个命令控制着一个装置
    Command slot;

    public SimpleRemoteControl(){
     }

    //用于设置插槽控制的命令
    //如果客户想要改变遥控器按钮的行为,可以多次调用这个方法
    public void setCommand(Command command) {
     
        slot = command;
    }

    //当按下按钮时,这个方法就会被调用
    //并执行插槽对应命令的execute方法
    public void buttonWasPressed() {
     
        slot.execute();
    }
}

//本类是命令模式的客户
public class RemoteControlTest {
     

    public static void main(String[] args) {
     
        //创建一个调用者,之后会传入一个命令给他
        SimpleRemoteControl remoteControl = new SimpleRemoteControl();

        //这个电灯对象就是请求的接收者
        Light light = new Light();

        //创建一个命令,并把接收者传给他
        LightOnCommand command = new LightOnCommand(light);

        //把命令穿给调用者
        remoteControl.setCommand(command);

        //模拟按下按钮
        remoteControl.buttonWasPressed();
    }
}

3、类图

4、实现遥控器

按照下图实现遥控器及其对应的命令模式

//接收者:灯
public class Light {
     

    String type;

    public Light(String type) {
     
        this.type = type;
    }

    public void on() {
     
        System.out.println(type + " Light on");
    }

    public void off() {
     
        System.out.println(type + " Light off");
    }
}

//接收者:音响
public class Stereo {
     

    String type;

    public Stereo(String type) {
     
        this.type = type;
    }
    
    public void on(){
     
        System.out.println(type + " Stereo on");
    }

    public void off(){
     
        System.out.println(type + " Stereo off");
    }

    public void setCd(){
     
        System.out.println(type + " Stereo setCd");
    }

    public void setDvd(){
     
        System.out.println(type + " Stereo setDvd");
    }

    public void setRadio(){
     
        System.out.println(type + " Stereo setRadio");
    }

    public void setVolume(int volume){
     
        System.out.println(type + " Stereo setVolume = " + volume);
    }
}

//命令接口
public interface Command {
     

    public void execute();
    
    public void undo();

}

//表示该开关暂时不对应命令
public class NoCommand implements Command{
     
    @Override
    public void execute() {
     
        System.out.println("Nothing to do");
    }
        
    @Override
    public void undo() {
     
        System.out.println("Nothing to do");
    }
}

//打开灯的命令
public class LightOnCommand implements Command{
     

    private Light light;

    //构造器中传入某个电灯,并记录在实例变量中
    //一旦调用execute(),就由这个电灯对象成为接收者,负责接受请求
    public LightOnCommand(Light light){
     
        this.light = light;
    }

    @Override
    public void execute() {
     
        light.on();
    }   
    
    @Override
    public void undo() {
     
        light.off();
    }
}

//关闭灯的命令
public class LightOffCommand implements Command{
     

    private Light light;

    //构造器中传入某个电灯,并记录在实例变量中
    //一旦调用execute(),就由这个电灯对象成为接收者,负责接受请求
    public LightOffCommand(Light light){
     
        this.light = light;
    }

    @Override
    public void execute() {
     
        light.off();
    }
        
    @Override
    public void undo() {
     
        light.on();
    }
}

//打开音响并放入CD
public class StereoOnWithCDCommand implements Command{
     

    private Stereo stereo;

    public StereoOnWithCDCommand(Stereo stereo) {
     
        this.stereo = stereo;
    }

    @Override
    public void execute() {
     
        stereo.on();
        stereo.setCd();
        stereo.setVolume(11);
    }
        
    @Override
    public void undo() {
     
        stereo.off();
    }
}

//关闭音响的命令
public class StereoOffCommand implements Command{
     

    private Stereo stereo;

    public StereoOffCommand(Stereo stereo) {
     
        this.stereo = stereo;
    }

    @Override
    public void execute() {
     
        stereo.off();
    }
    
    @Override
    public void undo() {
     
        stereo.on();
        stereo.setCd();
        stereo.setVolume(11);
    }
}

//调用者:遥控器
public class RemoteControl {
     

    Command[] onCommands;
    Command[] offCommands;
    command undoCommand;

    //在构造方法中实例化并初始化这两个开关数组
    public RemoteControl() {
     
        onCommands = new Command[7];
        offCommands = new Command[7];

        Command noCommand = new NoCommand();
        for (int i = 0; i < 7; i++) {
     
            onCommands[i] = noCommand;
            offCommands[i] = noCommand;
        }
        undoCommand = noCommand;
    }

    //setCommand有三个参数,分别是插槽号,和开关命令
    public void setCommand(int slot, Command onCommand, Command offCommand) {
     
        onCommands[slot] = onCommand;
        offCommands[slot] = offCommand;
        undoCommand = offCommands[slot];
    }

    //当开关按下时,硬件就会负责调用下面两个方法
    public void onButtonWasPushed(int slot) {
     
        onCommands[slot].execute();
    }

    public void offButtonWasPushed(int slot) {
     
        offCommands[slot].execute();
    }
    
    public void undoButtonWasPushed() {
     
        undoCommand.undo();
    }
}

//客户
public class RemoteLoader {
     

    public static void main(String[] args) {
     
        //创建遥控器(调用者)
        RemoteControl remoteControl = new RemoteControl();

        //创建家具(接收者)
        Light livingRoomLight = new Light("Living room");
        Light kitchenLight = new Light("Kitchen light");
        Stereo stereo = new Stereo("Living room");

        //创建开关命令
        LightOnCommand livingRoomLightOnCommand = new LightOnCommand(livingRoomLight);
        LightOffCommand livingRoomLightOffCommand = new LightOffCommand(livingRoomLight);
        LightOnCommand kitchenLightOnCommand = new LightOnCommand(kitchenLight);
        LightOffCommand kitchenLightOffCommand = new LightOffCommand(kitchenLight);
        StereoOnWithCDCommand livingRoomStereoOnCommand = new StereoOnWithCDCommand(stereo);
        StereoOffCommand livingRoomStereoOffCommand = new StereoOffCommand(stereo);

        //为调用者设置命令,(将命令放入遥控器插槽)
        remoteControl.setCommand(0, livingRoomLightOnCommand, livingRoomLightOffCommand);
        remoteControl.setCommand(1, kitchenLightOnCommand, kitchenLightOffCommand);
        remoteControl.setCommand(2, livingRoomStereoOnCommand, livingRoomStereoOffCommand);

        //按动遥控器开关
        remoteControl.onButtonWasPushed(0);
        remoteControl.offButtonWasPushed(0);
        remoteControl.undoButtonWasPushed();
        remoteControl.onButtonWasPushed(1);
        remoteControl.offButtonWasPushed(1);
        remoteControl.onButtonWasPushed(2);
        remoteControl.offButtonWasPushed(2);
        remoteControl.onButtonWasPushed(3);
        remoteControl.offButtonWasPushed(3);
    }
}

//执行结果
Living room Light on
Living room Light off
Living room Light on ------------> undo
Kitchen light Light on
Kitchen light Light off
Living room Stereo on
Living room Stereo setCd
Living room Stereo setVolume = 11
Living room Stereo off
Nothing to do
Nothing to do

5、要点

  • 命令模式可以将发出请求的对象和执行请求的对象解耦
  • 在被解耦的两者之间是通过命令对象进行沟通的。命令对象封装了接收者和一个或一组动作
  • 调用者通过调用命令对象的execute()发出请求,这会使得接收者的动作被调用
  • 调用者可以接受命令当作参数,甚至在运行时动态进行
  • 命令可以支持撤销,做法是实现一个undo()方法来回到execute()执行前的状态
  • 宏命令是命令的一种简单的延申,允许调用多个命令。宏命令也支持撤销
  • 实际操作时,命令对象可以直接实现请求,也不是把请求委托给接收者
  • 命令模式也可以用来实现日志和事务系统

七、适配器模式和外观模式

1、适配器模式概念

适配器模式将一个类的接口,转换成客户期望的另一个接口。适配器让原本接口不兼容的类可以合作无间。

2、适配器模式例子

将枚举器(老的迭代器)适配迭代器

//迭代器
public interface Iterator(){
     
	
	public boolean hasNext(){
     
        //判断是否有下一个元素
    }
    
    public Object next(){
     
        //获取下一位元素
    }
    
    public boolean remove(){
     
        //删除元素
    }

}

//枚举器
public interface Enumeration(){
     
    	
	public boolean hasMoreElements(){
     
        //判断是否有下一个元素
    }
    
    public Object nextElement(){
     
        //获取下一位元素
    }
}

//枚举器转换迭代器的适配器
public class EnumerationIteratorAdapter(){
     
    
    private Enumeration enumeration;
    
    public EnumerationIteratorAdapter(Enumeration enumeration){
     
        this.enumeration = enumeration;
    }
    
    public boolean hasNext(){
     
        //判断是否有下一个元素
        enumeration.hasMoreElements();
    }
    
    public Object next(){
     
        //获取下一位元素
        enumeration.nextElement();
    }
    
    public boolean remove(){
     
        //删除元素
        //由于Enumeration没有删除元素的操作,所以抛出异常
        throw new UnsupportedOperationException();
    }
}

3、类适配器和对象适配器类图

类适配器通过多继承类的方式对被适配者和适配者进行适配,而对象适配器则是使用接口和组合的方式

4、外观模式概念

外观模式提供了一个统一的接口,用来访问子系统中的一群接口。外观定义了一个高层接口,让子系统更容易使用。

5、外观模式设计原则

  • 最少知识原则:只和你的密友谈话

不要让太多类耦合在一起,免得系统中一部分,会影响到其他部分

就任何对象而言,在该对象的方法内,只应该调用属于一下范围的方法:

  • 该对象本身
  • 被当作方法的参数而传进传递进来的对象
  • 此方法所创建或实例化的任何对象
  • 对象的任何组件
  • 如果某个对象是调用其他的方法的返回结果,不要调用该对象的方法

6、外观模式类图

7、要点

  • 当需要使用一个现有的类而其接口并不符合你的需求时,就使用适配器
  • 当需要简化并统一一个很大的接口或者一群复杂的接口时,使用外观
  • 适配器改变接口以符合客户的期望
  • 外观将客户从一个复杂的子系统中解耦
  • 适配器模式有两种形式:对象适配器和类适配器。类适配器需要用到多重继承
  • 有必要的话,可以为一个子系统实现一个以上的外观
  • 装饰器、适配器、外观三者的区别
    • 适配器将一个对象包装起来以改变其接口
    • 装饰器将一个对象包装起来以增加新的行为和责任
    • 外观将一群对象“包装”起来以简化其接口

你可能感兴趣的:(设计模式,后端,Java,设计模式)