设计模式介绍
简单的介绍
- 设计模式是程序员在面对同类软件工程设计问题所总结出来的有用的经验,模式不是代码,而是某类问题的通用解决方案,设计模式(Design pattern)代表了最佳的实践。
- 设计模式的本质提高 软件的维护性,通用性和扩展性,并降低软件的复杂度。
- 设计模式并不局限于某种语言,java,php,c++ 都有设计模式。
设计模式的类型
-
创建型:单例模式、抽象工厂模式、原型模式、建造者模式、工厂模式
-
结构型:适配器模式、桥接模式、装饰模式、组合模式、外观模式、享
元模式、代理模式
-
行为型:模版方法模式、命令模式、访问者模式、迭代器模式、观察者
模式、中介者模式、备忘录模式、解释器模式(Interpreter模式)、状态模式、策略模式、职责链模式(责任链模式)。
单例模式
定义
- 所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法(静态方法)。
- 单例模式有八种实现方式:(1) 饿汉式(静态常量)(2) 饿汉式(静态代码块)(3) 懒汉式(线程不安全)(4) 懒汉式(线程安全,同步方法)(5) 懒汉式(线程安全,同步代码块)(6) 双重检查(7) 静态内部类(8) 枚举
饿汉式(静态常量)
步骤
1.构造器私有化 (防止new)
2.类的内部创建对象
3.向外暴露一个静态的公共方法:getInstance
4.代码实现
代码部分
public class SingletonTest01 {
public static void main(String[] args) {
Singleton instance = Singleton.getInstance();
}
}
//饿汉式(静态变量)
class Singleton {
//1. 构造器私有化, 外部能new
private Singleton() {}
//2.本类内部创建对象实例
private final static Singleton instance = new Singleton();
//3. 提供一个公有的静态方法,返回实例对象
public static Singleton getInstance() {return instance;}
}
优缺点分析
1.优点:这种写法比较简单,就是在类装载的时候就完成实例化。避免了线程同步问题。
2.缺点:在类装载的时候就完成实例化,没有达到Lazy Loading的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费。
3.结论:这种单例模式可用,可能造成内存浪费。
饿汉式(静态代码块)
与静态变量的方式类似,也是在类实例化的过程放在了静态代码块中。
class Singleton {
//1. 构造器私有化, 外部能new
private Singleton() {}
//2.本类内部创建对象实例
private static Singleton instance;
static { // 在静态代码块中,创建单例对象
instance = new Singleton();
}
//3. 提供一个公有的静态方法,返回实例对象
public static Singleton getInstance() {return instance;}
}
有关静态代码块的说明:
- 静态代码块随着类的加载而执行,而且只执行一次。
- 静态代码块:执行优先级高于非静态的初始化块,它会在类初始化的时候执行一次,执行完成便销毁,它仅能初始化类变量,即static修饰的数据成员。
懒汉式(线程不安全)
注:public static修饰的类的成员方法可以直接通过类名.方法名调用,类方法可在不实例化对象的前提下【直接调用】。
代码
class Singleton {
private static Singleton instance;
private Singleton() {}
//提供一个静态的公有方法,当使用到该方法时,才去创建 instance
//即懒汉式
public static Singleton getInstance() {
if(instance == null) {
instance = new Singleton();
}
return instance;
}
优缺点
1.优点:起到了Lazy Loading的效果,但是只能在单线程下使用。
2.缺点:可能会导致多个实例出现的错误情况:如果在多线程下,一个线程进入了if (singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。所以在多线程环境下不可使用这种方式
结论
在实际开发中,不要使用这种方式,因为这种方式可能会产生多个实例,违背了单例模式。
懒汉式(线程安全,同步方法)
在线程不安全的基础上添加synchronized关键字来控制多线程不安全问题。
代码部分
// 懒汉式(线程安全,同步方法)
class Singleton {
private static Singleton instance;
private Singleton() {}
//提供一个静态的公有方法,加入同步处理的代码,解决线程安全问题
//即懒汉式
public static synchronized Singleton getInstance() {
if(instance == null) {instance = new Singleton();}
return instance;
}
}
优缺点
1.优点:解决了线程不安全问题,并且实现了lazy loading
2.缺点:效率太低了,每个线程在想获得类的实例时候,执行getInstance()方法都要进行同步。而其实这个方法只执行一次实例化代码就够了,后面的想获得该类实例,直接return就行了。方法进行同步效率太低。
结论
在实际开发中,不推荐使用这种方式
懒汉式(线程安全,同步代码块)
该模式本质是在上一种模式上进行了改进,因为前面同步方法效率太低,
所以改为同步产生实例化的的代码块
代码部分
// 懒汉式(线程安全,同步代码块)
class Singleton {
private static Singleton instance;
private Singleton() {}
//提供一个静态的公有方法,加入同步处理的代码,解决线程安全问题
//即懒汉式
public static Singleton getInstance() {
if(instance == null) {
synchronized(Singleton.class){
instance = new Singleton();
}
}
return instance;
}
}
结论
这种方式无法实现线程同步的作用,跟第三种实现方式遇到的情形是一致的,假如一个线程进入了if (singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。
在实际开发中,不能使用这种方式。
双重检查
推荐使用,该方式既可以解决懒加载又可以解决线程安全和效率问题。
代码
class Singleton {
private static volatile Singleton instance;
private Singleton() {}
//提供一个静态的公有方法,加入双重检查代码,解决线程安全问题, 同时解决懒加载问题
//同时保证了效率, 推荐使用
public static Singleton getInstance() {
if(instance == null) {
synchronized (Singleton.class) {
if(instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
使用 volatile 修饰共享变量后,每个线程要操作变量时会从主内存中将变量拷贝到本地内存作为副本,当线程操作变量副本并写回主内存后,会通过 **CPU 总线嗅探机制**告知其他线程该变量副本已经失效,需要重新从主内存中读取。
**volatile 保证了不同线程对共享变量操作的可见性**,也就是说一个线程修改了 volatile 修饰的变量,当修改后的变量写回主内存时,其他线程能立即看到最新值。还有一个作用就是防止指令重排序:[单例模式之懒加载模式_Android_la的博客-CSDN博客_单例模式懒加载](https://blog.csdn.net/qq_40634846/article/details/114694089)
总结
1.Double-Check概念是多线程开发中常使用到的,如代码中所示,我们进行了两次if (singleton == null)检查,这样就可以保证线程安全了。
2.这样,实例化代码只用执行一次,后面再次访问时,判断if (singleton == null),直接return实例化对象,也避免的反复进行方法同步。
3.线程安全;延迟加载;效率较高
4.结论:在实际开发中,推荐使用这种单例设计模式。
静态内部类
推荐使用
代码
// 静态内部类完成, 推荐使用
class Singleton {
private static volatile Singleton instance;
//构造器私有化
private Singleton() {}
//写一个静态内部类,该类中有一个静态属性 Singleton
private static class SingletonInstance {
private static final Singleton INSTANCE = new Singleton();
}
//提供一个静态的公有方法,直接返回SingletonInstance.INSTANCE
public static synchronized Singleton getInstance() {return SingletonInstance.INSTANCE;}
}
静态内部类在类加载时不会自动调用,只有在类的静态公有方法被调用时才会调用到该静态内部类,且只加载一次,实现了懒加载。
总结
1.这种方式采用了类装载的机制来保证初始化实例时只有一个线程。
2.静态内部类方式在Singleton类被装载时并不会立即实例化,而是在需要实例化时,调用getInstance方法,才会装载SingletonInstance类,从而完成Singleton的实例化。
3.类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。
4.优点:避免了线程不安全,利用静态内部类特点实现延迟加载,效率高
5.结论:推荐使用
枚举
用枚举来实现单例。
注意枚举本身就是一种特殊的类。
代码
public class SingletonTest08 {
public static void main(String[] args) {
Singleton instance = Singleton.INSTANCE;
instance.sayOK();
}
}
//使用枚举,可以实现单例, 推荐
enum Singleton {
INSTANCE; //属性
public void sayOK() {
System.out.println("ok~");
}
}
为什么枚举就安全?并且懒加载?
参考为什么我墙裂建议大家使用枚举来实现单例。 - 哔哩哔哩 (bilibili.com)和枚举类型解决单例模式优点——懒加载+线程安全+解决反射破坏+解决反序列化破坏 - 代码先锋网 (codeleading.com)
先解释第一个问题,**为什么安全**?
定义枚举时使用enum和class一样,是Java中的一个关键字。就像class对应用一个Class类一样,enum也对应有一个Enum类。 通过将定义好的枚举反编译,我们就能发现,其实枚举在经过javac的编译之后,会被**转换**成形如public final class T extends Enum的定义。 而且,枚举中的各个枚举项同时通过static来定义的。如:
public enum T {
SPRING,SUMMER,AUTUMN,WINTER;
}
反编译后代码为:
public final class T extends Enum
{
//省略部分内容
public static final T SPRING;
public static final T SUMMER;
public static final T AUTUMN;
public static final T WINTER;
private static final T ENUM$VALUES[];
static
{
SPRING = new T("SPRING", 0);
SUMMER = new T("SUMMER", 1);
AUTUMN = new T("AUTUMN", 2);
WINTER = new T("WINTER", 3);
ENUM$VALUES = (new T[] {
SPRING, SUMMER, AUTUMN, WINTER
});
}
}
了解JVM的类加载机制的朋友应该对这部分比较清楚。**static类型的属性会在类被加载之后被初始化**,Java的ClassLoader机制中,<u>当一个Java类第一次被真正使用到的时候</u>**静态资源被初始化**、**Java类的加载**和**初始化过程**都是<u>线程安全的</u>(因为虚拟机在加载枚举的类的时候,会使用ClassLoader的loadClass方法,而这个方法使用同步代码块保证了线程安全)。所以,创建一个enum类型是线程安全的。
也就是说,我们定义的一个枚举,在第一次被真正用到的时候,**会被虚拟机加载并初始化**,而这个初始化过程是线程安全的。而我们知道,解决单例的并发问题,主要解决的就是初始化过程中的线程安全问题。
那么可以**解决反序列化问题吗**?可以。
用双重校验锁实现的单例其实是存在一定问题的,就是这种单例有可能被序列化锁破坏。但是枚举类在序列化的时候Java仅仅是将**枚举对象的name属性**输出到结果中,反序列化的时候则是通过java.lang.Enum的`valueOf`方法来根据名字**查找枚举对象**。同时,编译器是**不允许任何对这种序列化机制的定制的**,因此禁用了writeObject、readObject、readObjectNoData、writeReplace和readResolve等方法。
普通的Java类的反序列化过程中,会**通过反射调用类的<u>默认构造函数</u>来初始化对象**。所以,即使单例中构造函数是私有的,也会被反射给破坏掉。由于反序列化后的对象是重新new出来的,所以这就破坏了单例。但是,枚举的反序列化并不是通过反射实现的。所以,也就不会发生由于反序列化导致的单例破坏问题。
反射不能创建枚举类对象的原因在于`newInstance`方法中的下面的语句:
if ((clazz.getModifiers() & Modifier.ENUM) != 0)
throw new IllegalArgumentException("Cannot reflectively create enum objects")
如果当前要创建实例的类是枚举类,则抛出该异常。
如何实现懒加载?
其实可以看到,静态类反编译后,所有对实例初始化的行为都是在静态代码块中的,而静态代码块在这个类**第一次被调用**或**实例化的时候**就会被执行。静态代码块**只会执行一次**,一般会用来初始化一些值,并且在所有对象中全局共享。
总结
1.这借助JDK1.5中添加的枚举来实现单例模式。不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。
2.这种方式是Effective Java作者Josh Bloch 提倡的方式
3.结论:推荐使用。
单例模式在JDK中的应用
在JDK中,java.lang.Runtime就是经典的单例模式(饿汉式),因为runtime在java使用过程中一定会用到,所以不会存在内存浪费的现象。
注意事项和细节说明
1 单例模式保证了系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能。
2.当想实例化一个单例类的时候,必须要记住使用相应的获取对象的方法,而不是使用new
3.单例模式使用的场景:需要频繁的进行创建和销毁的对象、创建对象时耗时过多或耗费资源过多(即:重量级对象),但又经常用到的对象、工具类对象、频繁访问数据库或文件的对象(比如数据源、session工厂等)
工厂模式
引出
场景介绍
看一个披萨的项目:要便于披萨种类的扩展,要便于维护
1.披萨的种类很多(比如 GreekPizz、CheesePizz 等)
2.披萨的制作有 prepare,bake, cut, box
3.完成披萨店订购功能。
(1)披萨的父类抽象类
//将Pizza 类做成抽象
public abstract class Pizza {
protected String name; //名字
//准备原材料, 不同的披萨不一样,因此,我们做成抽象方法
public abstract void prepare();
public void bake() {System.out.println(name + " baking;");}
public void cut() {System.out.println(name + " cutting;");}
//打包
public void box() {System.out.println(name + " boxing;");}
public void setName(String name) {this.name = name;}
}
(2)订购披萨类
public class OrderPizza {
// 构造器
public OrderPizza() {
Pizza pizza = null;
String orderType; // 订购披萨的类型
do {
orderType = getType();//最核心的部分,就是通过外部输入来确定最终生产的对象类型
if (orderType.equals("greek")) {
pizza = new GreekPizza();
pizza.setName(" 希腊披萨 ");
} else if (orderType.equals("cheese")) {
pizza = new CheesePizza();
pizza.setName(" 奶酪披萨 ");
} else {
break;
}
//输出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 "";
}
}
}
(3)奶酪披萨、希腊披萨等
直接继承父类披萨类即可:
public class CheesePizza extends Pizza {
@Override
public void prepare() {
// TODO Auto-generated method stub
System.out.println(" 给制作奶酪披萨 准备原材料 ");
}
}
希腊披萨
public class GreekPizza extends Pizza {
@Override
public void prepare() {
// TODO Auto-generated method stub
System.out.println(" 给希腊披萨 准备原材料 ");
}
}
(4)披萨商店类,相当于一个客户端
//相当于一个客户端,发出订购
public class PizzaStore {
public static void main(String[] args) {
// TODO Auto-generated method stub
new OrderPizza();
}
}
以上就是使用普通思想进行的编程。
普通模式的优缺点
1.优点是比较好理解,简单易操作。
2.缺点是违反了设计模式的ocp原则,即对扩展开放,对修改关闭。即当我们给类增加新功能的时候,尽量不修改代码,或者尽可能少修改代码。
3.比如我们这时要新增加一个Pizza的种类(Pepper披萨),我们需要做如下修改:
简单工厂模式
分析:修改代码可以接受,但是如果我们在其它的地方也有创建Pizza的代码,就意味着,也需要修改,而创建Pizza的代码,往往有多处。
思路:把创建Pizza对象封装到一个类中,这样我们有新的Pizza种类时,只需要修改该类就可,其它有创建到Pizza对象的代码就不需要修改了.-> 简单工厂模式。就是你要任何的披萨类的子类都从工厂里拿,不要私下联系就ok。
介绍
1.简单工厂模式是属于创建型模式,是工厂模式的一种。简单工厂模式是由一个工厂对象决定创建出哪一种产品类的实例。简单工厂模式是工厂模式家族中最简单实用的模式
2.简单工厂模式:定义了一个创建对象的类,由这个类来封装实例化对象的行为(代码)
3.在软件开发中,当我们会用到大量的创建某种、某类或者某批对象时,就会使用到工厂模式.
代码改进
简单工厂模式的设计方案: 定义一个可以实例化Pizza对象的类,封装创建对象的代码。
//简单工厂类
public class SimpleFactory {
//更加orderType 返回对应的Pizza 对象
//简单工厂模式 也叫 静态工厂模式,这个静态方法就可以直接通过类名.方法名来调用
public static Pizza createPizza2(String orderType) {
Pizza pizza = null;
System.out.println("使用简单工厂模式2");
if (orderType.equals("greek")) {
pizza = new GreekPizza();
pizza.setName(" 希腊披萨 ");
} else if (orderType.equals("cheese")) {
pizza = new CheesePizza();
pizza.setName(" 奶酪披萨 ");
} else if (orderType.equals("pepper")) {
pizza = new PepperPizza();
pizza.setName("胡椒披萨");
}
return pizza;
}
}
orederpizza可以改成以下形式:
public class OrderPizza2 {
Pizza pizza = null;
String orderType = "";
// 构造器
public OrderPizza2() {
do {
orderType = getType();
pizza = SimpleFactory.createPizza2(orderType);//此处使用静态方法直接调用即可
// 输出pizza
if (pizza != null) { // 订购成功
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
} else {
System.out.println(" 订购披萨失败 ");
break;
}
} 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 "";
}
}
}
在客户端中直接使用new OrderPizza2调用即可。
工厂方法模式
核心思想就是将特殊类(具体类)的创建延迟到子类初始化时。
场景:披萨项目新的需求:客户在点披萨时,可以点不同口味的披萨,比如 北京的奶酪pizza、北京的胡椒pizza 或者是伦敦的奶酪pizza、伦敦的胡椒pizza。
介绍
最大的特征就是不同的待生产的对象的类都有对应的工厂类。
工厂方法模式设计方案:将披萨项目的实例化功能抽象成抽象方法,在不同的口味点餐子类中具体实现。
工厂方法模式:定义了一个创建对象的抽象方法,由子类决定要实例化的类。工厂方法模式将对象的实例化推迟到子类。
代码
重点部分:
OrderPizza类
public abstract class OrderPizza {
//定义一个抽象方法,createPizza , 让各个工厂子类自己实现
abstract Pizza createPizza(String orderType);
// 构造器
public OrderPizza() {
Pizza pizza = null;
String orderType; // 订购披萨的类型
do {
orderType = getType();
pizza = createPizza(orderType); //抽象方法,由工厂子类完成
//输出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 "";
}
}
}
BJOrderPizza类:
public class BJOrderPizza extends OrderPizza {
@Override
Pizza createPizza(String orderType) {
Pizza pizza = null;
if(orderType.equals("cheese")) {
pizza = new BJCheesePizza();
} else if (orderType.equals("pepper")) {
pizza = new BJPepperPizza();
}
// TODO Auto-generated method stub
return pizza;
}
}
LDOrderPizza类:
public class LDOrderPizza extends OrderPizza {
@Override
Pizza createPizza(String orderType) {
Pizza pizza = null;
if(orderType.equals("cheese")) {
pizza = new LDCheesePizza();
} else if (orderType.equals("pepper")) {
pizza = new LDPepperPizza();
}
// TODO Auto-generated method stub
return pizza;
}
}
商店客户端类:
public class PizzaStore {
public static void main(String[] args) {
String loc = "bj";//这里做成输入的方式就可以实现按照用户的选择来创建不同口味的披萨
if (loc.equals("bj")) {
//创建北京口味的各种Pizza
new BJOrderPizza();
} else {
//创建伦敦口味的各种Pizza
new LDOrderPizza();
}
// TODO Auto-generated method stub
}
}
抽象工厂模式
基本介绍
1.抽象工厂模式:定义了一个interface用于创建相关或有依赖关系的对象簇,而无需指明具体的类
2.抽象工厂模式可以将简单工厂模式和工厂方法模式进行整合。
3.从设计层面看,抽象工厂模式就是对简单工厂模式的改进(或者称为进一步的抽象)。
4.将工厂抽象成两层,AbsFactory(抽象工厂) 和 具体实现的工厂子类。程序员可以根据创建对象类型使用对应的工厂子类。这样将单个的简单工厂类变成了工厂簇,更利于代码的维护和扩展。
5.类图:
代码
1.AbsFactory
//一个抽象工厂模式的抽象层(接口)
public interface AbsFactory {
//让下面的工厂子类来 具体实现
public Pizza createPizza(String orderType);
}
2.BJFactory
//这是工厂子类
public class BJFactory implements AbsFactory {
@Override
public Pizza createPizza(String orderType) {
Pizza pizza = null;
if(orderType.equals("cheese")) {
pizza = new BJCheesePizza();
} else if (orderType.equals("pepper")){
pizza = new BJPepperPizza();
}
return pizza;
}
}
3.LDFactory
public class LDFactory implements AbsFactory {
@Override
public Pizza createPizza(String orderType) {
Pizza pizza = null;
if (orderType.equals("cheese")) {
pizza = new LDCheesePizza();
} else if (orderType.equals("pepper")) {
pizza = new LDPepperPizza();
}
return pizza;
}
}
4.OrderPizza
public class OrderPizza {
AbsFactory factory;
// 构造器
public OrderPizza(AbsFactory factory) {setFactory(factory);}
private void setFactory(AbsFactory factory) {
Pizza pizza = null;
String orderType = ""; // 用户输入
this.factory = factory;
do {
orderType = getType();
// factory 可能是北京的工厂子类,也可能是伦敦的工厂子类
pizza = factory.createPizza(orderType);
if (pizza != null) { // 订购ok
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
} else {
System.out.println("订购失败");
break;
}
} 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 "";
}
}
}
5.PizzaStore
public class PizzaStore {
public static void main(String[] args) {
// TODO Auto-generated method stub
//new OrderPizza(new BJFactory());
new OrderPizza(new LDFactory());
}
}
工厂模式在JDK-Calendar中的应用
工厂模式小结
1.工厂模式的意义:将实例化对象的代码提取出来,放到一个类中统一管理和维护,达到和主项目的依赖关系的解耦。从而提高项目的扩展和维护性。
2.三种工厂模式 (简单工厂模式、工厂方法模式、抽象工厂模式)
- 简单工厂模式就是用一个类A来生产所有的待生成类,你要某个类的对象,那就直接去找A要就可以了
- 工厂方法模式就是,用一个抽象类A或者接口(该抽象类中有生产待生产类的方法,比如说是create,通常所有的待生产类都会继承自某个超类),每个待生产类都有自己的工厂类,这些工厂类都继承自A;
- 比如我要产品CP1,那么我就找CP1Factory(这个类继承自A),然后调用实现的抽象方法create,方法返回的类型是父类的引用指向的子类的对象;
- 如果要CP2,那就找CP2Factory,然后调用create方法
- 抽象工厂模式就是用一个接口,这个接口有生产待生产类对象的方法,然后具体的工厂类实现接口,需要哪个产品就找对应的工厂类,调用接口方法即可。但是抽象工厂里的接口可以定义多个方法,来生产多个不同的类型,然后因为工厂类本身就有一种类型,就可以生成组合的类型。
- 要生产汽车和飞机,可以定义一个接口A,这个接口可以生成汽车和飞机;现在要生产中国汽车、中国飞机、美国汽车、美国飞机,这个时候就可以用两个工厂类来解决:比如ChinaFactory负责生产中国制造的(无论是飞机还是汽车),USAFactory生产美国制造的,以此类推.......因为中国飞机和中国汽车有共同的属性就是来自中国,这个时候用抽象工厂模式就更加合适。
- 那么其实抽象工厂模式就是在接口中有多个创建不同类型对象的方法供用户调用,可以把抽象工厂模式看成工厂方法模式的一种改进。
总结,工厂方法模式适合单一产品的创建,而抽象工厂模式更适合创建一组相关或相互依赖的对象。在选择使用哪个模式时,应根据具体需求和项目的复杂性来决定。
3.设计模式的依赖抽象原则
- 创建对象实例时,不要直接 new 类, 而是把这个new 类的动作放在一个工厂的方法中,并返回。有的书上说,变量不要直接持有具体类的引用。
- 不要让类继承具体类,而是继承抽象类或者是实现interface(接口)
- 不要覆盖基类中已经实现的方法。
原型模式
引出
场景
现在有一只羊tom,姓名为: tom, 年龄为:1,颜色为:白色,请编写程序创建和tom羊属性完全相同的10只羊。
传统方法可以直接new十下羊,属性设置一样就可以了。
传统方式的优缺点
1.优点是比较好理解,简单易操作。
2.在创建新的对象时,总是需要重新获取原始对象的属性,如果创建的对象比较复杂时,效率较低,比如这个类有好几十个属性,那么在复制的时候就非常的麻烦。
3.总是需要重新初始化对象,而不是动态地获得对象运行时的状态, 不够灵活
改进思路
Java中Object类是所有类的根类,Object类提供了一个clone()方法,该方法可以将一个Java对象复制一份,但是需要实现clone的Java类必须要实现一个接口Cloneable,该接口表示该类能够复制且具有复制的能力 => 原型模式
定义
1.原型模式(Prototype模式)是指:用原型实例指定创建对象的种类,并且通过拷贝这些原型,创建新的对象。
2.原型模式是一种创建型设计模式,允许一个对象再创建另外一个可定制的对象, 无需知道如何创建的细节。
3.工作原理是:通过将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝它们自己来实施创建,即 对象.clone()。
浅拷贝实现
浅拷贝指的是若拷贝的对象中含有引用类型的成员变量,那么浅拷贝只会进行值传递,将该属性值(引用地址)复制一份给新的对象。浅拷贝使用默认的clone()方法来实现。
代码实现
1.羊的本类
public class Sheep implements Cloneable {
private String name;
private int age;
private String color;
private String address = "蒙古羊";
public Sheep friend; //是对象, 浅拷贝时只会拷贝引用类型的地址
public Sheep(String name, int age, String color) {
super();
this.name = name;
this.age = age;
this.color = color;
}
public String getName() {return name;}
public void setName(String name) {this.name = name;}
public int getAge() {return age;}
public void setAge(int age) {this.age = age;}
public String getColor() {return color;}
public void setColor(String color) {this.color = color;}
@Override
public String toString() {return "Sheep [name=" + name + ", age=" + age + ", color=" + color + ", address=" + address + "]";}
//克隆该实例,使用默认的clone方法来完成
@Override
protected Object clone() {
Sheep sheep = null;
try {
sheep = (Sheep)super.clone();
} catch (Exception e) {
System.out.println(e.getMessage());
}
return sheep;
}
}
2.客户端类
public class Client {
public static void main(String[] args) {
Sheep sheep = new Sheep("tom", 1, "白色");
sheep.friend = new Sheep("jack", 2, "黑色");
Sheep sheep2 = (Sheep)sheep.clone(); //克隆
Sheep sheep3 = (Sheep)sheep.clone(); //克隆
Sheep sheep4 = (Sheep)sheep.clone(); //克隆
Sheep sheep5 = (Sheep)sheep.clone(); //克隆
System.out.println("sheep2 =" + sheep2 + "sheep2.friend=" +sheep2.friend.hashCode());
System.out.println("sheep3 =" + sheep3 + "sheep3.friend=" +sheep3.friend.hashCode());
System.out.println("sheep4 =" + sheep4 + "sheep4.friend=" +sheep4.friend.hashCode());
System.out.println("sheep5 =" + sheep5 + "sheep5.friend=" +sheep5.friend.hashCode());
}
}
引用对象的hashcode都是一致的。
浅拷贝在spring中的应用
在spring中的beans.xml中将类的scope设置为prototype就是原型模式中的浅拷贝模式。
浅拷贝总结
1.对于数据类型是基本数据类型的成员变量,浅拷贝会直接进行值传递,也就是将该属性值复制一份给新的对象。
2.对于数据类型是引用数据类型的成员变量,比如说成员变量是某个数组、某个类的对象等,那么浅拷贝会进行引用传递,也就是只是将该成员变量的引用值(内存地址)复制一份给新的对象。因为实际上两个对象的该成员变量都指向同一个实例。在这种情况下,在一个对象中修改该成员变量会影响到另一个对象的该成员变量值。
深拷贝
基本介绍:
1.复制对象的所有基本数据类型的成员变量值。
2.为所有引用数据类型的成员变量申请存储空间,并复制每个引用数据类型成员变量所引用的对象,直到该对象可达的所有对象。也就是说,对象进行深拷贝要对整个对象进行拷贝。
实现深拷贝有两种方式:
(1)重写clone方法来实现深拷贝
(2)通过对象序列化实现深拷贝(推荐)
代码
假设现在我们需要拷贝的对象的类是DeepProtoType,而这个类中成员引用类型的变量是DeepCloneableTarget。
1.DeepCloneableTarget类
public class DeepCloneableTarget implements Serializable, Cloneable {
private static final long serialVersionUID = 1L;
private String cloneName;
private String cloneClass;
public DeepCloneableTarget(String cloneName, String cloneClass) {
this.cloneName = cloneName;
this.cloneClass = cloneClass;
}
//因为该类的属性,都是String , 因此我们这里使用默认的clone完成即可
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
2.DeepProtoType类
public class DeepProtoType implements Serializable, Cloneable{
public String name; //String 属性
public DeepCloneableTarget deepCloneableTarget;// 引用类型
public DeepProtoType() {
super();
}
//方法1和方法2都在这里面,我只不过把具体的代码移到了下方。
}
其中深拷贝的方法1是用clone方法,这种方法比较繁琐,而且如果有多个引用对象类的话会写多次clone
//深拷贝 - 方式 1 使用clone 方法
@Override
protected Object clone() throws CloneNotSupportedException {
Object deep = null;
//这里完成对基本数据类型(属性)和String的克隆
deep = super.clone();
//对引用类型的属性,进行单独处理
DeepProtoType deepProtoType = (DeepProtoType)deep;
deepProtoType.deepCloneableTarget = (DeepCloneableTarget)deepCloneableTarget.clone();
return deepProtoType;
}
方法2直接使用序列化的方法,将这个类作为对象序列化流进行读取写入,方便快捷。
//深拷贝 - 方式2 通过对象的序列化实现 (推荐)
public Object deepClone() {
//创建流对象
ByteArrayOutputStream bos = null;
ObjectOutputStream oos = null;
ByteArrayInputStream bis = null;
ObjectInputStream ois = null;
try {
//序列化
bos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(bos);
oos.writeObject(this); //当前这个对象以对象流的方式输出
//反序列化
bis = new ByteArrayInputStream(bos.toByteArray());
ois = new ObjectInputStream(bis);
DeepProtoType copyObj = (DeepProtoType)ois.readObject();
return copyObj;
} catch (Exception e) {
e.printStackTrace();
return null;
} finally {
//关闭流
try {
bos.close();
oos.close();
bis.close();
ois.close();
} catch (Exception e2) {
System.out.println(e2.getMessage());
}
}
}
原型模式的注意事项与细节
1.创建新的对象比较复杂时,可以利用原型模式简化对象的创建过程,同时也能够提高效率。
2.不用重新初始化对象,而是动态地获得对象运行时的状态。
3.如果原始对象发生变化(增加或者减少属性),其它克隆对象的也会发生相应的变化,无需修改代码。
4.在实现深克隆的时候可能需要比较复杂的代码。
5.原型模式的缺点:
- 需要为每一个类都配置一个 clone 方法
- clone 方法位于类的内部,当对已有类进行改造的时候,需要修改代码,违背了开闭原则。
- 当实现深克隆时,需要编写较为复杂的代码,而且当对象之间存在多重嵌套引用时,为了实现深克隆,每一层对象对应的类都必须支持深克隆,实现起来会比较麻烦。因此,深克隆、浅克隆需要运用得当。
建造者模式
引出
先有一个建房子的需求,这个过程为打桩、砌墙、封顶,不过各种房子的过程虽然一样,但是要求不要相同的。
传统方式解决问题
具体做法
首先写一个房屋的抽象类,让具体的房屋去继承抽象类,之后再由客户端来控制房屋创建的流程。
抽象类AbstractHouse
public abstract class AbstractHouse {
//打地基
public abstract void buildBasic();
//砌墙
public abstract void buildWalls();
//封顶
public abstract void roofed();
public void build() {
buildBasic();
buildWalls();
roofed();
}
}
具体类,普通房屋
public class CommonHouse extends AbstractHouse {
@Override
public void buildBasic() {System.out.println(" 普通房子打地基 ");}
@Override
public void buildWalls() {System.out.println(" 普通房子砌墙 ");}
@Override
public void roofed() {System.out.println(" 普通房子封顶 ");}
}
客户端类
public class Client {
public static void main(String[] args) {
CommonHouse commonHouse = new CommonHouse();
commonHouse.build();
}
}
缺点
在房屋的建造过程具有较多共性时,这样设计过于简单,因为没有设计缓存层对象,对程序的拓展和维护不好。换句话说,这种设计方案将产品(即房子)和创建对象的过程(即:建房子的流程)封装在一起了,耦合性增强了。
由此提出建造者模式,将产品和产品的建造过程解耦。
建造者模式基本介绍
基本介绍
1.建造者模式(Builder Pattern) 又叫生成器模式,是一种对象构建模式。它可以将复杂对象的建造过程抽象出来(抽象类别),使这个抽象过程的不同实现方法可以构造出不同表现(属性)的对象。
2.建造者模式是一步一步创建一个复杂的对象,它允许用户只通过指定复杂对象的类型和内容就可以构建它们,用户不需要知道内部的具体构建细节。
四个角色
1.Product(产品角色): 一个具体的产品对象。
2.Builder(抽象建造者): 创建一个Product对象的各个部件指定的接口/抽象类。
3.ConcreteBuilder(具体建造者): 实现接口,构建和装配各个部件。
4.Director(指挥者): 构建一个使用Builder接口的对象。它主要是用于创建一个 复杂的对象。它主要有两个作用,一是:隔离了客户与对象的生产过程,二是: 负责控制产品对象的生产过程。
原理类图
建造者解决盖房需求
假设我们建造房子的流程还是不变,依次为打地基、砌墙、封顶,只不过现在有两种房子需要建造,分别是普通房屋CommonHouse和高楼HighBuilding。下面我们用建造者模式解决该问题:
1.House,是建造出来的产品
public class House {
private String baise;
private String wall;
private String roofed;
public String getBaise() {return baise;}
public void setBaise(String baise) {this.baise = baise;}
public String getWall() {return wall;}
public void setWall(String wall) {this.wall = wall;}
public String getRoofed() {return roofed;}
public void setRoofed(String roofed) {this.roofed = roofed;}
}
2.HouseBuilder,抽象的建造者,指定建造产品的流程。
public abstract class HouseBuilder {
protected House house = new House();
//将建造的流程写好, 抽象的方法
public abstract void buildBasic();
public abstract void buildWalls();
public abstract void roofed();
//建造房子好, 将产品(房子) 返回
public House buildHouse() {return house;}
}
3.CommonHouse普通房屋的具体建造者
public class CommonHouse extends HouseBuilder {
@Override
public void buildBasic() {System.out.println(" 普通房子打地基5米 ");}
@Override
public void buildWalls() {System.out.println(" 普通房子砌墙10cm ");}
@Override
public void roofed() {System.out.println(" 普通房子屋顶 ");}
}
4.HighBuilding高楼的具体建造者
public class HighBuilding extends HouseBuilder {
@Override
public void buildBasic() {System.out.println(" 高楼的打地基100米 ");
@Override
public void buildWalls() {System.out.println(" 高楼的砌墙20cm ");}
@Override
public void roofed() {System.out.println(" 高楼的透明屋顶 ");}
}
5.HouseDirector指挥者
//指挥者,这里去指定制作流程,返回产品
public class HouseDirector {
HouseBuilder houseBuilder = null;
//构造器传入 houseBuilder
public HouseDirector(HouseBuilder houseBuilder) {this.houseBuilder = houseBuilder;}
//通过setter 传入 houseBuilder
public void setHouseBuilder(HouseBuilder houseBuilder) {this.houseBuilder = houseBuilder;}
//如何处理建造房子的流程,交给指挥者
public House constructHouse() {
houseBuilder.buildBasic();
houseBuilder.buildWalls();
houseBuilder.roofed();
return houseBuilder.buildHouse();
}
}
6.客户端Client
public class Client {
public static void main(String[] args) {
//盖普通房子
CommonHouse commonHouse = new CommonHouse();
//准备创建房子的指挥者
HouseDirector houseDirector = new HouseDirector(commonHouse);
//完成盖房子,返回产品(普通房子)
House house = houseDirector.constructHouse();
//若是又要盖高楼
HighBuilding highBuilding = new HighBuilding();
//重置建造者
houseDirector.setHouseBuilder(highBuilding);
//完成盖房子,返回产品(高楼)
houseDirector.constructHouse();
}
}
类图
注意事项和细节
1.客户端(使用程序)不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象。
2.每一个具体建造者都相对独立,而与其他的具体建造者无关,因此可以很方便地替换具体建造者或增加新的具体建造者, 用户使用不同的具体建造者即可得到不同的产品对象
3.可以更加精细地控制产品的创建过程 。将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰,也更方便使用程序来控制创建过程
4.增加新的具体建造者无须修改原有类库的代码,指挥者类针对抽象建造者类编程,系统扩展方便,符合 “开闭原则。
5.建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制。
6.如果产品的内部变化复杂,可能会导致需要定义很多具体建造者类来实现这种变化,导致系统变得很庞大,因此在这种情况下,要考虑是否选择建造者模式
7.抽象工厂模式和建造者模式
抽象工厂模式实现对产品家族的创建,一个产品家族是这样的一系列产品:具有不同分类维度的产品组合,采用抽象工厂模式不需要关心构建过程,只关心什么产品由什么工厂生产即可。而建造者模式则是要求按照指定的蓝图建造产品,它的主要目的是通过组装零配件而产生一个新产品。
适配器模式
引出
现实生活中,不同地区的插座可能不同,这个时候如果想跨地区的使用电源则需要适配器。
基本介绍
1.适配器模式(Adapter Pattern)将某个类的接口转换成客户端期望的另一个接口表示,主的目的是兼容性,让原本因接口不匹配不能一起工作的两个类可以协同工作。其别名为包装器(Wrapper)
2.适配器模式属于结构型模式。
3.主要分为三类:类适配器模式、对象适配器模式、接口适配器模式。
类适配器
介绍
基本介绍:Adapter类,通过继承 src类,实现 dst 类接口,完成src->dst的适配。
代码实现
加入以生活中的充电器来举例说明,220V交流电相当于src,我们的目标dst是5V直流电
类图如下所示
1.Voltage220V被适配的类
public class Voltage220V {
//输出220V的电压
public int output220V() {
int src = 220;
System.out.println("电压=" + src + "伏");
return src;
}
}
2.Voltage5V适配接口
public interface IVoltage5V {public int output5V();}
3.VoltageAdapter适配器类,注意这里的适配主要是通过继承被适配类来实现的
public class VoltageAdapter extends Voltage220V implements IVoltage5V {
@Override
public int output5V() {
//获取到220V电压
int srcV = output220V();
int dstV = srcV / 44 ; //转成 5v
return dstV;
}
}
4.Phone手机类
public class Phone {
public void charging(IVoltage5V iVoltage5V) {
if(iVoltage5V.output5V() == 5) {
System.out.println("电压为5V, 可以充电~~");
} else if (iVoltage5V.output5V() > 5) {
System.out.println("电压大于5V, 不能充电~~");
}
}
}
5.Clinet客户端类
public class Client {
public static void main(String[] args) {
System.out.println(" === 类适配器模式 ====");
Phone phone = new Phone();
phone.charging(new VoltageAdapter());
}
}
注意事项和细节
1.Java是单继承机制,所以类适配器需要继承src类这一点算是一个缺点, 因为这要求dst必须是接口,有一定局限性;
2.src类的方法在Adapter中都会暴露出来,也增加了使用的成本。
3.由于其继承了src类,所以它可以根据需求重写src类的方法,使得Adapter的灵活性增强了。
对象适配器模式(常用)
1.基本思路和类的适配器模式相同,只是将Adapter类作修改,不是继承src类,而是持有src类的实例,以解决兼容性的问题。
2.根据“合成复用原则”,在系统中尽量使用关联关系来替代继承关系。
代码
体现在代码层面最关键的一个就是不继承而是持有src类的示例,用关联代替继承。
最关键的就是VoltageAdapter类的改变:
public class VoltageAdapter implements IVoltage5V {
private Voltage220V voltage220V;//关联关系-聚合
//通过构造器,传入一个Voltage220V实例
public VoltageAdapter(Voltage220V voltage220v){
this.voltage220v=voltage220v;
}
@Override
public int output5V() {
int dst=0;
if(null!=voltage220V){
int src=voltage220V.output220V();
dst = src/44;
}
return dst;
}
}
然后Client类需要做下微调,需要实例化被适配器类的对象以供构造器初始化:
public class Client {
public static void main(String[] args) {
Phone phone = new Phone();
phone.charging(new VoltageAdapter(new Voltage220V()));
}
}
注意事项
1.对象适配器和类适配器其实算是同一种思想,只不过实现方式不同。 根据合成复用原则,使用组合替代继承, 所以它解决了类适配器必须继承src的局限性问题,也不再要求dst必须是接口。
2.使用成本更低,更灵活。
接口适配器
非常简单,用一个抽象类类实现接口,抽象类里对每个方法都提供默认实现(空方法),那么那么该抽象类的子类可有选择地覆盖父类的某些方法来实现需求。
1.一些书籍称为:适配器模式(Default Adapter Pattern)或缺省适配器模式。
2.当不需要全部实现接口提供的方法时,可先设计一个抽象类实现接口,并为该接口中每个方法提供一个默认实现(空方法),那么该抽象类的子类可有选择地覆盖父类的某些方法来实现需求。
3.适用于一个接口不想使用其所有的方法的情况。
桥接模式
问题引出
如果我们需要对不同版型的手机的不同品牌实现操作编程,需要模拟出手机的各个功能(比如:开机、关机、上网、打电话等),如下图所示:
如果像上图一样用传统的方式,每个版型下面都有各自的品牌,则如下图所示:
首先是一个大的手机父类,然后由具体的版型继承手机父类,再然后使用品牌来继承具体的版型类从而实现确定一个手机的版型与品牌,但是这样不利于增加手机的版型或者品牌,即造成扩展性问题。
总结下来,传统方式有如下缺点:
1.扩展性问题(类爆炸),如果我们再增加手机的样式(旋转式),就需要增加各个品牌手机的类,同样如果我们增加一个手机品牌,也要在各个手机样式类下增加。
2.违反了单一职责原则,当我们增加手机样式时,要同时增加所有品牌的手机,这样增加了代码维护成本.
问题的解决
桥接模式基本介绍
1.桥接模式(Bridge模式)是指:将实现与抽象放在两个不同的类层次中,使两个层次可以独立改变。
2.是一种结构型设计模式。
3.Bridge模式基于类的最小设计原则,通过使用封装、聚合及继承等行为让不同的类承担不同的职责。它的主要特点是把抽象(Abstraction)与行为实现(Implementation)分离开来,从而可以保持各部分的独立性以及应对他们的功能扩展。
代码
UML类图
各个类
1.Brand接口
public interface Brand {
void open();
void close();
void call();
}
2.Vivo和XiaoMi类实现接口。
public class Vivo implements Brand {
@Override
public void open() {System.out.println(" Vivo手机开机 ");}
@Override
public void close() {System.out.println(" Vivo手机关机 ");}
@Override
public void call() {System.out.println(" Vivo手机打电话 ");}
}
public class XiaoMi implements Brand {
@Override
public void open() {System.out.println(" 小米手机开机 ");}
@Override
public void close() {System.out.println(" 小米手机关机 ");}
@Override
public void call() {System.out.println(" 小米手机打电话 ");}
}
3.Phone抽象类
public abstract class Phone {
//组合品牌
private Brand brand;
//构造器
public Phone(Brand brand) {
super();
this.brand = brand;
}
protected void open() {this.brand.open();}
protected void close() {this.brand.close();}
protected void call() {this.brand.call();}
}
4.FoldedPhone具体类
//折叠式手机类,继承 抽象类 Phone
public class FoldedPhone extends Phone {
//构造器
public FoldedPhone(Brand brand) {super(brand);}
public void open() {
super.open();
System.out.println(" 折叠样式手机 ");
}
public void close() {
super.close();
System.out.println(" 折叠样式手机 ");
}
public void call() {
super.call();
System.out.println(" 折叠样式手机 ");
}
}
5.Client客户端类
public class Client {
public static void main(String[] args) {
//获取折叠式手机 (样式(折叠) + 品牌(小米) )
Phone phone1 = new FoldedPhone(new XiaoMi());
phone1.open();
phone1.call();
phone1.close();
System.out.println("=======================");
Phone phone2 = new FoldedPhone(new Vivo());//(Vivo牌的折叠手机)
phone2.open();
phone2.call();
phone2.close();
System.out.println("==============");
}
}
这个时候如果想添加一个直立式版型,则非常方便,直接继承Phone即可
6.UpRightPhone
public class UpRightPhone extends Phone {
//构造器
public UpRightPhone(Brand brand) {super(brand);}
public void open() {
super.open();
System.out.println(" 直立样式手机 ");
}
public void close() {
super.close();
System.out.println(" 直立样式手机 ");
}
public void call() {
super.call();
System.out.println(" 直立样式手机 ");
}
}
桥接模式在JDBC源码中的使用
主要使用在:
1.Jdbc 的 Driver接口,如果从桥接模式来看,Driver就是一个接口,下面可以有MySQL的Driver,Oracle的Driver,这些就可以当做实现接口类
注意事项和细节
1.实现了抽象和实现部分的分离,从而极大的提供了系统的灵活性,让抽象部分和实现部分独立开来,这有助于系统进行分层设计,从而产生更好的结构化系统。
2.对于系统的高层部分,只需要知道抽象部分和实现部分的接口就可以了,其它的部分由具体业务来完成。
3.桥接模式替代多层继承方案,可以减少子类的个数,降低系统的管理和维护成本。
4.桥接模式的引入增加了系统的理解和设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计和编程。
5.桥接模式要求正确识别出系统中两个独立变化的维度(在本例子中,两个独立变化的维度就是手机的版型和各个品牌),因此其使用范围有一定的局限性,即需要有这样的应用场景。
桥接模式的其他应用场景
1.对于那些不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统,桥接模式尤为适用.
2.常见的应用场景:
装饰者设计模式
问题引出
现在考虑星巴克咖啡(咖啡馆)订单项目:
1.咖啡种类/单品咖啡:Espresso(意大利浓咖啡)、ShortBlack、LongBlack(美式咖啡)、Decaf(无因咖啡)
2.调料:Milk、Soy(豆浆)、Chocolate
3.要求在扩展新的咖啡种类时,具有良好的扩展性、改动方便、维护方便。
4.使用OOP(面向对象的思想)的来计算不同种类咖啡的费用 :客户可以点单品咖啡,也可以单品咖啡+调料组合。
较差的方案1
把每个组合类都列举出来,这种方法的弊端显而易见,一是组合数量十分庞大,二是当调料可以加多份时,组合数是无穷不可计算的。
示意图
分析
较好的方案二
前面分析到方案1因为咖啡单品+调料组合会造成类的倍增,因此可以做改进,将调料内置到Drink类,这样就不会造成类数量过多。从而提高项目的维护性(如图)
示意图
分析
1.方案2可以控制类的数量,不至于造成很多的类
2.在增加或者删除调料种类时,代码的维护量很大(弊端)
定义
1.装饰者模式:动态的将新功能附加到对象上。在对象功能扩展方面,它比继承更有弹性,装饰者模式也体现了开闭原则(ocp)。而且可以通过递归的方式添加任意个装饰物。
原先我们的思想是把调料倒进咖啡里,现在这个装饰者模式的思想更倾向于将咖啡导入装饰物中,比如将Decaf导进chocolate中,再将Decf+chocolate导入soy(豆浆)中从而形成Decaf+chocolate+soy的组合。
2.原理
(1)装饰者模式就像打包一个快递
主体:比如:陶瓷、衣服 (Component) // 被装饰者
包装:比如:报纸填充、塑料泡沫、纸板、木板(Decorator)
(2)Component。主体:比如类似前面的Drink
(3)ConcreteComponent和Decorator
ConcreteComponent:具体的主体,比如前面的各个单品咖啡。
Decorator: 装饰者,比如各调料。
使用装饰者模式解决星巴克咖啡问题
示意图
假设我们想要在装饰者模式下点一份订单,订单是:2份巧克力+1份牛奶的LongBlack
代码
1.ShortBlack、Decaf、Espresso和LongBlack是四种单品咖啡种类
(1)ShortBlack
public class ShortBlack extends Coffee{
public ShortBlack() {
setDes(" shortblack ");
setPrice(4.0f);
}
}
(2)Decaf
public class DeCaf extends Coffee {
public DeCaf() {
setDes(" 无因咖啡 ");
setPrice(1.0f);
}
}
(3)Espresso
public class Espresso extends Coffee {
public Espresso() {
setDes(" 意大利咖啡 ");
setPrice(6.0f);
}
}
(4)LongBlack
public class LongBlack extends Coffee {
public LongBlack() {
setDes(" longblack ");
setPrice(5.0f);
}
}
2.这四种咖啡可以提取共性后形成一个缓冲层类Coffee
public class Coffee extends Drink {
@Override
public float cost() {
// TODO Auto-generated method stub
return super.getPrice();
}
}
3.而Coffee类是缓冲层,在具体类(比如LongBlack)和最抽象的饮品类(抽象Drink)之间做一个缓冲。
public abstract class Drink {
public String des; // 描述
private float price = 0.0f;//价格
public String getDes() {return des;}
public void setDes(String des) {this.des = des;}
public float getPrice() {return price;}
public void setPrice(float price) {this.price = price;}
//计算费用的抽象方法
//子类来实现
public abstract float cost();
}
4.Decorator类是所有调料的父类,而Decorator类也是Drink的子类。
public class Decorator extends Drink {
private Drink obj;//组合
public Decorator(Drink obj) { this.obj = obj;}
@Override
public float cost() {
return super.getPrice() + obj.cost();// getPrice 自己的价格,加上组合物的价格
}
@Override
public String getDes() {
// obj.getDes() 输出被装饰者的信息
return des + " " + getPrice() + " && " + obj.getDes();
}
}
5.Chocolate、Milk和Soy类都是调料品类
(1)Chocolate
//具体的Decorator, 这里就是调味品
public class Chocolate extends Decorator {
public Chocolate(Drink obj) {
super(obj);
setDes(" 巧克力 ");
setPrice(3.0f); // 调味品 的价格
}
}
(2)Milk
public class Milk extends Decorator {
public Milk(Drink obj) {
super(obj);
setDes(" 牛奶 ");
setPrice(2.0f);
}
}
(3)Soy
public class Soy extends Decorator{
public Soy(Drink obj) {
super(obj);
setDes(" 豆浆 ");
setPrice(1.5f);
}
}
6.客户端CoffeeBar类
public class CoffeeBar {
public static void main(String[] args) {
// 装饰者模式下的订单:2份巧克力+一份牛奶的LongBlack
// 1. 点一份 LongBlack
Drink order = new LongBlack();
System.out.println("费用1=" + order.cost());
System.out.println("描述=" + order.getDes());
// 2. order 加入一份牛奶
order = new Milk(order);//将第一个order传入milk中再赋值给自己,最后计算的时候使用递归的方式得到总价
System.out.println("order 加入一份牛奶 费用 =" + order.cost());
System.out.println("order 加入一份牛奶 描述 = " + order.getDes());
// 3. order 加入一份巧克力
order = new Chocolate(order);
System.out.println("order 加入一份牛奶 加入一份巧克力 费用 =" + order.cost());
System.out.println("order 加入一份牛奶 加入一份巧克力 描述 = " + order.getDes());
// 3. order 加入一份巧克力
order = new Chocolate(order);
System.out.println("order 加入一份牛奶 加入2份巧克力 费用 =" + order.cost());
System.out.println("order 加入一份牛奶 加入2份巧克力 描述 = " + order.getDes());
System.out.println("===========================");
Drink order2 = new DeCaf();
System.out.println("order2 无因咖啡 费用 =" + order2.cost());
System.out.println("order2 无因咖啡 描述 = " + order2.getDes());
order2 = new Milk(order2);
System.out.println("order2 无因咖啡 加入一份牛奶 费用 =" + order2.cost());
System.out.println("order2 无因咖啡 加入一份牛奶 描述 = " + order2.getDes());
}
}
输出如下:
装饰者模式在JDK中主要应用在FilterInputStream
组合模式
问题引出
编写程序展示一个学校院系结构:需求是这样,要在一个页面中展示出学校的院系组成,一个学校有多个学院,一个学院有多个系。如图:
传统方式
如果我们用传统方式,将学院堪称学校的子类,各个系堪称学院的子类的话,这样就不能很好实现管理的功能,比如对学院、系的增加,删除,遍历等等。
使用组合模式
把学校、院、系都看做是组织结构,他们之间没有继承的关系,而是一个树形结构,可以更好的实现管理操作。 => 组合模式
定义
1.组合模式(Composite Pattern),又叫部分整体模式,它创建了对象组的树形结构,将对象组合成树状结构以表示“整体-部分”的层次关系。
2.组合模式依据树形结构来组合对象,用来表示部分以及整体层次。
3.这种类型的设计模式属于结构型模式。
4.组合模式使得用户对单个对象和组合对象的访问具有一致性,即:组合能让客户以一致的方式处理个别对象以及组合对象。
原理类图
各个角色的定义和作用:
代码
类图
具体类
1.OrganizationComponent作为Component
public abstract class OrganizationComponent {
private String name; // 名字
private String des; // 说明
protected void add(OrganizationComponent organizationComponent) {
//默认实现
throw new UnsupportedOperationException();
}
protected void remove(OrganizationComponent organizationComponent) {
//默认实现
throw new UnsupportedOperationException();
}
//构造器
public OrganizationComponent(String name, String des) {
super();
this.name = name;
this.des = des;
}
public String getName() {return name;}
public void setName(String name) {this.name = name;}
public String getDes() {return des;}
public void setDes(String des) {this.des = des;}
//方法print, 做成抽象的, 子类都需要实现
protected abstract void print();
}
2.University、College和Department都继承自OrganizationComponent,他们之间互相的关系是平行同类的,但是我们逻辑上应该把他们看成一个包含的关系。不过在代码层面上,理论上来说University可以直接包含Department,反之亦然,这也是组合模式的一个缺点,在新增加构件时可能会出现一些问题。
(1)University类
//University 就是 Composite , 可以管理College
public class University extends OrganizationComponent {
List<OrganizationComponent> organizationComponents = new ArrayList<OrganizationComponent>();
// 构造器
public University(String name, String des) {super(name, des);}
// 重写add
@Override
protected void add(OrganizationComponent organizationComponent) {
organizationComponents.add(organizationComponent);
}
// 重写remove
@Override
protected void remove(OrganizationComponent organizationComponent) {
organizationComponents.remove(organizationComponent);
}
@Override
public String getName() {return super.getName();}
@Override
public String getDes() {return super.getDes();}
// print方法,就是输出University 包含的学院
@Override
protected void print() {
// TODO Auto-generated method stub
System.out.println("--------------" + getName() + "--------------");
//遍历 organizationComponents
for (OrganizationComponent organizationComponent : organizationComponents) {
organizationComponent.print();
}
}
}
(2)College类
public class College extends OrganizationComponent {
//List 中 存放的Department
List<OrganizationComponent> organizationComponents = new ArrayList<OrganizationComponent>();
// 构造器
public College(String name, String des) {super(name, des);}
// 重写add
@Override
protected void add(OrganizationComponent organizationComponent) {
// 这里的话add逻辑与University一致,但是将来实际业务中,Colleage 的 add 和 University add不一定完全一样
organizationComponents.add(organizationComponent);
}
// 重写remove
@Override
protected void remove(OrganizationComponent organizationComponent) {
organizationComponents.remove(organizationComponent);
}
@Override
public String getName() {return super.getName();}
@Override
public String getDes() {return super.getDes();}
// print方法,就是输出University 包含的学院
@Override
protected void print() {
System.out.println("--------------" + getName() + "--------------");
//遍历 organizationComponents
for (OrganizationComponent organizationComponent : organizationComponents) {
organizationComponent.print();
}
}
}
(3)Department类作为叶子结点,无需实现add和remove抽象方法。
public class Department extends OrganizationComponent {
//没有集合
public Department(String name, String des) {super(name, des);}
//add , remove 就不用写了,因为他是叶子节点
@Override
public String getName() {return super.getName();}
@Override
public String getDes() {return super.getDes();}
@Override
protected void print() {System.out.println(getName());}
}
在JDK中的应用
主要体现在HashMap中
对应的类图
注意事项
组合模式适合解决的问题
1.组合模式解决这样的问题,当我们的要处理的对象可以生成一颗树形结构,而我们要对树上的节点和叶子进行操作时,它能够提供一致的方式,而不用考虑它是节点还是叶子。即节点和叶子差异并不是很大的时候。如下图所示
细节
1.组合模式简化了客户端操作,客户端只需要面对一致的对象而不用考虑整体部分或者节点叶子的问题。
2.具有较强的扩展性。当我们要更改组合对象时,我们只需要调整内部的层次关系,客户端不用做出任何改动
3.方便创建出复杂的层次结构。客户端不用理会组合里面的组成细节,容易添加节点或者叶子从而创建出复杂的树形结构。
4.需要遍历组织结构,或者处理的对象具有树形结构时, 非常适合使用组合模式。
5.要求较高的抽象性,如果节点和叶子有很多差异性的话,比如很多方法和属性都不一样,不适合使用组合模式。
外观模式
问题引出
现有一个组建家庭影院的功能,要求统筹调用各个部件,具体如下
传统方式
传统的思想做法
若用传统思考方式来解决的话,会直接在ClientTest类中调用各个部件类里的方法,在代码层面看上去就会比较混乱,如下图所示:
传统方式分析
1.在ClientTest 的main方法中,创建各个子系统的对象,并直接去调用子系统(对象) 相关方法,会造成调用过程混乱,没有清晰的过程。
2.不利于在ClientTest 中,去维护对子系统的操作
3.解决思路:定义一个高层接口,给子系统中的一组接口提供一个一致的界面(比如在高层接口提供四个方法 ready, play, pause, end ),用来访问子系统中的 一群接口。
4.也就是说就是通过定义一个一致的接口(界面类),用以屏蔽内部子系统的细节, 使得调用端只需跟这个接口发生调用,而无需关心这个子系统的内部细节 => 外观模式
外观模式的定义
基本介绍
1.外观模式(Facade),也叫“过程模式:外观模式为子系统中的一组接口提供一个一致的界面,此模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
2.外观模式通过定义一个一致的接口,用以屏蔽内部子系统的细节,使得调用端只需跟这个接口发生调用,而无需关心这个子系统的内部细节。
原理类图
说明:
1.外观类(Facade): 为调用端提供统一的调用接口, 外观类知道哪些子系统负责处理请求,从而将调用端的请求代理给适当 子系统对象
2.调用者(Client): 外观接口的调用者
3.子系统的集合:指模块或者子系统,处理Facade 对象指派的任务,他是功能的实际提供者
外观模式解决影院管理问题
思路
1.外观模式可以理解为转换一群接口,客户只要调用一个接口,而不用调用多个接口才能达到目的。比如:在pc上安装软件的时候经常有一键安装选项(省去选择安装目录、安装的组件等等),还有就是手机的重启功能(把关机和启动合为一个操作)。
2.外观模式就是解决多个复杂接口带来的使用困难,起到简化用户操作的作用。
示意图
代码
下面各个子系统因为在整个程序的生命周期里只有一个,所以考虑使用单例模式,这里的单例模式使用最为 推荐的枚举型单例模式。
1.各个子部件系统:
(1)DVDPlayer
public enum DVDPlayer {
Instance;
public void on() {System.out.println(" dvd on ");}
public void off() {System.out.println(" dvd off ");}
public void play() {System.out.println(" dvd is playing ");}
public void pause() {System.out.println(" dvd pause ..");}
}
(2)Popcorn
public enum Popcorn {
Instance;
public void on() {System.out.println(" popcorn on ");}
public void off() {System.out.println(" popcorn ff ");}
public void pop() {System.out.println(" popcorn is poping ");}
}
(3)Projector
public enum Projector {
Instance;
public void on() {System.out.println(" Projector on ");}
public void off() {System.out.println(" Projector ff ");}
public void focus() {System.out.println(" Projector is Projector ");}
}
(4)Screen
public enum Screen {
Instance;
public void up() {System.out.println(" Screen up ");}
public void down() {System.out.println(" Screen down ");}
}
(5)Stereo
public enum Stereo {
Instance;
public void on() {System.out.println(" Stereo on ");}
public void off() {System.out.println(" Screen off ");}
public void up() {System.out.println(" Screen up.. ");}
}
(6)TheaterLight
public enum TheaterLight {
Instance;
public void on() {System.out.println(" TheaterLight on ");}
public void off() {System.out.println(" TheaterLight off ");}
public void dim() {System.out.println(" TheaterLight dim.. ");}
public void bright() {System.out.println(" TheaterLight bright.. ");}
}
2.外观类,即给调用者提供一个统一的接口方便调用
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.Instance;
this.popcorn = Popcorn.Instance;
this.stereo = Stereo.Instance;
this.projector = Projector.Instance;
this.screen = Screen.Instance;
this.dVDPlayer = DVDPlayer.Instance;
}
//操作分成 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();
}
}
3.调用端Client类,通过外观类可以方便的统筹整个系统中各个子类的关系和调用顺序
public class Client {
public static void main(String[] args) {
HomeTheaterFacade homeTheaterFacade = new HomeTheaterFacade();
homeTheaterFacade.ready();
homeTheaterFacade.play();
homeTheaterFacade.end();
}
}
运行结果如下:
外观模式在MyBatis框架的应用
MyBatis中的Configuration去创建MetaObject对象时使用到了外观模式。
代码
类图
注意事项与细节
1.外观模式对外屏蔽了子系统的细节,因此外观模式降低了客户端对子系统使用的复杂性。
2.外观模式对客户端与子系统的耦合关系,让子系统内部的模块更易维护和扩展。
3.通过合理的使用外观模式,可以帮我们更好的划分访问的层次
4.当系统需要进行分层设计时,可以考虑使用Facade模式
5.在维护一个遗留的大型系统时,可能这个系统已经变得非常难以维护和扩展,此时可以考虑为新系统开发一个Facade类,来提供遗留系统的比较清晰简单的接口,让新系统与Facade类交互,提高复用性。
5.不能过多的或者不合理的使用外观模式,使用外观模式好,还是直接调用模块好。要以让系统有层次,利于维护为目的。
享元模式
问题引出
小型的外包项目,给客户A做一个产品展示网站,客户A的朋友感觉效果不错,也希望做这样的产品展示网站,但是要求都有些不同:
- 有客户要求以新闻的形式发布
- 有客户人要求以博客的形式发布
- 有客户希望以微信公众号的形式发布
传统方案
但是传统方案有一些问题,分析如下:
1.需要的网站结构相似度很高,而且都不是高访问量网站,如果分成多个虚拟空间来处理,相当于一个相同网站的实例对象很多,造成服务器的资源浪费。
2.解决思路:整合到一个网站中,共享其相关的代码和数据,对于硬盘、内存、CPU、数据库空间等服务器资源都可以达成共享,减少服务器资源
3.对于代码来说,由于是一份实例,维护和扩展都更加容易
换言之,可以使用**享元模式**来解决问题。
基本介绍
原理类图
说明:
- FlyWeight 是抽象的享元角色, 他是产品的抽象类, 同时定义出对象的外部状态和内部状态(后面介绍) 的接口或实现
- ConcreteFlyWeight 是具体的享元角色,是具体的产品类,实现抽象角色定义相关业务
- UnSharedConcreteFlyWeight 是不可共享的角色,一般不会出现在享元工厂
内部状态和外部状态
比如围棋、五子棋、跳棋,它们都有大量的棋子对象,围棋和五子棋只有黑白两色,跳棋颜色多一 点,所以**棋子颜色就是棋子的内部状态**;而各个棋子之间的差别就是位置的不同,当我们落子后,落子颜色是定的,但位置是变化的,所以**棋子坐标就是棋子的外部状态**。
1.享元模式提出了两个要求:细粒度和共享对象。这里就涉及到内部状态和外部状态了,即将对象的信息分为两个部分:内部状态和外部状态。
2.内部状态指对象共享出来的信息,存储在享元对象内部且不会随环境的改变而改变。
3.外部状态指对象得以依赖的一个标记,是随环境改变而改变的、不可共享的状态。
举个例子:
围棋理论上有361个空位可以放棋子,每盘棋都有可能有两三百个棋子对象产生,因为内存空间有限,一台服务器很难支持更多的玩家玩围棋游戏,如果用享元模式来处理棋子,**那么棋子对象就可以减少到只有两个实例**,这样就很好的解决了对象的开销问题。
用享元模式解决网站展示问题
原理类图
代码部分
1.WebSite
public abstract class WebSite {
public abstract void use(User user);//抽象方法
}
2.ConcreteWebSite
public class ConcreteWebSite extends WebSite {//具体网站
//共享的部分,内部状态
private String type = ""; //网站发布的形式(类型)
//构造器
public ConcreteWebSite(String type) {this.type = type;}
@Override
public void use(User user) {
System.out.println("网站的发布形式为:" + type + " 在使用中 .. 使用者是" + user.getName());
}
}
3.WebSiteFactory
import java.util.HashMap;
// 网站工厂类,根据需要返回压一个网站
public class WebSiteFactory {
//集合, 充当池的作用
private HashMap<String, ConcreteWebSite> pool = new HashMap<>();
//根据网站的类型,返回一个网站, 如果没有就创建一个网站,并放入到池中,并返回
public WebSite getWebSiteCategory(String type) {
if(!pool.containsKey(type)) {
//就创建一个网站,并放入到池中
pool.put(type, new ConcreteWebSite(type));
}
return (WebSite)pool.get(type);
}
//获取网站分类的总数 (池中有多少个网站类型)
public int getWebSiteCount() {
return pool.size();
}
}
4.User,用于给网站标记外部状态
package com.atguigu.flyweight;
public class User {
private String name;
public User(String name) {
super();
this.name = name;
}
public String getName() {return name;}
public void setName(String name) {this.name = name;}
}
5.Client
public class Client {
public static void main(String[] args) {
// 创建一个工厂类
WebSiteFactory factory = new WebSiteFactory();
// 客户要一个以新闻形式发布的网站
WebSite webSite1 = factory.getWebSiteCategory("新闻");
webSite1.use(new User("tom"));
// 客户要一个以博客形式发布的网站
WebSite webSite2 = factory.getWebSiteCategory("博客");
webSite2.use(new User("jack"));
// 客户要一个以博客形式发布的网站
WebSite webSite3 = factory.getWebSiteCategory("博客");
webSite3.use(new User("smith"));
// 客户要一个以博客形式发布的网站
WebSite webSite4 = factory.getWebSiteCategory("博客");
webSite4.use(new User("king"));
System.out.println("网站的分类共=" + factory.getWebSiteCount());
}
}
运行结果:
注意事项和细节
1.在享元模式这样理解,“享”就表示共享,“元”表示对象。
2.系统中有大量对象,这些对象消耗大量内存,并且对象的状态大部分可以外部化时,我们就可以考虑选用享元模式。
3.用唯一标识码判断,如果在内存中有,则返回这个唯一标识码所标识的对象,用HashMap/HashTable存储。
4.享元模式大大减少了对象的创建,降低了程序内存的占用,提高效率。
5.享元模式提高了系统的复杂度。需要分离出内部状态和外部状态,而外部状态具有固化特性,不应该随着内部状态的改变而改变,这是我们使用享元模式需要注意的地方。
6.使用享元模式时,注意划分内部状态和外部状态,并且需要有一个工厂类加以控制。
7.享元模式经典的应用场景是需要缓冲池的场景,比如 String常量池、数据库连接池。
代理模式
基本概念
1.定义:为一个对象提供一个替身,以控制对这个对象的访问。即通过代理对象访问目标对象.
这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能。
2.被代理的对象可以是远程对象、创建开销大的对象或需要安全控制的对象。
3.主要有三种代理模式:
静态代理、动态代理 (JDK代理、接口代理)和 Cglib代理 (可以在内存动态的创建对象,而不需要实现接口, 它是属于动态代理的范畴) 。
4.示意图:用户(客户端)通过代理才能接触到目标对象。
静态代理
静态代理在使用时,需要**定义接口**或者**父类,**被代理对象(即目标对象)与代理对象一起实现相同的接口或者是继承相同父类。
代码
示意图如下:
1.ITeacherDao.java
public interface ITeacherDao {
void teach(); // 授课的方法
}
2.TeacherDao.java
public class TeacherDao implements ITeacherDao {
@Override
public void teach() {System.out.println(" 老师授课中 。。。。。");}
}
3.TeacherDaoProxy.java
//代理对象,静态代理
public class TeacherDaoProxy implements ITeacherDao{
private ITeacherDao target; // 目标对象,通过接口来聚合
//构造器
public TeacherDaoProxy(ITeacherDao target) {this.target = target;}
@Override
public void teach() {
System.out.println("开始代理 完成某些操作。。。。。 ");//方法
target.teach();
System.out.println("提交。。。。。");//方法
}
}
4.Client.java
public class Client {
public static void main(String[] args) {
//创建目标对象(被代理对象)
TeacherDao teacherDao = new TeacherDao();
//创建代理对象, 同时将被代理对象传递给代理对象
TeacherDaoProxy teacherDaoProxy = new TeacherDaoProxy(teacherDao);
//通过代理对象,调用到被代理对象的方法
//即:执行的是代理对象的方法,代理对象再去调用目标对象的方法
teacherDaoProxy.teach();
}
}
运行结果:
优缺点
1.优点:在不修改目标对象的功能前提下, 能通过代理对象对目标功能扩展。
2.缺点:
(1)因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类。
(2)一旦接口增加方法,目标对象与代理对象都要维护。
动态代理
基本介绍
1.代理对象,不需要实现接口,但是目标对象要实现接口,否则不能用动态代理。
2.代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象,并且通过反射机制返回一个代理对象。
3.动态代理也叫做:JDK代理、接口代理。
JDK的api所在的包是:java.lang.reflect.Proxy,JDK实现代理只需要使用newProxyInstance方法,但是该方法需要接收三个参数,完整的写法是:
static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h )
代码
将前面的静态代理改成动态代理模式,示意图如下:
1.ITeacherDao.java
public interface ITeacherDao {//接口
void teach(); // 授课方法
void sayHello(String name);
}
2.TeacherDao.java
public class TeacherDao implements ITeacherDao {
@Override
public void teach() {System.out.println(" 老师授课中.... ");}
@Override
public void sayHello(String name) {System.out.println("hello " + name);}
}
3.ProxyFactory.java
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class ProxyFactory {
//维护一个目标对象 , Object
private Object target;
//构造器 , 对target 进行初始化
public ProxyFactory(Object target) {this.target = target;}
//给目标对象 生成一个代理对象
public Object getProxyInstance() {
/* public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
//1. ClassLoader loader : 指定当前目标对象使用的类加载器, 获取加载器的方法固定
//2. Class<?>[] interfaces: 目标对象实现的接口类型,使用泛型方法确认类型
//3. InvocationHandler h : 事情处理,执行目标对象的方法时,会触发事情处理器方法, 会把当前执行的目标对象方法作为参数传入
*/
return Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("JDK代理开始~~");
Object returnVal = method.invoke(target, args);//反射机制调用目标对象的方法
System.out.println("JDK代理提交");
return returnVal;
}
});
}
}
4.Client.java
public class Client {
public static void main(String[] args) {
//创建目标对象
ITeacherDao target = new TeacherDao();
//给目标对象,创建代理对象, 可以转成 ITeacherDao
ITeacherDao proxyInstance = (ITeacherDao)new ProxyFactory(target).getProxyInstance();
// proxyInstance=class com.sun.proxy.$Proxy0 内存中动态生成了代理对象
System.out.println("proxyInstance=" + proxyInstance.getClass());
//通过代理对象,调用目标对象的方法。proxyInstance.teach();
proxyInstance.sayHello(" tom ");
}
}
运行结果:
Cglib
基本介绍
1.静态代理和JDK代理模式都要求目标对象是实现一个接口,但是有时候目标对象只是一个单独的对象,并没有实现任何的接口,这个时候可使用目标对象子类来实现 代理-这就是Cglib代理。
2.Cglib代理也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能扩展, 有些书也将Cglib代理归属到动态代理。
3.Cglib是一个强大的高性能的代码生成包,它可以在运行期扩展java类与实现java接 口.它广泛的被许多AOP的框架使用,例如Spring AOP,实现方法拦截。
4.在AOP编程中如何选择代理模式?
(1)目标对象**需要实现接口**,用JDK代理。
(2)目标对象**不需要实现接口**,用Cglib代理。
5.Cglib包的底层是通过使用字节码处理框架ASM来转换字节码并生成新的类。
实现步骤
1.需要引入cglib的jar文件
2.在内存中动态构建子类,注意代理的类不能为final,否则报错:java.lang.IllegalArgumentException:
3.目标对象的方法如果为final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法。
代码
还是将之前的例子改成Cglib的格式,示意图如下
1.TeacherDao.java
public class TeacherDao {
public String teach() {
System.out.println(" 老师授课中 , 我是cglib代理,不需要实现接口 ");
return "hello";
}
}
2.ProxyFactory.java
import java.lang.reflect.Method;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
public class ProxyFactory implements MethodInterceptor {
//维护一个目标对象
private Object target;
//构造器,传入一个被代理的对象
public ProxyFactory(Object target) {this.target = target;}
//返回一个代理对象: 是 target 对象的代理对象
public Object getProxyInstance() {
Enhancer enhancer = new Enhancer();//1. 创建一个工具类
enhancer.setSuperclass(target.getClass());//2. 设置父类
enhancer.setCallback(this);//3. 设置回调函数
return enhancer.create();//4. 创建子类对象,即代理对象
}
@Override//重写 intercept 方法,会调用目标对象的方法
public Object intercept(Object arg0, Method method, Object[] args, MethodProxy arg3) throws Throwable {
System.out.println("Cglib代理模式 ~~ 开始");
Object returnVal = method.invoke(target, args);
System.out.println("Cglib代理模式 ~~ 提交");
return returnVal;
}
}
3.Client.java
public class Client {
public static void main(String[] args) {
TeacherDao target = new TeacherDao();//创建目标对象
//获取到代理对象,并且将目标对象传递给代理对象
TeacherDao proxyInstance = (TeacherDao)new ProxyFactory(target).getProxyInstance();
//执行代理对象的方法,触发intecept 方法,从而实现 对目标对象的调用
String res = proxyInstance.teach();
System.out.println("res=" + res);
}
}
运行结果:
代理模式的几种变体
模板方法模式
基本介绍
1.模板方法模式(Template Method Pattern),又叫模板模式(Template Pattern),在一个抽象类公开定义了执行它的方法的模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。
2.简单说,模板方法模式 定义一个操作中的算法的骨架,而将一些步骤延迟到子类中,使得子类可以不改变一个算法的结构,就可以重定义该算法的某些特定步骤。
3.这种类型的设计模式属于行为型模式
4.示意图:
说明:
(1)AbstractClass 抽象类, 类中实现了模板方法(template),定义了算法的骨架,具体子类需要去实现其它的抽象方法operationr2,3,4。
(2)ConcreteClass 实现抽象方法operationr2,3,4, 以完成算法中特点子类的步骤。
代码
1.要求:
编写制作豆浆的程序,说明如下:
(1)制作豆浆的流程 选材--->添加配料--->浸泡--->放到豆浆机打碎
(2)通过添加不同的配料,可以制作出不同口味的豆浆
(3)选材、浸泡和放到豆浆机打碎这几个步骤对于制作每种口味的豆浆都是一样的(红豆、花生豆浆...)
2.图解,未包括钩子方法boolean customerWantCondiments()。
2.钩子方法:
(1)在模板方法模式的父类中,我们可以定义一个方法,它默认不做任何事,子类可以视情况要不要覆盖它,该方法称为“钩子”。
(2)还是用上面做豆浆的例子来讲解,比如,我们还希望制作纯豆浆,不添加任何的配料,请使用钩子方法对前面的模板方法进行改造。
注意模板方法make()是这个模板方法模式的核心!
1.SoyaMilk.java
public abstract class SoyaMilk {//抽象类,表示豆浆
//模板方法, make , 模板方法可以做成final , 不让子类去覆盖.
final void make() {
select();
if(customerWantCondiments()) {addCondiments();}
soak();
beat();
}
//选材料
void select() {System.out.println("第一步:选择好的新鲜黄豆 ");}
//添加不同的配料, 抽象方法, 子类具体实现
abstract void addCondiments();
//浸泡
void soak() {System.out.println("第三步, 黄豆和配料开始浸泡, 需要3小时 ");}
void beat() {System.out.println("第四步:黄豆和配料放到豆浆机去打碎 ");}
boolean customerWantCondiments() {return true;}//钩子方法,决定是否需要添加配料
}
2.红豆豆浆,RedBeanSoyaMilk.java
public class RedBeanSoyaMilk extends SoyaMilk {
@Override
void addCondiments() {System.out.println(" 加入上好的红豆 ");}
}
3.花生豆浆,PeanutSoyaMilk.java
public class PeanutSoyaMilk extends SoyaMilk {
@Override
void addCondiments() {System.out.println(" 加入上好的花生 ");}
}
4.纯豆浆,PureSoyaMilk
public class PureSoyaMilk extends SoyaMilk{
@Override
void addCondiments() {//空实现
}
@Override
boolean customerWantCondiments() {return false;}
}
5.Client.java
public class Client {
public static void main(String[] args) {
//制作红豆豆浆
System.out.println("----制作红豆豆浆----");
SoyaMilk redBeanSoyaMilk = new RedBeanSoyaMilk();
redBeanSoyaMilk.make();
System.out.println("----制作花生豆浆----");
SoyaMilk peanutSoyaMilk = new PeanutSoyaMilk();
peanutSoyaMilk.make();
System.out.println("----制作纯豆浆----");
SoyaMilk pureSoyaMilk = new PureSoyaMilk();
pureSoyaMilk.make();
}
}
运行结果:
模板方法模式在Spring中的应用
源码的类图:
注意事项和细节
命令模式
基本介绍
1.命令模式(Command Pattern):在软件设计中,我们经常需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是哪个,我们只需在程序运行时指定具体的请求接收者即可,此时,可以使用命令模式来进行设计。
2.命令模式使得请求发送者与请求接收者消除彼此之间的耦合,让对象之间的调用关系更加灵活,实现解耦。
3.在命令模式中,会将一个请求封装为一个对象,以便使用不同参数来表示不同的请求(即命名),同时命令模式也支持可撤销的操作。
4.通俗易懂的理解:将军发布命令,士兵去执行。其中有几个角色:将军(命令发布者)、士兵(命令的具体执行者)、命令(连接将军和士兵)。
Invoker是调用者(将军),Receiver是被调用者(士兵),MyCommand是命令,实现了Command接口,持有接收对象
原理图
说明:
1.Invoker 是调用者角色。
2.Command: 是命令角色,需要执行的所有命令都在这里,可以是接口或抽象类。
3.Receiver: 接受者角色,知道如何实施和执行一个请求相关的操作。
4.ConcreteCommand: 将一个接受者对象与一个动作绑定,调用接受者相应的操作,实现execute。
代码
需求分析
假设我们用一个统一的遥控器去控制,遥控器有10个按钮分别对应各个智能家电的开关,还有额外的一个撤销按钮,即一共11个按钮。
思路原理图:(未包含TV相关操作,TV操作是为了演示添加代码的便利性。)
1.Command.java
public interface Command {//创建命令接口
public void execute();//执行动作(操作)
public void undo();//撤销动作(操作)
}
2.LightReceiver.java
public class LightReceiver {
public void on() {System.out.println(" 电灯打开了.. ");}
public void off() {System.out.println(" 电灯关闭了.. ");}
}
3.LightOnCommand.java
public class LightOnCommand implements Command {
LightReceiver light;//聚合LightReceiver
public LightOnCommand(LightReceiver light) {//构造器
super();
this.light = light;
}
@Override
public void execute() {
light.on();//调用接收者的方法
}
@Override
public void undo() {
light.off();//调用接收者的方法
}
}
4.LightOffCommand.java
public class LightOffCommand implements Command {
LightReceiver light;// 聚合LightReceiver
public LightOffCommand(LightReceiver light) {// 构造器
super();
this.light = light;
}
@Override
public void execute() {
light.off();// 调用接收者的方法
}
@Override
public void undo() {
light.on();// 调用接收者的方法
}
}
5.NoCommand.java
/**
* 没有任何命令,即空执行: 用于初始化每个按钮, 当调用空命令时,对象什么都不做
* 其实,这样是一种设计模式, 可以省掉对空判断
*/
public class NoCommand implements Command {
@Override
public void execute() { }
@Override
public void undo() {}
}
6.RemoteController.java充当遥控器的角色,即Invoker
public class RemoteController {
Command[] onCommands;// 开 按钮的命令数组
Command[] offCommands;// 关 按钮的命令数组
Command undoCommand;// 执行撤销的命令
public RemoteController() {// 构造器,完成对按钮初始化
onCommands = new Command[5];
offCommands = new Command[5];
for (int i = 0; i < 5; i++) {
onCommands[i] = new NoCommand();
offCommands[i] = new NoCommand();
}
}
// 给我们的按钮设置你需要的命令
public void setCommand(int no, Command onCommand, Command offCommand) {
onCommands[no] = onCommand;
offCommands[no] = offCommand;
}
public void onButtonWasPushed(int no) { // 按下开按钮
onCommands[no].execute();// 找到你按下的开的按钮, 并调用对应方法
undoCommand = onCommands[no];// 记录这次的操作,用于撤销
}
public void offButtonWasPushed(int no) { // 按下关按钮
offCommands[no].execute();// 找到你按下的关的按钮, 并调用对应方法
undoCommand = offCommands[no];// 记录这次的操作,用于撤销
}
public void undoButtonWasPushed() {// 按下撤销按钮
undoCommand.undo();
}
}
7.Client.java
public class Client {
public static void main(String[] args) {
//使用命令设计模式,完成通过遥控器,对电灯的操作
//创建电灯的对象(接受者)
LightReceiver lightReceiver = new LightReceiver();
//创建电灯相关的开关命令
LightOnCommand lightOnCommand = new LightOnCommand(lightReceiver);
LightOffCommand lightOffCommand = new LightOffCommand(lightReceiver);
//需要一个遥控器
RemoteController remoteController = new RemoteController();
//给我们的遥控器设置命令, 比如 no = 0 是电灯的开和关的操作
remoteController.setCommand(0, lightOnCommand, lightOffCommand);
System.out.println("--------按下灯的开按钮-----------");
remoteController.onButtonWasPushed(0);
System.out.println("--------按下灯的关按钮-----------");
remoteController.offButtonWasPushed(0);
System.out.println("--------按下撤销按钮-----------");
remoteController.undoButtonWasPushed();
}
}
运行结果:
如果有额外的只能家电要整合进来的话也比较简单,只要写好相应的Off/OnCommand和Receiver就可以了:
1.TVReceiver.java
public class TVReceiver {
public void on() {System.out.println(" 电视机打开了.. ");}
public void off() {System.out.println(" 电视机关闭了.. ");}
}
2.TVOffCommand.java
public class TVOffCommand implements Command {
TVReceiver tv;// 聚合TVReceiver
public TVOffCommand(TVReceiver tv) {// 构造器
super();
this.tv = tv;
}
@Override
public void execute() {
tv.off();// 调用接收者的方法
}
@Override
public void undo() {
// 调用接收者的方法
tv.on();
}
}
3.TVOnCommand.java
public class TVOnCommand implements Command {
TVReceiver tv;// 聚合TVReceiver
public TVOnCommand(TVReceiver tv) {// 构造器
super();
this.tv = tv;
}
@Override
public void execute() {tv.on();}// 调用接收者的方法
@Override
public void undo() {tv.off();}// 调用接收者的方法
}
然后只需要在Clinet加入以下代码即可:
TVReceiver tvReceiver = new TVReceiver();
TVOffCommand tvOffCommand = new TVOffCommand(tvReceiver);
TVOnCommand tvOnCommand = new TVOnCommand(tvReceiver);
//给我们的遥控器设置命令, 比如 no = 1 是电视机的开和关的操作
remoteController.setCommand(1, tvOnCommand, tvOffCommand);
System.out.println("--------按下电视机的开按钮-----------");
remoteController.onButtonWasPushed(1);
System.out.println("--------按下电视机的关按钮-----------");
remoteController.offButtonWasPushed(1);
System.out.println("--------按下撤销按钮-----------");
remoteController.undoButtonWasPushed();
命令模式在JdbcTemplate中的应用
各个角色分析:
注意事项和细节
1.将发起请求的对象与执行请求的对象解耦。
发起请求的对象是调用者,调用者只要调用命令对象的execute()方法就可以让接收者工作,而不必知道具体的接收者对象是谁、是如何实现的,命令对象会负责让接收者执行请求的动作,也就是说:”请求发起者”和“请求执行者”之间的解耦是通过命令对象实现的,命令对象起到了纽带桥梁的作用
2.容易设计一个命令队列。只要把命令对象放到列队,就可以多线程的执行命令
3.容易实现对请求的撤销和重做
4.命令模式不足:可能导致某些系统有过多的具体命令类,增加了系统的复杂度,这点在在使用的时候要注意。
5.空命令也是一种设计模式,它为我们省去了判空的操作。在上面的实例中,如果没有用空命令,我们每按下一个按键都要判空,这给我们编码带来一定的麻烦。
6.命令模式经典的应用场景:界面的一个按钮都是一条命令、模拟CMD(DOS命令) 订单的撤销/恢复、触发-反馈机制
访问者模式
本部分参考访问者模式一篇就够了 - 简书 (jianshu.com),感谢JamFF。
基本介绍
最复杂的设计模式,并且使用频率不高,《设计模式》的作者评价为:大多情况下,你不需要使用访问者模式,但是一旦需要使用它时,那就真的需要使用了。
访问者模式是一种将数据操作和数据结构分离的设计模式。
使用场景
1.对象结构比较稳定,但经常需要在此对象结构上定义新的操作。
2.需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而需要避免这些操作“污染”这些对象的类,也不希望在增加新操作时修改这些类。
UML类图
角色介绍:
1.Visitor:接口或者抽象类,定义了对每个 Element 访问的行为,它的参数就是被访问的元素,它的方法个数理论上与元素的个数是一样的,因此,访问者模式要求元素的类型要稳定,如果经常添加、移除元素类,必然会导致频繁地修改 Visitor 接口,如果出现这种情况,则说明不适合使用访问者模式。
2.ConcreteVisitor:具体的访问者,它需要给出对每一个元素类访问时所产生的具体行为。
3.Element:元素接口或者抽象类,它定义了一个接受访问者(accept)的方法,其意义是指每一个元素都要可以被访问者访问。
4.ElementA,ElementB:具体的元素类,它提供接受访问的具体实现,而这个具体的实现,通常情况下是使用访问者提供的访问该元素类的方法。
5.ObjectStructure:定义当中所提到的对象结构,对象结构是一个抽象表述,它内部管理了元素集合,并且可以迭代这些元素提供访问者访问。
代码
场景
年底,CEO和CTO开始评定员工一年的工作绩效,员工分为工程师和经理,CTO关注工程师的代码量、经理的新产品数量;CEO关注的是工程师的KPI和经理的KPI以及新产品数量。由于CEO和CTO对于不同员工的**关注点是不一样的**,这就需要对不同员工类型进行**不同的处理**(即访问者不同,对对象的)。访问者模式此时可以派上用场了。
具体代码
1.Staff 类定义了员工基本信息及一个 accept 方法,accept 方法表示接受访问者的访问,由子类具体实现。Visitor 是个接口,传入不同的实现类,可访问不同的数据。下面看看工程师和经理的代码:
public abstract class Staff {// 员工基类
public String name;
public int kpi;// 员工KPI
public Staff(String name) {
this.name = name;
kpi = new Random().nextInt(10);
}
public abstract void accept(Visitor visitor);// 核心方法,接受Visitor的访问
}
2.Engineer.java,工程师类
public class Engineer extends Staff {
public Engineer(String name) {super(name);}
@Override
public void accept(Visitor visitor) {visitor.visit(this);}
public int getCodeLines() {return new Random().nextInt(10 * 10000);}// 工程师一年的代码数量
}
3.Manager.java,经理类
public class Manager extends Staff {
public Manager(String name) {super(name);}
@Override
public void accept(Visitor visitor) {visitor.visit(this);}
public int getProducts() {return new Random().nextInt(10);}// 一年做的产品数量
}
工程师是代码数量,经理是产品数量,他们的职责不一样,也就是因为<u>差异性</u>,才使得<u>访问模式</u>能够发挥它的作用。Staff、Engineer、Manager 3个类型就是**对象结构,这些类型相对稳定**,不会发生变化。
4.BusinessReport.java,员工业务报表类
然后将这些员工添加到一个业务报表类中,公司高层可以通过该报表类的 showReport 方法查看所有员工的业绩,具体代码如下
public class BusinessReport {
private List<Staff> mStaffs = new LinkedList<>();
public BusinessReport() {
mStaffs.add(new Manager("经理-A"));
mStaffs.add(new Engineer("工程师-A"));
mStaffs.add(new Engineer("工程师-B"));
mStaffs.add(new Engineer("工程师-C"));
mStaffs.add(new Manager("经理-B"));
mStaffs.add(new Engineer("工程师-D"));
}
/* 为访问者展示报表
* @param visitor 公司高层,如CEO、CTO
*/
public void showReport(Visitor visitor) {
for (Staff staff : mStaffs) {staff.accept(visitor);}
}
}
5.下面看看 Visitor 类型的定义, Visitor 声明了两个 visit 方法,分别是对工程师和经理对访问函数,具体代码如下:
public interface Visitor {
void visit(Engineer engineer);// 访问工程师类型
void visit(Manager manager);// 访问经理类型
}
首先定义了一个 Visitor 接口,该接口有两个 visit 函数,参数分别是 Engineer、Manager,也就是说对于 Engineer、Manager 的访问会调用两个不同的方法,以此达成**区别对待**、**差异化处理**。
6.CEOVisitor.java
在CEO的访问者中,CEO关注工程师的 KPI,经理的 KPI 和新产品数量,通过两个 visitor 方法**分别**进行处理。重点:**这两个visit函数名字和返回都是一致的,但是使用了参数进行区分,根据传进来的参数不同自动调用响应的方法。**
public class CEOVisitor implements Visitor {// CEO访问者
@Override
public void visit(Engineer engineer) {System.out.println("工程师: " + engineer.name + ", KPI: " + engineer.kpi);}
@Override
public void visit(Manager manager) {System.out.println("经理: " + manager.name + ", KPI: " + manager.kpi +
", 新产品数量: " + manager.getProducts());}
}
如果**不使用** Visitor 模式,只通过一个 visit 方法进行处理,那么就需要在这个 visit 方法中进行**判断**,然后分别处理,代码大致如下:
未使用访问者模式的反面教材:
public class ReportUtil {
public void visit(Staff staff) {
if (staff instanceof Manager) {
Manager manager = (Manager) staff;
System.out.println("经理: " + manager.name + ", KPI: " + manager.kpi +
", 新产品数量: " + manager.getProducts());
} else if (staff instanceof Engineer) {
Engineer engineer = (Engineer) staff;
System.out.println("工程师: " + engineer.name + ", KPI: " + engineer.kpi);
}
}
}
这就导致了 **if-else 逻辑的嵌套以及类型的强制转换,难以扩展和维护**,当类型较多时,这个 ReportUtil 就会很复杂。
7.CTOVisitor.java
public class CTOVisitor implements Visitor {
@Override
public void visit(Engineer engineer) {System.out.println("工程师: " + engineer.name + ", 代码行数: " + engineer.getCodeLines());}
@Override
public void visit(Manager manager) {System.out.println("经理: " + manager.name + ", 产品数量: " + manager.getProducts());}
}
**重载的 visit 方法会对元素进行不同的操作,而通过注入不同的 Visitor 又可以替换掉访问者的具体实现**,使得对元素的操作变得更灵活,可扩展性更高,同时也消除了类型转换、if-else 等“丑陋”的代码。
8.Client.java客户端代码
public class Client {
public static void main(String[] args) {
// 构建报表
BusinessReport report = new BusinessReport();
System.out.println("=========== CEO看报表 ===========");
report.showReport(new CEOVisitor());
System.out.println("=========== CTO看报表 ===========");
report.showReport(new CTOVisitor());
}
}
输出如下:
=========== CEO看报表 ===========
经理: 经理-A, KPI: 9, 新产品数量: 0
工程师: 工程师-A, KPI: 6
工程师: 工程师-B, KPI: 6
工程师: 工程师-C, KPI: 8
经理: 经理-B, KPI: 2, 新产品数量: 6
工程师: 工程师-D, KPI: 6
=========== CTO看报表 ===========
经理: 经理-A, 产品数量: 3
工程师: 工程师-A, 代码行数: 62558
工程师: 工程师-B, 代码行数: 92965
工程师: 工程师-C, 代码行数: 58839
经理: 经理-B, 产品数量: 6
工程师: 工程师-D, 代码行数: 53125
在上述示例中,Staff 扮演了 Element 角色,而 Engineer 和 Manager 都是 ConcreteElement;CEOVisitor 和 CTOVisitor 都是具体的 Visitor 对象;而 BusinessReport 就是 ObjectStructure;Client就是客户端代码。
访问者模式最大的优点就是**增加访问者非常容易**,我们从代码中可以看到,<u>如果要增加一个访问者,只要新实现一个 Visitor 接口的类,从而达到**数据对象与数据操作相分离**的效果</u>。如果不实用访问者模式,而又不想对不同的元素进行不同的操作,那么必定需要使用 if-else 和类型转换,这使得代码难以升级维护。
总结
我们要根据具体情况来评估是否适合使用访问者模式,例如,我们的对象结构是否**足够稳定**,是否需要经常定义新的操作,使用访问者模式是否能优化我们的代码,而不是使我们的代码变得更复杂。
优点
1.各角色职责分离,符合单一职责原则。
通过UML类图和上面的示例可以看出来,Visitor、ConcreteVisitor、Element 、ObjectStructure,职责单一,各司其责。
2.具有优秀的扩展性
如果需要增加新的访问者,增加实现类 ConcreteVisitor 就可以快速扩展。
3.使得数据结构和作用于结构上的操作解耦,使得操作集合可以独立变化
员工属性(数据结构)和CEO、CTO访问者(数据操作)的解耦。
4.灵活性
缺点
1.具体元素对访问者公布细节,违反了迪米特原则
CEO、CTO需要调用具体员工的方法。
2.具体元素变更时导致修改成本大
变更员工属性时,多个访问者都要修改。
3.违反了依赖倒置原则,为了达到“区别对待”而依赖了具体类,没有以来抽象
访问者 visit 方法中,依赖了具体员工的具体方法。
迭代器模式
在现实生活以及程序设计中,经常要访问一个聚合对象中的各个元素,如“数据结构”中的链表遍历,通常的做法是将链表的创建和遍历都放在同一个类中,但这种方式**不利于程序的扩展**,如果要更换遍历方法就必须修改程序源代码,这违背了 “开闭原则”。
既然将遍历方法封装在聚合类中不可取,那么聚合类中不提供遍历方法,将遍历方法由用户自己实现是否可行呢?答案是同样不可取,因为这种方式会存在两个缺点:
-
暴露了聚合类的内部表示,使其数据不安全;
-
增加了客户的负担。
“迭代器模式”能较好地克服以上缺点,它在客户访问类与聚合类之间插入一个**迭代器**,这分离了聚合对象与其遍历行为,对客户也**隐藏**了其**内部细节**,且满足“单一职责原则”和“开闭原则”,如 Java中的 Collection、List、Set、Map 等都包含了迭代器。
基本介绍
1.迭代器模式(Iterator Pattern)是常用的设计模式,属于行为型模式
2.如果我们的集合元素是用不同的方式实现的,有数组,还有java的集合类,或者还有其他方式,当客户端要遍历这些集合元素的时候就要使用多种遍历方式,而且还会暴露元素的内部结构,可以考虑使用迭代器模式解决。
3.迭代器模式,提供一种遍历集合元素的统一接口,用一致的方法遍历集合元素,不需要知道集合对象的底层表示,即:不暴露其内部的结构。
迭代器模式在生活中应用的比较广泛,比如:物流系统中的传送带,不管传送的是什么物品,都会被打包成一个个箱子,并且有一个统一的二维码。这样我们不需要关心箱子里是什么,在分发时只需要一个个检查发送的目的地即可。再比如,我们平时乘坐交通工具,都是统一刷卡或者刷脸进站,而不需要关心是男性还是女性、是残疾人还是正常人等信息。
UML类图
1.Iterator : 迭代器接口,是系统提供,含义 hasNext, next, remove
2.ConcreteIterator : 具体的迭代器类,管理迭代。
3.Aggregate :一个统一的聚合接口, 将客户端和具体聚合解耦
4.ConcreteAggregate:具体的聚合持有对象集合,并提供一个方法,返回一个迭代器,该迭代器可以正确遍历集合
5.Client:客户端,通过Iterator和Aggregate依赖子类
代码
场景
之前有一个案例是编写程序展示一个学校院系的结构,假设两个院里面系的组成不一样,假设计算机学院是数组,信息学院是列表,那么这个时候比较适合迭代器模式。
UML类图
具体代码
1.ComputerCollegeIterator,计算机学院的迭代器,假设计算机学院存储系的方式是数组
import java.util.Iterator;
public class ComputerCollegeIterator implements Iterator {
Department[] departments;//这里我们需要Department 是以怎样的方式存放=>数组
int position = 0; //遍历的位置
public ComputerCollegeIterator(Department[] departments) {this.departments = departments;}
@Override
public boolean hasNext() {//判断是否还有下一个元素
if(position >= departments.length || departments[position] == null) {
return false;
}else {
return true;
}
}
@Override
public Object next() {
Department department = departments[position];
position += 1;
return department;
}
public void remove() {}//删除的方法,默认空实现
}
2.InfoCollegeIterator.java,信息学院的迭代器,假设使用的是列表
import java.util.Iterator;
import java.util.List;
public class InfoCollegeIterator implements Iterator {
List<Department> departmentList; // 信息工程学院是以List方式存放系
int index = -1;//索引
public InfoCollegeIterator(List<Department> departmentList) {this.departmentList = departmentList;}
@Override
public boolean hasNext() {//判断list中还有没有下一个元素
if(index >= departmentList.size() - 1) {return false;} else {
index += 1;
return true;
}
}
@Override
public Object next() {return departmentList.get(index);}
public void remove() {}//空实现remove
}
3.College.java,:一个统一的聚合接口, 将客户端和具体聚合解耦
import java.util.Iterator;
public interface College {
public String getName();
public void addDepartment(String name, String desc);//增加系的方法
public Iterator createIterator();//返回一个迭代器,遍历
}
4.ComputerCollege.java,计算机学院
import java.util.Iterator;
public class ComputerCollege implements College {
Department[] departments;
int numOfDepartment = 0 ;// 保存当前数组的对象个数
public ComputerCollege() {
departments = new Department[5];
addDepartment("Java专业", " Java专业 ");
addDepartment("PHP专业", " PHP专业 ");
addDepartment("大数据专业", " 大数据专业 ");
}
@Override
public String getName() {return "计算机学院";}
@Override
public void addDepartment(String name, String desc) {
Department department = new Department(name, desc);
departments[numOfDepartment] = department;
numOfDepartment += 1;
}
@Override
public Iterator createIterator() {return new ComputerCollegeIterator(departments);}
}
5.InfoCollege.java,信息学院
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class InfoCollege implements College {
List<Department> departmentList;
public InfoCollege() {
departmentList = new ArrayList<Department>();
addDepartment("信息安全专业", " 信息安全专业 ");
addDepartment("网络安全专业", " 网络安全专业 ");
addDepartment("服务器安全专业", " 服务器安全专业 ");
}
@Override
public String getName() {return "信息工程学院";}
@Override
public void addDepartment(String name, String desc) {
Department department = new Department(name, desc);
departmentList.add(department);
}
@Override
public Iterator createIterator() {return new InfoColleageIterator(departmentList);}
}
6.Department.java,用于描述系的类
public class Department {
private String name;//系名
private String desc;//对系的秒数
public Department(String name, String desc) {
super();
this.name = name;
this.desc = desc;
}
public String getName() {return name;}
public void setName(String name) {this.name = name;}
public String getDesc() {return desc;}
public void setDesc(String desc) {this.desc = desc;}
}
7.OutPutImpl.java
import java.util.Iterator;
import java.util.List;
public class OutPutImpl {
List<College> collegeList;//学院集合
public OutPutImpl(List<College> collegeList) {this.collegeList = collegeList;}
//遍历所有学院,然后调用printDepartment 输出各个学院的系
public void printCollege() {
//从collegeList 取出所有学院, Java 中的 List 已经实现Iterator
Iterator<College> iterator = collegeList.iterator();
while(iterator.hasNext()) {
College college = iterator.next();//取出一个学院
System.out.println("=== "+college.getName() +"=====" );
printDepartment(college.createIterator()); //得到对应迭代器
}
}
public void printDepartment(Iterator iterator) {//输出 学院输出 系
while(iterator.hasNext()) {
Department d = (Department)iterator.next();
System.out.println(d.getName());
}
}
}
8.Client.java客户端类
import java.util.ArrayList;
import java.util.List;
public class Client {
public static void main(String[] args) {
List<College> collegeList = new ArrayList<College>();//创建学院
ComputerCollege computerCollege = new ComputerCollege();
InfoCollege infoCollege = new InfoCollege();
collegeList.add(computerCollege);
collegeList.add(infoCollege);
OutPutImpl outPutImpl = new OutPutImpl(collegeList);
outPutImpl.printCollege();
}
}
输出:
在JDK-ArrayList中的应用
注意事项和细节
优点:
1.提供一个统一的方法遍历对象,客户不用再考虑聚合的类型,使用一种方法就可以遍历对象了。
2.隐藏了聚合的内部结构,客户端要遍历聚合的时候只能取到迭代器,而不会知道聚合的具体组成。
3.提供了一种设计思想,就是一个类应该只有一个引起变化的原因(叫做单一责任原则)。在聚合类中,我们把迭代器分开,就是要把管理对象集合和遍历对象集合的责任分开,这样一来集合改变的话,只影响到聚合对象。而如果遍历方式改变的话,只影响到了迭代器。
4.当要展示一组相似对象,或者遍历一组相同对象时使用, 适合使用迭代器模式。
缺点:
每个聚合对象都要一个迭代器,会生成多个迭代器不好管理类。
观察者模式
问题引出
假设现在有一个天气预报项目需求,具体要求如下:
1.气象站可以将每天测量到的温度,湿度,气压等等以公告的形式发布出去(比如发布到自己的网站或第三方)。
2.需要设计开放型API,便于其他第三方也能接入气象站获取数据。
3.提供温度、气压和湿度的接口
4.测量数据更新时,要能实时的通知给第三方
普通方案解决
1.WeatherData.java
/**
* 类是核心
* 1. 包含最新的天气情况信息
* 2. 含有 CurrentConditions 对象
* 3. 当数据有更新时,就主动的调用 CurrentConditions对象update方法(含 display), 这样他们(接入方)就看到最新的信息
* @author Administrator
*
*/
public class WeatherData {
private float temperatrue;
private float pressure;
private float humidity;
private CurrentConditions currentConditions;//加入新的第三方
public WeatherData(CurrentConditions currentConditions) {this.currentConditions = currentConditions;}
public float getTemperature() {return temperatrue;}
public float getPressure() {return pressure;}
public float getHumidity() {return humidity;}
public void dataChange() {currentConditions.update(getTemperature(), getPressure(), getHumidity());}//调用 接入方的 update
//当数据有更新时,就调用 setData
public void setData(float temperature, float pressure, float humidity) {
this.temperatrue = temperature;
this.pressure = pressure;
this.humidity = humidity;
dataChange();//调用dataChange, 将最新的信息 推送给 接入方 currentConditions
}
}
2.CurrentConditions.java
public class CurrentConditions {//显示当前天气情况(可以理解成是气象站自己的网站)
private float temperature;// 温度
private float pressure;//气压
private float humidity;//温度
//更新 天气情况,是由 WeatherData 来调用,我使用推送模式
public void update(float temperature, float pressure, float humidity) {
this.temperature = temperature;
this.pressure = pressure;
this.humidity = humidity;
display();
}
public void display() {//显示
System.out.println("***Today mTemperature: " + temperature + "***");
System.out.println("***Today mPressure: " + pressure + "***");
System.out.println("***Today mHumidity: " + humidity + "***");
}
}
3.Client.java
public class Client {
public static void main(String[] args) {
//创建接入方 currentConditions
CurrentConditions currentConditions = new CurrentConditions();
//创建 WeatherData 并将 接入方 currentConditions 传递到 WeatherData中
WeatherData weatherData = new WeatherData(currentConditions);
//更新天气情况
weatherData.setData(30, 150, 40);
//天气情况变化
System.out.println("============天气情况变化=============");
weatherData.setData(40, 160, 20);
}
}
普通方案的问题分析:
基本介绍
原理:
使用观察者模式解决天气预报问题
类图
好处
1.观察者模式设计后,会以集合的方式来管理用户(Observer),包括注册,移除和通知。
2.这样,我们增加观察者(这里可以理解成一个新的公告板),就不需要去修改核心类WeatherData不会修改代码,遵守了ocp原则。
具体代码
1.Subject.java
public interface Subject {//接口, 让WeatherData 来实现
public void registerObserver(Observer o);
public void removeObserver(Observer o);
public void notifyObservers();
}
2.Observer.java
public interface Observer {//观察者接口,有观察者来实现
public void update(float temperature, float pressure, float humidity);
}
3.WeatherData.java
import java.util.ArrayList;
/* 类是核心
* 1. 包含最新的天气情况信息
* 2. 含有 观察者集合,使用ArrayList管理
* 3. 当数据有更新时,就主动的调用 ArrayList, 通知所有的(接入方)就看到最新的信息
*/
public class WeatherData implements Subject {
private float temperatrue;
private float pressure;
private float humidity;
private ArrayList<Observer> observers;//观察者集合
//加入新的第三方
public WeatherData() {observers = new ArrayList<Observer>();}
public float getTemperature() {return temperatrue;}
public float getPressure() {return pressure;}
public float getHumidity() {return humidity;}
public void dataChange() {notifyObservers();}//调用 接入方的 update
//当数据有更新时,就调用 setData
public void setData(float temperature, float pressure, float humidity) {
this.temperatrue = temperature;
this.pressure = pressure;
this.humidity = humidity;
//调用dataChange, 将最新的信息 推送给 接入方 currentConditions
dataChange();
}
@Override//注册一个观察者
public void registerObserver(Observer o) {observers.add(o);}
@Override
public void removeObserver(Observer o) {//移除一个观察者
if(observers.contains(o)) {observers.remove(o);}
}
//遍历所有的观察者,并通知
@Override
public void notifyObservers() {
for(int i = 0; i < observers.size(); i++) {
observers.get(i).update(this.temperatrue, this.pressure, this.humidity);
}
}
}
4.CurrentConditions.java
public class CurrentConditions implements Observer {
// 温度,气压,湿度
private float temperature;
private float pressure;
private float humidity;
// 更新 天气情况,是由 WeatherData 来调用,我使用推送模式
public void update(float temperature, float pressure, float humidity) {
this.temperature = temperature;
this.pressure = pressure;
this.humidity = humidity;
display();
}
public void display() {// 显示
System.out.println("***Today mTemperature: " + temperature + "***");
System.out.println("***Today mPressure: " + pressure + "***");
System.out.println("***Today mHumidity: " + humidity + "***");
}
}
5.BaiduSite.java
public class BaiduSite implements Observer {
private float temperature;
private float pressure;
private float humidity;
public void update(float temperature, float pressure, float humidity) {
this.temperature = temperature;
this.pressure = pressure;
this.humidity = humidity;
display();
}
public void display() {// 显示
System.out.println("===百度网站====");
System.out.println("***百度网站 气温 : " + temperature + "***");
System.out.println("***百度网站 气压: " + pressure + "***");
System.out.println("***百度网站 湿度: " + humidity + "***");
}
}
6.Client.java
public class Client {
public static void main(String[] args) {
//创建一个WeatherData
WeatherData weatherData = new WeatherData();
//创建观察者
CurrentConditions currentConditions = new CurrentConditions();
BaiduSite baiduSite = new BaiduSite();
//注册到weatherData
weatherData.registerObserver(currentConditions);
weatherData.registerObserver(baiduSite);
//测试
System.out.println("通知各个注册的观察者, 看看信息");
weatherData.setData(10f, 100f, 30.3f);
weatherData.removeObserver(currentConditions);
//测试
System.out.println();
System.out.println("通知各个注册的观察者, 看看信息");
weatherData.setData(10f, 100f, 30.3f);
}
}
输出结果:
在JDK中的应用
1.jdk的Observable类使用了观察者模式
中介者模式
问题引出
以之前的智能家具为例,如果各个设备有互相调用关系,即你中有我、我中有你的时候,这个时候耦合度就比较高。
基本介绍
1.中介者模式(Mediator Pattern),用一个中介对象来封装一系列的对象交互。中介者使各个对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。
2.中介者模式属于行为型模式,使代码易于维护。
3.比如MVC模式,C(Controller控制器)是M(Model模型)和V(View视图)的中介者,在前后端交互时起到了中间人的作用。
原理类图
代码
原理类图
具体代码
1.同事抽象类Colleague.java
public abstract class Colleague {
private Mediator mediator;
public String name;
public Colleague(Mediator mediator, String name) {
this.mediator = mediator;
this.name = name;
}
public Mediator GetMediator() {return this.mediator;}
public abstract void SendMessage(int stateChange);
}
2.中介者抽象类Mediator.java
public abstract class Mediator {
//将给中介者对象,加入到集合中
public abstract void Register(String colleagueName, Colleague colleague);
//接收消息, 具体的同事对象发出
public abstract void GetMessage(int stateChange, String colleagueName);
public abstract void SendMessage();
}
3.Alarm.java闹钟类
public class Alarm extends Colleague {//具体的同事类
public Alarm(Mediator mediator, String name) {//构造器
super(mediator, name);
//在创建Alarm 同事对象时,将自己放入到ConcreteMediator 对象中[集合]
mediator.Register(name, this);
}
public void SendAlarm(int stateChange) {SendMessage(stateChange);}
@Override
public void SendMessage(int stateChange) {
this.GetMediator().GetMessage(stateChange, this.name);//调用的中介者对象的getMessage
}
}
4.CoffeeMachine咖啡机类
public class CoffeeMachine extends Colleague {
public CoffeeMachine(Mediator mediator, String name) {
super(mediator, name);
mediator.Register(name, this);
}
@Override
public void SendMessage(int stateChange) {this.GetMediator().GetMessage(stateChange, this.name);}
public void StartCoffee() {System.out.println("It's time to startcoffee!");}
public void FinishCoffee() {
System.out.println("After 5 minutes!");
System.out.println("Coffee is ok!");
SendMessage(0);
}
}
5.Curtains.java幕布类
public class Curtains extends Colleague {
public Curtains(Mediator mediator, String name) {
super(mediator, name);
mediator.Register(name, this);
}
@Override
public void SendMessage(int stateChange) {this.GetMediator().GetMessage(stateChange, this.name);}
public void UpCurtains() {System.out.println("I am holding Up Curtains!");}
}
6.TV.java,电视类
public class TV extends Colleague {
public TV(Mediator mediator, String name) {
super(mediator, name);
mediator.Register(name, this);
}
@Override
public void SendMessage(int stateChange) {this.GetMediator().GetMessage(stateChange, this.name);}
public void StartTv() {System.out.println("It's time to StartTv!");}
public void StopTv() {System.out.println("StopTv!");}
}
7.具体中介者类ConcreteMediator.java
import java.util.HashMap;
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() {}
}
8.Client.java客户端类
public class ClientTest {
public static void main(String[] args) {
Mediator mediator = new ConcreteMediator();//创建一个中介者对象
//创建Alarm 并且加入到 ConcreteMediator 对象的HashMap
Alarm alarm = new Alarm(mediator, "alarm");
//创建了CoffeeMachine 对象,并 且加入到 ConcreteMediator 对象的HashMap
CoffeeMachine coffeeMachine = new CoffeeMachine(mediator,"coffeeMachine");
//创建 Curtains , 并 且加入到 ConcreteMediator 对象的HashMap
Curtains curtains = new Curtains(mediator, "curtains");
TV tV = new TV(mediator, "TV");
alarm.SendAlarm(0);//让闹钟发出消息
coffeeMachine.FinishCoffee();
alarm.SendAlarm(1);
}
}
运行结果:
注意事项
备忘录模式
基本介绍
1.备忘录模式(Memento Pattern)在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。
2.可以这里理解备忘录模式:
现实生活中的备忘录是用来记录某些要去做的事情,或者是记录已经达成的共同意见的事情,以防忘记了。而在软件层面,备忘录模式有着相同的含义,备忘录对象主要用来**记录一个对象的某种状态**,或者某些数据,当要做回退时,可以从备忘录对象里获取原来的数据进行恢复操作。
3.备忘录模式属于行为型模式
原理类图
说明:
1.originator : 对象(需要保存状态的对象)。
2.Memento : 备忘录对象,负责保存好记录,即Originator内部状态。
3.Caretaker: 守护者对象,负责保存多个备忘录对象, 使用集合管理,提高效率
4.说明:如果希望保存多个originator对象的不同时间的状态,也可以,只需要 HashMap <String, 集合>。
代码
问题
游戏角色有攻击力和防御力,在大战Boss前保存自身的状态(攻击力和防御力),当大战Boss后攻击力和防御力下降,从备忘录对象恢复到大战前的状态
传统解决
原理类图
具体代码
1.GameRole.java
public class GameRole {
private int vit;
private int def;
//创建Memento ,即根据当前的状态得到Memento
public Memento createMemento() {return new Memento(vit, def);}
public void recoverGameRoleFromMemento(Memento memento) {//从备忘录对象,恢复GameRole的状态
this.vit = memento.getVit();
this.def = memento.getDef();
}
public void display() {System.out.println("游戏角色当前的攻击力:" + this.vit + " 防御力: " + this.def);}//显示当前游戏角色的状态
public int getVit() {return vit;}
public void setVit(int vit) {this.vit = vit;}
public int getDef() {return def;}
public void setDef(int def) {this.def = def;}
}
2.Memento.java
public class Memento {
private int vit;//攻击力
private int def;//防御力
public Memento(int vit, int def) {
super();
this.vit = vit;
this.def = def;
}
public int getVit() {return vit;}
public void setVit(int vit) {this.vit = vit;}
public int getDef() {return def;}
public void setDef(int def) {this.def = def;}
}
3.Caretaker.java
import java.util.ArrayList;
import java.util.HashMap;
public class Caretaker {//守护者对象, 保存游戏角色的状态
//如果只保存一次状态
private Memento memento;
//对GameRole 保存多次状态
//private ArrayList<Memento> mementos;
//对多个游戏角色保存多个状态
//private HashMap<String, ArrayList<Memento>> rolesMementos;
public Memento getMemento() {return memento;}
public void setMemento(Memento memento) {this.memento = memento;}
}
4.Client.java
public class Client {
public static void main(String[] args) {
GameRole gameRole = new GameRole();//创建游戏角色
gameRole.setVit(100);
gameRole.setDef(100);
System.out.println("和boss大战前的状态");
gameRole.display();
Caretaker caretaker = new Caretaker();//把当前状态保存caretaker
caretaker.setMemento(gameRole.createMemento());
System.out.println("和boss大战~~~");
gameRole.setDef(30);
gameRole.setVit(30);
gameRole.display();
System.out.println("大战后,使用备忘录对象恢复到站前");
gameRole.recoverGameRoleFromMemento(caretaker.getMemento());
System.out.println("恢复后的状态");
gameRole.display();
}
}
注意事项
1.给用户提供了一种可以恢复状态的机制,可以使用户能够比较方便地回到某个历史的状态。
2.实现了信息的封装,使得用户不需要关心状态的保存细节。
3.如果类的成员变量过多,势必会占用比较大的资源,而且每一次保存都会消耗一定的内存, 这个需要注意。
4.适用的应用场景:1、后悔药; 2、打游戏时的存档;3、Windows 里的 ctri + z; 4、IE 中的后退;5、数据库的事务管理。
5.为了节约内存,备忘录模式可以和原型模式配合使用
解释器模式
这个模式比较难,平时开发的用的也比较少。
基本介绍
1.在编译原理中,一个算术表达式通过词法分析器形成词法单元,而后这些词法单元再通过语法分析器构建语法分析树,最终形成一棵抽象的语法分析树。这里的词法分析器和语法分析器都可以看做是解释器。
2.解释器模式(Interpreter Pattern):是指给定一个语言(表达式),定义它的文法的一种表示,并定义一个解释器,使用该解释器来解释语言中的句子(表达式)。
3.应用场景
(1)应用可以将一个需要解释执行的语言中的句子表示为一个抽象语法树
(2)一些重复出现的问题可以用一种简单的语言来表达
(3)一个简单语法需要解释的场景
4.这样的例子还有,比如编译器、运算表达式计算、正则表达式、机器人等
原理类图
说明:
1.Context: 是环境角色,含有解释器之外的全局信息。
2.AbstractExpression: 抽象表达式, 声明一个抽象的解释操作,这个方法为抽象语法树中所有的节点所共享。
3.TerminalExpression: 为终结符表达式, 实现与文法中的终结符相关的解释操作。
4.NonTermialExpression: 为非终结符表达式,为文法中的非终结符实现解释操作。
5.说明: 输入Context he TerminalExpression 信息通过Client 输入即可。
代码
应用问题
通过解释器模式来实现四则运算,如计算a+b-c的值。
原理类图
具体代码
1.Calculator.java
import java.util.HashMap;
import java.util.Stack;
public class Calculator {
// 定义表达式
private Expression expression;
// 构造函数传参,并解析
public Calculator(String expStr) { // expStr = a+b
Stack<Expression> stack = new Stack<>();// 安排运算先后顺序
// 表达式拆分成字符数组
char[] charArray = expStr.toCharArray();// [a, +, b]
Expression left = null;
Expression right = null;
//遍历我们的字符数组, 即遍历 [a, +, b]
//针对不同的情况,做处理
for (int i = 0; i < charArray.length; i++) {
switch (charArray[i]) {
case '+': //
left = stack.pop();// 从stack取出left => "a"
right = new VarExpression(String.valueOf(charArray[++i]));// 取出右表达式 "b"
stack.push(new AddExpression(left, right));// 然后根据得到left 和 right 构建 AddExpresson加入stack
break;
case '-': //
left = stack.pop();
right = new VarExpression(String.valueOf(charArray[++i]));
stack.push(new SubExpression(left, right));
break;
default:
//如果是一个 Var 就创建要给 VarExpression 对象,并push到 stack
stack.push(new VarExpression(String.valueOf(charArray[i])));
break;
}
}
this.expression = stack.pop();//当遍历完整个 charArray 数组后,stack 就得到最后Expression
}
public int run(HashMap<String, Integer> var) {
//最后将表达式a+b和 var = {a=10,b=20}
//然后传递给expression的interpreter进行解释执行
return this.expression.interpreter(var);
}
}
2.Expression.java
import java.util.HashMap;
/**
* 抽象类表达式,通过HashMap 键值对, 可以获取到变量的值
* @author Administrator
*/
public abstract class Expression {
// a + b - c
// 解释公式和数值, key 就是公式(表达式) 参数[a,b,c], value就是就是具体值
// HashMap {a=10, b=20}
public abstract int interpreter(HashMap<String, Integer> var);
}
3.VarExpression.java
import java.util.HashMap;
/**
* 变量的解释器
* @author Administrator
*/
public class VarExpression extends Expression {
private String key; // key=a,key=b,key=c
public VarExpression(String key) {this.key = key;}
// var 就是{a=10, b=20}
// interpreter 根据 变量名称,返回对应值
@Override
public int interpreter(HashMap<String, Integer> var) {return var.get(this.key);}
}
4.SymbolExpression.java
import java.util.HashMap;
/**
* 抽象运算符号解析器 这里,每个运算符号,都只和自己左右两个数字有关系,
* 但左右两个数字有可能也是一个解析的结果,无论何种类型,都是Expression类的实现类
* @author Administrator
*/
public class SymbolExpression extends Expression {
protected Expression left;
protected Expression right;
public SymbolExpression(Expression left, Expression right) {
this.left = left;
this.right = right;
}
//因为 SymbolExpression 是让其子类来实现,因此 interpreter 是一个默认实现
@Override
public int interpreter(HashMap<String, Integer> var) {return 0;}
}
5.SubExpression.java
import java.util.HashMap;
public class SubExpression extends SymbolExpression {
public SubExpression(Expression left, Expression right) {super(left, right);}
//求出left 和 right 表达式相减后的结果
public int interpreter(HashMap<String, Integer> var) {return super.left.interpreter(var) - super.right.interpreter(var);}
}
6.AddExpression.java
import java.util.HashMap;
/**
* 加法解释器
* @author Administrator
*/
public class AddExpression extends SymbolExpression {
public AddExpression(Expression left, Expression right) {super(left, right);}
//处理相加
//var 仍然是 {a=10,b=20}..
//一会我们debug 源码,就ok
public int interpreter(HashMap<String, Integer> var) {
//super.left.interpreter(var) : 返回 left 表达式对应的值 a = 10
//super.right.interpreter(var): 返回right 表达式对应值 b = 20
return super.left.interpreter(var) + super.right.interpreter(var);
}
}
7.ClientTest.java
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.HashMap;
public class ClientTest {
public static void main(String[] args) throws IOException {
String expStr = getExpStr(); // a+b
HashMap<String, Integer> var = getValue(expStr);// var {a=10, b=20}
Calculator calculator = new Calculator(expStr);
System.out.println("运算结果:" + expStr + "=" + calculator.run(var));
}
// 获得表达式
public static String getExpStr() throws IOException {
System.out.print("请输入表达式:");
return (new BufferedReader(new InputStreamReader(System.in))).readLine();
}
// 获得值映射
public static HashMap<String, Integer> getValue(String expStr) throws IOException {
HashMap<String, Integer> map = new HashMap<>();
for (char ch : expStr.toCharArray()) {
if (ch != '+' && ch != '-') {
if (!map.containsKey(String.valueOf(ch))) {
System.out.print("请输入" + String.valueOf(ch) + "的值:");
String in = (new BufferedReader(new InputStreamReader(System.in))).readLine();
map.put(String.valueOf(ch), Integer.valueOf(in));
}
}
}
return map;
}
}
运行结果:
注意事项
1.当有一个语言需要解释执行,可将该语言中的句子表示为一个抽象语法树,就可以考虑使用解释器模式,让程序具有良好的扩展性。
2.应用场景:编译器、运算表达式计算、正则表达式、机器人等
3.使用解释器可能带来的问题:解释器模式会引起类膨胀、解释器模式采用递归调用方法,将会导致调试非常复杂、效率可能降低。
状态模式
基本介绍
1.状态模式(State Pattern):它主要用来解决对象在多种状态转换时,需要对外输出不同的行为的问题。状态和行为是一一对应的,状态之间可以相互转换。
2.当一个对象的内在状态改变时,允许改变其行为,这个对象看起来像是改变了其类
原理类图
代码
问题介绍
原理类图
具体代码
1.State.java状态抽象类
public abstract class State {
public abstract void deductMoney();// 扣除积分 - 50
public abstract boolean raffle();// 是否抽中奖品
public abstract void dispensePrize();// 发放奖品
}
2.NoRaffleState.java,不能抽奖状态
public class NoRaffleState extends State {
RaffleActivity activity; // 初始化时传入活动引用,扣除积分后改变其状态
public NoRaffleState(RaffleActivity activity) {this.activity = activity;}
@Override// 当前状态可以扣积分 , 扣除后,将状态设置成可以抽奖状态
public void deductMoney() {
System.out.println("扣除50积分成功,您可以抽奖了");
activity.setState(activity.getCanRaffleState());
}
@Override// 当前状态不能抽奖
public boolean raffle() {
System.out.println("扣了积分才能抽奖喔!");
return false;
}
@Override// 当前状态不能发奖品
public void dispensePrize() {System.out.println("不能发放奖品");}
}
3.CanRaffleState.java,可以抽奖的状态
public class CanRaffleState extends State {
RaffleActivity activity;
public CanRaffleState(RaffleActivity activity) {this.activity = activity;}
@Override//已经扣除了积分,不能再扣
public void deductMoney() {System.out.println("已经扣取过了积分");}
@Override//可以抽奖, 抽完奖后,根据实际情况,改成新的状态
public boolean raffle() {
System.out.println("正在抽奖,请稍等!");
Random r = new Random();
int num = r.nextInt(10);
if(num == 0){// 10%中奖机会
activity.setState(activity.getDispenseState());//改变活动状态为发放奖品 context
return true;
}else{
System.out.println("很遗憾没有抽中奖品!");
activity.setState(activity.getNoRafflleState());// 改变状态为不能抽奖
return false;
}
}
@Override // 不能发放奖品
public void dispensePrize() {System.out.println("没中奖,不能发放奖品");}
}
4.DispenseState.java,发放奖品的状态
public class DispenseState extends State {
RaffleActivity activity; // 初始化时传入活动引用,发放奖品后改变其状态
public DispenseState(RaffleActivity activity) {this.activity = activity;}
@Override
public void deductMoney() {System.out.println("不能扣除积分");}
@Override
public boolean raffle() {
System.out.println("不能抽奖");
return false;
}
@Override
public void dispensePrize() {//发放奖品
if(activity.getCount() > 0){
System.out.println("恭喜中奖了");
activity.setState(activity.getNoRafflleState());// 改变状态为不能抽奖
}else{
System.out.println("很遗憾,奖品发送完了");
activity.setState(activity.getDispensOutState());// 改变状态为奖品发送完毕, 后面我们就不可以抽奖
}
}
}
5.DispenseOutState.java奖品发放完毕状态
/**
* 奖品发放完毕状态
* 说明,当我们activity 改变成 DispenseOutState, 抽奖活动结束
* @author Administrator
*/
public class DispenseOutState extends State {
RaffleActivity activity;// 初始化时传入活动引用
public DispenseOutState(RaffleActivity activity) {this.activity = activity;}
@Override
public void deductMoney() {System.out.println("奖品发送完了,请下次再参加");}
@Override
public boolean raffle() {
System.out.println("奖品发送完了,请下次再参加");
return false;
}
@Override
public void dispensePrize() {System.out.println("奖品发送完了,请下次再参加");}
}
6.RaffleActivity.java抽奖活动
public class RaffleActivity {
// state 表示活动当前的状态,是变化
State state = null;
// 奖品数量
int count = 0;
// 四个属性,表示四种状态
State noRafflleState = new NoRaffleState(this);
State canRaffleState = new CanRaffleState(this);
State dispenseState = new DispenseState(this);
State dispensOutState = new DispenseOutState(this);
//构造器
//1. 初始化当前的状态为 noRafflleState(即不能抽奖的状态)
//2. 初始化奖品的数量
public RaffleActivity( int count) {
this.state = getNoRafflleState();
this.count = count;
}
//扣分, 调用当前状态的 deductMoney
public void debuctMoney(){state.deductMoney();}
public void raffle(){ //抽奖
if(state.raffle()){// 如果当前的状态是抽奖成功
state.dispensePrize();//领取奖品
}
}
public State getState() {return state;}
public void setState(State state) {this.state = state;}
public int getCount() {//这里请大家注意,每领取一次奖品,count--
int curCount = count;
count--;
return curCount;
}
public void setCount(int count) {this.count = count;}
public State getNoRafflleState() {return noRafflleState;}
public void setNoRafflleState(State noRafflleState) {this.noRafflleState = noRafflleState;}
public State getCanRaffleState() {return canRaffleState;}
public void setCanRaffleState(State canRaffleState) {this.canRaffleState = canRaffleState;}
public State getDispenseState() {return dispenseState;}
public void setDispenseState(State dispenseState) {this.dispenseState = dispenseState;}
public State getDispensOutState() {return dispensOutState;}
public void setDispensOutState(State dispensOutState) {this.dispensOutState = dispensOutState;}
}
7.ClientTest.java
public class ClientTest {
public static void main(String[] args) {
RaffleActivity activity = new RaffleActivity(1);// 创建活动对象,奖品有1个奖品
// 我们连续抽30次奖
for (int i = 0; i < 30; i++) {
System.out.println("--------第" + (i + 1) + "次抽奖----------");
activity.debuctMoney();// 参加抽奖,第一步点击扣除积分
activity.raffle();// 第二步抽奖
}
}
}
结果如下:
注意事项
策略模式
基本介绍
1.策略模式(Strategy Pattern)中,定义算法族,分别封装起来,让他们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。
2.这算法体现了几个设计原则,第一、把变化的代码从不变的代码中分离出来;第二、针对接口编程而不是具体类(定义了策略接口);第三、多用组合/聚合,少用继承(客户通过组合方式使用策略)。
原理类图
代码
问题引出
现在有一个鸭子问题,有各种鸭子(比如 野鸭、北京鸭、水鸭等, 鸭子有各种行为,比如 叫、飞行等),显示鸭子的信息。比如有些鸭子交的声音很大,有的鸭子很会飞等等。
传统使用继承的方案解决这个问题的话,先新建一个Duck父类,将鸭子的所有方法都写上之后由子类去覆盖。
但是有明显的缺点就是:
1.其它鸭子,都继承了Duck类,所以fly让所有子类都会飞了,这是不正确的
2.上面说的1 的问题,其实是继承带来的问题:对类的局部改动,尤其超类的局部改动,会影响其他部分。会有溢出效应。
3.为了改进1问题,我们可以通过覆盖fly 方法来解决 => 覆盖解决
4.问题又来了,如果我们有一个玩具鸭子ToyDuck, 这样就需要ToyDuck去覆盖Duck的所有实现的方法 => 解决思路 策略模式 (strategy pattern)
具体代码
原理类图
代码
1.Duck抽象类,聚合了策略接口flyBehavior和quackBehavior。
public abstract class Duck {
FlyBehavior flyBehavior;//属性, 策略接口
QuackBehavior quackBehavior;//其它属性<->策略接口
public Duck() {}
public abstract void display();//显示鸭子信息
public void quack() {System.out.println("鸭子嘎嘎叫~~");}
public void swim() {System.out.println("鸭子会游泳~~");}
public void fly() {if(flyBehavior != null) {flyBehavior.fly();}}//改进
public void setFlyBehavior(FlyBehavior flyBehavior) {this.flyBehavior = flyBehavior;}
public void setQuackBehavior(QuackBehavior quackBehavior) {this.quackBehavior = quackBehavior;}
}
2.FlyBehavior.java,策略接口
public interface FlyBehavior {
void fly(); // 子类具体实现
}
3.GoodFlyBehavior.java
public class GoodFlyBehavior implements FlyBehavior {
@Override
public void fly() {System.out.println(" 飞翔技术高超 ~~~");}
}
4.BadFlyBehavior.java
public class BadFlyBehavior implements FlyBehavior {
@Override
public void fly() {System.out.println(" 飞翔技术一般 ");}
}
5.NoFlyBehavior.java
public class NoFlyBehavior implements FlyBehavior{
@Override
public void fly() {System.out.println(" 不会飞翔 ");}
}
6.QuackBehavior.java叫的策略接口
public interface QuackBehavior {
void quack();//子类实现
}
7.PekingDuck.java
public class PekingDuck extends Duck {
//假如北京鸭可以飞翔,但是飞翔技术一般
public PekingDuck() {flyBehavior = new BadFlyBehavior();}
@Override
public void display() {System.out.println("~~北京鸭~~~");}
}
8.ToyDuck.java
public class ToyDuck extends Duck{
public ToyDuck() {flyBehavior = new NoFlyBehavior();}
@Override
public void display() {System.out.println("玩具鸭");}
//需要重写父类的所有方法
public void quack() {System.out.println("玩具鸭不能叫~~");}
public void swim() {System.out.println("玩具鸭不会游泳~~");}
}
9.WildDuck.java
public class WildDuck extends Duck {
//构造器,传入FlyBehavor 的对象
public WildDuck() {flyBehavior = new GoodFlyBehavior();}
@Override
public void display() {System.out.println(" 这是野鸭 ");}
}
10.Client.java
public class Client {
public static void main(String[] args) {
WildDuck wildDuck = new WildDuck();
wildDuck.fly();
ToyDuck toyDuck = new ToyDuck();
toyDuck.fly();
PekingDuck pekingDuck = new PekingDuck();
pekingDuck.fly();
//动态改变某个对象的行为, 北京鸭 不能飞
pekingDuck.setFlyBehavior(new NoFlyBehavior());
System.out.println("北京鸭的实际飞翔能力");
pekingDuck.fly();
}
}
运行结果:
策略模式在JDK源码中的应用
注意事项
1.策略模式的关键是:分析项目中变化部分与不变部分
2.策略模式的核心思想是:多用组合/聚合 少用继承;用行为类组合,而不是行为的继承。更有弹性
3.体现了“对修改关闭,对扩展开放”原则,客户端增加行为不用修改原有代码,只要添加一种策略(或者行为)即可,避免了使用多重转移语句(if..else if..else)
4.提供了可以替换继承关系的办法: 策略模式将算法封装在独立的Strategy类中使得你可以独立于其Context改变它,使它易于切换、易于理解、易于扩展。
5.需要注意的是:每添加一个策略就要增加一个类,当策略过多是会导致类数目庞大
职责链模式
该部分有参考简说设计模式——职责链模式 - JAdam - 博客园 (cnblogs.com),感谢JAdam!
定义
从文字角度出发,我们可以先将关注点放在**“链**”字上,很容易联想到链式结构,举个生活中常见的例子,击鼓传花游戏就是一个很典型的链式结构,所有人形成一条链,相互传递。
而从另一个角度说,职责链就是所谓的**多级结构**,比如去医院开具病假条,普通医生只能开一天的证明,如果需要更多时常,则需将开具职责转交到上级去,上级医师只能开三天证明,如需更多时常,则需将职责转交到他的上级,以此类推,这就是一个**职责链模式**的典型应用。再比如公司请假,根据请假时常的不同,需要递交到的级别也不同,这种层级递进的关系就是一种多级结构。
**职责链模式(Chain Of Responsibility)**,使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这个对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。
原理类图
UML结构图如下:
代码
问题
传统解决方案
职责链模式
原理类图:
1.Approver.java
public abstract class Approver {
Approver approver; //下一个处理者
String name; // 名字
public Approver(String name) {this.name = name;}
//下一个处理者
public void setApprover(Approver approver) {this.approver = approver;}
//处理审批请求的方法,得到一个请求, 处理是子类完成,因此该方法做成抽象
public abstract void processRequest(PurchaseRequest purchaseRequest);
}
2.PurchaseRequest.java
public class PurchaseRequest {//请求类
private int type = 0; //请求类型
private float price = 0.0f; //请求金额
private int id = 0;
public PurchaseRequest(int type, float price, int id) {//构造器
this.type = type;
this.price = price;
this.id = id;
}
public int getType() {return type;}
public float getPrice() {return price;}
public int getId() {return id;}
}
3.DepartmentApprover.java教学主任审批
public class DepartmentApprover extends Approver {
public DepartmentApprover(String name) {super(name);}
@Override
public void processRequest(PurchaseRequest purchaseRequest) {
if(purchaseRequest.getPrice() <= 5000) {
System.out.println(" 请求编号 id= " + purchaseRequest.getId() + " 被 " + this.name + " 处理");
}else {
approver.processRequest(purchaseRequest);
}
}
}
4.CollegeApprover.java院长审批
public class CollegeApprover extends Approver {
public CollegeApprover(String name) {super(name);}
@Override
public void processRequest(PurchaseRequest purchaseRequest) {
if(purchaseRequest.getPrice() > 5000 && purchaseRequest.getPrice() <= 10000) {
System.out.println(" 请求编号 id= " + purchaseRequest.getId() + " 被 " + this.name + " 处理");
}else {
approver.processRequest(purchaseRequest);
}
}
}
5.ViceSchoolMasterApprover.java副校长审批
public class ViceSchoolMasterApprover extends Approver {
public ViceSchoolMasterApprover(String name) {super(name);}
@Override
public void processRequest(PurchaseRequest purchaseRequest) {
if(purchaseRequest.getPrice() < 10000 && purchaseRequest.getPrice() <= 30000) {
System.out.println(" 请求编号 id= " + purchaseRequest.getId() + " 被 " + this.name + " 处理");
}else {
approver.processRequest(purchaseRequest);
}
}
}
6.SchoolMasterApprover.java校长审批
public class SchoolMasterApprover extends Approver {
public SchoolMasterApprover(String name) {super(name);}
@Override
public void processRequest(PurchaseRequest purchaseRequest) {
// TODO Auto-generated method stub
if(purchaseRequest.getPrice() > 30000) {
System.out.println(" 请求编号 id= " + purchaseRequest.getId() + " 被 " + this.name + " 处理");
}else {
approver.processRequest(purchaseRequest);
}
}
}
7.Client.java
public class Client {
public static void main(String[] args) {
//创建一个请求
PurchaseRequest purchaseRequest = new PurchaseRequest(1, 31000, 1);
//创建相关的审批人
DepartmentApprover departmentApprover = new DepartmentApprover("张主任");
CollegeApprover collegeApprover = new CollegeApprover("李院长");
ViceSchoolMasterApprover viceSchoolMasterApprover = new ViceSchoolMasterApprover("王副校");
SchoolMasterApprover schoolMasterApprover = new SchoolMasterApprover("佟校长");
//需要将各个审批级别的下一个设置好 (处理人构成环形: )
departmentApprover.setApprover(collegeApprover);
collegeApprover.setApprover(viceSchoolMasterApprover);
viceSchoolMasterApprover.setApprover(schoolMasterApprover);
schoolMasterApprover.setApprover(departmentApprover);
departmentApprover.processRequest(purchaseRequest);
viceSchoolMasterApprover.processRequest(purchaseRequest);
}
}
注意细节
1.将请求和处理分开,实现解耦,提高系统的灵活性
2.简化了对象,使对象不需要知道链的结构
3.性能会受到影响,特别是在链比较长的时候,因此需控制链中最大节点数量,一般通过在Handler中设置一个最大节点数量,在setNext()方法中判断是否已经超过阀值,超过则不允许该链建立,避免出现超长链无意识地破坏系统性能
4.调试不方便。采用了类似递归的方式,调试时逻辑可能比较复杂
5.最佳应用场景:有多个对象可以处理同一个请求时,比如:多级请求、请假/加薪等审批流程、Java Web中Tomcat对Encoding的处理、拦截器