设计模式
七大原则
一、单一职责原则
二、接口隔离原则
三、依赖倒转原则
四、里氏替换原则
五、迪米特原则
六、合成复用原则
七、开闭原则(尽可能的少修改代码)
1.开闭原则(Open Closed Principle)是编程中最基础、最重要的设计原则。
2.一个软件实体如类,模块和函数应该对扩展开放(对提供方),对修改关闭(对使用方)。用抽象构建框架,用实现扩展细节。
3.当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化。
4.编程中遵循其它原则,以及使用设计模式的目的就是遵循开闭原则。
违反Ocp代码案例
package com.szh.principle.ocp;
/**
*
*/
//Shape类,基类
class Shape {
int m_type;
}
class Rectangle extends Shape {
Rectangle() {
super.m_type = 1;
}
}
class Circle extends Shape {
Circle() {
super.m_type = 2;
}
}
//这是一个用于绘图的类 [使用方]
class GraphicEditor {
//接收Shape对象,然后根据type,来绘制不同的图形
public void drawShape(Shape s) {
if (s.m_type == 1)
drawRectangle(s);
else if (s.m_type == 2)
drawCircle(s);
}
//绘制矩形
public void drawRectangle(Shape r) {
System.out.println(" 绘制矩形 ");
}
//绘制圆形
public void drawCircle(Shape r) {
System.out.println(" 绘制圆形 ");
}
}
public class Ocp {
public static void main(String[] args) {
//使用看看存在的问题
GraphicEditor graphicEditor = new GraphicEditor();
graphicEditor.drawShape(new Rectangle());
graphicEditor.drawShape(new Circle());
}
}
根据上面的代码及运行结果来看,没一点问题,我们如愿的画出了矩形、圆形。但是现在有了一个新的需求,说 要增添一个图形(三角形),使代码完成对三角形的绘制,那么我们就需要对上面的代码进行修改。
package com.szh.principle.ocp;
/**
*
*/
//Shape类,基类
class Shape {
int m_type;
}
class Rectangle extends Shape {
Rectangle() {
super.m_type = 1;
}
}
class Circle extends Shape {
Circle() {
super.m_type = 2;
}
}
//新增画三角形
class Triangle extends Shape {
Triangle() {
super.m_type = 3;
}
}
//这是一个用于绘图的类 [使用方]
class GraphicEditor {
//接收Shape对象,然后根据type,来绘制不同的图形
public void drawShape(Shape s) {
if (s.m_type == 1)
drawRectangle(s);
else if (s.m_type == 2)
drawCircle(s);
else if (s.m_type == 3)
drawTriangle(s);
}
//绘制矩形
public void drawRectangle(Shape r) {
System.out.println(" 绘制矩形 ");
}
//绘制圆形
public void drawCircle(Shape r) {
System.out.println(" 绘制圆形 ");
}
//绘制三角形
public void drawTriangle(Shape r) {
System.out.println(" 绘制三角形 ");
}
}
public class Ocp {
public static void main(String[] args) {
//使用看看存在的问题
GraphicEditor graphicEditor = new GraphicEditor();
graphicEditor.drawShape(new Rectangle());
graphicEditor.drawShape(new Circle());
graphicEditor.drawShape(new Triangle());
}
}
代码的修改完成了,也按照要求绘制出了三角形。但是大家仔细对比上面这两段代码,你会发现:第一,改动的地方偏多;第二,在使用方 GraphicEditor 类中也做了修改。 这就明显违反了开闭原则中的 对修改关闭 这个规则。
我们需要的是 对扩展开放,对修改关闭 的规则,也就是说增添一个三角形的时候,我们只需要在提供方做修改,在使用方是无需修改的。
也就是说,当我们给一个类增添新的功能时,尽量不修改代码,或者是尽量少的修改代码。
遵守Ocp代码案例
思路: 把创建Shape类做成抽象类,并提供一个抽象的draw方法,让子类去实现即可,这样我们有新的图形种类时,只需要让新的图形类继承Shape,并实现 draw方法即可,使用方的代码就不需要修 → 满足了开闭原则。
package com.szh.principle.ocp.improve;
/**
*
*/
//Shape类,基类
abstract class Shape {
int m_type;
public abstract void draw();//抽象方法
}
class Rectangle extends Shape {
Rectangle() {
super.m_type = 1;
}
@Override
public void draw() {
System.out.println(" 绘制矩形 ");
}
}
class Circle extends Shape {
Circle() {
super.m_type = 2;
}
@Override
public void draw() {
System.out.println(" 绘制圆形 ");
}
}
//这是一个用于绘图的类 [使用方]
class GraphicEditor {
//接收Shape对象,调用draw方法
public void drawShape(Shape s) {
s.draw();
}
}
public class Ocp {
public static void main(String[] args) {
GraphicEditor graphicEditor = new GraphicEditor();
graphicEditor.drawShape(new Rectangle());
graphicEditor.drawShape(new Circle());
}
}
上面是改进之后的代码,此时我们还像之前的案例一样,增添一个新的图形(三角形),并完成对三角形的绘制,那么对上面代码的修改就少之又少了。
package com.szh.principle.ocp.improve;
/**
*
*/
//Shape类,基类
abstract class Shape {
int m_type;
public abstract void draw();//抽象方法
}
class Rectangle extends Shape {
Rectangle() {
super.m_type = 1;
}
@Override
public void draw() {
System.out.println(" 绘制矩形 ");
}
}
class Circle extends Shape {
Circle() {
super.m_type = 2;
}
@Override
public void draw() {
System.out.println(" 绘制圆形 ");
}
}
//新增画三角形
class Triangle extends Shape {
Triangle() {
super.m_type = 3;
}
@Override
public void draw() {
System.out.println(" 绘制三角形 ");
}
}
//这是一个用于绘图的类 [使用方]
class GraphicEditor {
//接收Shape对象,调用draw方法
public void drawShape(Shape s) {
s.draw();
}
}
public class Ocp {
public static void main(String[] args) {
GraphicEditor graphicEditor = new GraphicEditor();
graphicEditor.drawShape(new Rectangle());
graphicEditor.drawShape(new Circle());
graphicEditor.drawShape(new Triangle());
}
}
可以看到,我们对提供方代码中新增了一个 Triangle 类,它来完成对三角形的绘制。而自始至终我们的使用方 GraphicEditor 类都没有做任何的修改。
这就自然而然的满足了开闭原则中的 对扩展开发、对修改关闭 了。
一、单例模式
1.饿汉式(静态常量)
代码演示:
/**
* 方式一:静态常量
*/
class SingTon1 {
private SingTon1() {
}
private static final SingTon1 instance = new SingTon1();
public static SingTon1 getInstance() {
return instance;
}
}
优缺点说明:
- 优点:这种写法比较简单,就是在类装载的时候就完成实例化。避免了线程同 步问题。
- 缺点:在类装载的时候就完成实例化,没有达到Lazy Loading的效果。如果从始 至终从未使用过这个实例,则会造成内存的浪费
- 这种方式基于classLoder机制避免了多线程的同步问题,不过,instance在类装载 时就实例化,在单例模式中大多数都是调用getInstance方法, 但是导致类装载 的原因有很多种,因此不能确定有其他的方式(或者其他的静态方法)导致类 装载,这时候初始化instance就没有达到lazy loading的效果
- 结论:这种单例模式可用,可能造成内存浪费
2.饿汉式(静态代码块)
代码演示:
/**
* 方式二:静态代码块
*/
class SingTon2 {
private static SingTon2 instance;
private SingTon2() {
}
static {
instance = new SingTon2();
}
public static SingTon2 getInstance() {
return instance;
}
}
优缺点说明:
- 这种方式和上面的方式其实类似,只不过将类实例化的过程放在了静态代码块 中,也是在类装载的时候,就执行静态代码块中的代码,初始化类的实例。优 缺点和上面是一样的。
- 结论:这种单例模式可用,但是可能造成内存浪费
3.懒汉式(线程不安全)
代码演示:
class Single{
private static Single instance;
private Single(){}
public static Single getInstance(){
if(instance==null){
instance=new Single();
}
return instance;
}
}
优缺点说明:
- 起到了Lazy Loading的效果,但是只能在单线程下使用。
- 如果在多线程下,一个线程进入了if (singleton == null)判断语句块,还未来得及 往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。所以 在多线程环境下不可使用这种方式
- 结论:在实际开发中,不要使用这种方式.
4.懒汉式(线程安全,同步方法)
代码演示:
class Single {
private static Single instance;
private Single() {
}
//同步方法
public static synchronized Single getInstance() {
if (instance == null) {
instance = new Single();
}
return instance;
}
}
优缺点说明:
- 解决了线程不安全问题 ‘
- 效率太低了,每个线程在想获得类的实例时候,执行getInstance()方法都要进行 同步。而其实这个方法只执行一次实例化代码就够了,后面的想获得该类实例, 直接return就行了。方法进行同步效率太低
- 结论:在实际开发中,不推荐使用这种方式
5.懒汉式(线程安全,同步代码块)
代码演示
class Single {
private static Single instance;
private Single() {
}
//同步代码块
public static Single getInstance() {
if (instance == null) {
synchronized (Single.class){
instance = new Single();
}
}
return instance;
}
}
优缺点说明:
- 这种方式,本意是想对第四种实现方式的改进,因为前面同步方法效率太低, 改为同步产生实例化的的代码块
- 但是这种同步并不能起到线程同步的作用。跟第3种实现方式遇到的情形一 致,假如一个线程进入了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;
}
}
优缺点说明:
- Double-Check概念是多线程开发中常使用到的,如代码中所示,我们进行了两 次if (singleton == null)检查,这样就可以保证线程安全了。
- 这样,实例化代码只用执行一次,后面再次访问时,判断if (singleton == null), 直接return实例化对象,也避免的反复进行方法同步.
- 线程安全;延迟加载;效率较高
- 结论:在实际开发中,推荐使用这种单例设计模式
6.静态内部类
代码演示
class SingleModeClass {
private SingleModeClass() {
}
private static class StaticInsideClass {
private static final SingleModeClass INSTANCE = new SingleModeClass();
}
public static SingleModeClass getInstance() {
return StaticInsideClass.INSTANCE;
}
}
优缺点说明:
- 这种方式采用了类装载的机制来保证初始化实例时只有一个线程。
- 静态内部类方式在Singleton类被装载时并不会立即实例化,而是在需要实例化 时,调用getInstance方法,才会装载SingletonInstance类,从而完成Singleton的 实例化。
- 类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们 保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。 (一个类内部有静态内部类和非静态内部类 , 静态内部类和非静态内部类一样,都是在被调用时才会被加载 )
- 优点:避免了线程不安全,利用静态内部类特点实现延迟加载,效率高
- 结论:推荐使用.
7.枚举
代码演示
//使用枚举,可以实现单例, 推荐
enum Singleton {
INSTANCE; //属性
public void sayOK() {
System.out.println("ok~");
}
}
优缺点说明:
- 这借助JDK1.5中添加的枚举来实现单例模式。不仅能避免多线程同步问题,而 且还能防止反序列化重新创建新的对象。
- 这种方式是Effective Java作者Josh Bloch 提倡的方式
- 结论:推荐使用
二、工厂设计模式
1.简单工厂模式
看一个具体的需求
看一个披萨的项目:要便于披萨种类的扩展,要便于维护
- 披萨的种类很多(比如 GreekPizz、CheesePizz 等)
- 披萨的制作有 prepare,bake, cut, box
- 完成披萨店订购功能。
代码演示
//将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;
}
}
public class GreekPizza extends Pizza {
@Override
public void prepare() {
// TODO Auto-generated method stub
System.out.println(" 给希腊披萨 准备原材料 ");
}
}
//简单工厂类
public class SimpleFactory {
//简单工厂模式 也叫 静态工厂模式
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;
}
}
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 "";
}
}
}
//相当于一个客户端,发出订购
public class PizzaStore {
public static void main(String[] args) {
new OrderPizza2();
/*
* 买家 -----> 订单 ------> 工厂 -----> 商品
买家首先先下单 然后形成订单 然后工厂根据订单的需求制作相应的商品
*
* */
}
}
- 简单工厂模式是属于创建型模式,是工厂模式的一种。简单工厂模式是由一 个工厂对象决定创建出哪一种产品类的实例。简单工厂模式是工厂模式家族 中最简单实用的模式
- 简单工厂模式:定义了一个创建对象的类,由这个类来封装实例化对象的行 为(代码)
- 在软件开发中,当我们会用到大量的创建某种、某类或者某批对象时,就会 使用到工厂模式.
2.工厂方法模式
看一个新的需求
披萨项目新的需求:客户在点披萨时,可以点不同口味的披萨,比如 北京的奶酪pizza、 北京的胡椒pizza 或者是伦敦的奶酪pizza、伦敦的胡椒pizza。
思路1
使用简单工厂模式,创建不同的简单工厂类,比如BJPizzaSimpleFactory、 LDPizzaSimpleFactory 等等.从当前这个案例来说,也是可以的,但是考虑到项目的规模,以及软件的可维护性、可扩展性并不是特别好
思路2
使用工厂方法模式
工厂方法模式设计方案:将披萨项目的实例化功能抽象成抽象方法,在不同的口味点 餐子类中具体实现。
工厂方法模式:定义了一个创建对象的抽象方法,由子类决定要实例化的类。工厂方法模式将对象的实例化推迟到子类。
代码演示
public class BJCheesePizza extends Pizza {
@Override
public void prepare() {
setName("北京奶酪披萨");
System.out.println("北京奶酪披萨准备原材料");
}
}
public class BJPepperPizza extends Pizza {
@Override
public void prepare() {
setName("北京胡椒披萨");
System.out.println("北京胡椒披萨准备原材料");
}
}
public class LDCheesePizza extends Pizza {
@Override
public void prepare() {
setName("伦敦奶酪披萨");
System.out.println("伦敦奶酪披萨准备原材料");
}
}
public class LDPepperPizza extends Pizza {
@Override
public void prepare() {
setName("伦敦胡椒披萨");
System.out.println("伦敦胡椒披萨准备原材料");
}
}
//订单抽象类 供子类继承使用 (不使用工厂)
public abstract class OrderPizza {
Pizza pizza = null;
public abstract Pizza createPizza(String orderType);
public OrderPizza() {
String orderType = "";
do {
orderType = getType();
pizza = createPizza(orderType);
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 种类(pepper/cheese):");
String str = strin.readLine();
return str;
} catch (IOException e) {
e.printStackTrace();
return "";
}
}
}
//伦敦披萨订单
public class LDOrderPizza extends OrderPizza {
@Override
public Pizza createPizza(String orderType) {
Pizza pizza = null;
if ("cheese".equals(orderType)) {
pizza = new LDCheesePizza();
} else if ("pepper".equals(orderType)) {
pizza = new LDPepperPizza();
}
return pizza;
}
//子类会默认调用父类无参的构造器
}
//北京披萨订单
public class BJOrderPizza extends OrderPizza {
@Override
public Pizza createPizza(String orderType) {
if ("cheese".equals(orderType)) {
pizza = new BJCheesePizza();
} else if ("pepper".equals(orderType)) {
pizza = new BJPepperPizza();
}
return pizza;
}
//子类会默认调用父类无参的构造器
}
public class PizzaStore {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入披萨的地区(bj/ld):");
String input = scanner.next();
if (input.equals("bj")) {
//创建北京口味的各种Pizza
new BJOrderPizza();
} else {
//创建伦敦口味的各种Pizza
new LDOrderPizza();
}
}
}
3.抽象工厂模式
基本介绍
- 抽象工厂模式:定义了一个interface用于创建相关或有依赖关系的对象簇,而无需 指明具体的类
- 抽象工厂模式可以将简单工厂模式和工厂方法模式进行整合。
- 从设计层面看,抽象工厂模式就是对简单工厂模式的改进(或者称为进一步的抽象)。
- 将工厂抽象成两层,AbsFactory(抽象工厂) 和 具体实现的工厂子类。程序员可以 根据创建对象类型使用对应的工厂子类。这样将单个的简单工厂类变成了工厂簇, 更利于代码的维护和扩展。
public interface AbsFactory { //依赖倒转原则
Pizza createPizza(String orderType);
}
public class BJFactory implements AbsFactory {
@Override
public Pizza createPizza(String orderType) {
Pizza pizza = null;
if ("cheese".equals(orderType)) {
pizza = new BJCheesePizza();
} else if ("pepper".equals(orderType)) {
pizza = new BJPepperPizza();
}
return pizza;
}
}
public class LDFactory implements AbsFactory {
@Override
public Pizza createPizza(String orderType) {
Pizza pizza = null;
if ("cheese".equals(orderType)) {
pizza = new LDCheesePizza();
} else if ("pepper".equals(orderType)) {
pizza = new LDPepperPizza();
}
return pizza;
}
}
public class LDCheesePizza extends Pizza {
@Override
public void prepare() {
setName("伦敦奶酪披萨");
System.out.println("伦敦奶酪披萨准备原材料");
}
}
public class BJCheesePizza extends Pizza {
@Override
public void prepare() {
setName("北京奶酪披萨");
System.out.println("北京奶酪披萨准备原材料");
}
}
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();
pizza = factory.createPizza(orderType);
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 "";
}
}
}
public class PizzaStore {
public static void main(String[] args) {
new OrderPizza(new BJFactory());
}
}
工厂模式小结
- 工厂模式的意义 将实例化对象的代码提取出来,放到一个类中统一管理和维护,达到和主项目的 依赖关系的解耦。从而提高项目的扩展和维护性。
- 三种工厂模式 (简单工厂模式、工厂方法模式、抽象工厂模式)
- 设计模式的依赖抽象原则
- 创建对象实例时,不要直接 new 类, 而是把这个new 类的动作放在一个工厂的方法 中,并返回。有的书上说,变量不要直接持有具体类的引用。
- 不要让类继承具体类,而是继承抽象类或者是实现interface(接口)
- 不要覆盖基类中已经实现的方法。
三、原型模式
克隆羊问题
现在有一只羊tom,姓名为: tom, 年龄为:1,颜色为:白色,请编写程序创建和tom 羊 属性完全相同的10只羊。
基本介绍
- 原型模式(Prototype模式)是指:用原型实例指定创建对象的种类,并且通过拷 贝这些原型,创建新的对象
- 原型模式是一种创建型设计模式,允许一个对象再创建另外一个可定制的对象, 无需知道如何创建的细节
- 工作原理是:通过将一个原型对象传给那个要发动创建的对象,这个要发动创建 的对象通过请求原型对象拷贝它们自己来实施创建,即 对象.clone()
- 形象的理解:孙大圣拔出猴毛, 变出其它孙大圣
代码演示
public class Sheep implements Cloneable {
String name;
int age;
String color;
Sheep sheepFriend;
public Sheep(String name, int age, String color) {
this.name = name;
this.age = age;
this.color = color;
}
@Override
public String toString() {
return "Sheep{" +
"name='" + name + '\'' +
", age=" + age +
", color='" + color + '\'' +
", sheepFriend=" + sheepFriend +
'}';
}
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;
}
//克隆该实例,使用默认的clone方法来完成
@Override
protected Object clone() {
Sheep sheep = null;
try {
sheep = (Sheep) super.clone();
} catch (Exception e) {
// TODO: handle exception
System.out.println(e.getMessage());
}
// TODO Auto-generated method stub
return sheep;
}
}
public class Client {
public static void main(String[] args) throws Exception {
System.out.println("原型模式完成对象的创建");
// TODO Auto-generated method stub
Sheep sheep = new Sheep("tom", 1, "白色");
sheep.sheepFriend = 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.sheepFriend.hashCode());
System.out.println("sheep3 =" + sheep3 + "sheep3.friend=" + sheep3.sheepFriend.hashCode());
System.out.println("sheep4 =" + sheep4 + "sheep4.friend=" + sheep4.sheepFriend.hashCode());
System.out.println("sheep5 =" + sheep5 + "sheep5.friend=" + sheep5.sheepFriend.hashCode());
}
}
四、建造者模式
盖房项目需求
- 需要建房子:这一过程为打桩、砌墙、封顶
- 房子有各种各样的,比如普通房,高楼,别墅,各种房子的过程虽然一样,但是 要求不要相同的.
- 请编写程序,完成需求
基本介绍
- 建造者模式(Builder Pattern) 又叫生成器模式,是一种对象构建模式。它可以 将复杂对象的建造过程抽象出来(抽象类别),使这个抽象过程的不同实现方 法可以构造出不同表现(属性)的对象。
- 建造者模式 是一步一步创建一个复杂的对象,它允许用户只通过指定复杂对象 的类型和内容就可以构建它们,用户不需要知道内部的具体构建细节。
建造者模式的四个角色
- Product(产品角色): 一个具体的产品对象。
- Builder(抽象建造者): 创建一个Product对象的各个部件指定的 接口/抽象类。
- ConcreteBuilder(具体建造者): 实现接口,构建和装配各个部件。
- Director(指挥者): 构建一个使用Builder接口的对象。它主要是用于创建一个 复杂的对象。它主要有两个作用,一是:隔离了客户与对象的生产过程,二是: 负责控制产品对象的生产过程。
代码演示
public class House {
private String base;
private String wall;
private String roofed;
public String getBase() {
return base;
}
public void setBase(String base) {
this.base = base;
}
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;
}
@Override
public String toString() {
return "House{" +
"base='" + base + '\'' +
", wall='" + wall + '\'' +
", roofed='" + roofed + '\'' +
'}';
}
}
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;
}
}
public class CommonHouse extends HouseBuilder {
@Override
public void buildBasic() {
house.setBase("5米");
System.out.println(" 普通房子打地基5米 ");
}
@Override
public void buildWalls() {
house.setWall("10cm");
System.out.println(" 普通房子砌墙10cm ");
}
@Override
public void roofed() {
house.setRoofed("50cm");
System.out.println(" 普通房子屋顶50cm ");
}
}
public class HouseDirector {
private HouseBuilder builder;
//构造器传入 houseBuilder
public HouseDirector(HouseBuilder houseBuilder) {
this.builder = houseBuilder;
}
//通过setter 传入 houseBuilder
public void setHouseBuilder(HouseBuilder houseBuilder) {
this.builder = houseBuilder;
}
//如何处理建造房子的流程,交给指挥者
public House constructHouse() {
builder.buildBasic();
builder.buildWalls();
builder.roofed();
return builder.buildHouse();
}
}
public class Client {
public static void main(String[] args) {
//盖普通房子
CommonHouse commonHouse = new CommonHouse();
//准备创建房子的指挥者
HouseDirector houseDirector = new HouseDirector(commonHouse);
//完成盖房子,返回产品(普通房子)
House house = houseDirector.constructHouse();
System.out.println(house);
}
}
建造者模式的注意事项和细节
五、适配器模式
基本介绍
- 适配器模式(Adapter Pattern)将某个类的接口转换成客户端期望的另一个接口表 示,主的目的是兼容性,让原本因接口不匹配不能一起工作的两个类可以协同 工作。其别名为包装器(Wrapper)
- 适配器模式属于结构型模式
- 主要分为三类:类适配器模式、对象适配器模式、接口适配器模式
工作原理
- 适配器模式:将一个类的接口转换成另一种接口.让原本接口不兼容的类可以兼 容
- 从用户的角度看不到被适配者,是解耦的
- 用户调用适配器转化出来的目标接口方法,适配器再调用被适配者的相关接口 方法
- 用户收到反馈结果,感觉只是和目标接口交互,如图
类适配器模式介绍
Adapter类,通过继承 src类,实现 dst 类接口,完成src->dst的适配。
以生活中充电器的例子来讲解适配器,充电器本身相当于Adapter,220V交流电 相当于src (即被适配者),我们的目dst(即 目标)是5V直流电
代码实现
public interface Voltage5V {
public int outPut5V();
}
public class Voltage220V {
//输出220V的电压
public int outPut220() {
int src = 220;
System.out.println("电压" + src + "V");
return src;
}
}
public class VoltageAdapter extends Voltage220V implements Voltage5V {
@Override
public int outPut5V() {
//获取当220V电压
int srcV = outPut220();
int dstV = srcV / 44; //转成5v
return dstV;
}
}
类适配器模式注意事项和细节
- Java是单继承机制,所以类适配器需要继承src类这一点算是一个缺点, 因为这要 求dst必须是接口,有一定局限性;
- src类的方法在Adapter中都会暴露出来,也增加了使用的成本。
- 由于其继承了src类,所以它可以根据需求重写src类的方法,使得Adapter的灵 活性增强了。
对象适配器模式介绍
- 基本思路和类的适配器模式相同,只是将Adapter类作修改,不是继承src类,而 是持有src类的实例,以解决兼容性的问题。 即:持有 src类,实现 dst 类接口, 完成src->dst的适配
- 根据“合成复用原则”,在系统中尽量使用关联关系来替代继承关系。
- 对象适配器模式是适配器模式常用的一种
对象适配器模式应用实例
- 应用实例说明
以生活中充电器的例子来讲解适配器,充电器本身相当于Adapter,220V交流电 相当于src (即被适配者),我们的目dst(即目标)是5V直流电,使用对象适配器模 式完成。
-
思路分析(类图):只需修改适配器即可, 如下:
public class VoltageAdapter2 implements Voltage5 { private Voltage220 voltage220; //持有Voltage220对象,不是继承了 }
代码实现
public class Phone {
public void charging(Voltage5V voltage5V) {
if (voltage5V.outPut5V() == 5) {
System.out.println("可以充电!");
} else {
System.out.println("电压不对,不能充电!");
}
}
}
public interface Voltage5V {
public int outPut5V();
}
public class Voltage220V {
//输出220V的电压
public int outPut220() {
int src = 220;
System.out.println("电压" + src + "V");
return src;
}
}
public class VoltageAdapter implements Voltage5V {
private Voltage220V voltage220V;
public VoltageAdapter(Voltage220V voltage220V) {
this.voltage220V = voltage220V;
}
@Override
public int outPut5V() {
int dstV = 0;
//获取当220V电压
if (voltage220V != null) {
int srcV = voltage220V.outPut220();
System.out.println("使用对象适配器,进行适配~~");
dstV = srcV / 44; //转成5v
System.out.println("适配完成,输出的电压为=" + dstV);
}
return dstV;
}
}
public class Client {
public static void main(String[] args) {
Phone phone = new Phone();
phone.charging(new VoltageAdapter(new Voltage220V()));
}
}
对象适配器模式注意事项和细节
- 对象适配器和类适配器其实算是同一种思想,只不过实现方式不同。 根据合成复用原则,使用组合替代继承, 所以它解决了类适配器必须继承src的 局限性问题,也不再要求dst必须是接口。
- 使用成本更低,更灵活。
六、桥接模式(解决两个维度)
- 桥接模式(Bridge模式)是指:将实现与抽象放在两个不同的类层次中,使两个层 次可以独立改变。
- 是一种结构型设计模式
- Bridge模式基于类的最小设计原则,通过使用封装、聚合及继承等行为让不同 的类承担不同的职责。它的主要特点是把抽象(Abstraction)与行为实现 (Implementation)分离开来,从而可以保持各部分的独立性以及应对他们的功能 扩展
桥接模式解决手机操作问题
代码实现
public interface Brand {
void open();
void close();
void call();
}
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("小米手机打电话");
}
}
public abstract class Phone {
private Brand brand;
public Phone(Brand brand) {
this.brand = brand;
}
public void open() {
brand.open();
}
public void close() {
brand.close();
}
public void call() {
brand.call();
}
}
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(" 折叠样式手机 ");
}
}
/**
* 扩展
*/
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(" 直立样式手机 ");
}
}
public class Client {
public static void main(String[] args) {
Phone phone1 = new FoldedPhone(new XiaoMi());
phone1.open();
phone1.close();
phone1.call();
System.out.println("----------------------------------------");
//扩展
Phone phone2 = new UpRightPhone(new Vivo());
phone2.open();
phone2.close();
phone2.call();
}
}
桥接模式的注意事项和细节
- 实现了抽象和实现部分的分离,从而极大的提供了系统的灵活性,让抽象部分和实 现部分独立开来,这有助于系统进行分层设计,从而产生更好的结构化系统。
- 对于系统的高层部分,只需要知道抽象部分和实现部分的接口就可以了,其它的部 分由具体业务来完成。
- 桥接模式替代多层继承方案,可以减少子类的个数,降低系统的管理和维护成本。
- 桥接模式的引入增加了系统的理解和设计难度,由于聚合关联关系建立在抽象层, 要求开发者针对抽象进行设计和编程
- 桥接模式要求正确识别出系统中两个独立变化的维度,因此其使用范围有一定的局 限性,即需要有这样的应用场景。
桥接模式其它应用场景
- 对于那些不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统,桥接模式尤为适用.
- 一个类存在两个独立变化的维度,且这两个维度都需要进行扩展。
- 如果一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性,避免在两个层次之间建立静态的继承联系,通过桥接模式可以使它们在抽象层建立一个关联关系。
- 常见的应用场景:
- JDBC驱动程序
- 银行转账系统 转账分类: 网上转账,柜台转账,AMT转账
- 转账用户类型:普通用户,银卡用户,金卡用户…
- 消息管理 消息类型:即时消息,延时消息
- 消息分类:手机短信,邮件消息,QQ消息…
七、装饰者模式(不好理解但很强)
星巴克咖啡订单项目
- 咖啡种类/单品咖啡:Espresso(意大利浓咖啡)、ShortBlack、LongBlack(美式 咖啡)、Decaf(无因咖啡)
- 调料:Milk、Soy(豆浆)、Chocolate 3)
- 要求在扩展新的咖啡种类时,具有良好的扩展性、改动方便、维护方便
- 使用OO的来计算不同种类咖啡的费用: 客户可以点单品咖啡,也可以单品咖 啡+调料组合
装饰者模式定义
- 装饰者模式:动态的将新功能附加到对象上。在对象功能扩展方面,它比继承更 有弹性,装饰者模式也体现了开闭原则(ocp)
- 这里提到的动态的将新功能附加到对象和ocp原则,在后面的应用实例上会以代 码的形式体现,请同学们注意体会。
代码演示
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();
}
public class Decorator extends Drink {
public Drink obj;
public Decorator(Drink obj) {
this.obj = obj;
}
@Override
public float cost() {
// getPrice 调料价格
return super.getPrice() + obj.cost();
}
@Override
public String getDes() {
// obj.getDes() 输出被装饰者的信息
return des + " " + getPrice() + " && " + obj.getDes(); //套娃 obj=Milk obj=LongBlack
}
}
public class Coffee extends Drink {
@Override
public float cost() {
return super.getPrice();
}
}
public class Chocolate extends Decorator {
public Chocolate(Drink obj) {
super(obj);
setDes("巧克力");
setPrice(2.0f);
}
}
public class Milk extends Decorator {
public Milk(Drink obj) {
super(obj);
setDes("牛奶");
setPrice(3.0f);
}
}
//美式咖啡
public class LongBlack extends Coffee {
public LongBlack() {
setDes(" longBack ");
setPrice(5.0f);
}
}
//浓缩咖啡
public class ShortBlack extends Coffee {
public ShortBlack() {
setDes("shortBlack");
setPrice(6.0f);
}
}
public class Client {
public static void main(String[] args) {
Drink order = new LongBlack(); //美式咖啡
order = new Milk(order); //美式咖啡+牛奶
order = new Chocolate(order); //美式咖啡+牛奶+巧克力
float cost = order.cost();
System.out.println(cost);
System.out.println(order.getDes());
}
}
总结
- 优点:
- 装饰器是继承的有力补充,比继承灵活,在不改变原有对象的情况下,动态的给一个对象扩展功能,即插即用;
- 通过使用不用装饰类及这些装饰类的排列组合,可以实现不同效果;
- 装饰器模式完全遵守开闭原则。
缺点:
装饰器模式会增加许多子类,过度使用会增加程序得复杂性。
八、组合模式
基本介绍
- 组合模式(Composite Pattern),又叫部分整体模式,它创建了对象组的树形结 构,将对象组合成树状结构以表示“整体-部分”的层次关系。
- 组合模式依据树形结构来组合对象,用来表示部分以及整体层次。
- 这种类型的设计模式属于结构型模式。
- 组合模式使得用户对单个对象和组合对象的访问具有一致性,即:组合能让客 户以一致的方式处理个别对象以及组合对象
解决的问题
- 组合模式解决这样的问题,当我们的要处理的对象可以生成一颗树形结构,而 我们要对树上的节点和叶子进行操作时,它能够提供一致的方式,而不用考虑 它是节点还是叶子
- 对应的示意图
3.应用实例要求
编写程序展示一个学校院系结构:需求是这样,要在一个页面中展示出学校的院系组 成,一个学校有多个学院,一个学院有多个系。
代码演示
public abstract class OrganizationComponent {
public String name;
public String des;
public void add(OrganizationComponent organizationComponent) {
} //默认实现
public void remove(OrganizationComponent organizationComponent) {
} //默认实现
public abstract void print();
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;
}
public OrganizationComponent(String name, String des) {
this.name = name;
this.des = des;
}
}
public class University extends OrganizationComponent {
List<OrganizationComponent> organizationComponents = new ArrayList<>();
public University(String name, String des) {
super(name, des);
}
@Override
public void print() {
System.out.println("----------------" + getName() + "---------------");
//遍历 organizationComponents
for (OrganizationComponent organizationComponent : organizationComponents) {
organizationComponent.print();
}
}
public void add(OrganizationComponent organizationComponent) {
organizationComponents.add(organizationComponent);
}
public void remove(OrganizationComponent organizationComponent) {
organizationComponents.remove(organizationComponent);
}
public class College extends OrganizationComponent {
List<OrganizationComponent> organizationComponents = new ArrayList<>();
@Override
public void add(OrganizationComponent organizationComponent) {
organizationComponents.add(organizationComponent);
}
@Override
public void remove(OrganizationComponent organizationComponent) {
organizationComponents.remove(organizationComponent);
}
@Override
public void print() {
System.out.println("----------------" + getName() + "---------------");
//遍历 organizationComponents
for (OrganizationComponent organizationComponent : organizationComponents) {
organizationComponent.print();
}
}
public College(String name, String des) {
super(name, des);
}
}
public class Department extends OrganizationComponent {
public Department(String name, String des) {
super(name, des);
}
@Override
public void print() {
System.out.println(getName());
}
}
public class Client {
public static void main(String[] args) {
OrganizationComponent university = new University("广州理工学院", "野鸡大学");
College college1 = new College("计算机科学与工程学院", "第一学院");
College college2 = new College("外国语学院", "学外语的地方");
college1.add(new Department("计算机科学与技术", "不好学"));
college1.add(new Department("数字媒体艺术", "还不错"));
college2.add(new Department("英语", "大众外语"));
college2.add(new Department("日语", "小日子过的不错的外语"));
university.add(college1);
university.add(college2);
university.print();
}
}
组合模式的注意事项和细节
- 简化客户端操作。客户端只需要面对一致的对象而不用考虑整体部分或者节点叶子 的问题。
- 具有较强的扩展性。当我们要更改组合对象时,我们只需要调整内部的层次关系, 客户端不用做出任何改动.
- 方便创建出复杂的层次结构。客户端不用理会组合里面的组成细节,容易添加节点 或者叶子从而创建出复杂的树形结构
- 需要遍历组织机构,或者处理的对象具有树形结构时, 非常适合使用组合模式.
- 要求较高的抽象性,如果节点和叶子有很多差异性的话,比如很多方法和属性 都不一样,不适合使用组合模式
九、外观模式(统一接口)
基本介绍
-
外观模式(Facade),也叫“过程模式:外观模式为子系统中的一组接口提供 一个一致的界面,此模式定义了一个高层接口,这个接口使得这一子系统更加 容易使用
-
外观模式通过定义一个一致的接口,用以屏蔽内部子系统的细节,使得调用端只需跟这个接口发生调用,而无需关心这个子系统的内部细节
代码实现
public class HomeTheaterFacade {
private TheaterLight theaterLight;
private Popcorn popcorn;
private Stereo stereo;
private Projector projector;
private Screen screen;
private DVDPlayer dVDPlayer;
public HomeTheaterFacade() {
super();
this.theaterLight = TheaterLight.getInstance();
this.popcorn = Popcorn.getInstance();
this.stereo = Stereo.getInstance();
this.projector = Projector.getInstance();
this.screen = Screen.getInstance();
this.dVDPlayer = DVDPlayer.getInstance();
}
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();
}
}
public class Client {
public static void main(String[] args) {
HomeTheaterFacade homeTheaterFacade = new HomeTheaterFacade();
homeTheaterFacade.ready();
homeTheaterFacade.play();
homeTheaterFacade.end();
}
}
外观模式的注意事项和细节
- 外观模式对外屏蔽了子系统的细节,因此外观模式降低了客户端对子系统使用的复 杂性
- 外观模式对客户端与子系统的耦合关系,让子系统内部的模块更易维护和扩展
- 通过合理的使用外观模式,可以帮我们更好的划分访问的层次
- 当系统需要进行分层设计时,可以考虑使用Facade模式
- 在维护一个遗留的大型系统时,可能这个系统已经变得非常难以维护和扩展,此时 可以考虑为新系统开发一个Facade类,来提供遗留系统的比较清晰简单的接口, 让新系统与Facade类交互,提高复用性
- 不能过多的或者不合理的使用外观模式,使用外观模式好,还是直接调用模块好。 要以让系统有层次,利于维护为目的。
十、享元模式
展示网站项目需求
小型的外包项目,给客户A做一个产品展示网站,客户A的朋友感觉效果不错,也希 望做这样的产品展示网站,但是要求都有些不同:
- 有客户要求以新闻的形式发布
- 有客户人要求以博客的形式发布
- 有客户希望以微信公众号的形式发布
享元模式基本介绍
- 享元模式(Flyweight Pattern) 也叫 蝇量模式: 运 用共享技术有效地支持大量细粒度的对象
- 常用于系统底层开发,解决系统的性能问题。像 数据库连接池,里面都是创建好的连接对象,在这些连接对象中有我们需要的则直接拿来用,避免重新创建,如果没有我们需要的,则创建一个
- 享元模式能够解决重复对象的内存浪费的问题, 当系统中有大量相似对象,需要缓冲池时。不需 总是创建新对象,可以从缓冲池里拿。这样可以 降低系统内存,同时提高效率
- 享元模式经典的应用场景就是池技术了,String常 量池、数据库连接池、缓冲池等等都是享元模式 的应用,享元模式是池技术的重要实现方式
内部状态和外部状态
比如围棋、五子棋、跳棋,它们都有大量的棋子对象,围棋和五子棋只有黑白两色,跳棋颜色多一 点,所以棋子颜色就是棋子的内部状态;而各个棋子之间的差别就是位置的不同,当我们落子后, 落子颜色是定的,但位置是变化的,所以棋子坐标就是棋子的外部状态
- 享元模式提出了两个要求:细粒度和共享对象。这里就涉及到内部状态和外部状态 了,即将对象的信息分为两个部分:内部状态和外部状态
- 内部状态指对象共享出来的信息,存储在享元对象内部且不会随环境的改变而改变
- 外部状态指对象得以依赖的一个标记,是随环境改变而改变的、不可共享的状态。
- 举个例子:围棋理论上有361个空位可以放棋子,每盘棋都有可能有两三百个棋子对 象产生,因为内存空间有限,一台服务器很难支持更多的玩家玩围棋游戏,如果用 享元模式来处理棋子,那么棋子对象就可以减少到只有两个实例,这样就很好的解 决了对象的开销问题
public abstract class WebSite {
public abstract void use(User user);
}
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());
}
}
public class User {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public User(String name) {
this.name = name;
}
}
// 网站工厂类,根据需要返回一个网站
public class WebSiteFactory {
//集合, 充当池的作用
private Map<String, ConcreteWebSite> pool = new HashMap<>();
//根据网站的类型,返回一个网站, 如果没有就创建一个网站,并放入到池中,并返回
public WebSite getWebSiteCategory(String type) {
if (!pool.containsKey(type)) {
//就创建一个网站,并放入到池中
pool.put(type, new ConcreteWebSite(type));
}
return pool.get(type);
}
//获取网站分类的总数 (池中有多少个网站类型)
public int getWebSiteCount() {
return pool.size();
}
}
public class Client {
public static void main(String[] args) {
// 创建一个工厂类
WebSiteFactory factory = new WebSiteFactory();
WebSite webSite = factory.getWebSiteCategory("新闻");
webSite.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());
}
}
享元模式的注意事项和细节
- 在享元模式这样理解,“享”就表示共享,“元”表示对象
- 系统中有大量对象,这些对象消耗大量内存,并且对象的状态大部分可以外部化时, 我们就可以考虑选用享元模式
- 用唯一标识码判断,如果在内存中有,则返回这个唯一标识码所标识的对象,用 HashMap/HashTable存储
- 享元模式大大减少了对象的创建,降低了程序内存的占用,提高效率
- 享元模式提高了系统的复杂度。需要分离出内部状态和外部状态,而外部状态具有 固化特性,不应该随着内部状态的改变而改变,这是我们使用享元模式需要注意的 地方.
- 使用享元模式时,注意划分内部状态和外部状态,并且需要有一个工厂类加以控制。
- 享元模式经典的应用场景是需要缓冲池的场景,比如 String常量池、数据库连接池
十一、代理模式
代理模式的基本介绍
- 代理模式:为一个对象提供一个替身,以控制对这个对象的访问。即通过代理 对象访问目标对象.这样做的好处是:可以在目标对象实现的基础上,增强额外的 功能操作,即扩展目标对象的功能。
- 被代理的对象可以是远程对象、创建开销大的对象或需要安全控制的对象
- 代理模式有不同的形式, 主要有三种 静态代理、动态代理 (JDK代理、接口代 理)和 Cglib代理 (可以在内存动态的创建对象,而不需要实现接口, 他是属于 动态代理的范畴) 。
静态代理
基本介绍
静态代理在使用时,需要定义接口或者父类,被代理对象(即目标对象)与代理对象一 起实现相同的接口或者是继承相同父类
应用实例
具体要求
- 定义一个接口:ITeacherDao
- 目标对象TeacherDAO实现接口ITeacherDAO
- 使用静态代理方式,就需要在代理对象TeacherDAOProxy中也实现ITeacherDAO
- 调用的时候通过调用代理对象的方法来调用目标对象.
- 特别提醒:代理对象与目标对象要实现相同的接口,然后通过调用相同的方法来 调用目标对象的方法。
代码实现
public interface ITeacherDao {
void teach();
}
//目标对象
public class TeacherDao implements ITeacherDao {
@Override
public void teach() {
System.out.println("老师授课中...");
}
}
//代理对象
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("代理结束。。。。");
}
}
public class Client {
public static void main(String[] args) {
TeacherDaoProxy proxy = new TeacherDaoProxy(new TeacherDao());
proxy.teach();
}
}
静态代理优缺点
- 优点:在不修改目标对象的功能前提下, 能通过代理对象对目标功能扩展
- 缺点:因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类
- 一旦接口增加方法,目标对象与代理对象都要维护
动态代理
基本介绍
- 代理对象,不需要实现接口,但是目标对象要实现接口,否则不能用动态代理
- 代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象
- 动态代理也叫做:JDK代理、接口代理
JDK中生成代理对象的API
- 代理类所在包:java.lang.reflect.Proxy
- JDK实现代理只需要使用newProxyInstance方法,但是该方法需要接收三个参数,完 整的写法是: static Object newProxyInstance(ClassLoader loader, Class[] interfaces,InvocationHandler h )
代码实现
public interface ITeacherDao {
void teach();
}
public class TeacherDao implements ITeacherDao {
@Override
public void teach() {
System.out.println("老师授课中...");
}
}
public class ProxyFactory {
//维护一个目标对象
private Object 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("动态代理开始");
Object returnVal = method.invoke(target, args);
System.out.println("动态代理结束");
return returnVal;
}
});
}
}
public class Client {
public static void main(String[] args) {
//创建目标对象
ITeacherDao target = new TeacherDao();
//给目标对象创建代理对象
ITeacherDao proxyInstance = (ITeacherDao) new ProxyFactory(target).getProxyInstance();
proxyInstance.teach();
}
}
Cglib代理
基本介绍
- 静态代理和JDK代理模式都要求目标对象是实现一个接口,但是有时候目标对象只 是一个单独的对象,并没有实现任何的接口,这个时候可使用目标对象子类来实现 代理-这就是Cglib代理
- Cglib代理也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功 能扩展, 有些书也将Cglib代理归属到动态代理。
- Cglib是一个强大的高性能的代码生成包,它可以在运行期扩展java类与实现java接 口.它广泛的被许多AOP的框架使用,例如Spring AOP,实现方法拦截
- 在AOP编程中如何选择代理模式:
- 目标对象需要实现接口,用JDK代理
- 目标对象不需要实现接口,用Cglib代理
5.Cglib包的底层是通过使用字节码处理框架ASM来转换字节码并生成新的类
代码实现
public class TeacherDao {
public void teach() {
System.out.println("老师授课中...");
}
}
public class ProxyFactory implements MethodInterceptor {
//维护一个目标对象
private Object target;
public ProxyFactory(Object target) {
this.target = target;
}
//获取目标对象的代理对象
public Object getProxyInstance() {
//1.创建一个工具类
Enhancer enhancer = new Enhancer();
//2.设置父类
enhancer.setSuperclass(target.getClass());
//3.设置回调函数
enhancer.setCallback(this);
//4.创建子类对象即代理对象
return enhancer.create();
}
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("cglib代理开始。。。");
Object returnVal = method.invoke(target, args);
System.out.println("cglib代理结束");
return returnVal;
}
}
public class Client {
public static void main(String[] args) {
//创建目标对象
TeacherDao target = new TeacherDao();
// 创建目标对象的的代理对象
TeacherDao proxyInstance = (TeacherDao) new ProxyFactory(target).getProxyInstance();
//执行代理对象的方法.触发intercept方法 从而实现对目标对象的调用
proxyInstance.teach();
}
}
目标对象的方法如果为final/static,那么就不会被拦截,即不会执行目标对象额外的 业务方法.
十二、模板方法模式
豆浆制作问题
编写制作豆浆的程序,说明如下:
- 制作豆浆的流程 选材—>添加配料—>浸泡—>放到豆浆机打碎
- 通过添加不同的配料,可以制作出不同口味的豆浆
- 选材、浸泡和放到豆浆机打碎这几个步骤对于制作每种口味的豆浆都是一样的
- 请使用 模板方法模式 完成 (说明:因为模板方法模式,比较简单,很容易就 想到这个方案,因此就直接使用,不再使用传统的方案来引出模板方法模式 )
基本介绍
- 模板方法模式(Template Method Pattern),又叫模板模式(Template Pattern), 在一个抽象类公开定义了执行它的方法的模板。它的子类可以按需要重写方法 实现,但调用将以抽象类中定义的方式进行。
- 简单说,模板方法模式 定义一个操作中的算法的骨架,而将一些步骤延迟到子 类中,使得子类可以不改变一个算法的结构,就可以重定义该算法的某些特定 步骤
- 这种类型的设计模式属于行为型模式。
应用实例
编写制作豆浆的程序,说明如下: • 制作豆浆的流程 选材—>添加配料—>浸泡—>放到豆浆机打碎 • 通过添加不同的配料,可以制作出不同口味的豆浆 • 选材、浸泡和放到豆浆机打碎这几个步骤对于制作每种口味的豆浆都是一样的(红 豆、花生豆浆。。。)
模板方法模式的钩子方法
- 在模板方法模式的父类中,我们可以定义一个方法,它默认不做任何事,子类可以视情况要不要覆盖它,该方法称为“钩子”。
- 还是用上面做豆浆的例子来讲解,比如,我们还希望制作纯豆浆,不添加任何的配 料,请使用钩子方法对前面的模板方法进行改造
代码实现
public abstract class SoybeanMilk {
//模板方法
public void make() {
select();
if (customerWantCondiments()) {
add();
}
soak();
beat();
System.out.println("制作完成...");
}
public void select() {
System.out.println("选择新鲜的黄豆...");
}
public abstract void add();
public void soak() {
System.out.println("将黄豆和配料浸泡一小时...");
}
public void beat() {
System.out.println("放到豆浆机打碎...");
}
//钩子方法 决定是否需要添加配料
public boolean customerWantCondiments() {
return true;
}
}
public class BlackSoybeanMilk extends SoybeanMilk {
@Override
public void add() {
System.out.println("添加黑豆...");
}
}
public class PeanutSoyMilk extends SoybeanMilk {
@Override
public void add() {
System.out.println("添加花生");
}
}
public class PureSoyBeanMilk extends SoybeanMilk {
@Override
public void add() {
//空实现 不添加配料
}
@Override
public boolean customerWantCondiments() {
return false;
}
}
public class Client {
public static void main(String[] args) {
SoybeanMilk blackSoybeanMilk = new BlackSoybeanMilk();
System.out.println("-----制作黑豆豆浆-----");
blackSoybeanMilk.make();
System.out.println("-----制作花生豆浆-----");
SoybeanMilk peanutSoyMilk = new PeanutSoyMilk();
peanutSoyMilk.make();
System.out.println("-----制作纯豆浆-----");
SoybeanMilk pureSoyBeanMilk = new PureSoyBeanMilk();
pureSoyBeanMilk.make();
}
}
模板方法模式的注意事项和细节
- 基本思想是:算法只存在于一个地方,也就是在父类中,容易修改。需要修改算 法时,只要修改父类的模板方法或者已经实现的某些步骤,子类就会继承这些修改
- 实现了最大化代码复用。父类的模板方法和已实现的某些步骤会被子类继承而直接 使用。
- 既统一了算法,也提供了很大的灵活性。父类的模板方法确保了算法的结构保持不 变,同时由子类提供部分步骤的实现。
- 该模式的不足之处:每一个不同的实现都需要一个子类实现,导致类的个数增加, 使得系统更加庞大
- 一般模板方法都加上final关键字, 防止子类重写模板方法.
- 模板方法模式使用场景:当要完成在某个过程,该过程要执行一系列步骤 ,这一 系列的步骤基本相同,但其个别步骤在实现时 可能不同,通常考虑用模板方法模 式来处理
十三、命令模式
智能生活项目需求
- 我们买了一套智能家电,有照明灯、风扇、冰箱、洗衣机,我们只要在手机上安装app就 可以控制对这些家电工作。
- 这些智能家电来自不同的厂家,我们不想针对每一种家电都安装一个App,分别控制,我 们希望只要一个app就可以控制全部智能家电。
- 要实现一个app控制所有智能家电的需要,则每个智能家电厂家都要提供一个统一的接口 给app调用,这时 就可以考虑使用命令模式。
- 命令模式可将“动作的请求者”从“动作的执行者”对象中解耦出来.
- 在我们的例子中,动作的请求者是手机app,动作的执行者是每个厂商的一个家电产品
基本介绍
- 命令模式(Command Pattern):在软件设计中,我们经常需要 向某些对象发送请求,但是并不知道请求的接收者是谁,也不知 道被请求的操作是哪个, 我们只需在程序运行时指定具体的请求接收者即可,此时,可以 使用命令模式来进行设计
- 命名模式使得请求发送者与请求接收者消除彼此之间的耦合,让 对象之间的调用关系更加灵活,实现解耦。
- 在命名模式中,会将一个请求封装为一个对象,以便使用不同参 数来表示不同的请求(即命名),同时命令模式也支持可撤销的操作。
- 通俗易懂的理解:将军发布命令,士兵去执行。其中有几个角色: 将军(命令发布者)、士兵(命令的具体执行者)、命令(连接将 军和士兵)。 Invoker是调用者(将军),Receiver是被调用者(士兵), MyCommand是命令,实现了Command接口,持有接收对象
代码实现
//创建命令接口
public interface command {
//执行动作(操作)
void execute();
//撤销动作(操作)
void undo();
}
public class LightOffCommand implements command {
//聚合LightReceiver
LightReceiver light;
public LightOffCommand(LightReceiver light) {
this.light = light;
}
@Override
public void execute() {
light.off();
}
@Override
public void undo() {
light.on();
}
}
public class LightOnCommand implements command {
//聚合LightReceiver
LightReceiver light;
public LightOnCommand(LightReceiver light) {
this.light = light;
}
@Override
public void execute() {
light.on();
}
@Override
public void undo() {
light.off();
}
}
/**
* 没有任何命令,即空执行: 用于初始化每个按钮, 当调用空命令时,对象什么都不做
* * 其实,这样是一种设计模式, 可以省掉对空判断
*/
public class NoCommand implements command {
@Override
public void execute() {
}
@Override
public void undo() {
}
}
public class LightReceiver {
public void on() {
System.out.println("电灯打开了...");
}
public void off() {
System.out.println("电灯关闭了...");
}
}
public class RemoteController {
command[] onCommands;
command[] offCommands;
command undoCommand;
public RemoteController() {
this.offCommands = new command[5];
this.onCommands = 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) { // no 0
// 找到你按下的开的按钮, 并调用对应方法
onCommands[no].execute();
// 记录这次的操作,用于撤销
undoCommand = onCommands[no];
}
// 按下关按钮
public void offButtonWasPushed(int no) { // no 0
// 找到你按下的关的按钮, 并调用对应方法
offCommands[no].execute();
// 记录这次的操作,用于撤销
undoCommand = offCommands[no];
}
// 按下撤销按钮
public void undoButtonWasPushed() {
undoCommand.undo();
}
}
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();
}
}
命令模式的注意事项和细节
- 将发起请求的对象与执行请求的对象解耦。发起请求的对象是调用者,调用者只要 调用命令对象的execute()方法就可以让接收者工作,而不必知道具体的接收者对 象是谁、是如何实现的,命令对象会负责让接收者执行请求的动作,也就是说:” 请求发起者”和“请求执行者”之间的解耦是通过命令对象实现的,命令对象起到 了纽带桥梁的作用。
- 容易设计一个命令队列。只要把命令对象放到列队,就可以多线程的执行命令
- 容易实现对请求的撤销和重做
- 命令模式不足:可能导致某些系统有过多的具体命令类,增加了系统的复杂度,这 点在在使用的时候要注意
- 空命令也是一种设计模式,它为我们省去了判空的操作。在上面的实例中,如果没 有用空命令,我们每按下一个按键都要判空,这给我们编码带来一定的麻烦。
- 命令模式经典的应用场景:界面的一个按钮都是一条命令、模拟CMD(DOS命令) 订单的撤销/恢复、触发-反馈机制
十四、访问者模式
测评系统的需求
将观众分为男人和女人,对歌手进行测评,当看完某个歌手表演后,得到他们对 该歌手不同的评价(评价 有不同的种类,比如 成功、失败 等)
基本介绍
- 访问者模式(Visitor Pattern),封装一些作用于某种数据结构的各元素的操作, 它可以在不改变数据结构的前提下定义作用于这些元素的新的操作。
- 主要将数据结构与数据操作分离,解决 数据结构和操作耦合性问题
- 访问者模式的基本工作原理是:在被访问的类里面加一个对外提供接待访问者 的接口
- 访问者模式主要应用场景是:需要对一个对象结构中的对象进行很多不同操作 (这些操作彼此没有关联),同时需要避免让这些操作"污染"这些对象的类,可以 选用访问者模式解决
代码实现
public abstract class Person {
//提供一个方法,让访问者可以访问
public abstract void accept(Action action);
}
public class Man extends Person {
private String name;
public Man(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public void accept(Action action) {
action.getManResult(this);
}
}
public class Woman extends Person {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Woman(String name) {
this.name = name;
}
@Override
public void accept(Action action) {
action.getWomanResult(this);
}
}
public abstract class Action {
//得到男性的测评
public abstract void getManResult(Man man);
//得到女性的测评
public abstract void getWomanResult(Woman woman);
}
public class Success extends Action {
@Override
public void getManResult(Man man) {
System.out.println(man.getName() + " 成功晋级!");
}
@Override
public void getWomanResult(Woman woman) {
System.out.println(woman.getName() + " 成功晋级!");
}
}
public class Fail extends Action {
@Override
public void getManResult(Man man) {
}
@Override
public void getWomanResult(Woman woman) {
}
}
//数据结构,管理很多人(Man , Woman)
public class ObjectStructure {
//维护了一个集合
private List<Person> personList = new ArrayList<>();
public void attach(Person p) {
personList.add(p);
}
public void detach(Person p) {
personList.remove(p);
}
//显示测评情况
public void display(Action action) {
for (Person p : personList) {
p.accept(action);
}
}
}
public class Client {
public static void main(String[] args) {
ObjectStructure objectStructure = new ObjectStructure();
Person person1 = new Man("邓超");
Person person2 = new Woman("孙俪");
objectStructure.attach(person1);
objectStructure.attach(person2);
//成功
Success success = new Success();
objectStructure.display(success);
System.out.println("===============");
}
}
访问者模式的注意事项和细节
优点
- 访问者模式符合单一职责原则、让程序具有优秀的扩展性、灵活性非常高
- 访问者模式可以对功能进行统一,可以做报表、UI、拦截器与过滤器,适用于数据 结构相对稳定的系统
缺点
- 具体元素对访问者公布细节,也就是说访问者关注了其他类的内部细节,这是迪米 特法则所不建议的, 这样造成了具体元素变更比较困难
- 违背了依赖倒转原则。访问者依赖的是具体元素,而不是抽象元素
- 因此,如果一个系统有比较稳定的数据结构,又有经常变化的功能需求,那么访问 者模式就是比较合适的
十五、迭代器模式
看一个具体的需求
编写程序展示一个学校院系结构:需求是这样,要在一个页面中展示出学校的院系 组成,一个学校有多个学院,一个学院有多个系。如图:
基本介绍
- 迭代器模式(Iterator Pattern)是常用的设计模式,属于行为型模式
- 如果我们的集合元素是用不同的方式实现的,有数组,还有java的集合类, 或者还有其他方式,当客户端要遍历这些集合元素的时候就要使用多种遍历 方式,而且还会暴露元素的内部结构,可以考虑使用迭代器模式解决。
- 迭代器模式,提供一种遍历集合元素的统一接口,用一致的方法遍历集合元素, 不需要知道集合对象的底层表示,即:不暴露其内部的结构
代码实现
public class Department {
private String name;
private String 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;
}
public Department(String name, String des) {
this.name = name;
this.des = des;
}
}
public interface College {
String getName(); //学院名字
void addDepartment(String name, String des); //增加系
Iterator createIterator(); //返回一个迭代器
}
public class ComputeCollege implements College {
Department[] departments;
int numOfDepartment = 0;
public ComputeCollege() {
departments = new Department[5];
addDepartment("计算机科学与技术", "good");
addDepartment("软件工程", "goodGood");
addDepartment("数字媒体技术", "goodGoodGood");
addDepartment("大数据", "goodGoodGoodGood");
}
@Override
public String getName() {
return "计算机学院";
}
@Override
public void addDepartment(String name, String des) {
Department department = new Department(name, des);
departments[numOfDepartment] = department;
numOfDepartment++;
}
@Override
public Iterator createIterator() {
return new ComputeCollegeIterator(departments);
}
}
public class InfoCollege implements College {
List<Department> departmentList;
@Override
public String getName() {
return "信息工程学院";
}
public InfoCollege() {
departmentList = new ArrayList<>();
addDepartment("信息安全", "nice");
addDepartment("网络安全", "niceNice");
addDepartment("服务器安全", "niceNiceNice");
}
@Override
public void addDepartment(String name, String des) {
Department department = new Department(name, des);
departmentList.add(department);
}
@Override
public Iterator createIterator() {
return new InfoCollegeIterator(departmentList);
}
}
public class ComputeCollegeIterator implements Iterator {
private Department[] departments;
int position = 0;
public ComputeCollegeIterator(Department[] departments) {
this.departments = departments;
}
@Override
public boolean hasNext() {
if (position > departments.length || departments[position] == null) {
return false;
}
return true;
}
@Override
public Object next() {
Department department = departments[position];
position++;
return department;
}
}
public class InfoCollegeIterator implements Iterator {
private List<Department> departments; //信息工程学院以List方式存储
int index = -1;
public InfoCollegeIterator(List<Department> departments) {
this.departments = departments;
}
@Override
public boolean hasNext() {
if (index >= departments.size() - 1) {
return false;
} else {
index++;
return true;
}
}
@Override
public Object next() {
return departments.get(index);
}
}
public class OutputImpl {
List<College> collegeList;
public OutputImpl(List<College> collegeList) {
this.collegeList = collegeList;
}
//输出学院
public void printCollege() {
Iterator<College> iterator = collegeList.iterator();
while (iterator.hasNext()) {
College coll = iterator.next();
System.out.println("------" + coll.getName() + "------");
printDepartment(coll.createIterator());
}
}
//输出系
public void printDepartment(Iterator iterator) {
while (iterator.hasNext()) {
Department dep = (Department) iterator.next();
System.out.println(dep.getName());
}
}
}
public class Client {
public static void main(String[] args) {
List<College> collegeList = new ArrayList<>();
ComputeCollege computeCollege = new ComputeCollege();
InfoCollege infoCollege = new InfoCollege();
collegeList.add(computeCollege);
collegeList.add(infoCollege);
OutputImpl output = new OutputImpl(collegeList);
output.printCollege();
}
}
迭代器模式的注意事项和细节
优点
- 提供一个统一的方法遍历对象,客户不用再考虑聚合的类型,使用一种方法就可以 遍历对象了。
- 隐藏了聚合的内部结构,客户端要遍历聚合的时候只能取到迭代器,而不会知道聚 合的具体组成。
- 提供了一种设计思想,就是一个类应该只有一个引起变化的原因(叫做单一责任 原则)。在聚合类中,我们把迭代器分开,就是要把管理对象集合和遍历对象集 合的责任分开,这样一来集合改变的话,只影响到聚合对象。而如果遍历方式改变 的话,只影响到了迭代器。
- 当要展示一组相似对象,或者遍历一组相同对象时使用, 适合使用迭代器模式
缺点
每个聚合对象都要一个迭代器,会生成多个迭代器不好管理类
十六、观察者模式(发布订阅)
天气预报项目需求
- 气象站可以将每天测量到的温度,湿度,气压等等以公告的形式发布出去(比如 发布到自己的网站或第三方)。
- 需要设计开放型API,便于其他第三方也能接入气象站获取数据。
- 提供温度、气压和湿度的接口
- 测量数据更新时,要能实时的通知给第三方
代码实现
public interface Subject {
void registerObserver(Observer observer);
void remove(Observer observer);
void notifyObserver();
}
/**
* 类是核心
* 1. 包含最新的天气情况信息
* 2. 含有 观察者集合,使用ArrayList管理
* 3. 当数据有更新时,就主动的调用 ArrayList, 通知所有的(接入方)就看到最新的信息
*/
public class WeatherData implements Subject {
private float temperature;
private float pressure;
private float humidity;
private ArrayList<Observer> observerList;
public WeatherData() {
observerList = new ArrayList<>();
}
//当数据有更新时,就调用 setData
public void setData(float temperature, float pressure, float humidity) {
this.temperature = temperature;
this.pressure = pressure;
this.humidity = humidity;
//notifyObserver, 将最新的信息 推送给 接入方
notifyObserver();
}
//注册一个观察者
@Override
public void registerObserver(Observer observer) {
observerList.add(observer);
}
//移除观察者
@Override
public void remove(Observer observer) {
if (observerList.contains(observer)) {
observerList.remove(observer);
}
}
//遍历所有的观察者,并通知
@Override
public void notifyObserver() {
for (Observer observer : observerList) {
System.out.println("-----天气-----");
observer.update(this.temperature, this.pressure, this.humidity);
}
}
}
public interface Observer {
void update(float temperature, float pressure, float humidity);
}
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("百度温度为:" + temperature);
System.out.println("百度气压为:" + pressure);
System.out.println("百度湿度为:" + humidity);
}
}
public class CurrentCondition 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("现在的温度为:" + temperature);
System.out.println("现在的气压为:" + pressure);
System.out.println("现在的湿度为:" + humidity);
}
}
public class Client {
public static void main(String[] args) {
//创建一个天气站
WeatherData weatherData = new WeatherData();
//创建一个观察者
CurrentCondition currentCondition = new CurrentCondition();
BaiDuSite baiDuSite = new BaiDuSite();
//将观察者注册到天气站中
weatherData.registerObserver(currentCondition);
weatherData.registerObserver(baiDuSite);
//设置天气站的天气情况
weatherData.setData(30, 150, 40);
//天气站通知观察者
weatherData.notifyObserver();
}
}
观察者模式的好处
- 观察者模式设计后,会以集合的方式来管理用户(Observer),包括注册,移除 和通知。
- 这样,我们增加观察者(这里可以理解成一个新的公告板),就不需要去修改核 心类WeatherData不会修改代码,遵守了ocp原则。
十七、中介者模式(MVC)
智能家庭管理问题
- 智能家庭包括各种设备,闹钟、咖啡机、电视机、窗帘 等
- 主人要看电视时,各个设备可以协同工作,自动完成看电视的准备工作,比如流 程为:闹铃响起->咖啡机开始做咖啡->窗帘自动落下->电视机开始播放
基本介绍
- 中介者模式(Mediator Pattern),用一个中介对象来封装一系列的对象交互。 中介者使各个对象不需要显式地相互引用,从而使其耦合松散,而且可以独立 地改变它们之间的交互
- 中介者模式属于行为型模式,使代码易于维护
- 比如MVC模式,C(Controller控制器)是M(Model模型)和V(View视图)的中 介者,在前后端交互时起到了中间人的作用
代码实现
public abstract class Mediator {
//将给同事对象,加入到集合中
public abstract void Register(String colleagueName, Colleague colleague);
//接收消息, 具体的同事对象发出
public abstract void GetMessage(int stateChange, String colleagueName);
public abstract void SendMessage();
}
//具体的中介者类
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() {
}
}
//同事抽象类
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);
}
public class CoffeeMachine extends Colleague {
public CoffeeMachine(Mediator mediator, String name) {
super(mediator, name);
// TODO Auto-generated constructor stub
mediator.Register(name, this);
}
@Override
public void SendMessage(int stateChange) {
// TODO Auto-generated method stub
this.GetMediator().GetMessage(stateChange, this.name);
}
public void StartCoffee() {
System.out.println("It's time to start coffee!");
}
public void FinishCoffee() {
System.out.println("After 5 minutes!");
System.out.println("Coffee is ok!");
SendMessage(0);
}
}
public class Alarm extends Colleague {
//构造器
public Alarm(Mediator mediator, String name) {
super(mediator, name);
// TODO Auto-generated constructor stub
//在创建Alarm 同事对象时,将自己放入到ConcreteMediator 对象中[集合]
mediator.Register(name, this);
}
public void SendAlarm(int stateChange) {
SendMessage(stateChange);
}
@Override
public void SendMessage(int stateChange) {
// TODO Auto-generated method stub
//调用的中介者对象的getMessage
this.GetMediator().GetMessage(stateChange, this.name);
}
}
public class Client {
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);
}
}
中介者模式的注意事项和细节
- 多个类相互耦合,会形成网状结构, 使用中介者模式将网状结构分离为星型结构, 进行解耦
- 减少类间依赖,降低了耦合,符合迪米特原则
- 中介者承担了较多的责任,一旦中介者出现了问题,整个系统就会受到影响
- 如果设计不当,中介者对象本身变得过于复杂,这点在实际使用时,要特别注意
十八、备忘录模式
游戏角色状态恢复问题
游戏角色有攻击力和防御力,在大战Boss前保存自身的状态(攻击力和防御力),当大 战Boss后攻击力和防御力下降,从备忘录对象恢复到大战前的状态
基本介绍
- 备忘录模式(Memento Pattern)在不破坏封装性的前提下,捕获一个对象的内 部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保 存的状态
- 可以这里理解备忘录模式:现实生活中的备忘录是用来记录某些要去做的事情, 或者是记录已经达成的共同意见的事情,以防忘记了。而在软件层面,备忘录 模式有着相同的含义,备忘录对象主要用来记录一个对象的某种状态,或者某 些数据,当要做回退时,可以从备忘录对象里获取原来的数据进行恢复操作
- 备忘录模式属于行为型模式
代码实现
public class Caretaker {
//在List 集合中会有很多的备忘录对象
private List<Memento> mementoList = new ArrayList<Memento>();
public void add(Memento memento) {
mementoList.add(memento);
}
//获取到第index个Originator 的 备忘录对象(即保存状态)
public Memento get(int index) {
return mementoList.get(index);
}
}
public class Memento {
private String state;
//构造器
public Memento(String state) {
super();
this.state = state;
}
public String getState() {
return state;
}
}
public class Originator {
private String state;//状态信息
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
//编写一个方法,可以保存一个状态对象 Memento
//因此编写一个方法,返回 Memento
public Memento saveStateMemento() {
return new Memento(state);
}
//通过备忘录对象,恢复状态
public void getStateFromMemento(Memento memento) {
state = memento.getState();
}
}
public class Client {
public static void main(String[] args) {
// TODO Auto-generated method stub
Originator originator = new Originator();
Caretaker caretaker = new Caretaker();
originator.setState(" 状态#1 攻击力 100 ");
//保存了当前的状态
caretaker.add(originator.saveStateMemento());
originator.setState(" 状态#2 攻击力 80 ");
caretaker.add(originator.saveStateMemento());
originator.setState(" 状态#3 攻击力 50 ");
caretaker.add(originator.saveStateMemento());
System.out.println("当前的状态是 =" + originator.getState());
//希望得到状态 1, 将 originator 恢复到状态1
originator.getStateFromMemento(caretaker.get(0));
System.out.println("恢复到状态1 , 当前的状态是");
System.out.println("当前的状态是 =" + originator.getState());
}
}
备忘录模式的注意事项和细节
- 给用户提供了一种可以恢复状态的机制,可以使用户能够比较方便地回到某个历史 的状态
- 实现了信息的封装,使得用户不需要关心状态的保存细节
- 如果类的成员变量过多,势必会占用比较大的资源,而且每一次保存都会消耗一定 的内存, 这个需要注意
- 适用的应用场景:1、后悔药。 2、打游戏时的存档。 3、Windows 里的 ctri + z。 4、IE 中的后退。 4、数据库的事务管理
- 为了节约内存,备忘录模式可以和原型模式配合使用
十九、解释器模式(不容易用到)
四则运算问题
通过解释器模式来实现四则运算,如计算a+b-c的值,具体要求
- 先输入表达式的形式,比如 a+b+c-d+e, 要求表达式的字母不能重复
- 在分别输入a ,b, c, d, e 的值
- 最后求出结果:如图
基本介绍
- 在编译原理中,一个算术表达式通过词法分析器形成词法单元,而后这些词法 单元再通过语法分析器构建语法分析树,最终形成一颗抽象的语法分析树。这 里的词法分析器和语法分析器都可以看做是解释器
- 解释器模式(Interpreter Pattern):是指给定一个语言(表达式),定义它的文法 的一种表示,并定义一个解释器,使用该解释器来解释语言中的句子(表达式)
- 应用场景
• 应用可以将一个需要解释执行的语言中的句子表示为一个抽象语法树
• 一些重复出现的问题可以用一种简单的语言来表达
• 一个简单语法需要解释的场景
- 这样的例子还有,比如编译器、运算表达式计算、正则表达式、机器人等
代码实现
/**
* 抽象类表达式,通过HashMap键值对,可以获取到变量的值
*/
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);
}
//变量的解释器
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);
}
}
/**
* 抽象运算符号解释器 每个运算符号都只和自己左右两个数字有关系,
* 但左右两个数字有可能也是一个解析的结果,无论何种类型,都是Expression的实现类
*/
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;
}
}
/**
* 加法解释器
*/
public class AddExpression extends SymbolExpression {
public AddExpression(Expression left, Expression right) {
super(left, right);
}
//处理相加
// var 仍然是{a=10,b=20}..
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);
}
}
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);
}
}
public class Calculator {
//定义表达式
private Expression expression;
//构造函数传参,并解析
public Calculator(String expStr) {
//安排运算先后顺序
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]));
stack.push(new AddExpression(left, right));
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;
}
}
//当遍历完整个charArray数组后,stack就得到最后Expression
this.expression = stack.pop();
}
public int run(HashMap<String, Integer> var) {
//最后将表达式a+b和var 绑定{a=10,b=20}
//然后传递给expression的interpreter进行解释执行
return this.expression.interpreter(var);
}
}
public class Client {
public static void main(String[] args) throws IOException {
String expStr = getExpStr();
HashMap<String, Integer> var = getValue(expStr);
Calculator calculator = new Calculator(expStr);
System.out.println("运算结果:" + expStr + "=" + calculator.run(var));
}
//获得表达式
private static String getExpStr() throws IOException {
System.out.print("请输入表达式:");
return (new BufferedReader(new InputStreamReader(System.in))).readLine();
}
//获得值映射
private 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.println("请输入" + String.valueOf(ch) + "的值:");
String in = (new BufferedReader(new InputStreamReader(System.in))).readLine();
map.put(String.valueOf(ch), Integer.valueOf(in));
}
}
}
return map;
}
}
解释器模式的注意事项和细节
- 当有一个语言需要解释执行,可将该语言中的句子表示为一个抽象语法树,就可以 考虑使用解释器模式,让程序具有良好的扩展性
- 应用场景:编译器、运算表达式计算、正则表达式、机器人等
- 使用解释器可能带来的问题:解释器模式会引起类膨胀、解释器模式采用递归调用 方法,将会导致调试非常复杂、效率可能降低
二十、状态模式
APP抽奖活动问题
请编写程序完成APP抽奖活动 具 体要求如下:
- 假如每参加一次这个活动要 扣除用户50积分,中奖概率 是10%
- 奖品数量固定,抽完就不能 抽奖
- 活动有四个状态: 可以抽奖、 不能抽奖、发放奖品和奖品 领完
- 活动的四个状态转换关系图 (右图)
基本介绍
- 状态模式(State Pattern):它主要用来解决对象在多种状态转换时,需要对外 输出不同的行为的问题。状态和行为是一一对应的,状态之间可以相互转换
- 当一个对象的内在状态改变时,允许改变其行为,这个对象看起来像是改变了 其类
代码实现
/**
* 状态抽象类
*
* @author Administrator
*/
public abstract class State {
// 扣除积分 - 50
public abstract void deductMoney();
// 是否抽中奖品
public abstract boolean raffle();
// 发放奖品
public abstract void dispensePrize();
}
/**
* 不能抽奖状态
*
* @author Administrator
*/
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("不能发放奖品");
}
}
/**
* 可以抽奖的状态
*
* @author Administrator
*/
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);
// 10%中奖机会
if (num == 0) {
// 改变活动状态为发放奖品 context
activity.setState(activity.getDispenseState());
return true;
} else {
System.out.println("很遗憾没有抽中奖品!");
// 改变状态为不能抽奖
activity.setState(activity.getNoRafflleState());
return false;
}
}
// 不能发放奖品
@Override
public void dispensePrize() {
System.out.println("没中奖,不能发放奖品");
}
}
/**
* 发放奖品的状态
*
* @author Administrator
*/
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());
System.out.println("抽奖活动结束");
System.exit(0);
}
}
}
/**
* 奖品发放完毕状态
* 说明,当我们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("奖品发送完了,请下次再参加");
}
}
/**
* 抽奖活动 //
*
* @author Administrator
*/
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;
}
//这里请大家注意,每领取一次奖品,count--
public int getCount() {
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;
}
}
/**
* 状态模式测试类
*
* @author Administrator
*/
public class Client {
public static void main(String[] args) {
// TODO Auto-generated method stub
// 创建活动对象,奖品有1个奖品
RaffleActivity activity = new RaffleActivity(1);
// 我们连续抽300次奖
for (int i = 0; i < 30; i++) {
System.out.println("--------第" + (i + 1) + "次抽奖----------");
// 参加抽奖,第一步点击扣除积分
activity.debuctMoney();
// 第二步抽奖
activity.raffle();
}
}
}
状态模式的注意事项和细节
- 代码有很强的可读性。状态模式将每个状态的行为封装到对应的一个类中
- 方便维护。将容易产生问题的if-else语句删除了,如果把每个状态的行为都放到一 个类中,每次调用方法时都要判断当前是什么状态,不但会产出很多if-else语句, 而且容易出错
- 符合“开闭原则”。容易增删状态
- 会产生很多类。每个状态都要一个对应的类,当状态过多时会产生很多类,加大维 护难度
- 应用场景:当一个事件或者对象有很多种状态,状态之间会相互转换,对不同的状 态要求有不同的行为的时候,可以考虑使用状态模式
二十一、策略模式(策略替换)
鸭子问题
- 有各种鸭子(比如 野鸭、北京鸭、水鸭等, 鸭子有各种行为,比如 叫、飞行等)
- 显示鸭子的信息
基本介绍
- 策略模式(Strategy Pattern)中,定义算法族,分别封装起来,让他们之间可以 互相替换,此模式让算法的变化独立于使用算法的客户
- 这算法体现了几个设计原则,第一、把变化的代码从不变的代码中分离出来; 第二、针对接口编程而不是具体类(定义了策略接口);第三、多用组合/聚合, 少用继承(客户通过组合方式使用策略)。
代码实现
public abstract class Duck {
//属性, 策略接口
FlyBehavior flyBehavior;
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 class ToyDuck extends Duck {
public ToyDuck() {
// TODO Auto-generated constructor stub
flyBehavior = new NoFlyBehavior();
}
@Override
public void display() {
// TODO Auto-generated method stub
System.out.println("玩具鸭");
}
//需要重写父类的所有方法
public void quack() {
System.out.println("玩具鸭不能叫~~");
}
public void swim() {
System.out.println("玩具鸭不会游泳~~");
}
}
public class PekingDuck extends Duck {
//假如北京鸭可以飞翔,但是飞翔技术一般
public PekingDuck() {
// TODO Auto-generated constructor stub
flyBehavior = new BadFlyBehavior();
}
@Override
public void display() {
// TODO Auto-generated method stub
System.out.println("~~北京鸭~~~");
}
}
public class WildDuck extends Duck {
//构造器,传入FlyBehavior 的对象
public WildDuck() {
// TODO Auto-generated constructor stub
flyBehavior = new GoodFlyBehavior();
}
@Override
public void display() {
// TODO Auto-generated method stub
System.out.println(" 这是野鸭 ");
}
}
public interface FlyBehavior {
void fly(); // 子类具体实现
}
public class BadFlyBehavior implements FlyBehavior {
@Override
public void fly() {
// TODO Auto-generated method stub
System.out.println(" 飞翔技术一般 ");
}
}
public class GoodFlyBehavior implements FlyBehavior {
@Override
public void fly() {
// TODO Auto-generated method stub
System.out.println(" 飞翔技术高超 ~~~");
}
}
public class Client {
public static void main(String[] args) {
WildDuck wildDuck = new WildDuck();
wildDuck.fly();
wildDuck.setFlyBehavior(new BadFlyBehavior()); // 改变鸭子的飞行能力
wildDuck.fly();
}
}
策略模式的注意事项和细节
- 策略模式的关键是:分析项目中变化部分与不变部分
- 策略模式的核心思想是:多用组合/聚合 少用继承;用行为类组合,而不是行为的 继承。更有弹性
- 体现了“对修改关闭,对扩展开放”原则,客户端增加行为不用修改原有代码,只 要添加一种策略(或者行为)即可,避免了使用多重转移语句(if…else if…else)
- 提供了可以替换继承关系的办法: 策略模式将算法封装在独立的Strategy类中使得 你可以独立于其Context改变它,使它易于切换、易于理解、易于扩展
- 需要注意的是:每添加一个策略就要增加一个类,当策略过多是会导致类数目庞大
二十二、职责链模式
OA系统采购审批需求
- 采购员采购教学器材
- 如果金额 小于等于5000, 由教学主任审批 (0<=x<=5000)
- 如果金额 小于等于10000, 由院长审批 (5000<x<=10000)
- 如果金额 小于等于30000, 由副校长审批 (10000<x<=30000)
- 如果金额 超过30000以上,有校长审批 ( 30000<x)
基本介绍
- 职责链模式(Chain of Responsibility Pattern), 又叫 责任链模式,为请求创建了一个接收者 对象的链(简单示意图)。这种模式对请求的 发送者和接收者进行解耦。
- 职责链模式通常每个接收者都包含对另一个接 收者的引用。如果一个对象不能处理该请求, 那么它会把相同的请求传给下一个接收者,依 此类推。
- 这种类型的设计模式属于行为型模式
代码实现
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);
}
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);
}
}
}
public class CollegeApprover extends Approver {
public CollegeApprover(String name) {
// TODO Auto-generated constructor stub
super(name);
}
@Override
public void processRequest(PurchaseRequest purchaseRequest) {
// TODO Auto-generated method stub
if (purchaseRequest.getPrice() > 5000 && purchaseRequest.getPrice() <= 10000) {
System.out.println(" 请求编号 id= " + purchaseRequest.getId() + " 被 " + this.name + " 处理");
} else {
approver.processRequest(purchaseRequest);
}
}
}
public class SchoolMasterApprover extends Approver {
public SchoolMasterApprover(String name) {
// TODO Auto-generated constructor stub
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);
}
}
}
public class PurchaseRequest {
private int type = 0; //请求类型
private float price = 0.0f;//请求金额
private int id;
public PurchaseRequest(int type, float price, int id) {
this.type = type;
this.price = price;
this.id = id;
}
}
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);
}
}
职责链模式的注意事项和细节
- 将请求和处理分开,实现解耦,提高系统的灵活性
- 简化了对象,使对象不需要知道链的结构
- 性能会受到影响,特别是在链比较长的时候,因此需控制链中最大节点数量,一般 通过在Handler中设置一个最大节点数量,在setNext()方法中判断是否已经超过阀值, 超过则不允许该链建立,避免出现超长链无意识地破坏系统性能
- 调试不方便。采用了类似递归的方式,调试时逻辑可能比较复杂
- 最佳应用场景:有多个对象可以处理同一个请求时,比如:多级请求、请假/加薪 等审批流程、Java Web中Tomcat对Encoding的处理、拦截器