文章目录
设计模式 | 简述 | 目的 | 生活案例 |
---|---|---|---|
工厂模式(Factory Pattern) | 不同条件下创建不同实例 | 封装创建细节 | 实体工厂 |
单例模式(Singleton Pattern) | Pattern) 保证一个类仅有一个实例,并且提供一个全局访问点 | 保证独一无二 | CEO |
原型模式(Prototype Pattern) | 通过拷贝原型创建新的对象 | 高效创建对象 | 克隆 |
建造者模式(Builder Pattern) | 用来创建复杂的复合对象 | 开放个性配置步骤 | 选配 |
代理模式(Proxy Pattern) | 为其他对象提供一种代理以控制对这个对象的访问 | 增强职责 | 媒婆 |
外观模式(Facade Pattern) | 对外提供一个统一的接口用来访问子系统 | 统一访问入口 | 前台 |
装饰器模式(Decorator Pattern) | 为对象添加新功能 | 灵活扩展、同宗同源 | 煎饼 |
享元模式(Flyweight Pattern) | 使用对象池来减少重复对象的创建 | 共享资源池 | 全国社保联网 |
组合模式(Composite Pattern) | 将整体与局部(树形结构)进行递归组合,让客户端能够以一种的方式对其进行处理 | 统一整体和个体 | 组织架构树 |
适配器模式(Adapter Pattern) | 将原来不兼容的两个类融合在一起 | 兼容转换 | 电源适配 |
桥接模式(Bridge Pattern) | 将两个能够独立变化的部分分离开来 | 约定优于配置 | 桥 |
模板模式(Template Pattern) | 定义一套流程模板,根据需要实现模板中的操作 | 逻辑复用 | 工作流 |
策略模式(Strategy Pattern) | 封装不同的算法,算法之间能互相替换 | 把选择权交给用户 | 选择支付方式 |
责任链模式(Chain of Responsibility Pattern) | 拦截的类都实现统一接口,每个接收者都包含对下一个接收者的引用。将这些对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止。 | 解耦处理逻辑 | 踢皮球 |
迭代器模式(Iterator Pattern) | 提供一种方法顺序访问一个聚合对象中的各个元素 | 统一对集合的访问方式 | 逐个检票进站 |
命令模式(Command Pattern) | 将请求封装成命令,并记录下来,能够撤销与重做 | 解耦请求和处理 | 遥控器 |
状态模式(State Pattern) | 根据不同的状态做出不同的行为 | 绑定状态和行为 | 订单状态跟踪 |
备忘录模式(Memento Pattern) | 保存对象的状态,在需要时进行恢复 | 备份、后悔机制 | 草稿箱 |
中介者模式(Mediator Pattern) | 将对象之间的通信关联关系封装到一个中介类中单独处理,从而使其耦合松散 | 统一管理网状资源 | 朋友圈 |
观察者模式(Observer Pattern) | 状态发生改变时通知观察者,一对多的关系 | 到点就通知我 | 闹钟 |
访问者模式(Visitor Pattern) | 稳定数据结构,定义新的操作行为 | 解耦数据结构和数据操作 | KPI考核 |
一.创建型模式
创建型模式:用于描述“怎样创建对象”,它的主要特点是“将对象的创建与使用分离”。GoF 中提供了单例、原型、工厂方法、抽象工厂、建造者等 5 种创建型模式。
1.1 单例模式
某个类只能生成一个实例,该类提供了一个全局访问点供外部获取该实例,其拓展是有限多例模式。
单例模式有 3 个特点:
- 单例类只有一个实例对象;
- 该单例对象必须由单例类自行创建;
- 单例类对外提供一个访问该单例的全局访问点。
单例模式的优点:
- 单例模式可以保证内存里只有一个实例,减少了内存的开销。
- 可以避免对资源的多重占用。
- 单例模式设置全局访问点,可以优化和共享资源的访问。
单例模式的应用场景
- 需要频繁创建的一些类,使用单例可以降低系统的内存压力,减少GC。
- 某类只要求生成一个对象的时候,如一个班中的班长、每个人的身份证号等。
- 某些类创建实例时占用资源较多,或实例化耗时较长,且经常使用。
- 某类需要频繁实例化,而创建的对象又频繁被销毁的时候,如多线程的线程池、网络连接池等。
- 频繁访问数据库或文件的对象。
- 对于一些控制硬件级别的操作,或者从系统上来讲应当是单一控制逻辑的操作,如果有多个实例,则系统会完全乱套。
- 当对象需要被共享的场合。由于单例模式只允许创建一个对象,共享该对象可以节省内存,并加快对象访问速度。如 Web 中的配置对象、数据库的连接池等。
懒汉式单例:
该模式的特点是类加载时没有生成单例,只有当第一次调用 getlnstance 方法时才去创建这个单例。
@Data
public class LazyUser {
private String name;
private Integer age;
//保证instance在所有线程中同步
private static volatile LazyUser instance = null;
//private避免类在外部被实例化
private LazyUser() {
System.out.println("产生一个user");
}
public static synchronized LazyUser getInstance() {
//在getInstance方法上加同步
if (instance == null) {
instance = new LazyUser();
}
return instance;
}
}
饿汉式单例:
饿汉式单例在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变,所以是线程安全的,可以直接用于多线程而不会出现问题。
@Data
public class HungryUser {
private String name;
private Integer age;
private static final HungryUser INSTANCE = new HungryUser();
//private避免类在外部被实例化
private HungryUser() {
System.out.println("产生一个user");
}
public static synchronized HungryUser getInstance() {
return INSTANCE;
}
}
枚举
最佳的单例实现模式就是枚举模式。利用枚举的特性,让JVM来帮我们保证线程安全和单一实例的问题
public enum SingletonEnum {
SINGLETON_ENUM;
}
测试
public class Test {
public static void main(String[] args) throws CloneNotSupportedException {
LazyUser lazyUser = LazyUser.getInstance();
lazyUser.setName("张三");
lazyUser.setAge(18);
System.out.println("lazyUser = " + lazyUser); //LazyUser(name=张三, age=18)
LazyUser lazyUser2 = LazyUser.getInstance();
System.out.println("lazyUser2 = " + lazyUser2); //LazyUser(name=张三, age=18)
System.out.println(lazyUser == lazyUser2); //true
System.out.println(lazyUser.equals(lazyUser2)); //true
SingletonEnum singletonEnum = SingletonEnum.SINGLETON_ENUM;
SingletonEnum singletonEnum1 = SingletonEnum.SINGLETON_ENUM;
System.out.println(singletonEnum==singletonEnum1); //true
System.out.println(singletonEnum.equals(singletonEnum1)); //true
}
}
1.2 原型模式
将一个对象作为原型,通过对其进行复制而克隆出多个和原型类似的新实例。
优点:
- Java 自带的原型模式基于内存二进制流的复制,在性能上比直接 new 一个对象更加优良。
- 可以使用深克隆方式保存对象的状态,使用原型模式将对象复制一份,并将其状态保存起来,简化了创建对象的过程,以便在需要的时候使用(例如恢复到历史某一状态),可辅助实现撤销操作。
原型模式包含以下主要角色。
- 抽象原型类:规定了具体原型对象必须实现的接口 -> Cloneable接口
- 具体原型类:实现抽象原型类的 clone() 方法,它是可被复制的对象。->User类
- 访问类:使用具体原型类中的 clone() 方法来复制新的对象。
原型模式的克隆分为浅克隆和深克隆。
- 浅克隆:创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原有属性所指向的对象的内存地址。
- 深克隆:创建一个新对象,属性中引用的其他对象也会被克隆,不再指向原有对象地址。
原型模式通常适用于以下场景。
- 对象之间相同或相似,即只是个别的几个属性不同的时候。
- 创建对象成本较大,例如初始化时间长,占用CPU太多,或者占用网络资源太多等,需要优化资源。
- 创建一个对象需要繁琐的数据准备或访问权限等,需要提高性能或者提高安全性。
- 系统中大量使用该类对象,且各个调用者都需要给它的属性重新赋值。
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User implements Cloneable {
private String name;
private Integer age;
private String grade;
@Override
protected Object clone() throws CloneNotSupportedException {
User user = (User) super.clone();
System.out.println("user对象克隆成功");
return user;
}
}
测试
public class Test {
public static void main(String[] args) throws CloneNotSupportedException {
User user = new User("张三", 18, "一班");
User clone = (User) user.clone();
clone.setGrade("二班");
System.out.println("clone = " + clone);
System.out.println("user = " + user);
}
}
1.3 简单工厂模式
定义一个创建产品对象的工厂接口,将产品对象的实际创建工作推迟到具体子工厂类当中。这满足创建型模式中所要求的“创建与使用相分离”的特点。
在简单工厂模式中创建实例的方法通常为静态(static)方法,因此简单工厂模式又叫作静态工厂方法模式。
简单工厂模式不在 GoF 23 种设计模式之列。
优点:
- 工厂类包含必要的逻辑判断,可以决定在什么时候创建哪一个产品的实例。客户端可以免除直接创建产品对象的职责,很方便的创建出相应的产品。工厂和产品的职责区分明确。
- 客户端无需知道所创建具体产品的类名,只需知道参数即可。
- 也可以引入配置文件,在不修改客户端代码的情况下更换和添加新的具体产品类。
简单工厂模式的主要角色如下:
- 简单工厂(SimpleFactory):是简单工厂模式的核心,负责实现创建所有实例的内部逻辑。工厂类的创建产品类的方法可以被外界直接调用,创建所需的产品对象。->ProductFactory
- 抽象产品(Product):是简单工厂创建的所有对象的父类,负责描述所有实例共有的公共接口。->Product
- 具体产品(ConcreteProduct):是简单工厂模式的创建目标。->Apple Oranage Banana
public interface Product {
void doSth();
}
public class Apple implements Product{
@Override
public void doSth() {
System.out.println("苹果...");
}
}
public class Banana implements Product{
@Override
public void doSth() {
System.out.println("香蕉...");
}
}
public class Oranage implements Product{
@Override
public void doSth() {
System.out.println("橘子....");
}
}
public class ProductFactory {
public static final String ORANAGE = "ORANAGE";
public static final String BANANA = "BANANA";
public static final String APPLE = "APPLE";
public static Product makeProduct(String kind) {
switch (kind) {
case ProductFactory.ORANAGE:
return new Oranage();
case ProductFactory.BANANA:
return new Banana();
case ProductFactory.APPLE:
return new Apple();
default:
return null;
}
}
}
测试:
public class Test {
public static void main(String[] args) {
Product product = ProductFactory.makeProduct(ProductFactory.ORANAGE);
Product product1 = ProductFactory.makeProduct(ProductFactory.BANANA);
Product product2 = ProductFactory.makeProduct(ProductFactory.APPLE);
product.doSth();
product1.doSth();
product2.doSth();
}
}
1.4 工厂方法模式
工厂模式违背了开闭原则,而“工厂方法模式”是对简单工厂模式的进一步抽象化,其好处是可以使系统在不修改原来代码的情况下引进新的产品,即满足开闭原则。
优点:
- 用户只需要知道具体工厂的名称就可得到所要的产品,无须知道产品的具体创建过程。
- 灵活性增强,对于新产品的创建,只需多写一个相应的工厂类。
- 典型的解耦框架。高层模块只需要知道产品的抽象类,无须关心其他实现类,满足迪米特法则、依赖倒置原则和里氏替换原则。
应用场景:
- 客户只知道创建产品的工厂名,而不知道具体的产品名。如 TCL 电视工厂、海信电视工厂等。
- 创建对象的任务由多个具体子工厂中的某一个完成,而抽象工厂只提供创建产品的接口。
- 客户不关心创建产品的细节,只关心产品的品牌
工厂方法模式的主要角色如下。
- 抽象工厂(Abstract Factory):提供了创建产品的接口,调用者通过它访问具体工厂的工厂方法 newProduct() 来创建产品。–>AnimalFarm
- 具体工厂(ConcreteFactory):主要是实现抽象工厂中的抽象方法,完成具体产品的创建。–>HorseFarm MonkeyFarm
- 抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能。Animal
- 具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间一一对应。Monkey Horse
public interface AnimalFarm {
Animal newAnimal();
}
public class HorseFarm implements AnimalFarm{
@Override
public Animal newAnimal() {
return new Horse();
}
}
public class MonkeyFarm implements AnimalFarm{
@Override
public Animal newAnimal() {
return new Monkey();
}
}
public interface Animal {
}
public class Horse implements Animal {
public Horse() {
System.out.println("我是一匹马");
}
}
public class Monkey implements Animal {
public Monkey() {
System.out.println("我是一只猴子");
}
}
测试:
public class Test {
public static void main(String[] args) {
AnimalFarm horseFarm = new HorseFarm();
horseFarm.newAnimal();
horseFarm=new MonkeyFarm();
horseFarm.newAnimal();
}
}
1.5 抽象工厂模式
是一种为访问类提供一个创建一组相关或相互依赖对象的接口,且访问类无须指定所要产品的具体类就能得到同族的不同等级的产品的模式结构。抽象工厂模式是工厂方法模式的升级版本,工厂方法模式只生产一个等级的产品,而抽象工厂模式可生产多个等级的产品。
使用抽象工厂模式一般要满足以下条件。
- 系统中有多个产品族,每个具体工厂创建同一族但属于不同等级结构的产品。
- 系统一次只可能消费其中某一族产品,即同族的产品一起使用。
优点:
- 可以在类的内部对产品族中相关联的多等级产品共同管理,而不必专门引入多个新的类来进行管理。
- 当需要产品族时,抽象工厂可以保证客户端始终只使用同一个产品的产品组。
- 抽象工厂增强了程序的可扩展性,当增加一个新的产品族时,不需要修改原代码,满足开闭原则。
应用场景:
- 当需要创建的对象是一系列相互关联或相互依赖的产品族时,如电器工厂中的电视机、洗衣机、空调等。
- 系统中有多个产品族,但每次只使用其中的某一族产品。如有人只喜欢穿某一个品牌的衣服和鞋。
- 系统中提供了产品的类库,且所有产品的接口相同,客户端不依赖产品实例的创建细节和内部结构。
工厂方法模式的主要角色如下。
- 抽象工厂(Abstract Factory):提供了创建产品的接口,它包含多个创建产品的方法 newProduct(),可以创建多个不同等级的产品。–>AnimalFarm
- 具体工厂(Concrete Factory):主要是实现抽象工厂中的多个抽象方法,完成具体产品的创建。HorseFarm MonkeyFarm
- 抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能,抽象工厂模式有多个抽象产品。Animal Plant
- 具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间是多对一的关系。Apple Horse Banana Monkey
public interface AnimalFarm {
Animal newAnimal();
Plant newPlant();
}
public class HorseFarm implements AnimalFarm {
@Override
public Animal newAnimal() {
return new Horse();
}
@Override
public Plant newPlant() {
return new Apple();
}
}
public class MonkeyFarm implements AnimalFarm {
@Override
public Animal newAnimal() {
return new Monkey();
}
@Override
public Plant newPlant() {
return new Banana();
}
}
public interface Animal {
}
public class Horse implements Animal {
public Horse() {
System.out.println("我是一匹马");
}
}
public class Monkey implements Animal {
public Monkey() {
System.out.println("我是一只猴子");
}
}
public interface Plant {
}
public class Apple implements Plant {
public Apple() {
System.out.println("苹果");
}
}
public class Banana implements Plant {
public Banana() {
System.out.println("香蕉");
}
}
测试:
public class Test {
public static void main(String[] args) {
AnimalFarm horseFarm = new HorseFarm();
horseFarm.newAnimal();
horseFarm.newPlant();
horseFarm = new MonkeyFarm();
horseFarm.newAnimal();
horseFarm.newPlant();
}
}
1.6 建造者模式
指将一个复杂对象的构造与它的表示分离,使同样的构建过程可以创建不同的表示,这样的设计模式被称为建造者模式。
它是将一个复杂的对象分解为多个简单的对象,然后一步一步构建而成。它将变与不变相分离,即产品的组成部分是不变的,但每一部分是可以灵活选择的。
该模式的主要优点如下:
- 封装性好,构建和表示分离。
- 扩展性好,各个具体的建造者相互独立,有利于系统的解耦。
- 客户端不必知道产品内部组成的细节,建造者可以对创建过程逐步细化,而不对其它模块产生任何影响,便于控制细节风险。
其缺点如下:
- 产品的组成部分必须相同,这限制了其使用范围。
- 如果产品的内部变化复杂,如果产品内部发生变化,则建造者也要同步修改,后期维护成本较大。
方式一:
建造者(Builder)模式的主要角色如下。
- 产品角色(Product):它是包含多个组成部件的复杂对象,由具体建造者来创建其各个零部件。Product
- 抽象建造者(Builder):它是一个包含创建产品各个子部件的抽象方法的接口,通常还包含一个返回复杂产品的方法getResult()。Builder
- 具体建造者(Concrete Builder):实现 Builder 接口,完成复杂产品的各个部件的具体创建方法。ConcreteBuilder
- 指挥者(Director):它调用建造者对象中的部件构造与装配方法完成复杂对象的创建,在指挥者中不涉及具体产品的信息。Director
public abstract class Builder {
protected abstract void buildId();
protected abstract void buildName();
protected abstract void buildGender();
protected abstract Product getProduct();
}
public class ConcreteBuilder extends Builder {
private Product product;
public ConcreteBuilder() {
product = new Product();
}
@Override
protected void buildId() {
product.setId(1);
}
@Override
protected void buildName() {
product.setName("狒狒");
}
@Override
protected void buildGender() {
product.setGender("女");
}
@Override
protected Product getProduct() {
return product;
}
}
@ToString
@Setter
public class Product {
private Integer id;
private String name;
private String gender;
}
public class Director {
public Product create(Builder builder) {
builder.buildId();
builder.buildName();
builder.buildGender();
return builder.getProduct();
}
}
测试:
public class Test {
public static void main(String[] args) {
Director director=new Director();
Product product = director.create(new ConcreteBuilder());
System.out.println("product = " + product);
}
}
方式二:
通过静态内部类方式实现零件无序装配话构造:这种方式使用更加灵活,更符合定义。
内部有复杂对象的默认实现,使用时可以根据用户需求自由定义更改内容,并且无需改变具体的构造方式。就可以生产出不同复杂产品
建造者(Builder)模式的主要角色如下:
- 产品角色(Product):它是包含多个组成部件的复杂对象,由具体建造者来创建其各个零部件。Product
- 具体建造者(Concrete Builder):实现 Builder 接口,完成复杂产品的各个部件的具体创建方法。ProductBuilder
@ToString
public class Product {
private Integer id;
private String name;
private String gander;
private Product(Integer id, String name, String gander) {
this.id = id;
this.name = name;
this.gander = gander;
}
public static ProductBuilder builder() {
return new ProductBuilder();
}
public static class ProductBuilder implements Builder<Product> {
private Integer id;
private String name;
private String gander;
private ProductBuilder() {
}
public ProductBuilder id(Integer id) {
this.id = id;
return this;
}
public ProductBuilder name(String name) {
this.name = name;
return this;
}
public ProductBuilder gander(String gander) {
this.gander = gander;
return this;
}
@Override
public Product build() {
return new Product(id, name, gander);
}
}
}
测试:
public class Test {
public static void main(String[] args) {
Product product = Product.builder().id(1).name("狒狒").gander("女").build();
System.out.println("product = " + product);
}
}
二.结构型模式
结构型模式描述如何将类或对象按某种布局组成更大的结构。它分为类结构型模式和对象结构型模式,前者采用继承机制来组织接口和类,后者釆用组合或聚合来组合对象。
2.1 代理模式
由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。
代理模式的主要优点有:
- 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;
- 代理对象可以扩展目标对象的功能;
- 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度,增加了程序的可扩展性
代理模式的主要角色如下。
- 抽象主题(Subject)类:通过接口或抽象类声明真实主题和代理对象实现的业务方法。House
- 真实主题(Real Subject)类:实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。HouseImpl
- 代理(Proxy)类:提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。HouseProxy
根据代理的创建时期,代理模式分为静态代理和动态代理。
- 静态:由程序员创建代理类或特定工具自动生成源代码再对其编译,在程序运行前代理类的 .class 文件就已经存在了。
- 动态:在程序运行时,运用反射机制动态创建而成
静态代理:
public interface House {
void buyHouse(String name);
}
@Data
public class HouseImpl implements House {
private String name;
@Override
public void buyHouse(String name) {
System.out.println(name + "开始买房子");
}
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class HouseProxy implements House {
private House house;
@Override
public void buyHouse(String name) {
pre(name);
house.buyHouse(name);
post(name);
}
public void pre(String name) {
System.out.println(name + "拿出了自己全部家当");
}
public void post(String name) {
System.out.println(name + "喜提新房");
}
}
测试:
public class Test {
public static void main(String[] args) {
House house=new HouseImpl();
HouseProxy proxy=new HouseProxy(house);
proxy.buyHouse("狒总");
}
}
动态代理:
在程序运行时,运用反射机制动态创建而成
public class ProxyHandler implements InvocationHandler {
private Object object;
public ProxyHandler(Object object) {
this.object = object;
}
/**
* @param proxy 代理对象 这里指的就是:proxy_house
* @param method 代理对象调用的方法封装为对象
* @param args 代理对象调用方法传递的实际参数
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(args[0].toString() + "买房前准备");
Object result = Object result = method.invoke(object, args);
System.out.println(args[0].toString() + "喜提新房");
return result;
}
}
测试:
public class Test {
public static void main(String[] args) {
House house = new HouseImpl();
/**
*1.类加载器:真实对象.getClass().getClassLoader()
*2.接口数组:真实对象.getClass().getInterfaces() 保证代理对象和真实对象实现相同的接口
*3.处理器:new InvocationHandler(){} 匿名内部类的写法,里面是我们核心业务逻辑的处理
*/
House house1 = (House) Proxy.newProxyInstance(HouseImpl.class.getClassLoader(),
HouseImpl.class.getInterfaces(), new ProxyHandler(house));
house1.buyHouse("狒总");
}
}
2.2 享元模式
运用共享技术来有效地支持大量细粒度对象的复用。
它通过共享已经存在的对象来大幅度减少需要创建的对象数量、避免大量相似类的开销,从而提高系统资源的利用率。
享元模式的主要优点是:
相同对象只要保存一份,这降低了系统中对象的数量,从而降低了系统中细粒度对象给内存带来的压力。
模式的结构:
- 抽象享元角色(Flyweight):是所有的具体享元类的基类,为具体享元规范需要实现的公共接口,非享元的外部状态以参数的形式通过方法传入。AbstractConnect
- 具体享元(Concrete Flyweight)角色:实现抽象享元角色中所规定的接口。Connect
- 非享元(Unsharable Flyweight)角色:是不可以共享的外部状态,它以参数的形式注入具体享元的相关方法中。Client
- 享元工厂(Flyweight Factory)角色:负责创建和管理享元角色。当客户对象请求一个享元对象时,享元工厂检査系统中是否存在符合要求的享元对象, 如果存在则提供给客户;如果不存在的话,则创建一个新的享元对象。ConnectPool
应用场景:
- 系统中存在大量相同或相似的对象,这些对象耗费大量的内存资源。
- 大部分的对象可以按照内部状态进行分组,且可将不同部分外部化,这样每一个组只需保存一个内部状态。
- 由于享元模式需要额外维护一个保存享元的数据结构,所以应当在有足够多的享元实例时才值得使用享元模式。
@Data
@AllArgsConstructor
public class Client {
private String name;
}
public abstract class AbstractConnect {
protected Client client;
public AbstractConnect(Client client) {
this.client = client;
}
protected abstract void use();
}
public class Connect extends AbstractConnect{
public Connect(Client client) {
super(client);
}
@Override
protected void use() {
System.out.println(client.getName()+"创建连接成功...");
}
}
public class ConnectPool {
private final HashMap<String, AbstractConnect> pool = new HashMap<>();
public AbstractConnect getConnect(Client client) {
if (!pool.containsKey(client.getName())) {
pool.put(client.getName(), new Connect(client));
}
return pool.get(client.getName());
}
public int count() {
return pool.size();
}
}
测试:
public class Test {
public static void main(String[] args) {
ConnectPool pool=new ConnectPool();
AbstractConnect admin = pool.getConnect(new Client("admin"));
admin.use();
AbstractConnect admin1 = pool.getConnect(new Client("root"));
admin1.use();
AbstractConnect admin3 = pool.getConnect(new Client("root"));
admin3.use();
AbstractConnect admin2 = pool.getConnect(new Client("admin"));
admin2.use();
System.out.println("pool.count() = " + pool.count());
}
}
2.3 适配器模式
将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。适配器模式分为类结构型模式和对象结构型模式两种,前者类之间的耦合度比后者高,且要求程序员了解现有组件库中的相关组件的内部结构,所以应用相对较少些。
该模式的主要优点如下。
- 客户端通过适配器可以透明地调用目标接口。
- 复用了现存的类,程序员不需要修改原有代码而重用现有的适配者类。
- 将目标类和适配者类解耦,解决了目标类和适配者类接口不一致的问题。
- 在很多业务场景中符合开闭原则。
适配器模式(Adapter)包含以下主要角色。
- 目标(Target)接口:当前系统业务所期待的接口,它可以是抽象类或接口。Engine
- 适配者(Adaptee)类:它是被访问和适配的现存组件库中的组件接口。ElectricEngine LightEngine
- 适配器(Adapter)类:它是一个转换器,通过继承或引用适配者的对象,把适配者接口转换成目标接口,让客户按目标接口的格式访问适配者。AdapterElectric AdapterLight
public interface Engine {
void drive();
}
public class AdapterElectric implements Engine{
private ElectricEngine electricEngine;
public AdapterElectric(ElectricEngine electricEngine) {
this.electricEngine = electricEngine;
}
@Override
public void drive() {
electricEngine.electricDrive();
}
}
public class AdapterLight implements Engine {
private LightEngine lightEngine;
public AdapterLight(LightEngine lightEngine) {
this.lightEngine = lightEngine;
}
@Override
public void drive() {
lightEngine.lightDrive();
}
}
public class ElectricEngine {
public void electricDrive(){
System.out.println("电能引擎驱动发动机汽车..");
}
}
public class LightEngine {
public void lightDrive(){
System.out.println("光能引擎驱动发动机汽车..");
}
}
测试:
public class Test {
public static void main(String[] args) {
Engine adapterElectric=new AdapterElectric(new ElectricEngine());
adapterElectric.drive();
Engine adapterLigh=new AdapterLight(new LightEngine());
adapterLigh.drive();
}
}
2.4 桥接模式
将抽象与实现分离,使它们可以独立变化。它是用组合关系代替继承关系来实现,从而降低了抽象和实现这两个可变维度的耦合度。
桥接(Bridge)模式的优点是:
- 抽象与实现分离,扩展能力强
- 符合开闭原则
- 符合合成复用原则
- 其实现细节对客户透明
桥接(Bridge)模式包含以下主要角色。
- 抽象化(Abstraction)角色:定义抽象类,并包含一个对实现化对象的引用。AbstractBus
- 扩展抽象化(Refined Abstraction)角色:是抽象化角色的子类,实现父类中的业务方法,并通过组合关系调用实现化角色中的业务方法。BenZBus BMWBus
- 实现化(Implementor)角色:定义实现化角色的接口,供扩展抽象化角色调用。Color
- 具体实现化(Concrete Implementor)角色:给出实现化角色接口的具体实现。BlueColor GreenColor
public abstract class AbstractBus {
protected Color color;
public AbstractBus(Color color) {
this.color = color;
}
protected AbstractBus() {
}
public abstract String getName();
}
public class BMWBus extends AbstractBus {
public BMWBus(Color color) {
super(color);
}
@Override
public String getName() {
return "宝马汽车 " + color.getColor();
}
}
public class BenZBus extends AbstractBus {
public BenZBus(Color color) {
super(color);
}
@Override
public String getName() {
return "奔驰汽车 " + color.getColor();
}
}
public interface Color {
String getColor();
}
public class BlueColor implements Color{
@Override
public String getColor() {
return "blue color";
}
}
public class GreenColor implements Color{
@Override
public String getColor() {
return "green color...";
}
}
测试
public class Test {
public static void main(String[] args) {
Color color=new GreenColor();
AbstractBus abstractBus=new BMWBus(color);
String name = abstractBus.getName();
System.out.println("name = " + name);
}
}
2.5 装饰器模式
指在不改变现有对象结构的情况下,动态地给该对象增加一些职责(即增加其额外功能)的模式,它属于对象结构型模式。
装饰器模式的主要优点有:
- 装饰器是继承的有力补充,比继承灵活,在不改变原有对象的情况下,动态的给一个对象扩展功能,即插即用
- 通过使用装饰类及这些装饰类的排列组合,可以实现不同效果
- 装饰器模式完全遵守开闭原则
装饰器模式主要包含以下角色。
- 抽象构件(Component)角色:定义一个抽象接口以规范准备接收附加责任的对象。IPhone
- 具体构件(ConcreteComponent)角色:实现抽象构件,通过装饰角色为其添加一些职责。SimplePhone
- 抽象装饰(Decorator)角色:继承抽象构件,并包含具体构件的实例,可以通过其子类扩展具体构件的功能。Decorator
- 具体装饰(ConcreteDecorator)角色:实现抽象装饰的相关方法,并给具体构件对象添加附加的责任。GBPhone MBPhone
应用场景
- 当需要给一个现有类添加附加职责,而又不能采用生成子类的方法进行扩充时。例如,该类被隐藏或者该类是终极类或者采用继承方式会产生大量的子类。
- 当需要通过对现有的一组基本功能进行排列组合而产生非常多的功能时,采用继承关系很难实现,而采用装饰器模式却很好实现。
- 当对象的功能要求可以动态地添加,也可以再动态地撤销时。
public abstract class IPhone {
protected abstract void buy();
}
public class SimplePhone extends IPhone{
@Override
protected void buy() {
System.out.println("裸机...");
}
}
public abstract class Decorator extends IPhone{
private IPhone iPhone;
public Decorator(IPhone iPhone) {
this.iPhone = iPhone;
}
@Override
protected void buy() {
this.iPhone.buy();
}
}
public class GBPhone extends Decorator{
public GBPhone(IPhone iPhone) {
super(iPhone);
}
private void decorateMethod(){
System.out.println("加耳机");
}
@Override
protected void buy() {
decorateMethod();
super.buy();
}
}
public class MBPhone extends Decorator{
public MBPhone(IPhone iPhone) {
super(iPhone);
}
private void decorateMethod(){
System.out.println("加充电器");
}
@Override
protected void buy() {
decorateMethod();
super.buy();
}
}
测试:
public class Test {
public static void main(String[] args) {
IPhone iPhone=new SimplePhone();
iPhone.buy();
Decorator decorator=new GBPhone(iPhone);
decorator.buy();
decorator=new MBPhone(iPhone);
decorator.buy();
}
}
2.6 组合模式
有时又叫作整体-部分(Part-Whole)模式,它是一种将对象组合成树状的层次结构的模式,用来表示“整体-部分”的关系,使用户对单个对象和组合对象具有一致的访问性,属于结构型设计模式。
组合模式一般用来描述整体与部分的关系,它将对象组织到树形结构中,顶层的节点被称为根节点,根节点下面可以包含树枝节点和叶子节点,树枝节点下面又可以包含树枝节点和叶子节点,类似树结构.其实根节点和树枝节点本质上属于同一种数据类型,可以作为容器使用;而叶子节点与树枝节点在语义上不属于用一种类型。但是在组合模式中,会把树枝节点和叶子节点看作属于同一种数据类型(用统一接口定义),让它们具备一致行为。
组合模式的主要优点有:
- 组合模式使得客户端代码可以一致地处理单个对象和组合对象,无须关心自己处理的是单个对象,还是组合对象,这简化了客户端代码;
- 更容易在组合体内加入新的对象,客户端不会因为加入了新的对象而更改源代码,满足“开闭原则”;
组合模式包含以下主要角色。
- 抽象构件(Component角色):它的主要作用是为树叶构件和树枝构件声明公共接口,并实现它们的默认行为。 在透明式的组合模式中抽象构件还声明访问和管理子类的接口;
在安全式的组合模式中不声明访问和管理子类的接口,管理工作由树枝构件完成。(总的抽象类或接口,定义一些通用的方法,比如新增、删除) -->Article - 树叶构件(Leaf)角色:是组合中的叶节点对象,它没有子节点,用于继承或实现抽象构件。–>Goods
- 树枝构件(Composite)角色 / 中间构件:是组合中的分支节点对象,它有子节点,用于继承和实现抽象构件。 它的主要作用是存储和管理子部件,通常包含 Add()、Remove()、GetChild() 等方法。–>Bags
应用场景:
- 在需要表示一个对象整体与部分的层次结构的场合。
- 要求对用户隐藏组合对象与单个对象的不同,用户可以用统一的接口使用组合结构中的所有对象的场合。
public interface Article {
float calculate();
void show();
}
public class Bags implements Article {
private String name;
private List<Article> goodList = new ArrayList<>();
public Bags(String name) {
this.name = name;
}
public void add(Article c) {
goodList.add(c);
}
public void remove(Article c) {
goodList.remove(c);
}
public Article getChild(int i) {
return goodList.get(i);
}
@Override
public float calculate() {
return goodList.stream().
map(Article::calculate).
collect(Collectors.toList()).
stream().
reduce(Float::sum).
get();
/*float sum = 0;
for (Article article : goodList) {
sum += article.calculate();
}
return sum;*/
}
@Override
public void show() {
for (Article article : goodList) {
article.show();
}
}
}
@Data
public class Goods implements Article {
private StringBuilder sb = new StringBuilder();
private String name;
private int count;
private float price;
public Goods(String name, int count, float price) {
this.name = name;
this.count = count;
this.price = price;
}
@Override
public float calculate() {
return count * price;
}
@Override
public void show() {
sb.append("{").append("名称").append(':').append(name).append(",数量").append(":").append(count)
.append(",单价").append(price).append("}");
System.out.println(sb.toString());
}
}
测试:
public class Test {
public static void main(String[] args) {
Bags bigBag, mediumBag, smallRedBag, smallWhiteBag;
Goods sp;
bigBag = new Bags("大袋子");
mediumBag = new Bags("中袋子");
smallRedBag = new Bags("红色小袋子");
smallWhiteBag = new Bags("白色小袋子");
sp = new Goods("婺源特产", 2, 7.9f);
smallRedBag.add(sp);
sp = new Goods("婺源地图", 1, 9.9f);
smallRedBag.add(sp);
sp = new Goods("韶关香菇", 2, 68);
smallWhiteBag.add(sp);
sp = new Goods("韶关红茶", 3, 180);
smallWhiteBag.add(sp);
sp = new Goods("景德镇瓷器", 1, 380);
mediumBag.add(sp);
mediumBag.add(smallRedBag);
sp = new Goods("李宁牌运动鞋", 1, 198);
bigBag.add(sp);
bigBag.add(smallWhiteBag);
bigBag.add(mediumBag);
System.out.println("您选购的商品有:");
bigBag.show();
float calculate = bigBag.calculate();
System.out.println("要支付的总价是:" + calculate + "元");
}
}
2.7 门面模式
外观(Facade)模式又叫作门面模式,是一种通过为多个复杂的子系统提供一个一致的接口,而使这些子系统更加容易被访问的模式。
该模式对外有一个统一接口,外部应用程序不用关心内部子系统的具体细节,这样会大大降低应用程序的复杂度,提高了程序的可维护性。
外观(Facade)模式是“迪米特法则”的典型应用,它有以下主要优点。
- 降低了子系统与客户端之间的耦合度,使得子系统的变化不会影响调用它的客户类。
- 对客户屏蔽了子系统组件,减少了客户处理的对象数目,并使得子系统使用起来更加容易。
- 降低了大型软件系统中的编译依赖性,简化了系统在不同平台之间的移植过程,因为编译一个子系统不会影响其他的子系统,也不会影响外观对象。
外观(Facade)模式包含以下主要角色。
- 外观(Facade)角色:为多个子系统对外提供一个共同的接口。
- 子系统(Sub System)角色:实现系统的部分功能,客户可以通过外观角色访问它。
- 客户(Client)角色:通过一个外观角色访问各个子系统的功能。
应用场景:
- 对分层结构系统构建时,使用外观模式定义子系统中每层的入口点可以简化子系统之间的依赖关系。
- 当一个复杂系统的子系统很多时,外观模式可以为系统设计一个简单的接口供外界访问。
- 当客户端与多个子系统之间存在很大的联系时,引入外观模式可将它们分离,从而提高子系统的独立性和可移植性。
public class Facade {
private Clothes clothes = new Clothes();
private Window window = new Window();
private static final Facade FACADE = new Facade();
public static synchronized Facade getInstance() {
return FACADE;
}
public void doSth() {
clothes.doSth();
window.doSth();
}
}
public class Clothes {
public void doSth(){
System.out.println("洗衣服...");
}
}
public class Window {
public void doSth(){
System.out.println("擦窗户..");
}
}
测试:
public class Test {
public static void main(String[] args) {
Facade facade = Facade.getInstance();
facade.doSth();
}
}
三.行为型模式
行为型模式用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配。
3.1 模板方法模式
定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。它是一种类行为型模式。
该模式的主要优点如下。
- 它封装了不变部分,扩展可变部分。它把认为是不变部分的算法封装到父类中实现,而把可变部分算法由子类继承实现,便于子类继续扩展。
- 它在父类中提取了公共的部分代码,便于代码复用。
- 部分方法是由子类实现的,因此子类可以通过扩展方式增加相应的功能,符合开闭原则
模板方法模式包含以下主要角色:
- 抽象模板类,负责给出一个算法的轮廓和骨架。它由一个模板方法和若干个基本方法构成。这些方法的定义如下。–>AbstractClass
① 模板方法:定义了算法的骨架,按某种顺序调用其包含的基本方法。
② 基本方法:是整个算法中的一个步骤,包含以下几种类型。
抽象方法:在抽象类中声明,由具体子类实现。
具体方法:在抽象类中已经实现,在具体子类中可以继承或重写它。
钩子方法:在抽象类中已经实现,包括用于判断的逻辑方法和需要子类重写的空方法两种。 - 具体实现类,实现抽象类中所定义的抽象方法和钩子方法,它们是一个顶级逻辑的一个组成步骤。–> HomeImpl SchoolImpl
模板方法模式通常适用于以下场景。
- 算法的整体步骤很固定,但其中个别部分易变时,这时候可以使用模板方法模式,将容易变的部分抽象出来,供子类实现。
- 当多个子类存在公共的行为时,可以将其提取出来并集中到一个公共父类中以避免代码重复。
- 当需要控制子类的扩展时,模板方法只在特定点调用钩子操作,这样就只允许在这些点进行扩展。
public abstract class AbstractClass {
/**
* 模板方法
*/
public void templateMethod() {
method3();
if (hookMethod()) {
method1();
}
method2();
method4();
}
/**
* 钩子方法 可以在子类中重写该方法以控制改变父类模板方法中的逻辑
* @return boolean
*/
protected boolean hookMethod() {
return true;
}
/**
* 抽象方法
*/
protected abstract void method1();
/**
* 抽象方法
*/
protected abstract void method2();
/**
* 具体方法
*/
protected void method3() {
System.out.println("起床..洗漱..");
}
/**
* 具体方法
*/
protected void method4() {
System.out.println("睡觉 休息");
}
}
public class HomeImpl extends AbstractClass{
@Override
protected void method1() {
System.out.println("在家打游戏");
}
@Override
protected void method2() {
System.out.println("吃饭");
}
@Override
protected boolean hookMethod() {
return false;
}
}
public class SchoolImpl extends AbstractClass {
@Override
protected void method1() {
System.out.println("去上学");
}
@Override
protected void method2() {
System.out.println("吃饭");
}
}
测试:
public class Test {
public static void main(String[] args) {
AbstractClass abstractClass=new HomeImpl();
abstractClass.templateMethod();
}
}
3.2 策略模式
该模式定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的变化不会影响使用算法的客户。策略模式属于对象行为模式,它通过对算法进行封装,把使用算法的责任和算法的实现分割开来,并委派给不同的对象对这些算法进行管理。
策略模式的主要优点如下。
- 多重条件语句不易维护,而使用策略模式可以避免使用多重条件语句,如 if…else 语句、switch…case 语句。
- 策略模式提供了一系列的可供重用的算法族,恰当使用继承可以把算法族的公共代码转移到父类里面,从而避免重复的代码。
- 策略模式可以提供相同行为的不同实现,客户可以根据不同时间或空间要求选择不同的实现。
- 策略模式提供了对开闭原则的完美支持,可以在不修改原代码的情况下,灵活增加新算法。
- 策略模式把算法的使用放到环境类中,而算法的实现移到具体策略类中,实现了二者的分离。
策略模式的主要角色如下。
- 抽象策略(Strategy)类:定义了一个公共接口,各种不同的算法以不同的方式实现这个接口,环境角色使用这个接口调用不同的算法,一般使用接口或抽象类实现。–>Tool
- 具体策略(Concrete Strategy)类:实现了抽象策略定义的接口,提供具体的算法实现。–>Car Bicycle
- 环境(Context)类:持有一个策略类的引用,最终给客户端调用。–>Context
策略模式的应用场景
- 一个系统需要动态地在几种算法中选择一种时,可将每个算法封装到策略类中。
- 一个类定义了多种行为,并且这些行为在这个类的操作中以多个条件语句的形式出现,可将每个条件分支移入它们各自的策略类中以代替这些条件语句。
- 系统中各算法彼此完全独立,且要求对客户隐藏具体算法的实现细节时。
- 系统要求使用算法的客户不应该知道其操作的数据时,可使用策略模式来隐藏与算法相关的数据结构。
- 多个类只区别在表现行为不同,可以使用策略模式,在运行时动态选择具体要执行的行为。
public interface Tool {
void driver();
}
public class Bicycle implements Tool{
@Override
public void driver() {
System.out.println("骑车");
}
}
public class Car implements Tool{
@Override
public void driver() {
System.out.println("开车");
}
}
public class Context {
private Tool tool;
public Tool getTool() {
return tool;
}
public void setTool(Tool tool) {
this.tool = tool;
}
public void drive() {
tool.driver();
}
}
测试:
public class Test {
public static void main(String[] args) {
Context context=new Context();
context.setTool(new Car());
context.drive();
}
}
3.3 命令模式
将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。这样两者之间通过命令对象进行沟通,这样方便将命令对象进行储存、传递、调用、增加与管理。
命令模式的主要优点如下。
- 通过引入中间件(抽象接口)降低系统的耦合度。
- 扩展性良好,增加或删除命令非常方便。采用命令模式增加与删除命令不会影响其他类,且满足“开闭原则”。
- 可以实现宏命令。命令模式可以与组合模式结合,将多个命令装配成一个组合命令,即宏命令。
- 方便实现 Undo 和 Redo 操作。命令模式可以与后面介绍的备忘录模式结合,实现命令的撤销与恢复。
- 可以在现有命令的基础上,增加额外功能。比如日志记录,结合装饰器模式会更加灵活。
模式的结构
- 抽象命令类(Command)角色:声明执行命令的接口,拥有执行命令的抽象方法 execute()。–>Command
- 具体命令类(Concrete Command)角色:是抽象命令类的具体实现类,它拥有接收者对象,并通过调用接收者的功能来完成命令要执行的操作。–>ConcreteCommand
- 实现者/接收者(Receiver)角色:执行命令功能的相关操作,是具体命令对象业务的真正实现者。Receiver
- 调用者/请求者(Invoker)角色:是请求的发送者,它通常拥有很多的命令对象,并通过访问命令对象来执行相关请求,它不直接访问接收者。Invoker
public interface Command {
void excute();
}
public class ConcreteCommand implements Command {
Receiver receiver = null;
public ConcreteCommand(Receiver receiver) {
this.receiver = receiver;
}
@Override
public void excute() {
receiver.excute();
}
}
public class Receiver {
public void excute(){
System.out.println("接受者Receiver的excute方法被调用...");
}
}
public class Invoker {
private Command command;
public Invoker(Command command) {
this.command = command;
}
public Command getCommand() {
return command;
}
public void setCommand(Command command) {
this.command = command;
}
public void call(){
System.out.println("调用者执行命令...");
command.excute();
}
}
测试:
public class Test {
public static void main(String[] args) {
Invoker invoker=new Invoker(new ConcreteCommand(new Receiver()));
invoker.call();
}
}
3.4 责任链模式
为了避免请求发送者与多个请求处理者耦合在一起,于是将所有请求的处理者通过前一对象记住其下一个对象的引用而连成一条链;
当有请求发生时,可将请求沿着这条链传递,直到有对象处理它为止。
在责任链模式中,客户只需要将请求发送到责任链上即可,无须关心请求的处理细节和请求的传递过程,请求会自动进行传递。
所以责任链将请求的发送者和请求的处理者解耦了。
责任链模式是一种对象行为型模式,其主要优点如下。
- 降低了对象之间的耦合度。该模式使得一个对象无须知道到底是哪一个对象处理其请求以及链的结构,发送者和接收者也无须拥有对方的明确信息。
- 增强了系统的可扩展性。可以根据需要增加新的请求处理类,满足开闭原则。
- 增强了给对象指派职责的灵活性。当工作流程发生变化,可以动态地改变链内的成员或者调动它们的次序,也可动态地新增或者删除责任。
- 责任链简化了对象之间的连接。每个对象只需保持一个指向其后继者的引用,不需保持其他所有处理者的引用,这避免了使用众多的 if 或者 if···else 语句。
- 责任分担。每个类只需要处理自己该处理的工作,不该处理的传递给下一个对象完成,明确各类的责任范围,符合类的单一职责原则。
职责链模式主要包含以下角色。
- 抽象处理者(Handler)角色:定义一个处理请求的接口,包含抽象处理方法和一个后继连接。Leader
- 具体处理者(Concrete Handler)角色:实现抽象处理者的处理方法,判断能否处理本次请求,如果可以处理请求则处理,否则将该请求转给它的后继者。TeacherLead HeadLead DirectorLead
- 客户类(Client)角色:创建处理链,并向链头的具体处理者对象提交请求,它不关心处理细节和请求的传递过程。
public abstract class Leader {
private Leader leader;
public Leader getLeader() {
return leader;
}
public void setLeader(Leader leader) {
this.leader = leader;
}
protected abstract void doRequest(Integer req);
}
public class TeacherLead extends Leader {
@Override
protected void doRequest(Integer req) {
if (req <= 2) {
System.out.println("班主任批准假期为" + req + "天");
} else {
if (getLeader() != null) {
getLeader().doRequest(req);
} else {
System.out.println("假期不允");
}
}
}
}
public class DirectorLead extends Leader {
@Override
protected void doRequest(Integer req) {
if (req > 2 && req <= 7) {
System.out.println("教导主批准假期" + req + "天");
} else {
if (getLeader() != null) {
getLeader().doRequest(req);
} else {
System.out.println("假期不允");
}
}
}
}
public class HeadLead extends Leader{
@Override
protected void doRequest(Integer req) {
if (req > 7 && req <= 15) {
System.out.println("校长批准假期" + req + "天");
} else {
if (getLeader() != null) {
getLeader().doRequest(req);
} else {
System.out.println("假期不允");
}
}
}
}
public class Test {
public static void main(String[] args) {
Leader teacherLead = new TeacherLead();
Leader directorLea=new DirectorLead();
Leader headLead=new HeadLead();
teacherLead.setLeader(directorLea);
directorLea.setLeader(headLead);
teacherLead.doRequest(16);
}
}
3.5 状态模式
对有状态的对象,把复杂的“判断逻辑”提取到不同的状态对象中,允许状态对象在其内部状态发生改变时改变其行为。
状态模式是一种对象行为型模式,其主要优点如下。
- 结构清晰,状态模式将与特定状态相关的行为局部化到一个状态中,并且将不同状态的行为分割开来,满足“单一职责原则”。
- 将状态转换显示化,减少对象间的相互依赖。将不同的状态引入独立的对象中会使得状态转换变得更加明确,且减少对象间的相互依赖。
- 状态类职责明确,有利于程序的扩展。通过定义新的子类很容易地增加新的状态和转换。
- 状态模式把受环境改变的对象行为包装在不同的状态对象里,其意图是让一个对象在其内部状态改变的时候,其行为也随之改变。
状态模式包含以下主要角色。
- 环境类(Context)角色:也称为上下文,它定义了客户端需要的接口,内部维护一个当前状态,并负责具体状态的切换。ThreadContext
- 抽象状态(State)角色:定义一个接口,用以封装环境对象中的特定状态所对应的行为,可以有一个或多个行为。ThreadState
- 具体状态(Concrete State)角色:实现抽象状态所对应的行为,并且在需要的情况下进行状态切换。New->Runnable->Running->Blocked->Dead
通常在以下情况下可以考虑使用状态模式。
- 当一个对象的行为取决于它的状态,并且它必须在运行时根据状态改变它的行为时,就可以考虑使用状态模式。
- 一个操作中含有庞大的分支结构,并且这些分支决定于对象的状态时。
public abstract class ThreadState {
protected String stateName;
}
public class ThreadContext {
private ThreadState state;
public ThreadContext() {
state = new New();
}
public ThreadState getState() {
return state;
}
public void setState(ThreadState state) {
this.state = state;
}
public void start() {
((New) state).start(this);
}
public void getCpu() {
((Runnable) state).getCpu(this);
}
public void suspend() {
((Running) state).suspend(this);
}
public void stop() {
((Running) state).stop(this);
}
public void resume() {
((Blocked) state).resume(this);
}
}
public class New extends ThreadState {
public New() {
stateName = "新建状态";
System.out.println("当前线程处于新建状态...");
}
public void start(ThreadContext context){
System.out.println("调用start方法...");
if ("新建状态".equalsIgnoreCase(stateName)){
context.setState(new Runnable());
}else {
System.out.println("当前线程不是新建状态,不能调用start方法");
}
}
}
public class Runnable extends ThreadState {
public Runnable() {
stateName = "就绪状态";
System.out.println("当前线程处于就绪状态...");
}
public void getCpu(ThreadContext context){
System.out.println("获取cpu时间片...");
if ("就绪状态".equalsIgnoreCase(stateName)){
context.setState(new Running());
}else {
System.out.println("当前线程不是就绪状态,不能获取cpu时间片");
}
}
}
public class Running extends ThreadState {
public Running() {
stateName = "运行状态";
System.out.println("当前线程处于运行状态...");
}
public void suspend(ThreadContext context){
System.out.println("调用suspend...");
if ("运行状态".equalsIgnoreCase(stateName)){
context.setState(new Blocked());
}else {
System.out.println("当前线程不是运行状态,不能调用suspend..");
}
}
public void stop(ThreadContext context){
System.out.println("调用stop...");
if ("运行状态".equalsIgnoreCase(stateName)){
context.setState(new Dead());
}else {
System.out.println("当前线程不是运行状态,不能调用stop..");
}
}
}
public class Blocked extends ThreadState {
public Blocked() {
stateName = "阻塞状态";
System.out.println("当前线程处于阻塞状态...");
}
public void resume(ThreadContext context) {
System.out.print("调用resume()方法-->");
if ("阻塞状态".equalsIgnoreCase(stateName)) {
context.setState(new Running());
} else {
System.out.println("当前线程不是阻塞状态,不能调用resume()方法...");
}
}
}
public class Dead extends ThreadState{
public Dead() {
stateName="死亡状态";
System.out.println("线程死亡...");
}
}
测试:
public class Test {
public static void main(String[] args) {
ThreadContext context=new ThreadContext();
context.start();
context.getCpu();
context.suspend();
context.resume();
context.stop();
}
}
3.6 观察者模式
指多个对象间存在一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
这种模式有时又称作发布-订阅模式、模型-视图模式,它是对象行为型模式。
观察者模式是一种对象行为型模式,其主要优点如下。
降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系。符合依赖倒置原则。目标与观察者之间建立了一套触发机制。
观察者模式的主要角色如下。
- 抽象主题(Subject)角色:也叫抽象目标类,它提供了一个用于保存观察者对象的聚集类和增加、删除观察者对象的方法,以及通知所有观察者的抽象方法。->Observable
- 具体主题(Concrete Subject)角色:也叫具体目标类,它实现抽象目标中的通知方法,当具体主题的内部状态发生改变时,通知所有注册过的观察者对象。->LOF
- 抽象观察者(Observer)角色:它是一个抽象类或接口,它包含了一个更新自己的抽象方法,当接到具体主题的更改通知时被调用。->Observer
- 具体观察者(Concrete Observer)角色:实现抽象观察者中定义的抽象方法,以便在得到目标的更改通知时更新自身的状态。->Up Down
应用场景:
- 对象间存在一对多关系,一个对象的状态发生改变会影响其他对象。
- 当一个抽象模型有两个方面,其中一个方面依赖于另一方面时,可将这二者封装在独立的对象中以使它们可以各自独立地改变和复用。
- 实现类似广播机制的功能,不需要知道具体收听者,只需分发广播,系统中感兴趣的对象会自动接收该广播。
- 多层级嵌套使用,形成一种链式触发机制,使得事件具备跨域(跨越两种观察者类型)通知。
/*
* Observable类
* Observable 类是抽象目标类,它有一个 Vector 向量,用于保存所有要通知的观察者对象,下面来介绍它最重要的 3 个方法。
* void addObserver(Observer o) 方法:用于将新的观察者对象添加到向量中。
* void notifyObservers(Object arg) 方法:调用向量中的所有观察者对象的 update() 方法,通知它们数据发生改变。通常越晚加入向量的观察者越先得到通知。
* void setChange() 方法:用来设置一个 boolean 类型的内部标志位,注明目标对象发生了变化。当它为真时,notifyObservers() 才会通知观察者。
*/
public class LOF extends Observable {
private double price;
public double getPrice() {
return price;
}
public void setPrice(double price) {
super.setChanged();
super.notifyObservers(price);
this.price = price;
}
}
public class Down implements Observer {
@Override
public void update(Observable o, Object arg) {
double price = (double) arg;
if (price > 0) {
System.out.println("股票上涨了" + price + "元,卖早了");
} else {
System.out.println("股票下降了" + price + "元,逃顶了");
}
}
}
/* *
* Observer 接口是抽象观察者,它监视目标对象的变化,当目标对象发生变化时,观察者得到通知,并调用 void update(Observable o,Object arg) 方法,进行相应的工作。
*/
public class Up implements Observer {
@Override
public void update(Observable o, Object arg) {
double price = (double) arg;
if (price > 0) {
System.out.println("股票上涨了" + price + "元,抄顶成功");
} else {
System.out.println("股票下降了" + price + "元,山顶真凉快");
}
}
}
测试:
public class Test {
public static void main(String[] args) {
LOF lof = new LOF();
lof.addObserver(new Up());
lof.addObserver(new Down());
lof.setPrice(-10.0);
}
}
3.7 中介者模式
定义一个中介对象来封装一系列对象之间的交互,使原有对象之间的耦合松散,且可以独立地改变它们之间的交互。中介者模式又叫调停模式,它是迪米特法则的典型应用。
中介者模式是一种对象行为型模式,其主要优点如下。
- 类之间各司其职,符合迪米特法则。
- 降低了对象之间的耦合性,使得对象易于独立地被复用。
- 将对象间的一对多关联转变为一对一的关联,提高系统的灵活性,使得系统易于维护和扩展。
中介者模式包含以下主要角色。
- 抽象中介者(Mediator)角色:它是中介者的接口,提供了同事对象注册与转发同事对象信息的抽象方法。AgentImpl
- 具体中介者(Concrete Mediator)角色:实现中介者接口,定义一个 List 来管理同事对象,协调各个同事角色之间的交互关系,因此它依赖于同事角色。AgentImpl
- 抽象同事类(Colleague)角色:定义同事类的接口,保存中介者对象,提供同事对象交互的抽象方法,实现所有相互影响的同事类的公共功能。Consumer
- 具体同事类(Concrete Colleague)角色:是抽象同事类的实现者,当需要与其他同事对象交互时,由中介者对象负责后续的交互。ConsumerImpl1 ConsumerImpl2
应用场景:
- 当对象之间存在复杂的网状结构关系而导致依赖关系混乱且难以复用时。
- 当想创建一个运行于多个类之间的对象,又不想生成新的子类时。
public interface Consumer {
void receive();
void send();
}
@EqualsAndHashCode
public class ConsumerImpl1 implements Consumer {
public ConsumerImpl1() {
AgentImpl agent = AgentImpl.getInstance();
System.out.println(agent);
agent.register(this);
}
@Override
public void receive() {
System.out.println("具体同事类1收到请求...");
}
@Override
public void send() {
AgentImpl agent = AgentImpl.getInstance();
System.out.println(agent);
System.out.println("具体同事类1发出请求...");
agent.forward(this);
}
}
@EqualsAndHashCode
public class ConsumerImpl2 implements Consumer{
public ConsumerImpl2() {
AgentImpl agent = AgentImpl.getInstance();
System.out.println(agent);
agent.register(this);
}
@Override
public void receive() {
System.out.println("具体同事类2收到请求...");
}
@Override
public void send() {
AgentImpl agent = AgentImpl.getInstance();
System.out.println(agent);
System.out.println("具体同事类2发出请求...");
agent.forward(this);
}
}
public class AgentImpl {
private static AgentImpl agent = new AgentImpl();
private List<Consumer> consumers = new ArrayList<>();
public static AgentImpl getInstance() {
return agent;
}
public void register(Consumer consumer) {
if (!consumers.contains(consumer)) {
consumers.add(consumer);
}
}
public void forward(Consumer consumer) {
for (Consumer c : consumers) {
if (!c.equals(consumer)) {
c.receive();
}
}
}
}
测试:
public class Test {
public static void main(String[] args) {
Consumer consume1=new ConsumerImpl1();
Consumer consumer2=new ConsumerImpl2();
consume1.send();
consumer2.send();
}
}
3.8 迭代器模式
提供一个对象来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示。
迭代器模式是一种对象行为型模式,其主要优点如下。
- 访问一个聚合对象的内容而无须暴露它的内部表示。
- 遍历任务交由迭代器完成,这简化了聚合类。
- 它支持以不同方式遍历一个聚合,甚至可以自定义迭代器的子类以支持新的遍历。
- 增加新的聚合类和迭代器类都很方便,无须修改原有代码。
- 封装性良好,为遍历不同的聚合结构提供一个统一的接口。
迭代器模式主要包含以下角色。
- 抽象聚合(Aggregate)角色:定义存储、添加、删除聚合对象以及创建迭代器对象的接口。Aggreagte
- 具体聚合(ConcreteAggregate)角色:实现抽象聚合类,返回一个具体迭代器的实例。AggreagteImpl
- 抽象迭代器(Iterator)角色:定义访问和遍历聚合元素的接口,通常包含 hasNext()、first()、next() 等方法。Iterator
- 具体迭代器(Concretelterator)角色:实现抽象迭代器接口中所定义的方法,完成对聚合对象的遍历,记录遍历的当前位置。IteratorImpl
public interface Iterator<T> {
boolean hasNext();
T next();
T first();
}
{
this.list = list;
}
@Override
public boolean hasNext() {
if (index < list.size() - 1) {
return true;
} else {
return false;
}
}
@Override
public T next() {
T t = null;
if (hasNext()) {
t = list.get(++index);
}
return t;
}
@Override
public T first() {
return list.get(0);
}
}
public interface Aggreagte<T> {
boolean add(T t);
boolean remove(T t);
Iterator<T> iterator();
}
public class AggreagteImpl<T> implements Aggreagte<T> {
List<T> list = new ArrayList<>();
@Override
public boolean add(T o) {
return list.add(o);
}
@Override
public boolean remove(T o) {
return list.remove(o);
}
@Override
public Iterator<T> iterator() {
return new IteratorImpl<T>(list);
}
}
测试:
public class Test {
public static void main(String[] args) {
Aggreagte<String> aggreagte = new AggreagteImpl<String>();
aggreagte.add("2");
aggreagte.add("4");
aggreagte.add("5");
aggreagte.add("1");
Iterator<String> iterator = aggreagte.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
}
3.9 访问者模式
将作用于某种数据结构中的各元素的操作分离出来封装成独立的类,使其在不改变数据结构的前提下可以添加作用于这些元素的新的操作,
为数据结构中的每个元素提供多种访问方式。它将对数据的操作与数据结构进行分离,是行为类模式中最复杂的一种模式。
访问者(Visitor)模式是一种对象行为型模式,其主要优点如下。
- 扩展性好。能够在不修改对象结构中的元素的情况下,为对象结构中的元素添加新的功能。
- 复用性好。可以通过访问者来定义整个对象结构通用的功能,从而提高系统的复用程度。
- 灵活性好。访问者模式将数据结构与作用于结构上的操作解耦,使得操作集合可相对自由地演化而不影响系统的数据结构。
- 符合单一职责原则。访问者模式把相关的行为封装在一起,构成一个访问者,使每一个访问者的功能都比较单一。
访问者模式包含以下主要角色。
- 抽象访问者(Visitor)角色:定义一个访问具体元素的接口,为每个具体元素类对应一个访问操作 visit() ,该操作中的参数类型标识了被访问的具体元素。Visitor
- 具体访问者(ConcreteVisitor)角色:实现抽象访问者角色中声明的各个访问操作,确定访问者访问一个元素时该做什么。ConcreteVisitor1 ConcreteVisitor2
- 抽象元素(Element)角色:声明一个包含接受操作 accept() 的接口,被接受的访问者对象作为 accept() 方法的参数。Element
- 具体元素(ConcreteElement)角色:实现抽象元素角色提供的 accept() 操作,其方法体通常都是 visitor.visit(this) ,另外具体元素中可能还包含本身业务逻辑的相关操作。ElementA ElementB
- 对象结构(Object Structure)角色:是一个包含元素角色的容器,提供让访问者对象遍历容器中的所有元素的方法,通常由 List、Set、Map 等聚合类实现。ObjectStructure
通常在以下情况可以考虑使用访问者(Visitor)模式。
- 对象结构相对稳定,但其操作算法经常变化的程序。
- 对象结构中的对象需要提供多种不同且不相关的操作,而且要避免让这些操作的变化影响对象的结构。
- 对象结构包含很多类型的对象,希望对这些对象实施一些依赖于其具体类型的操作。
public interface Visitor {
void visitElementA(ElementA elementA);
void visitElementB(ElementB elementB);
}
public class ConcreteVisitor1 implements Visitor {
@Override
public void visitElementA(ElementA elementA) {
System.out.println(elementA.getClass().getName()+"被"+this.getClass().getName()+"访问了..");
}
@Override
public void visitElementB(ElementB elementB) {
System.out.println(elementB.getClass().getName()+"被"+this.getClass().getName()+"访问了..");
}
}
public class ConcreteVisitor2 implements Visitor {
@Override
public void visitElementA(ElementA elementA) {
System.out.println(elementA.getClass().getName() + "被" + this.getClass().getName() + "访问了..");
}
@Override
public void visitElementB(ElementB elementB) {
System.out.println(elementB.getClass().getName() + "被" + this.getClass().getName() + "访问了..");
}
}
public interface Element {
void accept(Visitor visitor);
}
public class ElementA implements Element {
@Override
public void accept(Visitor visitor) {
visitor.visitElementA(this);
}
}
public class ElementB implements Element {
@Override
public void accept(Visitor visitor) {
visitor.visitElementB(this);
}
}
public class ObjectStructure {
private List<Element> elementList = new ArrayList<>();
public void add(Element element) {
elementList.add(element);
}
public void remove(Element element) {
elementList.remove(element);
}
public void accept(Visitor visitor) {
for (Element element : elementList) {
element.accept(visitor);
}
}
}
测试:
public class Test {
public static void main(String[] args) {
ObjectStructure objectStructure=new ObjectStructure();
objectStructure.add(new ElementA());
objectStructure.add(new ElementB());
objectStructure.accept(new ConcreteVisitor1());
}
}
3.10 备忘录模式
在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便以后当需要时能将该对象恢复到原先保存的状态。该模式又叫快照模式。
备忘录模式是一种对象行为型模式,其主要优点如下。
- 提供了一种可以恢复状态的机制。当用户需要时能够比较方便地将数据恢复到某个历史的状态。
- 实现了内部状态的封装。除了创建它的发起人之外,其他对象都不能够访问这些状态信息。
- 简化了发起人类。发起人不需要管理和保存其内部状态的各个备份,所有状态信息都保存在备忘录中,并由管理者进行管理,这符合单一职责原则。
备忘录模式的主要角色如下。
- 发起人(Originator)角色:记录当前时刻的内部状态信息,提供创建备忘录和恢复备忘录数据的功能,实现其他业务功能,它可以访问备忘录里的所有信息。Originator
- 备忘录(Memento)角色:负责存储发起人的内部状态,在需要的时候提供这些内部状态给发起人。Memento
- 管理者(Caretaker)角色:对备忘录进行管理,提供保存与获取备忘录的功能,但其不能对备忘录的内容进行访问与修改。Caretaker
应用场景:
- 需要保存与恢复数据的场景,如玩游戏时的中间结果的存档功能。
- 需要提供一个可回滚操作的场景,如 Word、记事本、Photoshop,Eclipse 等软件在编辑时按 Ctrl+Z 组合键,还有数据库中事务操作。
public class Originator {
private String state;
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
public Memento creatMemento(){
return new Memento(state);
}
public void restoreMemento(Memento memento){
this.setState(memento.getState());
}
}
public class Memento {
private String state;
public Memento(String state) {
this.state = state;
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
}
public class Caretaker {
private Memento memento;
public Memento getMemento() {
return memento;
}
public void setMemento(Memento memento) {
this.memento = memento;
}
}
测试:
public class Test {
public static void main(String[] args) {
Caretaker caretaker = new Caretaker();
Originator originator = new Originator();
originator.setState("初始状态");
System.out.println(originator.getState());
caretaker.setMemento(originator.creatMemento());
originator.setState("改变状态");
System.out.println(originator.getState());
originator.restoreMemento(caretaker.getMemento());
System.out.println(originator.getState());
}
}