Java必会设计模式【持续更新中】

1、策咯模式

在面试字节跳动的时候,面试官就让我用代码实现一下策略模式,说实话,当时我是很慌张的。在面试深信服的时候面试官也问了一下设计模式的单例模式,那时候我还不是很懂设计模式,然后我就把《First Head设计模式》给啃完了,但是就是简单过了一遍。这次面试字节跳动的时候又来,干…(省略内心1000字的问候)但是还是要好好写。

首先我们要知道策略模式是干了一件什么事情?

  • 策咯模式定义了算法族,分别封装起来,让他们之间可以相互替换,此模式让算法的变化独立于使用算法的客户

简单来说就是,我针对某一个事件,定义了很多解决方案,当面对不同的事件的时候能选取不同的方案去解决这个事情。那么问题来了,很多的不同方案要方便管理啊,也就是说我要他们都满足于同一个规则,这样方便我管理和替换啊。(你可以理解为这个事件为一台PC机,而解决方案为U盘,那么U盘要使用的话,必须是USB接口)
在这里插入图片描述

理解了这个的话就很容易了,接下来就是实现代码了:

interface USB_Interface
{
	void event();
}

class PC
{
	USB_Interface usb;
	
	//构造方法,传进来到底要使用哪一个U盘
	public PC(USB_Interface usb)
	{
		this.usb = usb;
	}

	void use()
	{
		//使用U盘里面的事件,这里要去找具体传进来的类实现的event方法
		//(也就是这个设计让策略可以实现替换)
		usb.event();
	}
}

/**
创建一个U盘,这个U盘必须实现USB接口,否则就不能被PC机使用
*/
class UP implments USB_Interface
{
	//具体的实现方法
	void event()
	{
		System.out.println("我是第一个U盘,我里面存了一个txt文档");
	}
}

class Test
{
	public static void main(String[] args)
	{
		//首先创建一个U盘
		UP up = new UP();
		//实例化一个PC机,并把U盘传进去
		PC pc = new PC(up);
		pc.use();
		
		//当然了,你也可以不在初始化PC机的时候就把U盘插进去,你可以设置U盘的
		//这样做的话,就写一个默认的构造函数,另外写一个set函数,先调用set函数在调用use
		/**
		PC pc = new PC();
		pc.setUP(up);
		pc.use();
		*/
	}
}

2、单例模式

这个应该是面试中最经常考的一个模式了,我层在深信服的面试中被问到了单例模式能用在什么场景,奈何那个时候我刚刚午睡醒来,脑子都是懵的。其实我也是对这个模式一知半解,所以有了上面的一幕,我把First Head设计模式看完了,然后发现到现在还是一知半解,所以这东西还是要用自己的话语说一遍才能,不理解的知识永远就不是自己的

同样的,我们也要了解一下单例模式是用来干嘛的?

  • 单例模式是指在内存中只会创建出而且仅创建一次的设计模式

那么单例模式在什么情况下使用呢?

  • 有频繁实例化然后销毁的情况,也就是频繁的 new 对象,可以考虑单例模式;
  • 创建对象时耗时过多或者耗资源过多,但又经常用到的对象;
  • 频繁访问 IO 资源的对象,例如数据库连接池或访问本地文件;

单例模式有什么实际应用场景?

  • 数据库连接池,创建数据库连接时一个很费时间很费操作的事情,我们通过创建很多数据库连接的线程放在数据库连接池里面,当我们要用的时候去线程池里面取。如果我们数据库连接池不是使用单例模式的话,就会有很多线程池,这样线程池的作用就体现不出来了
  • 还有就是各种准确的计数器了,什么程序计数器,网站在线人数的计数器,如果有多个计数器的话就没意义了

单例模式还分两种模式

  • 懒汉式:在需要用到的时候才创建给你
  • 饿汉式:类加载的时候就给你创建好了,不管你会不会使用

接下来就懒汉式饿汉式的单例模式来分别讲解一下

(1)懒汉式

我们上面讲了,懒汉式就是这个人很懒,只有当我们需要使用的时候才给我们创建对象,那么根据这个思想,我们可以写出这个代码

public class Singleton
{
	private static Singleton singleton;

	//单例模式的构造器一定要设置为私有,只有自己能创建对象,外面的人别想创建对象
	//那我们怎么提供一个对象呢?
	//我们可以提供一个函数接口啊,想要对象?你必须通过我的接口
	//(你个程序员,想要对象?来相亲网站吧,不然我就不给你)
	private Singleton ()
	{
		//....
	}

	public static Singleton getInstance()
	{
		//我们给出一个对象的时候,我们先判断一个实例到底有没有创建啊
		if(null == singleton)
		{
			//不存在的话,我就要给你整一个对象
			//1
			singleton = new Singleton();//2
			return singleton;
		}
		//如果存在了,我就直接把对象给你
		return singleton;
		
	}
}

这就是一个最简单的单例模式了,但是这个单例模式存在一些问题。试想一下,如果我有两个人同时请求获取这个对象的话,会发生什么事情?

张三李四
我是张三,我想要一个对象,所以我来到相亲网站去getInstance()获取一个对象
当程序执行到2之前,也就是1的位置的时候,张三因为一点事情耽误了一下,所以就中断了一小会,此时李四就像一个程咬金一样,半路杀出来,说我也要一个对象我是李四,我想要一个对象,所以我来到相亲网站去getInstance()获取一个对象
此时张三的对象还没有创建出来,李四在条件判断的时候,判断对象没有啊,那我就要创建一个给他
于是程序创建一个对象A给李四
张三此时忙完了自己的事情,继续索要对象,于是从1处继续执行
然后程序又创建了一个对象B给张三

咦,是不是有点不对劲啊?我明明就是单例模式啊,怎么会创建出两个对象来呢?

这不行啊,我要查一下课本,然后发现了一个同步锁synchronized,这样我就可以大大方方的创建了吧,毕竟这是同步锁啊,只能又一个人进去创建对象,于是我改…

package c_Singleton;
/**
*@time 2020年8月19日:上午10:52:27
*@author Weirdo
*@version 1.0
**/
public class Sington_L {

	private static Sington_L singleton;

	//单例模式的构造器一定要设置为私有,只有自己能创建对象,外面的人别想创建对象
	//那我们怎么提供一个对象呢?
	//我们可以提供一个函数接口啊,想要对象?你必须通过我的接口
	//(你个程序员,想要对象?来相亲网站吧,不然我就不给你)
	private Sington_L ()
	{
		//....
	}

	public static Sington_L getInstance()
	{
		//我们给出一个对象的时候,我们先判断一个实例到底有没有创建啊
		//我在外面加一个锁
		synchronized(Sington_L.class)
		{
			if(null == singleton)
			{
				//不存在的话,我就要给你整一个对象
				//1
				singleton = new Sington_L();//2
				return singleton;
			}
		}
		//如果存在了,我就直接把对象给你
		return singleton;
		
	}
}


package c_Singleton;
/**
*@time 2020年8月19日:上午10:49:23
*@author Weirdo
*@version 1.0
**/
public class Main {
	
	public static void main(String[] args) {
		Sington_L a = Sington_L.getInstance();
		Sington_L b = Sington_L.getInstance();
		System.out.println(a==b);
		//true
	}

}


对了,这样就万无一失了,一次只能有一个人进去创建对象,等你创建出来的时候,另外一个人再去请求对象,是不是很完美?

但是你有没有发现,这样子用同步锁会发生一个很大的问题,就是我们要限制的只是创建对象这个操作,而我却把这个判断的动作都锁住了,。试着想一下,我是要去相亲的,那么大的一家相亲网站,为了不出错,把当一个人在相亲的时候,网站卡住了,不能用了要等这个人相亲完出来才能用,然后用户找到你说:“什么垃圾玩意,卡死了”。然后你就收拾收拾回家放牛去吧。

那有没有解决方案?

  • 既然我这样子做会锁住整个过程,那我把锁细化一下行不行?我就仅仅锁住创建对象这个操作,如果判断对象已经存在的话,直接给你返回一个;不存在的话,对不起没得商量,你还是要乖乖排队

好,既然这样子的话,我又改改改…一顿操作

public class Singleton
{
	private static Singleton singleton;

	//单例模式的构造器一定要设置为私有,只有自己能创建对象,外面的人别想创建对象
	//那我们怎么提供一个对象呢?
	//我们可以提供一个函数接口啊,想要对象?你必须通过我的接口
	//(你个程序员,想要对象?来相亲网站吧,不然我就不给你)
	private Singleton ()
	{
		//....
	}

	public static Singleton getInstance()
	{
		//我们给出一个对象的时候,我们先判断一个实例到底有没有创建啊
		if(null == singleton)
		{
			//1
			synchronized(singleton)
			{
				//不存在的话,我就要给你整一个对象
				//2
				singleton = new Singleton();//3
				return singleton;
			}
			//4
		}
		//如果存在了,我就直接把对象给你
		return singleton;
		
	}
}

好的,这时又来了两个人张三和李四

张三李四
我是张三,我想要一个对象,所以我来到相亲网站去getInstance()获取一个对象
于是张三进来了1这个,发现有一个门,这个门有个限制,仅仅能进去一个人,张三发现里面没人,我可以进去
于是张三来到了2,然后张三又是屁事多,有什么事情耽误了一下,没有创建出对象来,此时李四又来了我是李四,我想要一个对象,所以我来到相亲网站去getInstance()获取一个对象
在判断的时候发现没有对象存在,那我就进来1,但是发现有一个门限制,仅仅能进去一个人,里面已经有人了,那我就等吧
此时张三的屁事搞完了,继续创建对象,张三于是拿到了一个对象A,屁颠屁颠走了,此时门限制解除了李四看到门限制解除了,那我就进去呗
然后李四又新建了一个对象B,对着新的对象跑了

此时你又发现了,不对啊,怎么我这个锁加了和没加一样啊,加在外面整个过程锁住了,老板炒我鱿鱼,不满足单例模式又炒鱿鱼,我真的是太难了。

单例模式是不可能放弃的,性能也是需要的,那我怎么权衡呢?你想着想着的时候迷迷糊糊的睡着了

  • 晚上周公不忍看你失业,于是托梦给你。在梦里大骂你一顿说:小孩子才做选择,单例和性能我全都要,你在锁里面再判断一次对象有没有存在不就行了?我们称之为双重检查锁机制

突然的惊醒,赶紧把周公的教诲记下来,于是又一顿改…

public class Singleton
{
	//volatile关键字
	private static volatile Singleton singleton;

	//单例模式的构造器一定要设置为私有,只有自己能创建对象,外面的人别想创建对象
	//那我们怎么提供一个对象呢?
	//我们可以提供一个函数接口啊,想要对象?你必须通过我的接口
	//(你个程序员,想要对象?来相亲网站吧,不然我就不给你)
	private Singleton ()
	{
		//....
	}

	public static Singleton getInstance()
	{
		//我们给出一个对象的时候,我们先判断一个实例到底有没有创建啊
		if(null == singleton)
		{
			//确保只有一个人能进去
			synchronized(singleton)
			{
				
				//进来之后再判断,有没有对象
				//不存在的话,我就要给你整一个对象
				if(null == singleton)
				{
					singleton = new Singleton();//3
					return singleton;
				}
				//如果存在的话,我直接返回
				return singleton;
			}
			//4
		}
		//如果存在了,我就直接把对象给你
		return singleton;
		
	}
}

这个方案已经很接近正确的解决方案了,还差最后一个,volatile关键字。

volatile关键字

  • 能够禁止指令重排
  • 能使得修改的内容马上让别的在使用这个变量的人感知(这里涉及到JVM的知识,往后会慢慢展开,现在记得这个就好)

(2)饿汉式

类加载的时候就给你创建好了,不管你会不会使用

package c_Singleton;
/**
*@time 2020年8月19日:上午10:48:38
*@author Weirdo
*@version 1.0
**/
public class Sington_E {

	//我在初始化的时候就给你来个对象,你要我就返回,就算你不要,我也创建出来,简单粗暴
	private final static Sington_E singleton = new Sington_E();

	private Sington_E ()
	{
		//....
	}

	public static Sington_E getInstance()
	{
		return singleton;
	}
}

package c_Singleton;
/**
*@time 2020年8月19日:上午10:49:23
*@author Weirdo
*@version 1.0
**/
public class Main {
	
	public static void main(String[] args) {
		Sington_E a = Sington_E.getInstance();
		Sington_E b = Sington_E.getInstance();
		System.out.println(a==b);
		//true
	}

}


3、观察者模式

观察者模式的概念?

  • 观察者模式定义了对象之间的一多依赖,这样一来,当一个对象改变状态时,它的所有依赖着都会收到通知并自动更新

观察者模式一般使用于什么场景?

  • 在我的理解中,观察者模式就像微信用户和公众号的关系,是一种订阅与被订阅的关系。当我订阅一个公众号的时候,公众号会把推文更新给订阅的人,当然我也可以取消订阅,这样我就可以不接受推文信息。

那我们想一下,怎么设计一个观察者模式,来让我们的系统变得容易拓展呢?也就是低耦合

  • 首先设计肯定是面向接口的,我们需要设计一个被订阅的接口(公众号)和一个订阅者的接口(用户)
  • 这样我们就可以通过实现接口来做出多个公众号还有用户
  • 还需要考虑的问题就是,我怎么让公众号也用户之间产生关联?

综合以上,我们可以得出,必须设计一个被订阅的接口(公众号),他必须有三个方法

  • 订阅的接口
  • 取消订阅的接口
  • 发布信息的接口

设计一个订阅者的接口(用户),他必须有一个方法

  • 接收信息的接口
public interface PublicAble
{
	//接收一个用户参数
	void register(UserAble user);
	//接收一个用户参数
	void unRegister(UserAble user);
	
	void notify(String info);
}

public interface UserAble
{
	void receive();
}

那现在我们有一个规范了,我们尝试来实现一下观察者模式吧(公众号—用户)

//公众号
public interface PublicAble
{
	//接收一个用户参数
	void register(UserAble user);
	//接收一个用户参数
	void unRegister(UserAble user);
	//接收要发送的信息
	void notify(String info);
}

//用户
public interface UserAble
{
	void receive(String info);
}

public class Public implements PublicAble
{
	//使用list集合来存储订阅的用户
	List<UserAble> users;

	public Public()
	{
		users = new ArraysList<>();
	}

	//接收一个用户参数
	void register(UserAble user)
	{
		users.add(user);
	}
	//接收一个用户参数
	void unRegister(UserAble user)
	{
		users.remove(users.indexOf(user));
	}
	
	void notify(String info)
	{
		for(int i=0;i<users.length;i++)
		{
			users.get(i).receive(info);
		}
	}
}

public class User implements UserAble
{
	String name;
	String ID;
	
	public User()
	{
		//..
	}

	void receive(String info)
	{
		System.out.println("我是"+ID+"号"+name+",我接受到信息:"+info);
	}
}

public class Main
{
	public static void main(String[] args)
	{
		//新建一个公众号实例
		Public publicer = new public();
		//新建两个用户
		User user1 = new User();
		User user2 = new User();
		//两个用户订阅公众号
		publicer.register(user1);
		publicer.register(user2);
		//通过公众号发布信息
		publicer.notify("推文信息")
		//用户2取消订阅
		publicer.remove(user2);
		//通过公众号发布信息
		publicer.notify("推文信息")
	}
}

其实我感觉观察者模式有点像策略模式的,只不过策略模式是一个对象属性(当然了,也可以是多个对象属性),这里的是对象的集合,其实原理都差不多

4、装饰者模式

按照惯例,我们先来了解一下装饰着模式干了一件什么事情?

  • 动态的将责任附加到对象上,若要拓展功能,装饰着提供了比继承更有弹性的替代方案
  • 说白了,就是在给一个东西装上了一个外壳,穿了一件衣服,在原有的基础之上有了更多的功能,我也将这个理解为组合

这里使用同样使用First Head里的例子,奶茶店加料。首先抽象一个奶茶和要加的材料之间有什么共同点。首先肯定是名字,也就是描述description,然后就是价格price

那么:

package d_decoration;
/**
*@time 2020年8月19日:下午3:40:23
*@author Weirdo
*@version 1.0
**/
public abstract class Common {

	String description = "unknow";
	
	public String getDescriprion() {
		return description;
	}
	
	public abstract double cost();
}

对于调味料,我们可以更加具体一点,让我们知道编写的类是调料

package d_decoration;
/**
*@time 2020年8月19日:下午3:46:40
*@author Weirdo
*@version 1.0
**/
public abstract class CondimentDecorator extends Common{

	public abstract String getDescriprion();
	

}

以上工作准备好了之后,我们要想一下,装饰着模式的核心思想是什么?

  • 我的理解就是,用一个对象封装另外一个对象,从而达到装饰的效果

所以,我们开始编写咖啡类,注意:这里的咖啡是没有添加任何东西的,最原生的咖啡

package d_decoration;
/**
*@time 2020年8月19日:下午3:48:30
*@author Weirdo
*@version 1.0
**/
public class Espresso extends Common{
	
	public Espresso() {
		description = "Espresso";
	}

	@Override
	public double cost() {
		// TODO Auto-generated method stub
		return 1.99;
	}

}

package d_decoration;
/**
*@time 2020年8月19日:下午3:51:55
*@author Weirdo
*@version 1.0
**/
public class HouseBlend extends Common{
	
	public HouseBlend() {
		// TODO Auto-generated constructor stub
		description = "HouseBlend";
	}

	@Override
	public double cost() {
		// TODO Auto-generated method stub
		return 0.89;
	}
	

}


然后我们再来编写调料类,记得我们上面将的,装饰着模式就是通过一个对象把另外一个对象封装起来

那么我们已经封装好了一个咖啡的类了,我们要做的就是是用调料的类来封装一下咖啡的类

package d_decoration;
/**
*@time 2020年8月19日:下午3:54:34
*@author Weirdo
*@version 1.0
**/
public class Mocha extends CondimentDecorator{
	
	public Common common;
	
	

	public Mocha(Common common) {
		this.common = common;
	}

	@Override
	public String getDescriprion() {
		// TODO Auto-generated method stub
		return common.getDescriprion()+", Mocha";
	}

	@Override
	public double cost() {
		// TODO Auto-generated method stub
		return common.cost()+.20;
	}

}

package d_decoration;
/**
*@time 2020年8月19日:下午3:58:24
*@author Weirdo
*@version 1.0
**/
public class Pearl extends CondimentDecorator{
	
	public Common common;
	
	

	public Pearl(Common common) {
		this.common = common;
	}

	@Override
	public String getDescriprion() {
		// TODO Auto-generated method stub
		return common.getDescriprion()+", Pearl";
	}

	@Override
	public double cost() {
		// TODO Auto-generated method stub
		return common.cost()+.80;
	}

}


不要忘记了一个事实,我们所有的编码其实都有一个最原始的父类,也就是Common抽象类,也就是说,不管是咖啡还是调味料,其本质都是Common

接下来我们测试一下代码

package d_decoration;
/**
*@time 2020年8月19日:下午3:59:05
*@author Weirdo
*@version 1.0
**/
public class Main {

	public static void main(String[] args) {
		//点一杯咖啡
		Common espresso = new Espresso();
		//加一次Mocha
		Common addMocha1 = new Mocha(espresso);
		//再加一次Mocha
		Common addMocha2 = new Mocha(addMocha1);
		//输出
		System.out.println(addMocha2.getDescriprion());
		System.out.println(addMocha2.cost());
		
		//点一杯原生的
		espresso = new Espresso();
		//输出
		System.out.println(addMocha1.getDescriprion());
		System.out.println(addMocha1.cost());
	}
}

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值