设计模式 及 学习设计模式的必要性
设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。
- 设计模式的有点:
- 代码复用、代码易理解、保证代码可靠性。
- 代码编制真正工程化(模板化),可以解决很多复杂问题
- 提供该问题的核心解决方案。
- 比较常用的设计模式有十种:
单例模式、策略模式、代理模式、观察者模式、装饰模式、适配器模式、命令模式、组合模式、简单工厂模式、模板方法模式
必要性
设计模式的本质是面相对象设计原则
的实际运用,是对类的封装性、继承性、多态性
及类的关联关系
、组合关系
的充分理解。
- 正确使用设计模式的优点:
- 提高思维能力、编程能力、设计能力
- 让程序更加标准化、代码更加工程化、开发效率大大提高、缩短开发周期
- 提高代码重用性、可读性、可靠性、灵活性、维护性
分类
设计模式总共有 23种
,根据功能划分,设计模式分为以下三大类:
创建型模式
:对象实例化的模式,创建型模式用于解耦对象的实例化过程。- 作用:以代码复用为目标的技术,专为创建高效繁复的对象而设计的。它的主要目的是减少创建对象的数量,减少开发时间,减少代码的复杂度,提高软件质量和可维护性。
结构型模式
:把类或对象结合在一起形成一个更大的结构。- 作用:改变代码结构来达到解耦的目的,使得我们的代码容易维护和扩展
行为型模式
:类和对象如何交互,及划分责任和算法。- 作用:用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配。
如下图所示:
- 作用:用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配。
![](https://i-blog.csdnimg.cn/blog_migrate/de15f37f5dfe7acee0a32b6a8e6753c5.png)
设计模式 - UML图
详细参考:链接地址
类与类之间关系的表示方式
关联关系
- 关联关系是对象之间的一种引用关系,用于表示一类对象与另一类对象之间的联系。
如老师和学生、师傅和徒弟等等 - 关联关系是类与类之间最常用的一种关系,分为:
- 一般关联关系、聚合关系、组合关系
关联关系又分为:单向关联、双向关联、自关联
单向关联
在UML类图中,单向关联,用一个带箭头的实线表示。
如下图所示:
![](https://i-blog.csdnimg.cn/blog_migrate/35b50a42f9674c35ce9d8e765aa22c75.png)
表示:每个顾客都有一个地址,这通过让Customer类持有一个类型为Address的成员变量类实现。
双向关联
用一个不带箭头的直线表示。双向关联关系,就是双方各自持有对方类型的成员变量。如下图所示
![](https://i-blog.csdnimg.cn/blog_migrate/9b7cdf6505e2384fbc8e25ff7441b187.png)
图中,在Customer类中维护一个List,表示一个顾客可以购买多个商品;在Product类中维护一个Customer类型的成员变量,表示这个产品被哪个顾客所购买。
自关联
用一个带有箭头且指向自身的线表示。如下图所示
![](https://i-blog.csdnimg.cn/blog_migrate/f8c8267b2946f9db8bf5bd2505ab71eb.png)
图中,Node类包含类型为Node的成员变量,也就是“自己包含自己”
聚合关系
- 聚合关系是关联关系中的一种,是
强
关联关系,是整体与部分之间的关系。 - 聚合关系也是通过成员对象来实现,其中成员对象是整体的一部分,但是成员对象可以脱离整体对象而独立存在。
例如:学校与老师的关系、学校包含老师,但如果学校停办了,老师依然存在。 - 聚合关系用带
空心菱形的实线
来表示,菱形指向整体
。
![](https://img-blog.csdnimg.cn/fb9e58781c2f4514ac1b6761d863e07d.png)
组合关系
- 组合表示类之间的整体与部分的关系,但它是一种
更强烈
的聚合关系。 - 在组合关系中,整体对象可以控制部分对象的生命周期,一旦整体对象不存在,部分对象也将不存在,部分对象不能脱离整体对象而存在。
例如:头和嘴的关系,没有了头,嘴也就不存在了。 - 组合关系,用带
实心菱形的实线
表示,菱形指向整体。
![](https://i-blog.csdnimg.cn/blog_migrate/e649ef6b730140ef356846f5e7ecf954.png)
依赖关系
- 依赖关系是一种使用关系,它是对象之间
耦合度最弱
的一种关联方式,是临时性的关联。
在代码中,某个类的方法通过局部变量
、方法的参数
或者对静态方法的调用
来访问领一个类(被依赖类)中的某些方法来完成一些职责
。 - 依赖关系,用
带箭头的虚线
表示,箭头从使用类指向被依赖类。
![](https://i-blog.csdnimg.cn/blog_migrate/89ae4599bbd85713df12efe778d58530.png)
图中,司机和汽车的关系图,司机驾驶汽车
继承关系(抽象)
- 是
对象耦合度最大
的一种关系,表示一般与特殊的关系,是父类与子类之间的关系,是一种继承关系。 - 泛化关系用
空心三角箭头的实线
表示,箭头从子类指向父类。在代码实现时,使用面向对象的继承机制
来实现泛化关系
.
![](https://i-blog.csdnimg.cn/blog_migrate/e107b3d3946ddb4ca02e4b8830c034a9.png)
实现关系
- 是接口与实现类之间的关系。在这种关系中,类实现了接口,类中的操作实现了接口中所声明的所有的抽象方法。
- 用带
空心三角箭头的虚线
表示,箭头从实现类指向接口
![](https://i-blog.csdnimg.cn/blog_migrate/ca6d348c122e25c16a3edc64d1b8295c.png)
图中,汽车和船实现了交通工具
设计模式 - 设计原则
详细参考:链接地址
设计模式 - 创建型模式
1、单例模式(Singleton Pattern)
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。该类提供唯一的访问对象的方法,可以直接访问,不需要实例化该类的对象
- 单例模式,它的定义就是确保某一个类只有一个实例,并且提供一个全局访问点。
- 单例模式具备典型的3个特点:1、只有一个实例。 2、自我实例化。 3、提供全局访问点。
- 优点:
- 节约系统资源、提高了系统效率
- 严格控制客户对它的访问
- 缺点
- 单例类的职责过重,没有抽象类
- 违背了单一职责原则
UML结构图,如下:
![](https://i-blog.csdnimg.cn/blog_migrate/b13614248fe5f298dafd4f64cae9f250.png)
2.工厂方法模式
定义:用于创建对象的接口,让子类决定实例化哪个产品类对象。工厂方法使一个产品类的实例化延迟到期工厂的子类。
- 结构:工厂方法模式的主要角色
- 抽象工厂:提供了创建产品的接口或抽象类,调用者通过它访问具体工厂的方法来创建产品
- 具体工厂:主要实现抽象工厂方中的抽象方法,完成具体产品的创建
- 抽象产品:定义了产品的规范,描述了产品的主要特征和功能
- 具体产品:实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间一一对应。
- uml如下图所示:
![](https://i-blog.csdnimg.cn/blog_migrate/1cca9e193473baa5050beb687905f321.png)
- 优点:
- 用户只需要知道具体工厂的名称就可得到所要的产品,无需知道产品的创建过程。
- 在系统中增加新的产品时,只需要添加具体产品类和对应的具体工厂类,无序对原工厂进行任何修改,满足开闭原则
- 缺点:
- 每增加一个产品就要增加一个具体产品类和一个对应的具体工厂类,这增加了系统的复杂度
工厂方法模式案例:
- 抽象工厂类
/**
* @description 抽象工厂类(对于需要增加一个其他的咖啡类型,
* 只需要增加一个咖啡工厂的子实现类即可,原有的代码不动,
* 这就符合开闭原则 -- 对拓展开发,对修改关闭)
* @since jdk11
*/
public interface CoffeeFactory {
/**
* 创建产品对象
*
* @param clazz 产品类型
* @param <T> 类型
* @return 产品对象
*/
public <T> T createProduct(Class<T> clazz);
}
- 具体工厂类,负责实现创建所有产品的内部逻辑,该工厂类可以被直接调用,创建所需的具体对象
/**
* @description 具体工厂类
* @since jdk11
*/
public class AmericanCoffeeFactory implements CoffeeFactory {
@Override
public <T> T createProduct(Class<T> clazz) {
return BeanUtils.getBean(clazz);
}
}
- 抽象产品类,工厂类所创建的所有产品的父类,封装了产品对象的公共方法
/**
* @description 抽象产品类,工厂类所创建的所有产品的父类,封装了产品对象的公共方法
* @since jdk11
*/
public abstract class Coffee {
public abstract String getName();
public void addSugar() {
System.out.println("Coffee -> 加糖");
}
public void addMilk() {
System.out.println("Coffee -> 加奶");
}
}
- 具体产品类
/**
* @description 具体产品类(美式咖啡)
* @since jdk11
*/
public class AmericanCoffee extends Coffee {
@Override
public String getName() {
return "美式咖啡";
}
}
- 咖啡店
/**
* @description 点咖啡(依赖于Coffee),只依赖了抽象,而没有依赖于具体,这符合依赖倒转原则
* @since jdk11
*/
public class CoffeeStore {
public CoffeeFactory factory;
public CoffeeStore(CoffeeFactory factory) {
this.factory = factory;
}
public Coffee orderCoffee(Class cls){
Coffee coffee = (Coffee)factory.createProduct(cls);
coffee.addMilk();
coffee.addSugar();
return coffee;
}
}
- 工具类
/**
* @description 获取bean对象
* @since jdk11
*/
public class BeanUtils {
public static <T> T getBean(Class<T> clazz){
T bean = null;
try {
bean = (T) Class.forName(clazz.getName()).getConstructor().newInstance();
} catch (Exception e) {
e.printStackTrace();
}
return bean;
}
}
- 测试
public class ProductFactoryTest {
public static void main(String[] args) {
CoffeeFactory factory = new AmericanCoffeeFactory();
CoffeeStore store = new CoffeeStore(factory);
Coffee coffee = store.orderCoffee(AmericanCoffee.class);
System.out.println(coffee.getName());
}
}
3. 抽象工厂模式
- 定义:
是一种为访问类,提供一个创建一组相关或相互依赖对象的接口,且访问类无需指定所要产品的具体类,就能得到同族的不同等级的产品的模式结构。 - 结构:抽象工厂模式主要考虑角色如下:
- 抽象工厂:提供了创建产品的接口或抽象类,
包含多个创建产品的方法
,可以创建多个不同等级的产品
。 - 具体工厂:主要实现抽象工厂方中的
多个抽象方法
,完成具体产品的创建 - 抽象产品:定义了产品的规范,描述了产品的主要特征和功能,
抽象工厂模式有多个抽象产品
。 - 具体产品:实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间是
多对一
的关系。
- 抽象工厂:提供了创建产品的接口或抽象类,
- 工厂方法模式的缺陷:只考虑生产
同等级别的产品
,但是在现实生活中,许多工厂是综合性的工厂
,能生产多种等级(种类)的产品。- 例如:电子厂既能生产台式电脑,也能生产笔记本电脑、手机或者座机等等。此时工厂方法模式就不满足要求。
- 抽象工厂模式:
考虑多等级产品的生产
,将同一个具体工厂
所生产的位于不同等级的一组产品
,称为产品族
。- 如下图所示:
横轴
—— 产品等级,即:同一种类产品
;纵轴
:同一个品牌的产品,同一个品牌
的产品产自同一个工厂
。
- 如下图所示:
- 优点
当一个产品族中的多个对象被设计成一起工作时,能保证客户端只使用同一个产品族中的对象 - 缺点
当产品族中需要增加一个新的产品时,所有的工厂都需要修改 。
![](https://i-blog.csdnimg.cn/blog_migrate/ea662827d485d034dd4d82d0c88af2e7.png)
其UML结构图如下:
![](https://i-blog.csdnimg.cn/blog_migrate/51e1f973b1f6f06455f6f3d9811b6fd0.png)
案例:拿铁咖啡、美式咖啡同属于一个产品等级
,都是咖啡;提拉米苏、抹茶慕斯是一个产品等级
;拿铁咖啡和提拉米苏是同一个产品族
(属于意大利风味),美式咖啡和抹茶慕斯是同一个产品族
(属于美式咖啡);生产咖啡由抽象工厂完成。所以这个案例就可以使用抽象工厂模式;
- 抽象工厂
public interface DessertFactory {
/**
* 生产咖啡
* @param clazz 类型
* @return 咖啡
*/
Coffee createCoffee(Class clazz);
/**
* 生产甜品
* @param clazz 类型
* @return 甜品
*/
Dessert createDessert(Class clazz);
}
- 具体工厂:意大利式 族类 甜品工厂(生产意大利式咖啡 和 意大利风味的甜品)
/**
* @description 意大利式 族类 甜品工厂(生产意大利式咖啡 和 意大利风味的甜品)
* @since jdk11
*/
public class ItalyDessertFactory implements DessertFactory {
/**
* 生产意大利式咖啡
*
* @param clazz 类型
* @return 意大利式咖啡
*/
@Override
public Coffee createCoffee(Class clazz) {
System.out.println("开始生产 意大利式咖啡");
return (Coffee) BeanUtils.getBean(clazz);
}
/**
* 生产意大利式甜品
*
* @param clazz 类型
* @return 意大利式甜品
*/
@Override
public Dessert createDessert(Class clazz) {
System.out.println("开始生产 意大利式甜品");
return (Dessert) BeanUtils.getBean(clazz);
}
}
- 抽象类
/**
* @description 甜点产品等级抽象类
* @since jdk11
*/
public abstract class Dessert {
public abstract void show();
}
- 具体子类
/**
* @description 抹茶慕斯类(意大利风味)
* @since jdk11
*/
public class MatchaMousse extends Dessert{
@Override
public void show() {
System.out.println("抹茶慕斯类");
}
}
/**
* @description 提拉米苏(意大利风味)
* @since jdk11
*/
public class Tiramisu extends Dessert {
@Override
public void show() {
System.out.println("提拉米苏类");
}
}
- 测试
public class ProductFactoryTest {
public static void main(String[] args) {
CoffeeFactory factory = new AmericanCoffeeFactory();
CoffeeStore store = new CoffeeStore(factory);
Coffee coffee = store.orderCoffee(AmericanCoffee.class);
System.out.println(coffee.getName());
}
}
- 如果需要加同一个产品族,则只需要添加一个对应的工厂类即可,不需要修改其他的类
模式拓展
简单工厂 + 配置文件 解耦
通过简单工厂 + 配置文件的方式接触工厂对象和产品对象的耦合。在工厂类中加载配置文件的类全名,并创建对象进行存储,客户端如果需要对象,直接获取即可。在Spring底层框架中就是采用的这种方式
- 第一步:定义配置文件:bean.properties
american=design.mode.designmodel.factory.coffee.AmericanCoffee
letta=design.mode.designmodel.factory.coffee.LettaCoffee
- 第二步:改进工厂类
/**
* @description 工厂方法改进:简单工厂 + 配置文件
* @since jdk11
*/
public class SimpConfigureFactory {
private static Map<String, Object> map = new HashMap<>();
static {
Properties properties = new Properties();
InputStream resourceAsStream = SimpConfigureFactory.class.getClassLoader().getResourceAsStream("bean.properties");
try {
assert resourceAsStream != null;
properties.load(resourceAsStream);
Set<Object> keys = properties.keySet();
keys.forEach(key -> {
String className = (String) properties.get(key);
try {
Class<?> aClass = Class.forName(className);
//创建对象
map.put((String) key, BeanUtils.getBean(aClass));
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
});
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 获取bean对象
*
* @param name 类型
* @return bean对象
*/
public static Object createCoffee(String name) {
return map.get(name);
}
}
- 测试
public class SimpConfigureFactoryTest {
public static void main(String[] args) {
Coffee coffee = (Coffee)SimpConfigureFactory.createCoffee("american");
System.out.println(coffee.getName());
}
}
- 说明:静态成员变量:用来存储创建的对象(键存储的名称,值存储的是对应的对象),读取配置文件以及创建对象写在静态代码块中,只加载一次
4. 建造者模式
- 概述:建造者模式是将一个复杂对象的构建与表示分离,是的同样的构建过程可以创建不同的表示。如下图所示
![](https://i-blog.csdnimg.cn/blog_migrate/be0489205be196d03a1704c051a53819.png)
- 分离
零部件的构造
(由Builder来负责)和装配
(由Director负责)。从而可以构造出复杂的对象。- 适用场景:某个对象的构建过程复杂的情况。
- 由于实现构建和装配的解耦。
不同的构建器
,相同的装配
,也可以做出不同的对象
;相同的构建器
,不同的装配顺序
也可以做出不同的对象
。即:实现构建算法(零部件构建)、装配算法(组装方式)的解耦,实现更好的复用
。 - 建造者模式可以将零部件和组装过程分开,一步一步创建一个复杂的对象。用户只需要
指定复杂对象的类型
就可以得到该对象,而无需知道内部的具体构造细节
。- 例如上图中,不同厂家的零部件,通过组装,同样可以得到一个机箱。
- 结构:建造者模式包含以下角色
- 抽象建造者(Builder):该接口规定要实现复杂对象的哪些部分的创建,并
不涉及具体对象部件的创建
。 - 具体建造者:实现Builder接口,完成复杂产品各个零部件的
具体创建方法
。在构建完成后,提供产品实例
。 - 产品类(Product):要创建的复杂对象
- 指挥者类(Director):调用具体Builder来创建
复杂对象的各个部分
,在Director中不涉及具体产品的信息
,只负责保证对象各个部分完整创建
或者按某种顺序创建
。
- 抽象建造者(Builder):该接口规定要实现复杂对象的哪些部分的创建,并
- 集中强调:装配过程
UML模型图如下所示:Director角色、Builder角色、Product角色
![](https://i-blog.csdnimg.cn/blog_migrate/1980904ec4825d135e4a789d24f16534.png)
- 优点:
- 封装性好。可以有效的封装变化,在使用中,一般产品类和建造者类是比较稳定的,因此,将主要的业务逻辑封装在指挥者类中,对整体而言,可以取得比较好的稳定性
- 客户端不需要知道内部组成细节,将产品本身和产品创建过程解耦,让相同的创建过程可以创建不同的产品对象
- 更精细化控制产品创建过程。将复杂产品的创建步骤分解到不同的方法中,让创建过程更清晰,易控制
- 易扩展,如有新的需求,通过实现一个新的建造者类即可,基本不用修改之前的代码,不存在引入风险,符合开闭原则
- 缺点
- 所创建的产品具有较多的共同点,组成部分相似,如果产品之间差异很大,则不适合使用该模式,范围受限
例如:
- 所创建的产品具有较多的共同点,组成部分相似,如果产品之间差异很大,则不适合使用该模式,范围受限
- 抽象建造者
public abstract class Builder {
//聚合Bike类型变量,并进行赋值
protected Bike bike = new Bike();
public abstract void buildFrame();
public abstract void buildSeat();
public abstract Bike createBike();
}
- 具体建造者
public class MobileBuilder extends Builder {
@Override
public void buildFrame() {
bike.setFrame("MobileBuilder -> 碳纤维车架");
}
@Override
public void buildSeat() {
bike.setSeat("MobileBuilder -> 真皮车座");
}
@Override
public Bike createBike() {
return bike;
}
}
- 产品类
public class Bike {
//车架
private String frame;
//车座
private String seat;
public String getFrame() {
return frame;
}
public void setFrame(String frame) {
this.frame = frame;
}
public String getSeat() {
return seat;
}
public void setSeat(String seat) {
this.seat = seat;
}
}
- 指挥者类
/**
* @description 指挥者类,指挥Builder去造车
* @since jdk11
*/
public class Director {
private Builder builder;
public Director(Builder builder) {
this.builder = builder;
}
/**
* 组装车
*
* @return 车
*/
public Bike construct() {
builder.buildFrame();
builder.buildSeat();
return builder.createBike();
}
}
- 【说明】:以上案例是Builder模式的
常规用法
,指挥者类Director
在建造者模式中具有很重要的作用
,用于指导具体构建者如何构建产品
,控制调用先后顺序
,并向调用者返回完整的产品类
,但是特殊情况下
需要建海系统结构,可以直接把指挥者和抽象构建者结合使用
;
但是,简化以后,同时也加重抽象建造者的职责
,不太符合单一职责原则
- 如下所示:
public abstract class Builder {
//聚合Bike类型变量,并进行赋值
protected Bike bike = new Bike();
public abstract void buildFrame();
public abstract void buildSeat();
/**
* 造车
*
* @return 车
*/
public abstract Bike createBike();
/**
* 组装车(Builder与Director结合一起使用,新增功能)
*
* @return 车
*/
public Bike construct() {
this.buildFrame();
this.buildSeat();
return this.createBike();
}
}
模式拓展
- 在开发中,还有一个常用的使用方式:
- 当一个
类构造器
需要传入很多参数时
,如果创建这个类的实例,代码可读性极差
,且易引入错误
,此时,可以利用建造者模式重构
例如:改造前
- 当一个
public class Phone {
private String cpu;
private String screen;
private String memory;
private String mainBoard;
//构造函数参数过多
public Phone(String cpu, String screen, String memory, String mainBoard) {
this.cpu = cpu;
this.screen = screen;
this.memory = memory;
this.mainBoard = mainBoard;
}
//省略get、set方法
}
- 利用建造者重构:重构后的代码使用起来更加方便,提高开发效率,从设计上,要求较高
public class Phone {
private String cpu;
private String screen;
private String memory;
private String mainBoard;
public PhoneExpand(Builder builder) {
this.cpu = builder.cpu;
this.screen = builder.screen;
this.memory = builder.memory;
this.mainBoard = builder.mainBoard;
}
@Override
public String toString() {
return "PhoneExpand{" +
"cpu='" + cpu + '\'' +
", screen='" + screen + '\'' +
", memory='" + memory + '\'' +
", mainBoard='" + mainBoard + '\'' +
'}';
}
/**
* 建造者
*/
public static final class Builder {
private String cpu;
private String screen;
private String memory;
private String mainBoard;
public Builder cpu(String cpu) {
this.cpu = cpu;
return this;
}
public Builder screen(String screen) {
this.screen = screen;
return this;
}
public Builder memory(String memory) {
this.memory = memory;
return this;
}
public Builder mainBoard(String mainBoard) {
this.mainBoard = mainBoard;
return this;
}
/**
* 使用创建者构建Phone对象
*
* @return
*/
public Phone build() {
return new PhoneExpand(this);
}
}
}
- 测试
public class BuilderTest {
public static void main(String[] args) {
//通过构建者对象,获取PhoneExpand对象
Phone phone = new Phone.Builder()
.cpu("intel")
.screen("三星")
.memory("金士顿")
.mainBoard("华硕")
.build();
System.out.println(phone.toString());
}
}
5.原型模式
- 概述:用一个
已经创建的实例
作为原型
,通过复制
该原型对象来创建一个和原型对象相同的新对象
。 - 结构:原型模式包含以下角色
- 抽象原型类:规定了具体原型对象必须实现的clone()方法。
- 具体原型类:实现抽象原型类clone()方法,它是可被复制的对象
- 访问类:使用具体原型类中的clone()方法来复制新的对象
UML图如下:
![](https://i-blog.csdnimg.cn/blog_migrate/3a6d7b1cdc98ff5d66a2355ec71583f3.png)
设计模式 - 结构型模式
- 结构型模式,描述如何将类或对象,按
某种布局成更大的结构
。分为:类结构型模式
、对象结构型模式
两种;- 类结构型模式:采用
继承机制
来组织接口和类。 - 对象结构型模式:采用
组合
或聚合
来组合对象。
- 类结构型模式:采用
- 应用场景:
- 老系统
存在
满足新系统功能需求的类
,但接口与新系统的接口不一致
。 - 使用
第三方提供的组件
,但组件接口定义和自己要求的接口定义不同
。
- 老系统
由于组合关系或聚合关系比继承关系耦合度更低
,满足合成复用原则
,所以对象结构型模式比类结构型模式具有更大的灵活性
1. 适配器模式
适配器模式:如日常生活中,笔记本、手机、电磁炉等不同的物品,适应的电压是不一样的,此时需要用到适配器模,对电压进行转换。
![](https://i-blog.csdnimg.cn/blog_migrate/148ed6f1cafb3b5bd86f5c4b7e9239bf.png)
- 定义:将一个列的接口转换成客户希望的另一个接口,使得原本由于接口不兼容,而不能一起工作的那些类,能一起工作。
- 适配器模式:分为类(继承方式)适配器模式、对象(聚合方式)适配器模式。
- 类适配器模式:耦合度更高,要求开发者了解现有组件库中的相关组件的内部结构,应用较少。
![](https://i-blog.csdnimg.cn/blog_migrate/8a5de556ba5eceb16b8338a3ae15aa23.png)
- 结构:适配器模式包含以下角色
- 目标接口:当前系统业务所期待的接口,它可以是
抽象类或接口
。 - 适配者类:
被访问
和适配
的现存组件库中的组件接口
。 - 适配器类:是一个
转换器
,通过继承
或者引用
适配者的对象
,把适配者接口
转换成目标接口
,让客户按目标接口的格式访问适配者
。
- 目标接口:当前系统业务所期待的接口,它可以是
1.1 类适配器模式
- 定义一个
适配器类
来实现当前系统的业务接口
,同时又继承
现有组件库中已经存在的组件
。
例如:读卡器,UML图如下:
![](https://i-blog.csdnimg.cn/blog_migrate/adc0295d802aba02fa61329afdd26ec1.png)
- 类适配器模式:违背
合成复用原则
。类适配器是客户类有一个接口规范
的情况下可用
,否则不可用 - 目标接口:
/**
* @description 目标接口
* @since jdk11
*/
public interface SdCard {
/**
* 从SD卡中 读取数据
* @return SD卡数据
*/
String read();
/**
* 从SD卡中 写入数据
* @param message 数据信息
*/
void write(String message);
}
/**
* @description 具体的SD卡类
* @since jdk11
*/
public class SdCardImpl implements SdCard {
@Override
public String read() {
return "SdCard 读取SD卡信息: hello world SD card";
}
@Override
public void write(String message) {
System.out.println("SdCard Write message: "+message);
}
}
- 适配者类的接口
/**
* @description 适配者类的接口
* @since jdk11
*/
public interface TfCard {
/**
* 读卡
* @return 读取结果
*/
String readTf();
/**
* 写卡
* @param message 数据信息
*/
void writeTf(String message);
}
/**
* @description 适配者类
* @since jdk11
*/
public class TfCardImpl implements TfCard{
@Override
public String readTf() {
return "TF卡读取数据,hello world TF CARD";
}
@Override
public void writeTf(String message) {
System.out.println("TF CARD Write message : " + message);
}
}
- 适配器类
/**
* @description 适配器类
* @since jdk11
*/
public class SdToTfAdapter extends TfCardImpl implements SdCard {
@Override
public String read() {
System.out.println("SdToTfAdapter read TF card");
return super.readTf();
}
@Override
public void write(String message) {
System.out.println("SdToTfAdapter write TF card");
super.writeTf(message);
}
}
- 测试
public class AdapterTest {
public static void main(String[] args) {
Computer computer = new Computer();
String result = computer.readSDCard(new SdCardImpl());
System.out.println(result);
System.out.println("----------------------------------");
//适配器
String messageSd = computer.readSDCard(new SdToTfAdapter());
System.out.println(messageSd);
}
}
1.2 对象适配器
- 可采用将现有组件库中,已经实现的组件,引入适配器类中,该类同时实现当前系统的业务接口
- 注意:
- 还有一个适配器模式是接口适配器模式,当不希望实现一个接口中的所有方法时,可以创建一个抽象类Adapter,实现所有方法。此时,只需要继承抽象类即可。
读卡器案例:UML图如下
![](https://i-blog.csdnimg.cn/blog_migrate/b07d050aea102c143475e54628a0f9a6.png)
- 目标接口和适配者接口 参考1.1 章节部分
- 对象适配器
/**
* @description 对象适配器
* @since jdk11
*/
public class SdToTfAdapter implements SdCard {
/**
* 聚合适配者类对象
*/
private TfCard tfCard;
public SdToTfAdapter(TfCard tfCard) {
this.tfCard = tfCard;
}
@Override
public String read() {
System.out.println("SdToTfAdapter read TF card");
return tfCard.readTf();
}
@Override
public void write(String message) {
System.out.println("SdToTfAdapter write TF card");
tfCard.writeTf(message);
}
}
- 测试
public class AdapterTest {
public static void main(String[] args) {
Computer computer = new Computer();
String result = computer.readSDCard(new SdCardImpl());
System.out.println(result);
String message = computer.readSDCard(new SdToTfAdapter(new TfCardImpl()));
System.out.println(message);
}
}
2. 桥接模式
如果说某个系统能够从多个角度来进行分类,且每一种分类都可能会变化,那么我们需要做的就是讲这多个角度分离出来,使得他们能独立变化,减少他们之间的耦合,这个分离过程就使用了桥接模式。所谓桥接模式就是讲抽象部分和实现部分隔离开来,使得他们能够独立变化。
桥接模式将继承关系转化成关联关系,封装了变化,完成了解耦,减少了系统中类的数量,也减少了代码量。
编辑
桥接模式包含如下角色:
Abstraction:抽象类
RefinedAbstraction:扩充抽象类
Implementor:实现类接口
ConcreteImplementor:具体实现类
桥接模式示例
//假设:系统接口
public interface DrawApi {
public void DrawCircle(int redis,int x, int y);
}
//抽离的实现类 1
class RedisCircle implements DrawApi{
@Override
public void DrawCircle(int redis, int x, int y) {
System.out.println("画出红色的圆=="+ redis + "|| " + x + "||" + y);
}
}
//抽离的实现类 2
class GreenCircle implements DrawApi{
@Override
public void DrawCircle(int redis, int x, int y) {
System.out.println("画出绿色的圆=="+ redis + "|| " + x + "||" + y);
}
}
//桥接系统方法
abstract class Shape {
//将系统类,桥接到Shape中
protected DrawApi drawApi;
public Shape(DrawApi drawApi){
this.drawApi = drawApi;
}
public abstract void draw();
}
//业务实现
class Circle extends Shape{
private int x,y,redius;
public Circle(int x,int y,int redius,DrawApi drawAPI) {
super(drawAPI);
this.x=x;
this.y = y;
this.redius = redius;
}
//重写
@Override
public void draw() {
drawApi.DrawCircle(x,y,redius);
}
}
class test{
public static void main(String[] args) {
Shape redCircle = new Circle(100,100, 10,new RedisCircle());
Shape greenCircle = new Circle(100,100, 10,new GreenCircle());
//调用重写方法
redCircle.draw();
greenCircle.draw();
}
}
3. 组合模式
- 定义:组合模式又称为部分整体模式,用于把
一组相似的对象
当做一个单一的对象
,依据树形结构
来组合对象,用来表示部分以及整体层次。创建了对象组
的树形结构
。
例如:文件系统,一般都是树形结构,树形结构中通过调用某个方法遍历整个树,找到叶子结点后,即可对叶子结点操作,它们是整体与部分的关系。
![](https://i-blog.csdnimg.cn/blog_migrate/01b8e19f068e9d247f227b968916af72.png)
-
结构:组合模式主要包含三种角色:
- 抽象根节点:定义系统各层次对象的共有
方法和属性
,可以预先定义一些默认行为和属性 - 树枝节点:定义
树枝节点的行为,储存子节点
,组合树枝节点
和叶子结点
形成一个树形结构
。 - 叶子结点:其下
再无分支
,是系统层次遍历的最小单元
。
- 抽象根节点:定义系统各层次对象的共有
-
组合模式的UML结构图如下所示:
![](https://i-blog.csdnimg.cn/blog_migrate/79518f0e5123d0e5ef61cecb28ab3c43.png)
例如:软件菜单案例,UML结构如下图所示:
![](https://i-blog.csdnimg.cn/blog_migrate/9dbeaa2b025a78b1ea0044b663eb9d06.png)
- 抽象根节点:菜单组件,属于抽象根节点
/**
* @description 菜单组件,属于抽象根节点
* @since jdk11
*/
public abstract class MenuComponent {
/**
* 菜单组件名称
*/
protected String name;
/**
* 菜单组件层级
*/
protected int level;
/**
* 添加子菜单
*
* @param menuComponent 菜单项或菜单项
*/
public void add(MenuComponent menuComponent) {
throw new UnsupportedOperationException("菜单项下不能再有子菜单");
}
/**
* 移除子菜单
*
* @param menuComponent 菜单项或菜单项
*/
public void remove(MenuComponent menuComponent) {
}
/**
* 获取指定的菜单
*
* @param index 菜单索引
*/
public MenuComponent getChild(int index) {
throw new UnsupportedOperationException("菜单项下不能再有子菜单");
}
/**
* 打印菜单名称(包含子菜单和菜单项)
*/
public abstract void print();
public String getName() {
return name;
}
}
- 树枝节点 :
/**
* @description 菜单类:属于 树枝节点
* @since jdk11
*/
public class Menu extends MenuComponent {
//聚合MenuComponent:菜单有一到多个子菜单
private List<MenuComponent> menuComponentList = new ArrayList<>();
public Menu(String menuName, int menuLevel) {
this.name = menuName;
this.level = menuLevel;
}
@Override
public void add(MenuComponent menuComponent) {
menuComponentList.add(menuComponent);
}
@Override
public void remove(MenuComponent menuComponent) {
menuComponentList.remove(menuComponent);
}
@Override
public MenuComponent getChild(int index) {
return menuComponentList.get(index);
}
@Override
public void print() {
for (int i = 0; i < level; i++) {
System.out.print("--");
}
//打印菜单
System.out.println(name);
//打印子菜单或者子菜单项名称
for (MenuComponent menuComponent : menuComponentList) {
menuComponent.print();
}
}
}
- 叶子结点:
/**
* @description 菜单项:属于叶子节点
* @since jdk11
*/
public class MenuItem extends MenuComponent{
public MenuItem(String itemName,int itemLevel) {
this.name = itemName;
this.level = itemLevel;
}
@Override
public void print() {
for (int i = 0; i < level; i++) {
System.out.print("--");
}
//打印菜单名称
System.out.println(name);
}
}
- 测试
public class MenuTree {
protected MenuComponent getMenu(){
//菜单树
MenuComponent menuManagement = new Menu("菜单管理",2);
menuManagement.add(new MenuItem("页面访问",3));
menuManagement.add(new MenuItem("展开菜单",3));
menuManagement.add(new MenuItem("编辑菜单",3));
menuManagement.add(new MenuItem("删除菜单",3));
menuManagement.add(new MenuItem("新增菜单",3));
MenuComponent roleManagement = new Menu("角色管理",2);
roleManagement.add(new MenuItem("页面访问",3));
roleManagement.add(new MenuItem("新增角色",3));
roleManagement.add(new MenuItem("修改角色",3));
MenuComponent permissionManagement = new Menu("权限管理",2);
permissionManagement.add(new MenuItem("页面访问",3));
permissionManagement.add(new MenuItem("提交保存",3));
//一级菜单
MenuComponent mainMenu = new Menu("系统管理",1);
//二级菜单添加到一级菜单中
mainMenu.add(menuManagement);
mainMenu.add(roleManagement);
mainMenu.add(permissionManagement);
return mainMenu;
}
}
public class CompositeTest {
public static void main(String[] args) {
MenuTree menuTree = new MenuTree();
MenuComponent component = menuTree.getMenu();
component.print();
}
}
4. 装饰模式
我们可以通过继承和组合的方式来给一个对象添加行为,虽然使用继承能够很好拥有父类的行为,但是它存在几个缺陷:一、对象之间的关系复杂的话,系统变得复杂不利于维护。二、容易产生“类爆炸”现象。三、是静态的。在这里我们可以通过使用装饰者模式来解决这个问题。
装饰者模式,动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更加有弹性的替代方案。虽然装饰者模式能够动态将责任附加到对象上,但是他会产生许多的细小对象,增加了系统的复杂度。
模式结构
装饰模式包含如下角色:
Component: 抽象构件
ConcreteComponent: 具体构件
Decorator: 抽象装饰类
ConcreteDecorator: 具体装饰类
5. 外观模式
我们都知道类与类之间的耦合越低,那么可复用性就越好,如果两个类不必彼此通信,那么就不要让这两个类发生直接的相互关系,如果需要调用里面的方法,可以通过第三者来转发调用。外观模式非常好的诠释了这段话。外观模式提供了一个统一的接口,用来访问子系统中的一群接口。它让一个应用程序中子系统间的相互依赖关系减少到了最少,它给子系统提供了一个简单、单一的屏障,客户通过这个屏障来与子系统进行通信。通过使用外观模式,使得客户对子系统的引用变得简单了,实现了客户与子系统之间的松耦合。但是它违背了“开闭原则”,因为增加新的子系统可能需要修改外观类或客户端的源代码。
外观模式包含如下角色:
Facade: 外观角色
SubSystem:子系统角色
6. 亨元模式
在一个系统中对象会使得内存占用过多,特别是那些大量重复的对象,这就是对系统资源的极大浪费。享元模式对对象的重用提供了一种解决方案,它使用共享技术对相同或者相似对象实现重用。享元模式就是运行共享技术有效地支持大量细粒度对象的复用。系统使用少量对象,而且这些都比较相似,状态变化小,可以实现对象的多次复用。这里有一点要注意:享元模式要求能够共享的对象必须是细粒度对象。享元模式通过共享技术使得系统中的对象个数大大减少了,同时享元模式使用了内部状态和外部状态,同时外部状态相对独立,不会影响到内部状态,所以享元模式能够使得享元对象在不同的环境下被共享。同时正是分为了内部状态和外部状态,享元模式会使得系统变得更加复杂,同时也会导致读取外部状态所消耗的时间过长。
享元模式包含如下角色:
Flyweight: 抽象享元类
ConcreteFlyweight: 具体享元类
UnsharedConcreteFlyweight: 非共享具体享元类
FlyweightFactory: 享元工厂类
7. 代理模式
- 概述
- 由于某些原因需要给某个对象提供一个代理,以控制该对象的访问。此时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。
- 按照代理类分为:静态代理和动态代理。
- 静态代理:代理类在编译期生成,称为接口代理
- 动态代理:代理类在运行时生成,称为类代理。分为JDK代理和CGLIB代理两种
- 结构:代理模式分为三种角色:
- 抽象主题类:通过接口或抽象类,声明真实主题和代理对象的业务方法
- 真是主题类:实现抽象主题中的具体业务,是代理对象的真实对象,是最终引用的对象
- 代理类:提供与真实主题相同的接口,内部含有对真实主题的引用,可以访问、控制、扩展真实主题的功能。
![](https://i-blog.csdnimg.cn/blog_migrate/f8345695134f2fcffa84160657bfeaf4.png)
- 优点:
- 在客户端和目标对象之间起到一个中介作用和保护目标对象的作用
- 可以扩展目标对象的功能
- 将客户端与目标对象分离,降低了系统的耦合度
- 缺点
- 增加系统的复杂度
- 使用场景
- 控制对一个对象的访问,如果需要,可以给
不同的用户
提供不同级别
的使用权限
。
- 控制对一个对象的访问,如果需要,可以给
7.1 静态代理模式
火车票售卖案例,静态代理UML图如下:
![](https://i-blog.csdnimg.cn/blog_migrate/732f6737b4cd2417553856cc9a4bf17e.png)
- 抽象主题类
public interface SellTickets{
public void sell();
}
- 真是主题类:火车站
class TranStation implements SellTickets{
@Override
public void sell(){
System.out.println("TranStation -> 火车站售票点:售票: " + tickets);
}
}
- 代理类:代售点类
//代理 SellTickets
class ProxyPoint implements SellTickets{
//聚合火车站
private TranStation tranStation = new TranStation();
@Override
public void sell() {
System.out.println("火车票代售点,售卖火车票,并收取服务费");
//通过火车站拿到火车票代售权,并执行火车票销售
tranStation.sell()
}
}
- 测试
class testProxyDemo{
public static void main(String[] args) {
//代售点类对对象
ProxyPoint sellTickets = new ProxyPoint ();
//卖票
sellTickets.sell();
}
}
- 测试结果,测试类直接访问ProxyPoint,即:ProxyPoint 作为访问对象(testProxyDemo)和目标对象(TranStation)的中介。同时也对sell()方法增强(火车票代售点,售卖火车票,并收取服务费)
7.2 JDK 动态代理
java中提供了一个动态代理类Proxy,Proxy并不是上面所说代理对象的类,而是提供了一个创建代理对象的静态方法(newProxyInstance方法)来获取代理对象。
案例如下:
- 主题对象、真实对象沿用 7.1 部分对象
- 获取代理对象
public class ProxyFactory {
//目标对象
private TranStation tranStation = new TranStation();
public SellTickets getProxyObject() {
/**
* ClassLoader loader:类加载器,用于加载代理类。可以通过目标对象获取类加载器
* Class<?>[] interfaces:代理类实现的接口的字节码对象
* InvocationHandler h:代理对象的调用处理程序
*/
SellTickets sellTickets = (SellTickets) Proxy.newProxyInstance(tranStation.getClass().getClassLoader(),
tranStation.getClass().getInterfaces(), new InvocationHandler() {
/**
*
* @param proxy 代理对象。和 sellTickets是同一个对象,在invoke方法中基本不用
* @param method 对接口中的方法进行封装的method对象
* @param args 调用方法的实际参数:就是sell()方法的入参
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("代理方法内部类,调用处理程序...");
// 执行目标对象的方法
Object object = method.invoke(tranStation, args);
System.out.println("代售点收取一定的服务费用 : " + object);
return object;
}
});
System.out.println("代理方法,生成的代理对象为:" + sellTickets);
return sellTickets;
}
}
- 测试
public class JdkDynamicProxyTest {
public static void main(String[] args) {
ProxyFactory proxyFactory = new ProxyFactory();
SellTickets sellTickets = proxyFactory.getProxyObject();
sellTickets.sell();
}
}
7.3 CGLIB动态代理
CGLIB是一个功能强大、高性能的代码生成包。没有实现接口的类提供代理,为JDK的动态代理提供了较好的补充。
引入CGLIB坐标
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.5</version>
</dependency>
案例如下:
- 主题对象、真实对象沿用 7.1 部分对象
- 获取代理对象
public class CglibProxyFactory implements MethodInterceptor {
private TranStation tranStation = new TranStation();
public TranStation getProxyObject() {
//创建enhancer,类似jdk动态代理的proxy对象
Enhancer enhancer = new Enhancer();
//设置父类(TranStation)的字节码对象
enhancer.setSuperclass(TranStation.class);
//设置回调函数 :即:MethodInterceptor 的 intercept 方法,入参是MethodInterceptor子实现类的对象
//即:intercept()方法返回的对象
enhancer.setCallback(this);
return (TranStation) enhancer.create();
}
//在执行sellTickets.sell()方法时,代理工厂类中实际执行的是 intercept方法
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
//需要调用目标对象的方法
Object invoke = method.invoke(tranStation, objects);
System.out.println("代理售票点,售卖火车票,并收取手续费....." + invoke);
return invoke;
}
}
- 测试
public class CglibDynamicProxyTest {
public static void main(String[] args) {
CglibProxyFactory proxyFactory = new CglibProxyFactory();
TranStation sellTickets = proxyFactory.getProxyObject();
sellTickets.sell();
}
}
设计模式 - 行为型模式
1. 访问者模式
访问者模式俗称23大设计模式中最难的一个。除了结构复杂外,理解也比较难。在我们软件开发中我们可能会对同一个对象有不同的处理,如果我们都做分别的处理,将会产生灾难性的错误。对于这种问题,访问者模式提供了比较好的解决方案。访问者模式即表示一个作用于某对象结构中的各元素的操作,它使我们可以在不改变各元素的类的前提下定义作用于这些元素的新操作。
访问者模式的目的是封装一些施加于某种数据结构元素之上的操作,一旦这些操作需要修改的话,接受这个操作的数据结构可以保持不变。为不同类型的元素提供多种访问操作方式,且可以在不修改原有系统的情况下增加新的操作方式。同时我们还需要明确一点那就是访问者模式是适用于那些数据结构比较稳定的,因为他是将数据的操作与数据结构进行分离了,如果某个系统的数据结构相对稳定,但是操作算法易于变化的话,就比较适用适用访问者模式,因为访问者模式使得算法操作的增加变得比较简单了。
访问者模式包含如下角色:
Vistor: 抽象访问者
ConcreteVisitor: 具体访问者
Element: 抽象元素
ConcreteElement: 具体元素
ObjectStructure: 对象结构
2. 模板模式
有些时候我们做某几件事情的步骤都差不多,仅有那么一小点的不同,在软件开发的世界里同样如此,如果我们都将这些步骤都一一做的话,费时费力不讨好。所以我们可以将这些步骤分解、封装起来,然后利用继承的方式来继承即可,当然不同的可以自己重写实现嘛!这就是模板方法模式提供的解决方案。
所谓模板方法模式就是在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
模板方法模式就是基于继承的代码复用技术的。在模板方法模式中,我们可以将相同部分的代码放在父类中,而将不同的代码放入不同的子类中。也就是说我们需要声明一个抽象的父类,将部分逻辑以具体方法以及具体构造函数的形式实现,然后声明一些抽象方法让子类来实现剩余的逻辑,不同的子类可以以不同的方式来实现这些逻辑。所以模板方法的模板其实就是一个普通的方法,只不过这个方法是将算法实现的步骤封装起来的。
模板方法模式包含如下角色:
AbstractClass: 抽象类
ConcreteClass: 具体子类
- 模板模式是一种行为设计模式,它定义了一个操作中的算法框架,将某些步骤的具体实现延迟到子类中。模板模式使得子类能够在不改变算法结构的情况下重新定义算法的某些步骤。
在Java中实现模板模式,我们可以通过抽象类来定义算法框架,然后在具体的子类中实现具体的步骤。
例如:
abstract class AbstractClass {
// 模板方法定义了算法的框架
public void templateMethod() {
step1();
step2();
step3();
}
// 抽象方法,需要子类去实现
protected abstract void step1();
// 抽象方法,需要子类去实现
protected abstract void step2();
// 默认实现,子类可以选择覆盖
protected void step3() {
System.out.println("AbstractClass: Performing default step3");
}
}
class ConcreteClass1 extends AbstractClass {
@Override
protected void step1() {
System.out.println("ConcreteClass1: Performing step1");
}
@Override
protected void step2() {
System.out.println("ConcreteClass1: Performing step2");
}
}
class ConcreteClass2 extends AbstractClass {
@Override
protected void step1() {
System.out.println("ConcreteClass2: Performing step1");
}
@Override
protected void step2() {
System.out.println("ConcreteClass2: Performing step2");
}
@Override
protected void step3() {
System.out.println("ConcreteClass2: Performing custom step3");
}
}
public class TemplatePatternExample {
public static void main(String[] args) {
AbstractClass class1 = new ConcreteClass1();
class1.templateMethod();
AbstractClass class2 = new ConcreteClass2();
class2.templateMethod();
}
}
在上面的示例中,AbstractClass
定义了算法的框架,其中的templateMethod()
是模板方法,定义了算法的执行顺序。AbstractClass
还包含了一些抽象方法,step1()
和step2()
,这些方法需要具体的子类去实现。step3()
方法是一个可选择的步骤,提供了默认实现,但子类可以选择覆盖它。
ConcreteClass1
和ConcreteClass2
是具体的子类,它们实现了抽象父类中定义的抽象方法。在main()
方法中,我们可以看到如何使用模板模式,创建不同的子类实例并调用它们的templateMethod()
方法。这样就实现了相同的算法结构,但具体的实现可以根据不同的需求而变化。
3. 策略模式
- 策略模式是一种行为型设计模式,它允许在运行时动态地选择算法的行为。策略模式通过将算法封装成各个独立的类,使得它们可以互相替换,而不影响到使用算法的客户端。
我们知道一件事可能会有很多种方式来实现它,但是其中总有一种最高效的方式,在软件开发的世界里面同样如此,我们也有很多中方法来实现一个功能,但是我们需要一种简单、高效的方式来实现它,使得系统能够非常灵活,这就是策略模式。
所以策略模式就是定义了算法族,分别封装起来,让他们之前可以互相转换,此模式然该算法的变化独立于使用算法的客户。
在策略模式中它将这些解决问题的方法定义成一个算法群,每一个方法都对应着一个具体的算法,这里的一个算法我就称之为一个策略。虽然策略模式定义了算法,但是它并不提供算法的选择,即什么算法对于什么问题最合适这是策略模式所不关心的,所以对于策略的选择还是要客户端来做。客户必须要清楚的知道每个算法之间的区别和在什么时候什么地方使用什么策略是最合适的,这样就增加客户端的负担。
同时策略模式也非常完美的符合了“开闭原则”,用户可以在不修改原有系统的基础上选择算法或行为,也可以灵活地增加新的算法或行为。但是一个策略对应一个类将会是系统产生很多的策略类。
策略模式包含如下角色:
Context: 环境类
Strategy: 抽象策略类
ConcreteStrategy: 具体策略类
策略模式是一种行为型设计模式,它允许在运行时根据不同的情况选择不同的算法或策略。它可以使算法的变化独立于使用算法的客户端。
使用注解和枚举方式来实现策略模式可以增加代码的可读性和简化使用方式。
例如:
首先,我们定义一个策略接口:
public interface PaymentStrategy {
void pay(double amount);
}
然后,我们实现几个具体的策略类:
public class CreditCardStrategy implements PaymentStrategy {
private String cardNumber;
private String cvv;
private String dateOfExpiry;
public CreditCardStrategy(String cardNumber, String cvv, String dateOfExpiry) {
this.cardNumber = cardNumber;
this.cvv = cvv;
this.dateOfExpiry = dateOfExpiry;
}
@Override
public void pay(double amount) {
System.out.println(String.format("Paid $%.2f using credit card.", amount));
}
}
public class PayPalStrategy implements PaymentStrategy {
private String email;
private String password;
public PayPalStrategy(String email, String password) {
this.email = email;
this.password = password;
}
@Override
public void pay(double amount) {
System.out.println(String.format("Paid $%.2f using PayPal.", amount));
}
}
接下来,我们可以使用注解和枚举来实现策略的选择和配置。首先,定义一个注解@PaymentMethod
用于标记策略类:
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface PaymentMethod {
PaymentType value();
}
然后,定义一个枚举类型PaymentType
来表示不同的支付方式:
public enum PaymentType {
CREDIT_CARD,
PAYPAL
}
接着,我们修改策略类,使用PaymentMethod
注解标记不同的策略:
@PaymentMethod(PaymentType.CREDIT_CARD)
public class CreditCardStrategy implements PaymentStrategy {
//...
}
@PaymentMethod(PaymentType.PAYPAL)
public class PayPalStrategy implements PaymentStrategy {
//...
}
接下来,我们实现一个策略选择器类PaymentChooser
,它通过枚举类型来查找匹配的策略类:
import java.lang.annotation.Annotation;
import java.util.Arrays;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;
public class PaymentChooser {
private static Map<PaymentType, PaymentStrategy> strategies;
static {
strategies = Arrays.stream(PaymentType.values())
.collect(Collectors.toMap(Function.identity(), PaymentChooser::resolveStrategy));
}
private static PaymentStrategy resolveStrategy(PaymentType paymentType) {
String pk = paymentType.getClass().getPackage().getName();
String path = pk.replace('.', '/');
ClassLoader classloader = Thread.currentThread().getContextClassLoader();
URL url = classloader.getResource(path);
try {
List<Class<?>> classes = getClasses(new File(url.getFile()), pk);
Optional<Class<?>> strategyClass = classes.stream()
.filter(c -> c.isAnnotationPresent(PaymentMethod.class) && paymentType == c.getAnnotation(PaymentMethod.class).value())
.findFirst();
if (strategyClass.isPresent()) {
try {
return (PaymentStrategy) strategyClass.get().getDeclaredConstructor().newInstance();
} catch (Exception e) {
e.printStackTrace();
}
}
throw new IllegalArgumentException("No strategy found for payment type: " + paymentType);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return null;
}
public static void choosePayment(PaymentType paymentType, double amount) {
strategies.get(paymentType).pay(amount);
}
private static List<Class<?>> getClasses(File dir, String pk) throws ClassNotFoundException {
List<Class<?>> classes = new ArrayList<>();
if (!dir.exists()) {
return classes;
}
for (File f : dir.listFiles()) {
if (f.isDirectory()) {
classes.addAll(getClasses(f, pk + "." + f.getName()));
}
String name = f.getName();
if (name.endsWith(".class")) {
classes.add(Class.forName(pk + "." + name.substring(0, name.length() - 6)));
}
}
return classes;
}
}
最后,我们可以使用策略选择器类来选择并使用不同的支付策略:
public class Main {
public static void main(String[] args) {
PaymentChooser.choosePayment(PaymentType.CREDIT_CARD, 100.00);
PaymentChooser.choosePayment(PaymentType.PAYPAL, 50.00);
}
}
这样,根据不同的策略选择,输出结果如下:
Paid $100.00 using credit card.
Paid $50.00 using PayPal.
通过注解和枚举的方式实现策略模式,可以更加灵活地配置和选择不同的策略,同时也能提高代码的可读性和可维护性。
运行以上代码,根据不同的paymentMethod
选择相应的支付策略进行支付。
使用注解和枚举方式实现策略模式,可以更灵活地扩展和管理策略类,而无需硬编码每个具体策略类的实例化过程。通过使用ServiceLoader
来加载标有注解的类,可以实现自动发现并注册策略类的功能。这种方式使得策略的添加和修改更加方便。
4. 状态模式
在很多情况下我们对象的行为依赖于它的一个或者多个变化的属性,这些可变的属性我们称之为状态,也就是说行为依赖状态,即当该对象因为在外部的互动而导致他的状态发生变化,从而它的行为也会做出相应的变化。对于这种情况,我们是不能用行为来控制状态的变化,而应该站在状态的角度来思考行为,即是什么状态就要做出什么样的行为。这个就是状态模式。
所以状态模式就是允许对象在内部状态发生改变时改变它的行为,对象看起来好像修改了它的类。
在状态模式中我们可以减少大块的if…else语句,它是允许态转换逻辑与状态对象合成一体,但是减少if…else语句的代价就是会换来大量的类,所以状态模式势必会增加系统中类或者对象的个数。
同时状态模式是将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为。但是这样就会导致系统的结构和实现都会比较复杂,如果使用不当就会导致程序的结构和代码混乱,不利于维护。
状态模式包含如下角色:
Context: 环境类
State: 抽象状态类
ConcreteState: 具体状态类
5. 观察者模式
何谓观察者模式?观察者模式定义了对象之间的一对多依赖关系,这样一来,当一个对象改变状态时,它的所有依赖者都会收到通知并且自动更新。
在这里,发生改变的对象称之为观察目标,而被通知的对象称之为观察者。一个观察目标可以对应多个观察者,而且这些观察者之间没有相互联系,所以么可以根据需要增加和删除观察者,使得系统更易于扩展。所以观察者提供了一种对象设计,让主题和观察者之间以松耦合的方式结合。
观察者模式包含如下角色:
Subject: 目标
ConcreteSubject: 具体目标
Observer: 观察者
ConcreteObserver: 具体观察者
6. 备忘录模式
后悔药人人都想要,但是事实却是残酷的,根本就没有后悔药可买,但是也不仅如此,在软件的世界里就有后悔药!备忘录模式就是一种后悔药,它给我们的软件提供后悔药的机制,通过它可以使系统恢复到某一特定的历史状态。
所谓备忘录模式就是在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样可以在以后将对象恢复到原先保存的状态。它实现了对信息的封装,使得客户不需要关心状态保存的细节。保存就要消耗资源,所以备忘录模式的缺点就在于消耗资源。如果类的成员变量过多,势必会占用比较大的资源,而且每一次保存都会消耗一定的内存。
备忘录模式包含如下角色:
Originator: 原发器
Memento: 备忘录
Caretaker: 负责人
7. 中介者模式
租房各位都有过的经历吧!在这个过程中中介结构扮演着很重要的角色,它在这里起到一个中间者的作用,给我们和房主互相传递信息。在外面软件的世界里同样需要这样一个中间者。在我们的系统中有时候会存在着对象与对象之间存在着很强、复杂的关联关系,如果让他们之间有直接的联系的话,必定会导致整个系统变得非常复杂,而且可扩展性很差!在前面我们就知道如果两个类之间没有不必彼此通信,我们就不应该让他们有直接的关联关系,如果实在是需要通信的话,我们可以通过第三者来转发他们的请求。同样,这里我们利用中介者来解决这个问题。
所谓中介者模式就是用一个中介对象来封装一系列的对象交互,中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。在中介者模式中,中介对象用来封装对象之间的关系,各个对象可以不需要知道具体的信息通过中介者对象就可以实现相互通信。它减少了对象之间的互相关系,提供了系统可复用性,简化了系统的结构。
在中介者模式中,各个对象不需要互相知道了解,他们只需要知道中介者对象即可,但是中介者对象就必须要知道所有的对象和他们之间的关联关系,正是因为这样就导致了中介者对象的结构过于复杂,承担了过多的职责,同时它也是整个系统的核心所在,它有问题将会导致整个系统的问题。所以如果在系统的设计过程中如果出现“多对多”的复杂关系群时,千万别急着使用中介者模式,而是要仔细思考是不是您设计的系统存在问题。
Mediator: 抽象中介者
ConcreteMediator: 具体中介者
Colleague: 抽象同事类
ConcreteColleague: 具体同事类
8. 迭代器模式
对于迭代在编程过程中我们经常用到,能够游走于聚合内的每一个元素,同时还可以提供多种不同的遍历方式,这就是迭代器模式的设计动机。在我们实际的开发过程中,我们可能会需要根据不同的需求以不同的方式来遍历整个对象,但是我们又不希望在聚合对象的抽象接口中充斥着各种不同的遍历操作,于是我们就希望有某个东西能够以多种不同的方式来遍历一个聚合对象,这时迭代器模式出现了。
何为迭代器模式?所谓迭代器模式就是提供一种方法顺序访问一个聚合对象中的各个元素,而不是暴露其内部的表示。迭代器模式是将迭代元素的责任交给迭代器,而不是聚合对象,我们甚至在不需要知道该聚合对象的内部结构就可以实现该聚合对象的迭代。
通过迭代器模式,使得聚合对象的结构更加简单,它不需要关注它元素的遍历,只需要专注它应该专注的事情,这样就更加符合单一职责原则了。
迭代器模式包含如下角色:
Iterator: 抽象迭代器
ConcreteIterator: 具体迭代器
Aggregate: 抽象聚合类
ConcreteAggregate: 具体聚合类
9. 解释器模式
所谓解释器模式就是定义语言的文法,并且建立一个解释器来解释该语言中的句子。解释器模式描述了如何构成一个简单的语言解释器,主要应用在使用面向对象语言开发的编译器中。它描述了如何为简单的语言定义一个文法,如何在该语言中表示一个句子,以及如何解释这些句子。
解释器模式包含如下角色:
AbstractExpression: 抽象表达式
TerminalExpression: 终结符表达式
NonterminalExpression: 非终结符表达式
Context: 环境类
Client: 客户类
10. 命令模式
有些时候我们想某个对象发送一个请求,但是我们并不知道该请求的具体接收者是谁,具体的处理过程是如何的,们只知道在程序运行中指定具体的请求接收者即可,对于这样将请求封装成对象的我们称之为命令模式。所以命令模式将请求封装成对象,以便使用不同的请求、队列或者日志来参数化其他对象。同时命令模式支持可撤销的操作。
命令模式可以将请求的发送者和接收者之间实现完全的解耦,发送者和接收者之间没有直接的联系,发送者只需要知道如何发送请求命令即可,其余的可以一概不管,甚至命令是否成功都无需关心。同时我们可以非常方便的增加新的命令,但是可能就是因为方便和对请求的封装就会导致系统中会存在过多的具体命令类。
命令模式包含如下角色:
Command: 抽象命令类
ConcreteCommand: 具体命令类
Invoker: 调用者
Receiver: 接收者
Client:客户类
11. 职责链模式
职责链模式描述的请求如何沿着对象所组成的链来传递的。它将对象组成一条链,发送者将请求发给链的第一个接收者,并且沿着这条链传递,直到有一个对象来处理它或者直到最后也没有对象处理而留在链末尾端。
避免请求发送者与接收者耦合在一起,让多个对象都有可能接收请求,将这些对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止,这就是职责链模式。在职责链模式中,使得每一个对象都有可能来处理请求,从而实现了请求的发送者和接收者之间的解耦。同时职责链模式简化了对象的结构,它使得每个对象都只需要引用它的后继者即可,而不必了解整条链,这样既提高了系统的灵活性也使得增加新的请求处理类也比较方便。但是在职责链中我们不能保证所有的请求都能够被处理,而且不利于观察运行时特征。
职责链模式包含如下角色:
Handler: 抽象处理者
ConcreteHandler: 具体处理者
Client: 客户类
高内聚、低耦合
-
高内聚和低耦合是软件设计中常用的两个概念,目的是提高代码的可读性、可维护性和可扩展性。
高内聚(High Cohesion)是指一个模块或一个类应该有清晰的目标和职责,模块内部的各个成员(方法或属性)之间应该紧密相关,实现一个单一的功能,与外部模块之间的依赖应该尽量减少。高内聚的代码更容易理解和修改,也更易于进行单元测试和重用。 -
低耦合(Low Coupling)是指模块或类之间的关联关系应该尽量松散,一个模块对其他模块的依赖应该降到最低,模块之间只通过必要的接口进行通信。低耦合的代码更容易进行模块化开发和维护,有利于代码的重用和替换。
下面以一个简单的实例说明高内聚和低耦合的概念,我将使用Java代码来展示:
- 高内聚示例:
class Circle {
private double radius;
public void setRadius(double radius) {
this.radius = radius;
}
public double calculateArea() {
return Math.PI * radius * radius;
}
}
class Main {
public static void main(String[] args) {
Circle circle = new Circle();
circle.setRadius(5.0);
double area = circle.calculateArea();
System.out.println("圆的面积:" + area);
}
}
在上述代码中,Circle类具有高内聚性。它包含了一个属性 radius 和两个方法 setRadius 和 calculateArea。这个类只负责计算圆的面积,所有与圆相关的操作都被封装在这个类内部,与其他类之间没有耦合关系。
- 低耦合示例:
interface Shape {
double calculateArea();
}
class Circle implements Shape {
private double radius;
public Circle(double radius) {
this.radius = radius;
}
@Override
public double calculateArea() {
return Math.PI * radius * radius;
}
}
class Rectangle implements Shape {
private double length;
private double width;
public Rectangle(double length, double width) {
this.length = length;
this.width = width;
}
@Override
public double calculateArea() {
return length * width;
}
}
class Main {
public static void main(String[] args) {
Shape circle = new Circle(5.0);
double circleArea = circle.calculateArea();
System.out.println("圆的面积:" + circleArea);
Shape rectangle = new Rectangle(3.0, 4.0);
double rectangleArea = rectangle.calculateArea();
System.out.println("矩形的面积:" + rectangleArea);
}
}
在上述代码中,定义了一个通用的 Shape 接口,表示图形的形状。Circle 和 Rectangle 类实现了这个接口,每个类只关注自己的具体实现,没有直接的耦合关系。在 Main 类中,可以灵活地使用不同的 Shape 实现类来计算对应形状的面积,实现了低耦合。
这只是示例代码,希望能够帮助您理解高内聚和低耦合的概念以及如何通过编写Java代码来展示它们的应用。在实际开发中,我们需要根据具体情况和需求来设计和实现高内聚低耦合的代码。