网站高端设计公司,h5可以用什么网站做,百度实名认证,knowhow汉化wordpress简单梳理下二十三种设计模式#xff0c;在使用设计模式的时候#xff0c;不仅要对其分类了然于胸#xff0c;还要了解每个设计模式的应用场景、设计与实现#xff0c;以及其优缺点。同时#xff0c;还要能区分功能相近的设计模式#xff0c;避免出现误用的情况。
什么是…简单梳理下二十三种设计模式在使用设计模式的时候不仅要对其分类了然于胸还要了解每个设计模式的应用场景、设计与实现以及其优缺点。同时还要能区分功能相近的设计模式避免出现误用的情况。
什么是设计模式
设计模式是一套被反复使用的、多人知晓的、经过分类编目的、代码设计经验的总结。它描述了在软件设计过程中的一些不断重复发生的问题的解决方案。也就是说它是解决特定问题的一系列套路是前辈们的代码设计经验的总结具有一定的普遍性可以反复使用。其目的是为了提高代码的可重用性、代码的可读性和代码的可靠性。
为什么学习设计模式
设计模式的本质是面向对象设计原则的实际运用是对类的封装、继承和多态以及类的关联关系和组合关系的充分理解。正确使用设计模式具有以下优点 (1) 可以提高程序员的思维能力、编程能力和设计能力 (2) 可以使程序设计更加标准化、代码编制更加工程化从而提高软件开发效率缩短软件的开发周期 (3) 可以使设计的代码可重用性高、可读性强、可靠性高、灵活性好、可维护性强。
设计模式的基本要素
建筑大师Christopher Alexander说过“每一个模式描述了一个不断重复发生的问题以及该问题的解决方案的核心”。这种思想适用于面向对象设计领域。一般而言一个模式有四个基本要素 (1) 模式名称Pattern Name 一个助记名用来描述模式的问题、解决方案和效果。模式名称Pattern Name有助于我们理解和记忆该模式也方便我们来讨论自己的设计相同的设计模式语言。 (2) 问题Problem 问题描述了该模式的应用环境即何时使用该模式。它解释了设计问题和问题存在的前因后果以及必须满足的一系列先决条件。 (3) 解决方案Solution 解决方案描述设计的组成成分、它们之间的相互关系及各自的职责和协作方式。因为模式就像一个模板可应用于多种不同场合所以解决方案并不描述一个特定而具体的设计或实现而是提供设计问题的抽象描述和如何用一个具有一般意义的元素组合类或对象的 组合来解决这个问题。 (4) 效果Consequences 描述模式的应用效果以及使用该模式应该权衡的问题即模式的优缺点。主要是对时间和空间的衡量以及该模式对系统的灵活性、扩充性、可移植性的影响也考虑其实现问题。显式地列出这些效果Consequence对理解和评价这些模式有很大的帮助。
设计模式的分类–说一下知道的设计模式(分类)
设计模式的数量有很多这里仅以经典的GoF总结的设计模式进行分类。根据服务的范围是类还是对象以及根据设计模式使用的目的将二十三个设计模式进行如下划分
--创建型(5个)结构型(7个)行为型(11个)范围类工厂方法适配器模式(类)解释器、模板方法范围对象抽象工厂、建造器、原型模式、单例适配器模式(对象)、桥接模式、组合模式、装饰器模式、外观模式、享元模式、代理模式职责链模式、命令模式、迭代器、中介者模式、备忘录模式、观察者模式、状态模式、策略模式、访问者模式
简单说下创建型设计模式(5个)
创建型模式对类的实例化过程进行了抽象能够将对象的创建与对象的使用过程分离。 创建型模式抽象了实例化的过程。它帮助一个系统独立于如何创建、组合和表示它的那些对象。一个类创建模式使用继承改变被实例化的类而一个对象创建型模式将实例化委托给另一个对象(实例化对象)。 创建型模式主要关注点是“怎样创建对象”它的主要特点是“对象的创建与使用分离”。这样可以降低系统的耦合度使用者不需要关注对象的创建细节对象的创建由相关的类来完成。 创建型模式分为以下5种 (1) 单例Singleton模式某个类只能生成一个实例该类提供了一个全局访问点供外部获取该实例其拓展是有限多例模式。 (2) 原型Prototype模式将一个对象作为原型通过对其进行复制而克隆出多个和原型类似的新实例。 (3) 工厂方法FactoryMethod模式定义一个用于创建产品的接口由子类决定生产什么产品。 (4) 抽象工厂AbstractFactory模式提供一个创建产品族的接口其每个子类可以生产一系列相关的产品。 (5) 建造者Builder模式将一个复杂对象分解成多个相对简单的部分然后根据不同需要分别创建它们最后构建成该复杂对象。
简单介绍下简单工厂模式
简单工厂模式又称为静态工厂方法(Static Factory Method)模式是一种创建型设计模式。在简单工厂模式中可以根据参数的不同返回不同类的实例。简单工厂模式专门定义一个类来负责创建其他类的实例被创建的实例通常都具有共同的父类。简单工厂模式不属于GoF的23个设计模式可看成是工厂方法模式的退化实现。 在以下情况可以使用考虑简单工厂模式 (1) 工厂类负责创建的对象比较少由于创建的对象较少不会造成工厂方法中的业务逻辑太过复杂。 (2) 客户端只知道传入工厂类的参数对于如何创建对象不关心客户端既不需要关心创建细节甚至连类名都不需要记住只需要知道类型所对应的参数。 简单工厂模式定义一个工厂类来负责创建具体产品且每个产品都拥有共同的父类具体来说包含如下角色 Factory工厂类负责实现创建所有实例的内部逻辑。 Product抽象产品用来定义工厂所创建的对象的接口。 ConcreteProduct具体产品用来实现Product的接口。 简单工厂模式类图表示如下 简单工厂实现的示例代码如下
// 1.1 定义产品(Product)抽象类(也可以是基类)对产品接口进行声明
public abstract class Product {abstract void function();
}
// 1.2 定义具体产品(Concrete Products)产品接口的不同实现
public class ConcreteProductA extends Product {Overridepublic void function() {System.out.println(---------do some thing in a ConcreteProductA instance---------);}
}
public class ConcreteProductB extends Product {Overridepublic void function() {System.out.println(---------do some thing in a ConcreteProductB instance---------);}
}
// 2、产品工厂(Product Factory)定义返回产品对象的静态工厂方法。该方法的返回对象类型是产品的基类(Product)。
// 该类最主要的职责就是根据入参的不同创建不同的产品。
public class ProductFactory {/*** 根据条件生产产品* 注意这里以产品名作为入参具体场景还应灵活处理*/public static Product create(String productName) {if (productName.contains(ConcreteProductA)) {return new ConcreteProductA();}if (productName.contains(ConcreteProductB)) {return new ConcreteProductB();}return null;}
}// 3、客户端调用
public class FactoryMethodClient {public void test() {// (1) 通过工厂创建产品Product productA ProductFactory.create(ConcreteProductA.class.getName());// (2) 使用产品productA.function();Product productB ProductFactory.create(ConcreteProductB.class.getName());productB.function();}
}简单工厂模式有以下优点 (1) 责任分离。工厂类含有必要的判断逻辑可以决定在什么时候创建哪一个产品类的实例客户端可以免除直接创建产品对象的责任仅需要使用产品简单工厂模式通过这种做法实现了对责任的分割它提供了专门的工厂类用于创建对象。 (2) 使用简单。客户端在使用产品时只需要知道具体产品类所对应的参数即可对于一些复杂的类名通过简单工厂模式可以减少使用者的记忆量。 (3) 提高系统灵活性。通过引入配置文件可以在不修改任何客户端代码的情况下更换和增加新的具体产品类在一定程度上提高了系统的灵活性。 但是简单工厂模式也存在以下缺点 (1违反开闭原则。工厂类的职责相对过重增加新的产品需要修改工厂类的判断逻辑这一点与开闭原则是相违背的。如果产品数量过多则会容易引入上帝类。 (2) 可扩展性差。一旦添加新产品就不得不修改工厂逻辑在产品类型较多时有可能造成工厂逻辑过于复杂不利于系统的扩展。另外简单工厂模式由于使用了静态工厂方法造成工厂角色无法形成基于继承的等级结构。
简单介绍下工厂方法模式
工厂方法模式是一种创建型设计模式就是定义一个用于创建对象的接口让子类决定实例化哪一个类。工厂方法模式将类的实例化具体产品的创建延迟到工厂类的子类具体工厂中完成即由子工厂类来决定该实例化哪一个类。 在以下情况可以考虑使用工厂方法模式 (1) 如果无法预知对象确切类别及其依赖关系时可使用工厂方法。 工厂方法将创建产品的代码与实际使用产品的代码分离从而能在不影响其他代码的情况下扩展产品创建部分代码。 例如如果需要添加一种新产品则只需要开发新的产品工厂子类然后重写其工厂方法即可。 (2) 如果希望用户能扩展软件库或框架的内部组件可使用工厂方法。 继承是扩展软件库或框架默认行为的最简单方法。但是当使用子类替代标准组件时框架如何辨识出该子类解决方案是将各框架中构造组件的代码集中到单个工厂方法中并在继承该组件之外允许任何人对该方法进行重写。 (3) 如果希望复用现有对象来节省系统资源而不是每次都重新创建对象可使用工厂方法。 在处理大型资源密集型对象(如数据库连接、文件系统和网络资源等时 经常需要复用现有对象已节省资源。 正常情况下为实现现有对象的复用其处理流程一般如下 首先创建存储空间来存放经创建的对象。 然后在外部请求对时将优先在对象池中搜索可用对象。如果存在则立即返回该对象。 如果没有可用对象则创建并返回该新对象并将其添加到对象池中。 为避免上述代码不会因为重复而污染程序(对象的创建场景可能很多)可以将这些代码放在试图重用的对象类的构造函数中。 但是从定义上来讲构造函数始终返回的是新对象其无法返回现有实例。针对这种既需要一个能够创建新对象又可以重用现有对象的场景工厂方法是最佳选择。 工厂方法模式包含如下角色 Product抽象产品用来定义工厂方法所创建的对象的接口。 ConcreteProduct具体产品用来实现Product的接口。 Factory抽象工厂负责声明工厂方法该方法返回一个Product类型的对象。 ConcreteFactory具体工厂用来实现Factory声明的工厂方法。 工厂方法模式类图表示如下 工厂方法模式实现的示例代码如下
// 1、定义产品(Product)抽象类(也可以是基类)对产品接口进行声明
public abstract class Product {abstract void function();
}
// 2、定义具体产品(Concrete Products)产品接口的不同实现
public class ConcreteProductA extends Product {Overridepublic void function() {System.out.println(---------do some thing in a ConcreteProductA instance---------);}
}
public class ConcreteProductB extends Product {Overridepublic void function() {System.out.println(---------do some thing in a ConcreteProductB instance---------);}
}
// 3、产品工厂(Product Factory)抽象类或基类声明返回产品对象的工厂方法。该方法的返回对象类型必须与产品接口相匹配。
// 可以将工厂方法声明为抽象方法强制要求每个子类以不同方式实现该方法。或者在基础工厂方法中返回默认产品类型。
// 注意该类最主要的职责并不是创建产品。 一般来说 产品工厂类包含一些与产品相关的核心业务逻辑。工厂方法将这些逻辑处理从具体产品类中分离出来。
public abstract class ProductFactory {/*** 生产产品*/abstract Product create();
}
// 4、具体产品工厂(Concrete Product Factories)重写基础工厂方法 使其返回不同类型的产品。
// 注意 并不一定每次调用工厂方法都会创建新的实例。工厂方法也可以返回缓存、对象池或其他来源的已有对象。
public class ProductFactoryA extends ProductFactory {Overridepublic Product create() {System.out.println(create ProductA);return new ConcreteProductA();}
}
public class ProductFactoryB extends ProductFactory {Overridepublic Product create() {System.out.println(create ProductB);return new ConcreteProductB();}
}
// 5、客户端调用
public class FactoryMethodClient {public void foo() {// (1) 实例化产品工厂ProductFactory productFactoryA new ProductFactoryA();// (2) 生产产品Product productA productFactoryA.create();// (3) 使用产品productA.function();ProductFactory productFactoryB new ProductFactoryB();Product productB productFactoryB.create();productB.function();}
}工厂方法模式有以下优点 (1) 将创建产品的代码与实际使用产品的代码分离(解耦)避免产品创建工厂和实际产品之间的紧耦合。 (2) 单一职责原则。产品创建代码放在单独的类里从而使得代码更容易维护。 (3开闭原则。无需更改客户端调用代码 就可以在程序中引入新的产品类型。 但是工厂方法模式也存在以下缺点 (1) 应用工厂方法模式需要引入许多新的子类代码会因此变得更复杂。最好的情况是将该模式引入产品类的现有层次结构中(将工厂类组合到产品类里)。 (2) 由于考虑到系统的可扩展性需要引入抽象层在客户端代码中均使用抽象层进行定义增加了系统的抽象性和理解难度且在实现时可能需要用到反射等技术增加了系统的实现难度。 其实上述两个缺点也是设计模式的固有缺陷并不是工厂方法模式独有的问题。
简单介绍下抽象工厂模式
抽象工厂模式是一种创建型设计模式该模式提供一个创建一组相关或相互依赖的对象的接口而无须指定它们具体的类每个子类可以生产一系列相关的产品。 在以下情况可以考虑使用抽象工厂模式 (1) 如果需要与多个不同系列的相关产品交互但无法预知对象确切类别及其依赖关系时可使用抽象工厂。 抽象工厂将创建产品的代码与实际使用产品的代码分离从而能在不影响其他代码的情况下扩展产品创建部分代码。 (2) 如果希望用户能扩展软件库或框架的内部组件可使用抽象工厂。 继承是扩展软件库或框架默认行为的最简单方法。但是当使用子类替代标准组件时框架如何辨识出该子类解决方案是将各框架中构造组件的代码集中到单个工厂方法中并在继承该组件之外允许任何人对该方法进行重写。 (3) 如果存在一个基于一组抽象方法的类且其主要功能因此变得不明确可使用抽象工厂。 在设计良好的程序中 每个类仅负责一件事。如果一个类与多种类型产品交互就可以考虑将工厂方法抽取到独立的工厂类或具备完整功能的抽象工厂类中。 抽象工厂模式包含如下角色 AbstractFactory抽象工厂负责声明一组产品创建的工厂方法每个方法返回一个AbstractProduct类型的对象。 ConcreteFactory具体工厂用来实现AbstractFactory声明的工厂方法以创建具有特定实现的产品对象。 AbstractProduct抽象产品用来声明一组不同但相关的产品声明的接口。 ConcreteProduct具体产品用来实现AbstractProduct的接口以定义特定的产品。 抽象工厂模式类图表示如下 接下来将使用代码介绍下抽象工厂模式的实现。 (1) 定义一组产品(ProductA、ProductB、…)抽象类(也可以是基类)对产品接口进行声明。然后定义具体产品(Concrete Products)实现产品声明的接口。
public abstract class ProductA {abstract void function();
}
public class ConcreteProductA1 extends ProductA {Overridepublic void function() {System.out.println(---------do some thing in a ConcreteProductA1 instance---------);}
}
public class ConcreteProductA2 extends ProductA {Overridepublic void function() {System.out.println(---------do some thing in a ConcreteProductA2 instance---------);}
}public abstract class ProductB {abstract void foo();
}
public class ConcreteProductB1 extends ProductB {Overridepublic void foo() {System.out.println(---------do some thing in a ConcreteProductB1 instance---------);}
}
public class ConcreteProductB2 extends ProductB {Overridepublic void foo() {System.out.println(---------do some thing in a ConcreteProductB2 instance---------);}
}(2) 定义产品工厂(Product Factory)抽象类或基类声明返回一组产品对象的工厂方法。每个方法的返回对象类型必须与产品接口相匹配。然后定义具体产品工厂(Concrete Product Factories)重写基础工厂方法 使其返回不同类型的产品。
public abstract class ProductFactory {/*** 生产产品A*/abstract ProductA createProductA();/*** 生产产品B*/abstract ProductB createProductB();
}public class ProductFactory1 extends ProductFactory {Overridepublic ProductA createProductA() {System.out.println(create ProductA);return new ConcreteProductA1();}Overridepublic ProductB createProductB() {System.out.println(create ProductB);return new ConcreteProductB1();}
}public class ProductFactory2 extends ProductFactory {Overridepublic ProductA createProductA() {System.out.println(create ProductB);return new ConcreteProductA2();}Overridepublic ProductB createProductB() {System.out.println(create ProductB);return new ConcreteProductB2();}
}(3) 客户端调用。支持多种调用方式如直接在方法中实例化工厂子类、提供静态方法支持方法参数中传递工厂对象、提供对象方法使用构造函数中传入的工厂对象。
public class AbstractFactoryClient {private ProductFactory factory;public AbstractFactoryClient(ProductFactory factory) {this.factory factory;}// 调用方式一方法中实例化工厂子类public void test() {// (1) 实例化产品工厂ProductFactory productFactory1 new ProductFactory1();// (2) 生产产品ProductA productA1 productFactory1.createProductA();// (3) 使用产品productA1.function();// 下同ProductB productB1 productFactory1.createProductB();productB1.foo();ProductFactory productFactory2 new ProductFactory2();ProductA productA2 productFactory2.createProductA();productA2.function();ProductB productB2 productFactory2.createProductB();productB2.foo();}// 调用方式二工厂对象作为方法参数(工具类方法)public static void foo(ProductFactory factory) {ProductA productA1 factory.createProductA();productA1.function();ProductB productB1 factory.createProductB();productB1.foo();}// 调用方式三使用构造函数中传入的工厂(工厂一旦绑定无法修改)public void foo() {ProductA productA1 this.factory.createProductA();productA1.function();ProductB productB1 this.factory.createProductB();productB1.foo();}
}抽象工厂模式有以下优点 (1) 将创建产品的代码与实际使用产品的代码分离(解耦)避免产品创建工厂和实际产品之间的紧耦合。 (2) 单一职责原则。产品创建代码放在单独的类里从而使得代码更容易维护。 (3开闭原则。无需更改客户端调用代码 就可以在程序中引入新的产品类型。 但是抽象工厂模式也存在以下缺点 (1) 应用抽象工厂模式需要引入许多新的子类代码会因此变得更复杂。最好的情况是将该模式引入产品类的现有层次结构中(将工厂类组合到产品类里)。 (2) 由于考虑到系统的可扩展性需要引入抽象层在客户端代码中均使用抽象层进行定义增加了系统的抽象性和理解难度且在实现时可能需要用到反射等技术增加了系统的实现难度。
简单介绍下单例模式
单例模式是一种创建型设计模式就是保证一个类仅有一个实例并提供一个全局访问点来访问它。 在以下情况可以考虑使用单例模式 (1) 如果系统只需要一个实例对象则可以考虑使用单例模式。如提供一个唯一的序列号生成器或者需要考虑资源消耗太大而只允许创建一个对象(如数据库连接等)。 (2) 如果需要调用的实例只允许使用一个公共访问点则可以考虑使用单例模式。 (3) 如果一个系统只需要指定数量的实例对象则可以考虑扩展单例模式。如可以在单例模式中通过限制实例数量实现多例模式。 单例模式只有一个角色–单例类用来保证实例唯一并提供一个全局访问点。为实现访问点全局唯一可以定义一个静态字段同时为了封装对该静态字段的访问可以定义一个静态方法。 为了保证实例唯一这个类还需要在内部保证实例的唯一。基于以上思考单例模式的类图表示如下 单例模式的实现方式有很多种主要的实现方式有以下五种饿汉方式、懒汉方式、线程安全实现方式、双重校验方式、惰性加载方式。
(1) 饿汉方式
饿汉方式就是在类加载的时候就创建实例因为是在类加载的时候创建实例所以实例必唯一。由于在类加载的时候创建实例如果实例较复杂会延长类加载的时间。
// 1. 定义单例类提供全局唯一访问点保证实例唯一
public class HungrySingleton {// (1) 声明并实例化静态私有成员变量(在类加载的时候创建静态实例)private static final HungrySingleton instance new HungrySingleton();// (2) 私有构造方法private HungrySingleton() {}// (3) 定义静态方法提供全局唯一访问点public static HungrySingleton getInstance() {return instance;}public void foo() {System.out.println(---------do some thing in a HungrySingleton instance---------);}
}
// 2. 客户端调用
public class HungrySingletonClient {public void test() {// (1) 获取实例HungrySingleton singleton HungrySingleton.getInstance();// (2) 调用实例方法singleton.foo();}
}(2) 懒汉方式
懒汉方式就是在调用实例获取(如getInstance())接口时再创建实例这种方式可避免在加载类的时候就初始化实例。
// 1. 定义单例类提供全局唯一访问点保证实例唯一
public class LazySingleton {// (1) 声明静态私有成员变量private static LazySingleton instance;// (2) 私有构造方法private LazySingleton() {}// (3) 定义静态方法提供全局唯一访问点public static LazySingleton getInstance() {// 将实例的创建延迟到第一次获取实例if(instance null) {instance new LazySingleton();}return instance;}public void foo() {System.out.println(---------do some thing in a LazySingleton instance---------);}
}
// 2. 客户端调用
public class LazySingletonClient {public void test() {// (1) 获取实例LazySingleton instance LazySingleton.getInstance();// (2) 调用实例方法instance.foo();}
}需要说明的是对多线程语言来说(如java语言)懒汉方式会带来线程不安全问题。如果在实例前执行判空处理时至少两个线程同时进入这行代码则会创建多个实例。 所以对于多线程语言来说为了保证代码的正确性还需在实例化的时候保证线程安全。
(3) 线程安全实现方式
为保证线程安全可以在实例判空前进行线程同步处理如添加互斥锁。
// 1. 定义单例类提供全局唯一访问点保证实例唯一
public class ThreadSafeSingleton {// (1) 声明静态私有成员变量private static ThreadSafeSingleton instance;// (2) 私有构造方法private ThreadSafeSingleton() {}// (3) 定义静态方法提供全局唯一访问点public static ThreadSafeSingleton getInstance() {// 使用synchronized方法保证线程安全synchronized (ThreadSafeSingleton.class) {if (Objects.isNull(instance)) {instance new ThreadSafeSingleton();}return instance;}}public void foo() {System.out.println(---------do some thing in a ThreadSafeSingleton instance---------);}
}
// 2. 客户端调用
public class ThreadSafeSingletonClient {public void test() {// (1) 获取实例ThreadSafeSingleton instance ThreadSafeSingleton.getInstance();// (2) 调用实例方法instance.foo();}
}但是这种方式会因线程同步而带来性能问题。因为大多数场景下是不存在并发访问。
(4) 双重校验方式
为避免每次创建实例时加锁带来的性能问题引入双重校验方式即在加锁前额外进行实例判空校验这样就可保证非并发场景下仅在第一次实例化时去加锁并创建实例。
// 1. 定义单例类提供全局唯一访问点保证实例唯一
public class DoubleCheckSingleton {// (1) 声明静态私有成员变量private static volatile DoubleCheckSingleton instance;// (2) 私有构造方法private DoubleCheckSingleton() {}// (3) 定义静态方法提供全局唯一访问点public static DoubleCheckSingleton getInstance() {// 在加锁之前先执行判空检验提高性能if (Objects.isNull(instance)) {// 使用synchronized方法保证线程安全synchronized (DoubleCheckSingleton.class) {if (Objects.isNull(instance)) {instance new DoubleCheckSingleton();}}}return instance;}public void foo() {System.out.println(---------do some thing in a DoubleCheckSingleton instance---------);}
}
// 2. 客户端调用
public class DoubleCheckSingletonClient {public void test() {// (1) 获取实例DoubleCheckSingleton instance DoubleCheckSingleton.getInstance();// (2) 调用实例方法instance.foo();}
}注意使用双重校验方式时需明确语言是否支持指令重排序。以Java语言为例实例化一个对象的过程是非原子的。具体来说可以分为以下三步(1) 分配对象内存空间;(2)将对象信息写入上述内存空间;(3) 创建对上述内存空间的引用。其中(2)和(3)的顺序不要求固定(无先后顺序)所以存在实例以分配内存空间但还未初始化的情况。如果此时存在并发线程使用了该未初始化的对象则会导致代码异常。为避免指令重排序Java语言中可以使用 volatile 禁用指令重排序。更多细节可以参考java单例模式一文。
(5) 惰性加载方式
由于加锁会带来性能损耗最好的办法还是期望实现一种无锁的设计且又能实现延迟加载。对Java语言来说静态内部类会延迟加载(对C#语言来说内部类会延迟加载)。可以利用这一特性实现单例。
// 1. 定义单例类提供全局唯一访问点保证实例唯一
public class LazyLoadingSingleton {// (2) 私有构造方法private LazyLoadingSingleton() {}// (3) 定义静态方法提供全局唯一访问点public static LazyLoadingSingleton getInstance() {// 第一调用静态类成员或方法时才加载静态内部类实现了延迟加载return Holder.instance;}public void foo() {System.out.println(---------do some thing in a LazyLoadingSingleton instance---------);}// (1) 声明私有静态内部类并提供私有成员变量private static class Holder {private static LazyLoadingSingleton instance new LazyLoadingSingleton();}
}
// 2. 客户端调用
public class LazyLoadingSingletonClient {public void test() {// (1) 获取实例LazyLoadingSingleton instance LazyLoadingSingleton.getInstance();// (2) 调用实例方法instance.foo();}
}单例模式模式有以下优点 (1) 提供了对唯一实例的受控访问。 因为单例类封装了它的唯一实例所以它可以严格控制客户怎样以及何时访问它。 (2) 节约系统资源。由于在系统内存中只存在一个对象因此可以节约系统资源对于一些需要频繁创建和销毁的对象单例模式无疑可以提高系统的性能。 (3) 允许可变数目的实例。可以基于单例模式进行扩展使用与单例控制相似的方法来获得指定个数的对象实例。 但是单例模式模式也存在以下缺点 (1) 违反了单一职责原则。单例类的职责过重既充当工厂角色提供了工厂方法同时又充当产品角色包含一些业务方法将产品的创建和产品本身的功能融合到一起在一定程度上违背了单一职责原则。 (2) 单例类扩展困难。由于单例模式中没有抽象层且继承困难所以单例类的扩展有很大的困难。 (3) 滥用单例模式带来一些负面问题如过多的创建单例会导致这些单例类一直无法释放且占用内存空间另外对于一些不频繁使用的但占用内存空间较大的对象也不宜将其创建为单例。而且现在很多面向对象语言(如Java、C#)都提供了自动垃圾回收的技术。
简单介绍下原型模式
原型模式是一种创建型设计模式使调用方能够复制已有对象而又无需使代码依赖它们所属的类。当有一个类的实例原型并且想通过复制原型来创建新对象时通常会使用原型模式。 在以下情况可以考虑使用原型模式 (1) 如果需要复制一些对象同时又希望代码独立于这些对象所属的具体类可以使用原型模式。 例如代码里需要处理第三方接口传递过来的对象时即使不考虑代码耦合的情况 调用方的代码也不能依赖这些对象所属的具体类因为无法知道它们的具体信息。 原型模式可以为调用方提供一个通用接口调用方可通过这一接口与所有实现了克隆的对象进行交互它也使得调用方与其所克隆的对象具体类独立开来。 (2) 如果子类的区别仅在于其对象的初始化方式那么可以使用原型模式来减少子类的数量。 在原型模式中可以使用一系列预生成的、各种类型的对象作为原型(原型对象池)。客户端不必根据需求对子类进行实例化只需找到合适的原型并对其进行克隆即可。 原型模式就是定义一个原型类用来声明克隆接口然后集成该类以实现克隆接口。包含如下角色 Prototype原型类用来声明克隆方法。在绝大多数情况下只会有一个名为 clone 的方法。 ConcretePrototype具体原型类用来实现克隆方法。除了将原始对象的数据复制到克隆体中之外该方法有时还需处理克隆过程中的极端情况 例如克隆关联对象和梳理递归依赖等等。 原型模式类图表示如下 接下来将使用代码介绍下原型模式的实现
// 1.定义原型接口用来声明克隆方法
public interface Prototype {/*** 复制对象** return 复制后的对象*/Prototype clone();
}
// 2、定义具体原型类(ConcretePrototype)用来实现克隆方法
public class ConcretePrototype implements Prototype {private String field;public ConcretePrototype() {}public String getField() {return field;}public ConcretePrototype(String field) {this.field field;}Overridepublic Prototype clone() {ConcretePrototype concretePrototype new ConcretePrototype();concretePrototype.field this.field;return concretePrototype;}
}
// 3、客户端调用
public class PrototypeClient {public PrototypeClient() {}// 调用方式调用具体原型实例的克隆方法public void test() {Prototype concretePrototype new ConcretePrototype(foo);ConcretePrototype clonedPrototype (ConcretePrototype) concretePrototype.clone();System.out.println(clonedPrototype.getField());}
}需要说明的是对于Java语言来说Object基类已经提供了一个clone的保护方法用于实现对象的浅复制。注意只有实现了Cloneable接口才可以调用该方法否则抛出CloneNotSupportedException异常。更多clone方法的介绍可以参考笔者之前的文章。 所以在Java中实现原型模式并不需要单独定义声明克隆方法的接口直接继承Object基类并实现Cloneable接口接口。 原型模式有以下优点 (1) 将对象克隆与对象所属的具体类分离(解耦)避免克隆对象与具体类的紧耦合。 (2) 简化了复杂对象的初始化代码。对一些复杂对象可以通过提供克隆方法来简化调用方的使用。 (3) 提供了除继承以外的方式来实现复杂对象的初始化。继承建立了子类与父类的强耦合如果可以尽量不要使用继承。 但是原型模式也存在以下缺点 (1) 在重写克隆方法时对于复杂对象的关联对象或递归依赖等处理相比麻烦一些。
简单介绍下建造器模式
建造器模式也称建造者模式、生成器模式是一种创建型模式用于将一个复杂对象的构建与类的声明分离使得同样的构建过程可以创建不同类型和形式的对象。建造器模式隐藏了复杂对象的创建过程允许用户只通过指定复杂对象的类型和内容就可以构建它们而不需要知道类型的具体构建细节。 在以下情况下可以考虑使用建造器模式 (1) 如果可能出现重叠构造函数(telescoping constructor)可以考虑使用建造器模式重构。例如当前类的构造函数有十多个可选参数那么调用该构造函数会非常不方便。因此需要重载这个构造函数新建几个只有较少参数的简化版。但这些构造函数仍需调用主构造函数传递一些默认数值来替代省略掉的参数(仅会出现在支持方法重载的语言如Java、C、C#等)。建造器模式可以分步骤生成对象而且允许仅使用必须的步骤。应用该模式后可以不需要将十多个参数塞进构造函数里。 (2) 如果需要创建的各种形式的产品其制造过程相似且仅有细节上的差异可以考虑使用生成器模式。此时基本建造器定义所有可能的制造步骤具体生成器将实现这些步骤来制造特定形式的产品。同时指挥者类负责管理制造步骤的顺序。 (3) 如果需要生成的产品对象的属性相互依赖需要指定其生成顺序可以考虑使用建造器模式。例如产品的各部分存在依赖顺序则可以使用建造器模式并在指挥者类中指定制造步骤的顺序。 (3) 对象的创建过程独立于创建该对象的类。在建造者模式中引入了指挥者类将创建过程封装在指挥者类中而不在建造者类中。 (4) 如果需要隔离复杂对象的创建和使用可以考虑使用建造器模式。如需要构造组合树或其他复杂对象可以使用建造器模式分步骤构造产品。如可以延迟执行某些步骤而不会影响最终产品或者递归调用这些步骤以方便创建对象树。 建造器模式通过定义建造者来分担对象的创建职责具体来说包含如下角色 Builder抽象建造者用于声明产品构造步骤。 ConcreteBuilder具体建造者提供构造步骤的不同实现。 Director指挥者定义调用构造步骤的顺序实现创建和复用特定的产品配置。 Product产品最终生成的对象。 建造器模式类图表示如下 接下来将使用代码介绍下建造器模式的实现。
// 1.定义产品表示最终生成的对象
public class Product {private String partA;private String partB;private String partC;public void setPartA(String partA) {this.partA partA;}public void setPartB(String partB) {this.partB partB;}public void setPartC(String partC) {this.partC partC;}public void function() {System.out.printf(partA %s, partB %s, partC %s, partA, partB, partC);}
}// 2、定义抽象建造者用于声明产品构造步骤
public abstract class Builder {public abstract void buildPartA();public abstract void buildPartB();public abstract void buildPartC();public abstract Product getProduct();
}// 3、定义具体建造者用于实现构造步骤
public class ConcreteBuilder extends Builder {private Product product new Product();Overridepublic void buildPartA() {product.setPartA(A);}Overridepublic void buildPartB() {product.setPartB(B);}Overridepublic void buildPartC() {product.setPartC(C);}Overridepublic Product getProduct() {return this.product;}
}
// 4、定义指挥者用于定义调用构造步骤的顺序实现创建和复用特定的产品配置
public class Director {public Director(Builder builder) {builder.buildPartA();builder.buildPartB();builder.buildPartC();}
}// 5、客户端调用
public class BuilderClient {public void test() {// (1) 实例化建造器Builder builder new ConcreteBuilder();// (2) 基于建造器实例化指挥者new Director(builder);// (3) 从建造器中获取产品对象Product product builder.getProduct();// (4) 使用产品product.function();}
}建造器模式有以下优点 (1) 符合单一职责原则。建造者模式将复杂的产品创建代码从产品的业务逻辑中分离出来实现了解耦简化了产品创建的细节。 (2) 符合开闭原则。增加新的具体建造者无须修改原有类库的代码指挥者类针对抽象建造者类实现系统扩展方便。 (3) 可以更加精细地控制产品的创建过程。将复杂产品的创建步骤分解在不同的方法中使得创建过程更加清晰也更方便控制创建过程。 但是建造器模式也存在以下缺点 (1) 建造者模式创建的产品一般具有较多的共同点其组成部分相似如果产品之间的差异性很大则不适合使用建造者模式因此其使用范围受到一定的限制。 (2) 如果产品内部变化复杂可能会导致需要定义很多具体建造者类来实现这种变化导致系统变得很庞大。
简单说下结构型设计模式(7个)
结构型模式描述如何将类或对象按某种布局组成更大的结构。 结构型模型按照适用于对象或类可细分为类结构型模式和对象结构型模式前者采用继承机制来组织接口和类后者釆用组合或聚合来组合对象。由于组合关系或聚合关系比继承关系耦合度低满足“合成复用原则”所以对象结构型模式比类结构型模式具有更大的灵活性。 结构型模式分为以下 7 种 代理Proxy模式为某对象提供一种代理以控制对该对象的访问。即客户端通过代理间接地访问该对象从而限制、增强或修改该对象的一些特性。 适配器Adapter模式将一个类的接口转换成客户希望的另外一个接口使得原本由于接口不兼容而不能一起工作的那些类能一起工作。 桥接Bridge模式将抽象与实现分离使它们可以独立变化。它是用组合关系代替继承关系来实现的从而降低了抽象和实现这两个可变维度的耦合度。 装饰Decorator模式动态地给对象增加一些职责即增加其额外的功能。 外观Facade模式为多个复杂的子系统提供一个一致的接口使这些子系统更加容易被访问。 享元Flyweight模式运用共享技术来有效地支持大量细粒度对象的复用。 组合Composite模式将对象组合成树状层次结构使用户对单个对象和组合对象具有一致的访问性。 以上 7 种结构型模式除了适配器模式分为类结构型模式和对象结构型模式两种其他的全部属于对象结构型模式。
简单介绍下代理模式
代理模式是一种结构型设计模式让开发者能够提供对象的替代品或其占位符。代理对象控制着对于原对象的访问并允许在将请求提交给原对象前后进行一些处理。代理模式为原对象提供一种代理以控制对这个对象的访问并由代理对象控制对原对象的引用。 在以下情况下可以考虑使用代理模式 (1) 远程代理。本地执行远程服务适用于服务对象位于服务器(本地服务器或远程服务器)上的情形。在这种情形中代理通过网络传递客户端请求负责处理所有与网络相关的复杂细节。 (2) 虚拟代理。如果需要创建一个资源消耗较大的对象一直保持该对象运行会消耗系统资源。可以先创建一个消耗相对较小的对象来表示真实对象只在需要时才会被真正创建。虚拟代理是一种延迟初始化实现无需在程序启动时就创建该对象可将对象的初始化延迟到真正有需要的时候。 (3) Copy-on-Write代理。它是虚拟代理的一种实现把克隆对象的操作延迟到只有在客户端真正需要时才执行。一般来说对象的深克隆是一个开销较大的操作Copy-on-Write代理可以让这个操作延迟只有对象被用到的时候才被克隆。 (4) 保护代理。该代理控制对一个对象的访问可以给不同的用户提供不同级别的使用权限。如果只希望特定用户使用服务对象可考虑使用代理模式。 (5) 缓存代理。为某一个目标操作的结果提供临时的存储空间以便多个客户端可以共享这些结果。适用于需要缓存客户请求结果并对缓存生命周期进行管理时 特别是当返回结果的体积非常大时。 (6) 日志记录代理。当需要保存对于服务对象的请求历史记录时代理可以在向服务传递请求前进行记录。 (7) 智能引用。当一个对象被引用时提供一些额外的操作如将此对象被调用的次数记录下来在没有客户端使用某个重量级对象时立即销毁该对象等。 (8) 图片代理。当需要对大图浏览进行控制时可以考虑使用代理模式。用户通过浏览器访问网页时先不加载真实的大图而是通过代理对象的方法来进行处理在代理对象的方法中先使用一个线程向客户端浏览器加载一个小图片然后在后台使用另一个线程来调用大图片的加载方法将大图片加载到客户端。当需要浏览大图片时再将大图片在新网页中显示。如果用户在浏览大图时加载工作还没有完成可以再启动一个线程来显示相应的提示信息。通过代理技术结合多线程编程将真实图片的加载放到后台来操作不影响前台图片的浏览。 (9) 动态代理。动态代理是一种较为高级的代理模式它的典型应用就是Spring AOP。在传统的代理模式中客户端通过Proxy调用RealSubject类的request()方法同时还在代理类中封装了其他方法(如preRequest()和postRequest())可以处理一些其他问题。如果按照这种方法使用代理模式那么真实主题角色必须是事先已经存在的并将其作为代理对象的内部成员属性。如果一个真实主题角色必须对应一个代理主题角色这将导致系统中的类个数急剧增加因此需要想办法减少系统中类的个数此外如何在事先不知道真实主题角色的情况下使用代理主题角色这都是动态代理需要解决的问题。 为使用代理对象控制对某个对象(Real Subject)的访问可以创建一个代理(Proxy)并封装对源对象的访问。为保证代理和这个对象的接口一致还需提取公共接口(Subject)这样在任何需要控制这个对象的访问的地方都可使用代理实现。代理模式包含如下角色 Subject抽象对象声明了对象接口。代理必须遵循该接口才能伪装成原对象。 Real Subject真实对象实现了对象接口。 Proxy代理类包含一个指向服务对象的引用成员变量。代理完成其任务如延迟初始化、记录日志、访问控制和缓存等后会将请求传递给服务对象。 代理模式类图表示如下 接下来将使用代码介绍下代理模式的实现。
// 1、抽象对象对接口进行声明
public interface Subject {void operation();
}// 2、真实对象实现了接口
public class RealSubject implements Subject {Overridepublic void operation() {System.out.println(---------do some thing in a real subject instance---------);}
}// 3、代理类包含一个指向代理对象的引用成员变量
public class Proxy implements Subject {private RealSubject realSubject new RealSubject();Overridepublic void operation() {preOperation();realSubject.operation();afterOperation();}public void preOperation() {System.out.println(pre operation in the proxy);}public void afterOperation() {System.out.println(after operation in the proxy);}
}// 4、客户端调用
public class ProxyClient {public void test(){// (1) 声明接口并实例化代理类Subject subjectProxy new Proxy();// (2) 调用对象接口subjectProxy.operation();}
}注意 (1) 尽管可以提供一个公共接口供代理和对象使用但是更多的情况是这个对象的实现和代理的实现是两个不同的人或部门开发。一种可能的情况是开发代理类的是客户端开发人员而开发服务器端类的服务器端开发人员。所以代理和这个对象的公共接口可能并不会被创建。(无法完全做到面向接口编程) (2) 如果Proxy不需要知道待控制访问的对象的类型则可使用统一的接口处理代理而不需要为每个待控制访问的对象创建Proxy。 代理模式在访问对象时引入了一定程度的间接性。代理模式有以下优点 (1) 符合开闭原则。可以在不对服务或客户端做出修改的情况下创建新代理。 (2) 隐藏一个对象存在于不同地址空间的事实。如客户端调用服务器端方法使用代理后客户端像调用本地方法一样调用服务器端方法。 (3) 允许在访问一个对象时进行一些额外的处理。如将组合后的数据返回给调用者延迟对象的创建时间对对象进行生命周期管理等。 但是代理模式也存在以下缺点 (1) 服务响应可能会延迟。由于在客户端和真实对象之间增加了代理对象因此有些类型的代理模式可能会造成请求的处理速度变慢。 (2) 代码复杂度上升。实现代理模式需要额外的工作有些代理模式的实现非常复杂(如动态代理)。
简单介绍下适配器模式
适配器模式是一种结构型设计模式用于将一个接口转换成用户希望的另一个接口适配器模式使接口不兼容的那些类可以一起工作其别名为包装器(Wrapper)。适配器模式既可以作为类结构型模式也可以作为对象结构型模式。 在以下情况下可以考虑使用适配器模式 (1) 需要使用某个现有类但是这些类的接口不符合系统的需要可以考虑使用适配器。如以下场景 适配器模式允许创建一个中间层类 其可作为代码与遗留类、第三方类或提供接口的类之间的转换器。 如果需要复用这样一些类他们处于同一个继承体系并且他们又有了额外的一些共同的方法 但是这些共同的方法不是所有在这一继承体系中的子类所具有的共性。 扩展每个子类将缺少的功能添加到新的子类中且无法将功能提取到父类。但是 必须在所有新子类中重复添加这些代码这样会使得代码有坏味道。 (2) 想要建立一个可以重复使用的类用于与一些彼此之间没有太大关联的一些类一起工作可以考虑使用适配器。 (3) 将缺失功能添加到一个适配器类中是一种优雅的解决方案。 在这方案中开发者可以将缺少功能的对象封装在适配器中 从而动态地获取所需功能。 如要这一点正常运作 目标类必须要有通用接口 适配器的成员变量应当遵循该通用接口。 这种方式同装饰模 式非常相似。 适配器模式包含如下角色 Target目标类描述了其他类与客户端代码合作时必须遵循的协议简单来说就是客户端使用的目标接口。 Adaptee适配者类待适配的类。客户端与其接口不兼容因此无法直接调用其功能。 Adapter适配器类适配器Adapter是一个可以同时与客户端和服务交互的类它在实现客户端接口的同时封装了服务对象。适配器接受客户端通过适配器接口发起的调用并将其转换为适用于被封装服务对象的调用。 Client客户端类客户端代码只需通过接口与适配器交互即可 无需与具体的适配器类耦合。 因此开发者可以向程序中添加新类型的适配器而无需修改已有代码。这在服务类的接口被更改或替换时很有用开发者无需修改客户端代码就可以创建新的适配器类。 适配器模式有对象适配器和类适配器两种实现 接下来将使用代码介绍下适配器模式的实现。首先是对象适配器模式的实现
// 1、目标类对客户端需要使用的目标接口进行声明
public interface Target {void request();
}// 2、适配者类待适配类其声明的接口无法被客户端直接调用
public class Adaptee {public void specialRequest() {System.out.println(---------do some thing in an adaptee instance---------);}
}// 3、适配器类适配器可以接受客户端通过适配器接口发起的调用并将其转换为适用于被封装服务对象的调用。
// 对象适配器通过组合适配者实例并实现目标类来完成适配
public class Adapter implements Target {private Adaptee adaptee new Adaptee();Overridepublic void request() {adaptee.specialRequest();}
}// 4、客户端调用
public class AdapterClient {public void test() {Target adapter new Adapter();adapter.request();}
}其次是类适配器模式的实现
// 1、目标类对客户端需要使用的目标接口进行声明
public interface Target {void request();
}// 2、适配者类待适配类其声明的接口无法被客户端直接调用
public class Adaptee {public void specialRequest() {System.out.println(---------do some thing in an adaptee instance---------);}
}// 3、适配器类适配器可以接受客户端通过适配器接口发起的调用并将其转换为适用于被封装服务对象的调用。
// 类适配器通过继承适配者实例并实现目标类来完成适配
public class Adapter extends Adaptee implements Target {Overridepublic void request() {specialRequest();}
}// 4、客户端调用
public class AdapterClient {public void test() {Target adapter new Adapter();adapter.request();}
}从上面的实现不难发现类适配器是基于继承实现而对象适配器是基于组合关系实现。由于对象适配器是通过关联关系进行耦合的因此在设计时更灵活而类适配器就只能通过重写Adaptee的方法进行扩展。 适配器让接口不兼容的对象可以相互合作。适配器模式有以下优点 (1) 单一职责。可以将接口或数据转换代码从程序主要业务逻辑中分离。 (2) 将目标类和适配者类解耦通过引入一个适配器类来重用现有的适配者类而无须修改原有代码。 (3) 增加了类的透明性和复用性将具体的实现封装在适配者类中对于客户端来说是透明的而且提高了适配者的复用性。 (4) 灵活性和扩展性都非常好通过使用配置文件可以很方便地更换适配器也可以在不修改原有代码的基础上增加新的适配器类完全符合“开闭原则”。 类适配器模式还具有如下优点 由于适配器类是适配者类的子类因此可以在适配器类中置换一些适配者的方法使得适配器的灵活性更强。 对象适配器模式还具有如下优点 一个对象适配器可以把多个不同的适配者适配到同一个目标也就是说同一个适配器可以把适配者类和它的子类都适配到目标接口。 但是适配器模式也存在以下缺点 (1) 代码整体复杂度增加 因为开发者需要新增一系列接口和类。有时直接更改服务类使其与其他代码兼容会更简单。 类适配器模式还具有如下缺点 对于Java、C#等不支持多重继承的语言一次最多只能适配一个适配者类而且目标抽象类只能为抽象类不能为具体类其使用有一定的局限性不能将一个适配者类和它的子类都适配到目标接口。 对象适配器模式还具有如下缺点 与类适配器模式相比要想置换适配者类的方法就不容易。如果一定要置换掉适配者类的一个或多个方法就只好先做一个适配者类的子类将适配者类的方法置换掉然后再把适配者类的子类当做真正的适配者进行适配实现过程较为复杂。
简单介绍下享元模式
享元模式是一种结构型设计模式主要通过共享技术有效地减少大量细粒度对象的复用以减少内存占用和提高性能。由于享元模式要求能够共享的对象必须是细粒度对象因此它又称为轻量级模式。 在以下情况下可以考虑使用享元模式 (1) 一个系统有大量相同或者相似的对象由于这类对象的大量使用造成内存的大量耗费。 应用该模式所获的收益大小取决于使用它的方式和情景。它在下列情况中最有效 程序需要生成数量巨大的相似对象 这将耗尽目标设备的所有内存 对象中包含可抽取且能在多个对象间共享的重复状态 (2) 对象的大部分状态都可以外部化可以将这些外部状态传入对象中。 使用享元模式需要维护一个存储享元对象的享元池而这需要耗费资源因此应当在多次重复使用享元对象时才值得使用享元模式。 适配器模式包含如下角色 Flyweight: 抽象享元类包含原始对象中部分能在多个对象中共享的状态。 同一享元对象可在许多不同情景中使用。 享元中存储的状态被称为 “内在状态”。 传递给享元方法的状态被称为 “外在状态”。 ConcreteFlyweight: 具体享元类实现享元接口并为内部状态(如果存在的话)增加存储空间。 UnsharedConcreteFlyweight: 非共享具体享元类抽象享元类使共享成为可能但并不强制共享。 FlyweightFactory: 享元工厂类 享元工厂会对已有享元的缓存池进行管理。 有了工厂后 客户端就无需直接创建享元 它们只需调用工厂并向其传递目标享元的一些内在状态即可。 工厂会根据参数在之前已创建的享元中进行查找 如果找到满足条件的享元就将其返回 如果没有找到就根据参数新建享元。 享元模式类图表示如下 接下来将使用代码介绍下享元模式的实现。
// 1、抽象享元类存储的状态被称为 内在状态。 传递给享元方法的状态被称为 外在状态
public abstract class Flyweight {private String intrinsic;protected String extrinsic;public Flyweight(String extrinsic) {this.extrinsic extrinsic;}public abstract void operation(String extrinsic);
}
// 2、具体享元类实现享元接口
public class ConcreteFlyweight extends Flyweight {public ConcreteFlyweight(String extrinsic) {super(extrinsic);}Overridepublic void operation(String extrinsic) {System.out.println(do some thing in the concrete flyweight instance);}
}
// 3、非共享具体享元类抽象享元类使共享成为可能但并不强制共享
public class UnsharedConcreteFlyweight extends Flyweight {public UnsharedConcreteFlyweight(String extrinsic) {super(extrinsic);}Overridepublic void operation(String extrinsic) {System.out.println(do some thing in the unshared concrete flyweight instance);}
}
// 4、享元工厂类 享元工厂会对已有享元的缓存池进行管理。 有了工厂后 客户端就无需直接创建享元
// 它们只需调用工厂并向其传递目标享元的一些状态即可
public class FlyweightFactory {private static final MapString, Flyweight pool new HashMap();public static Flyweight getFlyweight(String extrinsic) {Flyweight flyweight pool.get(extrinsic);if (flyweight null) {flyweight new ConcreteFlyweight(extrinsic);pool.put(extrinsic, flyweight);System.out.println(put a fly weight instance to the pool);}return flyweight;}
}
// 5、客户端调用
public class FlyweightClient {public void test() {// 从享元工厂获取享元类Flyweight flyweight1 FlyweightFactory.getFlyweight(one);// 执行享元方法flyweight1.operation(one);// 从享元工厂获取重复享元类(直接从缓存池获取)Flyweight flyweight2 FlyweightFactory.getFlyweight(one);flyweight2.operation(one);// 获取非共享享元子类Flyweight unsharedConcreteFlyweight new UnsharedConcreteFlyweight(two);unsharedConcreteFlyweight.operation(two);}
}享元模式有以下优点 (1) 节省内存。享元模式的优点在于它可以极大减少内存中对象的数量使得相同对象或相似对象在内存中只保存一份。如果程序中有很多相似对象那么可以考虑使用该模式 (2) 享元模式的外部状态相对独立而且不会影响其内部状态从而使得享元对象可以在不同的环境中被共享 但是享元模式也存在以下缺点 (1) 可能需要牺牲执行速度来换取内存为了使对象可以共享享元模式需要将享元对象的状态外部化而读取外部状态使得运行时间变长。 (2) 代码会变得更加复杂。 需要分离出内部状态和外部状态这使得程序的逻辑复杂化。
简单介绍下外观模式
外观模式是一种结构型设计模式用来给子系统中的一组接口提供一个一致的界面。当外部与一到多个子系统的通信必须通过一个统一的外观对象进行时可以通过外观模式定义一个高层接口该接口使得这些子系统更加容易使用。外观模式又称为门面模式。 在以下情况可以考虑使用外观模式 (1) 当要为一个复杂子系统提供一个简单接口时可以使用外观模式。该接口可以满足大多数用户的需求而且用户也可以越过外观类直接访问子系统。 子系统通常会随着时间的推进变得越来越复杂。 为了解决这个问题外观将会提供指向子系统中最常用功能的快捷方式能够满足用户的大部分需求。 (2) 客户程序与多个子系统之间存在很大的依赖性。引入外观类将子系统与客户以及其他子系统解耦可以提高子系统的独立性和可移植性。 (3) 在层次化结构中可以使用外观模式定义系统中每一层的入口层与层之间不直接产生联系而通过外观类建立联系降低层之间的耦合度。 创建外观类来定义子系统中各层次的入口。 可以要求子系统仅使用外观来进行交互以减少子系统之间的耦合。 外观模式包含如下角色 Facade: 外观类提供了一种访问特定子系统功能的便捷方式将客户的请求代理给合适的子系统对象。 Subsystem: 子系统类实现子系统的功能子系统类不会意识到外观的存在它们在系统内运作并且相互之间可直接进行交互。 外观模式类图表示如下 接下来将使用代码介绍下外观模式的实现。
// 1、外观类提供了一种访问特定子系统功能的便捷方式将客户的请求代理给合适的子系统对象。
public class Facade {private SystemA systemA new SystemA();private SystemB systemB new SystemB();private SystemC systemC new SystemC();public void operationA() {systemA.operationA();}public void operationB() {systemB.operationB();}public void operationC() {systemC.operationC();}
}
// 2、子系统A实现子系统A的功能
public class SystemA {public void operationA() {System.out.println(do some thing in the systmA instance);}
}
// 3、子系统B实现子系统B的功能
public class SystemB {public void operationB() {System.out.println(do some thing in the SystemB instance);}
}
// 4、子系统C实现子系统C的功能
public class SystemC {public void operationC() {System.out.println(do some thing in the SystemC instance);}
}
// 5、客户端调用
public class DecoratorClient {public void test() {// (1) 实例化外观类Facade facade new Facade();// (2) 调用外观类接口facade.operationA();facade.operationB();facade.operationC();}
}外观模式有以下优点 (1) 符合迪米特法则。对客户屏蔽子系统组件减少了客户处理的对象数目并使得子系统使用起来更加容易。通过引入外观模式客户代码将变得很简单与之关联的对象也很少。 (2) 实现了子系统与客户之间的松耦合关系这使得子系统的组件变化不会影响到调用它的客户类只需要调整外观类即可。 但是外观模式也存在以下缺点 (1) 外观类可能成为与程序中所有类都耦合的上帝类。 (2) 不能很好地限制客户使用子系统类如果对客户访问子系统类做太多的限制则减少了可变性和灵活性。 (3) 在不引入抽象外观类的情况下增加新的子系统可能需要修改外观类或客户端的源代码违背了开闭原则。
简单介绍下装饰器模式
装饰器模式是一种结构型设计模式用来动态地给一个对象增加一些额外的职责。就增加对象功能来说装饰器模式比生成子类实现更为灵活。装饰器模式的别名为包装器(Wrapper)与适配器模式的别名相同但它们适用于不同的场合。 在以下情况可以考虑使用装饰器模式 (1) 在不影响其他对象的情况下以动态、透明的方式给单个对象添加职责。 (2) 需要动态地给一个对象增加功能这些功能也可以动态地被撤销。 装饰器能将业务逻辑组织为层次结构开发者可为各层创建一个装饰在运行时将各种不同逻辑组合成对象。由于这些对象都遵循通用接口客户端代码能以相同的方式使用这些对象。 (3) 当不能采用继承的方式对系统进行扩充或者采用继承不利于系统扩展和维护时。不能采用继承的情况主要有两类第一类是系统中存在大量独立的扩展为支持每一种组合将产生大量的子类使得子类数目呈爆炸性增长第二类是因为类定义不能继承如final类 说明一般有两种方式可以实现给一个类或对象增加行为 (1) 继承机制使用继承机制是给现有类添加功能的一种有效途径通过继承一个现有类可以使得子类在拥有自身方法的同时还拥有父类的方法。但是这种方法是静态的用户不能控制增加行为的方式和时机。 (2) 关联机制即将一个类的对象嵌入另一个对象中由另一个对象来决定是否调用嵌入对象的行为以便扩展自己的行为我们称这个嵌入的对象为装饰器(Decorator) 装饰模式以对客户透明的方式动态地给一个对象附加上更多的责任换言之客户端并不会觉得对象在装饰前和装饰后有什么不同。装饰模式可以在不需要创造更多子类的情况下将对象的功能加以扩展。这就是装饰模式的模式动机。 装饰器模式包含如下角色 Component: 抽象构件定义一个对象接口用来动态地给一个对象增加一些额外的职责 ConcreteComponent: 具体构件实现一个对象接口 Decorator: 抽象装饰类拥有一个指向被封装对象的引用成员变量。装饰类会将所有操作委派给被封装的对象。 ConcreteDecorator: 具体装饰类定义了可动态添加到抽象构件的具体行为。具体装饰类会重写装饰基类的方法并在调用父类方法之前或之后进行额外的行为。 装饰器模式类图表示如下 接下来将使用代码介绍下装饰器模式的实现。
// 1、抽象构件定义一个对象接口用来动态地给一个对象增加一些额外的职责
public interface Component {void operation();
}
// 2、具体构件实现一个对象接口
public class ConcreteComponent implements Component {Overridepublic void operation() {System.out.println(do some thing in the concrete component instance);}
}
// 3、抽象装饰类拥有一个指向被封装对象的引用成员变量。装饰类会将所有操作委派给被封装的对象
public class Decorator implements Component {private Component component null;public Decorator(Component component) {this.component component;}Overridepublic void operation() {this.component.operation();}
}
// 4、具体装饰类定义了可动态添加到抽象构件的具体行为。具体装饰类会重写装饰基类的方法并在调用父类方法之前或之后进行额外的行为
public class ConcreteDecorator extends Decorator {public ConcreteDecorator(Component component) {super(component);}Overridepublic void operation() {super.operation();afterOperation();}private void afterOperation() {System.out.printf(do some thing after operation in the concrete decorator);}
}
// 5、客户端调用
public class DecoratorClient {public void test() {// (1) 声明接口并实例化组件Component component new ConcreteComponent();// (2) 调用组件方法(装饰前)component.operation();// (3) 实例化具体装饰器并对组件进行装饰component new ConcreteDecorator(component);// (4) 调用组件方法(装饰后)component.operation();}
}装饰器模式有以下优点 (1) 符合开闭原则。具体构件类与具体装饰类可以独立变化用户可以根据需要增加新的具体构件类和具体装饰类在使用时再对其进行组合原有代码无须改变符合“开闭原则”。 (2) 符合单一职责原则。开发者可以将实现了许多不同行为的一个大类拆分为多个较小的类。 (3) 提高了代码的可扩展性和灵活性。装饰器模式与继承关系的目的都是要扩展对象的功能但是装饰器模式可以提供比继承更多的灵活性可以在不创建新子类的前提下扩展对象的行为。 (4) 可以通过一种动态的方式来扩展一个对象的功能通过配置文件可以在运行时选择不同的装饰器从而实现不同的行为。 (5) 通过使用不同的具体装饰类以及这些装饰类的排列组合可以创造出很多不同行为的组合。可以使用多个具体装饰类来装饰同一对象得到功能更为强大的对象。 但是装饰器模式也存在以下缺点 (1) 代码复杂度上升。使用装饰模式进行系统设计时将产生很多小对象这些对象的区别在于它们之间相互连接的方式有所不同而不是它们的类或者属性值有所不同同时还将产生很多具体装饰类。这些装饰类和小对象的产生将增加系统的复杂度加大学习与理解的难度。 (2) 装饰器模式比继承更加灵活也同时意味着装饰器模式比继承更加易于出错排错也很困难对于多次装饰的对象调试时寻找错误可能需要逐级排查较为烦琐。
简单介绍下桥接模式
桥接模式是一种结构型设计模式 又称为柄体(Handle and Body)模式或接口(Interface)模式。桥接模式可将抽象部分与它的实现部分分离使它们都可以独立地变化。如将一个大类或一系列紧密相关的类拆分为抽象和实现两个独立的层次结构从而在开发时分别使用。 在以下情况可以考虑使用桥接模式 (1) 如果想要拆分或重组一个具有多重功能的庞杂类例如需要与多个数据库进行交互的类上帝类可以考虑使用桥接模式。 桥接模式可以将庞杂类拆分为几个类层次结构。 此后 就可以修改任意一个类层次结构而不会影响到其他类层次结构。 这种方法可以简化代码的维护工作 并将修改已有代码的风险降到最低。 (2) 如果希望在几个独立维度上扩展一个类 可以考虑使用桥接模式。 桥接建议将每个维度抽取为独立的类层次。 初始类将相关工作委派给属于对应类层次的对象 无需自己完成所有工作。 (3) 如果需要在运行时切换不同实现方法 可以考虑使用桥接模式。 当然并不是说一定要实现这一点 桥接模式可替换抽象部分中的实现对象 具体操作就和给成员变量赋新值一样简单。 顺便提一句 最后一点是很多人混淆桥接模式和策略模式的主要原因。 记住 设计模式并不仅是一种对类进行组织的方式 它还能用于沟通意图和解决问题。 (4) 如果一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性避免在两个层次之间建立静态的继承联系通过桥接模式可以使它们在抽象层建立一个关联关系。 (5) 抽象化角色和实现化角色可以以继承的方式独立扩展而互不影响在程序运行时可以动态将一个抽象化子类的对象和一个实现化子类的对象进行组合即系统需要对抽象化角色和实现化角色进行动态耦合。 (6) 一个类存在两个独立变化的维度且这两个维度都需要进行扩展。 (7) 虽然在系统中使用继承是没有问题的但是由于抽象化角色和具体化角色需要独立变化设计要求需要独立管理这两者。 (8) 对于那些不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统桥接模式尤为适用。 桥接模式包含如下角色 Abstraction抽象类抽象部分提供高层控制逻辑依赖于完成底层实际工作的实现对象。 RefinedAbstraction扩充抽象类提供控制逻辑的变体。 与其父类一样 它们通过通用实现接口与不同的实现进行交互。 Implementor实现类接口实现部分为所有具体实现声明通用接口。抽象部分仅能通过在这里声明的方法与实现对象交互。抽象部分可以列出和实现部分一样的方法但是抽象部分通常声明一些复杂行为这些行为依赖于多种由实现部分声明的原语操作。 ConcreteImplementor具体实现类包括特定于平台或场景的代码。 桥接模式类图表示如下 接下来将使用代码介绍下桥接模式的实现。
// 1、抽象部分提供高层控制逻辑
public class Abstraction {protected Implementor implementor;public Abstraction(Implementor implementor) {this.implementor implementor;}public void operation() {implementor.operationImp();}
}// 2、实现部分为所有具体实现声明通用接口
public interface Implementor {void operationImp();
}// 3、具体实现类包含特定于平台或场景的代码
public class ConcreteImplementorA implements Implementor {Overridepublic void operationImp() {System.out.println(---------do some thing in a concrete implementor A instance---------);}
}// 4、具体实现类包括特定于平台或场景的代码
public class RefinedAbstraction extends Abstraction {public RefinedAbstraction(Implementor implementor) {super(implementor);}Overridepublic void operation() {this.implementor.operationImp();}
}
// 5、客户端调用
public class BridgeClient {public void test() {// (1) 指定抽象部分并绑定具体的实现部分Abstraction abstraction1 new RefinedAbstraction(new ConcreteImplementorA());// (2) 调用抽象部分接口abstraction1.operation();}
}桥接模式有以下优点 (1) 符合开闭原则。分离抽象接口及其实现部分。开发者可以单独新增抽象部分和实现部分它们之间不会相互影响。 (2) 符合单一职责原则。抽象部分专注于处理高层逻辑 实现部分处理平台细节。 (3) 提高了代码的可扩展性。在抽象部分或实现部分任意扩展一个维度都不需要修改原有系统。 (4) 提高了代码的透明性。 实现细节对客户透明可以对用户隐藏实现细节。 (5) 提高了代码的复用性。桥接模式有时类似于多继承方案但是多继承方案违背了类的单一职责原则即一个类只有一个变化的原因复用性比较差。而且多继承结构中类的个数非常庞大桥接模式是比多继承方案更好的解决方法。 但是桥接模式也存在以下缺点 (1) 代码复杂度上升。比如对高内聚的类使用该模式可能会使代码更加复杂。 (2) 增加系统的理解与设计难度由于聚合关联关系建立在抽象层要求开发者针对抽象进行设计与编程。 (3) 桥接模式要求正确识别出系统中两个独立变化的维度因此其使用范围具有一定的局限性。
简单介绍下组合模式
组合模式是一种结构型设计模式主要用来将多个对象组织成树形结构以表示“部分-整体”的层次结构因此该模式也称为“部分-整体”模式。简言之组合模式就是用来将一组对象组合成树状结构并且能像使用独立对象一样使用它们。 在以下情况下可以考虑使用组合模式 (1) 如果需要实现树状对象结构可以考虑使用组合模式。 组合模式提供了两种共享公共接口的基本元素类型简单叶节点和复杂容器。容器中可以包含叶节点和其他容器。这使得开发者可以构建树状嵌套递归对象结构。 (2) 如果希望客户端代码以相同方式处理简单和复杂元素 可以使用该模式。 组合模式中定义的所有元素共用同一个接口。在这一接口的帮助下客户端不必在意其所使用的对象的具体类。 为实现组合模式首先需要创建一个可以组合多个对象的单一对象Component这个对象用来访问和管理其子对象并对外提供公共接口。然后定义没有子节点的对象Leaf基本对象和包含子对象的对象Composite组合对象。最后将这些对象组装到之前创建的对象上。这样外部Client就可通过Component调用公共接口。组合模式包含如下角色 Component组合对象为组合中的对象声明公共接口并提供默认实现。 Leaf叶节点对象叶节点最终会完成大部分的实际工作因为它们无法将工作指派给其他部分。 Compoiste组合也称容器包含叶节点或其他容器的单位。容器不知道其子项目所属的具体类 它只通过通用的组件接口与其子项目交互。 组合模式类图表示如下 注意 (1) 组合模式对基本对象和组合对象的使用具有一致性。外部代码调用Component公共接口时无需区别对待基本对象和组合对象(透明性)大多数情况下可以一致地处理它们。 接下来将使用代码介绍下组合模式的实现。
// 1、Component组合对象为组合中的对象声明公共接口并提供默认实现。
public abstract class Component {private String name;protected ListComponent children new ArrayList();public Component(String componentName) {this.name componentName;}public void operation() {System.out.println(this.name);}public Component getChild(String componentName) {for (Component current : children) {if (current.name.equals(componentName)) {return current;}Component childComponent current.getChild(componentName);if (childComponent ! null) {return childComponent;}}return null;}public abstract void add(Component component);public abstract void remove(Component component);
}
// 2、Compoiste组合也称容器包含叶节点或其他容器的单位。容器不知道其子项目所属的具体类
// 它只通过通用的组件接口与其子项目交互。
public class Composite extends Component {public Composite(String componentName) {super(componentName);}Overridepublic void add(Component component) {this.children.add(component);}Overridepublic void remove(Component component) {this.children.remove(component);}
}
// 3、Leaf叶节点对象叶节点最终会完成大部分的实际工作因为它们无法将工作指派给其他部分。
public class Leaf extends Component {public Leaf(String componentName) {super(componentName);}Overridepublic void add(Component component) {throw new RuntimeException(叶节点不能添加子节点);}Overridepublic void remove(Component component) {throw new RuntimeException(叶节点不包含子节点无法移除子节点);}
}
// 4、客户端调用
public class CompositeClient {public void test() {Component root new Composite(root);root.add(new Leaf(Leaf A));Composite branch new Composite(Composite X);Leaf leafXa new Leaf(Leaf XA);branch.add(leafXa);branch.add(new Leaf(Leaf XB));branch.remove(leafXa);root.add(branch);Component leafXb root.getChild(Leaf XB);leafXb.operation();}
}这里只介绍了基于透明性的设计与实现组合模式还支持一种基于安全性的设计与实现更多安全性相关知识可以执行搜索并学习。 组合模式最大特点是将多个对象组织成树形结构。组合模式有以下优点 (1) 可以利用多态和递归机制更方便地使用复杂树结构。 (2) 符合开闭原则。无需更改现有代码开发者就可以在应用中添加新元素使其成为对象树的一部分。 但是组合模式也存在以下缺点 (1) 对于功能差异较大的类 提供公共接口或许会有困难。在特定情况下开发者需要过度一般化组件接口使其变得令人难以理解。
简单说下行为型设计模式(11个)
行为型模式用于描述程序在运行时复杂的流程控制即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务它涉及算法与对象间职责的分配。 行为型模式分为类行为模式和对象行为模式前者采用继承机制来在类间分派行为后者采用组合或聚合在对象间分配行为。由于组合关系或聚合关系比继承关系耦合度低满足“组合复用原则”所以对象行为模式比类行为模式具有更大的灵活性。 行为型模式是 GoF 设计模式中最为庞大的一类它包含以下 11 种模式。 模板方法Template Method模式定义一个操作中的算法骨架将算法的一些步骤延迟到子类中使得子类在可以不改变该算法结构的情况下重定义该算法的某些特定步骤。 策略Strategy模式定义了一系列算法并将每个算法封装起来使它们可以相互替换且算法的改变不会影响使用算法的客户。 命令Command模式将一个请求封装为一个对象使发出请求的责任和执行请求的责任分割开。 职责链Chain of Responsibility模式把请求从链中的一个对象传到下一个对象直到请求被响应为止。通过这种方式去除对象之间的耦合。 状态State模式允许一个对象在其内部状态发生改变时改变其行为能力。 观察者Observer模式多个对象间存在一对多关系当一个对象发生改变时把这种改变通知给其他多个对象从而影响其他对象的行为。 中介者Mediator模式定义一个中介对象来简化原有对象之间的交互关系降低系统中对象间的耦合度使原有对象之间不必相互了解。 迭代器Iterator模式提供一种方法来顺序访问聚合对象中的一系列数据而不暴露聚合对象的内部表示。 访问者Visitor模式在不改变集合元素的前提下为一个集合中的每个元素提供多种访问方式即每个元素有多个访问者对象访问。 备忘录Memento模式在不破坏封装性的前提下获取并保存一个对象的内部状态以便以后恢复它。 解释器Interpreter模式提供如何定义语言的文法以及对语言句子的解释方法即解释器。 以上 11 种行为型模式除了模板方法模式和解释器模式是类行为型模式其他的全部属于对象行为型模式下面我们将详细介绍它们的特点、结构与应用。
简单介绍下策略模式
策略模式是一种行为设计模式就是定义一系列算法然后将每一个算法封装起来并使它们可相互替换。本模式通过定义一组可相互替换的算法实现将算法独立于使用它的用户而变化。 在以下情况可以考虑使用策略模式 (1) 如果在一个系统里面有许多类它们之间的区别仅在于它们的行为那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为。 (2) 一个系统需要动态地在几种算法中选择一种 可考虑使用策略模式。 策略模式能够将对象关联至可以不同方式执行特定子任务的不同子对象从而以间接方式在运行时更改对象行为。 (3) 当类中使用了复杂条件运算符(如多重的条件选择语句)以在同一算法的不同变体中切换时 可使用该模式。 策略模式可将所有继承自同样接口的算法抽取到独立类中因此可以不需要条件语句。原始对象并不实现所有算法的变体而是将执行工作委派给其中的一个独立算法对象。 (4) 不希望客户端知道复杂的、与算法相关的数据结构在具体策略类中封装算法和相关的数据结构提高算法的保密性与安全性。 策略模式能将各种算法的代码、 内部数据和依赖关系与其他代码隔离开来。 不同客户端可通过一个简单接口执行算法 并能在运行时进行切换。 为实现一系列可相互替换的算法可定义一个公共接口然后定义一组实现该接口的具体策略这样就可在上下文中使用该接口调用具体策略上定义的算法。 Context上下文维护指向具体策略的引用且仅通过策略接口与该对象进行交流。上下文可以维护一个对策略对象的引用这符合组合设计原则。 上下文定义了一个接口以封装对策略对象的访问。如果策略对象直接暴露给外部使用会导致其内部实现细节的暴露从而增加接口使用难度。 Strategy策略基类或策略接口声明了一个上下文用于执行策略的方法。 ConcreteStrategy具体策略类实现了策略类声明的方法。 策略模式类图表示如下 接下来将使用代码介绍下策略模式的实现。
// 1、抽象策略类声明执行策略的方法
public interface IStrategy {void operation(String paramStr);
}
// 2、具体策略类A实现策略接口声明的方法
public class ConcreteAStrategy implements IStrategy {Overridepublic void operation(String paramStr) {System.out.println(do some thing in the concrete A instance);}
}
// 2、具体策略类B实现策略接口声明的方法
public class ConcreteBStrategy implements IStrategy {Overridepublic void operation(String paramStr) {System.out.println(do some thing in the concrete B instance);}
}
// 3、策略上下文维护指向具体策略的引用且仅通过策略接口与该对象进行交流。这里提供两种使用策略类的方式。
public class StrategyContext {private static final MapString, IStrategy STRATEGY_MAP;private IStrategy strategy;static {STRATEGY_MAP new HashMap();STRATEGY_MAP.put(type A, new ConcreteAStrategy());STRATEGY_MAP.put(type B, new ConcreteBStrategy());}public StrategyContext() {}public StrategyContext(IStrategy strategy) {this.strategy strategy;}public void doSomething(String paramStr) {strategy.operation(paramStr);}public void doSomething(String strategyType, String paramStr) {IStrategy currentStrategy STRATEGY_MAP.get(strategyType);if (Objects.isNull(currentStrategy)) {throw new RuntimeException(strategy is null);}currentStrategy.operation(paramStr);}
}
// 4、策略模式客户端
public class StrategyClient {public void test() {StrategyContext strategyContextA new StrategyContext(new ConcreteAStrategy());strategyContextA.doSomething(TEST);StrategyContext strategyContextB new StrategyContext();strategyContextB.doSomething(type B, TEST);}
}策略模式有以下优点 (1) 符合开闭原则。可以在不修改原有系统的基础上选择算法或行为也可以灵活地增加新的算法或行为。 (2) 定义一系列可重用的算法。策略模式提供了管理相关的算法族的办法。 (3) 使用组合来代替继承。实现支持多种算法或行为的方法。 (4) 避免使用多重条件语句。当不同的行为堆砌在一个类时很难避免使用条件语句来选择合适的行为。如果将行为封装在一个个独立的Strategy类中则可消除这些条件语句。 如使用字典的初始化从文件中读取的方式就可将策略配置移除到外部从而进一步减少不必要的代码修改。 但是策略模式也存在以下缺点 (1) 如果使用的算法极少发生改变那么没有任何理由引入新的类和接口。使用策略模式只会让程序过于复杂。 (2) 策略模式将造成产生很多策略类可以通过使用享元模式在一定程度上减少对象的数量。 (3) 许多现代编程语言支持函数类型功能允许在一组匿名函数中实现不同版本的算法。这样就可以使用这些函数来替换策略对象无需借助额外的类和接口来保持代码简洁。如在Java语言中是Lambda表达式在C语言中是函数指针。
简单介绍下模板方法模式
模板方法模式是一种行为设计模式在超类中定义了一个算法的框架而将一些步骤的实现延迟到子类中使得子类可重定义该算法的特定步骤。 在以下情况下可以考虑使用模板方法模式 (1) 如果只希望子类扩展某个特定算法步骤而不是整个算法或其结构时可考虑使用模板方法模式。模板方法将整个算法转换为一系列独立的步骤以便子类能对其进行扩展同时还可让超类中所定义的结构保持完整。 (2) 当多个类的算法除一些细微不同之外几乎完全一样时 可使用该模式。但其后果就是只要算法发生变化就可能需要修改所有的类。在将算法转换为模板方法时可将相似的实现步骤提取到超类中以去除重复代码。子类间各不同的代码可继续保留在子类中。 模板方法模式包含如下角色 Abstract Class抽象类实现一个模板方法定义了算法的骨架。需要说明的是算法中的步骤可以被声明为抽象类型也可以提供一些默认实现。 Concrete Class具体类按需实现算法步骤但不能重写模板方法自身。 模板方法模式类图表示如下 一个模板方法用一些抽象的操作定义一个算法而子类将重定义这些操作以提供具体的行为。通过使用抽象操作定义一个算法的一些步骤模板方法确定了它们的先后顺序但允许子类改变这些具体步骤以满足它们各自的需求。 接下来将使用代码介绍下模板方法模式的实现。
// 1、抽象类实现一个模板方法定义了算法的骨架
public abstract class AbstractClass {// 将方法声明为final表示该方法不可overridepublic final void templateMethod() {operation1();operation2();}protected abstract void operation1();protected abstract void operation2();
}//2、具体类按需实现算法步骤
public class ConcreteClassA extends AbstractClass {Overrideprotected void operation1() {System.out.println(operation1 in a ConcreteClassA instance);}Overrideprotected void operation2() {System.out.println(operation2 in a ConcreteClassA instance);}
}
public class ConcreteClassB extends AbstractClass {Overrideprotected void operation1() {System.out.println(operation1 in a ConcreteClassB instance);}Overrideprotected void operation2() {System.out.println(operation2 in a ConcreteClassB instance);}
}// 3、客户端
public class TemplateMethodClient {public void test() {// (1) 创建具体类示例AbstractClass classA new ConcreteClassA();// (2) 调用模板方法classA.templateMethod();AbstractClass classB new ConcreteClassB();classB.templateMethod();}
}模板方法模式有以下优点 (1) 仅允许子类重写算法中的特定部分使得算法其他部分修改对其所造成的影响减小。 (2) 可将重复代码提取到父类中。 但是该模式也存在以下缺点 (1) 部分子类可能会受到算法框架的限制。如果算法框架需要调整则该模式不适用。 (2) 通过子类抑制默认步骤实现可能会导致违反里氏替换原则。 (3) 每一个不同的实现都需要一个子类实现导致类的个数增加使得系统更加庞大。模板方法中的步骤越多其维护工作就可能会越困难。
简单介绍下职责链模式
责任链模式是一种行为设计模式允许将请求沿着处理者链进行发送。收到请求后每个处理者均可对请求进行处理或将其传递给链上的下个处理者。职责链模式使多个对象都有机会处理请求从而避免请求的发送者和接受者之间的耦合关系。 在以下情况可以考虑使用责任链模式 (1) 当必须按顺序执行多个处理者时 可以使用该模式。无论以何种顺序将处理者连接成一条链所有请求都会严格按照顺序通过链上的处理者。 (2) 当需要使用不同方式处理不同种类请求而且请求类型和顺序预先未知时可以使用责任链模式。该模式能将多个处理者连接成一条链。 接收到请求后 它会 “询问” 每个处理者是否能够对其进行处理。这样所有处理者都有机会来处理请求。 (3) 如果所需处理者及其顺序必须在运行时进行改变 可以使用责任链模式。如果在处理者类中有对引用成员变量的设定方法 将能动态地插入和移除处理者 或者改变其顺序。 责任链模式包含如下角色 Handler是抽象处理者声明了一个处理请求的接口该接口通常仅包含单个方法用于请求处理但有时还会包含一个设置后继者的方法。 ConcreteHandler是具体处理者处理它所负责的请求每个处理者接收到请求后都必须决定是否进行处理以及是否向下传递请求。可访问它的后继者如果可处理该请求就处理否则就将该请求转发给它的后继者。 处理者通常是独立且不可变的 需要通过构造函数一次性地获得所有必要地数据。 责任链模式类图表示如下 接下来将使用代码介绍下责任链模式的实现。
// 1、Handler是抽象处理者声明了一个处理请求的接口另外还包含一个设置后继者的方法
public abstract class Handler {private Handler nextHandler;public void setNextHandler(Handler nextHandler) {this.nextHandler nextHandler;}protected void doNext() {if (nextHandler ! null) {nextHandler.handleRequest();}}public abstract void handleRequest();
}// 2、具体处理者处理它所负责的请求接收到请求后决定是否进行处理以及是否向下传递请求
public class ConcreteHandlerA extends Handler {Overridepublic void handleRequest() {System.out.println(I am a concrete handler A instance);doNext();}
}
public class ConcreteHandlerB extends Handler {Overridepublic void handleRequest() {System.out.println(I am a concrete handler B instance);doNext();}
}// 3、客户端
public class ResponsibilityClient {public void test() {// (1) 创建处理器实例Handler handlerA new ConcreteHandlerA();Handler handlerB new ConcreteHandlerB();// (2) 定义先后顺序handlerA.setNextHandler(handlerB);// (3) 处理请求handlerA.handleRequest();}
}责任链模式有以下优点 (1) 提高了灵活性。请求处理的顺序可以按需控制提高了系统的灵活性。 (2) 符合开闭原则。可以在不更改现有代码的情况下在程序中新增处理者。 (3) 符合单一职责原则。发起操作和执行操作的类进行解耦。 但是该模式也存在以下缺点 (1) 性能可能会受到影响。职责链的链路不宜过长可能会引入性能问题。 (2) 不能保证请求一定被接收。因为没有指定处理者所以存在请求直到链的末尾都得不到处理的情况。
简单介绍下观察者模式
观察着模式是一种行为设计模式可以用来定义对象间的一对多依赖关系使得每当一个对象状态发生改变时其相关依赖对象皆得到通知并被自动更新。 观察者模式又叫做发布-订阅Publish/Subscribe模式、模型-视图Model/View模式、源-监听器Source/Listener模式或从属者Dependents模式。 在以下情况下可以考虑使用观察者模式 (1) 一个抽象模型有两个方面其中一个方面依赖于另一个方面。将这些方面封装在独立的对象中使它们可以各自独立地改变和复用。 (2) 一个对象的改变将导致其他一个或多个对象也发生改变而不知道具体有多少对象将发生改变可使用观察者模式以降低对象之间的耦合度。 (3) 一个对象必须通知其他对象而并不知道这些对象是谁。 (4) 需要在系统中创建一个触发链A对象的行为将影响B对象B对象的行为将影响C对象……可以使用观察者模式创建一种链式触发机制。 观察者模式包含如下角色 Subject: 目标提供注册和删除观察者对象的接口。会向观察者对象发送值得关注的事件。 ConcreteSubject: 具体目标实现注册和删除观察者对象的接口。当目标的状态发生改变时目标会遍历观察者列表并调用每个观察者对象的通知方法。 Observer: 观察者为那些在目标发生改变时需获得通知的对象定义了一个更新接口。在绝大多数情况下该接口仅包含一个update方法。该方法可以拥有多个参数使目标能在状态更新时传递详细信息。 ConcreteObserver: 具体观察者维护一个指向ConcreteSubject的引用。实现Observer的更新接口已使自身状态与目标状态保持一致。 观察者模式类图表示如下 接下来将使用代码介绍下观察者模式的实现。
// 1、观察者定义了一个更新接口用于目标发生改变时传递详细信息
public class Observer {public void update() {System.out.println(I am an observer instance);}
}
// 2、具体观察者实现观察者的更新接口使自身状态与目标状态保持一致
public class ConcreteObserver extends Observer {public void update() {super.update();doSomething();}private void doSomething() {System.out.println(I am a concrete observer instance);}
}
// 3、目标提供注册和删除观察者对象的接口会向观察者对象发送值得关注的事件
public abstract class Subject {private ListObserver observerList new ArrayList();public void attach(Observer observer) {observerList.add(observer);}public void detach(Observer observer) {observerList.remove(observer);}public void notifyObserver() {if (observerList null || observerList.size() 0) {return;}observerList.forEach(Observer::update);}public abstract void doSomething();
}
// 4、具体目标实现目标的接口指定通知观察者的具体时机
public class ConcreteSubject extends Subject {public void doSomething() {notifyObserver();}
}
// 5、客户端
public class ObserverClient {public void test() {Observer observer1 new ConcreteObserver();Observer observer2 new ConcreteObserver();Subject subject new ConcreteSubject();subject.attach(observer1);subject.attach(observer2);subject.doSomething();subject.detach(observer2);subject.doSomething();}
}观察者模式有以下优点 (1) 松耦合。在观察目标和观察者之间建立一个抽象的耦合。 (2) 符合开闭原则。无需修改发布者代码就能引入新的订阅者类 如果是发布者接口则可轻松引入发布者类。 (3) 支持广播通信。 (4) 可以实现表示层和数据逻辑层的分离并定义了稳定的消息更新传递机制抽象了更新接口使得可以有各种各样不同的表示层作为具体观察者角色。 但是该模式也存在以下缺点 (1) 如果一个观察目标对象有很多直接和间接的观察者的话将所有的观察者都通知到会花费很多时间。 (2) 如果在观察者和观察目标之间有循环依赖的话观察目标会触发它们之间进行循环调用可能导致系统崩溃。 (3) 观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的而仅仅只是知道观察目标发生了变化。
简单介绍下访问者模式
访问者模式是一种行为设计模式可封装一些作用于当前数据结构的各元素的操作它可以在不改变数据结构的前提下定义作用于这些元素的新的操作。 在以下情况下可以考虑使用访问者模式 (1) 如果需要对一个复杂对象结构中的所有元素执行某些操作可考虑使用访问者模式。访问者模式通过在访问者对象中为多个目标类提供相同操作的变体让开发者能在属于不同类的一组对象上执行同一操作。 (2) 可使用访问者模式来清理辅助行为的业务逻辑。访问者模式可将所有非主要的行为抽取到一组访问者类中使得程序的主要类能更专注于主要的工作。 (3) 当某个行为仅在类层次结构中的一些类中有意义而在其他类中没有意义时可考虑使用访问者模式。可将该行为抽取到单独的访问者类中只需实现接收相关类的对象作为参数的访问者方法并将其他方法留空即可。 访问者模式包含如下角色 Visitor访问者基类声明了一系列以对象结构的具体元素为参数的访问者方法。这些方法的名称可能是相同的但是其参数一定是不同的。 ConcreteVisitor具体访问者会为不同的具体元素类实现相同行为的几个不同版本。 ObjectStructure对象结构类该类能枚举它包含的元素可以提供一个高层的接口以允许访问者访问它的元素。 Element元素声明了一个方法来“接收”(accept)访问者。该方法必须有一个参数被声明为访问者接口类型。 ConcreteElement具体元素实现Element声明的接口。该方法的目的是根据当前元素类将其调用重定向到相应访问者的方法。 Client客户端客户端通常不知晓所有的具体元素类因为它们会通过抽象接口与集合中的对象进行交互。 访问者模式类图表示如下 访问者模式可将数据结构与数据操作分离可以解决稳定的数据结构和易变的操作耦合问题。 接下来将使用代码介绍下访问者模式的实现。
// 1、访问者基类声明了对对象结构的具体元素为参数的访问者方法
public interface IVisitor {void visitElement(ConcreteElementA element);void visitElement(ConcreteElementB element);
}//2、具体访问者为不同的具体元素类实现相同行为的几个不同版本
public class ConcreteVisitorA implements IVisitor {Overridepublic void visitElement(ConcreteElementA element) {System.out.println(handle a ConcreteElementA instance in ConcreteVisitorA);}Overridepublic void visitElement(ConcreteElementB element) {System.out.println(handle a ConcreteElementB instance in ConcreteVisitorA);}
}
public class ConcreteVisitorB implements IVisitor {Overridepublic void visitElement(ConcreteElementA element) {System.out.println(handle a ConcreteElementA instance in ConcreteVisitorB);}Overridepublic void visitElement(ConcreteElementB element) {System.out.println(handle a ConcreteElementB instance in ConcreteVisitorB);}
}// 3、元素声明了一个方法来“接收”(accept)访问者。该方法必须有一个参数被声明为访问者接口类型
public interface IElement {void accept(IVisitor visitor);
}// 4、具体元素实现Element声明的接口
public class ConcreteElementA implements IElement {public void accept(IVisitor visitor) {visitor.visitElement(this);}
}
public class ConcreteElementB implements IElement {public void accept(IVisitor visitor) {visitor.visitElement(this);}
}// 5、对象结构类可枚举它包含的元素可以提供一个高层的接口以允许访问者访问它的元素
public class ObjectStructure {private IElement elementA;private IElement elementB;public ObjectStructure(IElement elementA, IElement elementB) {this.elementA elementA;this.elementB elementB;}public IElement getElementA() {return this.elementA;}public IElement getElementB() {return this.elementB;}
}// 6、客户端
public class VisitorClient {public void test() {// (1) 创建元素实例IElement elementA new ConcreteElementA();IElement elementB new ConcreteElementB();// (2) 创建对象结构实例ObjectStructure objectStructure new ObjectStructure(elementA, elementB);// (3) 创建具体访问者实例IVisitor visitorA new ConcreteVisitorA();// (4) 调用访问者方法visitorA.visitElement((ConcreteElementA) objectStructure.getElementA());visitorA.visitElement((ConcreteElementB) objectStructure.getElementB());IVisitor visitorB new ConcreteVisitorB();visitorB.visitElement((ConcreteElementA) objectStructure.getElementA());visitorB.visitElement((ConcreteElementB) objectStructure.getElementB());}
}访问者模式有以下优点 (1) 符合开闭原则。以引入在不同类对象上执行的新行为 且无需对这些类做出修改。 (2) 符合单一职责原则。可将同一行为的不同版本移到同一个类中。 但是该模式也存在以下缺点 (1) 代码可能会变得更加复杂。使用访问者模式可能会导致某些系统有过多的具体访问者类。 (2) 每次在元素层次结构中添加或移除一个类时都要更新所有的访问者所以该模式对于频繁调整对象结构的类并不友好。 (3) 在访问者同某个元素进行交互时可能没有访问元素私有成员变量和方法的必要权限。这与迪米特法则相违背。 (4) 违背了依赖倒转原则。访问者依赖的是具体元素而不是抽象元素。
简单介绍下中介者模式
中介者模式是一种行为设计模式可以减少对象之间混乱无序的依赖关系。该模式会限制对象之间的直接交互迫使它们通过一个封装了对象间交互行为的中介者对象来进行合作从而使对象间耦合松散并可独立地改变它们之间的交互。中介者模式又称为调停者模式。 在以下情况下可以考虑使用中介者模式 (1) 当一些对象和其他对象紧密耦合产生的相互依赖关系结构混乱且难以理解从而导致难以对其进行修改时可考虑使用中介者模式。中介者模式可将对象间的所有关系抽取成为一个单独的类以使对于特定组件的修改工作独立于其他组件。 (2) 当组件因过于依赖其他组件而无法在不同应用中复用时可考虑使用中介者模式。应用中介者模式后 每个组件不再知晓其他组件的情况。尽管这些组件无法直接交流但它们仍可通过中介者对象进行间接交流。 如果希望在不同应用中复用一个组件则需要为其提供一个新的中介者类。 (3) 如果为了能在不同情景下复用一些基本行为导致需要被迫创建大量组件子类时可考虑使用中介者模式。由于所有组件间关系都被包含在中介者中 因此无需修改组件就能方便地新建中介者类以定义新的组件合作方式。 中介者模式包含如下角色 Component组件基类声明组件的基本功能有一个指向中介者的引用该引用被声明为中介者接口类型。组件不知道中介者实际所属的类因此可通过将其连接到不同的中介者以使其能在其他程序中复用。 Mediator中介者接口声明了与组件交流的方法但通常仅包括一个通知方法。组件可将任意上下文作为该方法的参数只有这样接收组件和发送者类之间才不会耦合。 ConcreteComponent具体组件实现组件声明的方法并自定义业务逻辑接口。 ConcreteMediator具体中介者实现中介者接口声明的方法。 中介者模式类图表示如下 接下来将使用代码介绍下中介者模式的实现。
// 1、中介者接口声明了与组件交流的方法
public interface IMediator {void notify(Sender sender);
}//2、具体中介者实现中介者接口声明的方法
public class ConcreteMediator implements IMediator {Overridepublic void notify(Sender sender) {String message sender.getMessage();Component target sender.getTarget();target.operation(message);}
}// 3、组件基类声明组件的基本功能有一个指向中介者的引用该引用被声明为中介者接口类型
public abstract class Component {protected IMediator mediator;public Component(IMediator mediator) {this.mediator mediator;}public void operation(String message) {System.out.println(message is message);}public void send(String message, Component target) {Sender sender new Sender(message, this, target);mediator.notify(sender);}
}// 4、具体组件实现组件声明的方法并自定义业务逻辑接口
public class ConcreteComponentA extends Component {public ConcreteComponentA(IMediator mediator) {super(mediator);}Overridepublic void operation(String message) {super.operation(message);operationA();}public void operationA() {System.out.println(operationA in a Concrete ComponentA instance);}
}
public class ConcreteComponentB extends Component {public ConcreteComponentB(IMediator mediator) {super(mediator);}Overridepublic void operation(String message) {super.operation(message);operationB();}public void operationB() {System.out.println(operationB in a Concrete ComponentB instance);}
}
public class ConcreteComponentC extends Component {public ConcreteComponentC(IMediator mediator) {super(mediator);}Overridepublic void operation(String message) {super.operation(message);operationC();}public void operationC() {System.out.println(operationC in a Concrete ComponentC instance);}
}
public class ConcreteComponentD extends Component {public ConcreteComponentD(IMediator mediator) {super(mediator);}Overridepublic void operation(String message) {super.operation(message);operationD();}public void operationD() {System.out.println(operationD in a Concrete ComponentD instance);}
}// 5、客户端
public class MediatorClient {public void test() {IMediator mediator new ConcreteMediator();Component componentA new ConcreteComponentA(mediator);Component componentB new ConcreteComponentB(mediator);Component componentC new ConcreteComponentC(mediator);Component componentD new ConcreteComponentD(mediator);componentA.send(i am a, componentB);componentB.send(i am b, componentC);componentC.send(i am c, componentD);componentD.send(i am d, componentA);}
}public class Sender {private String message;private Component source;private Component target;public Sender(String message, Component source, Component target) {this.message message;this.source source;this.target target;}public String getMessage() {return this.message;}public Component getSource() {return this.source;}public Component getTarget() {return this.target;}
}中介者模式有以下优点 (1) 符合单一职责原则。可以将多个组件间的交流抽取到同一位置使其更易于理解和维护。 (2) 符合开闭原则。无需修改实际组件就能增加新的中介者。 (3) 可以减轻应用中多个组件间的耦合情况。 但是该模式也存在以下缺点 (1) 在具体中介者类中包含了组件之间的交互细节可能会导致具体中介者类非常复杂使得系统难以维护。一段时间后中介者可能会演化成为上帝对象。
简单介绍下解释器模式
解释器模式是一种行为设计模式可以解释语言的语法或表达式。给定一个语言定义它的文法的一种表示然后定义一个解释器使用该文法来解释语言中的句子。解释器模式提供了评估语言的语法或表达式的方式。 在以下情况可以考虑使用解释器模式 (1)如果需要解释执行的语言中的句子可以表示为一个抽象语法树可以考虑使用解释器模式。如SQL 解析、符号处理引擎、正则表达式等。 (2) 对于重复出现的问题如果可以使用简单的语言来表达可以考虑使用解释器模式。 (3) 一个简单语法需要解释的场景可以考虑使用解释器模式。对于简单语法由于其文法规则较简单使用解释器模式要优于语法分析程序。 解释器模式包含如下角色 Context上下文包含解释器之外的一些全局信息。 AbstractExpression抽象表达式声明一个抽象的解释操作这个接口为抽象语法树中所有的节点所共享。 TerminalExression终结符表达式实现与文法中的终结符相关联的解释操作。 NonterminalExpression非终结符表达式实现与文法中的非终结符相关联的解释操作对文法中每一条规则R1、R2、…、Rn 都需要一个具体的非终结符表达式类。 Client客户端构建一个句子它是TerminalExression和NonterminalExpression的实例的一个抽象语法树然后初始化Context并调用解释操作。 解释器模式类图表示如下 接下来将使用代码介绍下解释器模式的实现。由于无法使用抽象的用例表示出解释器模式所以这里会基于特定的场景给出代码示例。这里以常见的四则运算(由于除法需要特殊处理这里暂不提供)的解析为例介绍下解释器模式的实现。
// 1、抽象表达式声明一个抽象的解释操作接口
public interface Expression {int interpret();
}//2、终结符表达式实现与文法中的终结符相关联的解释操作这里是数字
public class NumberExpression implements Expression {private int number;public NumberExpression(int number) {this.number number;}public NumberExpression(String number) {this.number Integer.parseInt(number);}Overridepublic int interpret() {return this.number;}
}// 3、非终结符表达式实现与文法中的非终结符相关联的解释操作这里是运算符
public class AdditionExpression implements Expression {private Expression firstExpression, secondExpression;public AdditionExpression(Expression firstExpression, Expression secondExpression) {this.firstExpression firstExpression;this.secondExpression secondExpression;}Overridepublic int interpret() {return Math.addExact(this.firstExpression.interpret(), this.secondExpression.interpret());}Overridepublic String toString() {return ;}
}
public class SubtractionExpression implements Expression {private Expression firstExpression, secondExpression;public SubtractionExpression(Expression firstExpression, Expression secondExpression) {this.firstExpression firstExpression;this.secondExpression secondExpression;}Overridepublic int interpret() {return Math.subtractExact(this.firstExpression.interpret(), this.secondExpression.interpret());}Overridepublic String toString() {return -;}
}
public class MultiplicationExpression implements Expression {private Expression firstExpression, secondExpression;public MultiplicationExpression(Expression firstExpression, Expression secondExpression) {this.firstExpression firstExpression;this.secondExpression secondExpression;}Overridepublic int interpret() {return Math.multiplyExact(this.firstExpression.interpret(), this.secondExpression.interpret());}Overridepublic String toString() {return *;}
}// 4、表达式分析器将输入解析成表达式并执行相关的计算
public class ExpressionParser {private static final String ADD ;private static final String SUBTRACT -;private static final String MULTIPLY *;private static final String SPLITTER ;private LinkedListExpression stack new LinkedList();public int parse(String str) {String[] tokenList str.split(SPLITTER);for (String symbol : tokenList) {if (!isOperator(symbol)) {Expression numberExpression new NumberExpression(symbol);stack.push(numberExpression);} else {Expression firstExpression stack.pop();Expression secondExpression stack.pop();Expression operator getExpressionObject(firstExpression, secondExpression, symbol);if (operator null) {throw new RuntimeException(unknown symbol: symbol);}int result operator.interpret();NumberExpression resultExpression new NumberExpression(result);stack.push(resultExpression);}}return stack.pop().interpret();}private boolean isOperator(String symbol) {return symbol.equals(ADD) || symbol.equals(SUBTRACT) || symbol.equals(MULTIPLY);}private Expression getExpressionObject(Expression firstExpression, Expression secondExpression, String symbol) {switch (symbol) {case ADD:return new AdditionExpression(firstExpression, secondExpression);case SUBTRACT:return new SubtractionExpression(firstExpression, secondExpression);case MULTIPLY:return new MultiplicationExpression(firstExpression, secondExpression);default:return null;}}
}// 5、客户端
public class InterpreterClient {public void test() {// (1) 定义输入String input 2 1 5 *;System.out.println(input is: input);// (2) 创建表达式分析器实例ExpressionParser expressionParser new ExpressionParser();// (3) 执行分析操作int result expressionParser.parse(input);System.out.println(result: result);}
}解释器模式有以下优点 (1) 可扩展性好。因为该模式使用类来表示文法规则可以使用继承来改变或扩展该文法。 (2) 易于实现简单的文法。定义抽象语法树各个节点的类的实现大体相似。 但是该模式也存在以下缺点 (1) 可利用场景比较少。 (2) 对于复杂的文法比较难维护。包含许多规则的文法可能难以管理和维护。 (3) 会引起类膨胀。随着文法规则的复杂化类的规模也会随之膨胀。 (4) 使用了大量的循环和递归需要考虑效率问题。
简单介绍下迭代器模式
迭代器模式是一种行为设计模式可以在不暴露底层实现(列表、栈或树等)的情况下遍历一个聚合对象中所有的元素。 在以下情况可以考虑使用迭代器模式 (1) 当聚合对象的数据结构较复杂且希望对客户端隐藏其复杂性时(出于易用性或安全性考虑)可考虑使用迭代器模式。迭代器封装了与复杂数据结构进行交互的细节为客户端提供多个访问集合元素的简单方法。 这种方式不仅对客户端来说非常方便而且能避免客户端在直接与集合交互时执行错误或有害的操作从而起到保护集合的作用。 (2) 使用该模式可以减少程序中重复的遍历代码。重要迭代算法的代码往往体积非常庞大。当这些代码被放置在程序业务逻辑中时它会让原始代码的职责模糊不清降低其可维护性。因此将遍历代码移到特定的迭代器中可使程序代码更加精炼和简洁。 (3) 如果希望代码能够遍历不同的甚至是无法预知的数据结构可考虑使用迭代器模式。该模式为集合和迭代器提供了一些通用接口。如果在代码中使用了这些接口那么将其他实现了这些接口的集合和迭代器传递给它时它仍将可以正常运行。 迭代器模式包含如下角色 Iterator迭代器基类声明遍历聚合对象所需的操作获取下一个元素、获取当前位置、下一个元素是否存在等。 ConcreteIterator迭代器实现类实现遍历聚合对象的算法。 Aggregate聚合基类声明一个获取迭代器的接口。 ConcreteAggregate具体聚合实现类实现获取一个迭代器的接口并返回一个具体迭代器实例。 Client客户端通过聚合基类和迭代器基类的接口与两者进行交互。 迭代器模式类图表示如下 接下来将使用代码介绍下迭代器模式的实现。
// 1、聚合接口声明一个获取迭代器的接口以及元素添加及删除接口
public interface IAggregate {IIterator createIterator();void append(Object element);void removeLast();
}//2、具体聚合实现类获取一个迭代器的接口并实现元素添加及删除接口这里使用数组存储元素
public class ConcreteAggregate implements IAggregate {private String[] elements;private int cursor -1;public ConcreteAggregate(int size) {elements new String[size];}Overridepublic void append(Object element) {cursor;elements[cursor] (String) element;}Overridepublic void removeLast() {elements[cursor] ;cursor--;}public int getCursor() {return this.cursor;}public String[] getElements() {return this.elements;}Overridepublic IIterator createIterator() {return new ConcreteIterator(this);}
}// 3、迭代器接口声明遍历聚合对象所需的操作获取下一个元素、获取当前位置、下一个元素是否存在等
public interface IIterator {Object first();Object next();boolean hasNext();Object currentItem();
}// 4、迭代器实现类实现遍历聚合对象的算法及其他已声明接口
public class ConcreteIterator implements IIterator {private ConcreteAggregate aggregate;private int index;public ConcreteIterator(ConcreteAggregate aggregate) {this.aggregate aggregate;this.index 0;}Overridepublic Object first() {String[] elements aggregate.getElements();return elements[0];}Overridepublic Object next() {int cursor aggregate.getCursor();if (cursor 0 || index cursor) {return null;}String[] elements aggregate.getElements();return elements[index];}Overridepublic boolean hasNext() {int cursor aggregate.getCursor();if (cursor 0 || index cursor) {return false;}return true;}Overridepublic Object currentItem() {int cursor aggregate.getCursor();if (cursor 0 || index cursor) {return null;}String[] elements aggregate.getElements();return elements[index];}
}// 5、客户端
public class IteratorClient {public void test() {// (1) 创建迭代对象实例IAggregate aggregate new ConcreteAggregate(10);// (2) 增删元素aggregate.append(hello);aggregate.append(world);aggregate.append(foo);aggregate.removeLast();// (3) 获取迭代器实例IIterator iterator aggregate.createIterator();// (4) 执行遍历操作while (iterator.hasNext()) {System.out.println(iterator.next());}}
}迭代器模式有以下优点 (1) 符合单一职责原则。通过将体积庞大的遍历算法代码抽取为独立的类可对客户端代码和集合进行整理。 (2) 符合开闭原则。可实现新型的集合和迭代器并将其传递给现有代码无需修改现有代码。 (3) 可以并行遍历同一集合因为每个迭代器对象都包含其自身的遍历状态。 但是该模式也存在以下缺点 (1) 如果只与简单的集合进行交互应用该模式可能会矫枉过正。 (2) 对于某些特殊集合使用迭代器可能比直接遍历的效率低。 (3) 增加了系统的复杂性。因为迭代器模式将存储数据和遍历数据的职责分离增加了新的聚合类需要对应增加新的迭代器类增加了系统的复杂性。
简单介绍下备忘录模式
备忘录模式是一种行为设计模式在不破坏封装性的前提下允许在不暴露对象实现细节的情况下保存和恢复对象之前的状态。 在以下情况下可以考虑使用备忘录模式 (1) 当需要创建对象状态快照来恢复其之前的状态时可以考虑使用备忘录模式。备忘录模式允许复制对象中的全部状态(包括私有成员变量)并将其独立于对象进行保存。 尽管大部分人因为 “撤销” 这个用例才记得该模式但其实它在处理事务(比如需要在出现错误时回滚一个操作)的过程中也必不可少。 (2) 当直接访问对象的成员变量、获取器或设置器将导致封装被突破时可以考虑使用备忘录模式。备忘录让对象自行负责创建其状态的快照。任何其他对象都不能读取快照这有效地保障了数据的安全性。 一个备忘录(memento)是一个对象它存储另一个对象在某个瞬间的内部状态而后者称为备忘录的原发器(originator)。当需要设置原发器的检查点时取消操作机制会向原发器请求一个备忘录。 原发器用描述当前状态的信息初始化备忘录。只有原发器可以向备忘录中存取信息备忘录对其他的对象不可见。 备忘录模式包含如下角色 Originator原发器可以生成自身状态的快照也可以在需要时通过快照恢复自身状态。 Memento备忘录是原发器状态快照的值对象(value object)。通常做法是将备忘录设为不可变的并通过构造函数一次性传递数据。 Caretaker负责人仅知道“何时”和“为何”捕捉原发器的状态以及何时恢复状态。负责人通过保存备忘录栈来记录原发器的历史状态。 当原发器需要回溯历史状态时负责人将从栈中获取最顶部的备忘录 并将其传递给原发器的恢复(restoration)方法。 备忘录模式类图表示如下 接下来将使用代码介绍下备忘录模式的实现。
// 1、原发器支持读写自身状态支持生成自身状态的快照支持通过快照恢复自身状态
public class Originator {private String name;private String describe;public String getName() {return name;}public void setName(String name) {this.name name;}public void setDescribe(String describe) {this.describe describe;}public String getDescribe() {return this.describe;}public Memento save() {return new Memento(this, name, describe);}public void restore(Memento memento) {setName(memento.getName());setDescribe(memento.getDescribe());}
}//2、备忘录是原发器状态快照的值对象
public class Memento {private String name;private String describe;private Originator originator;public Memento(Originator originator, String name, String describe) {this.originator originator;this.name name;this.describe describe;}public String getName() {return this.name;}public String getDescribe() {return this.describe;}
}// 3、负责人通过保存备忘录栈来记录原发器的历史状态。当原发器需要回溯历史状态时负责人将从栈中获取最顶部的备忘录并将其传递给原发器的恢复(restoration)方法
public class Caretaker {private Originator originator;private LinkedListMemento history;public Caretaker(Originator originator) {this.originator originator;history new LinkedList();}public void snapshot() {history.push(originator.save());}public void undo() {Memento lastMemento history.pop();originator.restore(lastMemento);}
}// 4、客户端
public class MementoClient {public void test() {// (1) 创建原生器实例并设置状态Originator originator new Originator();originator.setName(1);originator.setDescribe(one);// (2) 创建负责人实例Caretaker caretaker new Caretaker(originator);// (3) 创建快照caretaker.snapshot();System.out.println(name is originator.getName() , describe is originator.getDescribe());originator.setName(2);originator.setDescribe(two);caretaker.snapshot();System.out.println(name is originator.getName() , describe is originator.getDescribe());// (4) 恢复上一个状态caretaker.undo();System.out.println(name is originator.getName() , describe is originator.getDescribe());caretaker.undo();System.out.println(name is originator.getName() , describe is originator.getDescribe());}
} 备忘录模式有以下优点 (1) 可以在不破坏对象封装情况的前提下创建对象状态快照。 (2) 可以通过让负责人维护原发器状态历史记录来简化原发器代码。 (3) 给用户提供了一种可恢复状态的机制能够比较方便地回滚到某个历史状态。 但是该模式也存在以下缺点 (1) 如果客户端过于频繁地创建备忘录程序将消耗大量内存。 (2) 负责人必须完整跟踪原发器的生命周期这样才能销毁弃用的备忘录。
简单介绍下命令模式
命令模式是一种行为设计模式可将一个请求封装为一个对象用不同的请求将方法参数化从而实现延迟请求执行或将其放入队列中或记录请求日志以及支持可撤销操作。其别名为动作(Action)模式或事务(Transaction)模式。 在以下情况可以考虑使用命令模式 (1) 如果需要将请求调用者和请求接收者解耦使得调用者和接收者不直接交互可考虑使用该模式。 (2) 如果需要通过操作来参数化对象可考虑使用该模式。命令模式可将特定的方法调用转化为独立对象。这一改变也带来了许多有趣的应用开发者可以将命令作为方法的参数进行传递、将命令保存在其他对象中或者在运行时切换已连接的命令等。 (3) 如果需要将操作放入队列中、操作的执行或者远程执行操作可考虑使该模式。同其他对象一样 命令也可以实现序列化 序列化的意思是转化为字符串从而能方便地写入文件或数据库中。一段时间后该字符串可被恢复成为最初的命令对象。因此可以延迟或计划命令的执行。 但其功能远不止如此。使用同样的方式还可以将命令放入队列、 记录命令或者通过网络发送命令。 (4) 如果需要实现命令的撤销(Undo)操作和恢复(Redo)操作可考虑使该模式。为了能够回滚操作 需要实现已执行操作的历史记录功能。 命令历史记录是一种包含所有已执行命令对象及其相关程序状态备份的栈结构。 这种方法有两个缺点。 首先 程序状态的保存功能并不容易实现 因为部分状态可能是私有的。 可以使用备忘录模式来在一定程度上解决这个问题。 其次 备份状态可能会占用大量内存。 因此 有时需要借助另一种实现方式命令无需恢复原始状态而是执行反向操作。反向操作也有代价 它可能会很难甚至是无法实现。 命令模式包含如下角色 Command命令基类声明一个执行命令的接口。 ConcreteCommand具体命令类实现各种类型的请求。具体命令自身并不完成工作而是会将调用委派给一个业务逻辑对象(Receiver)。但为了简化代码这些类可以进行合并。 Invoker调用者负责对请求进行初始化其中必须包含一个成员变量来存储对于命令对象的引用。触发命令执行但不向接收者直接发送请求。 注意调用者并不负责创建命令对象它通常会通过构造函数从客户端处获得预先生成的命令。 Receiver接收者定义业务逻辑。几乎任何对象都可以作为接收者。绝大部分命令只处理如何将请求传递到接收者的细节接收者自己会完成实际的工作。简化代码这些类可以与具体命令类进行合并。 Client客户端创建并配置具体命令对象。客户端必须将包括接收者实体在内的所有请求参数传递给命令的构造函数。此后生成的命令就可以与一个或多个发送者相关联了。 命令模式类图表示如下 接下来将使用代码介绍下命令模式的实现。
// 1、命令基类声明一个执行命令的接口
public interface ICommand {void execute();
}// 2、具体命令类实现各种类型的请求
public class ConcreteCommandA implements ICommand {private Receiver receiver;public ConcreteCommandA(Receiver receiver) {this.receiver receiver;}Overridepublic void execute() {System.out.println(this is a concrete command A instance);receiver.actionA();}
}
public class ConcreteCommandB implements ICommand {private Receiver receiver;public ConcreteCommandB(Receiver receiver) {this.receiver receiver;}Overridepublic void execute() {System.out.println(this is a concrete command B instance);receiver.actionB();}
}// 3、接收者定义业务逻辑。绝大部分命令只处理如何将请求传递到接收者的细节接收者自己会完成实际的工作。为简化代码这些类可以与具体命令类进行合并
public class Receiver {public void actionA() {System.out.println(action A in a receiver instance);}public void actionB() {System.out.println(action B in a receiver instance);}
}// 4、调用者负责对请求进行初始化和触发命令执行。注意调用者并不负责创建命令对象它通常会通过构造函数从客户端处获得预先生成的命令
public class Invoker {private ICommand command;public Invoker(ICommand command) {this.command command;}public void executeCommand() {this.command.execute();}
}// 5、客户端
public class CommandClient {public void test() {// (1) 创建接收者实例Receiver receiver new Receiver();// (2) 创建命令实例ICommand commandA new ConcreteCommandA(receiver);// (3) 创建调用者实例Invoker invokerA new Invoker(commandA);// (4) 执行命令invokerA.executeCommand();ICommand commandB new ConcreteCommandB(receiver);Invoker invokerB new Invoker(commandB);invokerB.executeCommand();}
}命令模式有以下优点 (1) 可以实现撤销和恢复功能。 (2) 可以实现操作的延迟执行。 (3) 可以将一组简单命令组合成一个复杂命令。 (4) 符合开闭原则。可以解耦触发和执行操作的类。 (5) 符合单一职责原则。发起操作和执行操作的类进行解耦。 但是该模式也存在以下缺点 (1) 代码可能会变得更加复杂。使用命令模式可能会导致某些系统有过多的具体命令类。因为针对每一个命令都需要设计一个具体命令类因此某些系统可能需要大量具体命令类这将影响命令模式的使用。
简单介绍下状态模式
状态模式是一种行为设计模式允许一个对象在其内部状态改变时改变它的行为使其看起来修改了自身所属的类。其别名为状态对象(Objects for States)。 在很多情况下一个对象的行为取决于一个或多个动态变化的属性这样的属性叫做状态这样的对象叫做有状态的(stateful)对象这样的对象状态是从事先定义好的一系列值中取出的。 当一个这样的对象与外部事件产生互动时其内部状态就会改变从而使得系统的行为也随之发生变化。 在UML中可以使用状态图来描述对象状态的变化。 在以下情况可以考虑使用状态模式 (1) 对象的行为依赖于它的状态(属性)并且可以根据它的状态改变而改变它的相关行为同时状态的数量非常多且与状态相关的代码会频繁变更的话可以考虑使用状态模式。 (2) 代码中包含大量与对象状态有关的条件语句这些条件语句的出现会导致代码的可维护性和灵活性变差不能方便地增加和删除状态使客户类与类库之间的耦合增强。在这些条件语句中包含了对象的行为而且这些条件对应于对象的各种状态。 (3) 当相似状态和基于条件的状态机转换中存在许多重复代码时可考虑使用状态模式。状态模式能够生成状态类层次结构通过将公用代码抽取到抽象基类中来减少重复。 状态模式包含如下角色 Context上下文类保存了对于一个Concrete State对象(具体状态对象)的引用并会将所有与该状态相关的工作委派给它。上下文通过状态接口与状态对象交互。 State状态基类接口会声明特定于状态的方法。 Concrete State具体状态类会自行实现特定于状态的方法。当多个状态中包含相似代码可以提供一个封装有部分通用行为的中间抽象类。 状态对象可存储对于上下文对象的反向引用。状态对象可以通过该引用从上下文处获取所需信息并且能触发状态转移。但这可能会带来对象的循环引用在实际使用时要通过对象传参的方式使用。 状态模式类图表示如下 状态模式可能看上去与策略模式相似但有一个关键性的不同——在状态模式中特定状态知道其他所有状态的存在且能触发从一个状态到另一个状态的转换而策略则几乎完全不知道其他策略的存在。 接下来将使用代码介绍下状态模式的实现。
// 1、State状态接口声明特定于状态的方法
public interface IState {void handle(StateContext context);void doSomething();
}// 2、具体状态类会自行实现特定于状态的方法这里特定状态知道其他所有状态的存在且能触发从一个状态到另一个状态的转换
public class ConcreteStateA implements IState {private static ConcreteStateA state;// 这里的单例实现暂不考虑并发场景public static IState getInstance() {if (state null) {state new ConcreteStateA();}return state;}Overridepublic void handle(StateContext context) {doSomething();context.setCurrentState(ConcreteStateB.getInstance());}Overridepublic void doSomething() {System.out.println(do some thing in the concrete A instance);}
}
public class ConcreteStateB implements IState {private static ConcreteStateB state;// 这里的单例实现暂不考虑并发场景public static IState getInstance() {if (state null) {state new ConcreteStateB();}return state;}Overridepublic void handle(StateContext context) {doSomething();context.setCurrentState(ConcreteStateA.getInstance());}Overridepublic void doSomething() {System.out.println(do some thing in the concrete B instance);}
}// 3、状态上下文类保存了对于一个Concrete State对象(具体状态对象)的引用并会将所有与该状态相关的工作委派给它。
// 上下文通过状态接口与状态对象交互。
public class StateContext {private IState currentState;public StateContext(IState defaultState) {this.currentState defaultState;}public IState getCurrentState() {return this.currentState;}public void setCurrentState(IState newState) {this.currentState newState;}public void request() {currentState.handle(this);}
}// 4、客户端
public class StateClient {public void test() {StateContext stateContext new StateContext(new ConcreteStateA());stateContext.request();stateContext.request();stateContext.request();}
}状态模式有以下优点 (1) 封装了转换规则。调用方无需关心状态转换的实现。 (2) 符合开闭原则。无需修改已有状态类和上下文就能引入新状态。 (3) 符合单一职责原则。将与特定状态相关的代码放在单独的类中。 (4) 消除了可能存在的条件语句。通过消除臃肿的状态机条件语句简化上下文代码。 (5) 可以让多个环境对象共享一个状态对象从而减少系统中对象的个数。 但是该模式也存在以下缺点 (1) 如果状态机只有很少的几个状态 或者很少发生改变 那么应用该模式可能会显得小题大作。 (2) 增加系统类和对象的个数。 (3) 实现较为复杂如果使用不当将导致程序结构和代码的混乱。 (4) 对“开闭原则”的支持并不太好对于可以切换状态的状态模式增加新的状态类需要修改那些负责状态转换的源代码否则无法切换到新增状态而且修改某个状态类的行为也需修改对应类的源代码。
参考
《设计模式可复用面向对象软件的基础》 Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides 著 李英军, 马晓星 等译 https://blog.csdn.net/zhaohongfei_358/article/details/115085887 设计模式面试题设计模式速成版 https://zhuanlan.zhihu.com/p/531834565 设计模式面试题 https://blog.csdn.net/czqqqqq/article/details/80451880 单例模式 https://www.nowcoder.com/discuss/353157688295104512 轻松理解工厂模式