一、七大原则
1、单一职责原则
基本介绍
一个类只负责一项职责,如类A负责两个不同职责的时候,就需要将A的粒度分解为两个不同的类A1、A2
注意事项和细节
1)、降低类的复杂度,一个类只负责一项职责
2)、提高类的可读性,可维护性
3)、降低变更引起的风险
4)、通常情况下,应当遵守单一职责原则,但是只要逻辑足够简单,可以违反单一职责原则,只在方法级别遵守单一职责原则。
2、接口隔离原则
基本介绍
一个类对另一个类的依赖应该建立在最小的接口上,拆分接口
需要使用那个接口,就实现哪个接口
案例
public class Segregation1 {
public static void main(String[] args) {
A a = new A();
a.depend1(new B());//A类通过接口依赖B类
}
}
//接口
interface Interface1{
void operation1();
}
interface Interface2{
void operation2();
void operation3();
}
interface Interface3{
void operation4();
void operation5();
}
class B implements Interface1,Interface2 {
@Override
public void operation1() {
System.out.println("B实现了operation1");
}
@Override
public void operation2() {
System.out.println("B实现了operation2");
}
@Override
public void operation3() {
System.out.println("B实现了operation3");
}
}
class D implements Interface1,Interface3 {
@Override
public void operation1() {
System.out.println("D实现了operation1");
}
@Override
public void operation4() {
System.out.println("D实现了operation4");
}
@Override
public void operation5() {
System.out.println("D实现了operation5");
}
}
class A {//通过实现接口Interface1的类B
public void depend1(Interface1 i){
i.operation1();
}
public void depend2(Interface2 i){
i.operation2();
}
public void depend3(Interface2 i){
i.operation3();
}
}
class C {//通过实现接口Interface1的类D
public void depend1(Interface1 i){
i.operation1();
}
public void depend4(Interface3 i){
i.operation4();
}
public void depend5(Interface3 i){
i.operation5();
}
}
3、依赖倒转原则
基本介绍
1)、高层模块不应该依赖低层模块,二者都应该依赖其抽象
2)、抽象不应该依赖细节,细节应该依赖抽象
3)、依赖倒转(倒置)的中心思想是面向接口编程
实现方式
1)、通过构造方法传递
2)、通过接口传递实现依赖
3)、通过setter传递实现依赖
注意事项和细节
1)、低层模块尽量都要有抽象类或接口,或者两者都有,程序稳定性更好
2)、变量的声明类型尽量使抽象类或接口,这样我们的变量引用和实际对象间,就存在一个缓冲层,利于程序扩展和优化
3)、继承时遵循里氏替换原则
案例
public class DependencyInversion {
public static void main(String[] args) {
Person person = new Person();
person.receive(new WeiXin());
}
}
interface IReceiver{
public String getInfo();
}
class Email implements IReceiver{
@Override
public String getInfo(){
return "电子邮件信息:hello,world";
}
}
//增加微信
class WeiXin implements IReceiver{
@Override
public String getInfo() {
return "微信";
}
}
/**
* 方式2:对接口进行依赖
*/
class Person{
public void receive(IReceiver iReceiver){
System.out.println(iReceiver.getInfo());
}
}
4、里氏替换原则
基本介绍
1)、所有引用基类的地方必须能透明地使用其子类的对象
2)、在使用继承时,在子类中尽量不要重写父类的方法
3)、 里氏替换原则告诉我们,继承实际上让两个类耦合性增强了,在适当的情况下,可以通过聚合、组合、依赖来解决问题。
解决方案
让原来的父类和子类都继承一个更通俗的基类,原有的继承关系去掉
案例
public class Liskov {
public static void main(String[] args) {
B b = new B();
int i = b.func3(1, 2);
System.out.println(i);
}
}
class Base{
//把更加基础的方法和成员写到Base类中
}
class A extends Base{
public int func1(int num1,int num2){
return num1-num2;
}
}
class B extends Base {
//组合
private A a = new A();
public int func1(int a,int b){
return a+b;
}
public int func2(int a,int b){
return a*b;
}
public int func3(int a,int b){
return this.a.func1(a,b);
}
}
5、开闭原则
基本介绍
1)、开闭原则是编程中最基础、最重要的设计原则
2)、对扩展开放(对提供方)、对修改关闭(使用方)。
3)、当软件需求变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化。
4)、编程中遵循其他原则,以及使用设计模式的目的就是遵循开闭原则。
案例
public class Ocp {
public static void main(String[] args) {
GraphicEditor graphicEditor = new GraphicEditor();
graphicEditor.drawShape(new Rectangle());
graphicEditor.drawShape(new Circle());
graphicEditor.drawShape(new Triangle());
}
}
//用于绘图的类[使用方]
class GraphicEditor{
//接受Shape队形,然后根据type绘制不同的图形
public void drawShape(Shape s){
s.draw();
}
}
abstract class Shape{
int m_type;
public abstract void draw();
}
class Rectangle extends Shape {
Rectangle(){
super.m_type=1;
}
@Override
public void draw() {
System.out.println("矩形");
}
}
class Circle extends Shape {
Circle(){
super.m_type=2;
}
@Override
public void draw() {
System.out.println("圆形");
}
}
//新增画三角形的类[提供方]
class Triangle extends Shape {
Triangle(){
super.m_type=3;
}
@Override
public void draw() {
System.out.println("三角形");
}
}
6、迪米特原则
基本介绍
1)、一个对象应该对其他对象保持最少的了解
2)、类和类的关系越密切,耦合度越大
3)、一个类对自己依赖的类知道的越少越好。对于被依赖的类不管多么复杂,都尽量将逻辑封装在类的内部,对外除了提供public方法,不对外泄露任何信息。
4)、只与直接朋友通信
5)、直接的朋友:每个对象都会与其他对象有耦合关系,只要两个对象之间有耦合关系,我们就说这两个对象之间是朋友关系。耦合的方式很多,依赖、关联、组合、聚合等。
其中称,出现成员变量、方法参数、方法返回值中的类为直接的朋友,而出现在局部变量中的类不是直接的朋友。就是说,陌生的类最好不要以局部变量的形式出现在类的内部。
注意事项和细节
1)、核心是降低类之间的耦合
2)、注意:由于每个类都减少了不必要的依赖,因此迪米特法则只是要求降低类键(对象间)耦合关系,并不是要求完全没有依赖关系。
案例
public class demeter {
public static void main(String[] args) {
System.out.println("============改进============");
SchoolManager schoolManager = new SchoolManager();
schoolManager.printAllEmployee(new CollegeManager());
}
}
//学校总部员工类
class Employee {
private String id;
public void setId(String id) {
this.id = id;
}
public String getId() {
return id;
}
}
//学院的员工类
class CollegeEmployee {
private String id;
public void setId(String id) {
this.id = id;
}
public String getId() {
return id;
}
}
//管理学院员工的管理类
class CollegeManager {
//返回学院的所有员工
public List<CollegeEmployee> getAllEmployee() {
List<CollegeEmployee> list = new ArrayList<CollegeEmployee>();
for (int i = 0; i < 10; i++) { //这里我们增加了10个员工到 list
CollegeEmployee emp = new CollegeEmployee();
emp.setId("学院员工id= " + i);
list.add(emp);
}
return list;
}
//输出学院员工信息
public void printEmployee(){
//获取到学院员工
List<CollegeEmployee> list1 = getAllEmployee();
System.out.println("------------学院员工------------");
for (CollegeEmployee e : list1) {
System.out.println(e.getId());
}
}
}
//学校管理类
//分析 SchoolManager 类的直接朋友类有哪些 Employee、CollegeManager
//CollegeEmployee 不是 直接朋友 而是一个陌生类,这样违背了 迪米特法则
class SchoolManager {
//返回学校总部的员工
public List<Employee> getAllEmployee() {
List<Employee> list = new ArrayList<Employee>();
for (int i = 0; i < 5; i++) { //这里我们增加了5个员工到 list
Employee emp = new Employee();
emp.setId("学校总部员工id= " + i);
list.add(emp);
}
return list;
}
//该方法完成输出学校总部和学院员工信息(id)
void printAllEmployee(CollegeManager sub) {
//分析问题
/**
* 1.将输出学院的员工的方法,封装到CollegeManager中
*/
sub.printEmployee();
//获取到学校总部员工
List<Employee> list2 = this.getAllEmployee();
System.out.println("------------学校总部员工------------");
for (Employee e : list2) {
System.out.println(e.getId());
}
}
}
封装sub.printEmployee()方法的目的是为了下次调用的时候还要再次重写此方法。注重代码的封装。
7、合成复用原则
基本介绍
原则是尽量使用合成/聚合的方式,而不是使用继承
核心思想
1)、找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。
2)、针对接口编程,而不是针对实现编程。
3)、为了交互对象之间的松耦合设计而努力。
二、UML类图
1)、静态结构图:类图、对象图、包图。。
2)、动态行为图:交互图、状态图、活动图
3)、用例图
类图是描述类与类之间的关系
类图
依赖关系(虚线箭头)
1)、类中用到了对方
2)、如果是类的成员属性
3)、如果是方法的返回类型
4)、是方法接受的参数类型
5)、方法中使用到(违反迪米特法则)
泛化关系(实线三角形)
1)、就是继承关系
2)、如果A类继承了B类,就说A和B存在泛化关系
3)、是依赖关系的特例
实现关系(虚线三角形)
1)、是依赖关系的特例
2)、如果A类实现了B类,就说A和B存在实现关系
关联关系(直线)
他是类与类之间的联系,有双向关系与单项关系
1)、是依赖关系的特例
聚合关系(空心菱形)
表示整体与部分的关系,整体与部分可以分开。
1)、是关联关系的特例
public class Computer {
private Mouse mouse; //鼠标可以和computer分离
private Moniter moniter;//显示器可以和Computer分离
public void setMouse(Mouse mouse) {
this.mouse = mouse;
}
public void setMoniter(Moniter moniter) {
this.moniter = moniter;
}
}
组合关系(实心菱形)
整体与部分不可分开
public class Computer extends Person {
private Mouse mouse = new Mouse(); //鼠标可以和computer不能分离
private Moniter moniter = new Moniter();//显示器可以和Computer不能分离
public void setMouse(Mouse mouse) {
this.mouse = mouse;
}
public void setMoniter(Moniter moniter) {
this.moniter = moniter;
}
}
三、设计模式
概述
1)、创建型模式(如何创建对象):单例模式、抽象工厂模式、原型模式、建造者模式、工厂模式
2)、结构型模式(软件结构上思考):适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式
3)、行为型模式(方法上思考):模板方法模式、命令模式、访问者模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式、状态模式、策略模式、责任链模式
1、单例模式(易)
介绍
保证某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法(静态方法)
1)、饿汉式(静态常量)
优缺点说明:
1)、优点:写法简单,在类装载的时候就完成实例化。避免线程同步问题。
2)、缺点:在类装载的时候完成实例化,没有达到Lazy Loading的效果。
3)、结论:这种方式可用,可能造成内存浪费
//静态常量
class Singleton{
//1.构造方法私有化,外部不能new
private Singleton(){
}
//2.本类内部创建对象实例
private final static Singleton instance = new Singleton();
//3.提供一个公有的静态方法,返回实例对象
public static Singleton getInstance(){
return instance;
}
}
2)、饿汉式(静态代码块)
优缺点说明:与静态变量方式实现的相似
class Singleton{
//1.构造方法私有化,外部不能new
private Singleton(){
}
//2.本类内部创建对象实例
private static Singleton instance ;
static { //静态代码块中创建单例对象
instance = new Singleton();
}
//3.提供一个公有的静态方法,返回实例对象
public static Singleton getInstance(){
return instance;
}
}
3)、懒汉式(线程不安全)
优缺点说明:
1)、起到了Lazy Loading 的效果,只能在单线程下才能使用
2)、在多线程的情况下,会存在多个实例
3)、结论:在多线程情况下,不能使用此方法。
class Singleton{
private static Singleton singleton;
private Singleton(){}
//当使用到该方法使,才去创建instance
public static Singleton getInstance(){
if (singleton==null){
singleton = new Singleton();
}
return singleton;
}
}
4)、懒汉式(线程安全,同步方法)
优缺点说明:
1)、解决了线程不安全的行为
2)、效率太低了,每次要获取实例的时候,执行getInstance()都要进行同步。
3)、在实际开发中,不推荐使用。
class Singleton{
private static Singleton singleton;
private Singleton(){}
//当使用到该方法使,才去创建instance
public static synchronized Singleton getInstance(){
if (singleton==null){
singleton = new Singleton();
}
return singleton;
}
}
5)、懒汉式(线程不安全)
class Singleton{
private static Singleton singleton;
private Singleton(){}
//当使用到该方法时,才去创建instance
public static Singleton getInstance(){
if (singleton==null){
synchronized (Singleton.class){
singleton = new Singleton();
}
}
return singleton;
}
}
此方法是错误的,线程是不安全的
6)、双重检查
优缺点说明:
1)、保证了线程安全
2)、避免反复进行方法
3)、延迟加载、效率较高
4)、结论:推荐使用这种方式
class Singleton{
//volatile通知其他线程singleton的情况
private static volatile Singleton singleton;
private Singleton(){}
public static Singleton getInstance(){
if (singleton==null){
synchronized (Singleton.class){
if (singleton==null){
singleton = new Singleton();
}
}
}
return singleton;
}
}
7)、静态内部类
优缺点说明:
1)、利用了类装载的机制来保证初始化实例时只有一个线程
2)、静态内部类方式在Singleton类被装载时不会立即实例化,而是在需要实例化时,调用getInstance方法,才会装载inner类,从而完成Singleton的实例化。
3)、类的静态属性只会在第一次加载类的时候初始化,JVM帮我们保证了线程的安全性,在类初始化时,别的线程是无法进入的。
4)、优点:避免了线程不安全,利用静态内部类特点是实现延迟加载,效率高。
5)、结论:推荐使用
class Singleton{
private Singleton(){}
//Singleton类装载的时候inner类不会被装载,只有调用getInstance的时候才会被装载
private static class inner{
//装载类的时候线程是安全的,是JVM提供的
private static final Singleton singleton = new Singleton();
}
public static Singleton getInstance(){
return inner.singleton;
}
}
8)、枚举
优缺点说明:
1)、能避免多线程同步问题,而且能防止反序列化重新创建新的对象。
2)、结论:推荐使用
enum Singleton{
INSTANCE;
public void sayOK(){
System.out.println("ok~");
}
}
注意事项和细节说明
1)、单例模式保证了系统内存只存在一个对象,节省了系统资源,对于一些频繁创建销毁的对象,使用单例模式可以提高系统性能
2)、要想实例化单例类是,应该使用相应的获取对象的方法,而不是使用new
3)、使用场景:频繁使用的对象、创建对象耗时过多、耗费资源过多
如:工具类对象、频繁访问数据库或文件的对象(比如数据源、session工厂、JDK中Runtime类)
2、工厂模式(难)
1)、简单工厂模式
传统方式的优缺点:
1)、优点:比较好理解,简单易操作
2)、缺点:违反了设计模式的ocp原则,即对扩展开放,对修改关闭。即当我们给类增加新功能的时候,尽量不修改代码。???持保留意见,看菜鸟教程的工厂模式OrderPizza其实已经是工厂模式了
3)、比如增加pizza的种类,会修改许多相关的代码。
4)改进的思路分析
分析:如果在其他的地方也有创建Pizza的代码,就意味着,也需要修改OrderPizza;
思路:把创建Pizza对象封装到一个类中,这样有新的Pizza种类时,只需要修改改类就行,其他有创建到Pizza对象的代码就不需要修改了->简单工厂模式
简单介绍
1)、简单工厂模式是由一个工厂对象决定创建出哪一种产品类的实例。简单工厂模式是工厂模式家族中最简单实用的模式
2)、定义了一个创建对象的类,由这个类来封装实例化对象的行为(代码)
3)、当用到大量的创建某种、某类或者某批对象时,就可以用到工厂模式。
2)、工厂方法模式
简单介绍
定义一个创建对象的抽象方法,由子类决定要实例化的类,工厂方法模式将对象的实例化推迟到子类
public abstract class OrderPizza {
//定义一个抽象方法,让各个工厂子类自己实现
abstract Pizza createPizza(String orderType);
//构造器
public OrderPizza(){
Pizza pizza = null;
String orderType;//订购披萨的类型
do{
orderType = getType();
pizza = createPizza(orderType);
if (pizza==null){
System.out.println("没有库存,订购失败");
continue;
}
//输出pizza制作过程
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
}while (true);
}
//写一个方法,可以获取披萨类
private String getType() {
try {
BufferedReader strin = new BufferedReader(new InputStreamReader(System.in));
System.out.println("input pizza 种类:");
String str = strin.readLine();
return str;
} catch (IOException e) {
e.printStackTrace();
return "";
}
}
}
定义一个抽象方法,让各个工厂子类自己实现
Pizza类也是个抽象类,根据实际需要动态创建
3)、抽象工厂模式
简单介绍
1)、定义了一个interface用于创建相关或有依赖关系的对象簇,而无需指明具体的类。
2)、抽象工厂模式可以将简单工厂模式和工厂方法模式进行整合。
3)、从设计层面看,抽象工厂模式就是对简单工厂模式的改进(或者成为进一步的抽象)
4)、将工厂抽象成两层,AbsFactory(抽象工厂)和具体实现的工厂子类。
UML图:
抽象工厂模式就是对简单工厂模式在工厂Factory类上再次进行简单工厂模式
工厂模式总结
1)、将实例化对象的代码提取出来
2)、三种工厂模式
3)、设计模式的依赖倒转原则
4)、工厂模式注重结果,建造者模式强调过程
3、原型模式(易)
传统方式的优缺点
public static void main(String[] args) {
Sheep sheep1 = new Sheep("tom", 1, "白色");
Sheep sheep2 = new Sheep(sheep1.getName(), sheep1.getAge(), sheep1.getColor());
Sheep sheep3 = new Sheep(sheep1.getName(), sheep1.getAge(), sheep1.getColor());
Sheep sheep4 = new Sheep(sheep1.getName(), sheep1.getAge(), sheep1.getColor());
Sheep sheep5 = new Sheep(sheep1.getName(), sheep1.getAge(), sheep1.getColor());
System.out.println(sheep1);
System.out.println(sheep2);
System.out.println(sheep3);
System.out.println(sheep4);
System.out.println(sheep5);
}
1)、优点:比较好理解,易操作
2)、在创建新对象是,总是需要重新获取原始对象的属性;如果创建的对象比较复杂,效率较低。
3)、总是需要重新初始化对象,而不是动态地获取对象运行时的状态,不够灵活。
4)、改进的思路:Object类有一个clone()方法,该方法可以将一个java对象复制一份,但是需要实现clone的java类必须要实现一个接口Cloneable,该接口表示该类能够复制且具有复制的能力=》原型模式
基本介绍
1)、原型模式是指:用原型实例指定创建对象的种类,并且通过拷贝这些原型,创建新的对象。
浅拷贝
1)、字面量类型(String或基本数据类型和包装类)会进行值传递
2)、若是引用类型的成员变量,只是将成员变量的引用值复制给一个新的对象。这样改变原有的对象,会改变拷贝的其他对象。
3)、浅拷贝是使用默认clone()方法实现的
深拷贝
1)、字面量类型和引用类型都会拷贝一份,
2)、实现方式1:重写clone方法
3)、实现方式2:通过对象序列化
4)、实现方式3:JSON实现
5)、实现方式4:Spring的BeanUtils
public class DeepProtoType implements Serializable,Cloneable {
public String name;
public DeepCloneableTarget deepCloneableTarget;//引用类型
public DeepProtoType() {
}
//方式1,重写clone
@Override
protected Object clone() {
Object deep = null;
try {
//这里完成对字面量的克隆
deep = super.clone();
//对引用类型,单独处理
DeepProtoType deepProtoType = (DeepProtoType)deep;
deepProtoType.deepCloneableTarget = (DeepCloneableTarget) deepCloneableTarget.clone();
}catch ( Exception e){
e.printStackTrace();
}
return deep;
}
//方式2,序列化
public Object deepClone(){
//创建流对象
ByteArrayOutputStream bos = null;
ObjectOutputStream oos = null;
ByteArrayInputStream bis = null;
ObjectInputStream ois = null;
DeepProtoType o = null;
try {
bos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(bos);
oos.writeObject(this);//当前这个对象以对象流的方式输出(序列化)
//反序列化
bis = new ByteArrayInputStream(bos.toByteArray());
ois = new ObjectInputStream(bis);
o = (DeepProtoType)ois.readObject();
}catch (Exception e){
e.printStackTrace();
}finally {
try {
if (ois!=null)ois.close();
if (bis!=null)bis.close();
if (oos!=null)oos.close();
if (bos!=null)bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return o;
}
}
注意事项和细节
1)、创建新的对象比较复杂时,可以利用原型模式简化对象的创建过程,能提高效率。
2)、不用重新初始化对象,而是动态地获取对象运行时的状态
3)、原始对象发生改变(增加或减少属性),无需修改代码
4)、缺点:需要为每一个类配置一个克隆方法,这对全新的类来说不是很难,但对已有的类进行修改时,需要修改其源代码,违背了ocp原则。
4、建造者模式(易)
传统方式分析:
1)、优点:好理解,易操作
2)、没有缓存层对象,程序的扩展和维护不好,这种设计方案吧产品和创建产品的过程封装在一起,耦合性增强了,
3)、解决方案:将产品和产品建造过程解耦=》建造者模式
基本介绍
1)、可以将复杂对象的建造过程抽象出来,使这个抽象过程的不同实现方式可以构造出不同表现的对象。
2)、用户不需要知道内部的具体构建细节。建造者模式是一步一步创建一个复杂对象的,允许用户只通过制定复杂对象的类型和内容就可以构建他们。
3)、作用:构建复杂对象!!!
四个角色
1)、产品角色(Product);一个具体的产品对象
2)、抽象建造者(Builder);创建一个Product对象的各个部件指定的接口/抽象类
3)、具体建造者(ConcreteBuilder);实现接口,构建和装配各个部件
4)、指挥者(Director);构建一个使用Builder接口的对象。它主要是用于创建一个复杂的对象。它主要有两个作用:1.隔离了客户与对象的生产过程,2.负责控制产品对象的生产过程。
案例
利用静态内部类创建属性较多的对象:
public class Person {
int id;
String name;
int age;
int weight;
int height;
int score;
private Person(){}
public static class PersonBuilder{
Person p = new Person();
public PersonBuilder basicInfo(int id,String name,int age){
p.id = id;
p.name = name;
p.age = age;
return this;
}
public PersonBuilder appearance(int weight,int height){
p.weight=weight;
p.height=height;
return this;
}
public PersonBuilder score(int score){
p.score=score;
return this;
}
public Person build(){
return p;
}
}
@Override
public String toString() {
return "Person{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
", weight=" + weight +
", height=" + height +
", score=" + score +
'}';
}
}
5、策略模式(中)
基本介绍
**意图:**定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换。
主要解决:在有多种算法相似的情况下,使用 if…else 所带来的复杂和难以维护。
**何时使用:**一个系统有许多许多类,而区分它们的只是他们直接的行为。
**如何解决:**将这些算法封装成一个一个的类,任意地替换。
**关键代码:**实现同一个接口。
应用实例: 1、诸葛亮的锦囊妙计,每一个锦囊就是一个策略。 2、旅行的出游方式,选择骑自行车、坐汽车,每一种旅行方式都是一个策略。 3、JAVA AWT 中的 LayoutManager。
优点: 1、算法可以自由切换。 2、避免使用多重条件判断。 3、扩展性良好。
缺点: 1、策略类会增多。 2、所有策略类都需要对外暴露。
使用场景: 1、如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为。 2、一个系统需要动态地在几种算法中选择一种。 3、如果一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重的条件选择语句来实现。
**注意事项:**如果一个系统的策略多于四个,就需要考虑使用混合模式,解决策略类膨胀的问题。
案例
MyComparator.java
public interface MyComparator<T> {
int compare(T o1,T o2);
}
public class Sorter2 {
//选择排序,每次选出最小的
public static <T> void sort(T[] arr,MyComparator<T> myComparator){
for (int i=0;i<arr.length-1;i++){
int minPos = i;
for (int j=i+1;j<arr.length;j++){
minPos = myComparator.compare(arr[j],arr[minPos])<0?j:minPos;
}
swap(arr,i,minPos);
}
}
static <T> void swap(T[] arr,int i,int j){
T temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
@Test
public void test02(){
Cat[] cats = {new Cat(3,3),new Cat(5,5),new Cat(1,1)};
Sorter2.sort(cats, new MyComparator<Cat>() {
@Override
public int compare(Cat o1, Cat o2) {
return o1.getHeight()-o2.getHeight();
}
});
System.out.println(Arrays.toString(cats));
}
可以灵活地在sort方法里传入不同的比较策略
6、外观模式(易)
基本介绍
**意图:**为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
**主要解决:**降低访问复杂系统的内部子系统时的复杂度,简化客户端与之的接口。
何时使用: 1、客户端不需要知道系统内部的复杂联系,整个系统只需提供一个"接待员"即可。 2、定义系统的入口。
**如何解决:**客户端不与系统耦合,外观类与系统耦合。
**关键代码:**在客户端和复杂系统之间再加一层,这一层将调用顺序、依赖关系等处理好。
应用实例: 1、去医院看病,可能要去挂号、门诊、划价、取药,让患者或患者家属觉得很复杂,如果有提供接待人员,只让接待人员来处理,就很方便。 2、JAVA 的三层开发模式。
优点: 1、减少系统相互依赖。 2、提高灵活性。 3、提高了安全性。
**缺点:**不符合开闭原则,如果要改东西很麻烦,继承重写都不合适。
使用场景: 1、为复杂的模块或子系统提供外界访问的模块。 2、子系统相对独立。 3、预防低水平人员带来的风险。
**注意事项:**在层次化结构中,可以使用外观模式定义系统中每一层的入口。
案例
将调用放在外观类中,封装起来,集体调用
public class HomeTheaterFacade {
//定义各个子系统对象
private TheaterLight theaterLight;
private Popcorn popcorn;
private Stereo stereo;
private Projector projector;
private Screen screen;
private DVDPlayer dVDPlayer;
//构造器
public HomeTheaterFacade() {
super();
this.theaterLight = TheaterLight.getInstance();
this.popcorn = Popcorn.getInstance();
this.stereo = Stereo.getInstance();
this.projector = Projector.getInstance();
this.screen = Screen.getInstance();
this.dVDPlayer = DVDPlayer.getInstanc();
}
//操作分成 4 步
public void ready() {
popcorn.on();
popcorn.pop();
screen.down();
projector.on();
stereo.on();
dVDPlayer.on();
theaterLight.dim();
}
public void play() {
dVDPlayer.play();
}
public void pause() {
dVDPlayer.pause();
}
public void end() {
popcorn.off();
theaterLight.bright();
screen.up();
projector.off();
stereo.off();
dVDPlayer.off();
}
}
7、中介者模式(中)
基本介绍
**意图:**用一个中介对象来封装一系列的对象交互,中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。
**主要解决:**对象与对象之间存在大量的关联关系,这样势必会导致系统的结构变得很复杂,同时若一个对象发生改变,我们也需要跟踪与之相关联的对象,同时做出相应的处理。
**何时使用:**多个类相互耦合,形成了网状结构。
**如何解决:**将上述网状结构分离为星型结构。
**关键代码:**对象 Colleague 之间的通信封装到一个类中单独处理。
应用实例: 1、中国加入 WTO 之前是各个国家相互贸易,结构复杂,现在是各个国家通过 WTO 来互相贸易。 2、机场调度系统。 3、MVC 框架,其中C(控制器)就是 M(模型)和 V(视图)的中介者。
优点: 1、降低了类的复杂度,将一对多转化成了一对一。 2、各个类之间的解耦。 3、符合迪米特原则。
**缺点:**中介者会庞大,变得复杂难以维护。
使用场景: 1、系统中对象之间存在比较复杂的引用关系,导致它们之间的依赖关系结构混乱而且难以复用该对象。 2、想通过一个中间类来封装多个类中的行为,而又不想生成太多的子类。
**注意事项:**不应当在职责混乱的时候使用。
案例
通过构造中介者类处理类与类之间复杂的逻辑关系,方便用户调用。
//具体的中介者类
public class ConcreteMediator extends Mediator {
//集合,放入所有的同事对象
private HashMap<String, Colleague> colleagueMap;
private HashMap<String, String> interMap;
public ConcreteMediator() {
colleagueMap = new HashMap<String, Colleague>();
interMap = new HashMap<String, String>();
}
@Override
public void Register(String colleagueName, Colleague colleague) {
colleagueMap.put(colleagueName, colleague);
if (colleague instanceof Alarm) {
interMap.put("Alarm", colleagueName);
} else if (colleague instanceof CoffeeMachine) {
interMap.put("CoffeeMachine", colleagueName);
} else if (colleague instanceof TV) {
interMap.put("TV", colleagueName);
} else if (colleague instanceof Curtains) {
interMap.put("Curtains", colleagueName);
}
}
//具体中介者的核心方法
//1. 根据得到消息,完成对应任务
//2. 中介者在这个方法,协调各个具体的同事对象,完成任务
@Override
public void GetMessage(int stateChange, String colleagueName) {
//处理闹钟发出的消息
if (colleagueMap.get(colleagueName) instanceof Alarm) {
if (stateChange == 0) {
((CoffeeMachine) (colleagueMap.get(interMap
.get("CoffeeMachine")))).StartCoffee();
((TV) (colleagueMap.get(interMap.get("TV")))).StartTv();
} else if (stateChange == 1) {
((TV) (colleagueMap.get(interMap.get("TV")))).StopTv();
}
} else if (colleagueMap.get(colleagueName) instanceof CoffeeMachine) {
((Curtains) (colleagueMap.get(interMap.get("Curtains"))))
.UpCurtains();
} else if (colleagueMap.get(colleagueName) instanceof TV) {//如果TV发现消息
} else if (colleagueMap.get(colleagueName) instanceof Curtains) {
//如果是以窗帘发出的消息,这里处理...
}
}
@Override
public void SendMessage() {
// TODO Auto-generated method stub
}
}
8、装饰模式(难)
基本介绍
**意图:**动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式相比生成子类更为灵活。
**主要解决:**一般的,我们为了扩展一个类经常使用继承方式实现,由于继承为类引入静态特征,并且随着扩展功能的增多,子类会很膨胀。
**何时使用:**在不想增加很多子类的情况下扩展类。
**如何解决:**将具体功能职责划分,同时继承装饰者模式。
关键代码: 1、Component 类充当抽象角色,不应该具体实现。 2、修饰类引用和继承 Component 类,具体扩展类重写父类方法。
应用实例: 1、孙悟空有 72 变,当他变成"庙宇"后,他的根本还是一只猴子,但是他又有了庙宇的功能。 2、不论一幅画有没有画框都可以挂在墙上,但是通常都是有画框的,并且实际上是画框被挂在墙上。在挂在墙上之前,画可以被蒙上玻璃,装到框子里;这时画、玻璃和画框形成了一个物体。
**优点:**装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。
**缺点:**多层装饰比较复杂。
使用场景: 1、扩展一个类的功能。 2、动态增加功能,动态撤销。
**注意事项:**可代替继承。
案例
给某些类加上”装备“。
装饰类的基类Decorator.java,需要继承和需要“装备”的类一样的接口。
Drink drink = new LongBlack();//咖啡
drink = new Chocolate(drink);//咖啡中需要有巧克力
public class Decorator implements Drink {
public String des; // 描述
private float price = 0.0f;
private Drink drink;
public Decorator(Drink drink) { //组合 这里就是为了把drink(被修饰)引进来!!
this.drink = drink;
}
public String getDes() {
// obj.getDes() 输出被装饰者的信息
return des + " " + getPrice() + " && " + drink.getDes();
}
public void setDes(String des) {
this.des = des;
}
public float getPrice() {
return price;
}
public void setPrice(float price) {
this.price = price;
}
@Override
public float cost() {
// getPrice 自己价格
return getPrice() + drink.cost();
}
}
9、责任链模式(难)
基本介绍
**意图:**避免请求发送者与接收者耦合在一起,让多个对象都有可能接收请求,将这些对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止。
**主要解决:**职责链上的处理者负责处理请求,客户只需要将请求发送到职责链上即可,无须关心请求的处理细节和请求的传递,所以职责链将请求的发送者和请求的处理者解耦了。
**何时使用:**在处理消息的时候以过滤很多道。
**如何解决:**拦截的类都实现统一接口。
**关键代码:**Handler 里面聚合它自己,在 HandlerRequest 里判断是否合适,如果没达到条件则向下传递,向谁传递之前 set 进去。
应用实例: 1、红楼梦中的"击鼓传花"。 2、JS 中的事件冒泡。 3、JAVA WEB 中 Apache Tomcat 对 Encoding 的处理,Struts2 的拦截器,jsp servlet 的 Filter。
优点: 1、降低耦合度。它将请求的发送者和接收者解耦。 2、简化了对象。使得对象不需要知道链的结构。 3、增强给对象指派职责的灵活性。通过改变链内的成员或者调动它们的次序,允许动态地新增或者删除责任。 4、增加新的请求处理类很方便。
缺点: 1、不能保证请求一定被接收。 2、系统性能将受到一定影响,而且在进行代码调试时不太方便,可能会造成循环调用。 3、可能不容易观察运行时的特征,有碍于除错。
使用场景: 1、有多个对象可以处理同一个请求,具体哪个对象处理该请求由运行时刻自动确定。 2、在不明确指定接收者的情况下,向多个对象中的一个提交一个请求。 3、可动态指定一组对象处理请求。
**注意事项:**在 JAVA WEB 中遇到很多应用。
案例
处理request
public class Main {
@Test
public void test(){
Msg msg = new Msg();
msg.setMsg(":)<script>.asd996,www.baidu.com");
// new HTMLFilter().doFilter(msg);
// new SensitiveFilter().doFilter(msg);
// List<Filter> filters = new ArrayList<>();
// filters.add(new HTMLFilter());
// filters.add(new SensitiveFilter());
// for (Filter f:filters){
// f.doFilter(msg);
// }
FilterChain fc = new FilterChain();
fc.add(new HTMLFilter()).add(new SensitiveFilter());
FilterChain fc2 = new FilterChain();
fc2.add(new FaceFilter()).add(new URLFilter());
fc.add(fc2);
fc.doFilter(msg);
System.out.println(msg);
}
class Msg{
String name;
String msg;
@Override
public String toString() {
return "Msg{" +
"name='" + name + '\'' +
", msg='" + msg + '\'' +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
interface Filter{
boolean doFilter(Msg m);
}
class HTMLFilter implements Filter{
@Override
public boolean doFilter(Msg m) {
String r = m.getMsg();
r = r.replace('<','[');
r = r.replace('>',']');
m.setMsg(r);
return true;
}
}
class SensitiveFilter implements Filter{
@Override
public boolean doFilter(Msg m) {
String r = m.getMsg();
r = r.replace("996","007");
m.setMsg(r);
return false;
}
}
class FaceFilter implements Filter{
@Override
public boolean doFilter(Msg m) {
String r = m.getMsg();
r = r.replace(":)","^V^");
m.setMsg(r);
return true;
}
}
class URLFilter implements Filter{
@Override
public boolean doFilter(Msg m) {
String r = m.getMsg();
r = r.replace("www.baidu.com","www.bilibili.com");
m.setMsg(r);
return true;
}
}
class FilterChain implements Filter{
List<Filter> filters = new ArrayList<>();
public FilterChain add(Filter f){
filters.add(f);
return this;
}
@Override
public boolean doFilter(Msg msg){
for (Filter f:filters){
if (!f.doFilter(msg))return false;
}
return true;
}
}
}
request,response,运用了程序自身的压栈
public class ServletMain {
@Test
public void test(){
Request request = new Request();
request.str = ":)<script>.asd996,www.baidu.com";
Response response = new Response();
response.str = "response=";
FilterChain fc = new FilterChain();
fc.add(new HTMLFilter()).add(new FaceFilter());
fc.doFilter(request,response,fc);
System.out.println(request.str);
System.out.println(response.str);
}
class Msg{
String name;
String msg;
@Override
public String toString() {
return "Msg{" +
"name='" + name + '\'' +
", msg='" + msg + '\'' +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
interface Filter{
boolean doFilter(Request request,Response response,FilterChain chain);
}
class Request{
String str;
}
class Response{
String str;
}
class HTMLFilter implements Filter{
@Override
public boolean doFilter(Request request,Response response,FilterChain chain) {
request.str = request.str.replace("996","007");
chain.doFilter(request,response,chain);
response.str+="--HTMLFilter";
return true;
}
}
class FaceFilter implements Filter {
@Override
public boolean doFilter(Request request,Response response,FilterChain chain) {
request.str = request.str.replace(":)","^V^");
chain.doFilter(request,response,chain);
response.str+="--FaceFilter";
return true;
}
}
class FilterChain implements Filter{
List<Filter> filters = new ArrayList<>();
private int index=0;
public FilterChain add(Filter f){
filters.add(f);
return this;
}
public boolean doFilter(Request request,Response response,FilterChain chain){
if(index==filters.size()){
return true;
}
Filter filter = filters.get(index);
index++;
filter.doFilter(request,response,chain);
return true;
}
}
}
10、观察者模式(难)
基本介绍
**意图:**定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
**主要解决:**一个对象状态改变给其他对象通知的问题,而且要考虑到易用和低耦合,保证高度的协作。
**何时使用:**一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知,进行广播通知。
**如何解决:**使用面向对象技术,可以将这种依赖关系弱化。
**关键代码:**在抽象类里有一个 ArrayList 存放观察者们。
应用实例: 1、拍卖的时候,拍卖师观察最高标价,然后通知给其他竞价者竞价。 2、西游记里面悟空请求菩萨降服红孩儿,菩萨洒了一地水招来一个老乌龟,这个乌龟就是观察者,他观察菩萨洒水这个动作。3、监听器Listener。(钩子函数)
优点: 1、观察者和被观察者是抽象耦合的。 2、建立一套触发机制。
缺点: 1、如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。 2、如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。 3、观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。
使用场景:
- 一个抽象模型有两个方面,其中一个方面依赖于另一个方面。将这些方面封装在独立的对象中使它们可以各自独立地改变和复用。
- 一个对象的改变将导致其他一个或多个对象也发生改变,而不知道具体有多少对象将发生改变,可以降低对象之间的耦合度。
- 一个对象必须通知其他对象,而并不知道这些对象是谁。
- 需要在系统中创建一个触发链,A对象的行为将影响B对象,B对象的行为将影响C对象……,可以使用观察者模式创建一种链式触发机制。
注意事项: 1、JAVA 中已经有了对观察者模式的支持类。 2、避免循环引用。 3、如果顺序执行,某一观察者错误会导致系统卡壳,一般采用异步方式。
案例
//被观察者(广播器)
public class Child {
boolean cry;
List<Observer> observers = new ArrayList<>();
public void wakeUp(){
cry = true;
//根据事件的实际情况处理
WakeUpEvent event = new WakeUpEvent(System.currentTimeMillis(),"bed",this);
for (Observer o:observers){
o.actionOnWakeUp(event);
}
}
}
//事件也能有相应的继承体系
//根据事件的实际情况处理
public class WakeUpEvent implements Event<Child> {
long timestamp;
String loc;
Child source;//事件源对象
public WakeUpEvent(long timestamp,String loc,Child source){
this.timestamp = timestamp;
this.loc = loc;
this.source = source;
}
@Override
public Child getSource() {
return source;
}
}
观察者(监听器、钩子函数)
public class Dad implements Observer {
public void feed(){
System.out.println("dad..feeding...");
}
@Override
public void actionOnWakeUp(WakeUpEvent event) {
feed();
}
}
11、组合模式(易)
基本介绍
**意图:**将对象组合成树形结构以表示"部分-整体"的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。
**主要解决:**它在我们树型结构的问题中,模糊了简单元素和复杂元素的概念,客户程序可以像处理简单元素一样来处理复杂元素,从而使得客户程序与复杂元素的内部结构解耦。
何时使用: 1、您想表示对象的部分-整体层次结构(树形结构)。 2、您希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象。
**如何解决:**树枝和叶子实现统一接口,树枝内部组合该接口。
**关键代码:**树枝内部组合该接口,并且含有内部属性 List,里面放 Component。
应用实例: 1、算术表达式包括操作数、操作符和另一个操作数,其中,另一个操作符也可以是操作数、操作符和另一个操作数。 2、在 JAVA AWT 和 SWING 中,对于 Button 和 Checkbox 是树叶,Container 是树枝。
优点: 1、高层模块调用简单。 2、节点自由增加。
**缺点:**在使用组合模式时,其叶子和树枝的声明都是实现类,而不是接口,违反了依赖倒置原则。
**使用场景:**部分、整体场景,如树形菜单,文件、文件夹的管理。
**注意事项:**定义时为具体类。
案例
组合模式就是专门处理树状结构的
public class Main {
abstract class Node{
abstract public void p();
}
class LeafNode extends Node{
String content;
public LeafNode(String content){
this.content = content;
}
@Override
public void p() {
System.out.println(content);
}
}
class BranchNode extends Node{
List<Node> nodes = new ArrayList<>();
String name;
public BranchNode(String name){
this.name = name;
}
@Override
public void p() {
System.out.println(name);
}
public void add(Node node){
nodes.add(node);
}
}
@Test
public void test(){
BranchNode root = new BranchNode("root");
BranchNode chapter1 = new BranchNode("chapter1");
BranchNode chapter2 = new BranchNode("chapter2");
Node c11 = new LeafNode("c11");
Node c12 = new LeafNode("c12");
BranchNode b21 = new BranchNode("section21");
Node c211 = new LeafNode("c211");
Node c212 = new LeafNode("c212");
root.add(chapter1);
root.add(chapter2);
chapter1.add(c11);
chapter1.add(c12);
chapter2.add(b21);
b21.add(c211);
b21.add(c212);
tree(root,0);
}
public void tree(Node root,int depth){
for (int i = 0;i<depth;++i) System.out.print("--");
root.p();
if (root instanceof BranchNode){
for (Node n:((BranchNode) root).nodes){
tree(n,depth+1);
}
}
}
}
12、享元模式(易)
基本介绍
**意图:**运用共享技术有效地支持大量细粒度的对象。
**主要解决:**在有大量对象时,有可能会造成内存溢出,我们把其中共同的部分抽象出来,如果有相同的业务请求,直接返回在内存中已有的对象,避免重新创建。
何时使用: 1、系统中有大量对象。 2、这些对象消耗大量内存。 3、这些对象的状态大部分可以外部化。 4、这些对象可以按照内蕴状态分为很多组,当把外蕴对象从对象中剔除出来时,每一组对象都可以用一个对象来代替。 5、系统不依赖于这些对象身份,这些对象是不可分辨的。
**如何解决:**用唯一标识码判断,如果在内存中有,则返回这个唯一标识码所标识的对象。
**关键代码:**用 HashMap 存储这些对象。
应用实例: 1、JAVA 中的 String,如果有则返回,如果没有则创建一个字符串保存在字符串缓存池里面。 2、数据库的数据池。
**优点:**大大减少对象的创建,降低系统的内存,使效率提高。
**缺点:**提高了系统的复杂度,需要分离出外部状态和内部状态,而且外部状态具有固有化的性质,不应该随着内部状态的变化而变化,否则会造成系统的混乱。
使用场景: 1、系统有大量相似对象。 2、需要缓冲池的场景。3、JVM常量池
注意事项: 1、注意划分外部状态和内部状态,否则可能会引起线程安全问题。 2、这些类必须有一个工厂对象加以控制。
案例
系统中有大量对象,避免重复创建,从对象池中获取对象。
public class Main {
@Test
public void test(){
ColorPool colorPool = new ColorPool();
for (int i = 0;i<10;i++){
Color color = colorPool.getColor();
if (i>=3)color.setLiving(false);
System.out.println(color.hashCode());
}
}
class ColorPool{
List<Color> colors = new ArrayList<>();
//静态代码域,在类的加载的时候调用一次,整个生命周期只会调用一次。
// static {
//
// }
//普通代码域,在类的每个对象创建的时候调用。
{
for(int i = 0;i<5;i++)colors.add(new Color());
}
public Color getColor(){
for (int i =0;i<colors.size();++i){
Color c = colors.get(i);
if (!c.living){
c.setLiving(true);
return c;
}
}
return new Color();
}
}
class Color{
private String name;
private boolean living;//true:正在使用 false:未使用
public String getName(String name){
return name;
}
public boolean isLiving() {
return living;
}
public void setLiving(boolean living) {
this.living = living;
}
}
}
结果:
463345942
195600860
1334729950
1347137144 <===从第4个开始用的都是ColorPool中的第4个位置的Color,所以hash值相同
1347137144
1347137144
1347137144
1347137144
1347137144
1347137144
13、代理模式(难)
基本介绍
**意图:**为其他对象提供一种代理以控制对这个对象的访问。
**主要解决:**在直接访问对象时带来的问题,比如说:要访问的对象在远程的机器上。在面向对象系统中,有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问),直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时加上一个对此对象的访问层。
**何时使用:**想在访问一个类时做一些控制。
**如何解决:**增加中间层。
**关键代码:**实现与被代理类组合。
应用实例: 1、Windows 里面的快捷方式。 2、猪八戒去找高翠兰结果是孙悟空变的,可以这样理解:把高翠兰的外貌抽象出来,高翠兰本人和孙悟空都实现了这个接口,猪八戒访问高翠兰的时候看不出来这个是孙悟空,所以说孙悟空是高翠兰代理类。 3、买火车票不一定在火车站买,也可以去代售点。 4、一张支票或银行存单是账户中资金的代理。支票在市场交易中用来代替现金,并提供对签发人账号上资金的控制。 5、spring aop。
优点: 1、职责清晰。 2、高扩展性。 3、智能化。
缺点: 1、由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。 2、实现代理模式需要额外的工作,有些代理模式的实现非常复杂。
**使用场景:**按职责来划分,通常有以下使用场景: 1、远程代理。 2、虚拟代理。 3、Copy-on-Write 代理。 4、保护(Protect or Access)代理。 5、Cache代理。 6、防火墙(Firewall)代理。 7、同步化(Synchronization)代理。 8、智能引用(Smart Reference)代理。
注意事项: 1、和适配器模式的区别:适配器模式主要改变所考虑对象的接口,而代理模式不能改变所代理类的接口。 2、和装饰器模式的区别:装饰器模式为了增强功能,而代理模式是为了加以控制。
案例
静态代理
public class MainV2 {
@Test
public void test(){
new TankLogProxy(new TankTimeProxy(new Tank())).move();
}
interface Movable{
void move();
}
class Tank implements Movable {
@Override
public void move() {
System.out.println("tank...");
}
}
class TankTimeProxy implements Movable {
Movable m;
public TankTimeProxy(Movable m){
this.m = m;
}
@Override
public void move() {
long start = System.currentTimeMillis();
m.move();
long end = System.currentTimeMillis();
System.out.println(end-start);
}
}
class TankLogProxy implements Movable {
Movable m;
public TankLogProxy(Movable m){
this.m = m;
}
@Override
public void move() {
System.out.println("start...");
m.move();
}
}
}
动态代理
//动态代理
//不知道要代理的对象类型,也不知道他所拥有的方法,所以要用动态代理
public class MainV3 {
interface Movable{
void move();
void test();
}
static class Tank implements Movable {
@Override
public void move() {
System.out.println("tank...");
}
@Override
public void test() {
System.out.println("test..");
}
}
public static void main(String[] args) {
Tank tank = new Tank();
//新版本
// System.getProperties().put("jdk.proxy.ProxyGenerator.saveGeneratedFiles","true");
//JDK1.8 必须要在main方法中
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
//Proxy用哪个类加载器去加载,不一定要tank的,随便谁的类加载器都行
ClassLoader classLoader = tank.getClass().getClassLoader();
//Proxy需要继承那些接口
Class[] classes = {Movable.class};
//反射:通过二进制字节码分析类的属性和方法
//创建动态代理对象,$Proxy0
Movable m = (Movable) Proxy.newProxyInstance(classLoader, classes, new InvocationHandler() {
/**
* @Author alex
* @Description
* @Date 20:02 2020/8/20
* @Param [proxy, method, args] 生成的代理对象即m,调用的方法,调用方法的参数
* @return java.lang.Object
**/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(method.getName()+" start...");
Object invoke = method.invoke(tank, args);
System.out.println(method.getName()+" end...");
return invoke;
}
});
//继承了Movable接口,move()方法里面调用了invoke()方法
m.move();
}
}
生成的动态代理类,即m,$Proxy0.class
final class $Proxy0 extends Proxy implements Movable {
private static Method m1;
private static Method m4;
private static Method m3;
private static Method m2;
private static Method m0;
public $Proxy0(InvocationHandler var1) throws {
super(var1);
}
public final boolean equals(Object var1) throws {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
//Movable接口里的方法
public final void move() throws {
try {
super.h.invoke(this, m4, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
//Movable接口里的方法
public final void test() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final int hashCode() throws {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m4 = Class.forName("设计模式.代理模式.MainV3$Movable").getMethod("move");
m3 = Class.forName("设计模式.代理模式.MainV3$Movable").getMethod("test");
m2 = Class.forName("java.lang.Object").getMethod("toString");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
还可以利用cglib实现动态代理,Spring-AOP用的就是cglib实现的AOP
14、迭代器模式(易)
基本介绍
**意图:**提供一种方法顺序访问一个聚合对象中各个元素, 而又无须暴露该对象的内部表示。
主要解决:不同的方式来遍历整个整合对象。
**何时使用:**遍历一个聚合对象。
**如何解决:**把在元素之间游走的责任交给迭代器,而不是聚合对象。
**关键代码:**定义接口:hasNext, next。
**应用实例:**JAVA 中的 iterator。
优点: 1、它支持以不同的方式遍历一个聚合对象。 2、迭代器简化了聚合类。 3、在同一个聚合上可以有多个遍历。 4、在迭代器模式中,增加新的聚合类和迭代器类都很方便,无须修改原有代码。
**缺点:**由于迭代器模式将存储数据和遍历数据的职责分离,增加新的聚合类需要对应增加新的迭代器类,类的个数成对增加,这在一定程度上增加了系统的复杂性。
使用场景: 1、访问一个聚合对象的内容而无须暴露它的内部表示。 2、需要为聚合对象提供多种遍历方式。 3、为遍历不同的聚合结构提供一个统一的接口。
**注意事项:**迭代器模式就是分离了集合对象的遍历行为,抽象出一个迭代器类来负责,这样既可以做到不暴露集合的内部结构,又可让外部代码透明地访问集合内部的数据。
案例
class MyArrayList implements MyCollection {
Object[] objs = new Object[10];
private int size = 0;
public int getSize(){
return size;
}
@Override
public MyIterator getIterator() {
return new MyArrayListIterator();
}
//内部迭代器类
private class MyArrayListIterator implements MyIterator{
private int currentIndex = 0;
@Override
public boolean hasNext() {
return !(currentIndex>=size);
}
@Override
public Object next() {
return objs[currentIndex++];
}
}
public void add(Object obj){
if (obj==null)return;
if (size==objs.length){
Object[] newObjs = new Object[objs.length<<1];
System.arraycopy(objs,0,newObjs,0,objs.length);
objs = newObjs;
}
objs[size] = obj;
size++;
}
}
15、访问者模式(中)
基本介绍
**意图:**主要将数据结构与数据操作分离。
**主要解决:**稳定的数据结构和易变的操作耦合问题。
**何时使用:**需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而需要避免让这些操作"污染"这些对象的类,使用访问者模式将这些封装到类中。
**如何解决:**在被访问的类里面加一个对外提供接待访问者的接口。
**关键代码:**在数据基础类里面有一个方法接受访问者,将自身引用传入访问者。
**应用实例:**您在朋友家做客,您是访问者,朋友接受您的访问,您通过朋友的描述,然后对朋友的描述做出一个判断,这就是访问者模式。
优点: 1、符合单一职责原则。 2、优秀的扩展性。 3、灵活性。
缺点: 1、具体元素对访问者公布细节,违反了迪米特原则。 2、具体元素变更比较困难。 3、违反了依赖倒置原则,依赖了具体类,没有依赖抽象。
使用场景: 1、对象结构中对象对应的类很少改变,但经常需要在此对象结构上定义新的操作。 2、需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而需要避免让这些操作"污染"这些对象的类,也不希望在增加新操作时修改这些类。3、编译器抽象语法树AST
**注意事项:**访问者可以对功能进行统一,可以做报表、UI、拦截器与过滤器。
案例
Computer的部件一直增加的话,访问者模式不适合。访问者模式适用于Computer结构固定
public class Main {
interface ComputerPart{
void accept(Visitor v);
double getPrice();
}
class CPU implements ComputerPart{
@Override
public void accept(Visitor v) {
v.visitCPU(this);
}
@Override
public double getPrice() {
return 500;
}
}
class Memory implements ComputerPart{
@Override
public void accept(Visitor v) {
v.visitMemory(this);
}
@Override
public double getPrice() {
return 300;
}
}
class Board implements ComputerPart{
@Override
public void accept(Visitor v) {
v.visitBoard(this);
}
@Override
public double getPrice() {
return 200;
}
}
interface Visitor{
void visitCPU(CPU c);
void visitMemory(Memory M);
void visitBoard(Board B);
}
class PersonVisitor implements Visitor{
double totalPrice = 0.0;
@Override
public void visitCPU(CPU c) {
totalPrice+=c.getPrice()*0.9;
}
@Override
public void visitMemory(Memory m) {
totalPrice+=m.getPrice()*0.85;
}
@Override
public void visitBoard(Board b) {
totalPrice+=b.getPrice()*0.95;
}
}
class CorpVisitor implements Visitor{
double totalPrice = 0.0;
@Override
public void visitCPU(CPU c) {
totalPrice+=c.getPrice()*0.91;
}
@Override
public void visitMemory(Memory m) {
totalPrice+=m.getPrice()*0.851;
}
@Override
public void visitBoard(Board b) {
totalPrice+=b.getPrice()*0.915;
}
}
class Computer{
ComputerPart cpu = new CPU();
ComputerPart memory = new Memory();
ComputerPart board = new Board();
public void accept(Visitor v){
this.cpu.accept(v);
this.memory.accept(v);
this.board.accept(v);
}
}
}
16、适配器模式(Wrapper)(易)
基本介绍
**意图:**将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
**主要解决:**主要解决在软件系统中,常常要将一些"现存的对象"放到新的环境中,而新环境要求的接口是现对象不能满足的。
何时使用: 1、系统需要使用现有的类,而此类的接口不符合系统的需要。 2、想要建立一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作,这些源类不一定有一致的接口。 3、通过接口转换,将一个类插入另一个类系中。(比如老虎和飞禽,现在多了一个飞虎,在不增加实体的需求下,增加一个适配器,在里面包容一个虎对象,实现飞的接口。)
**如何解决:**继承或依赖(推荐)。
**关键代码:**适配器继承或依赖已有的对象,实现想要的目标接口。
应用实例: 1、美国电器 110V,中国 220V,就要有一个适配器将 110V 转化为 220V。 2、JAVA JDK 1.1 提供了 Enumeration 接口,而在 1.2 中提供了 Iterator 接口,想要使用 1.2 的 JDK,则要将以前系统的 Enumeration 接口转化为 Iterator 接口,这时就需要适配器模式。 3、在 LINUX 上运行 WINDOWS 程序。 4、JAVA 中的 jdbc。
优点: 1、可以让任何两个没有关联的类一起运行。 2、提高了类的复用。 3、增加了类的透明度。 4、灵活性好。
缺点: 1、过多地使用适配器,会让系统非常零乱,不易整体进行把握。比如,明明看到调用的是 A 接口,其实内部被适配成了 B 接口的实现,一个系统如果太多出现这种情况,无异于一场灾难。因此如果不是很有必要,可以不使用适配器,而是直接对系统进行重构。 2.由于 JAVA 至多继承一个类,所以至多只能适配一个适配者类,而且目标类必须是抽象类。
**使用场景:**有动机地修改一个正常运行的系统的接口,这时应该考虑使用适配器模式。
**注意事项:**适配器不是在详细设计时添加的,而是解决正在服役的项目的问题。
案例
public interface MediaPlayer {
void play(String audioType, String fileName);
}
public interface AdvancedMediaPlayer {
void playVlc(String fileName);
void playMp4(String fileName);
}
public class VlcPlayer implements AdvancedMediaPlayer{
@Override
public void playVlc(String fileName) {
System.out.println("Playing vlc file. Name: "+ fileName);
}
@Override
public void playMp4(String fileName) {
//什么也不做
}
}
public class Mp4Player implements AdvancedMediaPlayer{
@Override
public void playVlc(String fileName) {
//什么也不做
}
@Override
public void playMp4(String fileName) {
System.out.println("Playing mp4 file. Name: "+ fileName);
}
}
public class AudioPlayer implements MediaPlayer {
MediaAdapter mediaAdapter;
@Override
public void play(String audioType, String fileName) {
//播放 mp3 音乐文件的内置支持
if(audioType.equalsIgnoreCase("mp3")){
System.out.println("Playing mp3 file. Name: "+ fileName);
}
//mediaAdapter 提供了播放其他文件格式的支持
else if(audioType.equalsIgnoreCase("vlc")
|| audioType.equalsIgnoreCase("mp4")){
mediaAdapter = new MediaAdapter(audioType);
mediaAdapter.play(audioType, fileName);
}
else{
System.out.println("Invalid media. "+
audioType + " format not supported");
}
}
}
public class MediaAdapter implements MediaPlayer {
AdvancedMediaPlayer advancedMusicPlayer;
public MediaAdapter(String audioType){
if(audioType.equalsIgnoreCase("vlc") ){
advancedMusicPlayer = new VlcPlayer();
} else if (audioType.equalsIgnoreCase("mp4")){
advancedMusicPlayer = new Mp4Player();
}
}
@Override
public void play(String audioType, String fileName) {
if(audioType.equalsIgnoreCase("vlc")){
advancedMusicPlayer.playVlc(fileName);
}else if(audioType.equalsIgnoreCase("mp4")){
advancedMusicPlayer.playMp4(fileName);
}
}
}
@Test
public void test(){
AudioPlayer audioPlayer = new AudioPlayer();
audioPlayer.play("mp3", "beyond the horizon.mp3");
audioPlayer.play("mp4", "alone.mp4");
audioPlayer.play("vlc", "far far away.vlc");
audioPlayer.play("avi", "mind me.avi");
}
17、桥接模式(易)
基本介绍
**意图:**将抽象部分与实现部分分离,使它们都可以独立的变化。
**主要解决:**在有多种可能会变化的情况下,用继承会造成类爆炸问题,扩展起来不灵活。
**何时使用:**实现系统可能有多个角度分类,每一种角度都可能变化。
**如何解决:**把这种多角度分类分离出来,让它们独立变化,减少它们之间耦合。
**关键代码:**抽象类依赖实现类。
应用实例: 1、猪八戒从天蓬元帅转世投胎到猪,转世投胎的机制将尘世划分为两个等级,即:灵魂和肉体,前者相当于抽象化,后者相当于实现化。生灵通过功能的委派,调用肉体对象的功能,使得生灵可以动态地选择。 2、墙上的开关,可以看到的开关是抽象的,不用管里面具体怎么实现的。
优点: 1、抽象和实现的分离。 2、优秀的扩展能力。 3、实现细节对客户透明。
**缺点:**桥接模式的引入会增加系统的理解与设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计与编程。
使用场景: 1、如果一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性,避免在两个层次之间建立静态的继承联系,通过桥接模式可以使它们在抽象层建立一个关联关系。 2、对于那些不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统,桥接模式尤为适用。 3、一个类存在两个独立变化的维度,且这两个维度都需要进行扩展。
**注意事项:**对于两个独立变化的维度,使用桥接模式再适合不过了。
案例
用聚合代替继承
abstract class Gift{
GiftImpl impl;
}
class WarmGift extends Gift{//物品的类型
public WarmGift(GiftImpl impl){
this.impl = impl;
}
}
class WildGift extends Gift{
public WildGift(GiftImpl impl){
this.impl = impl;
}
}
class GiftImpl extends Gift{}//具体的物品
class Book extends GiftImpl{}
class Flower extends GiftImpl{}
class GG{
public void chase(MM mm){
Gift g = new WarmGift(new Flower());
give(mm,g);
}
public void give(MM mm,Gift g){
System.out.println(g+"gived!");
}
}
class MM{}
18、命令模式(易)
基本介绍
**意图:**将一个请求封装成一个对象,从而使您可以用不同的请求对客户进行参数化。
**主要解决:**在软件系统中,行为请求者与行为实现者通常是一种紧耦合的关系,但某些场合,比如需要对行为进行记录、撤销或重做、事务等处理时,这种无法抵御变化的紧耦合的设计就不太合适。
**何时使用:**在某些场合,比如要对行为进行"记录、撤销/重做、事务"等处理,这种无法抵御变化的紧耦合是不合适的。在这种情况下,如何将"行为请求者"与"行为实现者"解耦?将一组行为抽象为对象,可以实现二者之间的松耦合。
**如何解决:**通过调用者调用接受者执行命令,顺序:调用者→命令→接受者。
**关键代码:**定义三个角色:1、received 真正的命令执行对象 2、Command 3、invoker 使用命令对象的入口
**应用实例:**struts 1 中的 action 核心控制器 ActionServlet 只有一个,相当于 Invoker,而模型层的类会随着不同的应用有不同的模型类,相当于具体的 Command。
优点: 1、降低了系统耦合度。 2、新的命令可以很容易添加到系统中去。
**缺点:**使用命令模式可能会导致某些系统有过多的具体命令类。
**使用场景:**认为是命令的地方都可以使用命令模式,比如: 1、GUI 中每一个按钮都是一条命令。 2、模拟 CMD。
注意事项:系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作,也可以考虑使用命令模式,见命令模式的扩展。
案例
public class Main {
interface Command{
void execute();//run
void undo();
}
class InsertCommand implements Command{
Content c;
String strToInsert = "www.baidu.com";
public InsertCommand(Content c){
this.c = c;
}
@Override
public void execute() {
c.msg = c.msg+strToInsert;
}
@Override
public void undo() {
c.msg = c.msg.substring(0,c.msg.length()-strToInsert.length());
}
}
class CopyCommand implements Command{
Content c;
public CopyCommand(Content c){
this.c = c;
}
@Override
public void execute() {
c.msg = c.msg+c.msg;
}
@Override
public void undo() {
c.msg = c.msg.substring(0,c.msg.length()/2);
}
}
class DeleteCommand implements Command{
Content c;
String deleted;
public DeleteCommand(Content c){
this.c = c;
}
@Override
public void execute() {
deleted = c.msg.substring(0,5);
c.msg = c.msg.substring(5,c.msg.length());
}
@Override
public void undo() {
c.msg = deleted+c.msg;
}
}
class Content{
String msg = "hello world";
}
}
责任链模式可以实现多次Command
19、备忘录模式
基本介绍
**意图:**在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。
**主要解决:**所谓备忘录模式就是在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样可以在以后将对象恢复到原先保存的状态。
**何时使用:**很多时候我们总是需要记录一个对象的内部状态,这样做的目的就是为了允许用户取消不确定或者错误的操作,能够恢复到他原先的状态,使得他有"后悔药"可吃。
**如何解决:**通过一个备忘录类专门存储对象状态。
**关键代码:**客户不与备忘录类耦合,与备忘录管理类耦合。
应用实例: 1、后悔药。 2、打游戏时的存档。 3、Windows 里的 ctri + z。 4、IE 中的后退。 4、数据库的事务管理。
优点: 1、给用户提供了一种可以恢复状态的机制,可以使用户能够比较方便地回到某个历史的状态。 2、实现了信息的封装,使得用户不需要关心状态的保存细节。
**缺点:**消耗资源。如果类的成员变量过多,势必会占用比较大的资源,而且每一次保存都会消耗一定的内存。
使用场景: 1、需要保存/恢复数据的相关状态场景。 2、提供一个可回滚的操作。
注意事项: 1、为了符合迪米特原则,还要增加一个管理备忘录的类。 2、为了节约内存,可使用原型模式+备忘录模式。
案例
20、模板方法(钩子函数)(易)
基本介绍
**意图:**定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
**主要解决:**一些方法通用,却在每一个子类都重新写了这一方法。
**何时使用:**有一些通用的方法。
**如何解决:**将这些通用算法抽象出来。
**关键代码:**在抽象类实现,其他步骤在子类实现。
应用实例: 1、在造房子的时候,地基、走线、水管都一样,只有在建筑的后期才有加壁橱加栅栏等差异。 2、西游记里面菩萨定好的 81 难,这就是一个顶层的逻辑骨架。 3、spring 中对 Hibernate 的支持,将一些已经定好的方法封装起来,比如开启事务、获取 Session、关闭 Session 等,程序员不重复写那些已经规范好的代码,直接丢一个实体就可以保存。4、Spring的几乎所有外接扩展BeanPostProcessor、BeanDefinitionRegistryPostProcessor、BeanFactoryPostProcessor
优点: 1、封装不变部分,扩展可变部分。 2、提取公共代码,便于维护。 3、行为由父类控制,子类实现。
**缺点:**每一个不同的实现都需要一个子类来实现,导致类的个数增加,使得系统更加庞大。
使用场景: 1、有多个子类共有的方法,且逻辑相同。 2、重要的、复杂的方法,可以考虑作为模板方法。
**注意事项:**为防止恶意操作,一般模板方法都加上 final 关键词。
案例
public class Main {
abstract class F{
void m(){
A操作;
op1();
B操作;
op2();
C操作;
}
abstract void op1();
abstract void op2();
}
class C1 extends F{
@Override
void op1() {
System.out.println("op1...");
}
@Override
void op2() {
System.out.println("op2...");
}
}
@Test
public void test(){
F f = new C1();
f.m();
}
}
调用固有的m()方法,子类实现op1(),op2()相应的功能
21、状态模式(易)
基本介绍
**意图:**允许对象在内部状态发生改变时改变它的行为,对象看起来好像修改了它的类。
**主要解决:**对象的行为依赖于它的状态(属性),并且可以根据它的状态改变而改变它的相关行为。
**何时使用:**代码中包含大量与对象状态有关的条件语句。
**如何解决:**将各种具体的状态类抽象出来。
**关键代码:**通常命令模式的接口中只有一个方法。而状态模式的接口中有一个或者多个方法。而且,状态模式的实现类的方法,一般返回值,或者是改变实例变量的值。也就是说,状态模式一般和对象的状态有关。实现类的方法有不同的功能,覆盖接口中的方法。状态模式和命令模式一样,也可以用于消除 if…else 等条件选择语句。通过聚合状态接口实现。
应用实例: 1、打篮球的时候运动员可以有正常状态、不正常状态和超常状态。 2、曾侯乙编钟中,‘钟是抽象接口’,'钟A’等是具体状态,'曾侯乙编钟’是具体环境(Context)。
优点: 1、封装了转换规则。 2、枚举可能的状态,在枚举状态之前需要确定状态种类。 3、将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为。 4、允许状态转换逻辑与状态对象合成一体,而不是某一个巨大的条件语句块。 5、可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数。
缺点: 1、状态模式的使用必然会增加系统类和对象的个数。 2、状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱。 3、状态模式对"开闭原则"的支持并不太好,对于可以切换状态的状态模式,增加新的状态类需要修改那些负责状态转换的源代码,否则无法切换到新增状态,而且修改某个状态类的行为也需修改对应类的源代码。
使用场景: 1、行为随状态改变而改变的场景。 2、条件、分支语句的代替者。
**注意事项:**在行为受状态约束的时候使用状态模式,而且状态不超过 5 个。
根据状态决定行为
案例
interface MMState{
void smile();
void cry();
void say();
}
class MM{
String name;
MMState state;
public void smile() {
state.smile();
}
public void cry() {
state.cry();
}
public void say() {
state.say();
}
}
class HappyState implements MMState{
@Override
public void smile() {
System.out.println("happy..smile");
}
@Override
public void cry() {
System.out.println("happy...cry");
}
@Override
public void say() {
System.out.println("happy...say");
}
}
class SadState implements MMState{
@Override
public void smile() {
System.out.println("sad..smile");
}
@Override
public void cry() {
System.out.println("sad...cry");
}
@Override
public void say() {
System.out.println("sad...say");
}
}
22、解释器模式
基本介绍
**意图:**给定一个语言,定义它的文法表示,并定义一个解释器,这个解释器使用该标识来解释语言中的句子。
**主要解决:**对于一些固定文法构建一个解释句子的解释器。
**何时使用:**如果一种特定类型的问题发生的频率足够高,那么可能就值得将该问题的各个实例表述为一个简单语言中的句子。这样就可以构建一个解释器,该解释器通过解释这些句子来解决该问题。
**如何解决:**构建语法树,定义终结符与非终结符。
**关键代码:**构建环境类,包含解释器之外的一些全局信息,一般是 HashMap。
**应用实例:**编译器、运算表达式计算。
优点: 1、可扩展性比较好,灵活。 2、增加了新的解释表达式的方式。 3、易于实现简单文法。
缺点: 1、可利用场景比较少。 2、对于复杂的文法比较难维护。 3、解释器模式会引起类膨胀。 4、解释器模式采用递归调用方法。
使用场景: 1、可以将一个需要解释执行的语言中的句子表示为一个抽象语法树。 2、一些重复出现的问题可以用一种简单的语言来进行表达。 3、一个简单语法需要解释的场景。
**注意事项:**可利用场景比较少,JAVA 中如果碰到可以用 expression4J 代替。