Java常用的设计模式

对着背影说爱祢 提交于 2020-11-11 10:24:40

1. 设计模式的特点与简介

1.1 什么是设计模式

设计模式(Design pattern):代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。

简而言之:就是书写代码的一些规律,例如经常开车的人都知道,开车要用一档起步

1.2 Java中的设计模式

java的设计模式大体上分为三大类:一共23种

  • 创建型模式(5种):工厂方法模式抽象工厂模式单例模式建造者模式原型模式
  • 结构型模式(7种):适配器模式装饰器模式代理模式外观模式桥接模式组合模式享元模式
  • 行为型模式(11种):策略模式模板方法模式观察者模式迭代子模式责任链模式命令模式备忘录模式状态模式访问者模式中介者模式解释器模式

设计模式遵循的原则有6个:

  1. 开闭原则(Open Close Principle):对扩展开放,对修改关闭。
  2. 里氏代换原则(Liskov Substitution Principle):只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。
  3. 依赖倒转原则(Dependence Inversion Principle):这个是开闭原则的基础,对接口编程,依赖于抽象而不依赖于具体。
  4. 接口隔离原则(Interface Segregation Principle):使用多个隔离的接口来降低耦合度。
  5. 迪米特法则(最少知道原则)(Demeter Principle):一个实体应当尽量少的与其他实体之间发生相互作用,使得系统功能模块相对独立。
  6. 合成复用原则(Composite Reuse Principle)原则是尽量使用合成/聚合的方式,而不是使用继承。继承实际上破坏了类的封装性,超类的方法可能会被子类修改。

2. 工厂模式

2.1 简单工厂模式

定义一个用于创建对象的接口,让子类决定实例化哪一个类。Factory Method使一个类的实例化延迟到其子类。

工厂方法即Factory Method,是一种对象创建型模式。

工厂方法的目的是使得创建对象和使用对象是分离的,并且客户端总是引用抽象工厂和抽象产品:

┌─────────────┐      ┌─────────────┐
│   Product   │      │   Factory   │
└─────────────┘      └─────────────┘
       ▲                    ▲
       │                    │
┌─────────────┐      ┌─────────────┐
│ ProductImpl │<─ ─ ─│ FactoryImpl │
└─────────────┘      └─────────────┘

举例代码:

车的接口

package com.rj.bd.lx.lx01;

public interface ICar {
   
   
	//制造不同零件方法
	public void make();
}

车接口的实现类

package com.rj.bd.lx.lx01;
/**
 * @desc 发动机类
 * @author 86186
 *
 */
public class Engine implements ICar{
   
   

	@Override
	public void make() {
   
   
		System.out.println("发动机制作完毕!!");
	}

}

package com.rj.bd.lx.lx01;
/**
 * @desc 底盘类
 * @author 86186
 *
 */
public class Chassis implements ICar{
   
   
	@Override
	public void make() {
   
   
		System.out.println("底盘制作完毕!!");
	}
}

package com.rj.bd.lx.lx01;
/**
 * @desc 车身类
 * @author 86186
 *
 */
public class Body implements ICar{
   
   
	@Override
	public void make() {
   
   
		System.out.println("车身制作完毕!!");
	}
}

简单的工厂类

package com.rj.bd.lx.lx01;

public class SimpleCarFactory {
   
   
	public static final int TYPE_FDJ = 1;
	public static final int TYPE_DP = 2;
	public static final int TYPE_CS = 3;
	public static ICar createNoodles(int type) {
   
   
		switch(type) {
   
   
		case TYPE_FDJ:
    		return new Engine();
    	case TYPE_DP:
    		return new Chassis();
    	case TYPE_CS:
    		return new Body();
    	default:
    		return new Body();//默认底盘类
		}
    	
   }    
	
}

测试类

package com.rj.bd.lx.lx01;

public class Test {
   
   

	public static void main(String[] args) {
   
   
		ICar car01 = SimpleCarFactory.createNoodles(SimpleCarFactory.TYPE_FDJ);
		car01.make();
		ICar car02 = SimpleCarFactory.createNoodles(SimpleCarFactory.TYPE_DP);
		car02.make();
		ICar car03 = SimpleCarFactory.createNoodles(SimpleCarFactory.TYPE_CS);
		car03.make();
		System.out.println("全部制造完毕,起飞~");
	}
}

测试控制台

发动机制作完毕!!
底盘制作完毕!!
车身制作完毕!!
全部制造完毕,起飞~

2.2 小结

  1. 工厂方法是指定义工厂接口和产品接口,但如何创建实际工厂和实际产品被推迟到子类实现,从而使调用方只和抽象工厂与抽象产品打交道。
  2. 实际更常用的是更简单的静态工厂方法,它允许工厂内部对创建产品进行优化。
  3. 调用方尽量持有接口或抽象类,避免持有具体类型的子类,以便工厂方法能随时切换不同的子类返回,却不影响调用方代码。

3. 代理模式

为其他对象提供一种代理以控制对这个对象的访问。

暂不讲述静态代理模式

3.1 动态代理

相比静态代理,动态代理具有更强的灵活性,因为它不用在我们设计实现的时候就指定某一个代理类来代理哪一个被代理对象,我们可以把这种指定延迟到程序运行时由JVM来实现

使用Java动态代理机制的好处

  1. 减少编程的工作量:假如需要实现多种代理处理逻辑,只要写多个代理处理器就可以了,无需每种方式都写一个代理类。
  2. 功能的扩展:系统扩展性和维护性增强,程序修改起来也方便多了(一般只要改代理处理器类就行了)。

动态代理的使用场景

  1. 在后期的商业框架中很多都有用到的,例如Spring,Struts2

3.2 JDK动态代理

JDK动态代理: JDK动态代理所用到的代理类在程序调用到代理类对象时才由JVM真正创建,JVM根据传进来的 业务实现类对象 以及 方法名 ,动态地创建了一个代理类的class文件并被字节码引擎执行,然后通过该代理类对象进行方法调用。我们需要做的,只需指定代理类的预处理、调用后操作即可

步骤:

  1. 首先,定义业务逻辑接口
  2. 然后,实现业务逻辑接口创建业务实现类
  3. 最后,实现 调用管理接口InvocationHandler 创建动态代理(工具)类
  4. 在使用时,首先创建一个业务实现类对象和一个代理类对象,然后定义接口引用(这里使用向上转型)并用代理对象.bind(业务实现类对象)的返回值进行赋值。最后通过接口引用调用业务方法即可。(接口引用真正指向的是一个绑定了业务类的代理类对象,所以通过接口方法名调用的是被代理的方法们)

代码举例

接口

package com.rj.bd.lx.lx03;
/**
 * @desc	租房的接口
 * @author 86186
 *
 */
public interface IHouse {
   
   
	public void renting();
}

租户类

package com.rj.bd.lx.lx03;

/**
 * @desc 租户类
 * @author 86186
 *
 */
public class Tenant implements IHouse{
   
   
	public String rentName;// 租户名
	public int rent;// 租金

	public String getRentName() {
   
   
		return rentName;
	}

	public void setRentName(String rentName) {
   
   
		this.rentName = rentName;
	}

	public int getRent() {
   
   
		return rent;
	}

	public void setRent(int rent) {
   
   
		this.rent = rent;
	}

	@Override
	public void renting() {
   
   
		if (rent < 1000) {
   
   
			System.out.println(rentName + "你这" +  rent + "块钱租不到房子呀,最少要1000");
			return;
		}
		System.out.println(rentName + "租房花了" + rent);
	}

}

动态代理工具类

package com.rj.bd.lx.lx03;
/**
 * @desc 动态代理管理类
 * @author 86186
 */
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class DymaicProxy implements InvocationHandler {
   
   
	public Object object;// 需要代理的对象

	public DymaicProxy(Object object) {
   
   
		this.object = object;// 代理租客
	}

	@Override
	public Object invoke(Object arg0, Method arg1, Object[] arg2) throws Throwable {
   
   
		Object resultObject = arg1.invoke(this.object, arg2);
		return resultObject;
	}

}

测试JDK动态代理模式

package com.rj.bd.lx.lx03;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

/**
 * @desc 3.用代理模式模拟一个房屋租赁
 * @author 86186
 *
 */
public class Test {
   
   
	public static void main(String[] args) {
   
   
		Tenant tenant = new Tenant();
		tenant.setRentName("董梦宇");
		tenant.setRent(3000);

		InvocationHandler handler = new DymaicProxy(tenant);

		IHouse house = (IHouse) Proxy.newProxyInstance(handler.getClass().getClassLoader(),
				tenant.getClass().getInterfaces(), handler);
//		System.out.println(handler);
		house.renting();
		
		
		Tenant tenant01 = new Tenant();
		tenant01.setRentName("董大宇");
		tenant01.setRent(500);

		InvocationHandler handler01 = new DymaicProxy(tenant01);

		IHouse house01 = (IHouse) Proxy.newProxyInstance(handler01.getClass().getClassLoader(),
				tenant01.getClass().getInterfaces(), handler01);
//		System.out.println(handler01);
		house01.renting();
	}
}

测试控制台

董梦宇租房花了3000
董大宇你这500块钱租不到房子呀,最少要1000

3.3 小结

  1. 代理模式通过封装一个已有接口,并向调用方返回相同的接口类型,能让调用方在不改变任何代码的前提下增强某些功能(例如,鉴权、延迟加载、连接池复用等)。
  2. 使用Proxy模式要求调用方持有接口,作为Proxy的类也必须实现相同的接口类型。

4. 单例模式

保证一个类仅有一个实例,并提供一个访问它的全局访问点。

单例模式(Singleton)的目的是为了保证在一个进程中,某个类有且仅有一个实例。

4.1 懒汉式单例与饿汉式单例

  • 懒汉式:第一次调用的时候才初始化(线程不安全)
  • 饿汉式:类加载的时候就初始化实例

接下来用一段代码来表明懒汉式与饿汉式

懒汉式

package com.rj.bd.lx.lx02;
/**
 * @desc 懒汉模式
 * @author 86186
 *
 */
public class Insurance {
   
   
    private static Insurance insurance;
	// 私有无参构造器
    
	private Insurance() {
   
   
	}
	//初始化
    
	public static Insurance getInsurance() {
   
   
        if(insurance == null) {
   
   
            insurance new Insurance();
        }
		return insurance;
	}
}

饿汉式

package com.rj.bd.lx.lx02;
/**
 * @desc	饿汉模式
 * @author 86186
 *
 */
public class Insurance {
   
   
	private static Insurance insurance = new Insurance();
	// 私有无参构造器
    
	private Insurance() {
   
   
	}
	//初始化
    
	public static Insurance getInsurance() {
   
   
		return insurance;
	}
}

其中懒汉模式是线程不安全的,可以使用synchronized关键字进行同步,但是会浪费很多时间,所以可以在最外面的一层加一个判断,通过双重判断的方式(双重加锁机制),解决效率问题,减少锁的判断次数

如下:

package com.rj.bd.lx.lx02;

/**
 * @desc 懒汉模式
 * @author 86186
 *
 */
public class Insurance {
   
   
	private static Insurance insurance;
    
	// 私有无参构造器
    
	private Insurance() {
   
   
	}

	// 初始化
    
	public static Insurance getInsurance() {
   
   
		if (insurance == null) {
   
   
			synchronized (Insurance.class) {
   
   
				if (insurance == null) {
   
   
					insurance = new Insurance();
				}
			}

		}

		return insurance;
	}
}

代码举例

饿汉保单类

package com.rj.bd.lx.lx02;
/**
 * @desc	饿汉模式
 * @author 86186
 *
 */
public class Insurance {
   
   
	private static Insurance insurance = new Insurance();
	// 私有无参构造器
    
	private Insurance() {
   
   
	}
	//初始化
    
	public static Insurance getInsurance() {
   
   
		return insurance;
	}
}

测试类

package com.rj.bd.lx.lx02;
/**
 * @desc	2.用单例模式模拟一下保险单号
 * @author 86186
 *
 */
public class Test {
   
   
	public static void main(String[] args) {
   
   
		// 模拟一个保险单,一个单只能有一个保险号,如果是其他的类使用,则会出现不同的保险单号,饿汉不会出现线程安全问题
		for (int i = 0; i < 100; i++) {
   
   
			new Thread(new Runnable() {
   
   
				@Override
				public void run() {
   
   
					Insurance onlyInsurance = Insurance.getInsurance();
					System.out.println(onlyInsurance.hashCode());
				}
			}).start();
		}
	}
}

测试控制台

655160934
655160934
....
655160934

4.2 小结

  1. Singleton模式是为了保证一个程序的运行期间,某个类有且只有一个全局唯一实例;
  2. Singleton模式既可以严格实现,也可以以约定的方式把普通类视作单例。

5. 策略模式

定义一系列的算法,把它们一个个封装起来,并且使它们可相互替换。本模式使得算法可独立于使用它的客户而变化。

5.1 策略模式

策略模式:Strategy,是指,定义一组算法,并把其封装到一个对象中。然后在运行时,可以灵活的使用其中的一个算法。

话不多说直接上差异代码

不用策略模式:

package com.rj.bd.lx.lx04;

import java.security.Identity;
import java.util.Scanner;

/**
 * @desc 模拟一个商场的商品打折促销的小案例: 1)普通用户原价,但是要是满100元减去20元 2)普通会员是,打8折 0)VIP会员打7折
 * @author 86186
 *
 */
public class Test {
   
   
	public static void main(String[] args) {
   
   
		System.out.println("请输入消费金额.....");
		Scanner cin = new Scanner(System.in);
		Double d = cin.nextDouble();

		System.out.println("请输入您的身份,普通用户or普通会员orVIP会员:");
		String identity = cin.next();

		if (identity.equals("普通用户")) {
   
   
			if (d >= 100) {
   
   
				System.out.println("您为普通用户,消费为金额" + d + "满减后为" + (d - 20));
				return;
			}
			System.out.println("您为普通用户,消费金额为" + d);
		} else if (identity.equals("普通会员")) {
   
   
			System.out.println("您为普通会员,消费金额为" + d + ",打8折后价格为" + (d * 0.8));
		} else if (identity.equals("VIP会员")) {
   
   
			System.out.println("您为VIP会员,消费金额为" + d + ",打7折后价格为" + (d * 0.7));
		} else {
   
   
			System.out.println("输入错误!");
		}
	}
}

控制台输出

请输入消费金额.....
150
请输入您的身份,普通用户or普通会员orVIP会员:
普通会员
您为普通会员,消费金额为150.0,打8折后价格为120.0

使用策略模式

策略接口

package com.rj.bd.lx.lx04;
/**
 * @desc 策略接口
 * @author 86186
 *
 */
public interface IStrategy {
   
   
	public void identity(double d);
}

普通用户类

package com.rj.bd.lx.lx04;
/**
 * @desc	普通用户
 * @author 86186
 *
 */
public class OrdinaryUsers implements IStrategy{
   
   

	@Override
	public void identity(double d) {
   
   
		if (d >= 100) {
   
   
			System.out.println("您为普通用户,消费为金额" + d + "满减后为" + (d - 20));
			return;
		}
		System.out.println("您为普通用户,消费金额为" + d);
	}
	
}

普通会员类

package com.rj.bd.lx.lx04;
/**
 * @desc	普通会员
 * @author 86186
 *
 */
public class GeneralMember implements IStrategy{
   
   

	@Override
	public void identity(double d) {
   
   
		System.out.println("您为普通会员,消费金额为" + d + ",打8折后价格为" + (d * 0.8));
	}

}

VIP会员类

package com.rj.bd.lx.lx04;
/**
 * @desc VIP会员
 * @author 86186
 *
 */
public class VIPMembers implements IStrategy{
   
   

	@Override
	public void identity(double d) {
   
   
		System.out.println("您为VIP会员,消费金额为" + d + ",打7折后价格为" + (d * 0.7));
		
	}
	
}

策略内容类

package com.rj.bd.lx.lx04;
/**
 * @desc 策略内容类
 * @author 86186
 *
 */
public class StrategyContext {
   
   
	public IStrategy strategy;
	//策略内容的构造器

	public StrategyContext(IStrategy strategy) {
   
   
		super();
		this.strategy = strategy;
	}
	//设置金额
	public void consumptionAmount(double d) {
   
   
		strategy.identity(d);
	}
}

测试类

package com.rj.bd.lx.lx04;

import java.util.Scanner;

/**
 * @desc	4.用策略模式模拟一个商场的商品打折促销的小案例:
   			1)普通用户原价,但是要是满100元减去20元
   			2)普通会员是,打8折
   			0)VIP会员打7折
 * @author 86186
 *
 */
public class Test {
   
   
	public static void main(String[] args) {
   
   
		System.out.println("请输入消费金额.....");
        Scanner  cin=new Scanner(System.in);
        Double d = cin.nextDouble();
        
        System.out.print("普通用户:");
        StrategyContext strategyContext01 = new StrategyContext(new OrdinaryUsers());
        strategyContext01.consumptionAmount(d);
        
        System.out.print("会员用户:");
        StrategyContext strategyContext02 = new StrategyContext(new GeneralMember());
        strategyContext02.consumptionAmount(d);
        
        System.out.print("VIP会员:");
        StrategyContext strategyContext03 = new StrategyContext(new VIPMembers());
        strategyContext03.consumptionAmount(d);
	}
}

测试控制台

请输入消费金额.....
150
普通用户:您为普通用户,消费为金额150.0满减后为130.0
会员用户:您为普通会员,消费金额为150.0,打8折后价格为120.0
VIP会员:您为VIP会员,消费金额为150.0,打7折后价格为105.0

5.2小结

  1. 策略模式是为了允许调用方选择一个算法,从而通过不同策略实现不同的计算结果。
  2. 通过扩展策略,不必修改主逻辑,即可获得新策略的结果。
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!