常见设计模式

分类

分三大类:
创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。

六大原则

  1. 开闭原则(Open Close Principle)
    开闭原则就是说对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。所以一句话概括就是:为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类,后面的具体设计中我们会提到这点。
  2. 里氏代换原则(Liskov Substitution Principle)
    里氏代换原则(Liskov Substitution Principle LSP)面向对象设计的基本原则之一。 里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。 LSP是继承复用的基石,只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。里氏代换原则是对“开-闭”原则的补充。实现“开-闭”原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。—— From Baidu 百科
  3. 依赖倒转原则(Dependence Inversion Principle)
    这个是开闭原则的基础,具体内容:真对接口编程,依赖于抽象而不依赖于具体。
  4. 接口隔离原则(Interface Segregation Principle)
    这个原则的意思是:使用多个隔离的接口,比使用单个接口要好。还是一个降低类之间的耦合度的意思,从这儿我们看出,其实设计模式就是一个软件的设计思想,从大型软件架构出发,为了升级和维护方便。所以上文中多次出现:降低依赖,降低耦合。
  5. 迪米特法则(最少知道原则)(Demeter Principle)
    为什么叫最少知道原则,就是说:一个实体应当尽量少的与其他实体之间发生相互作用,使得系统功能模块相对独立。
  6. 合成复用原则(Composite Reuse Principle)
    原则是尽量使用合成/聚合的方式,而不是使用继承。

单例模式

单例保证一个对象JVM中只能有一个实例,常见单例 懒汉式、饿汉式
什么是懒汉式,就是需要的才会去实例化,线程不安全。
什么是饿汉式,就是当class文件被加载的时候,初始化,天生线程安全

懒汉式

public class Singleton {
    // 当需要的才会被实例化
    private static Singleton singleton;
    //构造函数私有,该类不能被外部类使用new方式实例化
    private Singleton() {

    }

    public static Singleton getInstance() {
        //synchronized加锁同步会降低效率,这里先判断是否为空
        //不为空则不需要加锁,提高程序效率
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

饿汉式

public class Singleton1 {
    //当class 文件被加载初始化
    private static Singleton1 singleton = new Singleton1();

    private Singleton1() {
    }

    public static Singleton1 getSingleton() {
        return singleton;
    }
}

单例模式优点

  • 1 在内存中只有一个对象,节省内存空间。
  • 2 避免频繁的创建销毁对象,可以提高性能。
  • 3 避免对共享资源的多重占用。
  • 4 可以全局访问。

适用场景

  • 1 需要频繁实例化然后销毁的对象。
  • 2 创建对象时耗时过多或者耗资源过多,但又经常用到的对象。
  • 3 有状态的工具类对象。
  • 4 频繁访问数据库或文件的对象。
  • 5 以及其他我没用过的所有要求只有一个对象的场景。

工厂模式

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

类图:


  1. 很多工厂都有一些相同的行为,比如汽车工厂。我们需要抽象这些相同的行为成接口,每个工厂都实现这个接口。
public interface IFactory {
 
    public void createProduct();
}
  1. 生产相同的产品每个工厂所使用的方法可能不同,所以具体如何生产产品由具体工厂实现。
public class Factory implements IFactory {
 
    @Override
    public void createProduct() {
 
    }
}

工厂模式特点

  1. 工厂接口是工厂方法模式的核心,与调用者直接交互用来提供产品。
  2. 工厂实现决定如何实例化产品,是实现扩展的途径,需要有多少种产品,就需要有多少个具体的工厂实现。

适用场景:

  1. 在任何需要生成复杂对象的地方,都可以使用工厂方法模式。有一点需要注意的地方就是复杂对象适合使用工厂模式,而简单对象,特别是只需要通过new就可以完成创建的对象,无需使用工厂模式。
  2. 工厂模式是一种典型的解耦模式,迪米特法则在工厂模式中表现的尤为明显。假如调用者自己组装产品需要增加依赖关系时,可以考虑使用工厂模式。将会大大降低对象之间的耦合度。
  3. 当需要系统有比较好的扩展性时,可以考虑工厂模式,不同的产品用不同的实现工厂来组装。

抽象工厂模式

抽象工厂是工厂模式的升级版,他用来创建一组相关或者相互依赖的对象。来看下抽象工厂模式的类图:



通过一个例子,来加深对抽象工厂的理解。

//CPU工厂接口
public interface CPUFactory {
    public void createCPU();
}
//IntelCPU工厂
public class IntelCPU implements CPUFactory {
    @Override
    public void createCPU() {
        System.out.println("Intel CPU");
    }
}
//AMDCPU工厂
public class AMDCPU implements CPUFactory {
    @Override
    public void createCPU() {
        System.out.println("AMD CPU");
    }
}
//创建抽象工厂类接口
public interface Provider {
    public CPUFactory createCPUFactory();
}
public class InterCPUFactory implements Provider {
    @Override
    public CPUFactory createCPUFactory() {
        return new InterCPU();
    }
}
public class AMDCPUFactory implements Provider {
    @Override
    public CPUFactory createCPUFactory() {
        return new AMDCPU();
    }
}
public static void main(String[] args) {
        //创建一个生产CPU工厂的工厂
        Provider cpufactory = new InterCPUFactory();
        //通过CPU工厂的工厂创建一个IntelCPU工厂
        CPUFactory intelcpu = cpufactory.createCPUFactory();
        //IntelCPU工厂生产intelCPU
        intelcpu.createCPU();

抽象工厂的优点:

抽象工厂模式除了具有工厂方法模式的优点外,最主要的优点就是可以在类的内部对产品族进行约束。所谓的产品族,一般或多或少的都存在一定的关联(例如不同厂商生产CPU)。

适用场景:

一个继承体系中,如果存在着多个等级结构(即存在着多个抽象类),并且分属各个等级结构中的实现类之间存在着一定的关联或者约束,就可以使用抽象工厂模式。

代理模式

代理模式是对象的结构模式。代理模式为其他对象提供一种代理以控制对这个对象的访问。
简单来说,在某些情况下,一个客户不想或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。
类似结婚找婚庆公司搭理,“新人”就是真实角色,“婚庆公司”就是代理角色。

静态代理模式

先理解几个概念:
角色 作用
抽象角色: 声明真实对象和代理对象的共同接口。
代理角色: 代理对象角色内部含有对真实对象的引用,从而可以操作真实对象,同时代理对象提供与真实对象相同的接口以便在任何时刻都能够代替真实对象。同时,代理对象可以在执行真实对象操作时,附加其他的操作,相当于对真实对象进行封装。
真实角色: 代理角色所代表的真实对象,是我们最终要引用的对象。

代码实现:

  1. 声明抽象角色。
public interface Marry {
    public void marry();
}
  1. 真实角色实现抽象角色接口。
@Override
public void marry() {
    System.out.println(this.getClass().getSimpleName() + "结婚啦");
}
  1. 代理角色持有真实角色引用,实现抽象角色接口,附加其他操作。
private Walidake walidake;

public WeddingCompany(Walidake walidake) {
    this.walidake = walidake;
}

@Override
public void marry() {
    System.out.println("婚礼筹备");

    walidake.marry();

    System.out.println("婚礼结束");

}
  1. 实际使用中,调用代理角色的方法对真实角色进行代理。
public static void main(String[] args) {
    Walidake walidake = new Walidake();
    WeddingCompany weddingCompany = new WeddingCompany(walidake);
    weddingCompany.marry();
}

从上述我们可以总结出:

  1. 代理角色,真实角色需要实现同一个抽象角色(接口)
  2. 代理角色需要持有真实角色的引用

上述代理方式我们称之为静态代理。那么有静态代理,那是不是也应该有动态代理?
答案是肯定的。不过,我们并不着急着匆匆进入动态代理的学习,我们先想想在日常编码中有没有看到过静态代理的例子?
我想你必定使用过Java的线程,对Runnable和Thread也很熟悉,而你现在你回过头去看,是否能发现Thread implements Runnable,这其实就是一个代理角色,而我们new Runnable,这就是一个真实角色,赋值给Thread,这是不是就让代理角色持有真实角色的引用。因此,Thread和Runnable这种关系也是静态代理。

动态代理模式

动态代理不同于静态代理的特点是它更为灵活,因为动态代理就是在运行期间动态生成代理类。我们沿用上面的例子,假设有五百个不一样的人要结婚,都交给婚庆公司来操办,那么按照静态代理的思路来做,我们需要写五百个真实角色,并且代理角色持有这五百个真实角色。这显然不合逻辑。这时候动态代理就应运而生了。

动态代理分两种,一种是基于接口实现的Java Proxy(Java自带的),一种是基于继承实现的cglib代理。下面会分别给出一个小demo,并且从源码解析角度来解析二者动态代理的实现。

Java Proxy

public class WeddingCompany implements InvocationHandler{

    private Object object;

    public WeddingCompany(Object object) {
        this.object = object;
    }

    @SuppressWarnings("unchecked")
    public  T getProxy(){
        return (T)Proxy.newProxyInstance(
                Thread.currentThread().getContextClassLoader(), 
                object.getClass().getInterfaces(), 
                this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        if ("marry".equals(method.getName())) {
            System.out.println("婚礼筹备");
            method.invoke(object, args);
            System.out.println("婚礼结束");
        }
        return null;
    }
}

InvocationHandler相当于一个处理器,在invoke方法中我们能够操作真实对象,可以附加其他操作。而我们通过Proxy.newProxyInstance(..)方法生成代理。下面invoke参数的解释说明。
参数 说明
proxy: 指代我们所代理的那个真实对象
method: 指代的是我们所要调用真实对象的某个方法的Method对象
args :指代的是调用真实对象某个方法时接受的参数
实现InvocationHandler接口并附加操作后,获取代理角色。

//第一个人
Walidake walidake = new Walidake();
Marry marry = new WeddingCompany(walidake).getProxy();
marry.marry();
System.out.println();
//第二个人
Other other = new Other();
Marry marry2 = new WeddingCompany(other).getProxy();
marry2.marry();

运行结果:


既然上述是基于接口实现的代理,那我们可不可以不写接口而是直接写一个类,然后也实现代理的功能呢?
我们说过,有两种动态代理的方式。cglib就是不通过接口也能实现动态代理的代理方式。

cglib

cglib是一个强大的高性能的代码生成包,可以为那些没有接口的类创建模仿(moke)对象。上一小节我们说到Java Proxy是通过生成字节码,再把类加载进内存后实现Proxy进行动态代理的。同样地,cglib也是通过生成操作字节码的技术实现动态代理的。但与前者不同的是它并不直接操作字节码,而是通过一个小而快的字节码处理框架ASM(Java字节码操控框架),来转换字节码并生成新的类。因此,cglib包要依赖于asm包,需要一起导入。
依然沿用上面的例子,这次我们不使用接口实现的方式。
实现细节:

public class WeddingCompany implements MethodInterceptor {
    @SuppressWarnings("unchecked")
    public  T getProxy(Class clazz) {
        Enhancer en = new Enhancer();     
         //进行代理     
         en.setSuperclass(clazz);     
         en.setCallback(this);     
         //生成代理实例     
         return (T)en.create();     
     } 

    @Override
    public Object intercept(Object object, Method method, Object[] args,
            MethodProxy methodProxy) throws Throwable {
        Object result = null;

        if ("marry".equals(method.getName())) {
            System.out.println("婚礼筹备");

            //通过继承的方法实现代理,因此这里调用的是invokeSuper
            result = methodProxy.invokeSuper(object, args);

            System.out.println("婚礼结束");
        }
        return result;
    }

}

MethodInterceptor是方法拦截器,我们能在这里做真实对象的附加操作,object就是我们的真实对象,method、args就是真实对象调用的方法和参数,methodProxy是方法代理。原来的方法可能通过使用java.lang.reflect.Method对象的一般反射调用,或者使用 net.sf.cglib.proxy.MethodProxy对象调用。net.sf.cglib.proxy.MethodProxy通常被首选使 用,因为它更快。在这个方法中,我们可以在调用原方法之前或之后注入自己的代码。
在这个例子中,我们通过Enhancer的无参数构造器时用来创建target实例,并使用setSuperClass传入代理的父类,setCallback决定方法回调。

调用细节:

public static void main(String[] args) {
        Walidake proxy = new WeddingCompany().getProxy(Walidake.class);
        proxy.marry();
}

运行结果:


应用场景

静态代理主要用来处理少部分类的托管或者扩展。静态代理对于被代理的对象很固定,我们只需要去代理一个类或者若干固定的类,数量不是太多的时候,可以使用,而且其实效果比动态代理更好。
动态代理在运行期间动态生成代理类,需要消耗的时间会更久一点。优点是可以做很多类的扩展(譬如可以通过动态代理实现aop,hibernate使用cglib来代理单端多对一和一对一关联等)。而且如果一个类的接口发生了变化,那么静态代理这时候修改起来就很麻烦了。这就是说动态代理灵活的原因。
动态代理主流的实现有两种,一种是基于接口的Java Proxy的代理机制,一种是基于继承的cglib代理机制。两种也都有其应用场景。(视乎具体业务逻辑而言)

观察者模式

对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
控件按钮、报警器等都是观察者模式。

/**
1.抽象主题(Subject)角色:把所有对观察者对象的引用保存在一个集合中,每个抽象主题角色都可以有任意数量的观察者。抽象主题提供一个接口,可以增加和删除观察者角色。一般用一个抽象类和接口来实现。​​​​​​​
2.抽象观察者(Observer)角色:为所有具体的观察者定义一个接口,在得到主题的通知时更新自己。​​​​​​
3.具体主题(ConcreteSubject)角色:在具体主题内部状态改变时,给所有登记过的观察者发出通知。具体主题角色通常用一个子类实现。​​​​​​​
4.具体观察者(ConcreteObserver)角色:该角色实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题的状态相协调。通常用一个子类实现。如果需要,具体观察者角色可以保存一个指向具体主题角色的引用。
*/
1.  public interface Subject {
2.  //添加观察者
3.  void attach(Observer o);
4.  //删除观察者
5.  void detach(Observer o);
6.  //通知观察者
7.  void notifyObservers();
8.  //发生某事
9.  void doSomeThings()
10.  }

1.  //观察者
2.  public interface Observer {

4.  void update();
5.  }

1.  public class ConcreteSubject implements Subject {

3.  ArrayList observers = new ArrayList<>();

5.  @Override
6.  public void attach(Observer o) {
7.  observers.add(o);
8.  }

10.  @Override
11.  public void detach(Observer o) {
12.  observers.remove(o);
13.  }

15.  @Override
16.  public void notifyObservers() {
17.  for (Observer o : observers) {
18.  o.update();
19.  }
20.  }

22.  public void doSomeThings(){
23.  //doSomeThings
24.  notifyObservers();//通知观察者
25.  }
26.  }

1.  //具体观察者
2.  public class ConcreteObserver implements Observer {
3.  @Override
4.  public void update() {
5.  System.out.println("我观察到subject发生了某事");
6.  }
7.  }

1.  public static void main(String[] args) {
2.  Subject cs = new ConcreteSubject();
3.  //添加观察者
4.  cs.attach(new ConcreteObserver());
5.  //subject发生了某事,通知观察者
6.  cs.doSomeThings();
7.  }

观察者模式优点:

  1. 观察者和被观察者是抽象耦合的。​​​​​​​
  2. 建立一套触发机制。

观察者模式缺点:

  1. 如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。​​​​​​​
  2. 如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。​​​​​​​
  3. 观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。

适用场景:

  1. 当一个抽象模型有两个方面, 其中一个方面依赖于另一方面。将这二者封装在独立的对象中以使它们可以各自独立地改变和复用。​​​​​​​
  2. 当对一个对象的改变需要同时改变其它对象, 而不知道具体有多少对象有待改变。
  3. 当一个对象必须通知其它对象,而它又不能假定其它对象是谁。换言之, 你不希望这些对象是紧密耦合的。

参考文章:
CSDN:https://blog.csdn.net/heijunwei/article/details/82056313
CSDN :https://blog.csdn.net/u013815218/article/details/52562536

你可能感兴趣的:(常见设计模式)