深入理解Java中23种设计模式
设计模式介绍
- 设计模式(Design Patterns):
- 一套被反复使用,多数人知晓,经过分类编目,代码设计的总结
- 使用设计模式是为了可重用代码,让代码更容易理解,保证代码可靠性
- 项目中合理运用设计模式可以完美的解决很多问题,每种模式都有相应的原理与之对应,
- 每个模式描述了一个在我们周围不断重复发生的问题,以及该问题的核心解决方案
设计模式分类
- 总体来说,设计模式分为三大类:
- 创建型模式(5种):
- 工厂方法模式
- 抽象工厂模式
- 单例模式
- 建造者模式
- 原型模式
- 结构型模式(7种):
- 适配器模式
- 装饰器模式
- 代理模式
- 外观模式
- 桥接模式
- 组合模式
- 享元模式
- 行为型模式(11种):
- 策略模式
- 模板方法模式
- 观察者模式
- 迭代子模式
- 责任链模式
- 命令模式
- 备忘录模式
- 状态模式
- 访问者模式
- 中介者模式
- 解释器模式
- 创建型模式(5种):
- 其余两类模式:
- 并发型模式
- 线程池模式
设计模式六大原则
单一职责原则(Single Responsibility Principle) - 这里的设计模式原则,主要讨论的是Java面向对象编程设计中设计原则,单一职责原则由于其适用的普遍性,个人认为不放在六大原则之中
- 单一职责原则 :一个类只负责一项职责
- 不能存在多于一个导致类变更的原因
- 单一职责原则符合"高内聚,低耦合"的思想
- 单一职责原则不只是面向对象编程思想所特有的,只要是模块化的程序设计,都适用单一职责原则
开闭原则(Open Close Principle)
- 开闭原则 :对扩展开放,对修改关闭
- 程序进行扩展的时候,不能修改原有的代码, 实现一个热插拔的效果
- 为了使程序扩展性好,易于维护和升级:需要使用接口和抽象类
里氏代换原则(Liskov Substitution Principle)
- 里氏代换原则 :任何基类可以出现的地方,子类一定可以出现
- LSP是继承复用的基石,只有当衍生类可以替换掉基类,软件单位的功能不受影响时, 基类才能真正被复用,衍生类也能够在基类的基础上增加新的行为
- 里氏代换原则是对实现抽象化的具体步骤的规范:
- 里氏代换原则是对开闭原则的补充
- 实现开闭原则的关键步骤就是抽象化
- 基类与子类的继承关系就是抽象化的具体实现
依赖倒转原则(Dependence Inversion Principle)
- 依赖倒转原则 :针对接口编程,依赖于抽象而不依赖于具体
- 依赖倒转原则是开闭原则的基础
接口隔离原则(Interface Segregation Principle)
- 接口隔离原则 :使用多个隔离的接口,比使用单个接口要好,降低类之间的耦合度
- 从接口隔离原则可以看出:设计模式就是一个软件的设计思想
- 从大型软件架构出发,为了升级和维护方便 :降低依赖,降低耦合
迪米特法则(最少知道原则)(Demeter Principle)
- 迪米特法则:最少知道原则 ,一个实体应当尽量少的与其它实体发生相互作用,使得功能模块相互独立
合成复用原则(Composite Reuse Principle)
- 合成复用原则 :尽量使用合成或者聚合的方式,而不是使用继承
Java中23种设计模式
创建型模式
工厂方法模式(Factory Method)
工厂方法模式分为三种 :普通工厂模式,多个工厂方法模式,静态工厂方法模式
普通工厂模式
- 建立一个工厂类,对实现了同一接口的一些类进行实例的创建:
--- 发送邮件和短信
- 接口
public interface Sender{
public void Send();
}
- 实现类
public class MailSender implements Sender{
@Override
public void Send(){
System.out.println("MailSender Method");
}
}
------------------------------------------------------------------------------------------------------------------------------------------------------------------------
public class SmsSender implements Sender{
@Override
public void Send(){
System.out.println("SmsSender Method");
}
}
- 工厂类
public class SendFactory{
public Sender produce(String type){
if("mail".equals(type)){
return new MailSender();
}else if("sms".equals(type)){
return new SmsSender();
}else{
System.out.println("Please input right type!");
}
}
}
- 测试类
public class FactoryTest{
public static void main(String[] args){
SendFactory factory=new SendFactory();
Sender sender=factory.produce("sms");
sender.Send();
}
}
多个工厂方法模式
- 多个工厂方法模式是对普通工厂方法模式的改进
- 普通工厂方法模式中,如果传递的字符串出错,则不能创建对象
- 多个工厂方法模式提供多个工厂方法,分别创建对象
- SendFactory类
public class SendFactory{
public Sender produceMail(){
return new MailSender();
}
public Sender produceSms(){
return new SmsSender();
}
}
- 测试类
public class FactoryTest{
public static void main(String[] args){
SendFactory factory=new SendFactory();
Sender sender=factory.produceMail();
sender.Send();
}
}
静态工厂方法模式
- 将多个工厂方法模式中的方法设置为静态方法, 不需要创建实例 ,直接调用即可
- SendFactory
public class SendFactory{
public static Sender produceMail(){
return new MailSender();
}
public static Sender produceSms(){
return new SmsSender();
}
}
- FactoryTest
public class FActoryTest{
public static void main(String[] args){
Sender sender=SenderFactory.produceMail();
sender.Send();
}
}
总结
- 工厂模式适合出现大量的产品需要创建,并且具有共同的接口,可以通过工厂方法模式创建:
- 普通工厂模式: 如果传入字符串有误,就不能创建对象
- 静态工厂方法模式相对于多个工厂方法模式 ,不需要实例化工厂类
- 大多数情况下,采用静态工厂方法模式
抽象工厂模式(Abstract Factory)
- 工厂方法模式问题: 类的创建依赖工厂类.如果想要扩展程序,必须对工厂类进行修改,这违背了闭包原则
- 抽象工厂模式: 创建多个工厂类,一旦需要增加新的功能,直接增加工厂类就可以,不需要修改之前的工厂类
- Sender
public interface Sender{
public void Sender();
}
- 两个实现类
- MailSender
public class MailSender implements Sender {
@Override
public void Send(){
System.out.println("This is MailSender!");
}
}
- SmsSender
public class SmsSender implements Sender{
@Override
public void Send(){
System.out.println("This is SmsSender!");
}
}
- 两个工厂类
- 工厂类接口:
public interface Provider{
public Sender produce();
}
- SendMailFactory
public class SendMailFactory implements Provider{
@Override
public Sender produce(){
return new MailSender();
}
}
- SendSmsFactory
public class SendSmsFactory implements Provider{
@Override
public Sender produce(){
return new SmsSender();
}
}
- Test
public class Test{
public static void main(String[] args){
Provider provider=new SendMailFactory();
Sender sender=provider.produce();
sender.Send();
}
}
- 抽象工厂模式的优点就是拓展性强:
- 如果需要增加一个功能,例如:发及时信息
- 只需做一个实现类, 实现Sender接口
- 做一个工厂类, 实现Provider接口
- 如果需要增加一个功能,例如:发及时信息
单例模式(Singleton)
- 单例模式 :保证在一个JVM中,一个单例对象只有一个实例存在
- 单例模式的优点:
- 某些类创建比较繁琐,对于一些大型对象,可以减少很大的系统开销
- 省去了new操作符,降低了系统内存的使用频率,减轻GC(Garbage Collection-垃圾回收)压力
- 有些类比如交易所的核心交易引擎,控制着交易流程,如果该类可以创建多个,系统会完全混乱,所有只有使用单例模式,才能保证核心交易服务器独立控制整个流程
- 单例类
public class Singleton{
/* 私有静态实例,防止被引用,赋值为null,目的是实现延迟加载 */
private static Singleton instance=null;
/* 私有构造方法,防止被实例化 */
private Singleton(){
}
/* 静态工厂方法,创建实例 */
public static Singleton getInstance(){
if(instance==null){
instance=new Singleton();
}
return instance;
}
/* 如果该对象被用于序列化,可以保证对象在序列化前后保持一致 */
public Object ReadResolve(){
return instance;
}
}
- 考虑到多线程安全,首先会想到对getInstance方法加synchronized关键字:
public static synchronized Singleton getInstance(){
if(instance==null){
instance=new Singleton();
}
return instance;
}
由于synchronized锁住的是这个对象,这样的用法,每次调用getInstance(),都要对对象上锁,在性能上会有所下降.
- 只有在第一次创建对象的时候需要加锁,之后就不需要了:
public static Singleton getInstance(){
if(instance==null){
synchronized(instance){
if(instance==null){
instance=new Singleton();
}
}
}
return instance;
}
这样似乎解决了问题,将synchronized关键字加入内部,这样在调用的时候是不需要加锁的,只有在instance为null,并创建对象的时候才需要的加锁,性能得到了提升,但是这样的情况还是有问题的
- 存在这样的情况:
- 在Java中创建对象和赋值操作是分开进行的
- 即instance=new Singleton()是分两步执行的
- JVM并不保证这两个操作的先后顺序:
- 有可能JVM会为新的Singleton实例分配空空间,然后直接赋值给instance成员
- 然后再去初始化这个Singleton实例
- 这样就可能会出错
- 示例:
- A,B两个线程
- A,B线程同时进入第一个if判断
- A首先进入synchronized块,由于instance为null, 执行instance=new Singleton()
- 由于JVM内部的优化机制 ,JVM先划出一些分配给Singleton的空白内存,并赋值给instance成员,此时还没有开始初始化这个实例,然后A离开了synchronized块
- B进入synchronized, 由于instance此时不是null, 因此它马上离开了synchronized块并将结果返回给调用该方法的程序
- 此时B线程打算使用Singleton实例,发现它还没有初始化,于是产生错误
- A,B两个线程
- 代码需要进一步优化:
private static class SingletonFactory{
private static Singleton instance=new Singleton();
}
public static Singleton getInstance(){
return SingletonFactory.instance;
}
- 实际情况是:
- 单例模式使用内部类来维护单例的实现
- JVM内部的机制能够保证当一个类被加载的时候,这个类的加载过程是互斥的
- 当第一次调用getInstance时,JVM能够保证instance只被创建一次,并且会保证把赋值给instance的内存初始化完毕
- 该方法也只会在第一次调用的时候采用互斥机制,可以完美解决低性能的问题
public class Singleton{
/* 私有构造方法,防止被实例化 */
private Singleton(){}
/* 使用内部类维护单例 */
private static class SingletonFactory{
private static Singleton instance=new Singleton();
}
/* 获取实例 */
public static Singleton getInstance(){
return SingletonFactory.instance;
}
/* 如果该对象被序列化,可以保证对象在序列化前后保持一致 */
public Object readResolve(){
return getInstance();
}
}
这种方法,如果在构造函数中抛出异常,实例将永远不会创建,也会出错.
只能根据实际场景,选择最适合应用场景的实现方法
- 因为只需要在创建类的时候进行同步,所以只要将创建和getInstance()分开,单独为创建加synchronized关键字,也是可以的:
public class SingletonTest{
private static SingletonTest instance=null;
private SingletonTest(){}
private static synchronized void syncInit(){
if(instance==null){
instance=new SingletonTest();
}
}
public static SingletonTest getInstance(){
if(instance==null){
syncInit();
}
return instance;
}
}
- 采用 "影子实例" 的方法为单例对象的属性同步更新:
public class SingletonTest{
private static SingletonTest instance=null;
private Vector properties=null;
public Vector getProperties(){
return properties;
}
private SingletonTest(){}
private static synchronized void syncInit(){
if(instance==null){
instance=new SingletonTest();
}
}
public static SingletonTest getInstance(){
if(intance==null){
syncInit();
}
return instance;
}
public void updateProperties(){
SingletonTest shadow=new SingletonTest();
properties=shadow.getProperties();
}
}
- 单例模式的特点:
- 单例模式理解起来简单,具体实现起来是有一定难度的\
- 同步
- 异步
- synchronized关键字锁定的是对象,使用的时候要在恰当的地方使用:
- 注意需要使用锁的对象和过程,有时候不是整个对象及整个过程都需要锁
- 单例模式理解起来简单,具体实现起来是有一定难度的\
- 采用类的静态方法,可以实现单例模式的效果
- 类的静态方法和单例模式的区别:
- 静态类不能实现接口:
- 从类的角度说,是可以的,但是这样就会破坏静态了
- 接口中不允许有static修饰的方法,即使实现了也是非静态的
- 单例可以被延迟启动:
- 静态类在第一次加载时初始化
- 单例延迟加载,是因为有些类比较庞大,延迟加载有助于提升性能
- 单例可以被继承:
- 单例中的方法可以被重写
- 静态类内部方法都是static,无法重写
- 单例比较灵活:
- 从实现上讲,单例只是一个普通的Java类,只要满足单例的基本要求,可以随心所欲地实现其它功能
- 静态类不行
- 静态类不能实现接口:
- 单例模式内部可以就是用一个静态类实现
- 类的静态方法和单例模式的区别:
建造者模式(Builder)
- 工厂模式提供的是创建单个类的模式
- 建造者模式: 将各种产品集中起来进行管理,用来创建复合对象
- 复合对象: 指某个类具有不同的属性
- 建造者模式就是抽象工厂类模式和Test类结合起来得到的
- 代码实现: 一个Sender接口,两个实现类MailSender和SmsSender
- Builder
public class Builder{
private List<Sender> list=new ArrayList<Sender>();
public void produceMailSender(int count){
for(int i=0;i<count;i++){
list.add(new MailSender());
}
}
public void produceSmsSender(int count){
for(int i=0;i<count;i++){
list.add(new SmsSender());
}
}
}
- 测试类
public class Test{
public static void main(String[] args){
Builder builder=new Builder();
builder.produceMailSender(10);
}
}
- 建造者模式将很多功能集成到一个类里,这个类就可以创造出比较复杂的模块
- 建造者模式和工厂模式的区别:
- 工厂模式关注的是创建单个产品
- 建造者模式关注的是创建符合对象,多个部分
原型模式(Prototype)
- 原型模式: 将一个对象作为原型,进行复制,克隆,产生一个和原对象类似的新对象
- 原型模式虽然是创建型模式,但是与工厂模式没有关系
- 在Java中,复制对象是通过clone() 实现的
- 原型类
public class Prototype implements Cloneable{
public Object clone() throws CloneNotSupportedException{
Prototype proto=(Prototype)super.clone();
return proto;
}
}
- 一个原型类,只需要实现Cloneable接口,重写clone() 方法
- clone方法可以改写为任何名称,因为Cloneable接口是个空接口,可以任意定义实现类的方法名
- 重点是super.clone():
- super.clone() 方法调用的是Object的**clone() ** 方法
- 在Object类中 ,clone() 方法时native的
- 对象的深复制和浅复制:
- 深复制:
- 将一个对象复制后,不论是基本类型还是引用类型,都是重新创建的
- 深复制会进行完全彻底的复制
- 浅复制:
- 将一个对象复制后,基本数据类型的变量都会重新创建,而引用类型指向的还是原对象的引用
- 深复制:
public class Prototype implements Cloneable,Serializable{
private static final long serialVersionUID=1L;
private String string;
private SerializableObject obj;
/* 浅复制 */
public Object clone() throws CloneNotSupportedException{
Prototype proto=(Prototype)super.clone();
return proto;
}
/* 深复制 */
public Object clone() throws IOException,ClassNotFoundException{
/* 写出当前对象的二进制流 */
ByteArrayOutputStream bos=new ByteArrayOutputStream();
ObjectOutputStream oos=new ObjectOutputStream(bos);
oos.writeObject(this);
/* 读入二进制流产生的新对象 */
ByteArrayInputStream bis=new ByteArrayInputStream(bos.toByteArray());
OnjectInputStream ois=new ObjectInputStream(bis);
return ois.readObject();
}
public String getString(){
return string;
}
public void setString(String string){
this.string=string;
}
public SerializableObject getObj(){
return obj;
}
public void setObj(SerializableObject obj){
this.obj=obj;
}
}
class SerializableObject implements Serializable{
private static final long serialVersionUID=1L;
}
- 要实现深复制:
- 要采用流的形式读入当前对象的二进制输入
- 再写出二进制数据对应的对象
结构型模式
适配器模式(Adapter Pattern)
- 对象的适配器模式是各种结构型模式的起源
- 适配器模式: 将某个类的接口转换成客户端期望的另一个接口表示
- 目的: 消除由于接口不匹配所造成的类的兼容性问题
- 适配器模式主要分为三类:
- 类的适配器模式
- 对象的适配器模式
- 接口的适配器模式
类的适配器模式
- 核心思想: 有一个Source类,拥有一个方法待适配,目标接口是Targetable, 通过Adapter类,将Source的功能扩展到Targetable里
- Source
public class Source{
public void method1(){
System.out.println("This is original method!");
}
}
- Targetable
public interface Targetable{
/* 与原类中的方法相同 */
public void method1();
/* 新类方法 */
public void method2();
}
- Adapter
public class Adapter extends Source implemments Targetable{
@Override
public void method2(){
System.out.println("This is the targetable method!");
}
}
- Adapter类继承Source类,实现Targetable接口:
- AdapterTest
public class AdapterTest{
public static void main(String[] args){
Targetable target=new Adapter();
target.method1();
target.method2();
}
}
- 这样Targetable接口的实现类就具有Source类的功能
对象的适配器模式
- 基本思路和类的适配器相同,只是将Adapter类作修改 ,不继承Source类,而是持有Source类的实例,以达到解决兼容性问题
- Wrapper
public class Wrapper implements Targetable{
private Source source;
public Wrapper(Source source){
super();
this.source=source;
}
@Override
public void method1(){
source.method1();
}
@override
public void method2(){
System.out.println("This is the targetable method!");
}
}
- Test
public class AdapterTest{
public static void main(String[] args){
Source source=new Source();
Targetable target=new Wrapper(source);
target.method1();
target.nethod2();
}
}
接口的适配器模式
- 一个接口中有多个抽象方法,当写该接口的实现类时,必须实现该接口的所有方法,这样明显比较浪费,因为并不是所有的方法都是需要用到的,有时只要引入一些即可.为了解决这样的问题,引入了接口适配器模式
- 接口适配器模式: 借助于一个抽象类,该抽象类实现了该接口以及所有的方法,只需要和该抽象类进行联系即可.
- 只需要写一个类,继承该抽象类,重写需要用到的方法
- Sourceable
public interface Sourceable{
public void method1();
public void method2();
}
- Wrapper-抽象类
public abstract class Wrapper implements Sourceable{
public void method1(){}
public void method2(){}
}
- SourceSub1
public class SourceSub1 extends Wrapper{
public void method1(){
System.out.println("The sourceable interface's first Sub");
}
}
- SourceSub2
public class SourceSub2 extends Wrapper(){
public void method2(){
System.out.println("The Sourceable interface's second Sub");
}
}
- WrapperTest
public class WrapperTest{
public static void main(String[] args){
Sourceable source1=new SourceSub1();
Sourceable source2=new SourceSub2();
source1.method1();
source1.method2();
source2.method1();
source2.method2();
}
}
- 三种适配器模式的应用场景:
- 类的适配器模式:
- 当希望一个类转换成满足另一个新接口的类时,可以使用类的适配器模式
- 创建一个新类,继承原有的类,实现新的接口即可
- 对象的适配器模式:
- 当希望一个对象转换成满足另一个新接口的对象时,可以使用对象的适配器模式
- 创建一个Wrapper类,持有原类的一个实例,在Wrapper类的方法中,调用实例的方法即可
- 接口的适配器模式:
- 当不希望实现一个接口中所有的方法时,可以使用接口的适配器模式
- 创建一个抽象类Wrapper,实现所有方法,写其它类时,只要继承抽象类即可
- 类的适配器模式:
装饰器模式(Decorator)
- 装饰器模式: 给一个对象动态地增加一些新的功能
- 装饰器模式要求装饰对象和被装饰对象实现同一个接口, 装饰对象持有被装饰对象的实例
- Source类时被装饰类 ,Decorator类是装饰类,可以为Source类动态地增加一些功能:
- Sourceable
public interface Sourceable{
public void method();
}
- Source
public class Source implements Sourceable{
@Override
public void method(){
System.out.println("The original method!");
}
}
- Decorator
public class Decorator implements Sourceable{
private Sourceable source;
public Decorator(Sourceable source){
super();
this.source=source;
}
@Override
public void method(){
System.out.println("Before decorator!");
source.method();
System.out.println("After decorator!");
}
}
-Test
public class DecoratorTest{
public static void main(String[] args){
Sourceable source=new Source();
Sourceable obj=new Decorator(source);
obj.method();
}
}
- 装饰器模式应用场景:
- 需要扩展一个类的功能
- 动态地为一个对象增加功能,而且还能动态地撤销(继承的功能是静态的,不能动态增删)
- 装饰器模式的缺点: 产生过多类似的对象,不易排错
代理模式(Proxy)
- 代理模式: 创建一个代理类,替原对象进行一些操作
- Sourceable
public interface Sourceable{
public void method();
}
- Source
public class Source implements Sourceable{
@Override
public void method(){
System.out.println("The original method!");
}
}
- Proxy
public class Proxy implements Sourceable{
private Source source;
public Proxy(){
super();
this.source=new Source;
}
@Override
public void method(){
before();
source.method();
after();
}
public void before(){
System.out.println("Before Proxy!");
}
public void after(){
System.out.println("After Proxy!");
}
}
- ProxyTest
public class ProxyTest{
public static void main(String[] args){
Sourceable source=new Proxy();
source.method();
}
}
- 代理模式的应用场景:
- 已有的方法在使用的时候需要对原有的方法进行改进,有两种方法:
- 修改原有的方法来适应: 这样违反了"对扩展开放,对修改关闭"的原则 .不推荐使用
- 采用一个代理类调用原有的方法,且对产生的结果进行控制. 即代理模式
- 已有的方法在使用的时候需要对原有的方法进行改进,有两种方法:
- 使用代理模式,可以将功能划分的更加清晰,有助于后期维护
外观模式(Facade)
- 在Spring中,可以将类与类之间的关系配置到配置文件中
- 外观模式: 为了解决类与类之间的依赖关系,将类鱼雷之间的关系放到一个Facade类中,降低类与类之间的耦合度,该模式中没有涉及到接口
- CPU
public class CPU{
public void startup(){
System.out.println("CPU startup!");
}
public void shutdown(){
System.out.println("CPU shutdown!");
}
}
- Memory
public class Memory{
public void startup(){
System.out.println("Memory startup!");
}
public void shutdown(){
System.out.println("Memory shutdown!");
}
}
- Disk
public class Disk{
public void startup(){
System.out.println("Disk startup!");
}
public void shutdown(){
System.out.println("Disk shutdown!");
}
}
- Computer
public class Computer{
private CPU cpu;
private Memory memory;
private Disk disk;
public Computer(){
cpu=new CPU();
memory=new Memory();
disk=new Disk();
}
public void startup(){
System.out.println("Start the computer!");
cpu.startup();
memory.startup();
disk.startup();
System.out.println("Start the computer finished!");
}
public void shutdown(){
System.out.println("Begin to close the computer!");
cpu.shutdown();
memory.shutdown();
disk.shutdown();
System.out.println("Computer closed!");
}
}
-User
public class User{
public static void main(String[] args){
Computer computer=new Computer();
computer.startup();
computer.shutdown();
}
}
- 如果没有Computer类 ,CPU,Memory,Disk之间会互相持有实例,产生关系,这样会造成严重依赖
- 修改一个类,可能会带来其它类的修改
- 有了Computer类,各个类之间的关系就放在类Computer类里,这样就起到解耦的作用
桥接模式(Bridge)
- 桥接模式: 将事物和具体实现分开,二者可以各自独立的变化
- 将抽象化与实现化解耦,使得二者可以独立变化:
- JDBC桥DriverManager:
- JDBC连接数据库的时候,在各个数据库之间进行切换,基本不需要改动太多的代码,甚至一点不用改动
- 原因在于JDBC提供统一接口,每个数据库提供各自实现,用一个叫作数据库驱动的程序来桥接即可
- JDBC桥DriverManager:
- Sourceable
public interface Sourceable{
public void method();
}
- SourceSub1
public class SourceSub1 implements Sourceable{
@Override
public void method(){
System.out.println("This is the first sub!");
}
}
- SourceSub2
public class SourceSub2 implements Sourceable{
@Override
public void method(){
System.out.println("This is the second sub!");
}
}
- 定义一个桥,持有Sourceable的一个实例
public abstract class Bridge{
private Sourceable source;
public void method(){
source.method();
}
public Sourceable getSource(){
return source;
}
public void getSource(Sourceable source){
this.source=source;
}
}
- MyBridge
public class MyBridge extends Bridge{
public void method(){
getSource().method();
}
}
- BridgeTest
public class BridgeTest{
public static void main(String[] args){
Bridge bridge=new MyBridge();
/* 调用第一个对象 */
Sourceable source1=new SourceSub1();
bridge.setSource(source1);
bridge.method();
/* 调用第二个对象 */
Sourceable source2=new SourceSub2();
bridge.setSource(source2);
bridge.method();
}
}
- 通过对Bridge类的调用,实现了对接口Sourceable的实现类SourceSub1和SourceSub2的调用
- 示例: JDBC连接原理
组合模式(Composite)
- 组合模式: 部分-整体模式,在处理类似树形结构的问题时比较方便
- TreeNode
public class TreeNode{
private String name;
private TreeNode parent;
private Vector<TreeNode> children=new Vector<TreeNode>();
public TreeNode(String name){
this.name=name;
}
public String getName(){
return name;
}
public void setName(String name){
this.name=name;
}
public TreeNode getParent(){
return parent;
}
public void setParent(TreeNode parent){
this.parent=parent;
}
/* 添加孩子节点 */
public void add(TreeNode node){
children.add(node);
}
/* 删除孩子节点 */
public void remove(TreeNode node){
children.remove(node);
}
/* 获得孩子节点 */
public Enumeration<TreeNode> getChildren(){
return children.elements();
}
}
- Tree
public class Tree{
TreeNode root=null;
public Tree(String name){
root=new TreeNode(name);
}
public void main(String[] args){
Tree tree=new Tree("A");
TreeNode nodeB=new TreeNode("B");
TreeNode nodeC=new TreeNode("C");
nodeB.add(nodeC);
tree.root.add(nodeB);
System.out.println("Build the tree finished!");
}
}
- 组合模式使用场景:
- 将多个对象组合在一起进行操作
- 常用于表示树形结构中:二叉树
享元模式
- 享元模式: 主要目的是实现对象共享,即共享池
- 当系统中对象多的时候可以减少内存的开销,通常与工厂模式一起使用
- FlyWeightFactory: 负责创建和管理享元单元
- 当一个客户端请求时,工厂需要检查当前对象池中是否有符合条件的对象
- 如果有,就返回已经存在的对象
- 如果没有,就创建一个新对象
- FlyWeight: 超类
- 共享的对象的特点:
- 共享对象有一些共同的属性
- 这些属性对于每个连接来说都是一样的
- 基于共享对象的特点,可以用享元模式处理共享对象:
- 将类似属性作为内部数据
- 其它的属性作为外部数据
- 在方法调用时,当作参数传进来
- 这样可以节省内存空间,减少实例的数量
- 示例: 数据库连接池
public class ConnectionPool{
private Vector<Connection> pool;
/* 公有属性 */
private String url="jdbc:mysql://localhost:3306/test";
private String username="root";
private String password="root";
private String driverClassName="com.mysql.jdbc.Driver";
private int poolSize=100;
private static ConnectionPool instance=null;
Connection conn=null;
/* 构造方法,负责初始化 */
private ConnectionPool(){
pool = new Vector<Connection>(poolSize);
for(int i=0;i<poolSize;i++){
try{
Class.forName(driverClassName);
conn=DriverManager.getConnection(url,user,password);
pool.add(conn);
}catch(ClassNotFoundException e){
e.printStackTrace();
}catch(SQLEXception e){
e.printStackTrace();
}
}
}
/* 返回连接到连接池 */
public sysnchronized void release(){
pool.add(conn);
}
/* 返回连接池中的一个数据库 */
public syschronized Connection getConnection(){
if(pool.size()>0){
Connection conn=pool.get(0);
pool.remove(conn);
return conn;
}else{
return null;
}
}
}
- 通过连接池的连接,实现数据库连接的共享:
- 不需要每一次重新创建连接,节省数据库重新创建的开销,提升了系统系能
行为型模式
- 11种行为模式的关系:
- 第一类: 通过父类与子类的关系进行实现
- 第二类: 通过两个类之间的关系进行实现
- 第三类: 通过类的状态进行实现
- 第四类: 通过中间类进行实现
策略模式(Strategy)
- 策略模式:
- 定义了一系列算法,并将每个算法封装起来,可以相互替换,算法的变化不会影响到使用算法的用户
- 设计一个接口,为一系列实现类提供统一的方法,多个实现类实现该接口
- 设计一个抽象类(选用,作为辅助类),提供辅助函数
- ICalculator接口提供统一的方法
- AbstractCalculator是抽象辅助类,提供辅助方法
- ICalculator
public interface ICalculator{
public int calculate(String exp);
}
- AbstractCalculator
public abstract class AbstractCalcuator{
public int[] split(String exp,String opt){
String array[]=exp.split(opt);
int arrayInt[]=new int[2];
arrayInt[0]=Integer.parseInt(array[0]);
arrayInt[1]=Integer.parseInt(array[1]);
return arrayInt;
}
}
- Plus
public class Plus extends AbstractCalculator implements ICalculator{
@Override
public int calculate(String exp){
int arrayInt[]=split(exp,"\\+");
return arrayInt[0]+arrayInt[1];
}
}
- Minus
public class Minus extends AbstractCalculator implements ICalculator{
@Override
public int calculate(String exp){
int arrayInt[]=split(exp,"-");
return arrayInt[0]-arrayInt[1];
}
}
- Multiply
public class Multiply extends AbstractCalculator implements ICalculator{
@Override
public int calculate(String exp){
int arrayInt[]=split(exp,"\\*");
return arrayInt[0]*arrayInt[1];
}
}
- StrategyTest
public class StrategyTest{
public static void mian(String[] args){
String exp="2+8";
ICalculator cal=new Plus();
int result=cal.calculate(exp);
System.out.println(result);
}
}
- 策略模式的决定权在于用户:
- 系统本身提供不同算法的实现
- 新增或删除算法
- 对各种算法做封装
- 策略模式多用在算法决策系统中,外部用户只需要决定使用哪一个算法即可
模板方法模式(Template Method)
- 模板方法模式:
- 一个抽象类中,定义一个主方法
- 再定义无数个方法,可以是抽象的,也可以是实际的方法
- 定义一个子类,继承抽象类,重写抽象类中的方法
- 通过调用抽象类,实现对子类的调用
- AbstractCalculator类中定义一个主方法calculate()
- calculate() 调用split() 等
- Plus和Minus分别继承AbstractCalculator类
- 通过对AbstractCalculator的调用实现对子类的调用
public abstract class AbstractCalculator{
/* 主方法,实现对本类的其它方法的调用 */
public final int calculate(String exp,String opt){
int array[]=split(exp,opt)
return calculate(array[0],array[1]);
}
/* 抽象方法,需要子类进行重写 */
abstract public int calculate(int num1,int num2);
public int[] split(String exp,String opt){
String array[]=exp.split(opt);
int arrayInt[]=new int[2];
arrayInt[0]=Integer.parseInt(array[0]);
arrayInt[1]=Integer.parseInt(array[1]);
return arrayInt;
}
}
- Plus
public class Plus extends AbstractCalculator{
@Override
public int calculate(int num1,int num2){
return num1+num2;
}
}
- StrategyTest
public class StrategyTest{
public static void main(String[] args){
String exp="8+8";
AbstractCalculator cal=new Plus();
int result=cal.calculate(exp,"\\+");
System.out.println(result);
}
}
Test的执行过程:
- 首先将exp和"\ \ +"做参数,调用AbstractCalculator类里的calculate(String,String)方法
- 在calculate(String,String) 里调用同类的split()
- 然后再调用calculate(int,int) 方法
- 从这个方法进入到子类中
- 执行完return num1+num2之后,将值返回到AbstractCalculator类,赋值给result, 打印出来
观察者模式(Observer)
- 观察者模式是类与类之间的关系,不涉及继承
- 观察者模式类似邮件订阅和RSS订阅:
- 当你订阅了该内容,如果后续有更新,会及时接收到通知
- 观察者模式: 当一个对象变化时,依赖该对象的对象都会接收到通知,并且随着变化.对象之间是一对多的关系
- MySubject类是主对象
- Observer1和Observer2是依赖于MySubject的对象
- 当MySubject变化时 ,Observer1和Observer2必然变化
- AbstractSubject类中定义者需要监控的对象列表,可以对这些对象进行修改:增加或者删除被监控对象
- 当MySubject变化时 ,AbstractSubject类负责通知在列表内存在的对象
- Observer
public interface Observer{
public void update();
}
- Observer1
public class Observer1 implements Observer{
@Override
public void update(){
System.out.println("Observer1 has received!");
}
}
- Observer2
public class Observer2 implements Observer{
@Override
public void update(){
System.out.println("Observer2 has received!");
}
}
- Subject
public interface Subject{
/* 增加观察者 */
public void add(Observer observer);
/* 删除观察者 */
public void del(Observer observer);
/* 通知所有观察者 */
public void notifyObservers();
/* 自身的操作 */
public void operation();
}
- AbstractSubject
public abstract class AbstractSubject implements Subject{
private Vector<Observer> vector=new Vector<Observer>();
@Override
public void add(Observer observer){
vector.add(observer);
}
@Override
public void del(Observer observer){
vector.remove(observer);
}
@Override
public void notifyObservers(){
Enumeration<Observer> enumo=vector.elements();
while(enumo.hasMoreElements()){
enumo.nextElement().update();
}
}
}
- MySubject
public class MySubject extends AbstractSubject{
@Override
public void operation(){
System.out.println("update self!");
notifyObservers();
}
}
- ObserverTest
public class ObserverTest{
public static void main(String[] args){
Subject sub=new MySubject();
sub.add(new Observer1());
sub.add(new Observer2());
sub.operation();
}
}
- 观察者模式根据关系图,新建项目,使用代码按照总体思路走一遍,这样容易理解观察者模式的思想
迭代子模式(Iterator)
- 迭代子模式是类与类之间的关系,不涉及继承
- 迭代子模式: 顺序访问聚集中的对象. 包含两层意思:
- 聚集对象: 需要遍历的对象
- 迭代器对象: 用于对聚集对象进行遍历访问
- MyCollection中定义了集合的一些操作
- MyIterator中定义了一系列迭代操作,并且持有Collection实例
- Collection
public interface Collection{
public Iterator iterator();
/* 取得集合元素 */
public Object get(int i);
/* 取得集合大小 */
public int size();
}
- Iterator
public interface Iterator{
// 前移
puublic Object previous();
// 后移
public Object next();
public boolean hasNext();
// 取得第一个元素
public Object first();
}
- MyCollection
public class MyCollection implements Collection{
public String string[]={"A","B","C","D","E"};
@Override
public Iterator iterator(){
return new MyIterator(this);
}
@Override
public Object get(int i){
return string[i];
}
@Override
public int size(){
return string.length;
}
}
- MyIterator
public class MyIterator implements Iterator{
private Collection collection;
private int pos=-1;
public MyIterator(Collection collection){
this.collection=collection;
}
@Override
pbulic Object previous(){
if(pos>0){
pos--;
}
return collection.get(pos);
}
@Override
public Object next(){
if(pos<collection.size()-1){
pos++;
}
return collection.get(pos);
}
@Override
public Object hasNext(){
if(pos<collection.size()-1){
return true;
}else{
return false;
}
}
@Override
public Object first(){
pos=0;
return collection.get(pos);
}
}
- Test
public class Test{
Collection collection=new MyCollection();
Iterator it=collection.iterator();
whhile(it.hasNext()){
System.out.println(it.next());
}
}
- JDK中各个类都是这些基本的集合,加上一些设计模式,再加一些优化组合到一起的,只要掌握这些,可以写出自定义的集合类,甚至框架
责任链模式(Chain of Responsibility)
- 类与类之间的关系,不涉及继承
- 责任链模式:
- 有多个对象,每个对象持有对下一个对象的引用,这样形成一条链,直到某个对象决定处理该请求
- 请求发出者并不清楚到底最终哪个对象会处理该请求
- 责任链模式可以实现 :在隐瞒客户端的情况下,对系统进行动态调整
- Abstracthandler类提供了get和set方法,方便MyHandler类设置和修改引用对象
- MyHandler类是核心,实例化后生成一系列相互持有的对象,构成一条链
- Handler
public interface Handler{
public void operator();
}
- AbstractHandler
public abstract class AbstractHandler{
private Handler handler;
private Handler getHandler(){
return handler;
}
private void setHandler(Handler handler){
this.handler=handler;
}
}
- MyHandler
public class MyHandler extends AbstractHandler implements Handler{
private String name;
public MyHandler(String name){
this.name=name;
}
@Override
public void operator(){
System.out.println(name+"deal!");
if(getHandler()!=null){
getHandler().operator();
}
}
}
- Test
public class Test{
public static void main(String[] args){
MyHandler h1=new MyHandler("h1");
MyHanlder h2=new MyHandler("h2");
MyHandler h3=new MyHandler("h3");
h1.setHandler(h2);
h2.setHandler(h3);
h1.operator();
}
}
- 链接上的请求可以是一条链,可以是一个树,还可以是一个环
- 模式本身不受这个约束,需要自定义实现
- 在同一个时刻,命令只允许由一个对象传给另一个对象,不允许传给多个对象
命令模式(Command)
- 类与类之间的关系,不涉及继承
- 命令模式理解示例:
- 司令员的作用是: 发出口令
- 口令经过传递,传到士兵耳中,士兵去执行
- 这个过程好在:司令,口令,士兵三者相互解藕
- 任何一方都不用去依赖其它方,只需要做好自身的事即可
- 司令员要的是结果,不会去关注士兵到底怎么实现的
- Invoker是调用者(司令员)
- Receiver是被调用者(士兵)
- MyCommand是命令,实现了Command接口,持有接收对象
- Command
public interface Command{
public void exe();
}
- MyCommand
public class MyCommand implements Command{
private Receiver receiver;
public MyCommand(Receiver receiver){
this.receiver=receiver;
}
@Override
public void exe(){
receiver.action();
}
}
- Receiver
public class Receiver{
public void action(){
System.out.println("Command Received!");
}
}
- Invoker
public class Invoker{
private Command command;
public Invoker(Command command){
this.command=command;
}
public void action(){
command.exe();
}
}
- Test
public class Test{
public static void main(String[] args){
Receiver receiver=new Receiver();
Command cmd=new MyCommand(receiver);
Invoker Invoker=new Invoker(cmd);
invoker.action();
}
}
- 命令模式的目的: 达到命令的发出者和执行者之间的解耦,实现请求和执行分开
- Struts其实就是一种将请求和呈现分离的技术,使用了命令模式的设计思想
备忘录模式(Memento)
- 备忘录模式 :主要目的是保存一个对象的某个状态,以便在适当的时候恢复对象
- 备忘录模式理解:
- 假设有原始类A,A中有各种属性,A可以决定需要备份的属性
- 备忘录类B用来存储A的一些内部状态
- 类C用来存储备忘录,并且只能存储,不能进行修改等操作
- Original类是原始类,里面有需要保存的属性value及创建一个备忘录类,用来保存value值
- Memento类是备忘录类
- Storage类是存储备忘录的类,持有Memento类的实例
- Original
public class Original{
private String value;
private String getValue(){
return value;
}
private void setValue(String value){
this.value=value;
}
public Original(String value){
this.value=value;
}
public Memento createMemento(){
return new Memento(value);
}
public void restoreMemento(Memento memento){
this.value=memento.getValue();
}
}
- Memento
public class Memento{
private String value;
public Memento(String value){
this.value=value;
}
public String getValue(){
return value;
}
public void setValue(String value){
this.value=value;
}
}
- Storage
public class Storage{
private Memento memento;
public Storage(Memento memento){
this.memento=memento;
}
public Memento getMemento(){
return memento;
}
public void setMemento(Memento memento){
this.memento=memento;
}
}
- Test
public class Test{
public static void main(String[] args){
// 创建原始类
Original original=new Original("egg");
// 创建备忘录
Storage storage=new Storage(original.createMemento());
// 修改原始类的状态
System.out.println("初始状态为:"+original.getValue());
original.setValue("bulk");
System.out.println("修改后的状态:"+original.getValue());
// 恢复原始类的状态
original.restoreMemento(storage.getMemento());
System.out.println("恢复后的状态为:"+original.getValue());
}
}
- 新建原始类时 ,value被初始化为egg, 后经过修改,将value值修改为bulk, 最后进行恢复状态,结果成功恢复
状态模式(State)
- 状态模式 :当对象的状态改变时,同时改变对象的行为
- 状态模式理解示例:
- QQ有几种不同的状态:在线,隐身,忙碌等
- 每个状态对应不同的操作,而且好友也能看到相关的状态
- 状态模式包括两层含义:
- 可以通过改变状态来获得不同的行为
- 对象状态的变化可以被发现
- State类是个状态类
- Context类可以实现切换
- State
public class State{
private String value;
private String getValue(){
return value;
}
private void setValue(String value){
this.value=value;
}
public void method1(){
System.out.println("Execute the first opt!");
}
public void method2(){
System.out.println("Execute the second opt!");
}
}
- Context
public class Context{
private State state;
private Context(State state){
this.state=state;
}
public State getState(){
return state;
}
public void setState(){
this.state=state;
}
public void method(){
if(state.getValue().equals("state1")){
state.method1();
}else if(state.getValue().equals("state2")){
state.method2();
}
}
}
- Test
public class Test{
public static void main(String[] args){
State state=new State();
Context context=new Context(state);
// 设置第一种状态
state.setValue("state1");
context.method();
// 设置第二种状态
state.setValue("state2");
context.method();
}
}
- 状态模式的应用场景十分广泛:在做网站的时候,希望根据对象的属性,区别一些功能等,比如说权限控制等等
访问者模式(Visitor)
- 访问者模式将数据结构和作用于结构上的操作解耦,使得操作集合可以相对自由地进行演化
- 访问者模式适用于数据结构相对稳定,算法容易变化的系统:
- 访问者模式使得算法操作增加变得更加容易
- 若系统数据结构对象易于变化,经常有新的对象增加进来,则不适合使用访问者模式
- 访问者模式的特点:
- 优点: 增加操作容易
- 增加操作意味着增加新的访问者
- 访问者模式将有关行为集中到一个访问者对象中, 这些行为的改变不影响系统数据结构
- 缺点: 增加新的数据结构很困难
- 优点: 增加操作容易
- 访问者模式 :是一种分离对象数据结构和行为的方法,通过这种分离,可以达到为一个被访问者动态添加新的操作而无需做任何修改的效果
- 一个Visitor类,存放要访问的对象
- Subject类中有accept方法,接收将要访问的对象 ,getSubject() 获取将要被访问的属性
- Visitor
public interface Visitor{
public void visit(Subject sub);
}
- MyVisitor
public class MyVisitor implements Visitor{
@Override
public void visit(Subject sub){
System.out.println("visit the subject:"+sub.getSubject());
}
}
- Subject
public interface Subject{
public void accept(Visitor visitor);
public String getSubject();
}
- MySubject
public class MySubject implements Subject{
@Override
public void accept(Visitor visitor){
visitor.visit(this);
}
@Override
public String getSubject(){
return "love";
}
}
- Test
public class Test{
public static void main(String[] args){
Visitor visitor=new MyVisitor();
Subject sub=new MySubject();
sub.accept(visitor);
}
}
- 访客模式适用场景:
- 如果要为一个现有的类增加新功能:
- 新功能是否会与现有功能出现兼容性问题
- 以后会不会还有新功能需要添加
- 如果类不允许修改代码怎么处理
- 这些问题最好的解决办法就是访客模式
- 访问者模式适用于数据结构相对稳定的系统,将数据结构和算法解耦
- 如果要为一个现有的类增加新功能:
中介者模式(Mediator)
- 中介者模式是用来降低类与类之间的耦合的:
- 类与类之间有依赖关系的话,不利于功能的拓展和维护
- 因为只要修改一个对象,其它关联的对象都要进行修改
- 中介者模式: 只需要关心Mediator类的关系,具体类与类之间的关系及调度交给Mediator, 与Spring容器的作用类似
- User类统一接口
- User1和User2分别是不同的对象:
- 二者之间有关联
- 如果不采用中介者模式.则需要二者相互持有引用,这样二者的耦合度很高
- 为了解耦,引入了Mediator类,提供统一接口
- MyMediator为实现类:
- 持有User1和User2的实例,用来实现对User1和User2的控制
- 这样User1和User2两个对象就可以相互独立,只需保持与Mediator之间的关系就可以
- Mediator类用来维护
- Mediator
public interface Mediator{
public void createMediator();
public void workAll();
}
- MyMediator
public class MyMediator implements Mediator{
private User user1;
private User user2;
public User getUser1(){
return user1;
}
public User getUser2(){
return user2;
}
@Override
public void createMediator(){
user1=new User1(this);
user2=new User2(this);
}
@Override
public void workAll(){
user1.work();
user2.work();
}
}
- User
public abstract class User{
private Mediator mediator;
public Mediator getMediator(){
return mediator;
}
public User(Mediator mediator){
this.mediator=mediator;
}
public abstract void work();
}
- User1
public class User1 extends User{
public User1(Mediator mediator){
super(mediator);
}
@Override
public void work(){
System.out.println("user1 exe!");
}
}
- User2
public User2 extends User{
public User2(Mediator mediator){
super(mediator);
}
@Override
public void work(){
System.out.println("user2 exe!");
}
}
- Test
public class Test{
public static void main(String[] args){
Mediator mediator=new MyMediator();
mediator.createMediator();
mediator.workAll();
}
}
解释器模式(Interpreter)
- 解释器模式一般主要应用在OOP开发中的编译器开发中,适用面比较窄
- Context类是一个上下文环境类
- Plus和Minus分别是计算的实现
- Expression
public interface Expression{
public int interpret(Context context);
}
- Plus
public class Plus implements Expression{
@Override
public int interpret(Context context){
return context.getNum1()+context.getNum2();
}
}
- Minus
public class Minus implements Expression{
@Override
public void interpret(Context context){
return context.getNum1()-context.getNum2();
}
}
- Context
public class Context{
private int num1;
private int num2;
public Context(int num1,int num2){
this.num1=num1;
this.num2=num2;
}
public int getNum1(){
return num1;
}
public void setNum1(int num1){
this.num1=num1;
}
public int getNum2(){
return num2;
}
public void setNum2(int num2){
this.num2=num2;
}
}
- Test
public class Test{
public static void main(String[] args){
// 计算 9+2-8
int result=new Minus().interpret((new Context(new Plus().interpret(new Context(9,2)),8)));
System.out.println(result);
}
}
- 解释器模式是来用作各种各样的解释器