Java 之 23种设计模式解析

shkstart

贡献于2014-09-24

字数:0 关键词: Java开发 Java

“玩转”Java 系列 ————————————————————————————— 1 【更多 Java – Android 资料下载,可访问尚硅谷(中国)官网 www.atguigu.com 下载区】 题目:Java 之 23 种设计模式解析 一、设计模式概述 总体来说设计模式分为三大类: 创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模 式、原型模式。 结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥 接模式、组合模式、享元模式。 行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模 式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、 解释器模式。 具体如下: 其中创建型有: 一、Singleton,单例模式:保证一个类只有一个实例,并提供一个访问它的 全局访问点 二、Abstract Factory,抽象工厂:提供一个创建一系列相关或相互依赖对象 的接口,而无须指定它们的具体类。 三、Factory Method,工厂方法:定义一个用于创建对象的接口,让子类决 定实例化哪一个类,Factory Method 使一个类的实例化延迟到了子类。 四、Builder,建造模式:将一个复杂对象的构建与他的表示相分离,使得同 样的构建过程可以创建不同的表示。 五、Prototype,原型模式:用原型实例指定创建对象的种类,并且通过拷贝 这些原型来创建新的对象。 行为型有: 六、Iterator,迭代器模式:提供一个方法顺序访问一个聚合对象的各个元素, 而又不需要暴露该对象的内部表示。 七、Observer,观察者模式:定义对象间一对多的依赖关系,当一个对象的 状态发生改变时,所有依赖于它的对象都得到通知自动更新。 八、Template Method,模板方法:定义一个操作中的算法的骨架,而将一 些步骤延迟到子类中,TemplateMethod 使得子类可以不改变一个算法的结构即 可以重定义该算法得某些特定步骤。 九、Command,命令模式:将一个请求封装为一个对象,从而使你可以用 不同的请求对客户进行参数化,对请求排队和记录请求日志,以及支持可撤销的 操作。 十、State,状态模式:允许对象在其内部状态改变时改变他的行为。对象看 起来似乎改变了他的类。 十一、Strategy,策略模式:定义一系列的算法,把他们一个个封装起来, 并使他们可以互相替换,本模式使得算法可以独立于使用它们的客户。 十二、China of Responsibility,职责链模式:使多个对象都有机会处理请求, 从而避免请求的送发者和接收者之间的耦合关系 十三、Mediator,中介者模式:用一个中介对象封装一些列的对象交互。 十四、Visitor,访问者模式:表示一个作用于某对象结构中的各元素的操作, 它使你可以在不改变各元素类的前提下定义作用于这个元素的新操作。 “玩转”Java 系列 ————————————————————————————— 2 【更多 Java – Android 资料下载,可访问尚硅谷(中国)官网 www.atguigu.com 下载区】 十五、Interpreter,解释器模式:给定一个语言,定义他的文法的一个表示, 并定义一个解释器,这个解释器使用该表示来解释语言中的句子。 十六、Memento,备忘录模式:在不破坏对象的前提下,捕获一个对象的内 部状态,并在该对象之外保存这个状态。 结构型有: 十七、Composite,组合模式:将对象组合成树形结构以表示部分整体的关 系,Composite 使得用户对单个对象和组合对象的使用具有一致性。 十八、Facade,外观模式:为子系统中的一组接口提供一致的界面,fa?ade 提供了一高层接口,这个接口使得子系统更容易使用。 十九、Proxy,代理模式:为其他对象提供一种代理以控制对这个对象的访 问 二十、Adapter,适配器模式:将一类的接口转换成客户希望的另外一个接口, Adapter 模式使得原本由于接口不兼容而不能一起工作那些类可以一起工作。 二十一、Decrator,装饰模式:动态地给一个对象增加一些额外的职责,就 增加的功能来说,Decorator 模式相比生成子类更加灵活。 二十二、Bridge,桥模式:将抽象部分与它的实现部分相分离,使他们可以 独立的变化。 二十三、Flyweight,享元模式 其实还有两类:并发型模式和线程池模式。用一个图片来整体描述一下: “玩转”Java 系列 ————————————————————————————— 3 【更多 Java – Android 资料下载,可访问尚硅谷(中国)官网 www.atguigu.com 下载区】 二、设计模式的六大原则 总原则:开闭原则(Open Close Principle) 开闭原则就是说对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的 代码,而是要扩展原有代码,实现一个热插拔的效果。所以一句话概括就是:为了使程序的 扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类等,后面的 具体设计中我们会提到这点。 1、单一职责原则 不要存在多于一个导致类变更的原因,也就是说每个类应该实现单一的职责,如若不然,就 应该把类拆分。 2、里氏替换原则(Liskov Substitution Principle) 里氏代换原则(Liskov Substitution Principle LSP)面向对象设计的基本原则之一。 里氏代换 原则中说,任何基类可以出现的地方,子类一定可以出现。 LSP 是继承复用的基石,只有 当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类 也能够在基类的基础上增加新的行为。里氏代换原则是对“开-闭”原则的补充。实现“开-闭” 原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现,所以里氏代 换原则是对实现抽象化的具体步骤的规范。—— From Baidu 百科 历史替换原则中,子类对父类的方法尽量不要重写和重载。因为父类代表了定义好的结构, 通过这个规范的接口与外界交互,子类不应该随便破坏它。 3、依赖倒转原则(Dependence Inversion Principle) 这个是开闭原则的基础,具体内容:面向接口编程,依赖于抽象而不依赖于具体。写代码时 用到具体类时,不与具体类交互,而与具体类的上层接口交互。 4、接口隔离原则(Interface Segregation Principle) 这个原则的意思是:每个接口中不存在子类用不到却必须实现的方法,如果不然,就要将接 口拆分。使用多个隔离的接口,比使用单个接口(多个接口方法集合到一个的接口)要好。 5、迪米特法则(最少知道原则)(Demeter Principle) “玩转”Java 系列 ————————————————————————————— 4 【更多 Java – Android 资料下载,可访问尚硅谷(中国)官网 www.atguigu.com 下载区】 就是说:一个类对自己依赖的类知道的越少越好。也就是说无论被依赖的类多么复杂,都应 该将逻辑封装在方法的内部,通过 public 方法提供给外部。这样当被依赖的类变化时,才 能最小的影响该类。 最少知道原则的另一个表达方式是:只与直接的朋友通信。类之间只要有耦合关系,就叫朋 友关系。耦合分为依赖、关联、聚合、组合等。我们称出现为成员变量、方法参数、方法返 回值中的类为直接朋友。局部变量、临时变量则不是直接的朋友。我们要求陌生的类不要作 为局部变量出现在类中。 6、合成复用原则(Composite Reuse Principle) 原则是尽量首先使用合成/聚合的方式,而不是使用继承。 三、Java 的 23 中设计模式 A、创建模式 从这一块开始,我们详细介绍 Java 中 23 种设计模式的概念,应用场景等情况,并结合他 们的特点及设计模式的原则进行分析。 首先,简单工厂模式不属于 23 中涉及模式,简单工厂一般分为:普通简单工厂、多方法简 单工厂、静态方法简单工厂。 0、简单工厂模式 简单工厂模式模式分为三种: 01、普通 就是建立一个工厂类,对实现了同一接口的一些类进行实例的创建。首先看下关系图: “玩转”Java 系列 ————————————————————————————— 5 【更多 Java – Android 资料下载,可访问尚硅谷(中国)官网 www.atguigu.com 下载区】 举例如下:(我们举一个发送邮件和短信的例子) 首先,创建二者的共同接口: [java] view plainc opy 1. public interface Sender { 2. public void Send(); 3. } 其次,创建实现类: [java] view plainc opy 1. public class MailSender implements Sender { 2. @Override 3. public void Send() { 4. System.out.println("this is mailsender!"); 5. } 6. } [java] view plainc opy 1. public class SmsSender implements Sender { 2. 3. @Override 4. public void Send() { 5. System.out.println("this is sms sender!"); 6. } 7. } 最后,建工厂类: [java] view plainc opy 1. public class SendFactory { “玩转”Java 系列 ————————————————————————————— 6 【更多 Java – Android 资料下载,可访问尚硅谷(中国)官网 www.atguigu.com 下载区】 2. 3. public Sender produce(String type) { 4. if ("mail".equals(type)) { 5. return new MailSender(); 6. } else if ("sms".equals(type)) { 7. return new SmsSender(); 8. } else { 9. System.out.println("请输入正确的类型!"); 10. return null; 11. } 12. } 13. } 我们来测试下: 1. public class FactoryTest { 2. 3. public static void main(String[] args) { 4. SendFactory factory = new SendFactory(); 5. Sender sender = factory.produce("sms"); 6. sender.Send(); 7. } 8. } 输出:this is sms sender! 02、多个方法 是对普通工厂方法模式的改进,在普通工厂方法模式中,如果传递的字符串出错,则不能正 确创建对象,而多个工厂方法模式是提供多个工厂方法,分别创建对象。关系图: 将上面的代码做下修改,改动下 SendFactory 类就行,如下: “玩转”Java 系列 ————————————————————————————— 7 【更多 Java – Android 资料下载,可访问尚硅谷(中国)官网 www.atguigu.com 下载区】 [java] view plainc opypublic class SendFactory { public Sender produceMail(){ 1. return new MailSender(); 2. } 3. 4. public Sender produceSms(){ 5. return new SmsSender(); 6. } 7. } 测试类如下: [java] view plainc opy 1. public class FactoryTest { 2. 3. public static void main(String[] args) { 4. SendFactory factory = new SendFactory(); 5. Sender sender = factory.produceMail(); 6. sender.Send(); 7. } 8. } 输出:this is mailsender! 03、多个静态方法 将上面的多个工厂方法模式里的方法置为静态的,不需要创建实例,直接调用即可。 [java] view plainc opy 1. public class SendFactory { 2. 3. public static Sender produceMail(){ 4. return new MailSender(); 5. } 6. 7. public static Sender produceSms(){ 8. return new SmsSender(); 9. } 10. } [java] view plainc opy 1. public class FactoryTest { 2. 3. public static void main(String[] args) { 4. Sender sender = SendFactory.produceMail(); 5. sender.Send(); 6. } 7. } 输出:this is mailsender! “玩转”Java 系列 ————————————————————————————— 8 【更多 Java – Android 资料下载,可访问尚硅谷(中国)官网 www.atguigu.com 下载区】 总体来说,工厂模式适合:凡是出现了大量的产品需要创建,并且具有共同的接口时,可以 通过工厂方法模式进行创建。在以上的三种模式中,第一种如果传入的字符串有误,不能正 确创建对象,第三种相对于第二种,不需要实例化工厂类,所以,大多数情况下,我们会选 用第三种——静态工厂方法模式。 1、工厂方法模式(Factory Method) 简单工厂模式有一个问题就是,类的创建依赖工厂类,也就是说,如果想要拓展程序,必须 对工厂类进行修改,这违背了闭包原则,所以,从设计角度考虑,有一定的问题,如何解决? 就用到工厂方法模式,创建一个工厂接口和创建多个工厂实现类,这样一旦需要增加新的功 能,直接增加新的工厂类就可以了,不需要修改之前的代码。 请看例子: [java] view plainc opy 1. public interface Sender { 2. public void Send(); 3. } 两个实现类: [java] view plainc opy 1. public class MailSender implements Sender { 2. @Override 3. public void Send() { 4. System.out.println("this is mailsender!"); 5. } 6. } [java] view plainc opy 1. public class SmsSender implements Sender { “玩转”Java 系列 ————————————————————————————— 9 【更多 Java – Android 资料下载,可访问尚硅谷(中国)官网 www.atguigu.com 下载区】 2. 3. @Override 4. public void Send() { 5. System.out.println("this is sms sender!"); 6. } 7. } 两个工厂类: [java] view plainc opy 1. public class SendMailFactory implements Provider { 2. 3. @Override 4. public Sender produce(){ 5. return new MailSender(); 6. } 7. } [java] view plainc opy 1. public class SendSmsFactory implements Provider{ 2. 3. @Override 4. public Sender produce() { 5. return new SmsSender(); 6. } 7. } 在提供一个接口: [java] view plainc opy 1. public interface Provider { 2. public Sender produce(); 3. } 测试类: [java] view plainc opy 1. public class Test { 2. 3. public static void main(String[] args) { 4. Provider provider = new SendMailFactory(); 5. Sender sender = provider.produce(); 6. sender.Send(); 7. } 8. } 其实这个模式的好处就是,如果你现在想增加一个功能:发及时信息,则只需做一个实现类, 实现 Sender 接口,同时做一个工厂类,实现 Provider 接口,就 OK 了,无需去改动现成的 代码。这样做,拓展性较好! “玩转”Java 系列 ————————————————————————————— 10 【更多 Java – Android 资料下载,可访问尚硅谷(中国)官网 www.atguigu.com 下载区】 2、抽象工厂模式 工厂方法模式和抽象工厂模式不好分清楚,他们的区别如下: 工厂方法模式: 一个抽象产品类,可以派生出多个具体产品类。 一个抽象工厂类,可以派生出多个具体工厂类。 每个具体工厂类只能创建一个具体产品类的实例。 抽象工厂模式: 多个抽象产品类,每个抽象产品类可以派生出多个具体产品类。 一个抽象工厂类,可以派生出多个具体工厂类。 每个具体工厂类可以创建多个具体产品类的实例,也就是创建的是一个产品线下的多个产 品。 区别: 工厂方法模式只有一个抽象产品类,而抽象工厂模式有多个。 工厂方法模式的具体工厂类只能创建一个具体产品类的实例,而抽象工厂模式可以创建多 个。 工厂方法创建 "一种" 产品,他的着重点在于"怎么创建",也就是说如果你开发,你的大量 代码很可能围绕着这种产品的构造,初始化这些细节上面。也因为如此,类似的产品之间有 很多可以复用的特征,所以会和模版方法相随。 抽象工厂需要创建一些列产品,着重点在于"创建哪些"产品上,也就是说,如果你开发,你 的主要任务是划分不同差异的产品线,并且尽量保持每条产品线接口一致,从而可以从同一 个抽象工厂继承。 对于 java 来说,你能见到的大部分抽象工厂模式都是这样的: ---它的里面是一堆工厂方法,每个工厂方法返回某种类型的对象。 比如说工厂可以生产鼠标和键盘。那么抽象工厂的实现类(它的某个具体子类)的对象都可 以生产鼠标和键盘,但可能工厂 A 生产的是罗技的键盘和鼠标,工厂 B 是微软的。 “玩转”Java 系列 ————————————————————————————— 11 【更多 Java – Android 资料下载,可访问尚硅谷(中国)官网 www.atguigu.com 下载区】 这样 A 和 B 就是工厂,对应于抽象工厂; 每个工厂生产的鼠标和键盘就是产品,对应于工厂方法; 用了工厂方法模式,你替换生成键盘的工厂方法,就可以把键盘从罗技换到微软。但是用了 抽象工厂模式,你只要换家工厂,就可以同时替换鼠标和键盘一套。如果你要的产品有几十 个,当然用抽象工厂模式一次替换全部最方便(这个工厂会替你用相应的工厂方法) 所以说抽象工厂就像工厂,而工厂方法则像是工厂的一种产品生产线 3、单例模式(Singleton) 单例对象(Singleton)是一种常用的设计模式。在 Java 应用中,单例对象能保证在一个 JVM 中,该对象只有一个实例存在。这样的模式有几个好处: 1、某些类创建比较频繁,对于一些大型的对象,这是一笔很大的系统开销。 2、省去了 new 操作符,降低了系统内存的使用频率,减轻 GC压力。 3、有些类如交易所的核心交易引擎,控制着交易流程,如果该类可以创建多个的话,系统完全 乱了。(比如一个军队出现了多个司令员同时指挥,肯定会乱成一团),所以只有使用单例模式, 才能保证核心交易服务器独立控制整个流程。 首先我们写一个简单的单例类: [java] view plainc opy 1. public class Singleton { 2. 3. /* 持有私有静态实例,防止被引用,此处赋值为 null,目的是实现延迟加载 */ 4. private static Singleton instance = null; 5. 6. /* 私有构造方法,防止被实例化 */ 7. private Singleton() { 8. } 9. 10. /* 静态工程方法,创建实例 */ 11. public static Singleton getInstance() { 12. if (instance == null) { 13. instance = new Singleton(); 14. } “玩转”Java 系列 ————————————————————————————— 12 【更多 Java – Android 资料下载,可访问尚硅谷(中国)官网 www.atguigu.com 下载区】 15. return instance; 16. } 17. 18. /* 如果该对象被用于序列化,可以保证对象在序列化前后保持一致 */ 19. public Object readResolve() { 20. return instance; 21. } 22. } 这个类可以满足基本要求,但是,像这样毫无线程安全保护的类,如果我们把它放入多线程 的环境下,肯定就会出现问题了,如何解决?我们首先会想到对 getInstance 方法加 synchronized 关键字,如下: [java] view plainc opy 1. public static synchronized Singleton getInstance() { 2. if (instance == null) { 3. instance = new Singleton(); 4. } 5. return instance; 6. } 但是,synchronized 关键字锁住的是这个对象,这样的用法,在性能上会有所下降,因为 每次调用 getInstance(),都要对对象上锁,事实上,只有在第一次创建对象的时候需要加 锁,之后就不需要了,所以,这个地方需要改进。我们改成下面这个: [java] view plainc opy 1. public static Singleton getInstance() { 2. if (instance == null) { 3. synchronized (instance) { 4. if (instance == null) { 5. instance = new Singleton(); 6. } 7. } 8. } 9. return instance; 10. } 似乎解决了之前提到的问题,将 synchronized 关键字加在了内部,也就是说当调用的时候 是不需要加锁的,只有在 instance 为 null,并创建对象的时候才需要加锁,性能有一定的提 升。但是,这样的情况,还是有可能有问题的,看下面的情况:在 Java 指令中创建对象和 赋值操作是分开进行的,也就是说 instance = new Singleton();语句是分两步执行的。但是 JVM 并不保证这两个操作的先后顺序,也就是说有可能 JVM 会为新的 Singleton 实例分配 空间,然后直接赋值给 instance 成员,然后再去初始化这个 Singleton 实例。这样就可能出 错了,我们以 A、B 两个线程为例: “玩转”Java 系列 ————————————————————————————— 13 【更多 Java – Android 资料下载,可访问尚硅谷(中国)官网 www.atguigu.com 下载区】 a>A、B 线程同时进入了第一个 if 判断 b>A 首先进入 synchronized 块,由于 instance 为 null,所以它执行 instance = new Singleton(); c>由于 JVM 内部的优化机制,JVM 先画出了一些分配给 Singleton 实例的空白内存,并赋 值给 instance 成员(注意此时 JVM 没有开始初始化这个实例),然后 A 离开了 synchronized 块。 d>B 进入 synchronized 块,由于 instance 此时不是 null,因此它马上离开了 synchronized 块并将结果返回给调用该方法的程序。 e>此时 B 线程打算使用 Singleton 实例,却发现它没有被初始化,于是错误发生了。 所以程序还是有可能发生错误,其实程序在运行过程是很复杂的,从这点我们就可以看出, 尤其是在写多线程环境下的程序更有难度,有挑战性。我们对该程序做进一步优化: [java] view plainc opy 1. private static class SingletonFactory{ 2. private static Singleton instance = new Singleton(); 3. } 4. public static Singleton getInstance(){ 5. return SingletonFactory.instance; 6. } 实际情况是,单例模式使用内部类来维护单例的实现,JVM 内部的机制能够保证当一个类 被加载的时候,这个类的加载过程是线程互斥的。这样当我们第一次调用 getInstance 的时 候,JVM 能够帮我们保证 instance 只被创建一次,并且会保证把赋值给 instance 的内存初 始化完毕,这样我们就不用担心上面的问题。同时该方法也只会在第一次调用的时候使用互 斥机制,这样就解决了低性能问题。这样我们暂时总结一个完美的单例模式: [java] view plainc opy 1. public class Singleton { 2. 3. /* 私有构造方法,防止被实例化 */ 4. private Singleton() { 5. } 6. 7. /* 此处使用一个内部类来维护单例 */ 8. private static class SingletonFactory { 9. private static Singleton instance = new Singleton(); 10. } 11. 12. /* 获取实例 */ 13. public static Singleton getInstance() { 14. return SingletonFactory.instance; 15. } 16. “玩转”Java 系列 ————————————————————————————— 14 【更多 Java – Android 资料下载,可访问尚硅谷(中国)官网 www.atguigu.com 下载区】 17. /* 如果该对象被用于序列化,可以保证对象在序列化前后保持一致 */ 18. public Object readResolve() { 19. return getInstance(); 20. } 21. } 其实说它完美,也不一定,如果在构造函数中抛出异常,实例将永远得不到创建,也会出错。 所以说,十分完美的东西是没有的,我们只能根据实际情况,选择最适合自己应用场景的实 现方法。也有人这样实现:因为我们只需要在创建类的时候进行同步,所以只要将创建和 getInstance()分开,单独为创建加 synchronized 关键字,也是可以的: [java] view plainc opy 1. public class SingletonTest { 2. 3. private static SingletonTest instance = null; 4. 5. private SingletonTest() { 6. } 7. 8. private static synchronized void syncInit() { 9. if (instance == null) { 10. instance = new SingletonTest(); 11. } 12. } 13. 14. public static SingletonTest getInstance() { 15. if (instance == null) { 16. syncInit(); 17. } 18. return instance; 19. } 20. } 考虑性能的话,整个程序只需创建一次实例,所以性能也不会有什么影响。 补充:采用"影子实例"的办法为单例对象的属性同步更新 [java] view plainc opy 1. public class SingletonTest { 2. 3. private static SingletonTest instance = null; 4. private Vector properties = null; 5. 6. public Vector getProperties() { 7. return properties; 8. } 9. 10. private SingletonTest() { “玩转”Java 系列 ————————————————————————————— 15 【更多 Java – Android 资料下载,可访问尚硅谷(中国)官网 www.atguigu.com 下载区】 11. } 12. 13. private static synchronized void syncInit() { 14. if (instance == null) { 15. instance = new SingletonTest(); 16. } 17. } 18. 19. public static SingletonTest getInstance() { 20. if (instance == null) { 21. syncInit(); 22. } 23. return instance; 24. } 25. 26. public void updateProperties() { 27. SingletonTest shadow = new SingletonTest(); 28. properties = shadow.getProperties(); 29. } 30. } 通过单例模式的学习告诉我们: 1、单例模式理解起来简单,但是具体实现起来还是有一定的难度。 2、synchronized 关键字锁定的是对象,在用的时候,一定要在恰当的地方使用(注意需要 使用锁的对象和过程,可能有的时候并不是整个对象及整个过程都需要锁)。 到这儿,单例模式基本已经讲完了,结尾处,笔者突然想到另一个问题,就是采用类的静态 方法,实现单例模式的效果,也是可行的,此处二者有什么不同? 首先,静态类不能实现接口。(从类的角度说是可以的,但是那样就破坏了静态了。因为接 口中不允许有 static 修饰的方法,所以即使实现了也是非静态的) 其次,单例可以被延迟初始化,静态类一般在第一次加载是初始化。之所以延迟加载,是因 为有些类比较庞大,所以延迟加载有助于提升性能。 再次,单例类可以被继承,他的方法可以被覆写。但是静态类内部方法都是 static,无法被 覆写。 最后一点,单例类比较灵活,毕竟从实现上只是一个普通的 Java 类,只要满足单例的基本 需求,你可以在里面随心所欲的实现一些其它功能,但是静态类不行。从上面这些概括中, 基本可以看出二者的区别,但是,从另一方面讲,我们上面最后实现的那个单例模式,内部 就是用一个静态类来实现的,所以,二者有很大的关联,只是我们考虑问题的层面不同罢了。 两种思想的结合,才能造就出完美的解决方案,就像 HashMap 采用数组+链表来实现一样, 其实生活中很多事情都是这样,单用不同的方法来处理问题,总是有优点也有缺点,最完美 的方法是,结合各个方法的优点,才能最好的解决问题! “玩转”Java 系列 ————————————————————————————— 16 【更多 Java – Android 资料下载,可访问尚硅谷(中国)官网 www.atguigu.com 下载区】 4、建造者模式(Builder) 5、原型模式(Prototype) 原型模式虽然是创建型的模式,但是与工程模式没有关系,从名字即可看出,该模式的思想 就是将一个对象作为原型,对其进行复制、克隆,产生一个和原对象类似的新对象。本小结 会通过对象的复制,进行讲解。在 Java 中,复制对象是通过 clone()实现的,先创建一个原 型类: [java] view plainc opy 1. public class Prototype implements Cloneable { 2. 3. public Object clone() throws CloneNotSupportedException { 4. Prototype proto = (Prototype) super.clone(); 5. return proto; 6. } 7. } 很简单,一个原型类,只需要实现 Cloneable 接口,覆写 clone 方法,此处 clone 方法可以 改成任意的名称,因为 Cloneable 接口是个空接口,你可以任意定义实现类的方法名,如 cloneA 或者 cloneB,因为此处的重点是 super.clone()这句话,super.clone()调用的是 Object 的 clone()方法,而在 Object 类中,clone()是 native 的,具体怎么实现,我会在另一篇文章 中,关于解读 Java 中本地方法的调用,此处不再深究。在这儿,我将结合对象的浅复制和 深复制来说一下,首先需要了解对象深、浅复制的概念: 浅复制:将一个对象复制后,基本数据类型的变量都会重新创建,而引用类型,指向的还是 原对象所指向的。 深复制:将一个对象复制后,不论是基本数据类型还有引用类型,都是重新创建的。简单来 说,就是深复制进行了完全彻底的复制,而浅复制不彻底。 此处,写一个深浅复制的例子: [java] view plainc opy 1. public class Prototype implements Cloneable, Serializable { 2. 3. private static final long serialVersionUID = 1L; 4. private String string; 5. 6. private SerializableObject obj; 7. 8. /* 浅复制 */ 9. public Object clone() throws CloneNotSupportedException { 10. Prototype proto = (Prototype) super.clone(); “玩转”Java 系列 ————————————————————————————— 17 【更多 Java – Android 资料下载,可访问尚硅谷(中国)官网 www.atguigu.com 下载区】 11. return proto; 12. } 13. 14. /* 深复制 */ 15. public Object deepClone() throws IOException, ClassNotFoundException { 16. 17. /* 写入当前对象的二进制流 */ 18. ByteArrayOutputStream bos = new ByteArrayOutputStream(); 19. ObjectOutputStream oos = new ObjectOutputStream(bos); 20. oos.writeObject(this); 21. 22. /* 读出二进制流产生的新对象 */ 23. ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray( )); 24. ObjectInputStream ois = new ObjectInputStream(bis); 25. return ois.readObject(); 26. } 27. 28. public String getString() { 29. return string; 30. } 31. 32. public void setString(String string) { 33. this.string = string; 34. } 35. 36. public SerializableObject getObj() { 37. return obj; 38. } 39. 40. public void setObj(SerializableObject obj) { 41. this.obj = obj; 42. } 43. 44. } 45. 46. class SerializableObject implements Serializable { 47. private static final long serialVersionUID = 1L; 48. } 要实现深复制,需要采用流的形式读入当前对象的二进制输入,再写出二进制数据对应的对 象。 “玩转”Java 系列 ————————————————————————————— 18 【更多 Java – Android 资料下载,可访问尚硅谷(中国)官网 www.atguigu.com 下载区】 B、结构模式(7 种) 我们接着讨论设计模式,上篇文章我讲完了 5 种创建型模式,这章开始,我将讲下 7 种结 构型模式:适配器模式、装饰模式、代理模式、外观模式、桥接模式、组合模式、享元模式。 其中对象的适配器模式是各种模式的起源,我们看下面的图: 6、适配器模式 适配器模式将某个类的接口转换成客户端期望的另一个接口表示,目的是消除由于接口不 匹配所造成的类的兼容性问题。主要分为三类:类的适配器模式、对象的适配器模式、接口 的适配器模式。 01、类的适配器模式 “玩转”Java 系列 ————————————————————————————— 19 【更多 Java – Android 资料下载,可访问尚硅谷(中国)官网 www.atguigu.com 下载区】 核心思想就是:有一个 Source 类,拥有一个方法,待适配,目标接口是 Targetable,通过 Adapter 类,将 Source 的功能扩展到 Targetable 里,看代码: [java] view plainc opy 1. public class Source { 2. 3. public void method1() { 4. System.out.println("this is original method!"); 5. } 6. } [java] view plainc opy 1. public interface Targetable { 2. 3. /* 与原类中的方法相同 */ 4. public void method1(); 5. 6. /* 新类的方法 */ 7. public void method2(); 8. } [java] view plainc opy 1. public class Adapter extends Source implements Targetable { 2. 3. @Override 4. public void method2() { 5. System.out.println("this is the targetable method!"); 6. } 7. } Adapter 类继承 Source 类,实现 Targetable 接口,下面是测试类: [java] view plainc opy “玩转”Java 系列 ————————————————————————————— 20 【更多 Java – Android 资料下载,可访问尚硅谷(中国)官网 www.atguigu.com 下载区】 1. public class AdapterTest { 2. 3. public static void main(String[] args) { 4. Targetable target = new Adapter(); 5. target.method1(); 6. target.method2(); 7. } 8. } 输出: this is original method! this is the targetable method! 这样 Targetable 接口的实现类就具有了 Source 类的功能。 02、对象的适配器模式 基本思路和类的适配器模式相同,只是将 Adapter 类作修改,这次不继承 Source 类,而是 持有 Source 类的实例,以达到解决兼容性的问题。看图: 只需要修改 Adapter 类的源码即可: [java] view plainc opy 1. public class Wrapper implements Targetable { 2. 3. private Source source; 4. 5. public Wrapper(Source source){ 6. super(); 7. this.source = source; “玩转”Java 系列 ————————————————————————————— 21 【更多 Java – Android 资料下载,可访问尚硅谷(中国)官网 www.atguigu.com 下载区】 8. } 9. @Override 10. public void method2() { 11. System.out.println("this is the targetable method!"); 12. } 13. 14. @Override 15. public void method1() { 16. source.method1(); 17. } 18. } 测试类: [java] view plainc opy 1. public class AdapterTest { 2. 3. public static void main(String[] args) { 4. Source source = new Source(); 5. Targetable target = new Wrapper(source); 6. target.method1(); 7. target.method2(); 8. } 9. } 输出与第一种一样,只是适配的方法不同而已。 03、接口的适配器模式 第三种适配器模式是接口的适配器模式,接口的适配器是这样的:有时我们写的一个接口中 有多个抽象方法,当我们写该接口的实现类时,必须实现该接口的所有方法,这明显有时比 较浪费,因为并不是所有的方法都是我们需要的,有时只需要某一些,此处为了解决这个问 题,我们引入了接口的适配器模式,借助于一个抽象类,该抽象类实现了该接口,实现了所 有的方法,而我们不和原始的接口打交道,只和该抽象类取得联系,所以我们写一个类,继 承该抽象类,重写我们需要的方法就行。看一下类图: “玩转”Java 系列 ————————————————————————————— 22 【更多 Java – Android 资料下载,可访问尚硅谷(中国)官网 www.atguigu.com 下载区】 这个很好理解,在实际开发中,我们也常会遇到这种接口中定义了太多的方法,以致于有时 我们在一些实现类中并不是都需要。看代码: [java] view plainc opy 1. public interface Sourceable { 2. 3. public void method1(); 4. public void method2(); 5. } 抽象类 Wrapper2: [java] view plainc opy 1. public abstract class Wrapper2 implements Sourceable{ 2. 3. public void method1(){} 4. public void method2(){} 5. } [java] view plainc opy 1. public class SourceSub1 extends Wrapper2 { 2. public void method1(){ 3. System.out.println("the sourceable interface's first Sub1!"); 4. } 5. } [java] view plainc opy 1. public class SourceSub2 extends Wrapper2 { 2. public void method2(){ 3. System.out.println("the sourceable interface's second Sub2!"); 4. } 5. } “玩转”Java 系列 ————————————————————————————— 23 【更多 Java – Android 资料下载,可访问尚硅谷(中国)官网 www.atguigu.com 下载区】 [java] view plainc opy 1. public class WrapperTest { 2. 3. public static void main(String[] args) { 4. Sourceable source1 = new SourceSub1(); 5. Sourceable source2 = new SourceSub2(); 6. 7. source1.method1(); 8. source1.method2(); 9. source2.method1(); 10. source2.method2(); 11. } 12. } 测试输出: the sourceable interface's first Sub1! the sourceable interface's second Sub2! 达到了我们的效果! 讲了这么多,总结一下三种适配器模式的应用场景: 类的适配器模式:当希望将一个类转换成满足另一个新接口的类时,可以使用类的适配器模 式,创建一个新类,继承原有的类,实现新的接口即可。 对象的适配器模式:当希望将一个对象转换成满足另一个新接口的对象时,可以创建一个 Wrapper 类,持有原类的一个实例,在 Wrapper 类的方法中,调用实例的方法就行。 接口的适配器模式:当不希望实现一个接口中所有的方法时,可以创建一个抽象类 Wrapper, 实现所有方法,我们写别的类的时候,继承抽象类即可。 7、装饰模式(Decorator) 顾名思义,装饰模式就是给一个对象增加一些新的功能,而且是动态的,要求装饰对象和被 装饰对象实现同一个接口,装饰对象持有被装饰对象的实例,关系图如下: “玩转”Java 系列 ————————————————————————————— 24 【更多 Java – Android 资料下载,可访问尚硅谷(中国)官网 www.atguigu.com 下载区】 Source 类是被装饰类,Decorator 类是一个装饰类,可以为 Source 类动态的添加一些功能, 代码如下: [java] view plainc opy 1. public interface Sourceable { 2. public void method(); 3. } [java] view plainc opy 1. public class Source implements Sourceable { 2. 3. @Override 4. public void method() { 5. System.out.println("the original method!"); 6. } 7. } [java] view plainc opy 1. public class Decorator implements Sourceable { 2. 3. private Sourceable source; 4. 5. public Decorator(Sourceable source){ 6. super(); 7. this.source = source; 8. } 9. @Override 10. public void method() { 11. System.out.println("before decorator!"); 12. source.method(); 13. System.out.println("after decorator!"); 14. } 15. } 测试类: [java] view plainc opy “玩转”Java 系列 ————————————————————————————— 25 【更多 Java – Android 资料下载,可访问尚硅谷(中国)官网 www.atguigu.com 下载区】 1. public class DecoratorTest { 2. 3. public static void main(String[] args) { 4. Sourceable source = new Source(); 5. Sourceable obj = new Decorator(source); 6. obj.method(); 7. } 8. } 输出: before decorator! the original method! after decorator! 装饰器模式的应用场景: 1、需要扩展一个类的功能。 2、动态的为一个对象增加功能,而且还能动态撤销。(继承不能做到这一点,继承的功能 是静态的,不能动态增删。) 缺点:产生过多相似的对象,不易排错! 8、代理模式(Proxy) 其实每个模式名称就表明了该模式的作用,代理模式就是多一个代理类出来,替原对象进行 一些操作,比如我们在租房子的时候回去找中介,为什么呢?因为你对该地区房屋的信息掌 握的不够全面,希望找一个更熟悉的人去帮你做,此处的代理就是这个意思。再如我们有的 时候打官司,我们需要请律师,因为律师在法律方面有专长,可以替我们进行操作,表达我 们的想法。先来看看关系图: 根据上文的阐述,代理模式就比较容易的理解了,我们看下代码: [java] view plainc opy “玩转”Java 系列 ————————————————————————————— 26 【更多 Java – Android 资料下载,可访问尚硅谷(中国)官网 www.atguigu.com 下载区】 1. public interface Sourceable { 2. public void method(); 3. } [java] view plainc opy 1. public class Source implements Sourceable { 2. 3. @Override 4. public void method() { 5. System.out.println("the original method!"); 6. } 7. } [java] view plainc opy 1. public class Proxy implements Sourceable { 2. 3. private Source source; 4. public Proxy(){ 5. super(); 6. this.source = new Source(); 7. } 8. @Override 9. public void method() { 10. before(); 11. source.method(); 12. atfer(); 13. } 14. private void atfer() { 15. System.out.println("after proxy!"); 16. } 17. private void before() { 18. System.out.println("before proxy!"); 19. } 20. } 测试类: [java] view plainc opy 1. public class ProxyTest { 2. 3. public static void main(String[] args) { 4. Sourceable source = new Proxy(); 5. source.method(); 6. } 7. 8. } 输出: “玩转”Java 系列 ————————————————————————————— 27 【更多 Java – Android 资料下载,可访问尚硅谷(中国)官网 www.atguigu.com 下载区】 before proxy! the original method! after proxy! 代理模式的应用场景: 如果已有的方法在使用的时候需要对原有的方法进行改进,此时有两种办法: 1、修改原有的方法来适应。这样违反了“对扩展开放,对修改关闭”的原则。 2、就是采用一个代理类调用原有的方法,且对产生的结果进行控制。这种方法就是代理模 式。 使用代理模式,可以将功能划分的更加清晰,有助于后期维护! 9、外观模式(Facade) 外观模式是为了解决类与类之家的依赖关系的,像 spring 一样,可以将类和类之间的关系 配置到配置文件中,而外观模式就是将他们的关系放在一个 Facade 类中,降低了类类之间 的耦合度,该模式中没有涉及到接口,看下类图:(我们以一个计算机的启动过程为例) 我们先看下实现类: [java] view plainc opy 1. public class CPU { 2. 3. public void startup(){ 4. System.out.println("cpu startup!"); 5. } 6. 7. public void shutdown(){ 8. System.out.println("cpu shutdown!"); “玩转”Java 系列 ————————————————————————————— 28 【更多 Java – Android 资料下载,可访问尚硅谷(中国)官网 www.atguigu.com 下载区】 9. } 10. } [java] view plainc opy 1. public class Memory { 2. 3. public void startup(){ 4. System.out.println("memory startup!"); 5. } 6. 7. public void shutdown(){ 8. System.out.println("memory shutdown!"); 9. } 10. } [java] view plainc opy 1. public class Disk { 2. 3. public void startup(){ 4. System.out.println("disk startup!"); 5. } 6. 7. public void shutdown(){ 8. System.out.println("disk shutdown!"); 9. } 10. } [java] view plainc opy 1. public class Computer { 2. private CPU cpu; 3. private Memory memory; 4. private Disk disk; 5. 6. public Computer(){ 7. cpu = new CPU(); 8. memory = new Memory(); 9. disk = new Disk(); 10. } 11. 12. public void startup(){ 13. System.out.println("start the computer!"); 14. cpu.startup(); 15. memory.startup(); 16. disk.startup(); 17. System.out.println("start computer finished!"); 18. } 19. “玩转”Java 系列 ————————————————————————————— 29 【更多 Java – Android 资料下载,可访问尚硅谷(中国)官网 www.atguigu.com 下载区】 20. public void shutdown(){ 21. System.out.println("begin to close the computer!"); 22. cpu.shutdown(); 23. memory.shutdown(); 24. disk.shutdown(); 25. System.out.println("computer closed!"); 26. } 27. } User 类如下: [java] view plainc opy 1. public class User { 2. 3. public static void main(String[] args) { 4. Computer computer = new Computer(); 5. computer.startup(); 6. computer.shutdown(); 7. } 8. } 输出: start the computer! cpu startup! memory startup! disk startup! start computer finished! begin to close the computer! cpu shutdown! memory shutdown! disk shutdown! computer closed! 如果我们没有 Computer 类,那么,CPU、Memory、Disk 他们之间将会相互持有实例,产 生关系,这样会造成严重的依赖,修改一个类,可能会带来其他类的修改,这不是我们想要 看到的,有了 Computer 类,他们之间的关系被放在了 Computer 类里,这样就起到了解耦 的作用,这,就是外观模式! 10、桥接模式(Bridge) 桥接模式就是把事物和其具体实现分开,使他们可以各自独立的变化。桥接的用意是:将抽 象化与实现化解耦,使得二者可以独立变化,像我们常用的 JDBC 桥 DriverManager 一样, JDBC 进行连接数据库的时候,在各个数据库之间进行切换,基本不需要动太多的代码,甚 “玩转”Java 系列 ————————————————————————————— 30 【更多 Java – Android 资料下载,可访问尚硅谷(中国)官网 www.atguigu.com 下载区】 至丝毫不用动,原因就是 JDBC 提供统一接口,每个数据库提供各自的实现,用一个叫做 数据库驱动的程序来桥接就行了。我们来看看关系图: 实现代码: 先定义接口: [java] view plainc opy 1. public interface Sourceable { 2. public void method(); 3. } 分别定义两个实现类: [java] view plainc opy 1. public class SourceSub1 implements Sourceable { 2. 3. @Override 4. public void method() { 5. System.out.println("this is the first sub!"); 6. } 7. } [java] view plainc opy 1. public class SourceSub2 implements Sourceable { 2. 3. @Override 4. public void method() { 5. System.out.println("this is the second sub!"); 6. } 7. } 定义一个桥,持有 Sourceable 的一个实例: [java] view plainc opy 1. public abstract class Bridge { 2. private Sourceable source; 3. 4. public void method(){ “玩转”Java 系列 ————————————————————————————— 31 【更多 Java – Android 资料下载,可访问尚硅谷(中国)官网 www.atguigu.com 下载区】 5. source.method(); 6. } 7. 8. public Sourceable getSource() { 9. return source; 10. } 11. 12. public void setSource(Sourceable source) { 13. this.source = source; 14. } 15. } [java] view plainc opy 1. public class MyBridge extends Bridge { 2. public void method(){ 3. getSource().method(); 4. } 5. } 测试类: [java] view plainc opy 1. public class BridgeTest { 2. 3. public static void main(String[] args) { 4. 5. Bridge bridge = new MyBridge(); 6. 7. /*调用第一个对象*/ 8. Sourceable source1 = new SourceSub1(); 9. bridge.setSource(source1); 10. bridge.method(); 11. 12. /*调用第二个对象*/ 13. Sourceable source2 = new SourceSub2(); 14. bridge.setSource(source2); 15. bridge.method(); 16. } 17. } output: this is the first sub! this is the second sub! 这样,就通过对 Bridge 类的调用,实现了对接口 Sourceable 的实现类 SourceSub1 和 SourceSub2 的调用。接下来我再画个图,大家就应该明白了,因为这个图是我们 JDBC 连 接的原理,有数据库学习基础的,一结合就都懂了。 “玩转”Java 系列 ————————————————————————————— 32 【更多 Java – Android 资料下载,可访问尚硅谷(中国)官网 www.atguigu.com 下载区】 11、组合模式(Composite) 组合模式有时又叫部分-整体模式在处理类似树形结构的问题时比较方便,看看关系图: 直接来看代码: [java] view plainc opy 1. public class TreeNode { 2. 3. private String name; 4. private TreeNode parent; 5. private Vector children = new Vector(); 6. 7. public TreeNode(String name){ 8. this.name = name; 9. } 10. 11. public String getName() { 12. return name; 13. } 14. 15. public void setName(String name) { 16. this.name = name; 17. } “玩转”Java 系列 ————————————————————————————— 33 【更多 Java – Android 资料下载,可访问尚硅谷(中国)官网 www.atguigu.com 下载区】 18. 19. public TreeNode getParent() { 20. return parent; 21. } 22. 23. public void setParent(TreeNode parent) { 24. this.parent = parent; 25. } 26. 27. //添加孩子节点 28. public void add(TreeNode node){ 29. children.add(node); 30. } 31. 32. //删除孩子节点 33. public void remove(TreeNode node){ 34. children.remove(node); 35. } 36. 37. //取得孩子节点 38. public Enumeration getChildren(){ 39. return children.elements(); 40. } 41. } [java] view plainc opy 1. public class Tree { 2. 3. TreeNode root = null; 4. 5. public Tree(String name) { 6. root = new TreeNode(name); 7. } 8. 9. public static void main(String[] args) { 10. Tree tree = new Tree("A"); 11. TreeNode nodeB = new TreeNode("B"); 12. TreeNode nodeC = new TreeNode("C"); 13. 14. nodeB.add(nodeC); 15. tree.root.add(nodeB); 16. System.out.println("build the tree finished!"); 17. } 18. } 使用场景:将多个对象组合在一起进行操作,常用于表示树形结构中,例如二叉树,数等。 “玩转”Java 系列 ————————————————————————————— 34 【更多 Java – Android 资料下载,可访问尚硅谷(中国)官网 www.atguigu.com 下载区】 12、享元模式(Flyweight) 享元模式的主要目的是实现对象的共享,即共享池,当系统中对象多的时候可以减少内存的 开销,通常与工厂模式一起使用。 FlyWeightFactory 负责创建和管理享元单元,当一个客户端请求时,工厂需要检查当前对象 池中是否有符合条件的对象,如果有,就返回已经存在的对象,如果没有,则创建一个新对 象,FlyWeight 是超类。一提到共享池,我们很容易联想到 Java 里面的 JDBC 连接池,想 想每个连接的特点,我们不难总结出:适用于作共享的一些个对象,他们有一些共有的属性, 就拿数据库连接池来说,url、driverClassName、username、password 及 dbname,这些 属性对于每个连接来说都是一样的,所以就适合用享元模式来处理,建一个工厂类,将上述 类似属性作为内部数据,其它的作为外部数据,在方法调用时,当做参数传进来,这样就节 省了空间,减少了实例的数量。 看个例子: 看下数据库连接池的代码: [java] view plainc opy 1. public class ConnectionPool { 2. 3. private Vector pool; 4. 5. /*公有属性*/ “玩转”Java 系列 ————————————————————————————— 35 【更多 Java – Android 资料下载,可访问尚硅谷(中国)官网 www.atguigu.com 下载区】 6. private String url = "jdbc:mysql://localhost:3306/test"; 7. private String username = "root"; 8. private String password = "root"; 9. private String driverClassName = "com.mysql.jdbc.Driver"; 10. 11. private int poolSize = 100; 12. private static ConnectionPool instance = null; 13. Connection conn = null; 14. 15. /*构造方法,做一些初始化工作*/ 16. private ConnectionPool() { 17. pool = new Vector(poolSize); 18. 19. for (int i = 0; i < poolSize; i++) { 20. try { 21. Class.forName(driverClassName); 22. conn = DriverManager.getConnection(url, username, password); 23. pool.add(conn); 24. } catch (ClassNotFoundException e) { 25. e.printStackTrace(); 26. } catch (SQLException e) { 27. e.printStackTrace(); 28. } 29. } 30. } 31. 32. /* 返回连接到连接池 */ 33. public synchronized void release() { 34. pool.add(conn); 35. } 36. 37. /* 返回连接池中的一个数据库连接 */ 38. public synchronized Connection getConnection() { 39. if (pool.size() > 0) { 40. Connection conn = pool.get(0); 41. pool.remove(conn); 42. return conn; 43. } else { 44. return null; 45. } 46. } 47. } “玩转”Java 系列 ————————————————————————————— 36 【更多 Java – Android 资料下载,可访问尚硅谷(中国)官网 www.atguigu.com 下载区】 通过连接池的管理,实现了数据库连接的共享,不需要每一次都重新创建连接,节省了数据 库重新创建的开销,提升了系统的性能! C、关系模式(11 种) 先来张图,看看这 11 中模式的关系: 第一类:通过父类与子类的关系进行实现。 第二类:两个类之间。 第三类:类的状态。 第四类:通过中间类 父类与子类关系 13、策略模式(strategy) 策略模式定义了一系列算法,并将每个算法封装起来,使他们可以相互替换,且算法的变化 不会影响到使用算法的客户。需要设计一个接口,为一系列实现类提供统一的方法,多个实 现类实现该接口,设计一个抽象类(可有可无,属于辅助类),提供辅助函数,关系图如下: “玩转”Java 系列 ————————————————————————————— 37 【更多 Java – Android 资料下载,可访问尚硅谷(中国)官网 www.atguigu.com 下载区】 图中 ICalculator 提供同意的方法, AbstractCalculator 是辅助类,提供辅助方法,接下来,依次实现下每个类: 首先统一接口: [java] view plainc opy 1. public interface ICalculator { 2. public int calculate(String exp); 3. } 辅助类: [java] view plainc opy 1. public abstract class AbstractCalculator { 2. 3. public int[] split(String exp,String opt){ 4. String array[] = exp.split(opt); 5. int arrayInt[] = new int[2]; 6. arrayInt[0] = Integer.parseInt(array[0]); 7. arrayInt[1] = Integer.parseInt(array[1]); 8. return arrayInt; 9. } 10. } 三个实现类: [java] view plainc opy 1. public class Plus extends AbstractCalculator implements ICalculator { 2. 3. @Override 4. public int calculate(String exp) { 5. int arrayInt[] = split(exp,"\\+"); 6. return arrayInt[0]+arrayInt[1]; 7. } 8. } [java] view plainc opy “玩转”Java 系列 ————————————————————————————— 38 【更多 Java – Android 资料下载,可访问尚硅谷(中国)官网 www.atguigu.com 下载区】 1. public class Minus extends AbstractCalculator implements ICalculator { 2. 3. @Override 4. public int calculate(String exp) { 5. int arrayInt[] = split(exp,"-"); 6. return arrayInt[0]-arrayInt[1]; 7. } 8. 9. } [java] view plainc opy 1. public class Multiply extends AbstractCalculator implements ICalculator { 2. 3. @Override 4. public int calculate(String exp) { 5. int arrayInt[] = split(exp,"\\*"); 6. return arrayInt[0]*arrayInt[1]; 7. } 8. } 简单的测试类: [java] view plainc opy 1. public class StrategyTest { 2. 3. public static void main(String[] args) { 4. String exp = "2+8"; 5. ICalculator cal = new Plus(); 6. int result = cal.calculate(exp); 7. System.out.println(result); 8. } 9. } 输出:10 策略模式的决定权在用户,系统本身提供不同算法的实现,新增或者删除算法,对各种算法 做封装。因此,策略模式多用在算法决策系统中,外部用户只需要决定用哪个算法即可。 14、模板方法模式(Template Method) 解释一下模板方法模式,就是指:一个抽象类中,有一个主方法,再定义 1...n 个方法,可 以是抽象的,也可以是实际的方法,定义一个类,继承该抽象类,重写抽象方法,通过调用 抽象类,实现对子类的调用,先看个关系图: “玩转”Java 系列 ————————————————————————————— 39 【更多 Java – Android 资料下载,可访问尚硅谷(中国)官网 www.atguigu.com 下载区】 就是在 AbstractCalculator 类中定义一个主方法 calculate,calculate()调用 spilt()等,Plus 和 Minus 分别继承 AbstractCalculator 类,通过对 AbstractCalculator 的调用实现对子类的 调用,看下面的例子: [java] view plainc opy 1. public abstract class AbstractCalculator { 2. 3. /*主方法,实现对本类其它方法的调用*/ 4. public final int calculate(String exp,String opt){ 5. int array[] = split(exp,opt); 6. return calculate(array[0],array[1]); 7. } 8. 9. /*被子类重写的方法*/ 10. abstract public int calculate(int num1,int num2); 11. 12. public int[] split(String exp,String opt){ 13. String array[] = exp.split(opt); 14. int arrayInt[] = new int[2]; 15. arrayInt[0] = Integer.parseInt(array[0]); 16. arrayInt[1] = Integer.parseInt(array[1]); 17. return arrayInt; 18. } 19. } [java] view plainc opy 1. public class Plus extends AbstractCalculator { 2. 3. @Override 4. public int calculate(int num1,int num2) { 5. return num1 + num2; 6. } 7. } 测试类: [java] view plainc opy “玩转”Java 系列 ————————————————————————————— 40 【更多 Java – Android 资料下载,可访问尚硅谷(中国)官网 www.atguigu.com 下载区】 1. public class StrategyTest { 2. 3. public static void main(String[] args) { 4. String exp = "8+8"; 5. AbstractCalculator cal = new Plus(); 6. int result = cal.calculate(exp, "\\+"); 7. System.out.println(result); 8. } 9. } 我跟踪下这个小程序的执行过程:首先将 exp 和"\\+"做参数,调用 AbstractCalculator 类里 的 calculate(String,String)方法,在 calculate(String,String)里调用同类的 split(),之后再调 用 calculate(int ,int)方法,从这个方法进入到子类中,执行完 return num1 + num2 后,将 值返回到 AbstractCalculator 类,赋给 result,打印出来。正好验证了我们开头的思路。 类之间的关系 15、观察者模式(Observer) 包括这个模式在内的接下来的四个模式,都是类和类之间的关系,不涉及到继承,学的时候 应该 记得归纳,记得本文最开始的那个图。观察者模式很好理解,类似于邮件订阅和 RSS 订阅,当我们浏览一些博客或 wiki 时,经常会看到 RSS 图标,就这的意思是,当你订阅了 该文章,如果后续有更新,会及时通知你。其实,简单来讲就一句话:当一个对象变化时, 其它依赖该对象的对象都会收到通知,并且随着变化!对象之间是一种一对多的关系。先来 看看关系图: “玩转”Java 系列 ————————————————————————————— 41 【更多 Java – Android 资料下载,可访问尚硅谷(中国)官网 www.atguigu.com 下载区】 我解释下这些类的作用:MySubject 类就是我们的主对象,Observer1 和 Observer2 是依赖 于 MySubject 的对象,当 MySubject 变化时,Observer1 和 Observer2 必然变化。 AbstractSubject 类中定义着需要监控的对象列表,可以对其进行修改:增加或删除被监控 对象,且当 MySubject 变化时,负责通知在列表内存在的对象。我们看实现代码: 一个 Observer 接口: [java] view plainc opy 1. public interface Observer { 2. public void update(); 3. } 两个实现类: [java] view plainc opy 1. public class Observer1 implements Observer { 2. 3. @Override 4. public void update() { 5. System.out.println("observer1 has received!"); 6. } 7. } [java] view plainc opy 1. public class Observer2 implements Observer { 2. 3. @Override 4. public void update() { 5. System.out.println("observer2 has received!"); 6. } 7. 8. } Subject 接口及实现类: [java] view plainc opy 1. public interface Subject { 2. 3. /*增加观察者*/ 4. public void add(Observer observer); 5. 6. /*删除观察者*/ 7. public void del(Observer observer); 8. 9. /*通知所有的观察者*/ 10. public void notifyObservers(); 11. 12. /*自身的操作*/ 13. public void operation(); 14. } “玩转”Java 系列 ————————————————————————————— 42 【更多 Java – Android 资料下载,可访问尚硅谷(中国)官网 www.atguigu.com 下载区】 [java] view plainc opy 1. public abstract class AbstractSubject implements Subject { 2. 3. private Vector vector = new Vector(); 4. @Override 5. public void add(Observer observer) { 6. vector.add(observer); 7. } 8. 9. @Override 10. public void del(Observer observer) { 11. vector.remove(observer); 12. } 13. 14. @Override 15. public void notifyObservers() { 16. Enumeration enumo = vector.elements(); 17. while(enumo.hasMoreElements()){ 18. enumo.nextElement().update(); 19. } 20. } 21. } [java] view plainc opy 1. public class MySubject extends AbstractSubject { 2. 3. @Override 4. public void operation() { 5. System.out.println("update self!"); 6. notifyObservers(); 7. } 8. 9. } 测试类: [java] view plainc opy 1. public class ObserverTest { 2. 3. public static void main(String[] args) { 4. Subject sub = new MySubject(); 5. sub.add(new Observer1()); 6. sub.add(new Observer2()); 7. 8. sub.operation(); 9. } “玩转”Java 系列 ————————————————————————————— 43 【更多 Java – Android 资料下载,可访问尚硅谷(中国)官网 www.atguigu.com 下载区】 10. 11. } 输出: update self! observer1 has received! observer2 has received! 这些东西,其实不难,只是有些抽象,不太容易整体理解,建议读者:根据关系图,新 建项目,自己写代码(或者参考我的代码),按照总体思路走一遍,这样才能体 会它的思想,理解起来容易! 16、迭代子模式(Iterator) 顾名思义,迭代器模式就是顺序访问聚集中的对象,一般来说,集合中非常常见,如果对集 合类比较熟悉的话,理解本模式会十分轻松。这句话包含两层意思:一是需要遍历的对象, 即聚集对象,二是迭代器对象,用于对聚集对象进行遍历访问。我们看下关系图: 这个思路和我们常用的一模一样,MyCollection 中定义了集合的一些操作,MyIterator 中定 义了一系列迭代操作,且持有 Collection 实例,我们来看看实现代码: 两个接口: [java] view plainc opy 1. public interface Collection { 2. “玩转”Java 系列 ————————————————————————————— 44 【更多 Java – Android 资料下载,可访问尚硅谷(中国)官网 www.atguigu.com 下载区】 3. public Iterator iterator(); 4. 5. /*取得集合元素*/ 6. public Object get(int i); 7. 8. /*取得集合大小*/ 9. public int size(); 10. } [java] view plainc opy 1. public interface Iterator { 2. //前移 3. public Object previous(); 4. 5. //后移 6. public Object next(); 7. public boolean hasNext(); 8. 9. //取得第一个元素 10. public Object first(); 11. } 两个实现: [java] view plainc opy 1. public class MyCollection implements Collection { 2. 3. public String string[] = {"A","B","C","D","E"}; 4. @Override 5. public Iterator iterator() { 6. return new MyIterator(this); 7. } 8. 9. @Override 10. public Object get(int i) { 11. return string[i]; 12. } 13. 14. @Override 15. public int size() { 16. return string.length; 17. } 18. } [java] view plainc opy 1. public class MyIterator implements Iterator { 2. 3. private Collection collection; “玩转”Java 系列 ————————————————————————————— 45 【更多 Java – Android 资料下载,可访问尚硅谷(中国)官网 www.atguigu.com 下载区】 4. private int pos = -1; 5. 6. public MyIterator(Collection collection){ 7. this.collection = collection; 8. } 9. 10. @Override 11. public Object previous() { 12. if(pos > 0){ 13. pos--; 14. } 15. return collection.get(pos); 16. } 17. 18. @Override 19. public Object next() { 20. if(pos

下载文档,方便阅读与编辑

文档的实际排版效果,会与网站的显示效果略有不同!!

需要 6 金币 [ 分享文档获得金币 ] 1 人已下载

下载文档

相关文档