设计模式简介-第一节

分类

总体来说设计模式分为三大类:

  • 创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。

  • 结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。

  • 行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。

一、创建型模式

1、单例模式

1.1、定义:

一个类只能有一个对象实例,并且自行实例化并向整个系统提供这个实例,有以下特点:

  • 单例类只能有一个实例。
  • 单例类必须自己创建自己的唯一实例。
  • 单例类必须给所有其他对象提供这一实例。

分为三种

  • 饿汉模式
  • 懒汉模式
  • 双重检查加锁

1.2、饿汉模式

在装载类的时候就创建了对象,这是线程安全的,但是占用空间比较多

public class TestSingleton {

    private static TestSingleton testSingleton = new TestSingleton();

    private TestSingleton() {
    }

    public static TestSingleton getInstance() {
        return testSingleton;
    }
}

1.3、懒汉模式

访问的时候再去创建对象,典型的时间换空间,每次访问都进行判断,会降低访问速度,但是如果访问量比较低就会节约空间。而且非线程安全

public class TestSingleton {

    private static TestSingleton testSingleton = null;

    private TestSingleton() {
    }

    public static TestSingleton getInstance() {
        if (testSingleton == null) {
            return new TestSingleton();
        } else {
            return testSingleton;
        }
    }
}

1.4、双重检查加锁

通过双重检查加锁,就可以既实现线程安全,又能够使性能不受很大的影响

并不是每次进入getInstance方法都需要同步,而是先不同步,进入方法后,先检查实例是否存在,如果不存在才进行下面的同步块,这是第一重检查,进入同步块过后,再次检查实例是否存在,如果不存在,就在同步的情况下创建一个实例,这是第二重检查。这样一来,就只需要同步一次了,从而减少了多次在同步情况下进行判断所浪费的时间。

“双重检查加锁”机制的实现会使用关键字volatile,它的意思是:被volatile修饰的变量的值,将不会被本地线程缓存,所有对该变量的读写都是直接操作共享内存,从而确保多个线程能正确的处理该变量。

public class Singleton {

    private volatile static Singleton instance = null;

    private Singleton(){}

    public static Singleton getInstance(){
        //先检查实例是否存在,如果不存在才进入下面的同步块
        if(instance == null){
            //同步块,线程安全的创建实例
            synchronized (Singleton.class) {
                //再次检查实例是否存在,如果不存在才真正的创建实例
                if(instance == null){
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

2、工厂模式

工厂模式分为简单工厂工厂方法抽象工厂

2.1、简单工厂

一个工厂类,不同条件下实例化不同的对象

public class SimplePizzaFactory {
       public Pizza CreatePizza(String ordertype) {
              Pizza pizza = null;
              if (ordertype.equals("cheese")) {  //不同的条件判断
                     pizza = new CheesePizza();
              } else if (ordertype.equals("greek")) {
                     pizza = new GreekPizza();
              } else if (ordertype.equals("pepper")) {
                     pizza = new PepperPizza();
              }
              return pizza;
       }
}

简单工厂带来的问题就是耦合性太强,修改条件就得修改工厂类,所以就有了工厂方法

2.2、工厂方法

定义一个抽象类,将实例化对象的任务交给抽象类的子类来完成,这样如果需要增加条件那么增加子类就好了,达到了低耦合的目标

//抽象类
public interface OrderPizza {
       Pizza CreatePizza(String ordertype) ;
}
//子类
public class LDOrderPizza extends OrderPizza {
       Pizza createPizza(String ordertype) {
              Pizza pizza = null;
              if (ordertype.equals("cheese")) {
                     pizza = new LDCheesePizza();
              } else if (ordertype.equals("pepper")) {
                     pizza = new LDPepperPizza();
              }
              return pizza;
       }
}
public class NYOrderPizza extends OrderPizza {
 
	Pizza createPizza(String ordertype) {
		Pizza pizza = null;
 
		if (ordertype.equals("cheese")) {
			pizza = new NYCheesePizza();
		} else if (ordertype.equals("pepper")) {
			pizza = new NYPepperPizza();
		}
		return pizza;
 
	}
 
}

工厂方法也有局限性,就是如果有多个系列产品,那就需要把工厂方法所有的类复制一份,修改成别的产品,为了解决这一问题,抽象工厂出现了

2.3、抽象工厂

披萨的抽象方法改成接口,同样也有例如蛋糕这样的接口,他们分别都有实现的工厂类。然后抽象工厂类里填写创建两个产品的接口方法,抽象工厂的实现子类去真正创建所有的产品

//披萨工厂接口
public interface PizzaFactory {
    Pizza CreatePizza(String orderType);
}
//水果披萨工厂
public class FruitPizzaFactory implements PizzaFactory {

    public Pizza CreatePizza(String orderType) {
        if (orderType.equals("Apple")) {
            return new Pizza(30,"Apple");
        }
        else if (orderType.equals("Banana")) {
            return new Pizza(30,"Banana");
        } else {
            return null;
        }
    }

}
#抽象工厂
public interface Order {
    Pizza CreatePizza(String orderType);
    //还可以写别的产品
}
//实现抽象工厂类
public class Supermarket implements Order{
    public Pizza CreatePizza(String orderType) {
        return new FruitPizzaFactory().CreatePizza(orderType);
    }
}
#主方法调用
public class Test {
    public static void main(String[] args) {
        Order order = new Supermarket();
        order.CreatePizza("banana");
    }
}

三种方法的比较:

  • 简单工厂 : 用来生产同一等级结构中的任意产品。(不支持拓展增加产品)

  • 工厂方法 :用来生产同一等级结构中的固定产品。(支持拓展增加产品)

  • 抽象工厂 :用来生产不同产品族的全部产品。(支持拓展增加产品;支持增加产品族)

二、结构型模式

1、代理模式

代理模式分为静态代理和动态代理

1.1、静态代理

角色分析:

  • 抽象角色:一般用接口或者抽象类
  • 真实角色(房东):被代理的角色
  • 代理角色(中介):代理真实角色,代理真实角色后,我们一般会有一些附属操作
  • 客户(租客):访问代理角色的人
#抽象角色
public interface BuyHouse {
    void RentHosue();
}
#房东
public class BuyHouseImpl implements BuyHouse {
       @Override
       public void RentHosue() {
              System.out.println("我要租房");
       }
}
#中介
public class RentHouseProxy implements RentHouse {
       private RentHouse rentHouse ;
       public RentHouseProxy(final RentHouse RentHouse ) {
              this.RentHouse = RentHouse ;
       }
       @Override
       public void RentHouse () {
              System.out.println("看房");
              buyHouse.buyHosue();
              System.out.println("收中介费");
       }
}

好处是:

  • 使得真实角色的操作更加纯粹,不用管一些公共的事物
  • 公共事物交给了代理,实现了分工
  • 公共业务发生拓展的时候方便管理

缺点是:

  • 因为是静态的,所以一个真实角色对应一个代理,耦合性太强

1.2、动态代理

  • 动态代理和静态代理的角色是一样的,区别在于代理角色是动态生成的
  • 动态代理分为两大类,基于接口的动态代理、基于类的动态代理

基于接口—JDK动态代理
基于类:cglib
java字节码实现:javasist

这里介绍一下基于接口的动态代理,需要了解两个类:Proxy(代理类),InvocationHandler:调用处理程序

Proxy:

InvocationHandler:

例子:

//抽象角色:
package com.wcy.demo;

public interface Rent {
    void rent();
}
//房东
public class Host implements Rent{

    @Override
    public void rent() {
        System.out.println("房东要出租房子");
    }
}
//动态代理类
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class testProxy implements InvocationHandler {

    private Object object;

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

    //生成代理类
    public Object getProxy() {
        return Proxy.newProxyInstance(this.getClass().getClassLoader(),object.getClass().getInterfaces(),this);
        //classloader用于知道类在哪,object.getClass().getInterfaces()用于知道代理的类,最后一个知道动态代理给谁
    }

    //处理代理实例,并返回结果
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        seeHouse();
        System.out.println("执行了" + method.getName() + "方法");
        Object result = method.invoke(object,args);
        fare();
        return result;
    }

    //代理公共操作
    public void seeHouse() {
        System.out.println("中介带看房子");
    }

    public void fare() {
        System.out.println("收费");
    }
}
//测试代码
public class Client {
    public static void main(String[] args) {
        //生成真实角色
        Host host = new Host();
        Host2 host2 = new Host2();

        //代理角色
        testProxy tproxy = new testProxy();
        tproxy.setRent(host);
        Rent actual_proxy = (Rent) tproxy.getProxy();
        actual_proxy.rent();
        //代理第二位房东
        tproxy.setRent(host2);
        actual_proxy.rent();
    }
}

1.3 动态代理源码解析

动态代理关键在于获取代理类,如上所述,getProxy()获取的代理类的方法已经实现了代理,rent方法实际已经变成了invoke里的逻辑,那么是如何实现的呢?

public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        //动态代理类非空判断
        Objects.requireNonNull(h);
        //被代理类接口复制
        final Class<?>[] intfs = interfaces.clone();
        //安全检查
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
        }

        /*
         * **通过加载器和接口数组获取一个特殊的代理类 (重要)**
         */
        Class<?> cl = getProxyClass0(loader, intfs);

        /*
         * Invoke its constructor with the designated invocation handler.
         */
        try {
            if (sm != null) {
                checkNewProxyPermission(Reflection.getCallerClass(), cl);
            }
            //获取特殊代理类有InvocationHandler参数的构造器
            final Constructor<?> cons = cl.getConstructor(constructorParams);
            final InvocationHandler ih = h;
            //如果构造器权限不是public的,设置未public
            if (!Modifier.isPublic(cl.getModifiers())) {
                AccessController.doPrivileged(new PrivilegedAction<Void>() {
                    public Void run() {
                        cons.setAccessible(true);
                        return null;
                    }
                });
            }
            //通过反射,将代理类作为参数,创建被代理对象,此时返回的被代理对象就实现了动态代理
            return cons.newInstance(new Object[]{h});
        } catch (IllegalAccessException|InstantiationException e) {
            throw new InternalError(e.toString(), e);
        } catch (InvocationTargetException e) {
            Throwable t = e.getCause();
            if (t instanceof RuntimeException) {
                throw (RuntimeException) t;
            } else {
                throw new InternalError(t.toString(), t);
            }
        } catch (NoSuchMethodException e) {
            throw new InternalError(e.toString(), e);
        }
    }

可以看到,除了一些必要的安全检查和权限check之外,主要步骤是

  • 生产一个能够将 代理类 传入 被代理类 的 特殊代理类
  • 通过反射,生产被代理类实例

生产这个特殊代理类的源码有些复杂,底层调用的是ProxyClassFactory.apply方法,核心代码如下

/*
* Generate the specified proxy class.
*/
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
   proxyName, interfaces, accessFlags);
try {
   //生产对应的被代理类对象
   return defineClass0(loader, proxyName,
             proxyClassFile, 0, proxyClassFile.length);
} catch (ClassFormatError e) {
  throw new IllegalArgumentException(e.toString());
}

2、适配器模式

适配器模式将某个类的接口转换成客户端期望的另一个接口表示,目的是消除由于接口不匹配所造成的类的兼容性问题。

主要分为三类:类的适配器模式、对象的适配器模式、接口的适配器模式。

#类适配器
/** 继承USBImpl,有了它的方法,然后本身又是VGA类,既可以把USBImpl适配到VGA上了
*/
public class AdapterUSB2VGA extends USBImpl implements VGA { 
       @Override
       public void projection() {
              super.showPPT();
       }
}
#对象适配器
/** 继承USBImpl,有了它的方法,然后本身实现VGA类,既可以把USBImpl适配到VGA上了
*/
public class AdapterUSB2VGA extends USBImpl implements VGA { 
       @Override
       public void projection() {
              super.showPPT();
       }
}
## 对象适配器
/** 通过依赖USBImpl对象,然后本身又是VGA类,既可以把USBImpl适配到VGA上了
*/
public class AdapterUSB2VGA implements VGA {
       USB u = new USBImpl();
       @Override
       public void projection() {
              u.showPPT();
       }
}
# 接口依赖器(当不想实现适配后的对象所有的方法时使用)
/** 可以先创建一个抽象类,依赖USBImpl对象,然后实现这个抽象类的时候选中对应的方法就好了
*/
public abstract class AdapterUSB2VGA implements VGA {
       USB u = new USBImpl();
       @Override
       public void projection() {
              u.showPPT();
       }
       @Override
       public void b() {
       };
       @Override
       public void c() {
       };
}

public class AdapterUSB2VGAImpl extends AdapterUSB2VGA {
       public void projection() {
              super.projection();
       }
}

三、行为型模式

1、策略型模式

策略模式定义了一系列算法,并将每个算法封装起来,使他们可以相互替换,且算法的变化不会影响到使用算法的客户。

其实就是一个函数如果有太多if...else耦合性就太强,那么通过不同的类封装不同的函数,就可以避免,这就是策略模式

//策略接口
public interface Strategy {
	public int calc(int num1,int num2);
}
//具体策略类
public class AddStrategy implements Strategy {

	@Override
	public int calc(int num1, int num2) {
		// TODO Auto-generated method stub
		return num1 + num2;
	}
}

public class SubstractStrategy implements Strategy {

	@Override
	public int calc(int num1, int num2) {
		// TODO Auto-generated method stub
		return num1 - num2;
	}
 
}
//环境
public class Environment {

	private Strategy strategy;

	public Environment(Strategy strategy) {
		this.strategy = strategy;
	}
 
	public int calculate(int a, int b) {
		return strategy.calc(a, b);
	}
}
//测试类
public class MainTest {
	public static void main(String[] args) {
		
		Environment environment=new Environment(new AddStrategy());
		int result=environment.calculate(20, 5);
		System.out.println(result);
		
		Environment environment1=new Environment(new SubstractStrategy());
		int result1=environment1.calculate(20, 5);
		System.out.println(result1);
	}
 
}
posted @ 2022-07-12 20:38  吴承勇  阅读(33)  评论(0编辑  收藏  举报