java有很多设计模式,“模式”两个字体现出通用性,主要是以java的多态作为实现的技术手段。
1. 单例模式
1.1、懒汉模式(多线程不安全)
线程不安全是因为:当多个线程同时第一次调用getInstace方法的时候,singleton为null,因此这些线程都会执行new Singleton()创建对象初始化,但是静态变量只能初始化一次,故会报错。
public class Singleton{
private static Singleton singleton=null;
private Singleton(){}
public static Singleton getInstace(){
if(singleton==null){
singleton=new Singleton();
}
return singleton;
}
}
1.2、懒汉模式(加同步锁,加锁过程复杂,会降低性能)
public class Singleton{
private static volatile Singleton singleton=null;
private Singleton(){}
public static Singleton getInstace(){
if(singleton == null){
synchronized(Singleton.class){
if(singleton==null){
singleton=new Singleton();
}
}
}
return singleton;
}
}
1.3、饿汉模式(多线程安全,但是类加载的时候就创建了实例,不管你用不用,它都会给你创建好,所以耗内存)
public class Singleton{
private static Singleton singleton=new Singleton();
private Singleton(){}
public static Singleton getInstace(){
return singleton;
}
}
1.4、 静态内部类(这个方案推荐,既不会事先给你创建好实例,而且线程安全)。
因为内部类在被调用的时候才去加载该内部类,从而达到只有当用到的时候,才去创建Singleton对象,实现了懒汉特性,其次,是线程安全,因为JVM会保证静态对象创建的线程安全的。
public class Singleton {
private Singleton(){
}
private static class SingletonHolder{
private final static Singleton instance=new Singleton();
}
public static Singleton getInstance(){
return SingletonHolder.instance;
}
}
2. 工厂模式
2.1、静态工厂模式
Apple 和 Banana等类都是实现的Fruit类,利用一个静态的工厂方法根据条件参数去生产相应的实例化对象。
public class FruitFactory{
public static Fruit createFruit(String fruitName){
switch(fruitName){
case "apple":
return new Apple();
case "banana":
return new Apple();
default:
return null;
}
}
}
public static void main(String[] args){
Fruit apple = FruitFactory.createFruit("apple");
}
2.2、工厂模式
给每一个种实例化对象都设置一个生成工厂,然后调用相应的工厂,就能生产出对应的实例化对象。
interface FruitFactory{
Fruit createFruit();
}
public class AppleFactory implements FruitFactory{
@Override
public Fruit createFruit(){
return new Apple();
}
}
public class BananaFactory implements FruitFactory{
@Override
public Fruit createFruit(){
return new Banana();
}
}
public static void main(String[] args){
Fruit apple = new AppleFactory().createFruit();
}
2.3、 抽象工厂模式
public interface Fruit{
void printName();
}
public interface Book{
void printBookName();
}
public class Apple implements Fruit{
@Override
public void printName(){
System.out.println("我是苹果");
}
}
public class Banana implements Fruit{
@Override
public void printName(){
System.out.println("我是香蕉");
}
}
public class Mathematics implements Book{
@Override
public void printBookName(){
System.out.println("我是数学");
}
}
public class English implements Book{
@Override
public void printBookName(){
System.out.println("我是英语");
}
}
public interface FruitFactory{
Fruit createApple();
Fruit createBanana();
Book createMathematics();
Book createEnglish();
}
public class MyFactory implements FruitFactory{
@Override
public Fruit createApple(){
return new Apple();
}
@Override
public Fruit createBanana(){
return new Banana();
}
@Override
public Book createMathematics(){
return new Mathematics();
}
@Override
public Book Book createEnglish(){
return new English();
}
}
总结三个工厂模式的区别:静态工厂模式比较简单,就是用于一类实例化对象的,根据参数条件的不同,生产出对应的对象(比如,生成水果,给定水果的类型,分别生成出对应的水果,比如苹果,香蕉等等)。工厂模式也是用于生产一类对象的,有一个工厂接口,针对每一类对象都有一个专门的生成工厂类(继承工厂接口),比如苹果工厂就生成苹果,香蕉工厂就生成香蕉。抽象工厂就是工厂模式的扩展版,不仅可以生成水果,还可以生成塑料袋等等,然后水果分为多种类型水果,塑料袋可以有多种塑料袋,但是抽象工厂只有一个(包含了全部的生成方法),实现工厂也有一个(实现 了全部的生成方法)。
3. 代理模式
代理可以理解为中介,客户只需要将信息交给中介,中介就会替你做完所有的事情,让客户和事情隔开,你客户有什么事情,让中介(代理)去做比如:理发这件事,你可以自己理发,但是如果你没有这个技能的话,就要找理发师,将自己交给理发师,然后理发这事情的执行就跟你没关系,理发师会执行的,这就是代理的主要作用。
3.1 静态代理
定义业务主题
public interface Hair{ //这是业务主题接口,即代理要处理的是什么类型的客户和业务
void execute();
}
public class cutHair implements Hair{ //这是具体的客户类
@Override
public void execute(){
System.out.println("我是做剪头发业务");
}
}
public class hotHair implements Hair{ //这是具体的客户类
@Override
public void execute(){
System.out.println("我是做烫头发业务");
}
}
定义代理类:
public class Proxy implements Hair{
private Hair hair = null;
public Proxy(Hair hair){
this.hair = hair;
}
@Override
public void execute(){
if(this.hair != null){
//前 这里可以执行一些代码
this.hair.execute();
//后 这里也可以执行一些代码
}
}
}
测试一下代理:
public class Test{
public static void main(String[] args){
Hair hair = new HotHair();
Proxy p = new Proxy(hair);
p.execute();
}
}
静态代理相比其它代理模式没有什么优点,唯一的优点也是代理的共同优点:将客户与要做的业务隔离开;缺点是:接口中的方法被重复实现,浪费,而且只能处理一种客户类(其实可以做到处理多种客户类,但是非常不灵活,且实现起来比较冗余)。
3.2 动态代理
动态代理可以处理任何客户类型的不同业务,功能大而全面,但是缺点是开销增大了。
怎么做到的呢? 客户类有任意多种,我们无法指定代理类去继承主题接口,所以解决方法就是java反射机制(根据对象获取类信息,再根据类信息实例化成对象)。根据客户对象,得到客户类信息、客户类的接口信息、调用的方法(执行什么业务,假设名字叫aaa())、代理类信息,有了这四样信息,可以让生成这样的一个暂时的新代理类class(继承客户类的接口,然后实现了“调用的方法”aaa()),然后再根据class信息实例化成一个新代理类对象(只能用Object存着),然后调用这个新代理类对象的aaa()方法,即可以达到通过代理来实现客户的业务执行。
第一种写法:
定义业务主题:
public interface Hair{ //这是业务主题接口,即代理要处理的是什么类型的客户和业务
void execute();
}
public class cutHair implements Hair{ //这是具体的客户类
@Override
public void execute(){
System.out.println("我是做剪头发业务");
}
}
public class hotHair implements Hair{ //这是具体的客户类
@Override
public void execute(){
System.out.println("我是做烫头发业务");
}
}
动态代理类如下:
public class ProxyHandler implements InvocationHandler {
//这用于存储客户对象的
private Object target;
//这个方法就是创建一个临时的新代理类对象(也叫动态代理类对象)
public Object newProxyInstance(Object target) {
this.target = target;
//第一个参数指客户类的类加载器。
//第二个参数客户类实现的接口。
//第三个参数表示代理类(注意不是动态代理类哦~)
Object result = Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(), this);
return result;
}
//当动态代理类对象调用方法时(执行业务时),就会触发这个方法的执行,即相当于代理开始执行业务
//第一个参数 代理
//第二个参数 调用的方法(执行的业务)
//第三个参数 方法的参数
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//前 这里可以是其他的执行代码
Object ret = null;
try {
//调用目标方法
ret = method.invoke(target, args);
} catch (Exception e) {
log.error("调用{}.{}发生异常", target.getClass().getName(), method.getName(), e);
throw e;
}
//后 这里可以是其他的执行代码
return ret;
}
}
测试动态代理:
//第一句创建了一个代理类对象
ProxyHandler handler = new ProxyHandler();
//第二句创建了一个动态代理类对象,然后用接口引用
Hair proxy = (Hair) handler.newProxyInstance(new HotHair());
//第三句调用接口引用的方法,实则调用的是动态代理类对象的方法,执行的内容就是invoke()方法的内容
proxy.execute();
第二种写法:
动态代理类写成下面这样(生成动态代理对象的任务没有放在这里):
public class ProxyHandler implements InvocationHandler {
//这用于存储客户对象的
private Object target;
public void newProxyInstance(Object target) {
this.target = target;
}
//当动态代理类对象调用方法时(执行业务时),就会触发这个方法的执行,即相当于代理开始执行业务
//第一个参数 代理
//第二个参数 调用的方法(执行的业务)
//第三个参数 方法的参数
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//前 这里可以是其他的执行代码
Object ret = null;
try {
//调用目标方法
ret = method.invoke(target, args);
} catch (Exception e) {
log.error("调用{}.{}发生异常", target.getClass().getName(), method.getName(), e);
throw e;
}
//后 这里可以是其他的执行代码
return ret;
}
}
使用动态代理的代码却变了:
//第一句创建了一个代理类对象
ProxyHandler handler = new ProxyHandler();
//第二句创建了一个客户对象
HotHair hothair = new HotHair();
//第三句创建了一个动态代理类对象,然后用接口引用
Hair proxy = (Hair) Proxy.newProxyInstance(hothair.getClass().getClassLoader(),
hothair.getClass().getInterfaces(), handler);
//第四句调用接口引用的方法,实则调用的是动态代理类对象的方法,执行的内容就是invoke()方法的内容
proxy.execute();
4. 命令模式
命令模式主要有三个角色:命令、执行者、调用者。就是将命令封装为对象,由调用者触发命令的执行,由执行者运行具体的命令代码。同一个命令形式,不同的人有不同的执行方式,比如,有一桶水,命令是“将这通水送到某某小区”,将这条命令发给不同的人,就会有不同的执行方式,给小明的话,小明可能会用电动车运输,给小王的话,可能小汽车运输。
定义命令:
public interface Commond{ //定义命令接口
void cutHair();
void hotHair();
}
public class HairCommond implements Commond{ //命令
public Acceiver acceiver = null;
public HairCommond(Acceiver acceiver){
this.acceiver = acceiver;
}
@Override
public void cutHair(){ //命令执行的内容由执行者决定
this.acceiver.shortHair();
}
@Override
public void hotHair(){ //命令执行的内容由执行者决定
this.acceiver.curllyHair();
}
}
public class Acceiver{ //执行者
public void shortHair(){
//执行减短头发
}
public void curllyHair(){
//执行烫卷头发
}
}
public class Invoker{ //命令调用者
private Commond commond;
public void setCommond(Commond commond){
this.commond = commond;
}
public Commond getCommond(){
return commond;
}
public executeCutHairCommond(){
this.commond.cutHair();
}
public executeHotHairCommond(){
this.commond.hotHair();
}
}
测试一下:
public static void main(String[] args){
Acceiver acceiver = new Acceiver();
HairCommond hairCommond = new HairCommond(acceiver);
Invoker invoker = new Invoker();
invoker.setCommond(hairCommond);
invoker.executeCutHairCommond();
}
个人领悟:可以定一个执行者接口,实现多个执行者,这样,针对同一个命令,我们可以指定不同的执行者,即达到同一个命令可以有多种执行
5. 策略模式
同一事情可以有多种不同的执行策略,进而达到不同的执行结果。
public interface Strategy{ //定义策略接口
executeStrategy();
}
public class StrategyOne implements Strategy{ //策略一
@Override
executeStrategy(){
//执行策略一
}
}
public class StrategyTwo implements Strategy{ //策略二
@Override
executeStrategy(){
//执行策略二
}
}
public class Context{ //策略模式的核心类
public Strategy strategy;
public void execute(){ //执行策略
this.strategy.executeStrategy();
}
public changeStrategy(Strategy strategy){ //更换策略
this.strategy = strategy;
}
}
测试一下:
public static void main(String[] args){
Context context = new Context();
context.changeStrategy(new StrategyOne());
context.execute(); //执行的是策略一
}
6. 适配器模式
适配器就是将两种不能代码协作的(类或者接口)进行适配,目的是让这两种八竿子打不着的类或者接口能够产生联系,比如一个手机的充电器是2头的,但是插座是3孔的,怎么适配呢?
public interface ThreeHoleSocket{ //定义一个三孔插座接口
void threeHoleCharge(); //三孔充电
}
public class TwoHoleSocket{ //两孔插座
void twoHoleCharge(); //两孔充电
}
public Adapter implements ThreeHoleSocket{
TwoHoleSocket twoHoleSocket;
public Adapter(TwoHoleSocket twoHoleSocket){
this.twoHoleSocket = twoHoleSocket;
}
@Override
void threeHoleCharge(){ //用三孔插座的电
this.twoHoleSocket.twoHoleCharge(); //利用两孔冲给手机
}
}
怎么让这上面两个能够协作起来呢,让工人能够打包水果。