23种设计模式——不断更新中

1.单例模式

  • 在大部分时候,我们把类的构造器定义成public访问权限,允许任何类自由创建该类的对象。但在某些时候,允许其他类自由创建该类的对象没有任何意义,还可能造成系统性能下降(因为创建一个对象的系统开销问题)。例如系统可能只有一个窗口管理器,一个假脱机打印设备或一个数据库引擎访问点,此时如果在系统中为这些类创建多个对象就没有太大的实际意义。
  • 如果一个类始终只能创建一个实例,则这个类被称为单例类。
  • 总之,在一些特殊场景下,要求不允许自由创建该类的对象,而是只允许为该类创建一个对象。为了避免其他类自由创建该类的实例,我们把该类的构造器使用private修饰,从而把该类的所有构造器隐藏起来。
  • 根据良好封装的原则:一旦把该类的构造器隐藏起来,则需要提供一个public方法作为该类的访问点,用于创建该类的对象,且该方法必须使用 static修饰(因为调用该方法之前还不存在对象,因此调用该方法的不可能是对象,只能是类)。
  • 除此之外,该类还必须缓存已经创建的对象,否则该类无法知道是否曾经创建过对象,也就无法保证只创建一个对象。为此该类需要使用一个属性来保存曾经创建的对象,因为该属性需要被上面的静态方法访问,故该属性必须使用static修饰。
    基于上面的介绍,下面的程序创建了一个单例类(懒汉式):
class Singleton{
    //使用一个变量来缓存曾经创建的对象,要被getInstance访问,所以static
    private static Singleton instance;
    //private私有化无参构造
    private Singleton(){}
    //提供外界接口,静态类方法,类加载的时候调用,可以通过类名直接调用
    //调用方法前不存在对象,因此调用方法时不可能是类,只能是对象,所以要加static修饰
    public static Singleton getInstance(){
        if(instance==null)instance=new Singleton();
        return instance;
    }
}
public class Main {
    public static void main(String[] args){
        //测试 instance1是直接创建的,instance2是返回之前创建的
        Singleton instance1 = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        //返回true
        System.out.println(instance1==instance2);
    }
}

正是通过上面getInstance方法提供的自定义控制(这也是封装的优势:不允许自由访问类的属性和实现细节,而是通过方法来控制合适暴露),保证Singleton类只能产生一个实例,所以在TestSingleton类的 main方法中看到两次产生的Singleton对象实际上是同一个对象。

  • 单例模式主要有4种
    在这里插入图片描述
  • 懒汉式-线程不安全
public class Singleton {
	private static Singleton Instance;
	private Singleton() {
	}
	public static Singleton GetInstance() {
		if (Instance == null) {
			Instance = new Singleton();
		}
		return Instance;
	}
}
优缺点:
私有静态变量 uniqueInstance 被延迟实例化,这样做的好处是,如果没有用到该类,那
么就不会实例化 uniqueInstance,从而节约资源。
线程不安全,多线程情况下会多次创建实例。
  • 饿汉式-线程安全
public class Singleton {
	private static Singleton Instance = new Singleton();
	private Singleton() {
	}
	public static Singleton GetInstance() {
		return Instance;
	}
}
优缺点:
采取直接实例化 uniqueInstance 的方式就不会产生线程不安全问题。
但是直接实例化的方式也丢失了延迟实例化带来的节约资源的好处。
  • 懒汉式-线程安全
public class Singleton {
	private static Singleton Instance;
	private Singleton() {
	}
	public static synchronized Singleton GetInstance() {
		if (Instance == null) {
			Instance = new Singleton();
		}
		return Instance;
	}
}
  • 双重校验锁-线程安全
public class Singleton {
	private volatile static Singleton Instance;
	private Singleton() {
	}
	public static Singleton getInstance() {
		if (Instance == null) {//先判断实例是否存在,不存在再加锁
			synchronized (Singleton.class) {//由于Instace实例有没有被创建过实例不知道,只能对其clss加锁
				if (Instance == null) {//当多个线程的时候,只有一个进入,避免多次创建对象!
					Instance = new Singleton();
				}
			}
		}
		return Instance;
	}
}

追问1:这里面试官可能会问使用volatile的好处?
答:Instance 采用 volatile 关键字修饰也是很有必要的, Instance = new Singleton();这段代码其实是分为三步执行:

  1. 为 uniqueInstance 分配内存空间
  2. 初始化 uniqueInstance
  3. 将 uniqueInstance 指向分配的内存地址

但是由于 JVM 具有指令重排的特性,执行顺序有可能变成 1>3>2。指令重排在单线程环境
下不会出现问题,但是在多线程环境下会导致一个线程获得还没有初始化的实例。例如,线
程 T1 执行了 1 和 3,此时 T2 调用 geteInstance() 后发现 Instance 不为空,因此返回
Instance,但此时 Instance 还未被初始化。
使用 volatile 可以禁止 JVM 的指令重排,保证在多线程环境下也能正常运行。

2. 模板模式

  • 抽象类体现的就是一种模板模式的设计,抽象类作为多个子类的通用模板,子类在抽象类的基础上进行扩展、改造,但子类总体上会大致保留抽象类的行为方式。
  • 如果编写一个抽象父类,父类提供了多个子类的通用方法,并把一个或多个方法留给其子类实现,这就是一种模板模式,模板模式也是最常见、最简单的设计模式之一。例如前面介绍的Shape、Circle和Triangle 三个类,已经使用了模板模式。下面再介绍一个模板模式的范例,在这个范例的抽象父类中,父类的普通方法依赖于一个抽象方法,而抽象方法则推迟到子类中提供实现。
//Mian函数可以当作计算具体一种汽车的转速(假如Main是小轿车的意思)
public class Main extends SpeedMeter{
    //在小轿车的类里面重写定义它的半径
    @Override
    public double getRadius() {
        return 0.28;
    }
    public static void main(String[] args){
        Main csm = new Main();
        //设置转速
        csm.setTurnRate(15);
        System.out.println(csm.getSpeed());

    }
}
//这个是抽象的父类
abstract class SpeedMeter{
    //转速
    private double turnRate;
    public SpeedMeter(){}
    //把返回车轮半径的方法定义成抽象方法
    public abstract double getRadius();
    public void setTurnRate(double turnRate){
        this.turnRate=turnRate;
    }
    //定义计算速度的通用算法
    public double getSpeed(){
        //速度等于 车轮半径*2*PI*转速
        return Math.PI*2*getRadius()*turnRate;
    }
}
  • 上面程序种SpeedMeter类定义了一个抽象的SpeedMeter类(车速表),该表里定义了一个getSpeed方法,该方法用于返回当前车速,getSpeed方法依赖于getRadius方法的返回值。对于一个抽象的SpeedMeter类而言,它无法确定车轮的半径,因此getRadius方法必须推迟到其子类中实现。
  • 上面程序中其子类Main的代码,该类实现了其抽象父类的getRadius()方法,即可创建SpeedMeter类的对象,也可通过该对象来取得当前速度。
  • SpeedMeter类里提供了速度表通用算法,但一些具体的实现细节则推迟到其子类Main类中实现。这也是一种典型的模板模式。
  • 模板模式在面向对象的软件中很常用。其原理简单,实现也很简单。下面是使用模板模式的一些简单规则:
  1. 抽象父类可以只定义需要使用的某些方法,其余则留给其子类实现。
  2. 父类中可能包含需要调用的其他系列方法的方法,这些被调方法既可以由父类实现,也可以由其子类实现。父类里提供的方法只是定义了一个通用算法,其实现也许并不完全由自身实现,而必须依赖于其子类的辅助。

3.命令模式

  • 考虑这样一种场景:某个方法需要完成某一个行为,但这个行为的具体实现无法确定,必须等到执行该方法时才可以确定。具体一点:假设有个方法需要遍历某个数组的数组元素,但无法确定在遍历数组元素时如何处理这些元素,需要在调用该方法时指定具体的处理行为。
  • 这个要求看起来有点奇怪:这个方法不仅需要普通数据可以变化,甚至还有方法执行体也需要变化,难道我们能把“处理行为”作为一个参数传入该方法?
  • 对于这样一个需求,我们必须把“处理行为”作为参数传入该方法,这个“处理行为”用编程来实现就是一段代码。那如何把这段代码传入该方法呢?
  • 因为Java不允许代码块单独存在,因此我们使用一个Command 接口来定义一个方法,用这个方法来封装“处理行为”。下面是该Command接口代码。
public interface Commond{
    //接口里面定义process方法用于封装"处理行为"
    void process(int[] target);
}
  • 上面的 Command 接口里定义了一个process方法,这个方法用于封装“处理行为”,但这个方法没有方法体——因为现在还无法确定这个处理行为。
  • 下面是需要处理数组的处理类,在这个处理类中包含一个process方法,这个方法无法确定处理数组的处理行为,所以定义该方法时使用了一个 Command参数,这个Command参数负责对数组的处理行为。该类的程序代码如下。
public class ProcessArray{
    public void process(int[] target,Commond cmd){
        cmd.process(target);
    }
}

通过一个Command类,就实现了让 ProcessArray类和具体“处理行为”的分离,程序使用Command接口代表了对数组的处理行为。Command接口也没有提供真正的处理,只有等到需要调用ProcessArray对象的process方法时,才真正传入一个 Command对象,才确定对数组的处理行为。

public class TestCommand{
    public static void main(String[] args) {
        ProcessArray pa = new ProcessArray();
        int[] target={-3,-4,5,6};
        //第一次处理数组,具体的处理行为取决于PrintCommand
        pa.process(target,new PrintCommand());
        //第二次处理数组,具体的处理行为取决于AddCommand
        pa.process(target,new AddCommand());
    }
}
  • 两次不同处理行为的效果,也就实现了process方法和“处理行为”的分离,两次不同的处理行为是通过 PrintCommand类和 AddCommand类提供的。下面分别是 PrintCommand类和AddCommand类的代码。
public class PrintCommand implements Command{
    public void process(int[] target) {
        for(int tmp:target){
            System.out.println("迭代输出目标数组的元素"+tmp);
        }
    }
}
public class AddCommand implements Command{
    public void process(int[] target) {
        int sum=0;
        for(int tmp:target){
        sum+=tmp;
        }
        System.out.println("数组的元素总和是:"+sum);
    }
}

对于PrintCommand和 AddCommand两个实现类而言,实际有意义的部分就是process(int[] target)方法,该方法的方法体就是传入ProcessArray类里 process方法的“处理行为”,通过这种方式就可实现process方法和“处理行为”的分离。

4. 简单工厂模式(可以看DateFormate源码就是使用的这种模式)

  1. 定义:定义一个工厂类,他可以根据参数的不同返回不同类的实例,被创建的实例通常都具有共同的父类
  2. 在简单工厂模式中用于被创建实例的方法通常为静态(static)方法,因此简单工厂模式又被成为静态工厂方法(Static Factory Method),属于类的创建型模式
  3. 需要什么,只需要传入一个正确的参数,就可以获取所需要的对象,而无需知道其实现过程
  4. 例如,我开一家披萨店,当客户需要某种披萨并且我这家店里也能做的时候,我就会为其提供所需要的披萨(当然是要钱的哈哈),如果其所需的我这没有,则是另外的情况,后面会谈。这时候,我这家 披萨店就可以看做工厂(Factory),而生产出来的披萨被成为产品(Product),披萨的名称则被称为参数,工厂可以根据参数的不同返回不同的产品,这就是简单工厂模式
    在这里插入图片描述
  • 结构
  1. Factory(工厂):核心部分,负责实现创建所有产品的内部逻辑,工厂类可以被外界直接调用,创建所需对象
  2. Product(抽象类产品):工厂类所创建的所有对象的父类,封装了产品对象的公共方法,所有的具体产品为其子类对象
  3. ConcreteProduct(具体产品):简单工厂模式的创建目标,所有被创建的对象都是某个具体类的实例。它要实现抽象产品中声明的抽象方法(有关抽象类)
  • 实现
public class Main{
    public static void main(String[] args) {
        //测试
        Product product=Factory.GetProduct("A");
        product.MethodDiff();
        Product product0=Factory.GetProduct("B");
        product0.MethodDiff();
    }
}

//定义一个工厂类
class Factory{
    public static Product GetProduct(String arg){
        Product product=null;
        if(arg.equals("A")){
            product = new ProductA();
        }else {
            product = new ProductB();
        }
        return product;
    }
}


//总的公共的抽象类
abstract class Product{
    public void MethodName(){
        System.out.println("我是一个产品");
        //公共方法实现
    }
    //声明抽象的业务方法
    public abstract void MethodDiff();
}
class ProductA extends Product{
    @Override
    public void MethodDiff() {
        System.out.println("产品A");
    }
}
class ProductB extends Product{

    @Override
    public void MethodDiff() {
        System.out.println("产品B");
    }
}
  • 优点:实现对象的创建和使用分离,创建交给工厂去负责,不关心怎么创建,只关心怎么使用
  • 缺点:工厂类不够灵活,新增一个就要修改工厂类,修改判断逻辑,产品很多的话,逻辑将会非常的复杂
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

西瓜程序设计

您的打赏将是我创作的最大动力!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值