设计模式
设计模式分为三种类型,共23种
创建型模式:单例模式、抽象工厂模式、原型模式、建造者模式、工厂模式
结构型模式:适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式
行为型模式:模板方法模式、命令模式、访问者模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式(Interpreter模式)、状态模式、策略模式、职责链模式(责任链模式)
设计模式的原则
1. 面向抽象原则
2. 开-闭原则
设计应当对扩展开放,对修改关闭
如果设计遵守了“开-闭原则”,那么这个设计一定是易维护的,因为在设计中增加新的模块时,不必去修改设计中的核心模块
3. 多用组合少用继承原则
4. 高内聚-低耦合原则
如果类中的方法是一组相关的行为,则称该类是高内聚
的,反之称为低内聚的
所谓低耦合
就是尽量不要让一个类含有太多的其它类的实例的引用,以避免修改系统的其中一部分会影响到其它部分
设计模式常用的七大原则有
:
单一职责原则
接口隔离原则
依赖倒转(倒置)原则
里氏替换原则
开闭原则
迪米特法则
合成复用原则
单例模式
单例模式注意事项和细节说明
:
单例模式保证了系统内存中该累只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能
当想实例化一个单例类的时候,必须要记住使用相应的获取对象的方法而不是new
单例模式使用的场景:
需要频繁进行创建和销毁的对象
创建对象时耗时过多或耗费资源过多(重量级对象),但又经常用到的对象
工具类对象
频繁访问数据库或文件的对象(比如数据源、session工程等)
饿汉式(静态常量)应用实例
步骤如下:
构造器私有化(防止new)
类的内部创建对象
向外暴露一个静态的公共方法。getInstance
代码实现
public class SingletonTest01 { public static void main(String[] args) { Singleton instance = Singleton.getInstance(); Singleton instance2 = Singleton.getInstance(); System.out.println(instance == instance2); // true System.out.println(instance.hashCode() == instance2.hashCode()); }}// 饿汉式(静态常量)class Singleton{ // 1. 构造器私有化,防止外部new private Singleton(){ } private final static Singleton instance = new Singleton(); public static Singleton getInstance(){ return instance; } }
优缺点说明:
优点:这种方法比较简单,就是在类装载的时候就完成实例化。避免了线程同步问题
缺点:在类装载的时候就完成实例化,没有达到Lazy Loading的效果。如果从始至终从未使用过这个实例,则会导致内存的浪费
这种方式基于classloader机制避免了多线程的同步问题,但是,instance在类装载的时候就实例化,在单例模式中大多数都是调用getInstance方法,但是导致类装载的原因有很多种,因此不能确定有其他的方式(或者其他的静态方法)当中类的装载,这时候初始化instance就没有达到lazy loading的效果
结论:这种单例模式可用,可能造成内存的浪费
public class SingletonTest02 { public static void main(String[] args) { Singleton instance = Singleton.getInstance(); Singleton instance2 = Singleton.getInstance(); System.out.println(instance == instance2); // true System.out.println(instance.hashCode()); System.out.println(instance2.hashCode()); }}// 饿汉式(静态变量)class Singleton{ // 1. 构造器私有化,防止外部new private Singleton(){ } private static final Singleton instance; static { instance = new Singleton(); } public static Singleton getInstance(){ return instance; } }
优缺点说明
这种方法和上面的方式其实类似,只不过将类实例化的过程放在了静态代码块中,也是在类的装载的时候,就执行静态代码块中的代码,初始化类的实例。优缺点和上面是一样的
结论:这种单例模式可用,但是可能造成内存浪费
class Singleton{ private static Singleton instance; // 1. 构造器私有化,防止外部new private Singleton(){ } // 提供一个静态的公有方法,当使用到该方法时候,才去创建instance // 即懒汉式 public static Singleton getInstance(){ if (instance == null){ instance = new Singleton(); } return instance; } }
优缺点说明
起到了Lazy Loading的效果,但是只能在单线程下使用
如果在多线程下,一个线程进入了if (singleton == null)
判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这是便会产生多个实例。所以在多线程环境下不可使用这种方法
结论:在实际开发中,不要使用这种方法
使用synchronized
解决线程不安全问题
class Singleton{ private static Singleton instance; // 1. 构造器私有化,防止外部new private Singleton(){ } // 提供一个静态的公有方法,当使用到该方法时候,才去创建instance // 即懒汉式 // 加入同步处理的代码 public static synchronized Singleton getInstance(){ if (instance == null){ instance = new Singleton(); } return instance; } }
优缺点说明:
解决了线程不安全问题
效率太低了,每个线程在想获得类的实例的时候,执行getInstance()
方法都要进行同步。而其他这个方法只执行一次实例化代码就够了,后面的想获得这类实例直接return就行了。方法进行同步效率太低
结论:在实际开发中,不推荐使用
class Singleton{ private static Singleton instance; // 1. 构造器私有化,防止外部new private Singleton(){ } // 提供一个静态的公有方法,当使用到该方法时候,才去创建instance // 即懒汉式 // 加入同步处理的代码 public static Singleton getInstance(){ if (instance == null){ synchronized (Singleton.class){ instance = new Singleton(); } } return instance; } }
优缺点说明:
这种方法,本意是想对第四种方法进行优化,因为前面同步方法效率太低,改为同步产生实例化的代码块
但是这种同步并不能起到线程同步的作用。跟第3中实现方式遇到的情况一致
结论:在实际开发过程中,不能使用这种方法
class Singleton{ private static Singleton instance; // 1. 构造器私有化,防止外部new private Singleton(){ } // 提供一个静态的公有方法,加入双重检查代码,解决线程安全问题 ,同时解决懒加载问题 // 同时保证了效率,推荐使用 public static Singleton getInstance(){ if (instance == null){ synchronized (Singleton.class){ if (instance == null){ instance = new Singleton(); } } } return instance; } }
优缺点说明:
Double-Check是多线程开发中常使用的,如代码中所示,我们进行了两次instance == null
的判断,这样就可以保证线程的安全了
这样,实例化代码只用执行一次,后面再次访问时,判断instance == null
,直接return实例化对象,也避免了反复进行方法同步
线程安全;延迟加载;效率较高
结论:在实际开发中推荐使用
class Singleton{ private Singleton(){} private static class SingletonInstance{ private static final Singleton INSTANCE = new Singleton(); } public static Singleton getInstance(){ return SingletonInstance.INSTANCE; } }
优缺点说明:
这种方法采用了类装载的机制
来保证初始化实例只有一个线程
静态内部类方式在Singleton
类被装载时并不会立即实例化,而是在需要实例化时,调用getInstance
方法,才会装载SingletonInstance
类,从而完成Singleton的实例化
类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的
优点:避免了线程不安全,利用静态内部类特点实现延迟加载,效率高
结论:推荐使用
enum Singleton{ INSTANCE; //属性 public void syaOk(){ System.out.println("ok"); } }
优缺点说明:
借助JDK1.5添加的枚举来实现单例模式,不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象
这种方式是Effective java作者Josh Bloch提倡的方式
结论:推荐使用
工厂方法模式
工厂方法模式的结构中包括四种角色
抽象产品(Product):抽象类或接口,负责定义具体产品必须实现的方法
具体产品(ConcreteProduct):具体产品是一个类,如果Product是一个抽象类,那么具体产品就是Product的子类;如果Product是一个接口,那么具体产品是实现Product接口的类
构造者(Creator):一个接口或抽象类。构造这负责定义一个称作工厂方法的抽象类,该方法返回具体产品类的实例
具体构造者(ConcreteCreator):具体构造这重写工厂方法,是该方法返回具体的产品实例
public class Main { public static void main(String[] args) { PenCore penCore; BallPen ballPen = new RedBallPen(); penCore = ballPen.getPenCore(); penCore.writeWord("wxynb"); ballPen = new BlueBallPen(); penCore = ballPen.getPenCore(); penCore.writeWord("Hello World"); }}abstract class PenCore{ String color; public abstract void writeWord(String s);}class RedPenCore extends PenCore{ public RedPenCore() { color = "红色"; } @Override public void writeWord(String s) { System.out.println("写出" + color + "的字:" + s); }}class BluePenCore extends PenCore{ public BluePenCore() { color = "蓝色"; } @Override public void writeWord(String s) { System.out.println("写出" + color + "的字:" + s); }}class BlackPenCore extends PenCore{ public BlackPenCore() { color = "黑色"; } @Override public void writeWord(String s) { System.out.println("写出" + color + "的字:" + s); }}abstract class BallPen{ BallPen(){ System.out.println("生产了一只装有" + getPenCore().color + "笔尖的圆珠笔"); } public abstract PenCore getPenCore();}class RedBallPen extends BallPen{ @Override public PenCore getPenCore() { return new RedPenCore(); }}class BlueBallPen extends BallPen{ @Override public PenCore getPenCore() { return new BluePenCore(); }}class BlackBallPen extends BallPen{ @Override public PenCore getPenCore() { return new BluePenCore(); } }
其中BallPen类是构造者
,ReadBallPen、BlueBallPen、BlackBallPen类是具体构造者角色
,PenCore类是抽象产品角色
,RedPenCore,BluePenCore、BlackPenCore类是具体产品角色
应用程序在使用工厂模式的时候,只和抽象产品、构造者以及具体构造这打交道,用户只需要了解抽象产品有哪些方法即可,不需要知道有哪些具体的产品
工厂方法的优点
使用工厂方法可以让用户的代码和某个特定类的子类的代码解耦
工厂方法使用户不必知道它所使用的对象是怎样被创建的,只需要知道该对象有哪些方法即可
适合工厂方法模式的场景
用户需要一个类的子类的实例,但不希望与该类的子类形成耦合
用户需要一个类的子类的实例,但用户不知道该类有哪些子类可用
观察者模式
观察者模式的结构中包括四种角色
主题(Subject):主题是一个接口,该接口规定了具体主题需要实现的方法,比如,添加、删除观察者以及通知观察者更新数据的方法
观察者(Observer):观察者是一个接口,该接口规定了具体观察者用来更新数据的方法
具体主题(ConcreteSubject):具体主题时实现主题接口类的一个实例,该实例包含有可以经常发生变化的数据。具体主题需使用一个集合,比如ArrayList,存放观察者的引用,以便数据变化时通知具体观察者
具体观察者(ConcreteObserver),具体观察者是实现观察者接口类的一个实例。具体观察者包含有可以存放具体主题引用的主题接口变量,以便具体观察者让具体主题将自己的引用添加到具体主题的集合中,使自己称为它的观察者,或让这个具体主题将自己从具体主题的集合中删除,使自己不再是它的观察者
观察者模式的优点
具体主题和具体观察者是松耦合关系。由于主题(Subject)接口仅仅依赖于观察者(Observer)接口,因此具体主题知识知道它的观察者是实现观察者(Observer)接口的某个类的实例,但不需要知道具体是哪个类。同样,由于观察者仅仅依赖于主题(Subject)接口,因此具体观察者知识知道它依赖的主题是实现主题(Subject)接口的某个类的实例,但不需要知道具体是哪个类
观察模式满足“开-闭原则”。主题(Subject)接口仅仅依赖于观察者(Observer)解耦,这样,就可以让创建具体主题的类也仅仅依赖于观察者(Observer)接口,因此如果增加新的实现观察者(Observer)接口的类,不必修改具有创建具体主题的类的代码,同样,创建具体观察者的类仅仅依赖于主题(Observer)接口,如果增加新的实现主题(Subject)接口的类,也不必修改创建具体观察者类的代码
适合观察者模式的场景
当一个对象的数据更新时需要通知其他对象,但这个对象又不希望和被通知的那些对象形成紧耦合
当一个对象的数据更新时,这个对象需要让其他对象也各自更新自己的数据,但这个对象不知道具体有多少对象那个需要更新数据
装饰器模式
装饰模式的结构中包括四种角色
抽象组件(Component):抽象组件是一个抽象类。抽象组件定义了”被装饰者“需要进行”装饰“的方法
具体组件(ConcreteComponent):具体组件是抽象组件的一个子类,具体组件嗯对实例称为”被装饰者“
装饰(Decorator):装饰也是抽象组件的一个子类,但装饰还包含一个抽象组件声明的变量以保存”被装饰者“的引用。装饰可以是抽象类也可以是一个非抽象类,如果是非抽象类,那么该类实例称为”装饰者“
具体装饰(ConcreteDecorator):具体装饰是装饰的一个非抽象子类,具体装饰的实例称为”装饰者“
public class Main { public static void needBird(Bird bird){ int flyDistance = bird.fly(); System.out.println("这只鸟能飞行:"+ flyDistance); } public static void main(String[] args) { Sparrow sparrow = new Sparrow(); // 这只鸟能飞行:100 needBird(sparrow); Bird sparrowDecorator1 = new SparrowDecorator(sparrow); //这只鸟能飞行:150 Bird sparrowDecorator2 = new SparrowDecorator(sparrowDecorator1); //这只鸟能飞行:200 needBird(sparrowDecorator1); needBird(sparrowDecorator2); }}abstract class Bird{ public abstract int fly();}// 具体组件是Sparrowclass Sparrow extends Bird{ @Override public int fly() { return 100; }}abstract class Decorator extends Bird{ protected Bird bird; public Decorator() { } public Decorator(Bird bird) { this.bird = bird; }}class SparrowDecorator extends Decorator{ public final int DISTANCE = 50; public SparrowDecorator() { } public SparrowDecorator(Bird bird) { super(bird); } @Override public int fly() { int distance = 0; distance = bird.fly() + eleFly(); // 委托被装饰着bird调用fly(),然后在调用eleFly() return distance; } // 设置private目的是使得客户程序只有调用Fly才能调用eleFly private int eleFly() { // 装饰者新添加的方法 return DISTANCE; } }
装饰器模式的优点
被装饰者和装饰者是松耦合关系。由于装饰(Decorator)仅仅依赖于抽象组件(Component),因此具体装饰只知道它要装饰的对象是抽象组件某一个子类的实例,但不需要知道是哪一个子类
装饰模式满足”开-闭原则“。不必修改具体组件,就可以增加新的针对该具体组件的具体装饰
可以使用多个具体装饰来装饰具体装饰的实例
适合装饰器模式的场景
程序希望动态地增强类的某个对象的功能,而又不影响到该类的其他对象
采用继承来增强对象功能不利于系统的扩展和维护
适配器模式
对象适配器模式的结构中包括三种角色
目标(Target):目标是一个接口,该接口是客户想要使用的接口
被适配者(Adaptee):被适配者是一个已经存在的接口或抽象类,这个接口或抽象类需要适配
适配器(Adapter):适配器是一个类,该类实现了目标接口并包含有被适配者的引用,即适配器的职责是对被适配者接口(抽象类)与目标接口进行适配
public class Main { public static void main(String[] args) { TreeElectricOutlet outlet; Wash wash = new Wash(); outlet = wash; System.out.println("使用三相插座接通电流:"); outlet.connectElectricCurrent(); Tv tv = new Tv(); TreeElectricAdapter adapter = new TreeElectricAdapter(tv); outlet = adapter; System.out.println("使用三相插座接通带电流:"); outlet.connectElectricCurrent(); // 长江电视机开始播放节目 }}// Targetinterface TreeElectricOutlet{ public abstract void connectElectricCurrent();}//被适配者 ,就是两个插头的想用三个插头的通电interface TwoElectricOutlet{ public abstract void connectElectricCurrent();}class TreeElectricAdapter implements TreeElectricOutlet{ TwoElectricOutlet outlet; public TreeElectricAdapter(TwoElectricOutlet outlet) { this.outlet = outlet; } @Override public void connectElectricCurrent() { outlet.connectElectricCurrent(); }}class Wash implements TreeElectricOutlet{ private String name; public Wash() { this.name = "黄河洗衣机"; } @Override public void connectElectricCurrent() { this.turnOn(); } public void turnOn(){ System.out.println(name+"开始洗衣物"); }}class Tv implements TwoElectricOutlet{ String name; Tv(){ this.name = "长江电视机"; } @Override public void connectElectricCurrent() { this.turnOn(); } public void turnOn(){ System.out.println(name+"开始播放节目"); }}
适配器模式的优点
目标(Target)与被适配者(Adaptee)是完全解耦的关系
适配器模式满足“开闭原则”。当添加一个实现Adapter接口的新类时,不必修改Adapter,Adapter就能对这个新类的实例进行适配
适合适配器模式的场景
一个程序想使用已经存在的类,但该类所实现的接口和当前程序所使用的接口不一致