今天说说几种常见的设计模式,写个博客,记录一下。
首先,啥叫设计模式呢? 设计模式是一套被反复使用、多数人知晓的、经过分类编码、代码设计经验的总结。举个例子,把编码比喻成打仗,那么设计模式就是孙子兵法三十六计。设计模式的目的是为了可重用代码、让代码更容易被他人理解、保证代码的可靠性。
单例模式
作用是保证整个应用程序代码中某个实例有且仅有一个。
分为饿汉模式和懒汉模式
区别:饿汉模式的特点是加载类时比较慢,但运行时获取对象的速度比较快,线程安全。
懒汉模式的特点是加载类时比较快,但运行时获取对象的速度比较慢,线程不安全。
创建单例模式的步骤:
1,将构造方法私有化,不允许外边直接创建对象
2,声明类的唯一实例,用private static修饰
3,提供一个用于获取实例的方法,使用public static修饰
常用的单例模式写法:
饿汉式
public class Singleton {
/**
* 饿汉式单例
*/
private final static Singleton instance = new Singleton();
private Singleton(){
}
public static Singleton getInstance(){
return instance;
}
}
这是一种饿汉式最简单的写法,在装载类的时候就完成了实例化,可以保证线程的安全,但是如果不使用,就会造成内存浪费。
懒汉式
public class Singleton {
/**
* 懒汉单线程单单例
*/
private static Singleton instance;
private Singleton(){
}
public static Singleton getInstance(){
if(instance ==null){
instance = new Singleton();
}
return instance;
}
}
这是懒汉式的一种简单写法,但是这种写法线程不安全,如果有多个线程同时调用,可能会出现多个实例,所以这种方法只适合单线程的单例。
鉴于以上线程不安全的问题,可以使用synchronized关键字在方法上进行同步,但是每个线程想获取到类的实例都需要执行getInstance方法进行同步,但是这个方法的目的是只实例化一次,后面如果有线程想要获得实例,直接return就可以了,所以这种同步的方法不好,不推荐使用,具体改进可以使用双重检查的单例模式,如下:
public class Singleton {
/**
* 懒汉双重检查模式 适合多线程 线程安全
*/
private static Singleton instance;
private Singleton(){
}
public static Singleton getInstance(){
if(instance ==null){
synchronized(Singleton.class){
if (instance ==null){
instance = new Singleton();
}
}
}
return instance;
}
}
双重检查模式保证了线程安全,也保证了只实例化一次。
适配器模式
适配器模式将一个类的接口,转换成客户期望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类可以在一起工作。
组合的方式,采用组合方式的适配器称为对象适配器,特点是把被适配者作为一个对象组合到适配器类中,以修改目标接口包装被适配者。
继承的方式,采用继承方式的称为类适配器,特点是通过多重继承不兼容接口,实现对目标接口的匹配,单一的为某个类而实现适配。
适配器模式的作用:
1,透明。通过适配器,客户端可以调用同一接口,因而对客户端来说是透明的。这样做更简单、更直接、更紧凑。
2,重用。复用了现存的类,解决了现存类和复用环境要求不一致的问题。
3,低耦合。将目标类和适配者类解耦,通过引入一个适配器类重用现有的适配者类,而无需修改原有代码(遵循开闭原则)
对象适配器demo,手机充电需要将220V电流转化为5V:
被包装类,即source类:
public class AC220 {
public int output220V(){
int output = 220;
return output;
}
}
包装类(目标类):
/**
* 此是一个接口,定义好抽象输出方法
*/
public interface DC5 {
int output5V();
}
适配器类:
/**
* 适配器类需要继承输出接口,传入源类对象,重写输出方法
*/
public class PowerAdapter implements DC5{
private AC220 mAC220;
public PowerAdapter(AC220 ac220){
this.mAC220 = ac220;
}
@Override
public int output5V() {
int output = 0;
if (mAC220 != null) {
output = mAC220.output220V() / 44;
}
return output;
}
}
主函数:
public static void main(String[] args) {
PowerAdapter adapter = new PowerAdapter(new AC220());
}
策略模式
策略模式将可变的部门从程序中抽象分离出算法接口(通用部分),在该接口下分别封装一系列算法实现并使他们可以互相替换,从而导致客户端程序独立于算法的改变。
策略模式的实现:
1,通过分离变化,对策略对象定义一个公共接口。
2,编写策略类,该类实现了上面的公共接口。(多个功能具体实现)
3,在使用策略对象的类中保存一个对策略对象的引用。
4,在使用策略对象的类中,实现对策略对象的setter和getter方法或者使用构造方法完成赋值。
demo:
定义一个接口(就是抽象策略,比如网银支付,不用管用什么银行支付,只是知道支付这一层就可以),定义一个方法对两个整数进行运算
public interface Strategy {
public abstract int calculate(int a,int b);
}
定义具体的算法类,实现两个整数的加减乘除运算:
public class AddStrategy implements Strategy{
@Override
public int calculate(int a, int b) {
return a+b;
}
}
public class SubstractStrategy implements Strategy{
@Override
public int calculate(int a, int b) {
return a-b;
}
}
public class MultiplyStrategy implements Strategy {
@Override
public int calculate(int a, int b) {
return a*b;
}
}
public class DivisionStrategy implements Strategy{
@Override
public int calculate(int a, int b) {
if(b!=0){
return a/b;
}
else {
throw new RuntimeException("除数不能为零");
}
}
}
定义具体的环境角色,持有Strategy接口的引用,并且有getter和setter方法可以完成策略更换,在环境角色中调用接口的方法完成动作。
public class Context {
private Strategy strategy;
public Context(Strategy strategy) {
super();
this.strategy = strategy;
}
public Strategy getStrategy() {
return strategy;
}
public void setStrategy(Strategy strategy) {
this.strategy = strategy;
}
public int calculate(int a,int b){
return strategy.calculate(a, b);
}
}
客户端在调用时,只需向环境角色设置相应的算法类,就可以得到相应的结果。
public class StrategyTest {
public static void main(String[] args) {
//加法
Context add=new Context(new AddStrategy());
System.out.println(add.calculate(10, 5));
//减法
Context sub=new Context(new SubstractStrategy());
System.out.println(sub.calculate(3, 2));
//乘法
Context mul=new Context(new MultiplyStrategy());
System.out.println(mul.calculate(6, 8));
//除法
Context div=new Context(new DivisionStrategy());
System.out.println(div.calculate(90, 9));
}
}
策略模式的优缺点:
优点:
1,使用了组合,使架构更加灵活。
2,富有弹性,可以较好的应对变化(开闭原则)。
3,更好的代码复用性(相对于继承)。
4,消除了大量的条件语句。(在多种情况下,通常switch case、if else,但是策略模式优化了这一点)。
缺点:
1,客户代码需要了解每个策略实现的细节,需要知道每一个实现类的具体实现。
2,增加了很多对象的数目。
策略模式的适用场景:
1,许多相关的类仅仅是行为的差异。
2,运行时选取不同的算法变体。
3,通过条件语句在多个分支中选取一种。
代理模式
定于是为其他对象提供一种代理以控制对这个对象的访问。代理对象起到中介作用,可去掉功能服务或增加额外的服务。在不修改源代码的基础上,增加删除功能。
静态代理:代理和被代理对象在代理之前是确定的,它们都实现相同的接口或者继承相同的抽象类、父类。并且可以连续代理多个。
demo:
接口Moveable
public interface Moveable {
void run();
}
目标对象run:
public class Run implements Moveable{
@Override
public void run() {
System.out.println("run run run");
}
}
代理对象car:
public class Car implements Moveable{
private Moveable moveable;
public Car(Moveable moveable){
this.moveable=moveable;
}
@Override
public void run() {
System.out.println("car run");
}
}
测试类
public class Test {
public static void main(String[] args) {
Run run = new Run();
Car car = new Car(run);
car.run();
}
}
静态代理总结:
优点:可以做到在不修改目标对象功能的前提下,对目标功能扩展。
缺点:代理对象需要与目标对象实现一样的接口,所以会出现很多代理类,一旦原始接口增加方法,目标对象和代理对象都要维护。
动态代理
动态代理实现步骤:
1,创建一个实现接口InvocationHandler的类,它必须实现invoke方法。
2,创建被代理的类以及接口。
3,调用Proxy的静态方法,创建一个代理类newProxyInstance(ClassLoader loader,Class[] interfaces,InvocationHandler h)
4,通过代理调用方法
demo:
原始接口:
public interface Moveable {
void run();
}
被代理对象:
public class Run implements Moveable{
@Override
public void run() {
System.out.println("run run run");
}
}
动态代理类:
public class TimeHandler implements InvocationHandler {
private Object object;
public TimeHandler(Object object) {
this.object = object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("run starting");
method.invoke(object);
System.out.println("run ending");
return null;
}
}
main方法测试:
public class Test {
public static void main(String[] args) {
/**
* 动态代理模式
*/
//被代理类
Run run = new Run();
InvocationHandler h = new TimeHandler(run);
Class<?> cls = run.getClass();
/**
* loader 类加载器
* interfaces 实现接口
* h invocationHandler
*/
Moveable m = (Moveable) Proxy.newProxyInstance(cls.getClassLoader(),
cls.getInterfaces(),h);
m.run();
}
}
cglib代理模式
动态代理与cglib代理的区别:
1,jdk动态代理,只能代理实现了接口的类,没有实现接口的类不能实现jdk动态代理。
2,cglib动态代理针对类来实现代理的,对指定目标类产生一个子类,通过方法拦截技术拦截所有父类方法的调用。
3,cglib动态代理的类不能是static和final,因为那样不能继承。
demo:
首先导入cglib的jar包
目标对象:
public class UserDao {
public void save() {
System.out.println("----已经保存数据!----");
}
}
Cglib代理类:
/**
* Cglib子类代理工厂
* 对UserDao在内存中动态构建一个子类对象
*/
public class ProxyFactory implements MethodInterceptor{
//维护目标对象
private Object target;
public ProxyFactory(Object target) {
this.target = target;
}
//给目标对象创建一个代理对象
public Object getProxyInstance(){
//1.工具类
Enhancer en = new Enhancer();
//2.设置父类
en.setSuperclass(target.getClass());
//3.设置回调函数
en.setCallback(this);
//4.创建子类(代理对象)
return en.create();
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("开始");
//执行目标对象的方法
Object returnValue = method.invoke(target, args);
System.out.println("结束");
return returnValue;
}
}
主函数测试:
/**
* 测试类
*/
public class Test {
public static void main(String[] args) {
//目标对象
UserDao target = new UserDao();
//代理对象
UserDao proxy = (UserDao)new ProxyFactory(target).getProxyInstance();
//执行代理对象的方法
proxy.save();
}
}
工厂模式
工厂模式概念:实例化对象,用工厂方法代替new操作。工厂模式包括工厂方法模式和抽象工厂模式,抽象工厂模式是工厂方法模式的拓展。
工厂模式的意图:定义一个接口来创建对象,但是让子类来决定哪些类需要被实例化,工厂方法把实例化的工作推迟到子类中实现。
工厂模式适用场景:
1,有一组类似的对象需要创建。
2,在编码时不能预见需要创建哪种类的实例。
3,系统需要考虑拓展性,不应依赖于产品类实例如何被创建、组合和表达的细节。
工厂模式与java spring框架依赖注入结合的较多,具体表现为controller层、dao层、service层以及具体实现impl,层层调用,但是互不干扰,如需添加新的功能,只需要增加新的方法即可,这里就不举例了。
以上写了几种常见的设计模式,如有问题,请大家留言纠正。