一. 设计模式六大原则

1.单一原则(Single Responsibility Principle):一个类或者一个方法只负责一项职责。

2.里氏替换原则(LSP liskov substitution principle):子类可以扩展父类的功能,但不能改变原有父类 的功能

3.依赖倒置原则(dependence inversion principle):面向接口编程,(通过接口作为参数实现应用 场景) 抽象就是接口或者抽象类,细节就是实现类 上层模块不应该依赖下层模块,两者应依赖其抽象; 抽象不应该依赖细节,细节应该依赖抽象; 通俗点就是说变量或者传参数,尽量使用抽象类,或者接口;

4.接口隔离(interface segregation principle):使用多个专门的接口,而不使用单一的总接口,即客户端不应该依赖那些它不需要的接口

5.迪米特原则(law of demeter LOD):最少知道原则,尽量降低类与类之间的耦合,一个对象应该 对其他对象有最少的了解

6.开闭原则(open closed principle):对扩展开放,对修改闭合

二. 设计模式的分类

根据目的来分;通过完成什么工作划分为创建型模式、结构型模式和行为型模式 3 种类型
1、创建型模式:作用于对象的创建,将对象的创建与使用分离。其中囊括了单例、原型、工厂方法、抽象工厂、建造者5 种创建型模式。
2、结构型模式:将类或对象按某种布局组成更大的结构,其中以代理、适配器、桥接、装饰、外观、享元、组合 7 种结构型模式为主。
3、行为型模式:作用于类或对象之间相互协作共同完成单个对象无法单独完成的任务,以及怎样分配职责。主要包含了模板方法、策略、命令、职责链、状态、观察
者、中介者、迭代器、访问者、备忘录、解释器等 11 种行为型模式。

根据作用范围来分;根据是用于类上还是用于对象上划分分为类模式和对象模式两种。
1、类模式:用于处理类与子类之间的关系,这些关系通过继承来建立,在编译时刻便确定下来了。工厂方法、(类)适配器、模板方法、解释器均属于该模式。
2、对象模式:用于处理对象之间的关系,这些关系可以通过组合或聚合来实现,在运行时刻是可以变化的,更具动态性。除了以上 4 种,其他的都是对象模式。

三. 单例模式及其实现方式

1 懒汉式

该方式是使用synchronized关键字进行加锁,保证了线程安全性。
优点:在第一次调用才初始化,避免了内存浪费。( 能延时加载)
缺点:对获取实例方法加锁,大大降低了并发效率。

由于加了锁,对性能影响较大,不推荐使用。

  1. public class SingletonLazy {
  2. /**
  3. * 私有实例
  4. */
  5. private static SingletonLazy instance;
  6. /**
  7. * 私有构造方法
  8. */
  9. private SingletonLazy() {
  10. }
  11. /**
  12. * 唯一公开获取实例的方法(静态工厂方法),该方法使用synchronized加锁,来保证线程安全性
  13. *
  14. * @return
  15. */
  16. public static synchronized SingletonLazy getInstance() {
  17. if (instance == null) {
  18. instance = new SingletonLazy();
  19. }
  20. return instance;
  21. }
  22. }

2 饿汉式

饿汉式是利用类加载机制来避免了多线程的同步问题,所以是线程安全的。
优点:未加锁,执行效率高。
缺点:类加载时就初始化实例( 不能延时加载 ),造成内存浪费。

如果对内存要求不高的情况,还是比较推荐使用这种方式。

  1. public class SingletonEager {
  2. /**
  3. * 私有实例,静态变量会在类加载的时候初始化,是线程安全的
  4. */
  5. private static final SingletonEager instance = new SingletonEager();
  6. /**
  7. * 私有构造方法
  8. */
  9. private SingletonEager() {
  10. }
  11. /**
  12. * 唯一公开获取实例的方法(静态工厂方法)
  13. *
  14. * @return
  15. */
  16. public static SingletonEager getInstance() {
  17. return instance;
  18. }
  19. }

3 双重校验锁

利用了volatile修饰符的线程可见性(被一个线程修改后,其他线程立即可见),即保证了懒加载,又保证了高性能,所以推荐使用

  1. public class SingletonDCL {
  2. /**
  3. * 私有实例,volatile修饰的变量是具有可见性的(即被一个线程修改后,其他线程立即可见)
  4. */
  5. private volatile static SingletonDCL instance;
  6. /**
  7. * 私有构造方法
  8. */
  9. private SingletonDCL() {
  10. }
  11. /**
  12. * 唯一公开获取实例的方法(静态工厂方法)
  13. *
  14. * @return
  15. */
  16. public static SingletonDCL getInstance() {
  17. if (instance == null) {
  18. synchronized (SingletonDCL.class) {
  19. if (instance == null) {
  20. instance = new SingletonDCL();
  21. }
  22. }
  23. }
  24. return instance;
  25. }
  26. }

4 静态内部类

该模式利用了静态内部类延迟初始化的特性,来达到与双重校验锁方式一样的功能。由于需要借助辅助类,并不常用。

  1. public class SingletonInnerClass {
  2. /**
  3. * 私有构造方法
  4. */
  5. private SingletonInnerClass() {
  6. }
  7. /**
  8. * 唯一公开获取实例的方法(静态工厂方法)
  9. *
  10. * @return
  11. */
  12. public static SingletonInnerClass getInstance() {
  13. return LazyHolder.INSTANCE;
  14. }
  15. /**
  16. * 私有静态内部类
  17. */
  18. private static class LazyHolder {
  19. private static final SingletonInnerClass INSTANCE = new SingletonInnerClass();
  20. }
  21. }

5 枚举类

该方式利用了枚举类的特性,不仅能避免线程同步问题,还防止反序列化重新创建新的对象。这种方式是 Effective Java 作者 Josh Bloch 提倡的方式。
但由于这种编码方式还不能适应,所以实际工作中很少使用。

  1. public enum SingletonEnum {
  2. INSTANCE;
  3. public void method() {
  4. System.out.println("枚举类中定义方法!");
  5. }
  6. }

四. 工厂模式

工厂模式分为简单工厂模式,工厂方法模式和 抽象工厂模式 ,它们都属于设计模式中的创建型模式。其 主要功能都是帮助我们把对象的实例化部分抽取了出来,目的是降低系统中代码耦合度,并且增强了系 统的扩展性。

(1)简单工厂设计模式
定义一个工厂类,他可以根据参数的不同返回不同类的实例,被创建的实例通常都具有共同的父类
但是其最大的缺点在于工厂类不够灵活,增加新的具体产品需要修改工厂类的判断逻辑代码,而且 产品较多时,工厂方法代码将会非常复杂

(2)工厂方法设计模式
工厂方法是针对每一种产品提供一个工厂类。通过不同的工厂实例来创建不同的产品实例。 (具体工厂负责生产具体的产品,每一个具体工厂对应一种具体产品 )
在同一等级结构中,支持增加任意产品。

(3)抽象工厂设计模式
提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。抽象工厂模式又称为Kit模式,它是一种对象创建型模式。

区别
简单工厂: 用来生产同一等级结构中的任意产品。(对于增加新的产品,无能为力)
工厂方法 :用来生产同一等级结构中的固定产品。(支持增加任意产品)
抽象工厂 :用来生产不同产品族的全部产品。(对于增加新的产品,无能为力;支持增加产品族)

五. 代理模式

代理模式主要使用了 Java 的多态,干活的是被代理类,代理类主要是 接活,你让我干活,好,我交给幕后的类去干,你满意就成,那怎么知道被代理类能不能干呢?看是不是同一个接口。

1.静态代理

静态代理在使用时,需要定义接口或者父类,被代理对象与代理对象一起实现相同的接口或者是继承相同父类.
代码实现 :
写一个接口

  1. public interface Singer {
  2. /**
  3. * 歌星都能唱歌
  4. */
  5. void sing();
  6. }

定义歌手

  1. public class MaleSinger implements Singer{
  2. private String name;
  3. public MaleSinger(String name) {
  4. this.name = name;
  5. }
  6. @Override
  7. public void sing() {
  8. System.out.println(this.name + "开始唱歌了!");
  9. }
  10. }

定义经纪人

  1. public class Agent implements Singer {
  2. private Singer singer;
  3. public Agent(Singer singer) {
  4. this.singer = singer;
  5. }
  6. @Override
  7. public void sing() {
  8. System.out.println("节目组找过来!需要演出,谈好演出费用。。。。。");
  9. singer.sing();
  10. System.out.println("结算费用,下一次合作预约。。。。。。");
  11. }
  12. }

Client . java 即客户

  1. public class Client {
  2. public static void main(String[] args) {
  3. Singer singer = new MaleSinger("鹿晗");
  4. Singer agent = new Agent(singer);
  5. agent.sing();
  6. }
  7. }

分析:在这个过程中,你直接接触的就是歌手的经济人,经纪人在歌手演出的前后跑前跑后发挥了巨大 的作用。

除了实现共同的接口,我们还能使用继承类的方式

  1. public class Agent extends MaleSinger {
  2. private MaleSinger maleSinger;
  3. public void setMaleSinger(MaleSinger maleSinger) {
  4. this.maleSinger = maleSinger;
  5. }
  6. @Override
  7. public void sing() {
  8. System.out.println("开始唱歌了-----------");
  9. maleSinger.sing();
  10. System.out.println("结束唱歌了-----------");
  11. }
  12. }
  1. public static void main(String[] args) {
  2. MaleSinger maleSinger = new MaleSinger("鹿晗");
  3. Agent agent = new Agent();
  4. agent.setMaleSinger(maleSinger);
  5. agent.sing();
  6. }

静态代理总结:
1.可以做到在不修改目标对象的功能前提下,对目标功能扩展.
2.缺点:

  • 因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类,类太多.同时,一旦接口增加方法,目标对象与代理对象都要维护.

如何解决静态代理中的缺点呢?答案是可以使用动态代理方式

2.动态代理

  • 动态代理的角色和静态代理的一样 .
  • 动态代理的代理类是动态生成的 . 静态代理的代理类是我们写的
  • 动态代理分为两类 : 一类是基于接口动态代理 , 一类是基于类的动态代理

    1. 基于接口的动态代理----JDK动态代理 <br /> 基于类的动态代理--cglib

动态代理就是当有大量的类需要执行一些共同代码时,我们自己写太麻烦,可直接使用java代码,自动生成一个类帮助我们批量的增强某些方法。

六. 模板模式

定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。它是一种类行为型模式。
通俗点的理解就是 :完成一件事情,有固定的数个步骤,但是每个步骤根据对象的不同,而实现细节不同;就可以在父类中定义一个完成该事情的总方法,按照完成事件需要的步骤去调用其每个步骤的实现方法。每个步骤的具体实现,由子类完成。

1. 何时使用

  • 有一些通用的方法时

    2. 方法

  • 将通用算法抽象出来

    3. 优点

  • 封装不变部分,扩展可变部分

  • 提取公共部分代码,便于维护
  • 行为由父类控制,子类实现

    4. 缺点

  • 每一个不同的实现都需要一个子类实现,导致类的个数增加,使得系统更加庞大