大话设计模式、UML、设计模式Java版完全总结

此篇博客为阅读大话设计模式后的笔记记录( 读完本文>≈读完《大话设计模式》 ),注意是笔记形式,优先适合于对设计模式有一定了解的读者,希望短时间快速温习的读者,同时也对所有设计模式添加了完整代码诠释与注释,方便初学者的理解,另外,文章末尾有对所有设计模式的总结,读者若对部分设计模式容易混淆,可以到文章末尾进行了解,其中文章有些内容我觉得不方便展开的,会附上我认为的比较优秀的博客地址进行讲解。

转载请注明出处~https://blog.csdn.net/qq_35642036/article/details/79663378

(长文警告!建议收藏后慢慢品读)

目录

UML讲解

常见的有以下几种关系:

泛化/继承(Generalization)

实现(Realization)

依赖(Dependency)

关联(Association)

聚合(Aggregation)

组合(Composition)

设计模式原则

总原则

开闭原则OCP

设计模式的六大原则:

1、单一职责原则

2、里氏替换原则(Liskov Substitution Principle)

3、依赖倒置原则(Dependence Inversion Principle)

4、接口隔离原则(Interface Segregation Principle)

5、迪米特法则(最少知道原则)(Demeter Principle)

6、合成复用原则(Composite Reuse Principle)

设计模式

策略模式(Strategy)

简单工厂模式(Simple Factory)+工厂方法(FactoryMethod)

抽象工厂模式(AbstractFactory)

装饰模式(Decorator)

代理模式(Proxy)

原型模式(Prototype)

模板方法模式(Template)

外观模式(Facade)

建造者模式(Builder)

观察者模式(Observer)

状态模式(State)

适配器模式(Adapter)

备忘录模式(Memento)

组合模式(Componet)

迭代器模式(Iterator)

单例模式(Singleton)

桥接模式(Bridge)

命令模式(Command)

职责链模式(ResponsibilityChain)

中介者模式(Mediator)

享元模式(FlyWeight)

解释器模式(Interpreter)

访问者模式(Visitor)

设计模式总结

针对案例的进一步总结:


设计模式分为三大类(加红代表常用):

创建型模式(五种)工厂方法模式抽象工厂模式单例模式建造者模式、原型模式。

结构型模式(七种)适配器模式装饰器模式代理模式、外观模式、桥接模式、组合模式、享元模式。

行为型模式(十一种)策略模式模板方法模式观察者模式迭代器模式责任链/职责链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。

其实还有两类:并发型模式和线程池模式。

 

UML讲解

详细参考网址:http://blog.csdn.net/garfielder007/article/details/54427742

或参考:UML科普文,一篇文章掌握14种UML图

UML即Unified Model Language,是一种建模语言,也是标准建模语言。

常见的有以下几种关系:

关系所表现的强弱程度依次为:组合>聚合>关联>依赖;

聚合跟组合其实都属于关联 只不过它们是两种特殊的关联

泛化/继承(Generalization)

继承(继承父类):带空心三角形的直线表示

实现(Realization)

(实现接口):带空心三角形的虚线表示

依赖(Dependency)

类与类之间最弱的关系,依赖可以简单的理解一个类使用了另一个类:带箭头的虚线表示依赖

        例如:Person类使用了car类里面的speed属性(一般在方法参数)

关联(Association)

一个类和另一类有联系:带箭头的实线表示关联

        例如:Person类里面有Address类属性(一般在成员变量)

聚合(Aggregation)

关联关系的一种,属于较强的关联,表示整体与部分的关系,但是部分可以脱离整体而存在:带空心菱形的直线加箭头表示,空心菱形指向整体,has-a关系

        例如:Person类里面有car类属性,人拥有车,车可以脱离人存在

组合(Composition)

关联关系的一种,表示部分和整体的关系,但是部分存活周期受到整体的影响,若整体不存在则部分也将不存在。此时部分需在整体的构造方法中创建:带实心菱形的直线加箭头表示。实心菱形指向整体,Contains-a关系

        例如:person类里面有finger类属性,人拥有手指,手指不能脱离人存在

设计模式原则

总原则

开闭原则OCP

对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,而是要扩展原有代码,实现一个热插拔的效果。所以一句话概括就是:为了使程序的扩展性好,易于维护和升级。

 

设计模式的六大原则:

1、单一职责原则

不要存在多于一个导致类变更的原因,也就是说每个类应该实现单一的职责,否则就应该把类拆分。

2、里氏替换原则(Liskov Substitution Principle)

任何基类可以出现的地方,子类一定可以出现。

3、依赖倒置原则(Dependence Inversion Principle)

面向接口编程,依赖于抽象而不依赖于具体。写代码时用到具体类时,不与具体类交互,而与具体类的上层接口交互。

4、接口隔离原则(Interface Segregation Principle)

每个接口中不存在子类用不到却必须实现的方法,如果不然,就要将接口拆分。使用多个隔离的 

5、迪米特法则(最少知道原则)(Demeter Principle)

一个类对自己依赖的类知道的越少越好。无论被依赖的类多么复杂,都应该将逻辑封装在方法的内部,通过public方法提供给外部。这样当被依赖的类变化时,才能最小的影响该类。

6、合成复用原则(Composite Reuse Principle)

优先首先使用合成/聚合的方式,而不是使用继承。

设计模式

策略模式(Strategy)

多个算法可实现类似功能,若将所有方法写在一个Utils里面会导致难以维护,代码复杂。所以策略模式考虑如何让算法和对象分开来,使得算法可以独立于使用它的客户而变化。具体的方案是把一个类中经常改变或者将来可能改变的部分提取出来,作为一个接口,然后在类中包含这个对象的实例,这样类的实例在运行时就可以随意调用实现了这个接口的类的行为。

策略模式就是用来封装算法的,但在实践中,我们发现可以用它来封装几乎任何类型的规则,只要在分析过程中听到需要在不同时间应用不同的业务规则,就可以考虑使用策略模式处理这种变化的可能性。

优点

1、可以动态的改变对象的行为

缺点

1、客户端必须知道所有的策略类,并自行决定使用哪一个策略类

2、策略模式将造成产生很多策略类

public static void main(String[] args) {
    Context context;
    context = new Context(new ConcreteStrategyA());
    context.contextInterface();
    context = new Context(new ConcreteStrategyB());
    context.contextInterface();
    context = new Context(new ConcreteStrategyC());
    context.contextInterface();
}

输出:
策略A的具体算法实现
策略B的具体算法实现
策略C的具体算法实现

简单工厂模式(Simple Factory)+工厂方法(FactoryMethod)

工厂模式可以分为三类,它们的区别如下(这一部分可以在看完三种工厂模式后再回来看):

1)简单工厂模式(Simple Factory):不符合开放-封闭原则(当我们需要增加一种计算时,例如开平方。这个时候我们需要先定义一个类继承Operation类,其中实现平方的代码。除此之外我们还要修改OperationFactory类的代码,增加一个case。所以显然违反了)

2)工厂方法(Factory Method):生产单一产品

3)抽象工厂模式(Abstract Factory):生产一个产品体系

简单工厂模式只有一个具体的工厂类

工厂方法模式只有一个抽象产品类,而抽象工厂模式有多个。  

工厂方法模式的具体工厂类只能创建一个具体产品类的实例,而抽象工厂模式可以创建多个。(工厂方法在增加一个具体产品的时候,都要增加对应的工厂。但是抽象工厂模式只有在新增一个类型的具体产品时才需要新增工厂)

工厂方法让类的实例化推迟到子类中进行

简单工厂模式:一个上帝类,能够生产A车,若有一种B车需要生产,则需要更改上帝类工厂代码

   

工厂方法模式:去掉了简单工厂模式中工厂方法的静态属性,使得它可以被子类继承。由于简单工厂模式不仅对扩展开放了,也对修改开放了(每添加一个类,就得去生成实例的工厂方法中增加新的分支),违背了“开放-封闭原则”。工厂方法把简单工厂的内部逻辑判断转移到了客户端代码来进行。缺点:每加一个产品,需要额外加一个产品工厂的类,增加了额外的开销。

 

 

 

工厂模式用于处理 如何获取实例对象问题,建造者模式用于处理如何建造实例对象 问题。

工厂方法的实现并不能减少工作量,但是它能够在必须处理新情况时,避免使已经很复杂的代码更加复杂

 

通常设计应该是从工厂方法开始,当设计者发现需要更大的灵活性时,设计便会向其他创建型模式演化。当设计者在设计标准之间进行权衡的时候,了解多个创建型模式可以给设计者更多的选择余地

 

抽象工厂模式(AbstractFactory)

提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类

好处:

1.     易于交换产品系列,例如Ifactory factory = new AccessFactory()在一个应用中只需要在初始化的时候出现一次,这就使得改变一个应用的具体工厂变得非常容易,它只需要改变具体工厂即可使用不同的产品配置。

2.     它让具体的创建实例过程与客户端分离,客户端是通过它们的抽象接口操纵实例,产品的具体类名也被具体工厂的实现分离,不会出现在客户代码中。

所有在用简单工厂的地方,都可以考虑用反射技术来去除switch或if,解除分支判断带来的耦合

缺点:如果要添加一个产品类,就得增加很多代码

用反射+抽象工厂的数据访问程序

 

装饰模式(Decorator)

动态给一个对象添加一些额外的职责,使用Decorator模式相比用生成子类方式达到功能的扩充显得更为灵活。

设计初衷:通常可以使用继承来实现功能的拓展,如果这些需要拓展的功能的种类很繁多,那么势必生成很多子类,增加系统的复杂性,同时,使用继承实现功能拓展,我们必须可预见这些拓展功能,这些功能是编译时就确定了,是静态的。

 

装饰者模式:

装饰者模式必然有一个公共的接口或抽象类,用来作为对象的传递。你需要根据接口实现基本的被装饰类(Person),以及装饰类的公共接口(Decorator ),以后所有的装饰类都是继承自公共的装饰类接口,内部实现。

       

输出:
具体对象的操作
具体装饰对象A的操作
B中的新增行为 具体装饰对象B的操作
C没有特殊行为 具体装饰对象C的操作

 

装饰者模式也可用于给一个方法添加开始时间,结束时间,或者添加事务,提交事务等  

核心要点:有一个抽象对象类或对象接口,而具体的对象和装饰类都要继承类或实现这个接口

装饰代码二:

public class CodingTask implements Runnable {
  private final int employeeId;
  public CodingTask(int employeeId) {
    this.employeeId = employeeId;
  }
  @Override
  public void run() {
    System.out.println("Employee " + employeeId
        + " started writing code.");
    try {
      Thread.sleep(5000);
    } catch (InterruptedException e) {
      throw new RuntimeException(e);
    }
    System.out.println("Employee " + employeeId
        + " finished writing code.");
  }
}
public class TransactionalRunnable implements Runnable {
  private final Runnable innerRunnable;
  public TransactionalRunnable(Runnable innerRunnable) {
    this.innerRunnable = innerRunnable;
  }
  @Override
  public void run() {
    boolean shouldRollback = false;
    try {
      beginTransaction();
      innerRunnable.run();
    } catch (Exception e) {
      shouldRollback = true;
      throw e;
    } finally {
      if (shouldRollback) {
        rollback();
      } else {
        commit();
      }
    }
  }
  private void commit() {
    System.out.println("commit");
  }
  private void rollback() {
    System.out.println("rollback");
  }
  private void beginTransaction() {
    System.out.println("beginTransaction");
  }
}
public class LoggingRunnable implements Runnable {
  private final Runnable innerRunnable;
  public LoggingRunnable(Runnable innerRunnable) {
    this.innerRunnable = innerRunnable;
  }
  @Override
  public void run() {
    long startTime = System.currentTimeMillis();
    System.out.println("Task started at "
        + startTime);
    innerRunnable.run();
    System.out.println("Task finished. Elapsed time: "
        + (System.currentTimeMillis() - startTime));
  }
}
public class Client {
  public static void main(String[] args) {
    new LoggingRunnable(
        new TransactionalRunnable(
            new CodingTask(0))).run();
  }
}
输出
Task started at 1522227362358
beginTransaction
Employee 0 started writing code. 
Employee 0 finished writing code. 
commit
Task finished. Elapsed time: 5012

 

代理模式(Proxy)

代理模式:为其他对象提供一种代理以控制对这个对象的访问,使得其他对象无法直接接触目标对象

 

输出:真实对象的请求

 

原型模式(Prototype)

原型模式:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。

涉及了浅拷贝与深拷贝

对象克隆学习网址:https://www.cnblogs.com/Qian123/p/5710533.html#_label3

 

原型模式其实就是从一个对象再创建另外一个可定制的对象,而且不需要知道任何创建的细节

优点:建立相应数目的原型并克隆它们通常比每次用合适的状态手工实例化该类更方便一些

输出:
>>>>>>浅度拷贝:
大鸟  男  25
工作经历: 1999-2002,  ZZ公司
大鸟  男  25
工作经历: 1999-2002,  ZZ公司
大鸟  男  25
工作经历: 1999-2002,  ZZ公司
==================================
>>>>>>深度拷贝:
大鸟  男  25
工作经历: 1999-2002,  XX公司
大鸟  男  25
工作经历: 1999-2002,  YY公司
大鸟  男  25
工作经历: 1999-2002,  ZZ公司

 

 

模板方法模式(Template)

模板方法模式:定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤

 

输出:
具体类A的方法1实现
具体类A的方法2实现
模板方法结束
 

具体类B的方法1实现
具体类B的方法2实现
模板方法结束
 

 

比如 AsyncTask 里面的四个方法 onPreExecute 、 doInBackground 、 onProgressUpdate 、 onPostExecute

还有 Android里面的Activity 也应用了模板方法模式

onCreate 、 onStart 、 onResume 、 onPause 、 onStop 、 onDestroy 、 onRestart

 

外观模式(Facade)

外观模式定义了一个高层的功能,为子系统中的多个模块协同的完成某种功能需求提供简单的对外功能调用方式,使得这一子系统更加容易被外部使用(完美体现了依赖倒转原则和迪米特法则)

案例解释1:一个人买很多股票,由于股票众多难以管理容易造成亏损,可以选择购买基金,由基金来管理这些股票(基金擅长管理股票)

案例解释2:启动电脑开关start()可视为façade,其中,cpu、disk、memory为子系统组件,对外只需要façade提供start(),由它集中管理子系统

输出:
方法组A:
子系统方法一
子系统方法二
子系统方法四
方法组B:
子系统方法三
子系统方法四

建造者模式(Builder)

扩展阅读:Java 帝国之建造者模式 

将一个复杂的对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

案例解释:麦当劳的汉堡包永远不会少加盐,摆摊的每次炒的东西味道可能有变化

使用建造者模式,用户只需指定需要建造的类型就可以得到它们,而具体建造的过程和细节就不需知道了。

什么时候使用建造者模式:需要创建一些复杂的对象,这些对象内部构建间的建造顺序通常是稳定的,但对象内部的构建通常面临着复杂的变化。

建造者模式如何实现高内聚松耦合的?将一个复杂对象的创建与它的表示分离,这就可以很容易地改变一个产品的内部表示,并且使得构造代码和表示代码分开。这样对于客户来说,它无需关心产品的创建过程,而只要告诉建造者需要什么,就能用同样的构建过程创建不同的产品给客户

 

 

与抽象工厂的区别:在建造者模式里,有个指导者,由指导者来管理建造者,用户是与指导者联系的,指导者联系建造者最后得到产品。即建造模式可以强制实行一种分步骤进行的建造过程。

观察者模式(Observer)

观察者模式(发布-订阅模式):定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态发送变化时,会通知所有观察者对象,让它们能够自动更新自己

一个被观察者管理所有相依于它的观察者物件,并且在本身的状态改变时主动发出通知。这通常通过呼叫各观察者所提供的方法来实现。此种模式通常被用来实现事件处理系统。

案例解释:前台小姐concreteSubject、里面的工作的人concreteObserver、老板来了前台小姐通知里面工作的人马上开始工作

输出:
观察者X的新状态是ABC
观察者Y的新状态是ABC

观察者Z的新状态是ABC

案例2:珠宝商运送一批钻石,有黄金强盗准备抢劫,珠宝商雇佣了私人保镖,警察局也派人护送,于是当运输车上路的时候,强盗保镖警察都要观察运输车一举一动

 

程序:

1.     抽象的被观察者watched接口,有addWatcher,deleteWatch,notifyWatch方法

2.     抽象的观察者watcher接口,update方法

3.     具体的被观察者Transporter实现watched接口

4.     三个具体的观察者实现watcher,有具体的update方法

5.     测试类

 

观察者模式在关于目标角色、观察者角色通信的具体实现中,有两个版本。

一种情况便是目标角色在发生变化后,仅仅告诉观察者角色“我变化了”,观察者角色如果想要知道具体的变化细节,则就要自己从目标角色的接口中得到。这种模式被很形象的称为:拉模式——就是说变化的信息是观察者角色主动从目标角色中“拉”出来的。

还有一种方法,那就是我目标角色“服务一条龙”,通知你发生变化的同时,通过一个参数将变化的细节传递到观察者角色中去。这就是“推模式”——管你要不要,先给你啦。

状态模式(State)

当一个对象的内在状态改变时允许改变其行为,这个对象看起来像是改变了其类

状态模式主要解决的是当控制一个对象状态转换的条件表达式过于复杂时的情况。把状态的判断逻辑转移到表示不同状态的一系列类当中,可以把复杂的判断逻辑简化。

好处:将与特定状态相关的行为局部化,并且将不同状态的行为分割开来。

也就是将特定的状态相关的行为都放入一个对象中,由于所有与状态相关的代码都存在于某个ConcreteState中,所以通过定义性的子类可以很容易地增加新的状态和转换。

目的:消除庞大的条件分支语句;状态模式通过把各种状态转移逻辑分布到State的子类之间,来减少相互间的依赖。

什么时候用:

当一个对象的行为取决于它的状态,并且它必须在运行时刻根据状态改变它的行为时,就可以考虑使用状态模式。

//Context类,维护一个ConcreteState子类的实例,这个实例定义当前的状态

public class Context {
    private State state;
    public Context(State state) {
       this.state = state;
    }
    public State getState() {
       return state;
    }
    public void setState(State state) {
       this.state = state;
    }
    public void request() {
       this.state.handle(this);
    }
}



//抽象状态类
public abstract class State {
    public abstract void handle(Context context);
}

//具体状态类A
class ConcreteStateA extends State {
    public void handle(Context context) {
       System.out.println("现在是在状态A");
       context.setState(new ConcreteStateB());
    }
}//具体状态类B
class ConcreteStateB extends State {
    public void handle(Context context) {
       System.out.println("现在是在状态B");
       context.setState(new ConcreteStateC());
    }
}//具体状态类C
class ConcreteStateC extends State {
    public void handle(Context context) {
       System.out.println("现在是在状态C");
       context.setState(new ConcreteStateA());
    }
}

//客户端:不断请求,不断更改状态
public class StateClient {
    public static void main(String[] args) {
      Context context = new Context(new ConcreteStateA());
      context.request();
      context.request();
      context.request();
      context.request();
      context.request();
    }
}
输出:
现在是在状态A
现在是在状态B
现在是在状态C
现在是在状态A
现在是在状态B


适配器模式(Adapter)

将一个类的接口转换成客户希望的另外一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以在一起工作。

 

优点

  通过适配器,客户端可以调用同一接口,因而对客户端来说是透明的。这样做更简单、更直接、更紧凑。

  复用了现存的类,解决了现存类和复用环境要求不一致的问题。

   将目标类和适配者类解耦,通过引入一个适配器类重用现有的适配者类,而无需修改原有代码。

  一个对象适配器可以把多个不同的适配者类适配到同一个目标,也就是说,同一个适配器可以把适配者类和它的子类都适配到目标接口。

缺点

   对于对象适配器来说,更换适配器的实现过程比较复杂。

适用场景

  系统需要使用现有的类,而这些类的接口不符合系统的接口。

  想要建立一个可以重用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作。

  两个类所做的事情相同或相似,但是具有不同接口的时候。

  旧的系统开发的类已经实现了一些功能,但是客户端却只能以另外接口的形式访问,但我们不希望手动更改原有类的时候。

       使用第三方组件,组件接口定义和自己定义的不同,不希望修改自己的接口,但是要使用第三方组件接口的功能。

 

实现:

public abstract class Target {
    public void request() {
   System.out.println("普通请求!");
    }
}

//需要适配的类
public class Adaptee {
    public void specificRequest() {
   System.out.println("特殊的请求!");
    }
}

//适配器类,通过在内部包装一个Adaptee对象,把原接口转换成目标接口
public class Adapter extends Target {
    private Adaptee adaptee = new Adaptee();
    @Override
    public void request() {
   adaptee.specificRequest();
    }
}

//适配器客户端
public class AdapterClient {
   public static void main(String[] args) {
      Target target;
      target = new Adapter();
      target.request();
   }
}
输出:
特殊的请求!

Android 里的  ListView 和  RecyclerView 的 setAdapter() 方法就是使用了适配器模式。

备忘录模式(Memento)

在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样以后就可将该对象回复到原先保存的状态

Memento比较适合用于功能复杂的,但需要维护或记录属性历史的类,或者需要保存的属性只是众多属性中的一小部分时,Originator可以根据保存的Memento信息还原到前一状态。

如果在某个系统中使用命令模式时,需要实现命令的撤销功能,那么命令模式可以使用备忘录模式来存储可撤销操作的状态。

使用备忘录可以把复杂的对象内部信息对其他的对象屏蔽起来,从而可以恰当地保持封装的边界

//备忘录(Memento)类
  public class Memento {
    private String state;
    public Memento(String state) {this.state = state;}
    public String getState() {return state;}
    public void setState(String state) {this.state = state;}
}
//管理者(CareTaker)类:管理备忘录
  public class CareTaker {
    private Memento memento;
    public Memento getMemento() { return memento; }
    public void setMemento(Memento memento) { this.memento = memento; }
}
//发起人(Originator) 类
  public class Originator {
    private String state;
    public Memento createMemento() { return new Memento(this.state);}
    public void recoverMemento(Memento memento) { this.setState(memento.getState()); }
    public void show() {System.out.println("state = " + this.state); }
    public String getState() { return state; }
    public void setState(String state) { this.state = state; }
}
//客户端
  public class MementoClient {
    public static void main(String[] args) {
   // 设置初始状态
   Originator originator = new Originator();
   originator.setState("On");
   originator.show();
   // 管理者通过备忘录保存状态,由于有了很好地封装,可以隐藏Originator的实现细节
   CareTaker careTaker = new CareTaker();
   careTaker.setMemento(originator.createMemento());
   // 改变状态
   originator.setState("Off");
   originator.show();
   // 通过管理者从备忘录中恢复状态
   originator.recoverMemento(careTaker.getMemento());
   originator.show();
    }
}
  输出:
state = On
state = Off
state = On

组合模式(Componet)

将对象组合成树形结构以表示“部分-整体”的层次结构。组合模式使得用于对单个对象和组合对象的使用具有一致性

何时使用?

答:需求中是体现部分与整体层次的结构时,以及希望用户可以忽略组合对象与单个对象的不同,统一地使用组合结构中的所有对象时,就应该考虑用组合模式

//Component为组合中的对象声明接口,在适当情况下,实现所有类共有接口的默认行为。
public abstract class Component {
    protected String name;
    public Component(String name) { this.name = name; }
    public abstract void add(Component component);
    public abstract void remove(Component component);
    public abstract void display(int depth);
}



//Leaf在组合中表示叶节点对象,叶节点没有子节点,实际上它的add、remove方法都是多余的,实际上我觉得这里有点违反了接口隔离原则:每个接口中不存在子类用不到却必须实现的方法,也就是Component接口存在子类Leaf用不上却必须实现的方法
public class Leaf extends Component {
    public Leaf(String name) {
        super(name);
    }
    public void add(Component component) {
        System.out.println("cannot add to a leaf");
    }
    public void remove(Component component) {
        System.out.println("cannot remove from a leaf");
    }
    public void display(int depth) {
        // 通过“-”的数目显示级别
        System.out.println(StringUtil.repeatableString("-", depth) + this.name);
    }
}



//定义有枝节点行为,用来存储子部件
public class Composite extends Component {
    private List<Component> children = new ArrayList<Component>();
    public Composite(String name) { super(name); }
    public void add(Component component) { children.add(component); }
    public void remove(Component component) { children.remove(component); }
    public void display(int depth) {
       // 显示其枝节点名称,并对其下级进行遍历
       System.out.println(StringUtil.repeatableString("-", depth) + this.name);
        for (Component component : children) {
            component.display(depth + 2);
        }
    }
}

//客户端。通过Component接口操作组合部件的对象
public class CompositeClient {
    public static void main(String[] args) {
      // 生成树根,根上长出两叶Leaf A和Leaf B
      Composite root = new Composite("root");
      root.add(new Leaf("Leaf A"));
      root.add(new Leaf("Leaf B"));
      // 根上长出分支Composite X,分支上也有两叶Leaf X-A和Leaf X-B
      Composite compositeX = new Composite("Composite X");
      compositeX.add(new Leaf("Leaf X-A"));
      compositeX.add(new Leaf("Leaf X-B"));
      root.add(compositeX);
      // 在Composite X上再长出分支Composite X-Y,分支上也有两叶Leaf X-Y-A和Leaf X-Y-B
      Composite compositeXY = new Composite("Composite X-Y");
      compositeXY.add(new Leaf("Leaf X-Y-A"));
      compositeXY.add(new Leaf("Leaf X-Y-B"));
      compositeX.add(compositeXY);
      // 显示大树的样子
      root.display(1);
    }
}

打印出:
-root
---Leaf A
---Leaf B
---Composite X
-----Leaf X-A
-----Leaf X-B
-----Composite X-Y
-------Leaf X-Y-A
-------Leaf X-Y-B


 

程序中提到Leaf实现类实现了add与remove方法,而这两个方法对其本身并没有用处,这种方式叫透明方式,也就是说在Component中声明所有用来管理子对象的方法,其中包括add、remove等,这样实现Component接口的所有子类都具备了Add和remove,这样做的好处就是叶节点和枝节点对于外界没有区别,它们具备完全一致的行为接口。但问题也很明显,因为Leaf类本身不具备Add、remove方法的功能,所以实现它是没有意义的。

迭代器模式(Iterator)

提供一种方法顺序访问一个聚合对象中各个元素,而又不暴露该对象的内部表示。

Java等语言都已经对其进行了封装

程序介绍:用一个Aggregate里面存储一个list,带有控制list的方法,而Iterator里面包装这个Aggregate,将对Iterator的各个接口方法的操作重写为对Aggregate的操作。Aggregate里面有一个可以创建其对应的Iterator的方法

//聚集接口,可以理解为Iterable
public interface Aggregate<T> {
    public Iterator<T> createIterator();
}

//具体聚集类
public class ConcreteAggregate<T> implements Aggregate<T> {
    private List<T> items = new ArrayList<T>();
    @Override
    public Iterator<T> createIterator() {
       return new ConcreteIterator<T>(this);
    }
    public int count() { return items.size(); }
    public T getItems(int index) { return items.get(index); }
    public void setItems(T item) { items.add(item); }
}

//迭代器接口
public interface Iterator<T> {
    public T first();
    public T next();
    public boolean isDone();
    public T currentItem();
}

//具体迭代器类,给出一种具体迭代的实现方式。思考:迭代器表示的是一种迭代的行为,而聚集则是真正要被迭代的数据集合。之所以要将迭代器和聚集分开,就是为了将行为与数据分开。 可类比Java中Iterator与Iterable的关系进行理解
public class ConcreteIterator<T> implements Iterator<T> {
    private ConcreteAggregate<T> concreteAggregate;
    private int current = 0;
    public ConcreteIterator(ConcreteAggregate<T> concreteAggregate) {

this.setConcreteAggregate(concreteAggregate);

}
    @Override
    public T first() { return concreteAggregate.getItems(0); }
    @Override
    public T next() {
        current++;
        if (current < concreteAggregate.count()) {
            return concreteAggregate.getItems(current);
        }
        return null;
    }
    @Override
    public boolean isDone() {

return current >= concreteAggregate.count() ? true : false;

}
    @Override
    public T currentItem() { return concreteAggregate.getItems(current); }
    public ConcreteAggregate<T> getConcreteAggregate() { return concreteAggregate; }
    public void setConcreteAggregate(ConcreteAggregate<T> concreteAggregate) {

this.concreteAggregate = concreteAggregate;

}
    public int getCurrent() { return current; }
    public void setCurrent(int current) { this.current = current; }
}

//迭代器客户端
public class IteratorClient {
    public static void main(String[] args) {
      ConcreteAggregate<String> bus = new ConcreteAggregate<String>();
      bus.setItems("大鸟");
      bus.setItems("小菜");
      bus.setItems("行李");
      bus.setItems("老外");
      bus.setItems("公交内部员工");
      bus.setItems("小偷");
      Iterator<String> iterator = new ConcreteIterator<String>(bus);
      while (!iterator.isDone()) {
         System.out.println(iterator.currentItem() + "请买票!");
         iterator.next();
      }
    }
}
输出:
大鸟请买票!
小菜请买票!
行李请买票!
老外请买票!
公交内部员工请买票!
小偷请买票!

单例模式(Singleton)

保证一个类仅有一个实例,并提供一个访问它的全局访问点。

优点:让类自身负责保存它的唯一实例。这个类可以保证没有其他实例可以被创建,并且还提供了一个访问该实例的方法,这样就使得对唯一的实例可以严格地控制客户怎样以及何时访问它。

在实际应用中,线程池、缓存、日志对象、对话框对象常被设计成单例,选择单例模式就是为了避免不一致状态

懒汉式、饿汉式、synchronized懒或饿汉式、双检锁DCL、volatile方式(工作内存优化方式)、静态内部类方式、枚举类方式

//版本一:饿汉式
//特点:线程安全;在类初始化执行到静态属性时就分配了资源,有资源浪费问题;
static class Singleton1{	
	//或者将私有静态final成员设为公有成员,可省去getInstance公有函数
	private static final Singleton1 instance = new Singleton1();
	private Singleton1(){}
	public static Singleton1 getInstance() {
		return instance;
	}		
}

//版本二:懒汉式(非线程安全)
//特点:在第一次调用获取实例方法时分配内存,实现了懒加载;非线程安全;
static class Singleton2{	
	private static Singleton2 instance = null;
	private Singleton2(){}
	public static Singleton2 getInstance() {
		if (instance==null) {
			instance = new Singleton2();
		}
		return instance;
	}
}

//版本三:懒汉式变种(synchronized同步方法,支持多线程)
//特点:线程安全;synchronized而造成的阻塞致使效率低,而且很多的阻塞都是没必要的。
static class Singleton3{	
	private static Singleton3 instance = null;
	private Singleton3(){}
	//把synchronized加在getinstance上会导致很多不必要的阻塞,比如实例已经不为null
	public static synchronized Singleton3 getInstance() {
		if (instance == null ) {
			instance = new Singleton3();
		}	
		return instance;
	}
}

//版本四:双检锁DCL,支持多线程-懒汉式
//特点:线程安全;多进行一次if判断,看似不会有问题了
//但是无法解决生成汇编代码时指令重排的问题
static class Singleton4{	
	private static Singleton4 instance = null;
	private Singleton4(){}
	public static Singleton4 getInstance() {
		//双重null检验:使得原先检测到null而阻塞的B线程在A线程成功返回
		//instance后能够发现其已经返回instance
		if (instance == null ) {
			synchronized (Singleton4.class){
				if (instance == null )
				instance = new Singleton4();
			}
		}	
		return instance;
	}
}

//版本五:双检锁DCL,支持多线程-懒汉式
//特点:线程安全;多进行一次if判断,加入volatile修饰,优点是只有在第一次实例化时加锁,之后不会加锁,提升了效率,缺点写法复杂
//指令重排:编译器在生成汇编代码的时候会对流程顺序进行优化
//instance = new Singleton5()可主要分为三步:
	//1在堆空间里分配内存									memory = allocate(); 
	//2调用构造函数进行初始化 								init(memory);  
	//3把instance指向被分配的内存(此时instance不为null了)	instance = memory;    
//正常顺序为123,指令重排可能执行顺序为132,会造成已不为null但未执行构造函数的问题
//内存可见性:
//如果字段是被volatile修饰的,Java内存模型将在写操作后插入一个写屏障指令,在读操作前插入一个读屏障指令。
//这意味着:
	//1一旦完成写入,任何访问这个字段的线程将会得到最新的;
	//2在写入前,任何更新过的数据值是可见的,因为内存屏障会把之前的写入值都刷新到缓存。
//因此volatile可提供一定的线程安全,但不适用于写操作依赖于当前值的情况,如自增,自减
//简单来说,volatile适合这种场景:一个变量被多个线程共享,线程直接给这个变量赋值。
static class Singleton5{	
	private static volatile Singleton5 instance = null; //这里的instance在主内存
	private Singleton5(){}
	public static Singleton5 getInstance() {
		//双重null检验:使得原先检测到null而阻塞的B线程在A线程成功返回instance后能够发现其已经返回instance
		if (instance == null ) {//读取一次主内存
			synchronized (Singleton5.class){
				if (instance == null )//再次读取主内存
				instance = new Singleton5();
			}
		}	
		return instance;//写入主内存
	}
}

//版本六:双检锁DCL,支持多线程-懒汉式,volatile,并且加入优化
//因为volatile操作的是主内存的数据,主内存速度比工作内存的慢,
//所以可以设置一个局部实例在工作内存,以此减少与主内存的交互次数
static class Singleton6{	
	//这里的instance在主内存
	private static volatile Singleton6 instance = null;
	private Singleton6(){}
	public static Singleton6 getInstance() {
		//读取一次主内存,后序没有读取主内存的操作了
		Singleton6 result = instance;
		if (result == null ) {
			synchronized (Singleton6.class){
				if (result == null )
				instance = result = new Singleton6();
			}
		}	
		return instance;//写会主内存
	}
}



//版本七:静态内部类,支持多线程-懒汉
//特点:利用静态内部类(只有在出现它的引用时才被加载),完成懒加载;final保证线程安全,防止资源的浪费;
//类的加载顺序:http://blog.csdn.net/u012123160/article/details/53224469
//final的作用:
//1. 在构造函数内对一个final域的写入,与随后把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序。
//2. 初次读一个包含final域的对象的引用,与随后读这个final域,这两个操作之间不能重排序。
//扩展:static变量初始化遵循以下规则:
//1.静态变量会按照声明的顺序先依次声明并设置为该类型的默认值,但不赋值为初始化的值。
//2.声明完毕后,再按声明的顺序依次设置为初始化的值,如果没有初始化的值就跳过。
//static变量初始化参考:http://www.jb51.net/article/86629.htm
static class Singleton7{
	private static class Singleton7Holder{
		public static final Singleton7 instance = new Singleton7();
	}
	private Singleton7(){}
	public static Singleton7 getInstance() {
		return Singleton7Holder.instance;
	}
}

//版本八:通过枚举实现
//一个完美的单例需要做到:单例,懒加载,线程安全,防止反序列化产生新对象,防止反射攻击
//而枚举的特性保证了以上除了懒加载以外的所有要求,而且实现代码及其简单
//Enum的单例模式参考:http://www.jianshu.com/p/83f7958b0944
/*
 * 枚举类型特点:
	枚举类就是class,而且是一个不可以被继承的final类。
	其枚举值(INSTANCE)都是Singleton类型的类静态常量
	默认私有的构造方法,即使不写访问权限也是private。(假构造器,底层没有无参数的构造器)
	所有枚举常量都通过静态代码块来进行初始化,即在类加载期间就初始化
	大部分方法也都是final的,特别是clone、readObject、writeObject这三个方法,
	这三个方法和枚举通过静态代码块来进行初始化一起,它保证了枚举类型的不可变性,
	不能通过克隆,不能通过序列化和反序列化来复制枚举,这能保证一个枚举常量只是一个实例,即是单例的
 */
enum Singleton8{
    instance;
    private String attribute;
    void setAttribute(String attribute){
        this.attribute = attribute;
    }
    String getAttribute(){
        return this.attribute;
    }
}

如果单例模式不是用枚举类创建的,那么其无法防止其序列化过程破坏单例特性,因为在序列化过程会利用反射调用单例的私有构造方法生成新的对象,如果要避免这种破坏,就要在单例类里面添加方法readResolve()

问:是不是通过在构造方法里面抛出异常就可以了?

答:序列化时用的构造方法与我们平时写的构造方法并不是同一个

另外,为了防止通过反射调用私有构造方法生成单例对象,可以在构造方法里面加上标志位,使得第二次创建对象时抛出异常

具体参考:http://mp.weixin.qq.com/s/iXC47w4tMfpZzTNxS_JQOw

1.     有一个双重检验锁的单例模式(即上面的版本五或版本六)

2.     序列化测试代码如下

另外一种防止序列化的方法:在创建第二个实例的时候抛异常

显然枚举单例模式确实是很不错的选择,因此我们推荐使用它。但是这总不是万能的,对于android平台这个可能未必是最好的选择,在android开发中,内存优化是个大块头,而使用枚举时占用的内存常常是静态变量的两倍还多,因此android官方在内存优化方面给出的建议是尽量避免在android中使用enum。但是不管如何,关于单例,我们总是应该记住:线程安全,延迟加载,序列化与反序列化安全,反射安全是很重要的。

桥接模式(Bridge)

将抽象部分与它的实现部分分离,使它们都可以独立地变化。

这里并不是说让抽象类与其派生类分离,因为这没有任何意义,实现指的是抽象类和它的派生类用来实现自己的对象

由于实现的方式有多种,桥接模式的核心意图就是把这些实现独立出来,让它们各自地变化,就使得每种实现的变化不会影响其他实现,从而达到应对变化的目的

注意:桥接模式与设计模式在代码上的实现时及其类似甚至相同的,不同点在于适配器模式的适配器类与被适配器类是已经存在的,需要开发一个接口使得他们实现这个接口一起工作,而桥接模式是先定下接口策略,再让不同的类去实现它。

 


 

public abstract class Abstraction {
    //桥接模式的关键,使得Abstraction聚合Implementor
    protected Implementor implementor;//聚合关系
    private String name;
    public Abstraction(String name) {
       this.setName(name);
    }
    public void setImplementor(Implementor implementor) {
       this.implementor = implementor;
    }
    public void operation() {
       System.out.print("Abstraction-" + this.getName() + ": ");
       implementor.operation();
    }
    public String getName() {
       return name;
    }
    public void setName(String name) {
       this.name = name;
    }
}

class AbstractionA extends Abstraction {
    public AbstractionA(String name) {
       super(name);
    }
    @Override
    public void operation() {
       super.operation();
    }
}
class AbstractionB extends Abstraction {
    public AbstractionB(String name) {
       super(name);
    }
    @Override
    public void operation() {
       super.operation();
    }
}



public abstract class Implementor {
    public abstract void operation();
}

class ConcreteImplemtorA extends Implementor {
    @Override
    public void operation() {
       System.out.println("ConcreteImplemtorA的方法执行");
    }
}

class ConcreteImplemtorB extends Implementor {
    @Override
    public void operation() {
       System.out.println("ConcreteImplemtorB的方法执行");
    }
}



public class BridgeClient {
    public static void main(String[] args) {
      Abstraction a = new AbstractionA("A");
      a.setImplementor(new ConcreteImplemtorA());
      a.operation();
      a.setImplementor(new ConcreteImplemtorB());
      a.operation();
      Abstraction b = new AbstractionB("B");
      b.setImplementor(new ConcreteImplemtorA());
      b.operation();
      b.setImplementor(new ConcreteImplemtorB());
      b.operation();
      // 这样通过使用“组合/聚合复用原则”
      // 如果继续有AbstractionC ... 或者ConcreteImplemtorC ...
      // 只需要扩展类即可,不需要修改现有类,符合“开放-封闭”原则
    }
}
输出:
Abstraction-A: ConcreteImplemtorA的方法执行
Abstraction-A: ConcreteImplemtorB的方法执行
Abstraction-B: ConcreteImplemtorA的方法执行
Abstraction-B: ConcreteImplemtorB的方法执行

将以上代码更加具体化到现实情况的代码如下:

命令模式(Command)

将一个请求封装为一个对象,从而使你可以用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作

优点

1.降低对象之间的耦合度。

2.新的命令可以很容易地加入到系统中。

3.可以比较容易地设计一个组合命令。

4.调用同一方法实现不同的功能

缺点

使用命令模式可能会导致某些系统有过多的具体命令类。因为针对每一个命令都需要设计一个具体命令类,因此某些系统可能需要大量具体命令类,这将影响命令模式的使用。

 

//知道如何实施与执行一个与请求相关的操作,任何类都可能作为一个接收者。真正执行请求的地方!
interface Reciever {   //比喻为厨师
    public void action();
}
class RecieverA implements Reciever { //烤肉的厨师
    @Override
    public void action() {
   System.out.println("RecieverA执行请求!");
    }
}
class RecieverB implements Reciever { //炒菜的厨师
    @Override
    public void action() {
   System.out.println("RecieverB执行请求!");
    }
}
class RecieverC implements Reciever {  //烤鱼的厨师
    @Override
    public void action() {
   System.out.println("RecieverC执行请求!");
    }
}
//用来声明执行操作的接口
public abstract class Command {
    //命令类,服务员手里的订单,订单里面包含了客户想要什么厨师做的菜
    protected Reciever reciever;
    public Command(Reciever reciever) {
       this.reciever = reciever;
    }
    public abstract void execute();
}
// 将一个接收者对象绑定于一个动作,调用接收者相应的操作,以实现execute
class ConcreteCommand extends Command {
    public ConcreteCommand(Reciever reciever) {
       super(reciever);
    }
    @Override
    public void execute() {
        reciever.action();
    }
}
//要求该命令执行这个请求
public class Invoker {//服务员,拥有很多订单
    private List<Command> list = new ArrayList();
    public void addCommand(Command command) {
        list.add(command);
    }
    public void executeCommand() {
        for (Command command:list) {
            command.execute();
        }
    }
}
//创建一个具体命令对象并设定它的接收者
public class CommandClient {
    public static void main(String[] args) {
      Reciever recieverA = new RecieverA();
      Reciever recieverB = new RecieverB();
      Reciever recieverC = new RecieverC();
      Command commandA = new ConcreteCommand(recieverA);
      Command commandB = new ConcreteCommand(recieverB);
      Command commandC = new ConcreteCommand(recieverC);
      Invoker invoker = new Invoker();
      invoker.addCommand(commandA);
      invoker.addCommand(commandB);
      invoker.addCommand(commandC);
      invoker.executeCommand();
    }
}
输出:
RecieverA执行请求!
RecieverB执行请求!
RecieverC执行请求!

职责链模式(ResponsibilityChain)

使多个对象都有机会处理请求,从而避免请求的发送者和接受者之间的耦合关系,

将这个对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理他为止。

 

案例解释:申请聚餐费用的管理,申请聚餐费用的大致流程一般是,由申请人先填写申请单,然后交给领导审批,如果申请批准下来,领导会通知申请人审批通过,然后申请人去财务领取费用,如果没有批准下来,领导会通知申请人审批未通过,此事也就此作罢。

不同级别的领导,对于审批的额度是不一样的,比如,项目经理只能审批500元以内的申请;部门经理能审批1000元以内的申请;而总经理可以审核任意额度的申请。

 

职责链灵活在哪

1. 改变内部的传递规则

在内部,项目经理完全可以跳过人事部到那一关直接找到总经理。

每个人都可以去动态地指定他的继任者。

2. 可以从职责链任何一关开始。

如果项目经理不在,可以直接去找部门经理,责任链还会继续,没有影响。

3.用与不用的区别

不用职责链的结构,我们需要和公司中的每一个层级都发生耦合关系。

如果反映在代码上即使我们需要在一个类中去写上很多丑陋的if….else语句。

如果用了职责链,相当于我们面对的是一个黑箱,我们只需要认识其中的一个部门,然后让黑箱内部去负责传递就好了

 

纯的与不纯的责任链模式

一个纯的责任链模式要求一个具体的处理者对象只能在两个行为中选择一个:一是承担责任,而是把责任推给下家。不允许出现某一个具体处理者对象在承担了一部分责任后又把责任向下传的情况。

在一个纯的责任链模式里面,一个请求必须被某一个处理者对象所接收;在一个不纯的责任链模式里面,一个请求可以最终不被任何接收端对象所接收。

纯的责任链模式的实际例子很难找到,一般看到的例子均是不纯的责任链模式的实现。

 

//处理请求的接口
public abstract class Handler {
    protected Handler successor;
    public void setSuccessor(Handler successor) {
      this.successor = successor;
    }
    public abstract void handleRequest(int request);
}

// 具体处理者类,处理它所负责的请求,可访问它的后继者,如果可处理该请求,则处理,否则转给它的后继者处理
class ConcreteHandlerA extends Handler {
    @Override
    public void handleRequest(int request) {
      if (request >= 0 && request <= 10) {
         System.out.println(this.getClass().getName() + "处理了请求" + request);
      } else if (successor != null) {
         successor.handleRequest(request);
      }
    }
}

class ConcreteHandlerB extends Handler {
    @Override
    public void handleRequest(int request) {
      if (request > 10 && request <= 20) {
         System.out.println(this.getClass().getName() + "处理了请求" + request);
      } else if (successor != null) {
         successor.handleRequest(request);
      }
    }
}
class ConcreteHandlerC extends Handler {
    @Override
    public void handleRequest(int request) {
      if (request > 20 && request <= 30) {
         System.out.println(this.getClass().getName() + "处理了请求" + request);
      } else if (successor != null) {
         successor.handleRequest(request);
      }
    }
}

//链上的具体处理者对象提交请求
public class Client {
    public static void main(String[] args) {
      Handler handlerA = new ConcreteHandlerA();
      Handler handlerB = new ConcreteHandlerB();
      Handler handlerC = new ConcreteHandlerC();
      handlerA.setSuccessor(handlerB);
      handlerB.setSuccessor(handlerC);
      int[] requests = { 2, 14, 5, 6, 8, 23, 12, 21 };
      for (int i : requests) {
         handlerA.handleRequest(i);
      }
    }
}
输出:
ConcreteHandlerA处理了请求2
ConcreteHandlerB处理了请求14
ConcreteHandlerA处理了请求5
ConcreteHandlerA处理了请求6
ConcreteHandlerA处理了请求8
ConcreteHandlerC处理了请求23
ConcreteHandlerB处理了请求12
ConcreteHandlerC处理了请求21


 

 

中介者模式(Mediator)

又见调停者模式:用一个中介对象来封装一系列的对象交互。中介者使各对象不需要现实地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。

优点:减少了各个Colleague的耦合,使得可以独立地改变和复用各个Colleague和Mediator,其次,由于把对象如何协作进行了抽象,将中介作为一个独立的概念并将其封装在一个对象中,这样关注的对象就从对象各自本身的行为转移到它们之间的交互上来,也就是站在一个更宏观的角度去看待系统。

缺点:由于ConcreteMediator控制了集中化,也是就把交互复杂性变为了中介者的复杂性,这就使得中介者会变得比任何一个ConcreteColleague都复杂。

//抽象中介者类
public abstract class Mediator {
    public abstract void send(String message, Colleague colleague);
}

class ConcreteMediator extends Mediator {
    // 需要了解所有的具体同事对象
    private ConcreteColleague1 c1;
    private ConcreteColleague2 c2;
    public ConcreteColleague1 getC1() {
       return c1;
    }
    public void setC1(ConcreteColleague1 c1) {
       this.c1 = c1;
    }
    public ConcreteColleague2 getC2() {
       return c2;
    }
    public void setC2(ConcreteColleague2 c2) {
       this.c2 = c2;
    }
    @Override
    public void send(String message, Colleague colleague) {
       // 重写发送信息的方法,根据对象做出选择判断,通知对象
        if (colleague == c1) {
            c2.notifyMsg(message);
        } else {
            c1.notifyMsg(message);
        }
    }
}

//抽象同事类
public abstract class Colleague {
    protected Mediator mediator;
    public Colleague(Mediator mediator) {
        this.mediator = mediator;
    }
    public abstract void sendMsg(String message);
    public abstract void notifyMsg(String message);
}

class ConcreteColleague1 extends Colleague {
    public ConcreteColleague1(Mediator mediator) {
       super(mediator);
    }
    @Override
    public void sendMsg(String message) {
       mediator.send(message, this);
    }
    @Override
    public void notifyMsg(String message) {
       System.out.println("同事1得到消息:" + message);
    }
}

class ConcreteColleague2 extends Colleague {
    public ConcreteColleague2(Mediator mediator) {
       super(mediator);
    }
    @Override
    public void sendMsg(String message) {
       mediator.send(message, this);
    }
    @Override
    public void notifyMsg(String message) {
       System.out.println("同事2得到消息:" + message);
    }
}

//客户端
public class MediatorClient {
    public static void main(String[] args) {
      ConcreteMediator concreteMediator = new ConcreteMediator();
      // 让两个具体同事类认识中介者对象
      ConcreteColleague1 concreteColleague1 = new ConcreteColleague1(
         concreteMediator);
      ConcreteColleague2 concreteColleague2 = new ConcreteColleague2(
         concreteMediator);
      // 让中介者认识各个具体同事类对象
      concreteMediator.setC1(concreteColleague1);
      concreteMediator.setC2(concreteColleague2);
      // 具体同事类对象的消息发送都是通过中介者对象转发
      concreteColleague1.sendMsg("吃过饭了没有?");
      concreteColleague2.sendMsg("没有呢,你打算请客?");
    }
}
输出:
同事2得到消息:吃过饭了没有?     
同事1得到消息:没有呢,你打算请客?

享元模式(FlyWeight)

运用共享技术有效地支持大量细粒度的对象。

也就是说在一个系统中如果有多个相同的对象,那么只共享一份就可以了,不必每个都去实例化一个对象。

享元模式可以避免大量非常相似类的开销。在程序设计中,有时需要生成大量细粒度的类实例来表示数据。如果能发现这些实例除了几个参数外基本上都是相同的,有时就能够大幅度地减少需要实例化的类的数量。如果能把那些参数移到类实例的外面,在方法调用时将它们传递进来,就可以通过共享大幅度地减少单个实例的数目。

 

 

什么时候用享元模式:如果一个应用程序使用了大量的对象,而大量的这些对象造成了很大的存储开销就应该考虑使用;还有就是对象的大多数状态可以外部状态,如果删除对象的外部状态,那么可以用相对较少的共享对象取代很多组对象,此时可以考虑使用享元模式。

String类型,数据库连接池,线程池等即是用享元模式的应用

 

 

//所有具体享元类的超类,接受并作用于外部状态
  public abstract class FlyWeight {
    public abstract void operation(int extrinsicState);
  }
  
  class ConcreteFlyWeight extends FlyWeight {
    @Override
    public void operation(int extrinsicState) {
       System.out.println("具体FlyWeight:" + extrinsicState);
    }
}
  
  class UnsharedConcreteFlyWeight extends FlyWeight {
    @Override
    public void operation(int extrinsicState) {
       System.out.println("不共享的具体FlyWeight:" + extrinsicState);
    }
}
//享元工厂
  public class FlyWeightFactory {
    private HashMap<String, FlyWeight> flyWeights = new HashMap<String, FlyWeight>();
    public FlyWeight getFlyWeight(String key) {
      if (!flyWeights.containsKey(key)) {
         flyWeights.put(key, new ConcreteFlyWeight());
      }
      return flyWeights.get(key);
    }
}
public class FlyWeightClient {
    public static void main(String[] args) {
   int extrinsicState = 22;
   FlyWeightFactory f = new FlyWeightFactory();
   FlyWeight fx = f.getFlyWeight("X");
   fx.operation(--extrinsicState);
   FlyWeight fy = f.getFlyWeight("Y");
   fy.operation(--extrinsicState);
   FlyWeight fz = f.getFlyWeight("Z");
   fz.operation(--extrinsicState);
   FlyWeight uf = new UnsharedConcreteFlyWeight();
   uf.operation(--extrinsicState);
    }
}
  输出:
  具体FlyWeight:21   
  具体FlyWeight:20   
  具体FlyWeight:19   
  不共享的具体FlyWeight:18
  
  

 

解释器模式(Interpreter)

给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。

如果一种特定类型的问题发生的频率足够高,那么就可以考虑将该问题的各个实例表述为一个简单语言中的句子。也就是说,通过构建一个解释器,该解释器解释这些句子来解决该问题

//声明一个抽象的解释操作,这个接口为抽象语法树中所有的节点所共享
public abstract class AbstractExpression {
    public abstract void interpret(Context context);
}
//实现与文法中的终结符相关联的解释操作,文法中每一个终结符都有一个具体终结表达式与之相对应
class TerminalExpression extends AbstractExpression{
    @Override
    public void interpret(Context context) {
        //这里根据传入的输入对象进行翻译,
        context.setOutput("“这是英语”");
        System.out.println("终端解释器:"+"原始信息:"
                +context.getInput()+"将会被翻译为:"+context.getOutput());
    }
}
class NonTerminalExpression extends AbstractExpression{
    @Override
    public void interpret(Context context) {
        //这里根据传入的输入对象进行翻译,
        context.setOutput("“123456”");
        System.out.println("非终端解释器:"+"原始信息:"
                +context.getInput()+"将会被翻译为:"+context.getOutput());
    }
}
//包含解释器之外的一些全局信息
public class Context {
    private String input;
    private String output;
    public String getInput() { return input; }
    public void setInput(String input) { this.input = input; }
    public String getOutput() { return output; }
    public void setOutput(String output) { this.output = output; }
}
//构建表示该文法定义的语言中一个特定的句子的抽象语法树,调用解释操作
public class ExpressionClient {
    public static void main(String[] args) {
        Context context = new Context();
        context.setInput("this is English");
        List<AbstractExpression> list = new ArrayList<AbstractExpression>();
        list.add(new TerminalExpression());
        list.add(new NonTerminalExpression());
        list.add(new TerminalExpression());
        list.add(new TerminalExpression());
        for (AbstractExpression expression : list) {
            expression.interpret(context);
        }
    }
}
输出:
终端解释器:原始信息:this is English将会被翻译为:“这是英语”
非终端解释器:原始信息:this is English将会被翻译为:“123456” 
终端解释器:原始信息:this is English将会被翻译为:“这是英语”
终端解释器:原始信息:this is English将会被翻译为:“这是英语”

访问者模式(Visitor)

表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作

访问者模式适用于数据结构相对稳定的系统

目的:把处理从数据结构分离出来。如果系统有比较稳定的数据结构,又有易于变化的算法的话,使用访问者模式就是比较合适的,因为访问者模式使得算法操作的增加变得容易

优点:增加新的操作很容易,因为增加新的操作就意味着增加一个新的访问者。访问者模式将有关的行为集中到一个访问者对象中。

缺点:增加新的数据结构变得困难了

事实上,我们很难找到数据结构不变化的情况,所以使用访问者模式的机会也就不太多了。必须在真正需要它的时候才考虑使用它,不能滥用

//为该对象结构中ConcreteElement的每一个类声明一个Visit操作
public abstract class Visitor {   //抽象状态
    public abstract void visitConcreteElementA(ConcreteElementA concreteElementA);
    public abstract void visitConcreteElementB(ConcreteElementB concreteElementB);
}

class ConcreteVisitor1 extends Visitor {  //状态:成功
    @Override
    public void visitConcreteElementA(ConcreteElementA concreteElementA) {  //男人的反应
       System.out.println(concreteElementA.getClass().getSimpleName() + "被"
          + this.getClass().getSimpleName() + "访问");
    }
    @Override
    public void visitConcreteElementB(ConcreteElementB concreteElementB) {  //女人的反应
        System.out.println(concreteElementB.getClass().getSimpleName() + "被"
            + this.getClass().getSimpleName() + "访问");
    }
}

class ConcreteVisitor2 extends Visitor {   //状态:失败
    @Override
    public void visitConcreteElementA(ConcreteElementA concreteElementA) {  //男人的反应
        System.out.println(concreteElementA.getClass().getSimpleName() + "被"
            + this.getClass().getSimpleName() + "访问");
    }
    @Override
    public void visitConcreteElementB(ConcreteElementB concreteElementB) {   //女人的反应
        System.out.println(concreteElementB.getClass().getSimpleName() + "被"
            + this.getClass().getSimpleName() + "访问");
    }
}

//定义一个accept操作,它以一个访问者为参数
public abstract class Element { //抽象人
    public abstract void accept(Visitor visitor);
}
class ConcreteElementA extends Element {   //具体男人
    @Override
    public void accept(Visitor visitor) {  //男人接受状态后的反应
       visitor.visitConcreteElementA(this);
    }
}
class ConcreteElementB extends Element {  //具体女人
    @Override
    public void accept(Visitor visitor) {  //女人接受状态后的反应
       visitor.visitConcreteElementB(this);
    }
}

//提供一个高层的接口以允许访问者访问它的元素
public class ObjectStructure {   //定义一个容器用来存储男人、女人这种不变的数据结构
    private List<Element> elements = new ArrayList<Element>();
    public void attach(Element element) {
       elements.add(element);
    }
    public void detach(Element element) {
       elements.remove(element);
    }
    public void accept(Visitor visitor) {  //遍历所有数据结构执行他们的方法
        for (Element element : elements) {
            element.accept(visitor);
        }
    }
}

public class VisitorClient {
    public static void main(String[] args) {
      ObjectStructure o = new ObjectStructure();
      o.attach(new ConcreteElementA());
      o.attach(new ConcreteElementB());
      ConcreteVisitor1 visitor1 = new ConcreteVisitor1();
      ConcreteVisitor2 visitor2 = new ConcreteVisitor2();
      o.accept(visitor1);
      o.accept(visitor2);
    }
}
输出:
ConcreteElementA被ConcreteVisitor1访问
ConcreteElementB被ConcreteVisitor1访问
ConcreteElementA被ConcreteVisitor2访问
ConcreteElementB被ConcreteVisitor2访问


 

设计模式总结

创建型模式

工厂方法:定义一个用于创建对象的接口,让子类决定实例化哪一个类,工厂模式使一个类的实例化延迟到其子类

抽象工厂:提供一个创建一系列或相关依赖对象的接口,而无需指定它们具体的类,其比工厂方法仅多了一个功能

通常设计应该是从工厂方法开始,当设计者发现需要更大的灵活性时,设计便会向其他创建型模式演化。当设计者在设计标准之间进行权衡的时候,了解多个创建型模式可以给设计者更多的选择余地

建造者模式:将一个复杂对象的创建与它的表示分离,使得同样的创建过程可以创建不同的表示

将一个复杂对象的创建与它的表示分离,这就可以很容易地改变一个产品的内部表示,并且使得构造代码和表示代码分开。这样对于客户来说,它无需关心产品的创建过程,而只要告诉建造者需要什么,就能用同样的构建过程创建不同的产品给客户

原型模式:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象

建立相应数目的原型并克隆它们通常比每次用合适的状态手工实例化该类更方便一些

单例模式:保证一个类仅有一个实例,并提供一个访问它的全局访问点

让类自身负责保存它的唯一实例。这个类可以保证没有其他实例可以被创建,并且还提供了一个访问该实例的方法,这样就使得对唯一的实例可以严格地控制客户怎样以及何时访问它。

为什么需要创建型模式?创建型模式隐藏了这些类的实例是如何被创建和放在一起,整个系统关于这些对象所知道的是由抽象类所定义的接口,这样,创建型模式在创建了什么、谁创建它、它是怎么被创建的,以及何时创建这些方面提供了很大的灵活性

怎么理解内聚与耦合?内聚性描述的是一个例程内部组成部分之间相互联系的紧密程度。而耦合性描述的是一个例程与其他例程之间联系的紧密程度。软件开发的目标应该是创建这样的例程:内部完整,也就是高内聚,而与其他例程之间的联系则是小巧、直接、可见、灵活的,这就是松耦合,即高内聚,松耦合

结构型模式

适配器模式:将一个类的接口转换成客户希望的另外一个接口。适配器使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。

想使用一个已经存在的类,而它的接口不符合要求,或者希望创建一个可以复用的类,该类可以与其他不相关的类或不可预见的类协同工作。

桥接模式:将抽象部分与它的实现部分分离,使它们都可以独立地变化。

继承体系中,有两个甚至多个方向的变化,那么就解耦这些不同方向的变化,通过对象组合的方式,把两个角色之间的继承关系改为组合关系,从而使这两者可以应对各自独立的变化,正符合合成复用原则,找出变化并封装之。

组合模式:将对象组合成树形结构以表示“部分-整体”的层次结构,组合模式使得用于对单个对象和组合对象的使用具有一致性。

擅长于表示对象的部分与整体的层次结构,客户可以一致地使用组合结构和单个对象。任何用到基本对象的地方都可以使用组合对象

装饰者模式:动态地给一个对象添加一些额外的职责,就增加的功能来说,装饰者模式相比生成子类更加灵活

子类多半只是为某个对象增加一些职责,此时通过装饰的方式,可以更加灵活、以动态、透明的方式给单个对象添加职责,并在不需要时,撤销相应的职责。

外观模式:为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用

让一个软件中的子系统间的通信和相互依赖关系达到最小,而具体方法就是引入一个外观对象,它为子系统间提供了一个单一而简单的屏障

享元模式:运用共享技术有效地支持大量细粒度的对象

代理模式:为其他对象提供一种代理以控制对这个对象的访问

代理与外观的主要区别:代理对象代表一个单一对象而外观对象代表一个子系统;代理的客户对象无法直接访问目标对象,由代理提供对单独的目标对象的访问控制,而外观的客户对象可以直接访问子系统中的各个对象,但通常由外观对象提供对子系统各元件功能的简化的共同层次的调用接口

代理与适配器的主要区别:代理是一种原来对象的代表,其他需要与这个对象打交道的操作都是和这个代表交涉。而适配器则不需要虚构出一个代表者,只需要应付特定使用目的,将原来的类进行一些组合

代理模式、适配器模式与外观模式、桥接模式区别:

       代理模式:代理者和被代理者都实现了共同的接口,代理者又持有接口的引用存储被代理者

       适配器模式:因为接口不同,为了让用户使用到统一的接口,把原先的对象通过适配器让用户统一的使用,大多数运用在代码维护的后期,或者借用第三方库的情况下

       外观模式:是大家经常无意中使用的,就是把错综复杂的子系统关系封装起来,然后提供一个简单的接口给客户使用,就类似于一个转接口,可以想象成一个漏斗,中间细的那一段,越细耦合度越低,外观模式就是为了降低耦合度。

       桥接模式:桥接模式与适配器模式在代码上的实现是极其类似甚至相同的,不同点在于适配器模式的适配器类与被适配器类是已经存在的,需要开发一个接口使得他们实现这个接口一起工作,而桥接模式是先定下接口策略,再让不同的类去实现它。

行为型模式

观察者模式:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新

 

模板方法:定义一个操作的算法骨架,而将一些步骤延迟到子类中,模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤

由一个抽象类组成,这个抽象类定义了需要覆盖的可能有不同实现的模板方法,每个从这个抽象类派生的具体类将为此模板实现新方法

命令模式:将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化,可以对请求排队或记录请求日志,以及支持可撤销的操作。

用于分离请求者与实现者,将调用操作的对象与知道如何实现该操作的对象解耦,发送者可以在发送完就完事了。可以在不同时刻指定、排列和执行请求,可在实施操作前将状态存储起来,以便支持取消/重做的操作。还可以记录整个操作的日志,也可以支持事务

 

状态模式:允许一个对象在其内部状态改变时改变它的行为,让对象看起来似乎修改了它的类

对于条件分支的扩展性变化,状态模式提供了一个更好的办法来组织与特定状态相关的代码,决定状态转移的逻辑不在单块的if或switch中,而是分布在各个状态子类之间,由于所有与状态相关的代码都存在于某个状态子类中,所以通过定义新的子类可以很容易地增加新的状态和转换

 

职责链模式:使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为之

有多个对象可以处理一个请求,哪个对象处理该请求实现并不知道,要在运行时刻自动确定,此时,最好的办法就是让请求发送者与具体处理者分离,让客户在不明确指定接受者的情况下,提交一个请求,然后由所有能处理这请求的对象连成一条链,并沿着这条键传递该请求,直到有一个对象处理它为止

解释器模式:给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子

如果一种特定类型的问题发生的频率足够高,那么就可以考虑将该问题的各个实例表述为一个简单语言中的句子。也就是说,通过构建一个解释器,该解释器解释这些句子来解决该问题

 

中介者模式:用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互

将集体行为封装一个单独的中介者对象来避免这个问题,中介者负责控制和协调一组对象间的交互。中介者充当一个中介以使组中的对象不再相互显式引用。这些对象仅知道中介者,从而减少了相互连接的数目,符合最小知道原则

 

访问者模式:作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作

访问者增加具体的Element是困难的,但增加依赖于复杂对象结构的构件的操作就变得容易。仅需增加一个新的访问者即可在一个对象结构上定义一个新的操作

 

策略模式:定义一系列的算法,把它们一个个封装起来,并且使它们可相互替换。本模式使得算法可独立于使用它的客户而变化

 

备忘录模式:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样以后就可将该对象恢复到原先保存的状态

可以避免暴露一些只应由对象A管理却又必须存储在对象A之外的信息。备忘录模式把可能很复杂的对象A的内部信息对其他对象屏蔽起来,从而保持了封装边界

迭代器模式:提供一种方法顺序访问一个聚合对象中各个元素,而又不需暴露该对象的内部表示

关键思想是将对列表的访问和遍历从列表对象中分离出来并放入一个迭代器对象中,迭代器类定义了一个访问该列表元素的接口。迭代器对象负责跟踪当前的元素,并且知道哪些元素已经遍历过了

针对案例的进一步总结:

创建型模式

简单工厂模式:不能算是创建型模式

工厂方法模式:抽象工厂创建单一产品,一个待实现方法

抽象工厂模式:象工厂创建多个产品,多个待实现方法

建造者模式:麦当劳的汉堡包永远不会少加盐,摆摊的每次炒的东西味道可能有变化,一个指导类指引如何建造一个对象

单例模式:

原型模式:使用clone浅拷贝与深拷贝

结构型模式

适配器模式:让被适配器adaptee实现一个接口从而与适配器Adapter(也实现接口)一起工作,Adapter中持有adaptee引用,需要新建一个Adapter类

桥接模式:≈适配器模式:先定下接口规则,再去适配。如Shape抽象类(adaptee),不同shape如circle、triangle具体类的Draw()动作,定义一个painter(adapter),持有adaptee引用,并在方法中调用adaptee方法

组合模式:子类与父类拥有一样的方法,如树枝、树叶

装饰者模式:目标类实现接口,decorator实现接口并持有目标引用,在调用接口方法的时候添加装饰方法再调用目标引用的接口方法,其实就是适配器模式+额外的代码

外观模式:买基金问题,将不擅长的事情交给擅长的人来做,封装复杂事情实现简单化

享元模式:用set实现字符A,B,C这些仅仅占用一个内存

代理模式:Proxy,客户无法直接调用到服务端的内容,通过代理调用

行为型模式

观察者模式:劫匪、警察、保镖、运钞车问题

模板方法:安卓中的onCreate、试卷印刷中的一些固定部分可以先实现,可变部分延迟到子类实现,Java中的AQS

命令模式:厨师、服务员、订单问题

状态模式:每次调用自动更换状态从而表现不同的结果

职责链模式:申请报销时要根据金额大小让不同的部门处理

解释器模式:翻译问题

中介者模式:两个人同时聊天需要都认识中介者,中介者也要认识两个同事

访问者模式:

策略模式:提供排序算法接口,但是有多个不同的子类实现

备忘录模式:

迭代器模式:各种语言中都已经有了迭代的具体实现

参考链接:

23种设计模式汇总整理_一个本科小生的奋斗史-CSDN博客_设计模式

https://www.jianshu.com/p/93bc5aa1f887

  • 51
    点赞
  • 245
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值