设计模式
前言
模式似乎与传统的分析、设计和实现的思维方式不同。相反,模式在程序中体现了一个完整的思想,因此它有时会出现在分析阶段或高级设计阶段。因为模式在代码中有一个直接的实现,所以你可能不会期望模式在低级设计或实现之前出现(而且通常在到达这些阶段之前,你不会意识到需要一个特定的模式)。模式的基本概念也可以看作是程序设计的基本概念:添加抽象层。当你抽象一些东西的时候,就像在剥离特定的细节,而这背后最重要的动机之一是:
将易变的事物与不变的事物分开
另一种方法是,一旦你发现程序的某些部分可能因某种原因而发生变化,你要保持这些变化不会引起整个代码中其他变化。 如果代码更容易理解,那么维护起来会更容易。
设计模式的目标是隔离代码中的更改
模式分类
设计模式有两种分类方法,即根据模式的目的来分和根据模式的作用的范围来分。
- 创建型模式:如何创建对象。 这通常涉及隔离对象创建的细节,这样你的代码就不依赖于具体的对象的类型,因此在添加新类型的对象时不会更改。它的主要特点是“将对象的创建与使用分离”。《设计模式》 中提供了单例、原型、工厂方法、抽象工厂、建造者等 5 种创建型模式。
- 结构型模式:设计对象以满足特定的项目约束。它们处理对象与其他对象连接的方式,以确保系统中的更改不需要更改这些连接。用于描述如何将类或对象按某种布局组成更大的结构,《设计模式》 中提供了代理、适配器、桥接、装饰、外观、享元、组合等 7 种结构型模式。
- 行为型模式:用于描述类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,以及怎样分配职责。《设计模式》 中提供了模板方法、策略、命令、职责链、状态、观察者、中介者、迭代器、访问者、备忘录、解释器等 11 种行为型模式。
根据模式是主要用于类上还是主要用于对象上来分,这种方式可分为类模式和对象模式两种。
- 类模式:用于处理类与子类之间的关系,这些关系通过继承来建立,是静态的,在编译时刻便确定下来了。《设计模式》 中的工厂方法、(类)适配器、模板方法、解释器属于该模式。
- 对象模式:用于处理对象之间的关系,这些关系可以通过组合或聚合来实现,在运行时刻是可以变化的,更具动态性。《设计模式》 中除了以上 4 种,其他的都是对象模式。
设计原则
在进行软件系统设计时所要遵循的一些经验准则,应用该准则的目的通常是为了避免某些经常出现的设计缺陷。目前,较为公认的设计原则包括:开放-封闭原则、依赖倒置原则、里氏替换原则、接口隔离原则等。
单一职责原则
一个类(模块)只负责一个功能领域中的相应职责。就一个类(模块)而言,应该只有一个引起它变化的原因。
往往在软件开发中,随着需求的不断增加,可能会给原来的类添加一些本来不属于它的一些职责,从而违反了单一职责原则。如果我们发现当前类的职责不仅仅有一个,就应该将本来不属于该类真正的职责分离出去。不仅仅是类,函数也要遵循单一职责原则,即一个函数制作一件事情。如果发现一个函数里面有不同的任务,则需要将不同的任务以另一个函数的形式分离出去。从此来实现代码的高内聚、低耦合。
开闭原则
对扩展开放,对修改关闭。
添加一个新的功能,应该是通过在已有代码基础上扩展代码(新增模块、类、方法、属性等),而非修改已有代码(修改模块、类、方法、属性等)的方式来完成。增加了程序的可扩展性,同时也降低了程序的维护成本。
依赖倒置原则
高层模块不应该依赖低层模块,它们都应该依赖其抽象。抽象不应该依赖细节,细节应该依赖抽象(依赖于抽象接口,不要依赖于具体实现)。
通过抽象来搭建框架,建立类和类的关联,以减少类间的耦合性。而且以抽象搭建的系统要比以具体实现搭建的系统更加稳定,扩展性更高,同时也便于维护。
接口分离原则
使用多个专门的接口,而不使用单一的总接口,即客户端不应该依赖那些它不需要的接口(多个特定的客户端接口要好于一个通用性的总接口)。
不建立庞大臃肿的接口,应尽量细化接口,将一个臃肿的接口分割为若干个小接口,通过小接口的不同组合可以满足更多的需求。接口责任划分更加明确,符合高内聚低耦合的思想。
迪米特法则
迪米特法则(Law of Demeter, LoD)又叫作最少知识原则,是1987年秋天由lan holland在美国东北大学一个叫做迪米特的项目设计提出的。一个类对于其他类知道的越少越好,就是说一个对象应当对其他对象有尽可能少的了解,只和朋友通信,不和陌生人说话(只和你最亲近的朋友交谈)。
每个对象都会与其他对象有耦合关系,只要两个对象之间有耦合关系,我们就说这两个对象之间是朋友关系。一个类应该只和它的成员变量,方法的输入,返回参数中的类作交流,而不应该引入其他的类(间接交流)。
里氏替换原则
任何基类可以出现的地方,子类一定可以出现(子类可以扩展父类的功能,但不能改变父类原有的功能。)。
将一个基类对象替换成它的子类对象,程序将不会产生任何错误和异常,反过来则不成立,如果是子类对象的话,那么它不一定能够使用基类对象。里氏代换原则是对 开闭原则 的补充。实现 开闭原则 的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。
合成复用原则
合成复用原则又称为组合/聚合复用原则(Composition/Aggregate Reuse Principle, CARP),要求在软件复用时,要尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现。如果要使用继承关系,则必须严格遵循里氏代换原则。合成复用原则同里氏替换原则相辅相成的,两者都是开闭原则的具体实现规范。
通过继承来进行复用,由于基类的内部细节通常对子类来说是可见的,如果基类发生改变,那么子类的实现也不得不发生改变。合成复用原则可以将已有的对象(也可称为成员对象)纳入到新对象中,使之成为新对象的一部分,因此新对象可以调用已有对象的功能,这样做可以使得成员对象的内部实现细节对于新对象不可见,相对继承关系而言,其耦合度相对较低,成员对象的变化对新对象的影响不大。合成复用原则可以在运行时动态进行,新对象可以动态地引用与成员对象类型相同的其他对象。
创建型模式
将对象的创建与使用分离
工厂模式(简单工厂、工厂方法、抽象工厂)
- 简单工厂模式
通常创建的对象称为“产品”,把创建产品的对象称为“工厂”。简单工厂模式中创建实例的方法通常为静态(static)方法,所以简单工厂模式(Simple Factory Pattern)又叫作静态工厂方法模式(Static Factory Method Pattern)。
以生产产品为例,示例代码如下:
//产品
public class Product {
public Product (){
System.out.println("产品");
}
}
//产品A
public class ProductA extends Product{
public ProductA() {
System.out.println("产品A");
}
}
//产品B
public class ProductB extends Product {
public ProductB() {
System.out.println("产品B");
}
}
//工厂
public class Factory {
public static Product create(String type){
if("A".equals(type)){
return new ProductA();
}else if("B".equals(type)){
return new ProductB();
}
return null;
}
public static void main(String[] args) {
Product product = Factory.create("A");
/** Output:
* 产品
* 产品A
*/
}
}
简易结构图:
简单工厂模式每增加一个产品就要增加一个具体产品类和一个对应的具体工厂类,这增加了系统的复杂度,违背了“开闭原则”。
- 工厂方法模式
“工厂方法模式”是对简单工厂模式的进一步抽象化,其好处是可以使系统在不修改原来代码的情况下引进新的产品,即满足开闭原则。
以生产产品为例,示例代码如下:
//产品
public class Product {
public Product (){
System.out.println("产品");
}
}
//产品A
public class ProductA extends Product{
public ProductA() {
System.out.println("产品A");
}
}
//产品B
public class ProductB extends Product {
public ProductB() {
System.out.println("产品B");
}
}
//工厂
public abstract class Factory {
public abstract Product create();
}
//工厂A
public class FactoryA extends Factory{
@Override
public ProductA create() {
return new ProductA();
}
}
//工厂B
public class FactoryB extends Factory{
@Override
public ProductB create() {
return new ProductB();
}
public static void main(String[] args) {
FactoryA factoryA = new FactoryA();
ProductA productA = factoryA.create();
FactoryB factoryB = new FactoryB();
ProductB productB = factoryB.create();
}
}
简易结构图:
- 抽象工厂
抽象工厂模式是工厂方法模式的升级版本,工厂方法模式只生产一个等级的产品,而抽象工厂模式可生产多个等级的产品,如农场里既养动物又种植物等。
抽象工厂(AbstractFactory)模式的定义:是一种为访问类提供一个创建一组相关或相互依赖对象的接口,且访问类无须指定所要产品的具体类就能得到同族的不同等级的产品的模式结构。
以生产产品为例,示例代码如下:
//产品
public class Product {
public Product (){
System.out.println("产品");
}
}
//产品A
public class ProductA extends Product{
public ProductA() {
System.out.println("产品A");
}
}
//产品B
public class ProductB extends Product {
public ProductB() {
System.out.println("产品B");
}
}
//零件
public class Part {
public Part() {
System.out.println("零件");
}
}
//零件A
public class PartA extends Part{
public PartA() {
System.out.println("零件A");
}
}
//零件B
public class PartB extends Part{
public PartB() {
System.out.println("零件B");
}
}
//工厂
public abstract class Factory {
public abstract Product createProduct();
public abstract Part createPrat();
}
//工厂A
public class FactoryA extends Factory{
@Override
public Product createProduct() {
return new ProductA();
}
@Override
public Part createPrat() {
return new PartA();
}
}
//工厂B
public class FactoryB extends Factory{
@Override
public Product createProduct() {
return new ProductB();
}
@Override
public Part createPrat() {
return new PartB();
}
public static void main(String[] args) {
FactoryA factoryA = new FactoryA();
factoryA.createProduct();
factoryA.createPrat();
FactoryB factoryB = new FactoryB();
factoryB.createProduct();
factoryB.createPrat();
/** Output:
* 产品
* 产品A
* 零件
* 零件A
* 产品
* 产品B
* 零件
* 零件B
*/
}
}
简易结构图:
需要增加一个新种类的产品时,则所有的工厂类都需要进行修改,不满足开闭原则,当系统中只存在一个等级结构的产品时,抽象工厂模式将退化到工厂方法模式。
单例模式
指一个类只有一个实例,且该类能自行创建这个实例的一种模式。例如,Windows 中只能打开一个任务管理器,这样可以避免因打开多个任务管理器窗口而造成内存资源的浪费,或出现各个窗口显示内容的不一致等错误。
通常,普通类的构造函数是公有的,外部类可以通过new constructor()
来生成多个实例。但是,如果将类的构造函数设为私有的,外部类就无法调用该构造函数,也就无法生成多个实例。这时该类自身必须定义一个静态私有实例,并向外提供一个静态的公有函数用于创建或获取该静态私有实例。
系统中只存在一个共用的实例对象,无需频繁创建和销毁对象,节约了系统资源,提高系统的性能。
- 饿汉单例
示例代码如下:
public class Singleton {
private static volatile Singleton singleton = new Singleton();
private Singleton() {
}
public Singleton getInstance() {
return singleton;
}
}
饿汉式单例在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变,所以是线程安全的。
- 懒汉单例
示例代码如下:
public class Singleton {
private static volatile Singleton singleton = null;
private Singleton(){
}
public Singleton getInstance(){
if(singleton == null){
synchronized (Singleton.class){
if(singleton == null){
singleton = new Singleton();
}
}
}
return singleton;
}
}
volatile 的作用主要是禁止指定重排序。使用两个 if 判断,确保了只有第一次调用单例的时候才会做同步,这样也是线程安全的,同时避免了每次都同步的性能损耗。
原型模式
用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型相同或相似的新对象。原型实例指定了要创建的对象的种类。用这种方式创建对象非常高效,根本无须知道对象创建的细节。
- 浅拷贝
创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原有属性所指向的对象的内存地址。
示例代码如下:
public class A implements Cloneable{
private ArrayList arrayList;
@Override
protected Object clone() throws CloneNotSupportedException {
A clone = (A) super.clone();
return clone;
}
@Override
public String toString() {
return "[arrayList="+this.arrayList+"]";
}
public static void main(String[] args) throws CloneNotSupportedException {
ArrayList arrayList = new ArrayList();
arrayList.add("123");
A a = new A();
a.arrayList = arrayList ;
A clone = (A) a.clone();
System.out.println("原集合:"+a.arrayList);
System.out.println("复制集合:"+clone.arrayList);
arrayList.add("abc");
System.out.println("修改后原集合:"+a.arrayList);
System.out.println("修改后复制集合:"+clone.arrayList);
/** Output:
* 原集合:[123]
* 复制集合:[123]
* 修改后原集合:[123, abc]
* 修改后复制集合:[123, abc]
*/
}
}
由于指向同一个内存地址,所以修改数据也相同。
- 深拷贝
创建一个新对象,属性中引用的其他对象也会被克隆,不再指向原有对象地址。
示例代码如下:
public class A implements Cloneable{
private ArrayList arrayList;
@Override
protected Object clone() throws CloneNotSupportedException {
A clone = (A) super.clone();
clone.arrayList = (ArrayList) arrayList.clone();
return clone;
}
@Override
public String toString() {
return "[arrayList="+this.arrayList+"]";
}
public static void main(String[] args) throws CloneNotSupportedException {
ArrayList arrayList = new ArrayList();
arrayList.add("123");
A a = new A();
a.arrayList = arrayList ;
A clone = (A) a.clone();
System.out.println("原集合:"+a.arrayList);
System.out.println("复制集合:"+clone.arrayList);
arrayList.add("abc");
System.out.println("修改后原集合:"+a.arrayList);
System.out.println("修改后复制集合:"+clone.arrayList);
/** Output:
* 原集合:[123]
* 复制集合:[123]
* 修改后原集合:[123, abc]
* 修改后复制集合:[123]
*/
}
}
原型模式需要为每一个类都配置一个 clone()
方法,clone()
方法位于类的内部,当对已有类进行改造的时候,需要修改代码,违背了开闭原则。
建造者模式
将一个复杂对象的构造与它的表示分离,使同样的构建过程可以创建不同的表示,这样的设计模式被称为建造者模式。它是将一个复杂的对象分解为多个简单的对象,然后一步一步构建而成。它将变与不变相分离,即产品的组成部分是不变的,但每一部分是可以灵活选择的。
示例代码如下:
//产品:指定有哪些产品
public class Product {
private String a;
private String b;
public void setA(String a) {
this.a = a;
}
public void setB(String b) {
this.b = b;
}
@Override
public String toString() {
return "[a="+this.a+",b="+this.b+"]";
}
}
//产品的抽象 具体需要做哪些方法,得到什么产品
public abstract class ProductService {
public abstract void productA();
public abstract void productB();
//获取产品结果
abstract Product getProduct();
}
//产品的具体实现(建造者)
public class ProductServiceImpl extends ProductService {
Product product = new Product();
@Override
public void productA() {
product.setA("产品A");
}
@Override
public void productB() {
product.setB("产品B");
}
@Override
Product getProduct() {
return product;
}
}
//指挥
public class Conduct {
public ProductService conduct(ProductService productService){
productService.productA();
productService.productB();
return productService;
}
//测试:
public static void main(String[] args) {
Conduct conduct = new Conduct();
ProductService productService = conduct.conduct(new ProductServiceImpl());
System.out.println(productService.getProduct());
/** Output:
* [a=产品A,b=产品B]
*/
}
}
简易结构图:
抽象工厂模式实现对产品族的创建,产品族指的是不同分类维度的产品组合,用抽象工厂模式不需要关心具体构建过程,只关心产品由什么工厂生产即可。而建造者模式则更关心的是对象的构建过程,要求按照指定的蓝图建造产品,主要目的是通过组装零配件而产生一个新产品。
总结
创建型模式(Creational Pattern)的主要特点是将对象的创建与使用分离,隐藏对象的创建细节,对象的创建由相关的工厂来完成,使用者不需要关注对象的创建细节,这样可以降低系统的耦合度。
工厂类包含必要的逻辑判断,可以决定在什么时候创建哪一个产品的实例。客户端可以免除直接创建产品对象的职责,很方便的创建出相应的产品:工厂和产品的职责区分明确。简单工厂模式的工厂类单一,负责所有产品的创建,职责过重,一旦异常,整个系统将受影响;工厂方法模式类的个数容易过多,增加复杂度只能生产一种产品;抽象工厂模式可以在类的内部对产品族中相关联的多等级产品共同管理,而不必专门引入多个新的类来进行管理。增强了程序的可扩展性,当增加一个新的产品族时,不需要修改原代码,满足开闭原则。
单例模式可以保证内存里只有一个实例,减少了内存的开销;原型模式需要为每一个类都配置一个 clone()
方法,深克隆、浅克隆需要运用得当;建造者模式封装性好,构建和表示分离,可以对创建过程逐步细化,而不对其它模块产生任何影响,便于控制细节风险。
结构型模式
结构型模式描述如何将类或对象按某种布局组成更大的结构。它分为类结构型模式和对象结构型模式,前者采用继承机制来组织接口和类,后者釆用组合或聚合来组合对象。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象结构型模式比类结构型模式具有更大的灵活性。除了适配器模式分为类结构型模式和对象结构型模式两种,其他的全部属于对象结构型模式
适配器模式
将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。适配器模式分为类结构型模式和对象结构型模式两种,前者类之间的耦合度比后者高,且要求程序员了解现有组件库中的相关组件的内部结构,所以应用相对较少些。
- 对象适配器模式
以插座为例,将二孔插座转换位三孔插座。示例代码如下:
//三孔插座(目标类)
public interface ThreeHoleSocket {
void connect();
}
//二孔插座(适配器)
public class TwoHoleSocketAdapter implements ThreeHoleSocket{
private TwoPlug twoPlug = null;
public TwoHoleSocketAdapter(TwoPlug twoPlug){
this.twoPlug = twoPlug;
}
@Override
public void connect() {
twoPlug.charge();
}
}
//二孔插头(被适配类)
public class TwoPlug {
public void charge(){
System.out.println("正在给手机充电。。。。。。");
}
}
//手机充电
public class Phone {
public static void main(String[] args) {
TwoHoleSocketAdapter twoHoleSocketAdapter = new TwoHoleSocketAdapter(new TwoPlug());
twoHoleSocketAdapter.connect();
/** Output:
* 正在给手机充电。。。。。。
*/
}
}
- 类适配器模式
示例代码如下:
//三孔插座(目标类)
public interface ThreeHoleSocket {
void connect();
}
//二孔插座(适配器)
public class TwoHoleSocketAdapter extends TwoPlug implements ThreeHoleSocket{
@Override
public void connect() {
super.charge();
}
}
//二孔插头(被适配类)
public class TwoPlug {
public void charge(){
System.out.println("正在给手机充电。。。。。。");
}
}
public class Phone {
public static void main(String[] args) {
TwoHoleSocketAdapter twoHoleSocketAdapter = new TwoHoleSocketAdapter();
twoHoleSocketAdapter.connect();
/** Output:
* 正在给手机充电。。。。。。
*/
}
}
代理模式
由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。
- Subject:通过接口或抽象类声明真实对象和代理对象实现的业务方法。
- Proxy:代理角色,实现了与真实对象相同的接口,所以在任何时刻都能够代理真实对象,并且代理对象内部包含了真实对象的引用,所以它可以操作真实对象,同时也可以附加其他的操作,相当于对真实对象进行封装。
- RealSubject:真实对象,是我们最终要引用的对象。
- 静态代理
以找房子为例,示例代码如下:
//房子类(抽象角色)
public interface House {
void houseHunting();
}
//人(真实对象)
public class People implements House{
@Override
public void houseHunting() {
System.out.println("我要找房子");
}
}
//中介(代理对象)
public class PeopleProxy implements House{
private People people;
public PeopleProxy(People people) {
this.people = people;
}
@Override
public void houseHunting() {
people.houseHunting();
}
//测试
public static void main(String[] args) {
People people = new People();
PeopleProxy peopleProxy = new PeopleProxy(people);
peopleProxy.houseHunting();
}
}
- 动态代理
Java动态代理是Spring AOP 的核心技术。这种动态代理也是JDK的一个特性。在Java的java.lang.reflect
包下提供了一个Proxy类和一个InvocationHandler接口,通过这个类和这个接口可以生成JDK动态代理类和动态代理对象。
//房子类(抽象角色)
public interface House {
void houseHunting();
}
//人(真实对象)
public class People implements House{
@Override
public void houseHunting() {
System.out.println("我要找房子");
}
}
//中介(代理对象)
public class PeopleProxy implements InvocationHandler {
private People people;
public PeopleProxy(People people) {
this.people = people;
}
public Object getProxy(){
//参数一:装入定义的代理类
//参数二:需要被代理的接口
//参数三:将方法调用分派给的调用处理程序
return Proxy.newProxyInstance(this.getClass().getClassLoader(),
people.getClass().getInterfaces(),this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//反射机制
return method.invoke(people,args);
}
public static void main(String[] args) {
People people = new People();
PeopleProxy peopleProxy = new PeopleProxy(people);
House house = (House) peopleProxy.getProxy();
house.houseHunting();
}
}
桥接模式
将抽象与实现分离,使它们可以独立变化。它是用组合关系代替继承关系来实现,从而降低了抽象和实现这两个可变维度的耦合度。
抽象化( Abstraction)角色:定义抽象的接口,包含一个对实现化角色的引。
扩展抽象化(RefinedAbstraction)角色 :抽象化角色的子类,一般对抽象部分的方法进行完善和扩展,实现父类中的业务方法,并通过组合/聚合关系调用实现化角色中的业务方法
实现化(Implementor)角色 :定义具体行为、具体特征的应用接口,供扩展抽象化角色使用,一般情况下是由实现化角色提供基本的操作,而抽象化角色定义基于实现部分基本操作的业务方法;
具体实现化(ConcreteImplementor)角色 :完善实现化角色中定义的具体逻辑。
以需求和产品建立连接为例,示例代码如下:
//需求(实现化角色)
public interface Demand {
void need();
}
//需求A(具体实现化角色)
public class DemandA implements Demand{
@Override
public void need() {
System.out.print("需求A:");
}
}
//需求B(具体实现化角色)
public class DemandB implements Demand{
@Override
public void need() {
System.out.print("需求B:");
}
}
//桥接(抽象化角色)
public abstract class Bridging {
protected Demand demand;
public Bridging(Demand demand) {
this.demand = demand;
}
protected abstract void info();
}
//产品A
public class ProductA extends Bridging {
public ProductA(Demand demand) {
super(demand);
}
@Override
public void info() {
demand.need();
System.out.println("产品A");
}
}
//产品B
public class ProductB extends Bridging {
public ProductB(Demand demand) {
super(demand);
}
@Override
public void info() {
demand.need();
System.out.println("产品B");
}
public static void main(String[] args) {
Bridging bridgingA = new ProductA(new DemandA());
bridgingA.info();
Bridging bridgingB = new ProductB(new DemandB());
bridgingB.info();
/** Output:
* 需求A:产品A
* 需求B:产品B
*/
}
}
装饰模式
在不改变现有对象结构的情况下,动态地给该对象增加一些职责(即增加其额外功能)的模式,它属于对象结构型模式。
抽象构件(Component)角色:定义一个抽象接口或类,可以给这个对象动态地添加职责。
具体构件(Concrete Component)角色:实现抽象构件,通过装饰角色为其添加一些职责。
抽象装饰(Decorator)角色:继承抽象构件,并包含具体构件的实例,可以通过其子类扩展具体构件的功能。
具体装饰(ConcreteDecorator)角色:实现抽象装饰的相关方法,并给具体构件对象添加附加的责任。
以生产一个手抓饼为例,示例代码如下:
//手抓饼(抽象构件角色)
public interface HandCake {
void cake();
}
//做一个手抓饼(具体构件角色)
public class Boss implements HandCake {
@Override
public void cake() {
System.out.println("先来一个饼");
}
}
//装饰器(抽象装饰角色)
public abstract class Decorator implements HandCake{
private HandCake handCake;
public Decorator(HandCake handCake) {
this.handCake = handCake;
}
@Override
public void cake() {
handCake.cake();
}
}
//鸡蛋(具体装饰角色)
public class Egg extends Decorator {
public Egg(HandCake handCake) {
super(handCake);
}
@Override
public void cake() {
super.cake();
System.out.println("加鸡蛋");
}
}
//火腿(具体装饰角色)
public class Ham extends Decorator {
public Ham(HandCake handCake) {
super(handCake);
}
@Override
public void cake() {
super.cake();
System.out.println("加火腿");
}
public static void main(String[] args) {
Decorator decorator = new Egg(new Ham(new Boss()));
decorator.cake();
/** Output:
* 先来一个饼
* 加火腿
* 加鸡蛋
*/
}
}
外观模式
外观(Facade)模式又叫作门面模式,是一种通过为多个复杂的子系统提供一个一致的接口,而使这些子系统更加容易被访问的模式。该模式对外有一个统一接口,外部应用程序不用关心内部子系统的具体细节,这样会大大降低应用程序的复杂度,提高了程序的可维护性。
外观(Facade)角色:为多个子系统对外提供一个共同的接口。
子系统(Sub System)角色:实现系统的部分功能,客户可以通过外观角色访问它。
客户(Client)角色:通过一个外观角色访问各个子系统的功能。
示例代码如下:
//子系统角色
public interface Eat {
void haveAMeal();
}
//子系统角色
public interface Sleep {
void gotoSleep();
}
//子系统角色实现
public class EatImpl implements Eat {
@Override
public void haveAMeal() {
System.out.println("需要吃饭");
}
}
//子系统角色实现
public class SleepImpl implements Sleep {
@Override
public void gotoSleep() {
System.out.println("去睡觉");
}
}
//外观角色
public class Facade {
private Eat eat;
private Sleep sleep;
public Facade() {
this.eat = new EatImpl();
this.sleep = new SleepImpl();
}
public void eat(){
eat.haveAMeal();
}
public void sleep(){
sleep.gotoSleep();
}
}
//客户角色
public class Client {
public static void main(String[] args) {
Facade facade = new Facade();
facade.eat();
facade.sleep();
/** Output:
* 需要吃饭
* 去睡觉
*/
}
}
外观(Facade)模式是“迪米特法则”的典型应用,降低了子系统与客户端之间的耦合度,对客户屏蔽了子系统组件,减少了客户处理的对象数目,并使得子系统使用起来更加容易。增加新的子系统可能需要修改外观类或客户端的源代码,违背了“开闭原则”。
享元模式
运用共享技术来有效地支持大量细粒度对象的复用。它通过共享已经存在的对象来大幅度减少需要创建的对象数量、避免大量相似类的开销,从而提高系统资源的利用率。
抽象享元(Flyweight)角色:是所有的具体享元类的基类,为具体享元规范需要实现的公共接口,非享元的外部状态以参数的形式通过方法传入。
具体享元(Concrete Flyweight)角色:实现抽象享元角色中所规定的接口。
非享元(Unsharable Flyweight)角色:是不可以共享的外部状态,它以参数的形式注入具体享元的相关方法中。
享元工厂(Flyweight Factory)角色:负责创建和管理享元角色。当客户对象请求一个享元对象时,享元工厂检査系统中是否存在符合要求的享元对象,如果存在则提供给客户;如果不存在的话,则创建一个新的享元对象。
在了解享元模式之前我们先要了解两个概念:内部状态、外部状态。
- 内部状态:在享元对象内部不随外界环境改变而改变的共享部分。
- 外部状态:随着环境的改变而改变,不能够共享的状态就是外部状态。
内部状态存储于享元对象内部,而外部状态则应该由客户端来考虑。示例代码如下:
//任意形状(非享元角色)
public class ArbitraryShape {
private String shape;
public ArbitraryShape(String shape) {
this.shape = shape;
}
public String getShape() {
return shape;
}
}
//形状(抽象享元角色)
public interface Shape {
void draw(ArbitraryShape arbitraryShape);
}
//画板(具体享元角色)
public class Palette implements Shape{
private String color;
public Palette(String color) {
this.color = color;
}
@Override
public void draw(ArbitraryShape arbitraryShape) {
System.out.println("画了一个"+color+"的"+arbitraryShape.getShape());
}
}
//享元工厂
public class FlyweightFactory {
private static Map<String,Shape> pool= new HashMap();
public static Shape getShape(String key){
if(!pool.containsKey(key)){
pool.put(key,new Palette(key));
}
return pool.get(key);
}
public static int getSize(){
return pool.size();
}
}
//测试
public class Client {
public static void main(String[] args) {
Shape shape = FlyweightFactory.getShape("红色");
shape.draw(new ArbitraryShape("圆形"));
Shape shape1 = FlyweightFactory.getShape("蓝色");
shape1.draw(new ArbitraryShape("三角形"));
Shape shape2 = FlyweightFactory.getShape("红色");
shape2.draw(new ArbitraryShape("正方形"));
System.out.println("一共画了"+FlyweightFactory.getSize()+"形状");
/** Output:
* 画了一个红色的圆形
* 画了一个蓝色的三角形
* 画了一个红色的正方形
* 一共画了2形状
*/
}
}
享元模式的核心是享元工厂类,享元工厂类维护了一个对象存储池,当客户端需要对象时,首先从享元池中获取,如果享元池中存在对象实例则直接返回,如果享元池中不存在,则创建一个新的享元对象实例返回给用户,并在享元池中保存该新增对象,这点有些单例的意思。如 HashMap、Hashtable、Vector 等等,在 Java 中,数据库连接池、线程池等都是用享元模式的应用。
在 Java 中,String 类型就是使用享元模式,String 对象是 final 类型,对象一旦创建就不可改变。而 Java 的字符串常量都是存在字符串常量池中的,JVM 会确保一个字符串常量在常量池中只有一个拷贝。虽然常量的存储空间不同,但是所指向的内存地址都是同一个。
public class Client {
public static void main(String[] args) {
String a = "hello";
String b = new String("hello");
System.out.println(a.hashCode() == b.hashCode());//true
}
}
组合模式
有时又叫作整体-部分(Part-Whole)模式,它是一种将对象组合成树状的层次结构的模式,用来表示“整体-部分”的关系,使用户对单个对象和组合对象具有一致的访问性,属于结构型设计模式。
抽象构件(Component)角色 :抽象构建类,组合中的对象声明接口,在适当的情况下,实现所有类共有接口的默认行为。声明一个接口用于访问和管理Component子部件。
树叶构件(Leaf)角色:叶子对象。叶子结点没有子结点。
树枝构件(Composite)角色:容器对象,定义有枝节点行为,用来存储子部件,在Component接口中实现与子部件有关操作,如增加(add)和删除(remove)等。
示例代码如下:
//员工(叶子对象)
public class Staff {
private String name;
private int salary;
public Staff(String name, int salary) {
this.name = name;
this.salary = salary;
}
@Override
public String toString() {
return "[name="+this.name+",salary="+this.salary+"]";
}
}
//操作(抽象构建角色)
public interface Operation {
void add(Staff staff);
void remove(Staff staff);
void getStaff();
}
//部门(树枝构件角色)
public class Department implements Operation{
private String name;
private List<Staff> staff; // 下属
public Department(String name) {
this.name = name;
staff = new ArrayList<>();
}
@Override
public void add(Staff staff) {
this.staff.add(staff);
}
@Override
public void remove(Staff staff) {
this.staff.remove(staff);
}
@Override
public void getStaff() {
System.out.println("部门:"+name);
for (Staff staff:staff) {
System.out.println(staff);
}
}
@Override
public String toString() {
return "[name="+this.name+"]";
}
}
//测试
public class Client {
public static void main(String[] args) {
Department department = new Department("技术部");
Staff staff = new Staff("张一",3000);
Staff staff1 = new Staff("张二",2000);
department.add(staff);
department.add(staff1);
department.getStaff();
}
}
总结
结构型模式重点考虑类或对象的布局方式,其目的是将现有类或对象组成更大的结构。
适配器模式可以透明地调用目标接口,将目标类和适配者类解耦,解决了目标类和适配者类接口不一致的问题。代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用,可以扩展目标对象的功能;桥接模式抽象与实现分离,扩展能力强,做到了很好的解耦;装饰器模式是继承的有力补充,比继承灵活,在不改变原有对象的情况下,动态的给一个对象扩展功能,即插即用;外观模式降低了子系统与客户端之间的耦合度,使得子系统的变化不会影响调用它的客户类减少了客户处理的对象数目;享元模式通过保存一份对象,降低系统中细粒度对象给内存带来的压力;组合模式使得客户端代码可以一致地处理单个对象和组合对象,无须关心自己处理的是单个对象,还是组合对象,这简化了客户端代码。
行为型模式
行为型模式用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配。
模板方法模式
定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。它是一种类行为型模式。
抽象类(AbstractClass):负责给出一个算法的轮廓和骨架。它由一个模板方法和若干个基本方法构成。
具体类(ConcreteClass):实现抽象类中的抽象方法,已完成完整的算法。
示例代码如下:
//抽象类
public abstract class AbstractTemplate {
//模版方法
protected final void templateMethod(){
init();
method();
}
protected void init(){
System.out.println("初始化");
}
protected abstract void method();
}
//具体类
public class ConcreteClass extends AbstractTemplate {
@Override
protected void method() {
System.out.println("子类方法一");
}
public static void main(String[] args) {
AbstractTemplate abstractTemplate = new ConcreteClass();
abstractTemplate.templateMethod();
/** Output:
* 初始化
* 子类方法一
*/
}
}
建议模版方法的步骤由子类去实现,降低耦合性,钩子方法的意义就不大,灵活性反而更高。
策略模式
定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的变化不会影响使用算法的客户。策略模式属于对象行为模式,它通过对算法进行封装,把使用算法的责任和算法的实现分割开来,并委派给不同的对象对这些算法进行管理。
抽象策略(Strategy)类:定义了一个公共接口,各种不同的算法以不同的方式实现这个接口,环境角色使用这个接口调用不同的算法,一般使用接口或抽象类实现。
具体策略(Concrete Strategy)类:实现了抽象策略定义的接口,提供具体的算法实现。
环境(Context)类:持有一个策略类的引用,最终给客户端调用。
示例代码如下:
//抽象策略类
public interface Demand {
void need();
}
//具体策略类
public class DemandA implements Demand{
@Override
public void need() {
System.out.println("需求A:");
}
}
//具体策略类
public class DemandB implements Demand{
@Override
public void need() {
System.out.println("需求B:");
}
}
//环境类
public class Strategy {
private Demand demand;
public Strategy(Demand demand) {
this.demand = demand;
}
public void demand(){
demand.need();
}
}
//测试
public class Client {
public static void main(String[] args) {
Strategy strategy = new Strategy(new DemandA());
strategy.demand();
strategy = new Strategy(new DemandB());
strategy.demand();
}
}
似乎与桥梁模式一样。
命令模式
将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。这样两者之间通过命令对象进行沟通,这样方便将命令对象进行储存、传递、调用、增加与管理。
抽象命令类(Command)角色:声明执行命令的接口,拥有执行命令的抽象方法 execute()。
具体命令类(Concrete Command)角色:是抽象命令类的具体实现类,它拥有接收者对象,并通过调用接收者的功能来完成命令要执行的操作。
接收者(Receiver)角色:执行命令功能的相关操作,是具体命令对象业务的真正实现者。
请求者(Invoker)角色:是请求的发送者,它通常拥有很多的命令对象,并通过访问命令对象来执行相关请求,它不直接访问接收者。
以遥控器控制电脑开关为例,示例代码如下:
//抽象命令类
public abstract class Command {
public abstract void execute();
}
//打开命令(具体命令类)
public class OpenCommand extends Command {
private Computer computer;
public OpenCommand(Computer computer) {
this.computer = computer;
}
@Override
public void execute() {
computer.open();
}
}
//关闭命令(具体命令类)
public class CloseCommand extends Command {
private Computer computer;
public CloseCommand(Computer computer) {
this.computer = computer;
}
@Override
public void execute() {
computer.close();
}
}
//接收者
public class Computer {
public void open(){
System.out.println("打开电脑");
}
public void close(){
System.out.println("关闭电脑");
}
}
//请求者
public class Client {
public static void main(String[] args) {
Computer computer = new Computer();
Command command = new OpenCommand(computer);
command.execute();
command = new CloseCommand(computer);
command.execute();
/** Output:
* 打开电脑
* 关闭电脑
*/
}
}
职责链模式
避免请求发送者与多个请求处理者耦合在一起,于是将所有请求的处理者通过前一对象记住其下一个对象的引用而连成一条链;当有请求发生时,可将请求沿着这条链传递,直到有对象处理它为止。
抽象处理者(Handler)角色:定义一个处理请求的接口,包含抽象处理方法和一个后继连接。
具体处理者(Concrete Handler)角色:实现抽象处理者的处理方法,判断能否处理本次请求,如果可以处理请求则处理,否则将该请求转给它的后继者。
客户类(Client)角色:创建处理链,并向链头的具体处理者对象提交请求,它不关心处理细节和请求的传递过程。
以公司请假流程为例,示例代码如下:
//请假信息
public class LeaveInfo {
//请假天数
private int days;
//请假人
private String name;
public LeaveInfo(int days, String name) {
this.days = days;
this.name = name;
}
public int getDays() {
return days;
}
public void setDays(int days) {
this.days = days;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
//抽象处理者角色
public abstract class AbstractLeader {
//领导姓名
private String leadName;
//上级领导
private AbstractLeader superLeader;
public AbstractLeader(String leadName, AbstractLeader superLeader) {
this.leadName = leadName;
this.superLeader = superLeader;
}
public String getLeadName() {
return leadName;
}
public void setLeadName(String leadName) {
this.leadName = leadName;
}
public AbstractLeader getSuperLeader() {
return superLeader;
}
public void setSuperLeader(AbstractLeader superLeader) {
this.superLeader = superLeader;
}
protected abstract void handle(LeaveInfo leaveInfo);
}
//组长(具体处理者角色)
public class TeamLeader extends AbstractLeader {
public TeamLeader(String leadName, AbstractLeader superLeader) {
super(leadName, superLeader);
}
@Override
protected void handle(LeaveInfo leaveInfo) {
if(leaveInfo.getDays() <= 7){
System.out.println("组长:"+this.getLeadName()+"审核:"+leaveInfo.getName()+",请假天数:"+leaveInfo.getDays()+"天,通过");
}else{
if(this.getSuperLeader() != null) {
this.getSuperLeader().handle(leaveInfo);
}
}
}
}
//部门领导(具体处理者角色)
public class DeptLeader extends AbstractLeader {
public DeptLeader(String leadName, AbstractLeader superLeader) {
super(leadName, superLeader);
}
@Override
protected void handle(LeaveInfo leaveInfo) {
if(leaveInfo.getDays() <= 10){
System.out.println("部门领导:"+this.getLeadName()+"审核:"+leaveInfo.getName()+",请假天数:"+leaveInfo.getDays()+"天,通过");
}else{
if(this.getSuperLeader() != null) {
this.getSuperLeader().handle(leaveInfo);
}
}
}
}
//客户类角色
public class Client {
public static void main(String[] args) {
AbstractLeader DeptLeader = new DeptLeader("张三",null);
AbstractLeader TeamLeader = new TeamLeader("李四",DeptLeader);
LeaveInfo leaveInfo = new LeaveInfo(10,"马小跳");
TeamLeader.handle(leaveInfo);
leaveInfo = new LeaveInfo(6,"阿衰");
TeamLeader.handle(leaveInfo);
/** Output:
* 部门领导:张三审核:马小跳,请假天数:10天,通过
* 组长:李四审核:阿衰,请假天数:6天,通过
*/
}
}
状态模式
对有状态的对象,把复杂的“判断逻辑”提取到不同的状态对象中,允许状态对象在其内部状态发生改变时改变其行为。
环境类(Context)角色:也称为上下文,它定义了客户端需要的接口,内部维护一个当前状态,并负责具体状态的切换。
抽象状态(State)角色:定义一个接口,用以封装环境对象中的特定状态所对应的行为,可以有一个或多个行为。
具体状态(Concrete State)角色:实现抽象状态所对应的行为,并且在需要的情况下进行状态切换。
以线程的四种状态:初始化、运行、睡眠、终止为例,示例代码如下:
//线程状态(抽象状态角色)
public abstract class ThreadState {
//线程名
protected String ThreadName;
//初始状态
protected abstract void init(ThreadContext threadContext);
//运行状态
protected abstract void running(ThreadContext threadContext);
//睡眠
protected abstract void sleep(ThreadContext threadContext);
//终止
protected abstract void shutdown(ThreadContext threadContext);
}
//环境类
public class ThreadContext {
private ThreadState threadState;
public ThreadContext() {
threadState = new ThreadInit();
}
public ThreadState getThreadState() {
return threadState;
}
public void setThreadState(ThreadState threadState) {
this.threadState = threadState;
}
public void init(){
threadState.init(this);
}
public void running(){
threadState.running(this);
}
public void sleep(){
threadState.sleep(this);
}
public void shutdown(){
threadState.shutdown(this);
}
public String getStateName() {
return threadState.ThreadName;
}
}
//初始化状态(具体状态角色)
public class ThreadInit extends ThreadState {
public ThreadInit() {
this.ThreadName = "初始化";
}
@Override
protected void running(ThreadContext threadContext) {
System.out.println("初始化状态");
threadContext.setThreadState(new ThreadRunning());
}
//省略部分代码......
}
//运行状态(具体状态角色)
public class ThreadRunning extends ThreadState {
public ThreadRunning() {
this.ThreadName = "运行";
}
@Override
protected void sleep(ThreadContext threadContext) {
System.out.println("运行状态");
threadContext.setThreadState(new ThreadSleep());
}
//省略部分代码......
}
//睡眠状态(具体状态角色)
public class ThreadSleep extends ThreadState {
public ThreadSleep() {
this.ThreadName = "睡眠";
}
@Override
protected void shutdown(ThreadContext threadContext) {
System.out.println("睡眠状态");
threadContext.setThreadState(new ThreadShutDown());
}
//省略部分代码......
}
//终止状态(具体状态角色)
public class ThreadShutDown extends ThreadState {
public ThreadShutDown() {
this.ThreadName = "终止";
}
@Override
protected void init(ThreadContext threadContext) {
System.out.println("终止状态");
}
@Override
protected void running(ThreadContext threadContext) {
System.out.println("终止状态");
}
@Override
protected void sleep(ThreadContext threadContext) {
System.out.println("终止状态");
}
@Override
protected void shutdown(ThreadContext threadContext) {
System.out.println("终止状态");
}
}
public class Client {
public static void main(String[] args) {
ThreadContext threadContext = new ThreadContext();
threadContext.running();
threadContext.sleep();
threadContext.shutdown();
threadContext.init();
/** Output:
* 初始化状态
* 运行状态
* 睡眠状态
* 终止状态
*/
}
}
简易结构图:
观察者模式
指多个对象间存在一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。这种模式有时又称作发布-订阅模式、模型-视图模式,它是对象行为型模式。
抽象主题(Subject)角色:也叫抽象目标类,它提供了一个用于保存观察者对象的聚集类和增加、删除观察者对象的方法,以及通知所有观察者的抽象方法。
具体主题(Concrete Subject)角色:也叫具体目标类,它实现抽象目标中的通知方法,当具体主题的内部状态发生改变时,通知所有注册过的观察者对象。
抽象观察者(Observer)角色:它是一个抽象类或接口,它包含了一个更新自己的抽象方法,当接到具体主题的更改通知时被调用。
具体观察者(Concrete Observer)角色:实现抽象观察者中定义的抽象方法,以便在得到目标的更改通知时更新自身的状态。
假设以汽油价格的波动来影响汽油老板和司机的不同处理状态,示例代码如下:
//抽象主题角色
public abstract class Subject {
public List<Observer> list = null;
public Subject() {
this.list = new ArrayList<>();
}
public void add(Observer observer){
list.add(observer);
}
public abstract void notifyObserver(int money);
}
//具体主题角色
public class ConcreteSubject extends Subject {
@Override
public void notifyObserver(int money) {
for (Observer observer:list) {
observer.deploy(money);
}
}
}
//抽象观察者
public interface Observer {
void deploy(int money);
}
//汽油老板(具体观察者)
public class Boss implements Observer{
@Override
public void deploy(int money) {
if(money < 10){
System.out.println("今日汽油价格:"+money+",汽油老板哭了。");
}else{
System.out.println("今日汽油价格:"+money+",老板笑了,日进斗金。");
}
}
}
//司机(具体观察者)
public class Driver implements Observer{
@Override
public void deploy(int money) {
if(money < 10){
System.out.println("今日汽油价格:"+money+",司机笑了。");
}else{
System.out.println("今日汽油价格:"+money+",司机哭了,再也不开车了。");
}
}
}
//测试
public class Client {
public static void main(String[] args) {
ConcreteSubject concreteSubject = new ConcreteSubject();
concreteSubject.add(new Boss());
concreteSubject.add(new Driver());
concreteSubject.notifyObserver(50);
concreteSubject.notifyObserver(9);
/** Output:
* 今日汽油价格:50,老板笑了,日进斗金。
* 今日汽油价格:50,司机哭了,再也不开车了。
* 今日汽油价格:9,汽油老板哭了。
* 今日汽油价格:9,司机笑了。
*/
}
}
简易结构图:
中介者模式
定义一个中介对象来封装一系列对象之间的交互,使原有对象之间的耦合松散,且可以独立地改变它们之间的交互。中介者模式又叫调停模式,它是迪米特法则的典型应用。
抽象中介者(Mediator)角色:它是中介者的接口,提供了同事对象注册与转发同事对象信息的抽象方法。
具体中介者(Concrete Mediator)角色:具体中介者,实现抽象中介者的方法,它需要知道所有的具体同事类,同时需要从具体的同事类那里接收信息,并且向其他具体的同事类发送信息
抽象同事类(Colleague)角色:定义同事类的接口,保存中介者对象,提供同事对象交互的抽象方法,实现所有相互影响的同事类的公共功能。
具体同事类(Concrete Colleague)角色:是抽象同事类的实现者,当需要与其他同事对象交互时,由中介者对象负责后续的交互。
以买卖双方通过中介交换消息为例,示例代码如下:
//抽象中介角色
public abstract class Intermediary {
public abstract void contact(String message,Colleague colleague);
}
//具体中介角色
public class ConcreteIntermediary extends Intermediary{
private Buyer buyer;
private Seller seller;
public void setBuyer(Buyer buyer) {
this.buyer = buyer;
}
public void setSeller(Seller seller) {
this.seller = seller;
}
@Override
public void contact(String message, Colleague colleague) {
//将买方消息发送卖方,卖方消息发送给买方
if(colleague == buyer){
seller.getMessage(message);
}else {
buyer.getMessage(message);
}
}
}
//抽象同事角色
public abstract class Colleague {
protected Intermediary intermediary;
public Colleague(Intermediary intermediary) {
this.intermediary = intermediary;
}
public abstract void contact(String message);
public abstract void getMessage(String message);
}
//买家(具体同事角色)
public class Buyer extends Colleague {
public Buyer(Intermediary intermediary) {
super(intermediary);
}
@Override
public void contact(String message) {
intermediary.contact(message,this);
}
@Override
public void getMessage(String message) {
System.out.println("买家收到消息:"+message);
}
}
//卖家(具体同事角色)
public class Seller extends Colleague {
public Seller(Intermediary intermediary) {
super(intermediary);
}
@Override
public void contact(String message) {
intermediary.contact(message,this);
}
@Override
public void getMessage(String message) {
System.out.println("卖家收到消息:"+message);
}
}
//测试
public class Client {
public static void main(String[] args) {
ConcreteIntermediary concreteIntermediary = new ConcreteIntermediary();
Buyer buyer = new Buyer(concreteIntermediary);
Seller seller = new Seller(concreteIntermediary);
concreteIntermediary.setBuyer(buyer);
concreteIntermediary.setSeller(seller);
buyer.contact("我要买一套房子");
seller.contact("我要出售一套房子");
/** Output:
* 卖家收到消息:我要买一套房子
* 买家收到消息:我要出售一套房子
*/
}
}
简易结构图:
迭代器模式
提供一个对象来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示。
抽象聚合(Aggregate)角色:定义存储、添加、删除聚合对象以及创建迭代器对象的接口。
具体聚合(ConcreteAggregate)角色:实现抽象聚合类,返回一个具体迭代器的实例。
抽象迭代器(Iterator)角色:定义访问和遍历聚合元素的接口,通常包含 hasNext()、first()、next() 等方法。
具体迭代器(Concretelterator)角色:实现抽象迭代器接口中所定义的方法,完成对聚合对象的遍历,记录遍历的当前位置。
示例代码如下:
//抽象聚合角色
public abstract class Aggregate {
protected abstract void add(String str);
protected abstract void remove(String str);
protected abstract List<String> getList();
}
//具体聚合角色
public class ConcreteAggregate extends Aggregate {
private List<String> arr = new ArrayList<>();
@Override
protected void add(String str) {
arr.add(str);
}
@Override
protected void remove(String str) {
arr.remove(str);
}
@Override
protected List<String> getList() {
return arr;
}
}
//抽象迭代器角色
public abstract class Iterator<T> {
protected abstract T first();
protected abstract T next();
protected abstract Boolean hasNext();
}
//具体迭代器角色
public class ConcreteIterator<T> extends Iterator{
private List<T> list;
private int index = 0;
public ConcreteIterator(List<T> list) {
this.list = list;
}
@Override
protected T first() {
return list.get(0);
}
@Override
protected T next() {
T t = null;
if(hasNext()){
t = list.get(index++);
}
return t;
}
@Override
protected Boolean hasNext() {
if(index < list.size()-1){
return true;
}
return false;
}
}
在日常开发中,我们几乎不会自己写迭代器。除非需要定制一个自己实现的数据结构对应的迭代器,否则,开源框架提供的 API 完全够用。
访问者模式
将作用于某种数据结构中的各元素的操作分离出来封装成独立的类,使其在不改变数据结构的前提下可以添加作用于这些元素的新的操作,为数据结构中的每个元素提供多种访问方式。它将对数据的操作与数据结构进行分离,是行为类模式中最复杂的一种模式。
抽象访问者(Visitor)角色:定义一个访问具体元素的接口,为每个具体元素类对应一个访问操作 visit() ,该操作中的参数类型标识了被访问的具体元素。
具体访问者(ConcreteVisitor)角色:实现抽象访问者角色中声明的各个访问操作,确定访问者访问一个元素时该做什么。
抽象元素(Element)角色:声明一个包含接受操作 accept() 的接口,被接受的访问者对象作为 accept() 方法的参数。
具体元素(ConcreteElement)角色:实现抽象元素角色提供的 accept() 操作,其方法体通常都是 visitor.visit(this) ,另外具体元素中可能还包含本身业务逻辑的相关操作。
对象结构(Object Structure)角色:是一个包含元素角色的容器,提供让访问者对象遍历容器中的所有元素的方法,通常由 List、Set、Map 等聚合类实现。
假设以男生或女生来评价某电影为例,示例代码如下:
//抽象访问者角色
public abstract class Visitor {
protected abstract void visitor(Man man);
protected abstract void visitor(Woman woman);
}
//好评(具体访问者角色)
public class Suit extends Visitor{
@Override
protected void visitor(Man man) {
System.out.println(man.name+"觉得电影很好看");
}
@Override
protected void visitor(Woman woman) {
System.out.println(woman.name+"觉得电影很好看");
}
}
//差评(具体访问者角色)
public class Ugly extends Visitor {
@Override
protected void visitor(Man man) {
System.out.println(man.name+"觉得电影不好看");
}
@Override
protected void visitor(Woman woman) {
System.out.println(woman.name+"觉得电影不好看");
}
}
//人(抽象元素角色)
public abstract class Person {
protected String name;
public Person(String name) {
this.name = name;
}
public abstract void accept(Visitor visitor);
}
//男生(具体元素角色)
public class Man extends Person{
public Man(String name) {
super(name);
}
@Override
public void accept(Visitor visitor) {
visitor.visitor(this);
}
}
//女生(具体元素角色)
public class Woman extends Person{
public Woman(String name) {
super(name);
}
@Override
public void accept(Visitor visitor) {
visitor.visitor(this);
}
}
//对象结构角色
public class ObjectStructure {
private List<Person> list = new ArrayList<>();
public void add(Person person){
list.add(person);
}
public void accept(Visitor visitor){
for (Person person:list) {
person.accept(visitor);
}
}
}
//测试
public class Client {
public static void main(String[] args) {
ObjectStructure objectStructure = new ObjectStructure();
objectStructure.add(new Man("张三"));
objectStructure.add(new Woman("小艾"));
objectStructure.accept(new Suit());
System.out.println();
objectStructure.accept(new Ugly());
/** Output:
* 张三觉得电影很好看
* 小艾觉得电影很好看
*
* 张三觉得电影不好看
* 小艾觉得电影不好看
*/
}
}
仔细看你会发现和观察者模式很相似,大致原理差不多,具体元素类调用访问者的方法,通过中间类对象结构类发起批量方法调用。
简易结构图:
备忘录模式
在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便以后当需要时能将该对象恢复到原先保存的状态。该模式又叫快照模式。
发起人(Originator)角色:记录当前时刻的内部状态信息,通过管理者访问备忘录里的所有信息。
备忘录(Memento)角色:负责存储发起人的内部状态,在需要的时候提供这些内部状态给发起人。
管理者(Caretaker)角色:对备忘录进行管理,提供保存与获取备忘录的功能,但其不能对备忘录的内容进行访问与修改。
假设以英雄对战,当血值为0后,复活为例,示例代码如下:
//角色(发起人角色)
public class Role {
private String name;
private int bloodVolume;
public Role(String name, int bloodVolume) {
this.name = name;
this.bloodVolume = bloodVolume;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getBloodVolume() {
return bloodVolume;
}
public void setBloodVolume(int bloodVolume) {
this.bloodVolume = bloodVolume;
}
public void resurgence(Memo memo){
this.name = memo.getName();
this.bloodVolume = memo.getBloodVolume();
}
}
//备忘录角色
public class Memo {
private String name;
private int bloodVolume;
public Memo(String name, int bloodVolume) {
this.name = name;
this.bloodVolume = bloodVolume;
}
public String getName() {
return name;
}
public int getBloodVolume() {
return bloodVolume;
}
}
public class Caretaker {
private Memo memo;
public Caretaker(String name, int bloodVolume) {
this.memo = new Memo(name,bloodVolume);
}
public Memo getMemo() {
return memo;
}
}
//测试
public class Client {
public static void main(String[] args) {
Role role = new Role("孙悟空",100);
System.out.println("当前英雄:"+role.getName()+",血量:"+role.getBloodVolume());
//保存备忘录
Caretaker caretaker = new Caretaker(role.getName(),role.getBloodVolume());
role.setBloodVolume(0);
System.out.println("大战500回合后"+role.getName()+",血量:"+role.getBloodVolume()+",阵亡。");
//恢复
role.resurgence(caretaker.getMemo());
System.out.println("复活后"+role.getName()+",血量:"+role.getBloodVolume());
/** Output:
* 当前英雄:孙悟空,血量:100
* 大战500回合后孙悟空,血量:0,阵亡。
* 复活后孙悟空,血量:100
*/
}
}
简易结构图:
解释器模式
解释器模式是指给定一个使用规定格式和语法的语言,并且建立一个解释器来解释该语言中的句子。
抽象表达式(Abstract Expression)角色:定义解释器的接口,约定解释器的解释操作,主要包含解释方法 interpret()。
终结符表达式(Terminal Expression)角色:终结符表达式,实现与文法中的元素相关联的解释操作,通常一个解释器模式中只有一个终结符表达式,但有多个实例,对应不同的终结符。文法中的每一个终结符都有一个具体终结表达式与之相对应。
非终结符表达式(Nonterminal Expression)角色:文法中的每条规则都对应于一个非终结符表达式。
环境(Context)角色:通常包含各个解释器需要的数据或是公共的功能,一般用来传递被所有解释器共享的数据,后面的解释器可以从这里获取这些值。
客户端(Client):主要任务是将需要分析的句子或表达式转换成使用解释器对象描述的抽象语法树,然后调用解释器的解释方法,当然也可以通过环境角色间接访问解释器的解释方法。
假设以公交卡为例,解析不同地区的公交卡给出对应的优惠,示例代码如下:
//抽象表达式
public abstract class Expression {
//提供解析方法
protected abstract boolean interpret(String content);
}
//终结符表达式角色
public class TerminalExpression extends Expression{
//定义具体终结表达式
public static List<String> list = new ArrayList<>();
static {
list.add("湖南");
list.add("湖北");
list.add("广州");
list.add("深圳");
}
@Override
protected boolean interpret(String content) {
return list.contains(content);
}
}
//非终结符表达式
public class AndExpressicm extends Expression {
private Expression expression;
public AndExpressicm(Expression expression) {
this.expression = expression;
}
@Override
protected boolean interpret(String content) {
content = content.substring(0,2);
return expression.interpret(content);
}
}
//环境类
public class Context {
Expression expression;
public Context() {
this.expression = new AndExpressicm(new TerminalExpression());
}
public void freeRide(String info) {
boolean ok = expression.interpret(info);
if (ok) System.out.println("您是" + info + ",您本次乘车免费!");
else System.out.println(info + ",您不是免费人员,本次乘车扣费2元!");
}
}
//客户端
public class Client {
public static void main(String[] args) {
Context context = new Context();
context.freeRide("湖南的小伙");
context.freeRide("湖北的女娃");
context.freeRide("四川的辣妹");
/** Output:
* 您是湖南的小伙,您本次乘车免费!
* 您是湖北的女娃,您本次乘车免费!
* 四川的辣妹,您不是免费人员,本次乘车扣费2元
*/
}
}
简易结构图:
总结
模板方法模式定义了算法的骨架,按某种顺序调用其包含的基本方法;策略模式可以避免使用多重条件语句,如 if…else 语句、switch…case 语句;命令模式增加与删除命令不会影响其他类,且满足“开闭原则”;责任链模式无须关心请求的处理细节和请求的传递过程,请求会自动进行传递,降低了对象之间的耦合度;状态模式将特定状态相关的行为局部化到一个状态中,并且将不同状态的行为分割开来,满足“单一职责原则”;观察者模式建立了一套触发机制,状态发生改变时,依赖于它的对象都得到通知;中介者模式将原本多个对象直接的相互依赖变成了中介者和多个同事类的依赖关系。当同事类越多时,中介者就会越臃肿,变得复杂且难以维护;迭代器模式是通过将聚合对象的遍历行为分离出来,抽象成迭代器类来实现的,其目的是在不暴露聚合对象的内部结构的情况下,让外部代码透明地访问聚合的内部数据;访问者模式实现的关键是如何将作用于元素的操作分离出来封装成独立的类;备忘录模式的核心是设计备忘录类以及用于管理备忘录的管理者类;解释器模式总体来说也是一种使用频率相对较低但学习难度较大的设计模式。
本章小结
软件设计的最终目的,是使软件达到强内聚、松耦合,从而使软件:
- 易扩展-易于增加新的功能
- 更强壮-不容易被粗心的程序员破坏
- 可移植-能够在多样的环境下运行
- 更简单-容易理解、容易维护
设计模式是用于解决某一种问题的通用的解决方案。它更是一种思想,不要尝试死记硬背,记下它,你需要更多的项目经验积累,以便灵活的使用它。