一、设计模式七大原则
1.1 设计模式的目的
编写软件过程中,程序员面临着来自耦合性,内聚性以及可维护性,可扩展性,重用性,灵活性 等多方面的挑战,设计模式是为了让程序(软件),具有更好
- 代码重用性 (即:相同功能的代码,不用多次编写)
- 可读性 (即:编程规范性, 便于其他程序员的阅读和理解)
- 可扩展性 (即:当需要增加新的功能时,非常的方便,称为可维护)
- 可靠性 (即:当我们增加新的功能后,对原来的功能没有影响)
1.2 设计模式七大原则
设计模式原则,其实就是程序员在编程时,应当遵守的原则,也是各种设计模式的基础(即:设计模式为什么 这样设计的依据)
设计模式常用的七大原则有:
- 单一职责原则
- 接口隔离原则
- 依赖倒转(倒置)原则
- 里氏替换原则
- 开闭原则
- 迪米特法则
- 合成复用原则
1.3单一职责
对类来说的,即一个类应该只负责一项职责。如类 A 负责两个不同职责:职责 1,职责 2。当职责 1 需求变更而改变 A 时,可能造成职责 2 执行错误,所以需要将类 A 的粒度分解为 A1,A2。
单一职责原则注意事项和细节 :
- 降低类的复杂度,一个类只负责一项职责。
- 提高类的可读性,可维护性
- 降低变更类的代码引起的风险
- 通常情况下,我们应当遵守单一职责原则,只有逻辑足够简单,才可以在代码级违反单一职责原则;只有类中 方法数量足够少,可以在方法级别保持单一职责原则
1.4接口隔离原则(Interface segregation principle)
客户端不应该依赖他不需要的接口,即一个类对另一个类的依赖应该建立在最小的接口上。
a.不遵循接口隔离原则
b.遵循接口隔离原则
1.5依赖倒转原则(Dependence Inversion Principle)
- 高层模块不应该依赖低层模块,二者都应该依赖其抽象
- 抽象不应该依赖细节,细节应该依赖抽象
- 依赖倒转(倒置)的中心思想是面向接口编程
- 依赖倒转原则是基于这样的设计理念:相对于细节的多变性,抽象的东西要稳定的多。以抽象为基础搭建的架构比以细节为基础的架构要稳定的多。在 java 中,抽象指的是接口或抽象类,细节就是具体的实现类
- 使用接口或抽象类的目的是制定好规范,而不涉及任何具体的操作,把展现细节的任务交给他们的实现类去完成
依赖关系传递的三种方式
- 接口传递
- 构造方法传递应用案例代码
- setter 方式传递
依赖倒转原则的注意事项和细节
- 低层模块尽量都要有抽象类或接口,或者两者都有,程序稳定性更好.
- 变量的声明类型尽量是抽象类或接口, 这样我们的变量引用和实际对象间,就存在一个缓冲层,利于程序扩展和优化
- 继承时遵循里氏替换原则
//事例
public class DependecyInversion {
public static void main(String[] args) {
//客户端无需改变
Person person = new Person();
person.receive(new Email());
person.receive(new WeiXin());
}
}
//定义接口
interface IReceiver {
public String getInfo();
}
class Email implements IReceiver {
public String getInfo() {
return "电子邮件信息: hello,world";
}
}
//增加微信
class WeiXin implements IReceiver {
public String getInfo() {
return "微信信息: hello,ok";
}
}
class Person {
//这里我们是对接口的依赖
public void receive(IReceiver receiver ) {
System.out.println(receiver.getInfo());
}
}
1.6里氏替换原则
OO中的继承性的思考和说明
- 继承包含这样一层含义:父类中凡是已经实现好的方法,实际上是在设定规范和契约,虽然它不强制要求所有的子类必须遵循这些契约,但是如果子类对这些已经实现的方法任意修改,就会对整个继承体系造成破坏。
- 继承在给程序设计带来便利的同时,也带来了弊端。比如使用继承会给程序带来侵入性,程序的可移植性降低,增加对象间的耦合性,如果一个类被其他的类所继承,则当这个类需要修改时,必须考虑到所有的子类,并且父类修改后,所有涉及到子类的功能都有可能产生故障
- 问题提出:在编程中,如何正确的使用继承? => 里氏替换原则
基本介绍
- 里氏替换原则(Liskov Substitution Principle)在 1988 年,由麻省理工学院的以为姓里的女士提出的。
- 如果对每个类型为 T1 的对象 o1,都有类型为 T2 的对象 o2,使得以 T1 定义的所有程序 P 在所有的对象 o1 都代换成 o2 时,程序 P 的行为没有发生变化,那么类型 T2 是类型 T1 的子类型。换句话说,所有引用基类的地方必须能透明地使用其子类的对象。
- 在使用继承时,遵循里氏替换原则,在子类中尽量不要重写父类的方法
- 里氏替换原则告诉我们,继承实际上让两个类耦合性增强了,在适当的情况下,可以通过聚合,组合,依赖来解决问题。
1.7开闭原则
- 开闭原则(Open Closed Principle)是编程中最基础、最重要的设计原则
- 一个软件实体如类,模块和函数应该对扩展开放(对提供方),对修改关闭(对使用方)。用抽象构建框架,用实现扩展细节。
- 当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化。
- 编程中遵循其它原则,以及使用设计模式的目的就是遵循开闭原则。
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.drawShape(new OtherGraphic());
}
}
//这是一个用于绘图的类 [使用方]
class GraphicEditor {
//接收Shape对象,调用draw方法
public void drawShape(Shape s) {
s.draw();
}
}
//Shape类,基类
abstract class Shape {
public abstract void draw();//抽象方法
}
class Rectangle extends Shape {
@Override
public void draw() {
// TODO Auto-generated method stub
System.out.println(" 绘制矩形 ");
}
}
class Circle extends Shape {
@Override
public void draw() {
// TODO Auto-generated method stub
System.out.println(" 绘制圆形 ");
}
}
//新增画三角形
class Triangle extends Shape {
@Override
public void draw() {
// TODO Auto-generated method stub
System.out.println(" 绘制三角形 ");
}
}
//新增一个图形
class OtherGraphic extends Shape {
@Override
public void draw() {
// TODO Auto-generated method stub
System.out.println(" 绘制其它图形 ");
}
}
1.8迪米特法则
- 一个对象应该对其他对象保持最少的了解
- 类与类关系越密切,耦合度越大
- 迪米特法则(Demeter Principle)又叫最少知道原则,即一个类对自己依赖的类知道的越少越好。也就是说,对于被依赖的类不管多么复杂,都尽量将逻辑封装在类的内部。对外除了提供的 public 方法,不对外泄露任何信息
- 迪米特法则还有个更简单的定义:只与直接的朋友通信
直接的朋友:每个对象都会与其他对象有耦合关系,只要两个对象之间有耦合关系,我们就说这两个对象之间是朋友关系。耦合的方式很多,依赖,关联,组合,聚合等。其中,我们称出现成员变量,方法参数,方法返回值中的类为直接的朋友,而出现在局部变量中的类不是直接的朋友。也就是说,陌生的类最好不要以局部变量的形式出现在类的内部。
迪米特法则注意事项和细节
- 迪米特法则的核心是降低类之间的耦合
- 但是注意:由于每个类都减少了不必要的依赖,因此迪米特法则只是要求降低类间(对象间)耦合关系,并不是要求完全没有依赖关系
1.9合成复用原则(Composite Reuse Principle)
原则是尽量使用合成/聚合的方式,而不是使用继承
1.10依赖关系(Dependence)
- 类中用到了对方
- 如果是类的成员属性
- 如果是方法的返回类型
- 是方法接收的参数类型
- 方法中使用到
1.11泛化关系(generalization)
- 泛化关系实际上就是继承关系
1.12聚合关系(Aggregation)
- 聚合关系(Aggregation)表示的是整体和部分的关系,整体与部分可以分开。聚合关系是关联关系的特例,所以他具有关联的导航性与多重性。
(成员变量的方式private Mouse mouse;
)
1.13组合关系(Composition)
- 组合关系:也是整体与部分的关系,但是整体与部分不可以分开。
(创建对象的方式Head head = new Head();
)
二、设计模式类型
- 创建型模式:单例模式、抽象工厂模式、原型模式、建造者模式、工厂模式。
- 结构型模式:适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式。
- 行为型模式:模版方法模式、命令模式、访问者模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式(Interpreter 模式)、状态模式、策略模式、职责链模式(责任链模式)。
三、单例模式
3.1单例设计模式介绍
所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法(静态方法)。
3.2 单例设计模式八种方式:
1) 饿汉式(静态常量)
2) 饿汉式(静态代码块)
3) 懒汉式(线程不安全)
4) 懒汉式(线程安全,同步方法)
5) 懒汉式(线程安全,同步代码块)
6) 双重检查
7) 静态内部类
8) 枚举
3.3 饿汉式(静态常量)
饿汉式(静态常量)应用实例步骤如下:
- 构造器私有化 (防止 new )
- 类的内部创建对象
- 向外暴露一个静态的公共方法。getInstance
优缺点说明:
- 优点:这种写法比较简单,就是在类装载的时候就完成实例化。避免了线程同步问题。
- 缺点:在类装载的时候就完成实例化,没有达到 Lazy Loading 的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费
- 这种方式基于 classloder 机制避免了多线程的同步问题,不过,instance 在类装载时就实例化,在单例模式中大多数都是调用 getInstance 方法,但是导致类装载的原因有很多种,因此不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化 instance 就没有达到 lazy loading 的效果
- 结论:这种单例模式可用,可能造成内存浪费
事例代码:
public class SingletonTest01 {
public static void main(String[] args) {
//测试
Singleton instance = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println(instance == instance2); // true
System.out.println("instance.hashCode=" + instance.hashCode());
System.out.println("instance2.hashCode=" + instance2.hashCode());
}
}
//饿汉式(静态变量)
class Singleton {
//1. 构造器私有化, 外部不能new
private Singleton() {
}
//2.本类内部创建对象实例(加载这个类的时候就会创建这个对象实例)
private final static Singleton instance = new Singleton();
//3. 提供一个公有的静态方法,返回实例对象
public static Singleton getInstance() {
return instance;
}
}
3.4 饿汉式(静态代码块)
//饿汉式(静态变量)
class Singleton {
//1. 构造器私有化, 外部不能new
private Singleton() {
}
//2.本类内部创建对象实例
private static Singleton instance;
static { //在静态代码块中,创建单例对象(静态代码块种的代码会在加载类的时候执行)
instance = new Singleton();
}
//3. 提供一个公有的静态方法,返回实例对象
public static Singleton getInstance() {
return instance;
}
}
3.5 双重检查
优缺点说明:
1) Double-Check 概念是多线程开发中常使用到的,如代码中所示,我们进行了两次 if (singleton == null)检查,这样就可以保证线程安全了。
2) 这样,实例化代码只用执行一次,后面再次访问时,判断 if (singleton == null),直接 return 实例化对象,也避免的反复进行方法同步.
3) 线程安全;延迟加载;效率较高
4) 结论:在实际开发中,推荐使用这种单例设计模式
public class SingletonTest06 {
public static void main(String[] args) {
System.out.println("双重检查");
Singleton instance = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println(instance == instance2); // true
System.out.println("instance.hashCode=" + instance.hashCode());
System.out.println("instance2.hashCode=" + instance2.hashCode());
}
}
// 懒汉式(线程安全,同步方法)
class Singleton {
// volatile轻量级的synchronized可见性,线程对变量的改变会在内存中实时更新
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;
}
}
3.6使用静态内部类完成单例模式(👍)
- 这种方式采用了类装载的机制来保证初始化实例时只有一个线程。
- 静态内部类方式在 Singleton 类被装载时并不会立即实例化,而是在需要实例化时,调用 getInstance 方法,才会装载 SingletonInstance 类,从而完成 Singleton 的实例化。
- 类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM 帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。
- 优点:避免了线程不安全,利用静态内部类特点实现延迟加载,效率高
- 结论:推荐使用.
//个人认为这个比较好
public class SingletonTest07 {
public static void main(String[] args) {
System.out.println("使用静态内部类完成单例模式");
Singleton instance = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println(instance == instance2); // true
System.out.println("instance.hashCode=" + instance.hashCode());
System.out.println("instance2.hashCode=" + instance2.hashCode());
}
}
// 静态内部类完成, 推荐使用
class Singleton {
private static Singleton instance;
//构造器私有化
private Singleton() {}
//写一个静态内部类,该类中有一个静态属性 Singleton
/**
静态内部类方式在 Singleton 类被装载时并不会立即实例化,而是在需要实例化时,
调用 getInstance 方法,才会装载 SingletonInstance 类
**/
private static class SingletonInstance {
private static final Singleton INSTANCE = new Singleton();
}
//提供一个静态的公有方法,直接返回SingletonInstance.INSTANCE
public static Singleton getInstance() {
return SingletonInstance.INSTANCE;
}
}
3.6 枚举
优缺点说明:
- 这借助 JDK1.5 中添加的枚举来实现单例模式。不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。
- 这种方式是 Effective Java 作者 Josh Bloch 提倡的方式
- 结论:推荐使用
public class SingletonTest08 {
public static void main(String[] args) {
Singleton instance = Singleton.INSTANCE;
Singleton instance2 = Singleton.INSTANCE;
System.out.println(instance == instance2);
System.out.println(instance.hashCode());
System.out.println(instance2.hashCode());
instance.sayOK();
}
}
//使用枚举,可以实现单例, 推荐
enum Singleton {
INSTANCE; //属性
public void sayOK() {
System.out.println("ok~");
}
}
3.7 单例模式注意事项和细节说明
- 单例模式保证了系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能
- 当想实例化一个单例类的时候,必须要记住使用相应的获取对象的方法,而不是使用 new
- 单例模式使用的场景:需要频繁的进行创建和销毁的对象、创建对象时耗时过多或耗费资源过多(即:重量级对象),但又经常用到的对象、工具类对象、频繁访问数据库或文件的对象(比如数据源、session 工厂等)
四、工厂模式
4.1简单工厂模式
- 简单工厂模式是属于创建型模式,是工厂模式的一种。简单工厂模式是由一个工厂对象决定创建出哪一种产品类的实例。简单工厂模式是工厂模式家族中最简单实用的模式
- 简单工厂模式:定义了一个创建对象的类,由这个类来封装实例化对象的行为(代码)
- 在软件开发中,当我们会用到大量的创建某类或者某批对象时,就会使用到工厂模式.
4.2工厂方法模式
- 工厂方法模式设计方案:将披萨项目的实例化功能抽象成抽象方法,在不同的口味点餐子类中具体实现。
- 工厂方法模式:定义了一个创建对象的抽象方法,由子类决定要实例化的类。工厂方法模式将对象的实例化推迟到子类。
public class my {
public static void main(String[] args) throws IOException {
BufferedReader strin = new BufferedReader(new InputStreamReader(System.in));
System.out.println("input pizza 产地:(信宜或广州)");
String str = strin.readLine();
if (str.equals("信宜")) {
//创建信宜口味的各种Pizza
new XYorderPzz();
} else {
//创建广州口味的各种Pizza
new GZorderPzz();
}
}
}
//工厂部分-----------------------------------------------------------------
abstract class OrderPzz{
//定义一个抽象方法,createPizza , 让各个工厂子类自己实现
abstract Pizza createPizza(String orderType);
// 构造器
public OrderPzz() {
Pizza pizza = null;
String orderType; // 订购披萨的类型
do {
orderType = getType();
pizza = createPizza(orderType); //抽象方法,由工厂子类完成
//输出pizza 制作过程
pizza.prepare();
pizza.before();
pizza.middle();
pizza.finish();
} while (true);
}
// 写一个方法,可以获取客户希望订购的披萨种类
private String getType() {
try {
BufferedReader strin = new BufferedReader(new InputStreamReader(System.in));
System.out.println("input pizza 种类:(A或B)");
String str = strin.readLine();
return str;
} catch (IOException e) {
e.printStackTrace();
return "";
}
}
}
class XYorderPzz extends OrderPzz{
Pizza pizza=null;
@Override
Pizza createPizza(String orderType) {
if (orderType.equals("A")){
pizza = new XinYi_A_Pizza();
}else if (orderType.equals("B")){
pizza = new XinYi_B_Pizza();
}
return pizza;
}
}
class GZorderPzz extends OrderPzz{
Pizza pizza=null;
@Override
Pizza createPizza(String orderType) {
if (orderType.equals("A")){
pizza = new GuangZhou_A_Pizza();
}else if (orderType.equals("B")){
pizza = new GuangZhou_B_Pizza();
}
return pizza;
}
}
//Pizza部分----------------------------------------------------------------
abstract class Pizza{
protected String name; //名字
public void setName(String name) {
this.name = name;
}
//准备原材料, 不同的披萨不一样,因此,我们做成抽象方法
public abstract void prepare();
public void before(){
System.out.println("准备做"+name+"了。。");
}
public void middle() {
System.out.println("正在进行"+name+"的中间步骤。");
}
public void finish() {
System.out.println(name+"做好了。");
}
}
class XinYi_A_Pizza extends Pizza{
@Override
public void prepare() {
setName("信宜的——A-pizza");
System.out.println("信宜的——A-pizza准备原材料");
}
}
class XinYi_B_Pizza extends Pizza{
@Override
public void prepare() {
setName("信宜的——B-pizza");
System.out.println("信宜的——B-pizza准备原材料");
}
}
class GuangZhou_A_Pizza extends Pizza{
@Override
public void prepare() {
setName("广州的——A-pizza");
System.out.println("广州的——A-pizza准备原材料");
}
}
class GuangZhou_B_Pizza extends Pizza{
@Override
public void prepare() {
setName("广州的——B-pizza");
System.out.println("广州的——B-pizza准备原材料");
}
}
4.3抽象工厂模式
- 抽象工厂模式:定义了一个 interface 用于创建相关或有依赖关系的对象簇,而无需指明具体的类
- 抽象工厂模式可以将简单工厂模式和工厂方法模式进行整合。
- 从设计层面看,抽象工厂模式就是对简单工厂模式的改进(或者称为进一步的抽象)。
- 将工厂抽象成两层,AbsFactory(抽象工厂) 和具体实现的工厂子类。程序员可以根据创建对象类型使用对应的工厂子类。这样将单个的简单工厂类变成了工厂簇,更利于代码的维护和扩展。
4.4工厂模式小结
- 工厂模式的意义
将实例化对象的代码提取出来,放到一个类中统一管理和维护,达到和主项目的依赖关系的解耦。从而提高项目的扩展和维护性。 - 三种工厂模式 (简单工厂模式、工厂方法模式、抽象工厂模式)
- 设计模式的依赖抽象原则
①创建对象实例时,不要直接 new 类, 而是把这个 new 类的动作放在一个工厂的 方法中,并返回。有的书上说,变量不要直接持有具体类的引用。
②不要让类继承具体类,而是继承抽象类或者是实现 interface(接口)
③不要覆盖基类中已经实现的方法。
五、原型模式
- 原型模式(Prototype 模式)是指:用原型实例指定创建对象的种类,并且通过拷贝这些原型,创建新的对象
- 原型模式是一种创建型设计模式,允许一个对象再创建另外一个可定制的对象,无需知道如何创建的细节
- 工作原理是:通过将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝它们自己来实施创建,即对象.clone()
- 形象的理解:孙大圣拔出猴毛,变出其它孙大圣
tips:clone的类需要实现Cloneable接口,序列化需要实现Serializable接口
- Prototype : 原型类,声明一个克隆自己的接口
- ConcretePrototype: 具体的原型类, 实现一个克隆自己的操作
- Client: 让一个原型对象克隆自己,从而创建一个新的对象(属性一样)
5.1浅拷贝
- 对于数据类型是基本数据类型的成员变量,浅拷贝会直接进行值传递,也就是将该属性值复制一份给新的对象。
- 对于数据类型是引用数据类型的成员变量,比如说成员变量是某个数组、某个类的对象等,那么浅拷贝会进行引用传递,也就是只是将该成员变量的引用值(内存地址)复制一份给新的对象。因为实际上两个对象的该成员变量都指向同一个实例。在这种情况下,在一个对象中修改该成员变量会影响到另一个对象的该成员变量值
- 浅拷贝是使用默认的 clone()方法来实现 sheep = (Sheep) super.clone();
5.2深拷贝
- 复制对象的所有基本数据类型的成员变量值
- 为所有引用数据类型的成员变量申请存储空间,并复制每个引用数据类型成员变量所引用的对象,直到该对象可达的所有对象。也就是说,对象进行深拷贝要对整个对象(包括对象的引用类型)进行拷贝
- 深拷贝实现方式 1:重写 clone 方法来实现深拷贝
- 深拷贝实现方式 2:通过对象序列化实现深拷贝(推荐)
5.3原型模式的注意事项和细节
- 创建新的对象比较复杂时,可以利用原型模式简化对象的创建过程,同时也能够提高效率
- 不用重新初始化对象,而是动态地获得对象运行时的状态
- 如果原始对象发生变化(增加或者减少属性),其它克隆对象的也会发生相应的变化,无需修改代码
- 在实现深克隆的时候可能需要比较复杂的代码
缺点:
- 需要为每一个类配备一个克隆方法,这对全新的类来说不是很难,但对已有的类进行改造时,需要修改其源代码,违背了 ocp 原则.
七、适配器模式
- 适配器模式(Adapter Pattern)将某个类的接口转换成客户端期望的另一个接口表示,主的目的是兼容性,让原本因接口不匹配不能一起工作的两个类可以协同工作。其别名为包装器(Wrapper)
- 适配器模式属于结构型模式
- 主要分为三类:类适配器模式、对象适配器模式、接口适配器模式
工作原理
- 适配器模式:将一个类的接口转换成另一种接口.让原本接口不兼容的类可以兼容
- 从用户的角度看不到被适配者,是解耦的
- 用户调用适配器转化出来的目标接口方法,适配器再调用被适配者的相关接口方法
- 用户收到反馈结果,感觉只是和目标接口交互
7.1类适配器模式
- Adapter(适配器)类,通过继承 src (被适配的类)类,实现 dst (含有输出目标结果的类的接口)类接口,完成 src->dst 的适配。
应用实例说明
- 以生活中充电器的例子来讲解适配器,充电器本身相当于 Adapter,220V 交流电相当于 src (即被适配者),我们的目 dst(即目标)是 5V 直流电
public class myClassAdapter {
public static void main(String[] args) {
System.out.println(" === 类适配器模式 ====");
M_Phone phone = new M_Phone();
phone.charging(new M_VoltageAdapter());
}
}
//被适配的类
class Voltage_220V {
//输出220V的电压
public int output_220V() {
int src = 220;
System.out.println("被适配的电压=" + src + "伏");
return src;
}
}
//适配接口
interface IVoltage_5V {
public int output_5V();
}
//适配器类
class M_VoltageAdapter extends Voltage_220V implements IVoltage_5V {
@Override
public int output_5V() {
//获取到220V电压
int srcV = output_220V();
int dstV = srcV / 44 ; //转成 5v
return dstV;
}
}
class M_Phone {
//充电
public void charging(IVoltage_5V iVoltage_5V) {
if(iVoltage_5V.output_5V() == 5) {
System.out.println("电压为5V, 正在充电~~");
} else if (iVoltage_5V.output_5V() > 5) {
System.out.println("电压大于5V, 充电失败!");
}
}
}
类适配器模式注意事项和细节
- Java 是单继承机制,所以类适配器需要继承 src 类这一点算是一个缺点, 因为这要求 dst 必须是接口,有一定局限性
- src 类的方法在 Adapter 中都会暴露出来,也增加了使用的成本。
- 由于其继承了 src 类,所以它可以根据需求重写 src 类的方法,使得 Adapter 的灵活性增强了。
7.2对象适配器模式
- 基本思路和类的适配器模式相同,只是将 Adapter 类作修改,不是继承 src 类,而是持有 src 类的实例,以解决兼容性的问题。即:持有 src 类,实现 dst 类接口,完成 src->dst 的适配
- 根据“合成复用原则”,在系统中尽量使用关联关系(聚合)来替代继承关系。
- 对象适配器模式是适配器模式常用的一种
public class myObjectAdapter {
public static void main(String[] args) {
System.out.println(" === 对象适配器模式 ====");
//new 一个适配器
M_VoltageAdapter m_voltageAdapter = new M_VoltageAdapter(new Voltage_220V());
//充电
M_Phone phone = new M_Phone();
phone.charging(m_voltageAdapter);
}
}
//被适配的类
class Voltage_220V {
//输出220V的电压
public int output_220V() {
int src = 220;
System.out.println("object被适配的电压=" + src + "伏");
return src;
}
}
//适配接口
interface IVoltage_5V {
public int output_5V();
}
//适配器类
class M_VoltageAdapter implements IVoltage_5V {
private Voltage_220V voltage_220V; // 关联关系-聚合
//通过构造器,220V 实例
public M_VoltageAdapter(Voltage_220V voltage_220V) {
this.voltage_220V=voltage_220V;
}
@Override
public int output_5V() {
//获取到220V电压
int srcV = voltage_220V.output_220V();
int dstV = srcV / 44 ; //转成 5v
return dstV;
}
}
class M_Phone {
//充电
public void charging(IVoltage_5V iVoltage_5V) {
if(iVoltage_5V.output_5V() == 5) {
System.out.println("object电压为5V, 正在充电~~");
} else if (iVoltage_5V.output_5V() > 5) {
System.out.println("object电压大于5V, 充电失败!");
}
}
}
对象适配器模式注意事项和细节
- 对象适配器和类适配器其实算是同一种思想,只不过实现方式不同。
根据合成复用原则,使用组合替代继承,所以它解决了类适配器必须继承 src 的局限性问题,也不再要求 dst 必须是接口。 - 使用成本更低,更灵活。
7.3接口适配器模式
- 一些书籍称为:适配器模式(Default Adapter Pattern)或缺省适配器模式。
- 核心思路:当不需要全部实现接口提供的方法时,可先设计一个抽象类实现接口,并为该接口中每个方法提供一个默认实现(空方法),那么该抽象类的子类可有选择地覆盖父类的某些方法来实现需求
- 适用于一个接口不想使用其所有的方法的情况。
public class myIbterfaceAdapter {
public static void main(String[] args) {
AbsAdapter absAdapter = new AbsAdapter() {
//只需要去覆盖我们 需要使用 接口方法
@Override
public void m1() {
System.out.println("只使用了m1的方法");
}
};
absAdapter.m1();
}
}
//接口
interface Interface {
public void m1();
public void m2();
public void m3();
public void m4();
}
//在AbsAdapter 我们将 Interface4 的方法进行默认实现
abstract class AbsAdapters implements Interface {
//默认实现(空实现)
public void m1() {}
public void m2() {}
public void m3() {}
public void m4() {}
}
八、桥接模式
- 桥接模式(Bridge 模式)是指:将实现与抽象放在两个不同的类层次中,使两个层次可以独立改变。
- 是一种结构型设计模式
- Bridge 模式基于类的最小设计原则,通过使用封装、聚合及继承等行为让不同的类承担不同的职责。它的主要特点是把抽象(Abstraction)与行为实现(Implementation)分离开来,从而可以保持各部分的独立性以及应对他们的功能扩展
8.1手机的例子
- phone充当桥类
public class myBridge {
public static void main(String[] args) {
System.out.println("====================折叠的华为手机====================");
phone p1=new folderPhone(new huawei());
p1.open();
p1.call();
p1.close();
System.out.println("====================折叠的荣耀手机====================");
phone p2=new folderPhone(new honor());
p2.open();
p2.call();
p2.close();
System.out.println("====================云端的华为手机====================");
phone p3=new onlinePhone(new huawei());
p3.open();
p3.call();
p3.close();
System.out.println("====================云端的荣耀手机====================");
phone p4=new onlinePhone(new honor());
p4.open();
p4.call();
p4.close();
}
}
//接口(把品牌抽象)
interface brand {
void open();
void call();
void close();
}
class huawei implements brand{
@Override
public void open() {
System.out.println("华为手机开机。。");
}
@Override
public void call() {
System.out.println("华为手机打电话。。");
}
@Override
public void close() {
System.out.println("华为手机关机。。");
}
}
class honor implements brand{
@Override
public void open() {
System.out.println("荣耀手机开机!!");
}
@Override
public void call() {
System.out.println("荣耀手机打电话!!");
}
@Override
public void close() {
System.out.println("荣耀手机关机!!");
}
}
abstract class phone{
//组合品牌
private brand brands;
//构造器
public phone(brand brands){
super(); //从子类中调用父类的构造方法
this.brands = brands;
}
//这三个方法让子类重写,添加新的功能
protected void open(){
this.brands.open();
}
protected void call(){
this.brands.call();
}
protected void close(){
this.brands.close();
}
}
class folderPhone extends phone{
//调用父类的构造方法
public folderPhone(brand brands) {
super(brands);
}
//重写父类的三个方法并加入相关功能
public void open(){
System.out.print("折叠的");
super.open();
}
public void call(){
System.out.print("折叠的");
super.call();
}
public void close(){
System.out.print("折叠的");
super.close();
}
}
class onlinePhone extends phone{
public onlinePhone(brand brands) {
super(brands);
}
//重写父类的三个方法并添加相应的功能
public void open(){
System.out.print("云端的");
super.open();
}
public void call(){
System.out.print("云端的");
super.call();
}
public void close(){
System.out.print("云端的");
super.close();
}
}
8.2桥接模式的注意事项和细节
- 实现了抽象和实现部分的分离,从而极大的提供了系统的灵活性,让抽象部分和实现部分独立开来,这有助于系统进行分层设计,从而产生更好的结构化系统。
- 对于系统的高层部分,只需要知道抽象部分和实现部分的接口就可以了,其它的部分由具体业务来完成。
- 桥接模式替代多层继承方案,可以减少子类的个数,降低系统的管理和维护成本。
- 桥接模式的引入增加了系统的理解和设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计和编程
- 桥接模式要求正确识别出系统中两个独立变化的维度(抽象、和实现),因此其使用范围有一定的局限性,即需要有这样的应用场景。
桥接模式其它应用场景
- 对于那些不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统,桥接模式尤为适用.
常见的应用场景:
- -JDBC 驱动程序
- -银行转账系统
转账分类: 网上转账,柜台转账,AMT 转账
转账用户类型:普通用户,银卡用户,金卡用户… - -消息管理
消息类型:即时消息,延时消息消息分类:手机短信,邮件消息,QQ 消息…
九、装饰者模式
装饰者模式:动态的将新功能附加到对象上。在对象功能扩展方面,它比继承更有弹性,装饰者模式也体现了开闭原则(ocp)
9.1星巴克咖啡订单项目
- 咖啡种类/单品咖啡:Espresso(意大利浓咖啡)、ShortBlack、LongBlack(美式咖啡)、Decaf(无因咖啡)
- 调料:Milk、Soy(豆浆)、Chocolate
- 要求在扩展新的咖啡种类时,具有良好的扩展性、改动方便、维护方便
- 使用 OO 的来计算不同种类咖啡的费用: 客户可以点单品咖啡,也可以单品咖啡+调料组合。
public class myDeorator {
public static void main(String[] args) {
// 装饰者模式下的订单:2份巧克力+一份牛奶的LongBlack
// 1. 点一份 LongBlack
Drinks order = new LongBlacks();
System.out.println("描述:一杯" + order.getDes()+"费用=" + order.cost());
// 2. order 加入一份牛奶
order = new Milks(order);
System.out.println("描述:加入一份牛奶" + order.getDes() +"费用=" + order.cost());
// 3. order 加入一份巧克力
order = new Chocolates(order);
System.out.println("描述:加入一份牛奶 加入一份巧克力"+ order.getDes()+" 费用 =" + order.cost());
// 3. order 加入一份巧克力
order = new Chocolates(order);
System.out.println("描述:加入一份牛奶 加入2份巧克力" + order.getDes()+ "费用 =" + order.cost());
}
}
abstract class Drinks {
public String des; // 描述
private double price;
public String getDes() {
return des;
}
public void setDes(String des) {
this.des = des;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
//计算费用的抽象方法,子类来实现
public abstract double cost();
}
class Coffees extends Drinks {
@Override
public double cost() {
return super.getPrice();
}
}
class ShortBlacks extends Coffees{
public ShortBlacks() {
setDes(" shortblack ");
setPrice(4.0);
}
}
class Espressos extends Coffees{
public Espressos() {
setDes(" 意大利咖啡 ");
setPrice(6.0);
}
}
class LongBlacks extends Coffees {
public LongBlacks() {
setDes(" longblack ");
setPrice(5.0);
}
}
class Decorators extends Drinks {
private Drinks obj;
public Decorators(Drinks obj) { //组合
this.obj = obj;
}
@Override
public double cost() {
// getPrice 自己价格
return super.getPrice() + obj.cost();
}
@Override
public String getDes() {
// obj.getDes() 输出被装饰者的信息
return des + "单价:" + getPrice() + " && " + obj.getDes();
}
}
//具体的Decorator, 这里就是调味品
class Chocolates extends Decorators {
public Chocolates(Drinks obj) {
super(obj);
setDes(" 巧克力 ");
setPrice(3.0); // 调味品 的价格
}
}
class Milks extends Decorators {
public Milks(Drinks obj) {
super(obj);
setDes(" 牛奶 ");
setPrice(2.0);
}
}
class Soys extends Decorators{
public Soys(Drinks obj) {
super(obj);
setDes(" 豆浆 ");
setPrice(1.5);
}
}
十、组合模式
- 组合模式(Composite Pattern),又叫部分整体模式,它创建了对象组的树形结构,将对象组合成树状结构以表示“整体-部分”的层次关系
- 组合模式依据树形结构来组合对象,用来表示部分以及整体层次。
- 这种类型的设计模式属于结构型模式。
- 组合模式使得用户对单个对象和组合对象的访问具有一致性,即:组合能让客户以一致的方式处理个别对象以及组合对象
组合模式原理类图
- Component :这是组合中对象声明接口,在适当情况下,实现所有类共有的接口默认行为,用于访问和管理Component 子部件, Component 可以是抽象类或者接口
- Leaf : 在组合中表示叶子节点,叶子节点没有子节点
- Composite :非叶子节点,用于存储子部件,在 Component 接口中实现子部件的相关操作,比如增加(add), 删除。
10.1组合模式解决学校院系展示的应用实例
应用实例要求
- 编写程序展示一个学校院系结构:需求是这样,要在一个页面中展示出学校的院系组成,一个学校有多个学院,一个学院有多个系。
- 思路分析和图解(类图)
import java.util.ArrayList;
import java.util.List;
public class myComposite {
public static void main(String[] args) {
//从大到小创建对象 学校
OrganizationComponents university = new Universitys("清华大学", " 中国顶级大学 ");
//创建 学院
OrganizationComponents computerCollege = new Colleges("计算机学院", " 计算机学院 ");
OrganizationComponents infoEngineercollege = new Colleges("信息工程学院", " 信息工程学院 ");
//创建各个学院下面的系(专业)
computerCollege.add(new Departments("软件工程", " 软件工程不错 "));
computerCollege.add(new Departments("网络工程", " 网络工程不错 "));
computerCollege.add(new Departments("计算机科学与技术", " 计算机科学与技术是老牌的专业 "));
infoEngineercollege.add(new Departments("通信工程", " 通信工程不好学 "));
infoEngineercollege.add(new Departments("信息工程", " 信息工程好学 "));
//将学院加入到 学校
university.add(computerCollege);
university.add(infoEngineercollege);
university.print();
System.out.println("========================================================");
infoEngineercollege.print();
}
}
abstract class OrganizationComponents {
private String name; // 名字
private String des; // 说明
//构造器
public OrganizationComponents(String name, String des) {
super();
this.name = name;
this.des = des;
}
protected void add(OrganizationComponents organizationComponents) {
//默认实现(先实现,因为如果声明为abstract,那leaf不需要实现这个方法的就亏了)
throw new UnsupportedOperationException();
}
protected void remove(OrganizationComponents organizationComponents) {
//默认实现
throw new UnsupportedOperationException();
}
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();
}
//University 就是 Composite , 可以管理College
class Universitys extends OrganizationComponents {
//Lis存放Colleges
List<OrganizationComponents> organizationComponentsH = new ArrayList<OrganizationComponents>();
// 构造器
public Universitys(String name, String des) {
super(name, des);
}
// 重写add
@Override
protected void add(OrganizationComponents organizationComponents) {
organizationComponentsH.add(organizationComponents);
}
// 重写remove
@Override
protected void remove(OrganizationComponents organizationComponents) {
organizationComponentsH.remove(organizationComponents);
}
@Override
public String getName() {
return super.getName();
}
@Override
public String getDes() {
return super.getDes();
}
// print方法,就是输出University 包含的学院
@Override
protected void print() {
System.out.println("==================" + getName() + "======================");
//遍历 organizationComponentsH
for (OrganizationComponents organizationComponent : organizationComponentsH) {
organizationComponent.print();
}
}
}
class Colleges extends OrganizationComponents {
//List中存放的Department
List<OrganizationComponents> organizationComponentsH = new ArrayList<OrganizationComponents>();
// 构造器
public Colleges(String name, String des) {
super(name, des);
}
// 重写add
@Override
protected void add(OrganizationComponents organizationComponents) {
// 将来实际业务中,Colleage 的 add 和 University add 不一定完全一样
organizationComponentsH.add(organizationComponents);
}
// 重写remove
@Override
protected void remove(OrganizationComponents organizationComponents) {
organizationComponentsH.remove(organizationComponents);
}
@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 (OrganizationComponents organizationComponent : organizationComponentsH) {
organizationComponent.print();
}
}
}
class Departments extends OrganizationComponents {
//没有集合
public Departments(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());
}
}
10.2组合模式的注意事项和细节
- 简化客户端操作。客户端只需要面对一致的对象而不用考虑整体部分或者节点叶子的问题。
- 具有较强的扩展性。当我们要更改组合对象时,我们只需要调整内部的层次关系,客户端不用做出任何改动.
- 方便创建出复杂的层次结构。客户端不用理会组合里面的组成细节,容易添加节点或者叶子从而创建出复杂的树形结构
- 需要遍历组织机构,或者处理的对象具有树形结构时, 非常适合使用组合模式.
- 要求较高的抽象性,如果节点和叶子有很多差异性的话,比如很多方法和属性都不一样,不适合使用组合模式
十一、外观模式
- 外观模式(Facade),也叫“过程模式:外观模式为子系统中的一组接口提供一个一致的界面,此模式定义了一个高层接口,这个接口使得这一子系统更加容易使用
- 外观模式通过定义一个一致的接口,用以屏蔽内部子系统的细节,使得调用端只需跟这个接口发生调用,而无需关心这个子系统的内部细节
外观模式原理类图
- 外观类(Facade): 为调用端提供统一的调用接口, 外观类知道哪些子系统负责处理请求,从而将调用端的请求代理给适当子系统对象
- 调用者(Client): 外观接口的调用者
- 子系统的集合:指模块或者子系统,处理 Facade 对象指派的任务,他是功能的实际提供者
11.1外观模式解决影院管理
- 外观模式可以理解为转换一群接口,客户只要调用一个接口,而不用调用多个接口才能达到目的。比如:在 pc 上安装软件的时候经常有一键安装选项(省去选择安装目录、安装的组件等等),还有就是手机的重启功能(把关机和启动合为一个操作)。
- 外观模式就是解决多个复杂接口带来的使用困难,起到简化用户操作的作用
- 示意图说明
实例类图
public class myFacade {
public static void main(String[] args) {
HomeTheaterFacades homeTheaterFacades = new HomeTheaterFacades();
System.out.println("准备阶段========================");
homeTheaterFacades.ready();
System.out.println("工作阶段========================");
homeTheaterFacades.play();
System.out.println("暂停阶段========================");
homeTheaterFacades.pause();
System.out.println("结束状态========================");
homeTheaterFacades.end();
}
}
class HomeTheaterFacades {
//定义各个子系统对象
private TheaterLights theaterLight;
private Popcorns popcorn;
private Stereos stereo;
private Projectors projector;
private Screens screen;
private DVDPlayers dVDPlayer;
//构造器
public HomeTheaterFacades() {
super();
//因为各种设备都是静态(static)的,通过类名直接调用
this.theaterLight = TheaterLights.getInstance();
this.popcorn = Popcorns.getInstance();
this.stereo = Stereos.getInstance();
this.projector = Projectors.getInstance();
this.screen = Screens.getInstance();
this.dVDPlayer = DVDPlayers.getInstanc();
}
//操作分成 4 步
public void ready() {
popcorn.on();
popcorn.pop();
screen.down();
projector.on();
stereo.on();
dVDPlayer.on();
theaterLight.dim();
}
public void play() {
dVDPlayer.play();
}
public void pause() {
dVDPlayer.pause();
}
public void end() {
popcorn.off();
theaterLight.bright();
screen.up();
projector.off();
stereo.off();
dVDPlayer.off();
}
}
//各种设备
class DVDPlayers {
//使用单例模式, 使用饿汉式
private static DVDPlayers instance = new DVDPlayers();
public static DVDPlayers getInstanc() {
return 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 ..");
}
}
class Popcorns {
private static Popcorns instance = new Popcorns();
public static Popcorns getInstance() {
return 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 ");
}
}
class Projectors {
private static Projectors instance = new Projectors();
public static Projectors getInstance() {
return 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 ");
}
//...
}
class Screens {
private static Screens instance = new Screens();
public static Screens getInstance() {
return instance;
}
public void up() {
System.out.println(" Screen up ");
}
public void down() {
System.out.println(" Screen down ");
}
}
class Stereos {
private static Stereos instance = new Stereos();
public static Stereos getInstance() {
return 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.. ");
}
//...
}
class TheaterLights {
private static TheaterLights instance = new TheaterLights();
public static TheaterLights getInstance() {
return 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.. ");
}
}
十二、享元模式
- 享元模式(Flyweight Pattern)也叫蝇量模式: 运用共享技术有效地支持大量细粒度的对象
- 常用于系统底层开发,解决系统的性能问题。像数据库连接池,里面都是创建好的连接对象,在这些连接对象中有我们需要的则直接拿来用,避免重新创建,如果没有我们需要的,则创建一个
- 享元模式能够解决重复对象的内存浪费的问题,当系统中有大量相似对象,需要缓冲池时。不需总是创建新对象,可以从缓冲池里拿。这样可以降低系统内存,同时提高效率
- 享元模式经典的应用场景就是池技术了,String 常量池、数据库连接池、缓冲池等等都是享元模式的应用,享元模式是池技术的重要实现方式
享元模式的原理类图
- FlyWeight 是抽象的享元角色, 他是产品的抽象类, 同时定义出对象的外部状态和内部状态(后面介绍) 的接口或实现
- ConcreteFlyWeight 是具体的享元角色,是具体的产品类,实现抽象角色定义相关业务
- UnSharedConcreteFlyWeight 是不可共享的角色,一般不会出现在享元工厂。
- FlyWeightFactory 享元工厂类,用于构建一个池容器(集合),同时提供从池中获取对象方法
12.1内部状态和外部状态
比如围棋、五子棋、跳棋,它们都有大量的棋子对象,围棋和五子棋只有黑白两色,跳棋颜色多一点,所以棋子颜色就是棋子的内部状态;而各个棋子之间的差别就是位置的不同,当我们落子后,落子颜色是定的,但位置是变化的,所以棋子坐标就是棋子的外部状态
- 享元模式提出了两个要求:细粒度和共享对象。这里就涉及到内部状态和外部状态了,即将对象的信息分为两个部分:内部状态和外部状态
- 内部状态指对象共享出来的信息,存储在享元对象内部且不会随环境的改变而改变
- 外部状态指对象得以依赖的一个标记,是随环境改变而改变的、不可共享的状态。
- 举个例子:围棋理论上有 361 个空位可以放棋子,每盘棋都有可能有两三百个棋子对象产生,因为内存空间有限,一台服务器很难支持更多的玩家玩围棋游戏,如果用享元模式来处理棋子,那么棋子对象就可以减少到只有两个实例,这样就很好的解决了对象的开销问题
12.2享元模式解决网站展现项目
import java.util.HashMap;
public class myFlyweight {
public static void main(String[] args) {
// 创建一个工厂类
WebSiteFactorys factory = new WebSiteFactorys();
// 客户要一个以新闻形式发布的网站
WebSites webSite1 = factory.getWebSiteCategory("新闻");
webSite1.use(new Users("tom"));
// 客户要一个以博客形式发布的网站
WebSites webSite2 = factory.getWebSiteCategory("博客");
webSite2.use(new Users("jack"));
// 客户要一个以博客形式发布的网站
WebSites webSite3 = factory.getWebSiteCategory("博客");
webSite3.use(new Users("smith"));
// 客户要一个以博客形式发布的网站
WebSites webSite4 = factory.getWebSiteCategory("博客");
webSite4.use(new Users("king"));
System.out.println("网站的分类共=" + factory.getWebSiteCount());
}
}
//抽象层
abstract class WebSites {
public abstract void use(Users user);//抽象方法
}
//具体网站
class ConcreteWebSites extends WebSites {
//共享的部分,内部状态
private String type = ""; //网站发布的形式(类型)
//构造器
public ConcreteWebSites(String type) {
this.type = type;
}
@Override
public void use(Users user) {
System.out.println("网站的发布形式为:" + type + " 在使用中 .. 使用者是" + user.getName());
}
}
// 网站工厂类,根据需要返回压一个网站
class WebSiteFactorys {
//集合, 充当池的作用
private HashMap<String, ConcreteWebSites> pool = new HashMap<>();
//根据网站的类型,返回一个网站, 如果没有就创建一个网站,并放入到池中,并返回
public WebSites getWebSiteCategory(String type) {
if(!pool.containsKey(type)) {
//就创建一个网站,并放入到池中
pool.put(type, new ConcreteWebSites(type));
}
return (WebSites)pool.get(type);
}
//获取网站分类的总数 (池中有多少个网站类型)
public int getWebSiteCount() {
return pool.size();
}
}
class Users {
private String name;
public Users(String name) {
super();
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
12.3享元模式的注意事项和细节
- 在享元模式这样理解,“享”就表示共享,“元”表示对象
- 系统中有大量对象,这些对象消耗大量内存,并且对象的状态大部分可以外部化时,我们就可以考虑选用享元模式
- 用唯一标识码判断,如果在内存中有,则返回这个唯一标识码所标识的对象,用 HashMap/HashTable 存储
- 享元模式大大减少了对象的创建,降低了程序内存的占用,提高效率
- 享元模式提高了系统的复杂度。需要分离出内部状态和外部状态,而外部状态具有固化特性,不应该随着内部状态的改变而改变,这是我们使用享元模式需要注意的地方.
- 使用享元模式时,注意划分内部状态和外部状态,并且需要有一个工厂类加以控制。
- 享元模式经典的应用场景是需要缓冲池的场景,比如 String 常量池、数据库连接池
十三、代理模式(Proxy)
- 代理模式:为一个对象提供一个替身,以控制对这个对象的访问。即通过代理对象访问目标对象.这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能。
- 被代理的对象可以是远程对象、创建开销大的对象或需要安全控制的对象
- 代理模式有不同的形式, 主要有三种静态代理、动态代理 (JDK 代理、接口代理)和 Cglib 代理 (可以在内存动态的创建对象,而不需要实现接口,他是属于动态代理的范畴) 。
13.1静态代理
静态代理在使用时,需要定义接口或者父类,被代理对象(即目标对象)与代理对象一起实现相同的接口或者是继承相同父类
应用实例
- 定义一个接口:ITeacherDao
- 目标对象 TeacherDAO 实现接口 ITeacherDAO
- 使用静态代理方式,就需要在代理对象 TeacherDAOProxy 中也实现 ITeacherDAO
- 调用的时候通过调用代理对象的方法来调用目标对象.
- 特别提醒:代理对象与目标对象要实现相同的接口,然后通过调用相同的方法来调用目标对象的方法
思路分析图解(类图)
public class myStaticproxy {
public static void main(String[] args) {
//创建目标对象(被代理对象)
TeacherDaos teacherDao = new TeacherDaos();
//创建代理对象, 同时将被代理对象传递给代理对象
TeacherDaoProxys teacherDaoProxy = new TeacherDaoProxys(teacherDao);
//通过代理对象,调用到被代理对象的方法
//即:执行的是代理对象的方法,代理对象再去调用目标对象的方法
teacherDaoProxy.teach();
}
}
//接口
interface ITeacherDaos {
void teach(); // 授课的方法
}
class TeacherDaos implements ITeacherDaos {
@Override
public void teach() {
System.out.println("老师授课中 ........................");
}
}
//代理对象,静态代理
class TeacherDaoProxys implements ITeacherDaos{
private ITeacherDaos target; // 目标对象,通过接口来聚合
//构造器
public TeacherDaoProxys(ITeacherDaos target) {
this.target = target;
}
@Override
public void teach() {
System.out.println("开始代理 完成某些操作................. ");//方法
target.teach();
System.out.println("提交。。。。。");//方法
}
}
静态代理优缺点
- 优点:在不修改目标对象的功能前提下, 能通过代理对象对目标功能扩展
- 缺点:因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类,一旦接口增加方法,目标对象与代理对象都要维护
13.2动态代理
- 代理对象不需要实现接口,但是目标对象要实现接口,否则不能用动态代理
- 代理对象的生成,是利用 JDK 的 API,动态的在内存中构建代理对象
- 动态代理也叫做:JDK 代理、接口代理
JDK 中生成代理对象的 API
- 代理类所在包:java.lang.reflect.Proxy
- JDK 实现代理只需要使用 newProxyInstance 方法,但是该方法需要接收三个参数,完整的写法是:
static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h )
//1. ClassLoader loader:指定当前目标对象使用的类加载器, 获取加载器的方法固定
//2. Class<?>[] interfaces:目标对象实现的接口类型,使用泛型方法确认类型
//3. InvocationHandler h :事情处理,执行目标对象的方法时,会触发事情处理器方法, 会把当前执行的目标对象方法作为参数传入
动态代理应用实例
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class myDynamicproxy {
public static void main(String[] args) {
//创建目标对象
ITeacherDaos target = new TeacherDaos();
//给目标对象,创建代理对象, 可以转成 ITeacherDao
//ProxyFactorys proxyFactorys = new ProxyFactorys(target);
//ITeacherDaos proxyInstance = (ITeacherDaos)proxyFactorys.getProxyInstance();
ITeacherDaos proxyInstance = (ITeacherDaos)new ProxyFactorys(target).getProxyInstance();
//通过代理对象,调用目标对象的方法
proxyInstance.teach();
System.out.println("============================");
proxyInstance.sayHello(" huan ");
//proxyInstance=class com.sun.proxy.$Proxy0 内存中动态生成了代理对象
System.out.println("proxyInstance=" + proxyInstance);
}
}
//接口
interface ITeacherDaos {
void teach(); // 授课方法
void sayHello(String name);
}
class TeacherDaos implements ITeacherDaos {
@Override
public void teach() {
System.out.println(" 老师授课中............ ");
}
@Override
public void sayHello(String name) {
System.out.println("你好 " + name);
}
}
class ProxyFactorys {
//维护一个目标对象 , Object
private Object target;
//构造器 ,对target 进行初始化(Object泛型接收对象)
public ProxyFactorys(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); //target是目标方法,args是参数(如果target有带参数,就会放在这里)
System.out.println("JDK代理提交。。。。。。。。");
return returnVal;
}
});
}
}
13.3Cglib 代理
- 静态代理和 JDK 代理模式都要求目标对象是实现一个接口,但是有时候目标对象只是一个单独的对象,并没有实现任何的接口,这个时候可使用目标对象子类来实现代理-这就是 Cglib 代理
- Cglib 代理也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能扩展, 有些书也将 Cglib 代理归属到动态代理。
- Cglib 是一个强大的高性能的代码生成包,它可以在运行期扩展 java 类与实现 java 接口.它广泛的被许多 AOP 的框架使用,例如 Spring AOP,实现方法拦截
- 在 AOP 编程中如何选择代理模式:
- 目标对象需要实现接口,用 JDK 代理
- 目标对象不需要实现接口,用 Cglib 代理
- Cglib 包的底层是通过使用字节码处理框架 ASM 来转换字节码并生成新的类
Cglib 代理模式实现步骤- 需要引入 cglib 的 jar 文件
- 在内存中动态构建子类,注意代理的类不能为 final,否则报错 java.lang.IllegalArgumentException:
- 目标对象的方法如果为 final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法.
Cglib 代理模式应用实例
思路图解(类图)
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class myCglibproxy {
public static void main(String[] args) {
//创建目标对象s
TeacherDaos target = new TeacherDaos();
//获取到代理对象,并且将目标对象传递给代理对象
TeacherDaos proxyInstance = (TeacherDaos)new ProxyFactorys(target).getProxyInstance();
//执行代理对象的方法,触发intecept 方法,从而实现 对目标对象的调用
String res = proxyInstance.teach();
System.out.println("res=" + res);
}
}
class TeacherDaos {
public String teach() {
System.out.println(" 老师授课中............. 我是cglib代理,不需要实现接口 ");
return "您好";
}
}
class ProxyFactorys implements MethodInterceptor {
//维护一个目标对象
private Object target;
//构造器,传入一个被代理的对象
public ProxyFactorys(Object target) {
this.target = target;
}
//返回一个代理对象: 是 target 对象的代理对象
public Object getProxyInstance() {
//1. 创建一个工具类
Enhancer enhancer = new Enhancer();
//2. 设置父类
enhancer.setSuperclass(target.getClass());
//3. 设置回调函数
enhancer.setCallback(this);
//4. 创建子类对象,即代理对象
return enhancer.create();
}
//重写 intercept 方法,会调用目标对象的方法
@Override
public Object intercept(Object arg0, Method method, Object[] args, MethodProxy arg3) throws Throwable {
System.out.println("Cglib代理模式..............开始");
Object returnVal = method.invoke(target, args); //Method method方法回调,Object[] args保存方法的参数
System.out.println("Cglib代理模式。。。。。。。。提交");
return returnVal;
}
}
十六、访问者模式
- 访问者模式(Visitor Pattern),封装一些作用于某种数据结构的各元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的新的操作。
- 主要将数据结构与数据操作分离,解决数据结构和操作耦合性问题
- 访问者模式的基本工作原理是:在被访问的类里面加一个对外提供接待访问者的接口
- 访问者模式主要应用场景是:需要对一个对象结构中的对象进行很多不同操作(这些操作彼此没有关联),同时需要避免让这些操作"污染"这些对象的类,可以选用访问者模式解决
访问者模式的原理类图
- Visitor 是抽象访问者,为该对象结构中的 ConcreteElement 的每一个类声明一个 visit 操作
- ConcreteVisitor :是一个具体的访问值实现每个有 Visitor 声明的操作,是每个操作实现的部分
- ObjectStructure 能枚举它的元素,可以提供一个高层的接口,用来允许访问者访问元素
- Element 定义一个 accept 方法,接收一个访问者对象
- ConcreteElement 为具体元素,实现了 accept 方法
16.1访问者模式应用实例
应用实例要求
- 将人分为男人和女人,对歌手进行测评,当看完某个歌手表演后,得到他们对该歌手不同的评价(评价有不同的种类,比如成功、失败等),请使用访问者模式来说实现
- 思路分析和图解(类图)
import java.util.LinkedList;
import java.util.List;
public class myVistor {
public static void main(String[] args) {
//创建ObjectStructure
ObjectStructures objectStructure = new ObjectStructures();
objectStructure.attach(new Mans());
objectStructure.attach(new Womans());
//成功
Successs success = new Successs();
objectStructure.display(success);
System.out.println("===============");
Fails fail = new Fails();
objectStructure.display(fail);
}
}
abstract class Actions {
//得到男的 测评
public abstract void getManResult(Mans man);
//得到女的 测评
public abstract void getWomanResult(Womans woman);
}
abstract class Persons {
//提供一个方法,让访问者可以访问
public abstract void accept(Actions action);
}
//说明
//1. 这里使用到了双分派, 即首先在客户端程序中,将具体状态作为参数传递Woman中(第一次分派)
//2. 然后Woman 类调用作为参数的 "具体方法" 中方法getWomanResult, 同时将自己(this)作为参数
// 传入,完成第二次的分派
class Womans extends Persons{
@Override
public void accept(Actions action) {
action.getWomanResult(this);
}
}
class Mans extends Persons {
@Override
public void accept(Actions action) {
action.getManResult(this);
}
}
class Fails extends Actions {
@Override
public void getManResult(Mans man) {
System.out.println(" 男人给的评价该歌手失败 !");
}
@Override
public void getWomanResult(Womans woman) {
System.out.println(" 女人给的评价该歌手失败 !");
}
}
class Successs extends Actions {
@Override
public void getManResult(Mans man) {
System.out.println(" 男人给的评价该歌手很成功 !");
}
@Override
public void getWomanResult(Womans woman) {
System.out.println(" 女人给的评价该歌手很成功 !");
}
}
//数据结构,管理很多人(Mans , Womans)
class ObjectStructures {
//维护了一个集合
private List<Persons> persons = new LinkedList<>();
//增加到list
public void attach(Persons p) {
persons.add(p);
}
//移除
public void detach(Persons p) {
persons.remove(p);
}
//显示测评情况
public void display(Actions action) {
for(Persons p: persons) {
p.accept(action);
}
}
}
双分派
- 所谓双分派是指不管类怎么变化,我们都能找到期望的方法运行。双分派意味着得到执行的操作取决于请求的种类和两个接收者的类型
- 以上述实例为例,假设我们要添加一个 Wait 的状态类,考察 Man 类和 Woman 类的反应,由于使用了双分派,只需增加一个 Action 子类即可在客户端调用即可,不需要改动任何其他类的代码。
16.2访问者模式的注意事项和细节
优点
- 访问者模式符合单一职责原则、让程序具有优秀的扩展性、灵活性非常高
- 访问者模式可以对功能进行统一,可以做报表、UI、拦截器与过滤器,适用于数据结构相对稳定的系统
缺点
- 具体元素对访问者公布细节,也就是说访问者关注了其他类的内部细节,这是迪米特法则所不建议的, 这样造成了具体元素变更比较困难
- 违背了依赖倒转原则。访问者依赖的是具体元素,而不是抽象元素
- 因此,如果一个系统有比较稳定的数据结构,又有经常变化的功能需求,那么访问者模式就是比较合适的.
十七、迭代器模式
- 迭代器模式(Iterator Pattern)是常用的设计模式,属于行为型模式
- 如果我们的集合元素是用不同的方式实现的,有数组,还有 java 的集合类,或者还有其他方式,当客户端要遍历这些集合元素的时候就要使用多种遍历方式,而且还会暴露元素的内部结构,可以考虑使用迭代器模式解决。
- 迭代器模式,提供一种遍历集合元素的统一接口,用一致的方法遍历集合元素,不需要知道集合对象的底层表示,即:不暴露其内部的结构。
迭代器模式的原理类图
- Iterator :迭代器接口,是系统提供,含义 hasNext, next, remove
- ConcreteIterator : 具体的迭代器类,管理迭代
- Aggregate :一个统一的聚合接口,将客户端和具体聚合解耦
- ConcreteAggreage : 具体的聚合持有对象集合,并提供一个方法,返回一个迭代器,该迭代器可以正确遍历集合
- Client :客户端,通过 Iterator 和 Aggregate 依赖子类
事例
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class MyIterator {
public static void main(String[] args) {
//创建学院
List<Colleges> collegeList = new ArrayList<Colleges>();
ComputerColleges computerCollege = new ComputerColleges();
InfoColleges infoCollege = new InfoColleges();
collegeList.add(computerCollege);
//collegeList.add(infoCollege);
OutPutImpls outPutImpl = new OutPutImpls(collegeList);
outPutImpl.printCollege();
}
}
interface Colleges {
public String getName();
//增加系的方法
public void addDepartment(String name, String desc);
//返回一个迭代器,遍历
public Iterator createIterator();
}
//ϵ
class Departments {
private String name;
private String desc;
public Departments(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;
}
}
class ComputerColleges implements Colleges {
Departments[] departments;
int numOfDepartment = 0 ;// 保存当前数组的对象个数
public ComputerColleges() {
departments = new Departments[5];
addDepartment("Java专业", " Java专业 ");
addDepartment("PHP专业", " PHP专业 ");
addDepartment("大数据专业", " 大数据专业 ");
}
@Override
public String getName() {
return "计算机学院";
}
@Override
public void addDepartment(String name, String desc) {
Departments department = new Departments(name, desc);
departments[numOfDepartment] = department;
numOfDepartment += 1;
}
@Override
public Iterator createIterator() {
return new ComputerCollegeIterators(departments);
}
}
class InfoColleges implements College {
List<Departments> departmentList;
public InfoColleges() {
departmentList = new ArrayList<Departments>();
addDepartment("信息安全专业", " 信息安全专业 ");
addDepartment("网络安全专业", " 网络安全专业 ");
addDepartment("服务器安全专业", " 服务器安全专业 ");
}
@Override
public String getName() {
return "信息工程学院";
}
@Override
public void addDepartment(String name, String desc) {
Departments department = new Departments(name, desc);
departmentList.add(department);
}
@Override
public Iterator createIterator() {
return new InfoColleageIterators(departmentList);
}
}
class ComputerCollegeIterators implements Iterator {
//这里我们需要Department 是以怎样的方式存放=>数组
Departments[] departments;
int position = 0; //遍历的位置
public ComputerCollegeIterators(Departments[] departments) {
this.departments = departments;
}
//判断是否还有下一个元素
@Override
public boolean hasNext() {
if(position >= departments.length || departments[position] == null) {
return false;
}else {
return true;
}
}
@Override
public Object next() {
Departments department = departments[position];
position += 1;
return department;
}
//删除的方法,默认空实现
public void remove() {
}
}
class InfoColleageIterators implements Iterator {
List<Departments> departmentList; // 信息工程学院是以List方式存放系
int index = -1;//索引
public InfoColleageIterators(List<Departments> departmentList) {
this.departmentList = departmentList;
}
//判断list中还有没有下一个元素
@Override
public boolean hasNext() {
if(index >= departmentList.size() - 1) {
return false;
} else {
index += 1;
return true;
}
}
@Override
public Object next() {
return departmentList.get(index);
}
//空实现remove
public void remove() {
}
}
class OutPutImpls {
//学院集合
List<Colleges> collegeList;
public OutPutImpls(List<Colleges> collegeList) {
this.collegeList = collegeList;
}
//遍历所有学院,然后调用printDepartment 输出各个学院的系
public void printCollege() {
//从collegeList 取出所有学院, Java 中的 List 已经实现Iterator
Iterator<Colleges> iterator = collegeList.iterator();
while(iterator.hasNext()) {
//取出一个学院
Colleges college = iterator.next();
System.out.println("=== "+college.getName() +"=====" );
printDepartment(college.createIterator()); //得到对应迭代器
}
}
//输出 学院输出 系
public void printDepartment(Iterator iterator) {
while(iterator.hasNext()) {
Departments d = (Departments)iterator.next();
System.out.println(d.getName());
}
}
}
16.1迭代器模式的注意事项和细节
优点
- 提供一个统一的方法遍历对象,客户不用再考虑聚合的类型,使用一种方法就可以遍历对象了。
- 隐藏了聚合的内部结构,客户端要遍历聚合的时候只能取到迭代器,而不会知道聚合的具体组成。
- 提供了一种设计思想,就是一个类应该只有一个引起变化的原因(叫做单一责任原则)。在聚合类中,我们把迭代器分开,就是要把管理对象集合和遍历对象集合的责任分开,这样一来集合改变的话,只影响到聚合对象。而如果遍历方式改变的话,只影响到了迭代器。
- 当要展示一组相似对象,或者遍历一组相同对象时使用, 适合使用迭代器模式
缺点
- 每个聚合对象都要一个迭代器,会生成多个迭代器不好管理类
十八、观察者模式
- 对象之间多对一依赖的一种设计方案,被依赖的对象为 Subject,依赖的对象为 Observer,Subject 通知 Observer 变化,比如这里的气象站是 Subject,是 1 的一方。用户是 Observer,是多的一方。
-
气象站:Subject
-
用户/第三方网站:Observer
Subject:登记注册、移除和通知
a. registerObserver 注册
b. removeObserver 移除
c. notifyObservers() 通知所有的注册的用户,根据不同需求,可以是更新数据,让用户来取,也可能是实施推送,看具体需求定
Observer:接收输入
-
import java.util.ArrayList;
public class myObserve {
}
//被观察者接口, 让WeatherData(被观察者) 来实现
interface Subjects{
public void registerObserver(Observers o);
public void removeObserver(Observers o);
public void notifyObservers();
}
//观察者接口,由观察者来实现
interface Observers {
public void update(int temperature, int pressure, int humidity);
}
/**
* (被观察者)类是核心
* 1. 包含最新的天气情况信息
* 2. 含有 观察者集合,使用ArrayList管理
* 3. 当数据有更新时,就主动的调用 ArrayList, 通知所有的(接入方)就看到最新的信息
*/
class WeatherDatas implements Subjects {
private int temperatrue;
private int pressure;
private int humidity;
//观察者集合
private ArrayList<Observers> observers;
//加入新的第三方
public WeatherDatas() {
observers = new ArrayList<Observers>();
}
public int getTemperatrue() {
return temperatrue;
}
public int getPressure() {
return pressure;
}
public int getHumidity() {
return humidity;
}
public void dataChange() {
//调用 接入方的 update
notifyObservers();
}
//当数据有更新时,就调用 setData
public void setData(int temperature,int pressure, int humidity) {
this.temperatrue = temperature;
this.pressure = pressure;
this.humidity = humidity;
//调用dataChange, 将最新的信息 推送给 接入方 currentConditions
dataChange();
}
//注册一个观察者
@Override
public void registerObserver(Observers o) {
observers.add(o);
}
//移除一个观察者
@Override
public void removeObserver(Observers 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);
}
}
}
//第一个客户端
class CurrentConditionss implements Observers {
// 温度,气压,湿度
private int temperature;
private int pressure;
private int humidity;
// 更新 天气情况,是由 WeatherData 来调用,我使用推送模式
public void update(int temperature, int pressure, int humidity) {
this.temperature = temperature;
this.pressure = pressure;
this.humidity = humidity;
display();
}
// 显示
public void display() {
System.out.println("==新浪网站==");
System.out.println("***今天的 mTemperature: " + temperature + "***");
System.out.println("***今天的 mPressure: " + pressure + "***");
System.out.println("***今天的 mHumidity: " + humidity + "***");
}
}
//第二个客户端
class BaiduSites implements Observers {
// 温度,气压,湿度
private int temperature;
private int pressure;
private int humidity;
// 更新 天气情况,是由 WeatherData 来调用,我使用推送模式
public void update(int temperature, int pressure, int 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 + "***");
}
}
class Clients {
public static void main(String[] args) {
//创建一个WeatherData
WeatherDatas weatherDatas = new WeatherDatas();
//创建观察者
CurrentConditionss currentConditionss = new CurrentConditionss();
BaiduSites baiduSites = new BaiduSites();
//注册到weatherData
weatherDatas.registerObserver(currentConditionss);
weatherDatas.registerObserver(baiduSites);
//测试
System.out.println("通知各个注册的观察者, 看看信息");
weatherDatas.setData(10, 100, 30);
weatherDatas.removeObserver(currentConditionss);
//测试
System.out.println();
System.out.println("通知各个注册的观察者, 看看信息");
weatherDatas.setData(10, 100, 30);
}
}
18.1观察者模式优点
- 观察者模式设计后,会以集合的方式来管理用户(Observer),包括注册,移除和通知。
- 这样,我们增加观察者(这里可以理解成一个新的公告板),就不需要去修改核心类 WeatherData 不会修改代码,遵守了 ocp 原则。
二十二、状态模式
- 状态模式(State Pattern):它主要用来解决对象在多种状态转换时,需要对外输出不同的行为的问题。状态和行为是一一对应的,状态之间可以相互转换
- 当一个对象的内在状态改变时,允许改变其行为,这个对象看起来像是改变了其类
- Context 类为环境角色, 用于维护 State 实例,这个实例定义当前状态
- State 是抽象状态角色,定义一个接口封装与 Context 的一个特点接口相关行为
- ConcreteState 具体的状态角色,每个子类实现一个与 Context 的一个状态相关行为
22.1抽奖例子
- 类图
在这里插入代码片
22.1状态模式的注意事项和细节
- 代码有很强的可读性。状态模式将每个状态的行为封装到对应的一个类中
- 方便维护。将容易产生问题的 if-else 语句删除了,如果把每个状态的行为都放到一个类中,每次调用方法时都要判断当前是什么状态,不但会产出很多 if-else 语句,而且容易出错
- 符合“开闭原则”。容易增删状态
- 会产生很多类。每个状态都要一个对应的类,当状态过多时会产生很多类,加大维护难度
- 应用场景:当一个事件或者对象有很多种状态,状态之间会相互转换,对不同的状态要求有不同的行为的时候,可以考虑使用状态模式
二十三、策略模式
- 策略模式(Strategy Pattern)中,定义算法族(策略组),分别封装起来,让他们之间可以互相替换,此模式让算法的变化独立于使用算法的客户
- 这算法体现了几个设计原则,
第一、把变化的代码从不变的代码中分离出来;
第二、针对接口编程而不是具体类(定义了策略接口);
第三、多用组合/聚合,少用继承(客户通过组合方式使用策略)。
客户 context 有成员变量 strategy 或者其他的策略接口,至于需要使用到哪个策略,我们可以在构造器中指定
23.1策略模式解决鸭子问题
策略模式:分别封装行为接口,实现算法族,超类里放行为接口对象,在子类里具体设定行为对象。原则就是:分离变化部分,封装接口,基于接口编程各种功能。此模式让行为的变化独立于算法的使用者
public class myStrategy {
public static void main(String[] args) {
System.out.println("====================================================");
WildDucks wildDucks = new WildDucks();
wildDucks.display();
wildDucks.fly();
wildDucks.quack();
wildDucks.swim();
System.out.println("====================================================");
ToyDucks toyDucks = new ToyDucks();
toyDucks.display();
toyDucks.fly();
toyDucks.quack();
toyDucks.swim();
System.out.println("====================================================");
PekingDucks pekingDucks = new PekingDucks();
pekingDucks.display();
pekingDucks.fly();
pekingDucks.quack();
pekingDucks.swim();
System.out.println("====================================================");
//动态改变某个对象的行为, 北京鸭 不能飞
pekingDucks.setFlyBehaviors(new NoFlyBehaviors());
System.out.println("北京鸭的实际飞翔能力");
pekingDucks.fly();
}
}
//飞行行为策略接口
interface FlyBehaviors {
void fly(); // 子类具体实现
}
//叫声行为策略接口
interface QuackBehaviors {
void quack();//子类实现
}
//飞行行为具体实现
class GoodFlyBehaviors implements FlyBehaviors {
@Override
public void fly() {
System.out.println(" 飞翔技术高超 ~~~");
}
}
class BadFlyBehaviors implements FlyBehaviors {
@Override
public void fly() {
System.out.println(" 飞翔技术一般 ");
}
}
class NoFlyBehaviors implements FlyBehaviors{
@Override
public void fly() {
System.out.println(" 不会飞翔 ");
}
}
abstract class Ducks {
//属性, 策略接口
FlyBehaviors flyBehaviors;
//其它属性<->策略接口
QuackBehaviors quackBehaviors;
//空构造器,让子类传参数就好了
public Ducks() {
}
public abstract void display();//显示鸭子信息
//先假设所有鸭子都会叫
public void quack() {
System.out.println("鸭子嘎嘎叫~~");
}
//先假设所有鸭子都会游泳
public void swim() {
System.out.println("鸭子会游泳~~");
}
public void fly() {
//改进
if(flyBehaviors != null) {
flyBehaviors.fly();
}
}
//提供set方法,方便在修改飞行行为的的能力
public void setFlyBehaviors(FlyBehaviors flyBehaviors) {
this.flyBehaviors = flyBehaviors;
}
public void setQuackBehaviors(QuackBehaviors quackBehaviors) {
this.quackBehaviors = quackBehaviors;
}
}
class WildDucks extends Ducks {
//构造器,传入FlyBehavor 的对象
public WildDucks() {
flyBehaviors = new GoodFlyBehaviors();//野鸭飞的很好
}
@Override
public void display() {
System.out.println(" 这是野鸭 ");
}
}
class PekingDucks extends Ducks {
//假如北京鸭可以飞翔,但是飞翔技术一般
public PekingDucks() {
flyBehaviors = new BadFlyBehaviors();
}
@Override
public void display() {
System.out.println("~~北京鸭~~~");
}
}
class ToyDucks extends Ducks{
public ToyDucks() {
flyBehaviors = new NoFlyBehaviors();
}
@Override
public void display() {
System.out.println("玩具鸭");
}
//重写父类没使用策略模式的两个方法就行
public void quack() {
System.out.println("玩具鸭不能叫~~");
}
public void swim() {
System.out.println("玩具鸭不会游泳~~");
}
}
23.2策略模式的注意事项和细节
- 策略模式的关键是:分析项目中变化部分与不变部分
- 策略模式的核心思想是:多用组合/聚合少用继承;用行为类组合,而不是行为的继承更有弹性
- 体现了“对修改关闭,对扩展开放”原则,客户端增加行为不用修改原有代码,只要添加一种策略(或者行为)即可,避免了使用多重转移语句(if…else if…else)
- 提供了可以替换继承关系的办法:策略模式将算法封装在独立的 Strategy 类中使得你可以独立于其 Context 改变它,使它易于切换、易于理解、易于扩展
- 需要注意的是:每添加一个策略就要增加一个类,当策略过多是会导致类数目庞大