设计模式(1)
《大话设计模式》
个人笔记,如有错误,恳请批评指正。
设计模式六大原则:
reference:
http://www.uml.org.cn/sjms/201211023.asp
原则1、单一职责原则
单一职责原则(SRP),就一个类而言,应该仅有一个引起它变化的原因。
如果能够想到多于一个的动机去改变一个类,那么这个类就具有多于一个的职责,就应该考虑类的职责分离。
原则2、开放-封闭原则
不能修改,但是可以扩展的思想。(The Open-Closed Principle,OCP)。或者说对扩展开放,对更改封闭。
原则3、依赖倒转原则
针对接口编程,不要对实现编程。抽象不应该依赖细节,细节应该依赖于抽象。
高层模块和底层模块都依赖于接口或抽象类。
原则4、里氏代换原则
一个软件实体如果使用的是一个父类的话,那么一定适用于其子类,而且它觉察不出父类对象和子类对象的区别。也就是说,在软件里面,把父类都替换成它的子类,程序的行为没有变化,简单的说,就是子类必须能够替换掉他们的父类。
只有当子类可以替换掉父类,软件单位的功能不受到影响时,父类才能真正的被复用,而子类也能够在父类的基础上增加新的行为。
原则5、迪米特原则
也称最小知识原则,如果两个类不必彼此直接通信,那么这两个类就不应当发生直接的相互作用。如果其中一个类需要调用另一个类的某一个方法的话,可以通过第三者转发这个调用。
迪米特原则首先强调了在类的结构设计上,每一个类都应当尽量降低成员的访问权限。其次,其根本思想是强调了类与类之间的松耦合。
类之间的耦合越弱,越有利于复用,一个处在弱耦合的类被修改时,不会对有关系的类造成波及。
原则6、合成/聚合原则
在面向对象的设计中,还有一个很重要的设计原则,那就是合成/聚合复用原则。即优先使用对象合成/聚合,而不是类继承。
使用继承时,如果需要复用子类,当继承下来的实现不适合解决新的问题,则父类必须重写或被其他更合是的子类替换,这种由继承引发的依赖关系限制了灵活性并最终限制了复用性。继承是一种强耦合的结构,父类变,子类就必须要变。在使用继承时,一定要是“is-a”关系。
合成(Composition)和聚合(Aggregation)都是特殊的关联种类。
聚合表示一种弱的拥有关系,体现的是A对象可以包含B对象,但是B对象不是A对象的一部分。
合成则是一种强的拥有关系,体现了严格的部分和整体关系,部分和整体的生命周期一样。
合成聚合复用原则的好处是,优先使用对象的合成/聚合将有助于您保持每个类被封装,并被集中在单个任务上。这样类和类继承层次会保持较小规模,并且不太可能增长成不可控制的庞然大物。
UML图
- 类框,抽象类用斜体:第一层:类名。第二层:类的特性,字段和属性。第三层:类的操作,方法或行为。属性之前的符号表示:“+”—public,”-“—private,”#”—protected。
- 接口,与类相似,区别在于顶部有一个《interface》。
类与类、接口与接口之间的关系:
- 继承:
- 实现:
- 关联:
- 聚合:
- 合成:
- 依赖:
1、简单工厂模式
public class SimpleFactoryMode {
//实体抽象类
abstract class Product{}
//实体类
class A extends Product{}
class B extends Product{}
public Product produce(String options){
switch (options) {
case "A":
return new A();
case "B":
return new B();
default:
return null;
}
}
}
2、策略模式——商场促销
策略模式(Strategy):它定义了算法家族,分别封装起来,让它们之间可以相互替换,此模式让算法的变化,不会影响到使用算法的客户。
策略模式是一种定义一系列算法的方法,从概念上来看,所有这些算法完成的都是相同的工作,只是实现不同,它可以以相同的方式调用所有的算法,减少各种算法类与使用算法类之间的耦合。
以超市收银的一些折后活动来举例,策略模式中封装了多种折后模式:
/*
* 策略模式,封装了很多种实现的算法,根据需要选择不同的实现,完成相同的功能。
*/
public class CashContext {
private CashSuper cashStratege;//维护了收费策略,其有多种实现方式
public CashContext() {
super();
// TODO Auto-generated constructor stub
}
public CashContext(CashSuper cashStratege) {
super();
this.cashStratege = cashStratege;
}
public double getResult(double money){
return cashStratege.getResult(money);
}
}
3、装饰者模式
Decorator,动态地给一个对象添加一些额外的职责,就增加功能来说,装饰者模式比生成子类更为灵活。
并且可以把类中的装饰功能从类中去除,放在单独的修饰类中,简化了原有的类。
4、代理模式
Proxy,为其他对象提供一种代理以控制对这个对象的访问。
Proxy类保存了一个引用使得代理可以访问实体,并提供一个与被代理类相同的父接口,这样代理就可以用来替代实体。
代理使用的场景:
1. 远程代理:为一个对象在不同的地址空间提供局部代表。这样可以隐藏一个 对象存在于不同地址空间的事实。
2. 虚拟代理:根据需要创建开销很大的对象,通过它来存放实例化需要很长时间的真实对象。
3. 安全代理:用来控制真实对象访问时的权限。
4. 智能指引:指当调用真实的对象时,代理处理另外一些事。
代理模式其实就是在访问对象时引入一定程度的间接性,因为这种间接性,可以附加多种用途。
5、工厂方法模式
Factory Method:定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类了。
简单工厂模式:
public class SimpleFactoryMode {
//实体抽象类
abstract class Product{}
//实体类
class A extends Product{}
class B extends Product{}
//工厂方法
public Product produce(String options){
switch (options) {
case "A":
return new A();
case "B":
return new B();
default:
return null;
}
}
}
工厂方法模式:将工厂抽象为一个接口,定义了一个抽象的工厂方法,所有工厂实现类需要实现该方法,在实现类中,决定具体生产什么类型的实体。
当需要增加不同的实体时,只需要创建新的工厂实现类,而不需要直接修改简单工厂模式中的工厂类中的源码了。这运用到了开放-封闭原则。
public class FactoryMethodMode {
/*
* 工厂类的接口
*/
interface IFactory{
public Product produce();
}
/*
* 实体类的接口
*/
abstract class Product{}
/*
* 具体工厂类
*/
class FactoryA implements IFactory{
//实现方法,生产产品A
public Product produce() {
return new ProductA();
}
}
//...
/*
* 产品类
*/
class ProductA extends Product{}
//...
}
工厂方法模式的弊端,需要根据需要选择用何种工厂生产对应的实例,因为特定的工厂实现类只能生产特定的实体类,因此也就是说,工厂方法把简单工厂的内部逻辑判断转移到了客户端代码来进行
工厂方法克服了简单工厂方法违背开放–封闭原则的缺点,又保持了封装对象创建过程的优点。即修改创建的对象时,只需要修改工厂类即可。比简单工厂模式更加的优雅(相对来说),并且降低了客户程序与产品对象的耦合。
缺点还是之前说的:增加了一个额外的工厂类,表面上增加了工作量。
6、原型模式
Prototype:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。保证新对象和对象是不同的对象,而不是源对象的引用。即不用重新初始化对象,而是动态地获得对象运行时的状态。
Java中的克隆是原型模式的体现。Cloneable接口是标识接口,clone方法存在于Object中。
原型模式避免了一次次的New新对象,而是通过一个对象,克隆出相互不影响的新对象。克隆,即隐藏了创建对象的细节,又对性能进行了提高。
public class PrototypeMode {
abstract class Prototype implements Cloneable {
private String name;
public Prototype() {
super();
// TODO Auto-generated constructor stub
}
public Prototype(String name) {
super();
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
//返回克隆对象,浅克隆
public Prototype cloneC() throws CloneNotSupportedException {
return (Prototype) this.clone();
}
}
class ProtoTypeA extends Prototype{
}
public static void main(String[] args) throws CloneNotSupportedException {
ProtoTypeA a = new PrototypeMode().new ProtoTypeA();
a.setName("jason");
Prototype b = a.cloneC();
System.out.println(a == b);
System.out.println(b.getName());
}
}
对象的浅克隆和深克隆。
同Java中的浅克隆和深克隆
7、模板方法模式
当我们要完成在某一个细节层次一直的一个过程或一系列步骤,但其个别步骤在更详细的层次上的实现可能不同时,我们通常考虑用模板方法模式来处理。
模板方法模式(TemplateMethod Mode):定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
AbstractClass是抽象类,其实也就是一抽象模板,定义并实现了一个模板方法。这个模板方法一般是一个具体方法,它给出了一个顶级逻辑的骨架,而逻辑的组成步骤在相应的抽象操作中,推迟到子类实现。顶级逻辑也可能调用一些具体方法。
ConcretClass实现父类所定义的一个或多个抽象方法,每一个ConcretClass都可以给出这些抽象方法的不同实现,从而使得顶级逻辑的实现各不相同。
/**
* 模板类
* @author Wch
*
*/
public abstract class AbstractCLass {
//定义框架模板
public abstract void primitiveOperation1();
public abstract void primitiveOperation2();
public void TemplateMethod(){
primitiveOperation1();
primitiveOperation2();
System.out.println("template Method done");
}
/**
* 具体的子类,使用模板类提供的模板
* @author Wch
*
*/
class ConcreteClassA extends AbstractCLass{
@Override
public void primitiveOperation1() {
System.out.println("ConcreteClassA do operation1");
}
@Override
public void primitiveOperation2() {
System.out.println("ConcreteClassA do operation2");
}
}
}
8、外观模式——股票还是基金?
也称门面模式。
Facade:为子系统中的一组接口提供一个一致的页面,此模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
角色
- Facade:外观类,知道哪些子系统类负责处理请求,将客户的请求代理给适当的子系统对象。子系统对客户是透明的。就像基金操作中的规则对用户是透明的一样。
- SubSystem Classes:子系统类集合,实现子系统的功能,处理Facade对象指派的任务,注意子类中没有Facade的任何信息,即没有Facade对象的引用。
如何实现?
- 首先,在设计初期,应有意识的将不同的两个层分离,比如经典的三层架构,就需要考虑在数据访问层和业务逻辑层和表示层的层与层之间建立外观Facade,这样可以为复杂的子系统提供一个简单的接口,使得耦合大大降低。
- 其次,在开发阶段,子系统往往会因为不断的重构演化而变得越来越复杂,增加外观Facade可以提供一个简单的接口,减少它们之间的依赖。
- 第三,在维护一个遗留的大型系统时,可能这个系统已经非常难以维护和扩展了,可以为该系统开发一个外观Facade类,来提供设计粗糙或高度复杂的遗留代码的比较清晰简单的接口,让新系统与Facade对象交互,Facade与遗留代码交互所有复杂的工作。
9、建造者模式
又称生成器模式,将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示的意图时,可以使用该模式。
用户只需指定需要构造的类型就可以得到它们,而不需要知道具体的构造过程和细节。
建造者模式,用于创建一些复杂的对象,这些对象内部构建间的建造顺序通常是稳定的,但对象内部的构建通常面临复杂的变化。
建造者模式是在当创建复杂对象的算法应该独立于该对象的组成部分以及它们的装配方式时适用的模式。
reference:
http://www.cnblogs.com/java-my-life/archive/2012/04/07/2433939.html#undefined
10、观察者模式
又称发布-订阅者模式。(Publish/Subscribe),定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象,这个主题对象在状态发生变化时,会通知所有观察者对象,使他们能够自动的更新自己。
什么时候使用观察者模式?
当一个对象的改变需要同时改变其他的对象时,而且它并不知道具体需要改变多少个对象,这时候应该使用观察者模式。接口的作用是接触了耦合,观察者不需要具体的消息发布者是谁,消息发布者也不需要知道具体的观察者是什么。让耦合的双方都依赖于抽象,而不是依赖于具体。从而使得各自的变化都不会影响另一边的变化。依赖倒转的体现。
观察者模式的缺陷
抽象通知者依然依赖于抽象观察者,可以采用委托模式来解决,具体可以参考:
http://blog.csdn.net/gdutxiaoxu/article/details/51824769
在这里先暂时放一放啦。
11、 抽象工厂模式
前面已经学习到,工厂方法模式是定义一个用于创建对象的接口,让子类决定实例化哪一个类。
抽象工厂模式(Abstract Factory):提供一个创建一系列相关或相互依赖对象的接口,而无需指定他们具体的类。
抽象工厂模式:
抽象工厂模式对工厂进行了抽象,抽象工厂类中定义了所有的产品创建的抽象方法,其下有多个抽象工厂的实现类,即ConcretFactory,对应每一种产品。
对于产品,可能存在多个产品抽象类,即AbstractProductA、AbstractProductB。而对每个抽象产品类其下有具体的规格。对于ProductA可能存在ProductA 1、ProductA 2。其都对应着抽象工厂的具体实现类。
关于抽象工厂模式和工厂方法模式的区别:
自己的理解,
对于简单工厂模式,所有的业务都在工厂类中执行,包括逻辑判断,以及产品类的创建。耦合性很大,但是配置起来简单。
对于工厂方法模式,抽象了一个工厂类,抽象了一个产品类,并且每一个产品实现类对应一个工厂实现类来生产该产品,一旦决定生产何种产品,就可以实例化响应的工厂进行生产。
而抽象工厂模式,与工厂模式的区别在于是针对多个产品多个规格的(工厂方法模式针对同一产品多个规格),如果产品单一,最合适用工厂模式,但是如果有多个业务品种、业务分类时,通过抽象工厂模式产生需要的对象是一种非常好的解决方式。再通俗深化理解下:工厂模式针对的是一个产品等级结构 ,抽象工厂模式针对的是面向多个产品等级结构的。
http://blog.csdn.net/wyxhd2008/article/details/5597975
http://www.cnblogs.com/zhangchenliang/p/3700820.html
12、状态模式
State:当一个对象的内在状态改变时允许改变其行为,这个对象看起来像是改变了其类。
状态模式主要解决的是当控制一个对象状态转换的条件表达式过于复杂时的情况。把状态的判断逻辑转移到表示不同状态的一系列类当中,可以把复杂的判断逻辑简化。【以不同的类来表示不同的状态】
http://www.cnblogs.com/ysw-go/p/5404918.html
13、适配器模式——翻译官
Adapter:将一个类的接口转换成客户希望的另一个接口。使得原本不兼容而不能一起工作的类可以一起工作。
系统的数据和行为都正确,但接口不符时,我们应该考虑用适配器,目的是使控制范围之外的一个原有对象与某个接口匹配。适配器模式主要应用于希望复用一些现存的类,但是接口又与复用环境要求不一致的情况。
模式所涉及的角色有:
● 目标(Target)角色:这就是所期待得到的接口。注意:由于这里讨论的是类适配器模式,因此目标不可以是类。
● 源(Adapee)角色:现在需要适配的接口。
● 适配器(Adaper)角色:适配器类是本模式的核心。适配器把源接口转换成目标接口。显然,这一角色不可以是接口,而必须是具体类。
reference:
http://www.cnblogs.com/java-my-life/archive/2012/04/13/2442795.html
14、备忘录模式——存档、读档
备忘录模式又叫做快照模式(Snapshot Pattern)或Token模式,是对象的行为模式。
备忘录对象是一个用来存储另外一个对象内部状态的快照的对象。备忘录模式的用意是在不破坏封装的条件下,将一个对象的状态捕捉(Capture)住,并外部化,存储起来,从而可以在将来合适的时候把这个对象还原到存储起来的状态。备忘录模式常常与命令模式和迭代子模式一同使用。
http://www.cnblogs.com/java-my-life/archive/2012/06/06/2534942.html
15、组合模式
Composite:将对象组合成树形结构以表示“部分-整体”的层次结构,组合模式使得用户对单个对象和组合对象的使用具有一致性。
暂不讨论。
16、迭代器模式——一个一个来
提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露该对象的内部表示。
迭代器模式分离了集合对象的遍历行为,抽象出一个迭代器类来负责,这样既可以做到不暴露集合的内部结构,又可让外部代码透明地访问集合内部的数据。
迭代器模式属于行为型模式,其意图是提供一种方法顺序访问一个聚合对象中得各个元素,而又不需要暴露该对象的
内部表示。
至少可以历遍first,next,previous,last,isOver,或是历遍选择符合某种条件的子元素.
结构:
由一个接口与一个实现类组成.
- 接口:
主要是定义各历遍的方法.
- 实现类:
需要一个计算点private int current=0 ; 以及一个容器Vector,来存在原来的进行历遍的一团东西;再对接口方法进
行实现.
示例
Iterator接口:
package iterator;
public interface Iterator{
/*
Item:即是集合中的各对象的类型.若为String,即把所有的ITEM改为String,若为其它自定义的类,则改为各自定义的类
的接口,或类. --->important.
*/
public Item first();
public Item next();
public boolean isDone();
public Item currentItem();
}
Controller类实现了Iterator接口。
package iterator;
import java.util.Vector;
public class Controller implements Iterator{
private int current =0;
Vector channel;
public Controller(Vector v){
channel = v;
}
public Item first(){
current = 0;
return (Item)channel.get(current);
}
public Item next(){
current ++;
return (Item)channel.get(current);
}
public Item currentItem(){
return (Item)channel.get(current);
}
public boolean isDone(){
return current>= channel.size()-1;
}
}
Television接口:
package iterator;
import java.util.Vector;
public interface Television{
public Iterator createIterator();
}
HaierTV类实现了Television接口。
package iterator;
import java.util.Vector;
public class HaierTV implements Television{ ---对象
private Vector channel;
public HaierTV(){
channel = new Vector();
channel.addElement(new Item("channel 1")); --各元素,用VECTOR存放
channel.addElement(new Item("channel 2"));
channel.addElement(new Item("channel 3"));
channel.addElement(new Item("channel 4"));
channel.addElement(new Item("channel 5"));
channel.addElement(new Item("channel 6"));
channel.addElement(new Item("channel 7"));
}
public Iterator createIterator(){
return new Controller(channel); --把这个VECTOR放到迭代器中构造方法中去
}
}
Client客户端:
package iterator;
public class Client{
public static void main(String[] args){
Television tv = new HaierTV();
Iterator it =tv.createIterator();
System.out.println(it.first().getName());
while(!it.isDone()){
System.out.println(it.next().getName());
}
}
}
Item类的接口:
package iterator;
public class Item{
private String name;
public Item(String aName){
name = aName;
}
public String getName(){
return name;
}
}
17、单例模式
Singleton:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
关键之处在于,让一个类自身负责保存它的唯一实例,这个类没有其他实例可以被创建,并且提供一个访问唯一实例的方法。
之前已经学习过该模式了。
需要注意的一点是,双重锁定(double-check locking)中,进行了两次null判断。
原因在于:若同时两个线程访问了getInstance
方法,线程A获取到了锁并进入方法,线程B阻塞(此时线程A、B都已经进行了第一次判空,所以现在两个线程都知道此时instance是null的)。当线程A执行完毕之后释放锁,线程B获得锁对象,进入了同步代码块,由于之前已经进行了判空,如果此时不再次进行判空的话,默认instance还是null,而在之前线程A可能已经创建了一个instance对象了,因此需要进行第二次判空,否则可能会重复创建对象。
public class Singleton {
private static Singleton instance;
private static String lock = " lock";
private Singleton(){};
public static Singleton getInstance(){
if(instance == null){
synchronized(lock){
if(instance == null){//为何要进行两次判空?
instance = new Singleton();
}
}
}
return instance;
}
}
饿汉单例:类加载时创建对象,需要提前占用系统资源。
懒汉单例:面临多线程创建对象的安全性问题,需要使用双重锁定的处理才能保证安全。
原则6、合成/聚合原则
在面向对象的设计中,还有一个很重要的设计原则,那就是合成/聚合复用原则。即优先使用对象合成/聚合,而不是类继承。
使用继承时,如果需要复用子类,当继承下来的实现不适合解决新的问题,则父类必须重写或被其他更合是的子类替换,这种由继承引发的依赖关系限制了灵活性并最终限制了复用性。继承是一种强耦合的结构,父类变,子类就必须要变。在使用继承时,一定要是“is-a”关系。
合成(Composition)和聚合(Aggregation)都是特殊的关联种类。
聚合表示一种弱的拥有关系,体现的是A对象可以包含B对象,但是B对象不是A对象的一部分。
合成则是一种强的拥有关系,体现了严格的部分和整体关系,部分和整体的生命周期一样。
合成聚合复用原则的好处是,优先使用对象的合成/聚合将有助于您保持每个类被封装,并被集中在单个任务上。这样类和类继承层次会保持较小规模,并且不太可能增长成不可控制的庞然大物。
18、桥接模式
Bridge,将抽象部分与它的实现部分分离,使他们都可以独立的变化。
什么叫抽象与它的实现分离,并不是说让抽象类与其派生类分离,因为这没有任何意义。实现指的是抽象类和他的派生类用来实现自己的对象。
实现系统可能有多角度的分类,每一种分类都可能变化,那么就把这种多角度分离出来让它们独立变化,减少它们之间的耦合。
19、命令模式
Command:将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化。
暂不讨论。
20、职责链模式——推卸责任
有点像类加载机制中的双亲委派模型。又有点像过滤器中的过滤器链…
Chain of Responsebility:使多个对象都有机会处理请求,从而避免请求的发送者与接受者之间的耦合关系。将这个对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。
当客户提交一个请求时,请求是沿链传递直至有一个ConcreteHandler对象负责处理它。
角色
抽象处理者角色(Handler):定义出一个处理请求的接口。如果需要,接口可以定义 出一个方法以设定和返回对下家的引用。这个角色通常由一个Java抽象类或者Java接口实现。
具体处理者角色(ConcreteHandler):具体处理者接到请求后,可以选择将请求处理掉,或者将请求传给下家。由于具体处理者持有对下家的引用,因此,如果需要,具体处理者可以访问下家。
http://blog.csdn.net/jason0539/article/details/45091639
21、中介者模式——世界需要和平
Meidtor:用一个中介对象来封装一些列的对象交互,中介者使各对象不需要显式的相互引用,从而使其耦合松散,而且可以独立的改变他们之间的交互。
角色
Meidtor:抽象中介者,需要了解所有的同事类。
Colleage:抽象同事类,需要注册相同的中介者。
ConcretMeditor:具体中介者。实现抽象类的方法,他需要知道所有的具体同事类,并从具体同事接受消息,向具体同事对象发出命令、
ConcretColleage:具体同事类,每个具体同事只知道自己的行为,而不了解其他同事类的情况,但他们却都认识中介对象。
中介者模式的优缺点:
优点:
Mediator的出现减少了Colleague的耦合,使得可以独立的更改和复用各个Colleague类和Mediator。
缺点:
Mediator的具体实现可能会因为Colleague类的增加变得复杂而任务繁重。
22、享元模式——共享代码
Flyweight:运用共享技术有效支持大量细粒度的对象。
在享元对象内部并且不会随环境改变而改变的共享部分,可以称为是享元对象的内部状态,而随环境改变而改变的、不可以共享的状态就是外部状态了。
享元模式可以避免大量非常相似类的开销,在程序设计中,有时需要生成大量细粒度的类实例来表示数据。
如果能发现这些实例除了几个参数外基本上都是相同的,有时就能大幅度地减少需要实例化的类的数量。
如果能把那些参数移到类实例的外面,在方法调用时将他们传递进来,就可以通过共享大幅度地减少单个实例的数目。
角色
- Flyweight:所有具体享元类的超类或接口,通过这个接口,Flyweight可以接受并作用于外部状态。
- FlyweightFactory:一个享元工厂,用来创建并管理Flyweight对象。它主要是用来确保合理的共享Flyweight,当用户请求一个Flyweight时,FlyweightFactory对象提供一个已创建的实例或者创建一个。
- ConcretFlyweight:继承Flyweight超类或实现接口,并为内部状态增加存储空间。
- UnsharedConcretFlyweight:指那些不需要共享的Flyweight子类,因为Flyweight接口共享成为可能,但它不强制共享。
什么时候使用享元模式?
如果一个应用程序使用了大量的对象,而这些对象早成了很大的存储开销时就应该使用;还有就是对象的大多数状态可以是外部状态,如果删除对象的外部状态,那么可以用相对较少的共享对象取代很多组对象,此时可以考虑使用享元模式。
但是,享元模式会使系统变得更加复杂,因此,只有当有足够多的对象实例可供共享时才值得使用享元模式。
从某种意义上来讲,Java中的String就是用到了享元模式。如果每一个字符串内容相同的String对象都需要new一个实例对象的话,将会产生巨大的开销,因此在常量池中维护了字符串对象,在没有new的情况下,字符串内容的String对象引用的是同一个内存地址,从而减少了内存的开销。享元模式更多的时候是一种底层使用的模式。
再比如说,围棋棋子的颜色只分为黑白,对于白棋来说,颜色是内部状态,而位置属于外部状态,有361种可能的位置。
23、解释器模式
Interpreter:给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。
暂不讨论。
24、访问者模式——visit&accept
Visitor:封装某些作用于某种数据结构中各元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的新的操作。
class A {
public void method1(){
System.out.println("我是A");
}
public void method2(B b){
b.showA(this);
}
}
class B {
public void showA(A a){
a.method1();
}
}
在例子中,对于类A来说,类B就是一个访问者。
角色
- 抽象访问者:抽象类或者接口,声明访问者可以访问哪些元素,具体到程序中就是visit方法中的参数定义哪些对象是可以被访问的。
- 访问者:实现抽象访问者所声明的方法,它影响到访问者访问到一个类后该干什么,要做什么事情。
- 抽象元素类:接口或者抽象类,声明接受哪一类访问者访问,程序上是通过accept方法中的参数来定义的。抽象元素一般有两类方法,一部分是本身的业务逻辑,另外就是允许接收哪类访问者来访问。
- 元素类:实现抽象元素类所声明的accept方法,通常都是visitor.visit(this),基本上已经形成一种定式了。
- 结构对象:一个元素的容器,一般包含一个容纳多个不同类、不同接口的容器,如List、Set、Map等,在项目中一般很少抽象出这个角色。
优点
符合单一职责原则:凡是适用访问者模式的场景中,元素类中需要封装在访问者中的操作必定是与元素类本身关系不大且是易变的操作,使用访问者模式一方面符合单一职责原则,另一方面,因为被封装的操作通常来说都是易变的,所以当发生变化时,就可以在不改变元素类本身的前提下,实现对变化部分的扩展。
扩展性良好:元素类可以通过接受不同的访问者来实现对不同操作的扩展。
缺点
增加新的元素类比较困难。通过访问者模式的代码可以看到,在访问者类中,每一个元素类都有它对应的处理方法,也就是说,每增加一个元素类都需要修改访问者类(也包括访问者类的子类或者实现类),修改起来相当麻烦。也就是说,在元素类数目不确定的情况下,应该慎用访问者模式。所以,访问者模式比较适用于对已有功能的重构,比如说,一个项目的基本功能已经确定下来,元素类的数据已经基本确定下来不会变了,会变的只是这些元素内的相关操作,这时候,我们可以使用访问者模式对原有的代码进行重构一遍,这样一来,就可以在不修改各个元素类的情况下,对原有功能进行修改。
适用场景
假如一个对象中存在着一些与本对象不相干(或者关系较弱)的操作,为了避免这些操作污染这个对象,则可以使用访问者模式来把这些操作封装到访问者中去。
假如一组对象中,存在着相似的操作,为了避免出现大量重复的代码,也可以将这些重复的操作封装到访问者中去。
reference:
http://blog.csdn.net/jason0539/article/details/45146271