设计模式

Posted yanhui007

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了设计模式相关的知识,希望对你有一定的参考价值。

设计原则
装饰模式
动态代理
命令模式
工厂模式
策略模式
委派模式

设计原则

开闭原则、里氏替换原则、依赖倒转原则、接口隔离原则、最少知道原则、单一职责原则、合成复用原则

开闭原则

对修改关闭,对扩展开发。

里氏替换原则

子类可以扩展父类的功能,但是不能改变父类原有的功能。比如子类可以覆盖父类的抽象方法(抽象方法在父类中没有实现),但是不能覆盖父类的非抽象方法(非抽象方法属于父类的原有功能,子类覆盖相当与改变父类的功能)

依赖倒转原则

模块之间依赖接口编程,不能依赖实现编程。

接口隔离原则和单一职责原则

接口隔离原则:细化接口,不要建立臃肿的接口。
单一职责原则:类的功能单一。
这两个原则咋一看挺像的,都是强调类/接口的设计要单一。接口隔离原则指的是接口设计方面。单一职责指的是业务方面。

最少知道原则

高内聚、低耦合

合成复用原则

尽量使用聚合、组合的方式,而不是使用继承。

返回顶部

装饰模式

给类增加功能可以通过几种方式?
1、直接修改类的代码。 违背开闭原则。
2、增加子类。 影响类的继承链。
3、通过装饰模式动态给类增加功能。
优点:扩展性更强,装饰类为可插拔
缺点:会产生多余的小对象(装饰类),相对继承,出现问题排查起来更困难。

技术图片

Component:最上层接口
ConcreteComponent:被装饰类
Decorator:装饰器接口
ConcreteDecorator:装饰器实现类

public interface Human {
    void eat();
}

public class Man implements Human{
    public void eat(){
        System.out.println("eat");
    }
}

public abstract class Decorator implements Human{
    private Human human;
    public Decorator(Human human){
        this.human = human;
    }

    public void eat(){
        human.eat();
    }

}

public class ConcreteDecorator extends Decorator{
    public ConcreteDecorator(Human human){
        super(human);
    }

    private void wash(){
        System.out.println("wash");
    }

    @Override
    public void eat() {
        this.wash();
        super.eat();
    }
}

public class Main {
    /***
     * 设计模式 -- 装饰模式
     * 人类都会吃饭,通过装饰模式,给吃饭增加一个洗手的功能
     * @param args
     */
    public static void main(String[] args) {
        Human man = new Man();
        Decorator d = new ConcreteDecorator(man);
        d.eat();
    }
}

返回顶部

动态代理

介绍三部分内容:静态代理、jdk动态代理、cglib

静态代理

被代理接口

public interface HelloInterface {
    void sayHello();
}

被代理类

public class Hello implements HelloInterface{
    @Override
    public void sayHello() {
        System.out.println("Hello zhanghao!");
    }
}

代理类

public class HelloProxy implements HelloInterface{
    private HelloInterface helloInterface = new Hello();
    @Override
    public void sayHello() {
        System.out.println("Before invoke sayHello" );
        helloInterface.sayHello();
        System.out.println("After invoke sayHello");
    }
}

在编译时就要为每个被代理类编写一个代理类,这样如果有多个被代理类的,就需要写多个代理类。接下来动态代理解决这个问题。

jdk动态代理

被代理接口

public interface UserService {
     public String execute() throws Throwable ;
}

被代理类

public class UserServiceImpl implements UserService{
    @Override
    public String execute() throws Throwable {
        System.out.println("step 2 执行方法啦!!");
        return "step 2 执行方法啦!!";
    }
}

自定义拦截器

public class MyInvocationHandler implements InvocationHandler {
    private UserService userService;
    public MyInvocationHandler(UserService userService) {
        this.userService = userService;
    }
   @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        before();   //事务开启
        method.invoke(userService, args);
        after();    //事务关闭
        return "成功了";
    }
    private void before() {
        System.out.println("事务开启!");
    }
    private void after() {
        System.out.println("事务关闭");
    }
}

运行

public class MyTest {
 
    public static void main(String[] args) throws Throwable {
        System.out.println("---------------JDK动态代理----------------");
        UserService userService = (UserService) Proxy.newProxyInstance(MyTest.class.getClassLoader(),//指定由哪个加载器来加载代理类
                new Class<?>[]{UserService.class},//指定被代理的接口,生成的代理类会实现该接口
                new MyInvocationHandler(new UserServiceImpl()));
 
        userService.execute();
    }
}

当有多个被代理类(这些被代理类都实现相同接口)时,只需要定义一个自定义拦截器,该拦截器持有被代理接口的引用,然后在运行阶段根据传入的具体是哪个被代理类生成代理类。

流程:
1、使用的时候首先创建被代理类,被代理类要实现接口
2、创建自己的MyInvocationHandler实现Jdk的InvocationHandler,在里边增加拦截逻辑,同时把被代理类传递进去
3、通过Proxy的newProxyInstance方法创建代理类,需要将被代理接口和MyInvocationHandler传递进去。
newProxyInstance里边是如何生成代理类的?
首先根据传入的被代理接口获取到class信息,然后获取到构造方法,然后拿着构造方法通过反射生成代理类(这个过程把自定义拦截器也传递进去了,这样代理类中就有了自定义拦截器的引用,可以调用自定义拦截器的方法)。
4、当调用时,实际调用的是代理类的方法,而代理类内部通过反射获取到被代理类要执行的方法,然后传递给MyInvocationHandler,MyInvocationHandler内部先执行拦截逻辑,然后通过反射执行被代理类的方法

原理
生成代理类方法

public static Object newProxyInstance(ClassLoader loader, Class<?>[]interfaces,InvocationHandler h) throws IllegalArgumentException { 
 
    if (h == null) { 
        throw new NullPointerException(); 
    Class cl = getProxyClass(loader, interfaces); 

    // 通过反射获取构造函数对象并生成代理类实例
    try { 
//获取构造方法的时候将
        Constructor cons = cl.getConstructor(constructorParams); 
        return (Object) cons.newInstance(new Object[] { h }); 
    } catch (NoSuchMethodException e) { throw new InternalError(e.toString()); 
    } catch (IllegalAccessException e) { throw new InternalError(e.toString()); 
    } catch (InstantiationException e) { throw new InternalError(e.toString()); 
    } catch (InvocationTargetException e) { throw new InternalError(e.toString()); 
    } 
}

生成的代理类经过反编译后代码


/**
*代理类也实现了Person接口,看起来和静态代理的方式也会一样的 
*同时代理类也继承了Proxy类
*/ 
public final class $Proxy0 extends Proxy implements Person{
  private static Method m4;
  private static Method m1;
  private static Method m0;
  private static Method m3;
  private static Method m2;
  
  public $Proxy0(InvocationHandler paramInvocationHandler)
    throws 
  {
    super(paramInvocationHandler);
  }
   //实现了Person接口的方法,这就是我们调用这个方法Proxy.newProxyInstance必须提供第二个参数的作用 
  public final void sayGoodBye(boolean paramBoolean, double paramDouble)
    throws 
  {
    try
    {
	// 我们看到通过调用代理类的方法时,最终方法都会委托给InvocationHandler实现类的invoke方法
	// m4为代理类通过反射获得的Method
      this.h.invoke(this, m4, new Object[] { Boolean.valueOf(paramBoolean), Double.valueOf(paramDouble) });
      return;
    }
    catch (Error|RuntimeException localError)
    {
    。
    。
    。
    。
    。
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }
  
  static
  {
    try
    {//代理类通过反射 获得的接口方法Method
      m4 = Class.forName("com.yujie.proxy.dynamic.Person").getMethod("sayGoodBye", new Class[] { Boolean.TYPE, Double.TYPE });
      m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
      m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);

      m3 = Class.forName("com.yujie.proxy.dynamic.Person").getMethod("sayHello", new Class[] { Class.forName("java.lang.String"), Integer.TYPE });
      m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
      return;
    }
    catch (NoSuchMethodException localNoSuchMethodException)
    {
      throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
    }
    catch (ClassNotFoundException localClassNotFoundException)
    {
      throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
    }
  }

cglib

如何使用

public class SampleClass {
    public void test(){
        System.out.println("hello world");
    }
 
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(SampleClass.class);//指定被代理类
        enhancer.setCallback(new MethodInterceptor() {//自定义拦截器
            @Override
            public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
                System.out.println("before method run...");
                Object result = proxy.invokeSuper(obj, args);此处与java proxy有区别
                System.out.println("after method run...");
                return result;
            }
        });
        SampleClass sample = (SampleClass) enhancer.create();//代理类,生成的代理类会继承被代理类,因为final类型的类无法被继承,所以final类服务通过cglib生成代理
        sample.test();
    }

}

cglib的实现与java Proxy的本质区别

java Proxy是通过反射不用解释。cglib底层是通过asm实现的。
asm是什么?
java代码从编译到执行的流程如图:
技术图片
asm做的就是动态生成class文件,放入装载器。

原理分析

被代理类每个方法会生成2个方法,例如被代理类有个方法g(),则在代理类中对应两个方法g()和CGLIB$g$0()。

流程:1、测试代码中的 sample.test();
2、直接调用到代理类的g()
3、因为代理类持有自定义拦截器的引用,所以可以直接调用自定义拦截器的intercept方法。
4、执行自定义拦截器的intecept逻辑。

注意自定义拦截器中:Object result = proxy.invokeSuper(obj, args);
此处与java proxy有区别,此处是调用的父类的方法(生成的代理类集成被代理类),而java proxy中是通过反射调用的被代理类的方法。为什么这么设计?
因为反射效率低,这里采用的是fastClass机制。
fastCLass机制大致意思就是为类中的每个方法加个索引以提高效率。

直接看生成的代理类

public class Target$$EnhancerByCGLIB$$788444a0 extends Target implements Factory
{
    private boolean CGLIB$BOUND;
    private static final ThreadLocal CGLIB$THREAD_CALLBACKS;
    private static final Callback[] CGLIB$STATIC_CALLBACKS;
    private MethodInterceptor CGLIB$CALLBACK_0;
    private static final Method CGLIB$g$0$Method;
    private static final MethodProxy CGLIB$g$0$Proxy;
    private static final Object[] CGLIB$emptyArgs;
    private static final Method CGLIB$f$1$Method;
    private static final MethodProxy CGLIB$f$1$Proxy;
    
    static void CGLIB$STATICHOOK1()
    {
      CGLIB$THREAD_CALLBACKS = new ThreadLocal();
      CGLIB$emptyArgs = new Object[0];
      Class localClass1 = Class.forName("net.sf.cglib.test.Target$$EnhancerByCGLIB$$788444a0");
      Class localClass2;
      Method[] tmp60_57 = ReflectUtils.findMethods(new String[] { "g", "()V", "f", "()V" }, (localClass2 = Class.forName("net.sf.cglib.test.Target")).getDeclaredMethods());
      CGLIB$g$0$Method = tmp60_57[0];
      CGLIB$g$0$Proxy = MethodProxy.create(localClass2, localClass1, "()V", "g", "CGLIB$g$0");
      CGLIB$f$1$Method = tmp60_57[1];
      CGLIB$f$1$Proxy = MethodProxy.create(localClass2, localClass1, "()V", "f", "CGLIB$f$1");
    }
    
    final void CGLIB$g$0()
    {
      super.g();
    }
    
    public final void g()
    {
      MethodInterceptor tmp4_1 = this.CGLIB$CALLBACK_0;
      if (tmp4_1 == null)
      {
          CGLIB$BIND_CALLBACKS(this);
          tmp4_1 = this.CGLIB$CALLBACK_0;
      }
      if (this.CGLIB$CALLBACK_0 != null) {
          tmp4_1.intercept(this, CGLIB$g$0$Method, CGLIB$emptyArgs, CGLIB$g$0$Proxy);
      }
      else{
          super.g();
      }
    }
}

返回顶部

命令模式

/***
 * 命令模式:
 * 命令模式将“请求”封装成对象,以便使用不同的请求、队列或者日志来参数化其他对象。
 * 将“行为请求者”与“行为实现者”解耦
 *
 * 命令模式涉及到的角色:
 * commond:命令接口
 * concreteCommond:命令实现类
 * reciever:真正负责执行命令的类
 * invoker:负责调用的类
 * client:负责产生命令的人
 *
 * 模拟餐厅点菜场景:
 * 顾客(client)来餐厅点菜,点了西红柿炒鸡蛋(concreteCommond)、
 * 宫保鸡丁(concreteCommond)、米饭(concreteCommond),
 * 服务员记录下来小单子,
 * 将小单子交给厨师(invoker)一个接一个的做菜(reviever)
 *
 */
public class Main {
    public static void main(String[] args) {
        List<Commond> commondList = new ArrayList<>();
        Commond c1 = new XhscjdCommond(new XhscjdReciever());
        Commond c2 = new GbjdCommond(new GbjdReciever());
        Commond c3 = new MfCommond(new MfReciever());
        commondList.add(c1);
        commondList.add(c2);
        commondList.add(c3);
        Invoker invoker = new Invoker(commondList);
        invoker.invoke();
    }
}

请求封装成命令,一个请求一个命令,命令持有reciever的引用,最终执行是reciever执行的

public interface Commond {
     void execute();
}

public class GbjdCommond implements Commond{

    private Reciever reciever;

    GbjdCommond (Reciever reciever){
        this.reciever = reciever;
    }
    @Override
    public void execute() {
        reciever.action();
    }
}

public class XhscjdCommond implements Commond{
    private Reciever reciever;

    XhscjdCommond (Reciever reciever){
        this.reciever = reciever;
    }
    @Override
    public void execute() {
        reciever.action();
    }
}

public class MfCommond implements Commond{
    private Reciever reciever;

    MfCommond (Reciever reciever){
        this.reciever = reciever;
    }
    @Override
    public void execute() {
        reciever.action();
    }
}
public abstract class Reciever {
    abstract void action();
}

public class XhscjdReciever extends Reciever{
    @Override
    public void action() {
        System.out.println("做西红柿炒鸡蛋");
    }
}

public class GbjdReciever extends Reciever{

    @Override
    public void action() {
        System.out.println("做宫保鸡丁");
    }
}

public class MfReciever extends Reciever{
    @Override
    public void action() {
        System.out.println("做米饭");
    }
}

命令有了,并且命令也持有reciever的引用, 但是由谁来发起对命令的执行调用呢?

public class Invoker{

    private List<Commond> commondList;

    Invoker(List<Commond> commondList){
        this.commondList = commondList;
    }
    
    public void invoke() {
        for(Commond c:commondList){
            c.execute();
        }
    }
}

返回顶部

工厂模式

简单工厂
工厂方法
抽象工厂方法

简单工厂

产品类

abstract  class Product {
    abstract void intro();
}

public class AProduct extends Product{
    @Override
    void intro() {
        System.out.println("可乐");
    }
}

public class BProduct extends Product{
    @Override
    void intro() {
        System.out.println("奶茶");
    }
}

public class CProduct extends Product{
    @Override
    void intro() {
        System.out.println("咖啡");
    }
}

工厂类

public class Factory {
    public static Product getProduct(String type) {
        switch (type) {
            case "A":
                return new AProduct();
            case "B":
                return new BProduct();
            case "C":
                return new CProduct();
            default:
                return null;
        }
    }
}

main方法

public class Test {
    public static void main(String[] args) {
        //创建具体的工厂
        Factory factory = new Factory();
        //根据传入的参数生产不同的产品实例
        //(按下不同的按钮,获取饮料)
        Product A = Factory.getProduct("A");
        A.intro();
        Product B = Factory.getProduct("B");
        B.intro();
        Product C = Factory.getProduct("C");
        C.intro();
    }
}

当增加产品的时候需要修改工厂类的代码,解决此问题接下来看工厂方法模式。

工厂方法模式

产品类同上

工厂类

abstract class Factory {
    abstract Product getProduct();
}

public class FactoryA extends Factory{
    @Override
    Product getProduct() {
        return new ProductA();
    }
}

public class FactoryB extends Factory{
    @Override
    Product getProduct() {
        return new ProductB();
    }
}

main方法

public class Test {
    public static void main(String[] args) {
        //创建具体的工厂
        FactoryA factoryA = new FactoryA();
        //生产相对应的产品
        factoryA.getProduct().intro();
        FactoryB factoryB = new FactoryB();
        factoryB.getProduct().intro();
    }
}

工厂方法对工厂类做了一层抽象,当需要增加新产品的时候不是改工厂类代码,而是增加删除工厂类。
工厂类的缺点是类太多(一个产品对应一个类),解决此问题继续看抽象工厂方法(一个工厂生产多个产品)。

抽象工厂

产品类(增加了一些产品,方便观看)

abstract class Product {
    abstract void intro();
}

abstract class ProductA extends Product{
    @Override
    abstract void intro();
}

abstract class ProductB extends Product{
    @Override
    abstract void intro();
}

public  class ProductAa extends ProductA{
    @Override
    void intro() {
        System.out.println("矿泉水");
    }
}

public class ProductBb extends ProductB{
    @Override
    void intro() {
        System.out.println("面包");
    }
}

工厂类

abstract class Factory {
    //生产饮料
    abstract Product getProductA();
    //生产零食
    abstract Product getProductB();
}

public class FactoryA extends Factory{
    @Override
    Product getProductA() {
        //生产矿泉水
        return new ProductAa();
    }
    @Override
    Product getProductB() {
        //生产面包
        return new ProductBb();
    }
}

main方法

public class Test {
    public static void main(String[] args) {
        //创建零食售卖机(具体工厂),
        FactoryA factoryA = new FactoryA();
        //获取矿泉水与面包(具体产品)
        factoryA.getProductA().intro();
        factoryA.getProductB().intro();
    }
}

返回顶部

策略模式

将一系列的算法封装起来,一个context持久这些算法类,context与客户端打交道,客户端告诉context我要选择哪种算法。

策略模式和工厂方法模式区别:都是客户端传递一个标识,调用不同的代码分支。
区别是策略模式执行的是一系列算法,工厂方法返回一个产品。
策略模式客户端必须要了解具体的算法,传递给context类。

实现一个计算机

首先定义一个策略的公共接口

public interface Strategy {
    int count(int a,int b);
}

然后加、减法分别实现该接口

public class AddStrategy implements Strategy{
    @Override
    public int count(int a, int b) {
        return a+b;
    }
}
public class DivStrategy implements Strategy{
    @Override
    public int count(int a, int b) {
        return a-b;
    }
}

然后定义一个策略持有类,该类负责与客户端打交道,选择具体的策略

public class StrategryContext {
    private  Strategy s ;
    public StrategryContext(Strategy a){
        s = a;
    }

    public int doCount(int a,int b ){
        return   s.count(a,b);
    }
}

main方法

public static void main(String[] args) {
        StrategryContext context = new StrategryContext(new AddStrategy());
        System.out.println(context.doCount(2,3));
    }

返回顶部

委派模式

委派模式和策略模式又很像。区别是:
委派模式context封装所有的算法,而客户端不需要了解算法,通过一个标识告诉context调用哪个算法就可以。

领导通过context把活委派给zhangsan和lisi,zhangsan是前端,lisi是后端。

public interface Employee {
    void work();
}
public class Lisi implements Employee {
    @Override
    public void work() {
        System.out.println("后端");
    }
}
public class Zhangsan implements Employee {
    @Override
    public void work() {
        System.out.println("前端");
    }
}
public class Context {
    private static Map<String,Employee> map = new HashMap<>();

    static{//需要把所有的员工都封装到context中,客户端调用的时候不需要知道员工,只要说想干嘛,这里来映射
        map.put("前端的活",new Zhangsan());
        map.put("后端的活",new Lisi());
    }

    public void doWork(String work){
        map.get(work).work();
    }
}
  public static void main(String[] args) {
        Context c = new Context();
        c.doWork("前端的活");
    }

返回顶部

以上是关于设计模式的主要内容,如果未能解决你的问题,请参考以下文章

十条实用的jQuery代码片段

尝试使用片段保存夜间模式状态

如何更改谷歌地图标记上方的标题和片段设计

炫酷 CSS 背景效果的 10 个代码片段

添加片段时的 FlyOut 菜单设计问题

高效Web开发的10个jQuery代码片段