设计模式
一、设计模式七大原则
- 1、开闭原则
- 2、依赖倒置原则
- 3、单一职责原则
- 4、接口隔离原则
- 5、迪米特法则(最少知道原则)
- 6、里氏替换原则
- 7、合成/复用原则(组合/复用原则)
1.1 开闭原则
-
一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。
-
开闭原则的优点是:提高软件系统的可复用性以及可维护性
-
开闭原则的含义是说一个软件实体应该通过扩展来实现变化,而不是通过修改已有代码来实现变化。
用抽象构建框架,用实现扩展细节
软件实体包括以下几个部分:
- 项目或软件产品中按照一定的逻辑规则划分的模块
- 抽象和类
- 方法
开闭原则是为软件实体的未来事物而制定的对现行开发设计进行约束的一个原则。
注意:开闭原则对扩展开放,对修改关闭,并不意味着不做任何修改,低层模块的变更,必然要有高层模块进行耦合,否则就是一个孤立无意义的代码片段了。
// 创建接口 public interface Product { /** * 获取商品名称 * @return */ String getName(); /** * 获取商品价格 * @return */ double getPrice(); }
/** * 创建一个商品具体实现 **/ public class CocoProduct implements Product { private String ID; private String name ; private double price ; public CocoProduct(String ID, String name, double price) { this.ID = ID; this.name = name; this.price = price; } @Override public String getId() { return this.ID; } @Override public String getName() { return this.name; } @Override public double getPrice() { return this.price; } }
/** * 测试 **/ public class OpenClosePrincipleTest { public static void main(String[] args) { Product coco = new CocoProduct("123123","奶茶YYDS",18.8); System.out.println("商品ID:"+coco.getId()+"--商品名称:"+coco.getName()+"---商品价格:"+coco.getPrice()); // 商品ID:123123--商品名称:奶茶YYDS---商品价格:18.8 } }
在此基础上增加满15-3的优惠功能
错误示例:
对Product接口进行修改
// 创建接口 public interface Product { /** * 获取商品名称 * @return */ String getName(); /** * 获取商品价格 * @return */ double getPrice(); /** * 获取优惠价格 * @return */ double getDiscountedPrices(); }
public class CocoProduct implements Product { // 参考上面的具体实现,此处只增加获取优惠价格的具体实现 /** * 获取优惠满减之后的价格 * @return */ @Override public double getDiscountedPrices() { if(this.price>15){ return this.price-3; } return this.price; } }
在上面的示例中,我们对接口进行了修改,增加了一个获取优惠的方法,实现类也要实现想对应的方法,接口与实现类没有接耦合,如果照这个逻辑继续下去,后续我们要增加不同的优惠方法,还要去不停的修改最底层的接口与他的实现类。
正确的做法应该是:
// 创建接口 public interface Product { /** * 获取商品名称 * @return */ String getName(); /** * 获取商品价格 * @return */ double getPrice(); }
// 创建一个Product商品类新的实现,属于对接口的扩展,在这个新的实现中去实现获取满15-3的优惠功能 public class CocoDiscountedProduct implements Product { private String ID; private String name ; private double price ; public CocoDiscountedProduct(String ID, String name, double price) { this.ID = ID; this.name = name; this.price = price; } @Override public String getId() { return this.ID; } @Override public String getName() { return this.name; } @Override public double getPrice() { if(this.price>15){ return this.price-3; } return this.price; } }
/** * 测试 **/ public class OpenClosePrincipleTest { public static void main(String[] args) { Product coco = new CocoDiscountedProduct("123123","奶茶YYDS",18.8); System.out.println("商品ID:"+coco.getId()+"--商品名称:"+coco.getName()+"---商品价格:"+coco.getPrice()); // 商品ID:123123--商品名称:奶茶YYDS---商品价格:15.8 } } // 开闭原则,对扩展开放,对修改关闭。 // 通过去扩展接口的实现类,实现不同的优惠功能,而不是去修改底层接口的方法
1.2 依赖倒置原则
-
高层模块不应该依赖底层模块,两者都应该依赖其抽象;
-
抽象不应该依赖细节;
细节应该依赖抽象
-
依赖倒置原则在JAVA中的体现是:
- 模块间的依赖通过抽象发生,实现类之间不发生直接的依赖关系,其依赖关系是通过接口或者抽象类产生的;
- 接口或抽象类不依赖于实现类;
- 实现类依赖于接口或抽象类
- 依赖倒置原则实际上就是要求“面向接口编程”。
-
依赖倒置原则的优点:
- 减少类间的耦合性
- 提高系统的稳定性
- 降低并行开发引起的风险
- 提高代码的可读性和可维护性。
错误示例:
// 创建一个学生对象
public class Student {
public void studyEnglish(){
System.out.println("学生正在学习英语");
}
public void studyChinese(){
System.out.println("学生正在学习语文");
}
}
public class InversionTest {
public static void main(String[] args) {
Student student = new Student();
student.studyEnglish();
student.studyChinese();
}
}
// 如何需要添加学习新的课程就直接在Student类上面添加
// 但是这种方式违背了依赖倒置原则,因为高层模块InversionTest类需要调用方法的时候,却在底层模块Student来添加方法
正确示例:
/**
* 创建课程类接口
**/
public interface Course {
/**
* 学习课程
*
*/
void studyCourse();
}
// 语文课程实现
public class ChineseCourse implements Course {
@Override
public void studyCourse() {
System.out.println("学习语文课程");
}
}
// 英语课程实现
public class EnglishCourse implements Course{
@Override
public void studyCourse() {
System.out.println("学习英语课程");
}
}
// 学生类
public class StudyStudent {
public void studyMyCourse(Course course){
course.studyCourse();
}
}
// 测试类
public class InversionTest {
public static void main(String[] args) {
/* Student student = new Student();
student.studyEnglish();
student.studyChinese();*/
StudyStudent student = new StudyStudent();
student.studyMyCourse(new EnglishCourse());
student.studyMyCourse(new ChineseCourse());
}
}
在如上的例子的中,StudyStudent类不必关心他要学习什么课程,由高层类InversionTest负责注入该学生需要学习什么课程。底层的Course类负责中转
如果需要添加生物,化学,数学等一系列课程,那么也只要创建不同课程的实现类,在高层类InversionTest调用时注入相应的实现即可。
运行结果:
1.3 单一职责原则
-
就一个类,应该只有一个引起它变化的原因。
-
一个类,一个接口/方法只负责一项职责
-
单一职责的优点:
- 提高类的可维护性和可读写性
- 提高系统的可维护性
- 降低变更的风险
-
如何遵守单一职责
- 相同的职责放到一起,不同的职责分解到不同的接口和实现中去,这个是最容易也是最难运用的原则,关键还是要从业务出发,从需求出发,识别出同一种类型的职责。举个例子:人的行为分析,包括了生理、生活和工作等行为的分析,生理行为包括吃、跑、睡等行为,生活行为包括穿衣等行为,工作行为包括上下班,开会等行为
错误示例:
public class Bird {
public void moveModel(String name ){
System.out.println(name+"用翅膀飞。。。");
}
}
public class SingleTest {
public static void main(String[] args) {
Bird bird = new Bird();
bird.moveModel("大雁");
bird.moveModel("鸵鸟");
}
}
// 运行结果:
// 大雁用翅膀飞。。。
// 鸵鸟用翅膀飞。。。
// 而总所周知: 鸵鸟是用脚走的
正确示例:
// 创建飞行的鸟类
public class FlyBird {
public void moveModel(String name){
System.out.println(name+"用翅膀飞。。");
}
}
// 创建徒步的鸟类
public class WalkBird {
public void moveModel(String name){
System.out.println(name+"用脚走。");
}
}
// 测试类
public class SingleTest {
public static void main(String[] args) {
FlyBird flyBird= new FlyBird();
flyBird.moveModel("大雁");
WalkBird walkBird= new WalkBird();
walkBird.moveModel("鸵鸟");
}
}
// 运行接口
// 大雁用翅膀飞。。
// 鸵鸟用脚走。。
在上面的示例中,将飞行与步行的鸟分别抽取出一个实现类,让其实现对应的移动方法。一个类对应其相应的职责,接口,方法上也应是如此。
1.4 接口隔离原则
- 用多个专用的接口,而不使用单一的总接口,客户端不应该依赖他不需要的接口
- 一个类对一个类的依赖应该建立在最小的接口上
- 建立单一接口,不要建立庞大且臃肿的接口
- 尽量细化接口,接口中的方法尽量少
- 注意适度原则,一定要适度
- 接口隔离原则的优点:
- 符合高内聚耦合
- 具备良好的可读性,可扩展性和可维护性
public interface EatAnimalInterface {
/**
* 吃东西
*/
void eat();
}
public interface FlyAnimalInterface {
/**
* 飞行
*/
void fly();
}
public interface WalkAnimalInterface {
void walk();
}
public class Geese implements EatAnimalInterface,FlyAnimalInterface{
@Override
public void eat() {
System.out.println("我吃肉");
}
@Override
public void fly() {
System.out.println("我用翅膀飞");
}
}
public class Sheep implements EatAnimalInterface,WalkAnimalInterface{
@Override
public void eat() {
System.out.println("我吃草");
}
@Override
public void walk() {
System.out.println("我有四条腿走路。");
}
}
public class IsolationTest {
public static void main(String[] args) {
// 大雁
Geese geese = new Geese();
geese.eat();
geese.fly();
// 羊
Sheep sheep = new Sheep();
sheep.eat();
sheep.walk();
}
}
运行结果:
在如上示例的代码中,分别定义了三个接口:吃,飞,走 三个动物不同行为的接口,以及大雁与羊的具体实现类,其中大雁实现了吃与飞行的接口,羊实现了吃与行走的接口。
1.5 迪米特原则
- 一个对象应该对其他对象保持最少的了解。又称—> 最少知道原则
- 尽量降低类与类之前的耦合
- 迪米特原则的优点
- 降低耦合
错误代码示例:
public class MyCourse {
}
public class TeamLeader {
public void getCourseSize(List<MyCourse> courses){
System.out.println("课程的数量为:"+courses.size());
}
}
public class Boss {
public void commandCourseNumber(TeamLeader teamLeader){
List<MyCourse> courseList = new ArrayList<MyCourse>();
for(int i =0;i<20;i++){
courseList.add(new MyCourse());
}
teamLeader.getCourseSize(courseList);
}
}
public class DimiterTest {
public static void main(String[] args) {
Boss boss = new Boss();
TeamLeader teamLeader = new TeamLeader();
boss.commandCourseNumber(teamLeader);
}
}
在如上代码示例中,分别创建了课程类,负责人,以及老板,通过上述代码中可以发现,在老板 Boss的实现类,与MyCourse课程类并与直接的关系,老板此时也并不需要知道有哪些课程,只是想知道课程数量而已。课程应该是由负责人负责具体的添加修改。所以代码修改如下:
正确代码:
public class TeamLeader {
public void getCourseSize(){
List<MyCourse> courses = new ArrayList<>();
for(int i=0; i<20;i++){
courses.add(new MyCourse());
}
System.out.println("课程的数量是:"+courses.size());
}
}
public class Boss {
public void commandCourseNumber(TeamLeader teamLeader){
teamLeader.getCourseSize();
}
}
public class DimiterTest {
public static void main(String[] args) {
Boss boss = new Boss();
TeamLeader teamLeader = new TeamLeader();
boss.commandCourseNumber(teamLeader);
}
}
// 此时老板并不关系课程是怎么创建,有具体哪些课程,这些都有负责人去负责实现,而老板只需要知道他想要知道的东西
运行结果:
1.6 里氏替换原则
-
里氏替换原则是任何基类出现的地方,子类一定可以替换它;是建立在基于抽象、多态、继承的基础复用的基石,该原则能够保证系统具有良好的拓展性,同时实现基于多态的抽象机制,能够减少代码冗余。
-
里氏替换原则要求我们在编码时使用基类或接口去定义对象变量,使用时可以由具体实现对象进行赋值,实现变化的多样性,完成代码对修改的封闭,扩展的开放
-
例如: 商城商品结算中,定义结算接口SettlementInterface,该接口有三个具体实现类,分别为FullReductionSettlement (满减活动,两百以上打八折)、DiscountSettlement(打折活动)、 CashBackSettlement(返现活动);
-
代码实现:
// 定义一个结算接口
public interface SettlementInterface{
/**
* 获取优惠完之后的价格
* @param consumePrice
* @return realPrice
*/
double realPrice(double consumePrice);
}
// 满减优惠接口
public class FullReductionSettlement implements SettlementInterface{
@Override
public double realPrice(double consumePrice) {
if(consumePrice>200){
return consumePrice*0.8;
}
return consumePrice;
}
}
// 打折接口
public class DiscountSettlement implements SettlementInterface{
@Override
public double realPrice(double consumePrice) {
return consumePrice*0.8;
}
}
// 返现接口
public class CashBackSettlement implements SettlementInterface{
@Override
public double realPrice(double consumePrice) {
return consumePrice-100;
}
}
// 具体结算实现
public class Settlement {
private SettlementInterface settlementInterface;
public Settlement(SettlementInterface settlementInterface){
this.settlementInterface = settlementInterface;
}
public double getPrice(double consumePrice) {
double realPrice = this.settlementInterface.realPrice(consumePrice);
// 格式化保留小数点后1位,即:精确到角
BigDecimal bd = new BigDecimal(realPrice);
bd = bd.setScale(1, BigDecimal.ROUND_DOWN);
return bd.doubleValue();
}
}
// 测试:
public class RibReplacementTest {
public static void main(String[] args) {
// 使用打折接口计算价格
Settlement settlement = new Settlement(new DiscountSettlement());
// 使用满减接口
Settlement settlement1 = new Settlement(new FullReductionSettlement());
// 使用返现接口
Settlement settlement2 = new Settlement(new CashBackSettlement());
// 使用打折接口计算价格
System.out.println(settlement.getPrice(999.9));;
// 使用满减接口
System.out.println(settlement1.getPrice(999.9));;
// 使用返现接口
System.out.println(settlement2.getPrice(999.9));;
}
}
运行结果:
1.7 合成/复用原则
- 尽量使用对象组合、集合而不是继承关系达到软件复用的目的
- 聚合:has-a; 组合:contains-a
代码示例:
DB Connection 是一个非常经典的组合复用
public abstract class DBConnection {
public abstract String getConnection();
}
public class MysqlConnection extends DBConnection{
@Override
public String getConnection() {
return "获取Mysql数据连接";
}
}
public class OracleConnection extends DBConnection {
@Override
public String getConnection() {
return "获取Oracle数据连接";
}
}
public class ProductDto {
private DBConnection dbConnection;
public void setDbConnection(DBConnection dbConnection){
this.dbConnection = dbConnection;
}
public void addProduct(){
String conn = dbConnection.getConnection();
System.out.println("获得数据库连接");
}
}
public class CopTest {
public static void main(String[] args) {
ProductDto productDto = new ProductDto();
productDto.setDbConnection(new MysqlConnection());
productDto.addProduct();
}
}
- 合成复用原则的宗旨就是能不用继承就不用继承。因为继承的耦合度很高。
二、常见的设计模式(23种)
(一)创建型
2.1 工厂方法
- 工厂模式(Factory Pattern)是 Java 中最常用的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
- 工厂模式大体分为简单工厂、工厂方法、抽象工厂等三种模式。工厂方法模式也可称为工厂模式,与抽象模式都是属于GOF23种设计模式中的一员。可以大概理解为:简单工厂进阶变成了工厂方法,然后再进阶成了抽象工厂。难度逐步增加,也越来越抽象。
2.1.1 简单工厂
- 定义:由一个工厂对象绝对创建出哪一种产品类的实例
- 类型:创建型,但不属于 GOF23种设计模式
- 适用场景
- 工厂类负责创建的对象比较少
- 客户端(应用层)只知道传入工厂类的参数对于如何创建对象(逻辑)不关心
- 简单工厂模式包含的角色以及其职责
- 工厂角色[Creator]:简单工厂模式的核心,它负责实现创建所有实例的内部逻辑。工厂类的创建产品类的方法可以被外界直接调 用,创建所需的产品对象。
- 抽象产品角色[Product]:单工厂模式所创建的所有对象的父类,它负责描述所有实例所共有的公共接口
- 具体产品角色[Concrete Product]:简单工厂模式的创建目标,所有创建的对象都是充当这个角色的某个具体类的实现。
代码示例:
// 创建游戏接口
public interface Game {
/**
* 获取游戏名字
*/
void getGameName();
}
// 创建游戏具体实现类
// CF
public class CrossFire implements Game{
@Override
public void getGameName() {
System.out.println("穿越火线---this is CF FPS game");
}
}
// LOL
public class LolGame implements Game{
@Override
public void getGameName() {
System.out.println("this is 英雄联盟!");
}
}
// 简单工厂的实现
public class SimpleGameFactory {
public static Game getGame(String gameName){
if("CF".equals(gameName)){
return new CrossFire();
}else if ("LOL".equals(gameName)){
return new LolGame();
}
// 此处新增一个游戏,需要对代码相应的增加一层判断
return null ;
}
}
// 测试
public class SimpleFactoryTest {
public static void main(String[] args) {
Game crossFire = SimpleGameFactory.getGame("CF");
Game Lol = SimpleGameFactory.getGame("LOL");
}
}
- 优点:
- 只需要传入一个正确的参数,就可以获取你所需要的对象而无须知道其创建细节
- 缺点:
- 工厂类的职责相对过重,增加新的产品需要修改工厂类的判断逻辑,违背开闭原则
由简单工厂方法衍生出工厂方法
2.1.2 工厂方法
- 定义一个创建对象的接口但让实现这个接口的类来决定实例化哪个类工厂方法让类的实例化推迟到子类中进行;
- 使用场景:
- 创建对象需要大量重复的代码
- 客户端(应用层)不依赖于产品类实例如何被创建、实现等细节
- 一个类通过其子类来指定创建哪个对象
- 优点:
- 用户只需要关系所需产品对应的工厂,无须关心创建细节
- 加入新产品符合开闭原则,提高了可扩展性
- 缺点:
- 类的个数容易过多,增加复杂度
- 增加了系统的抽象性和理解难度
代码示例:
// 创建游戏工厂
public interface GameFactory {
/**
* 获取游戏
* return 游戏
*/
Game getGame();
}
// LOL游戏工厂方法
public class LolGameFactory implements GameFactory {
@Override
public Game getGame() {
return new LolGame();
}
}
// 穿越火线游戏工厂
public class CrossFireGameFactory implements GameFactory {
@Override
public Game getGame() {
return new CrossFire();
}
}
// 测试
public class FactoryMethodTest {
public static void main(String[] args) {
Game game1 = new LolGameFactory().getGame();
Game game2 = new CrossFireGameFactory().getGame();
// 后续需要增加游戏增加相应游戏,以及其工厂类
}
}
由工厂方法演变到抽象工厂
2.2 抽象工厂
-
抽象工厂模式(Abstract Factory Pattern)是围绕一个超级工厂创建其他工厂。该超级工厂又称为其他工厂的工厂。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
-
在抽象工厂模式中,接口是负责创建一个相关对象的工厂,不需要显式指定它们的类。每个生成的工厂都能按照工厂模式提供对象。
-
为了更清晰地理解抽象工厂模式,需要先引入两个概念:
-
产品等级结构
产品等级结构即产品的继承结构,如一个抽象类是电视机,其子类有三星电视机、海信电视机、小米电视机,则抽象电
视机与具体品牌的电视机之间构成了一个产品等级结构,抽象电视机是父类,而具体品牌的电视机是其子类。
-
产品族
在抽象工厂模式中,产品族是指由同一个工厂生产的,位于不同产品等级结构中的一组产品,如格力电器工厂生产的格力电视
机、格力电冰箱,格力电视机位于电视机产品等级结构中,格力电冰箱位于电冰箱产品等级结构中。
-
-
适用环境
- 客户端(应用层)不依赖于产品类实例如何被创建、实现等细节
- 强调一系列相关的产品对象(属于同一产品族)一起使用创建对象需要大量重复的代码
- 提供一个产品类的库,所有的产品以同样的接口出现,从而使客户端不依赖于具体实现
-
优点
- 具体产品在应用层代码隔离,无须关心创建细节
- 将一个系列的产品族统一到一起创建
-
缺点
- 规定了所有可能创建的产品集合,产品族中扩展新的产品困难,需要修改抽象工厂的接口。
代码示例:
业务场景:现在每个课程都不仅仅要有相关视频 ,还要有对应的笔记。也就是相当于在垂直和水平方向增加了业务需求。
// 课程对象
public interface CourseFactory {
/**
* 获取视频
* @return
*/
Video getVideo();
/**
* 获取笔记
* @return
*/
Note getNote();
}
// 视频抽象类
public abstract class Video {
/**
* 生成方法
*/
public abstract void produce();
}
// 笔记抽象类
public abstract class Note {
/**
* 生成方法
*/
public abstract void produce();
}
// 生成数学笔记
public class MathNote extends Note{
@Override
public void produce() {
System.out.println("生成数学笔记");
}
}
// 生成数学视频
public class MathVideo extends Video {
@Override
public void produce() {
System.out.println("生成数学视频");
}
}
// 数学课程工厂方法
public class MathCourseFactory implements CourseFactory{
@Override
public Video getVideo() {
return new MathVideo();
}
@Override
public Note getNote() {
return new MathNote();
}
}
public class EnglishNote extends Note{
@Override
public void produce() {
System.out.println("生成英语笔记");
}
}
public class EnglishVideo extends Video{
@Override
public void produce() {
System.out.println("生成英语视频");
}
}
// 英语课程工厂
public class EnglishCourseFactory implements CourseFactory{
@Override
public Video getVideo() {
return new EnglishVideo();
}
@Override
public Note getNote() {
return new EnglishNote();
}
}
// 测试
public class AbstractFactoryTest {
public static void main(String[] args) {
CourseFactory englishesCourse = new EnglishCourseFactory();
englishesCourse.getNote().produce();
englishesCourse.getVideo().produce();
CourseFactory mathCourseFactory = new MathCourseFactory();
mathCourseFactory.getNote().produce();
mathCourseFactory.getVideo().produce();
}
}
查看UML图
2.3 原型模式
- 用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象
- 适合场景
- 类初始化消耗较多资源
- new产生的一个对象需要非常繁琐的过程(数据准备、访问权限等)
- 构造函数比较复杂
- 循环体中产生大量对象时
- 优点:
- 原型模式性能比直接new一个对象性能高
- 简化创建过程
- 缺点:
- 必须配备克隆方法
- 对克隆复杂对象或对克隆出的对象进行复杂改造时,容易引入风险。
- 原型模式的实例的拷贝包括浅复制和深复制:
- 浅拷贝:
- 将一个对象复制后,其基本数据类型的变量都会重新创建,而引用类型的变量指向的还是原对象所指向的,也就是指向的内存堆地址没变。
- 深复制:
- 将一个对象复制后,不论是基本数据类型还是引用类型,都是重新创建的。
- 浅拷贝:
示例代码:
浅拷贝
//创建一个对象
public class Message implements Cloneable , Serializable {
//消息名称
private String name;
//消息大小
private String size;
//消息类型
private int type;
//创建日期
private Date date;
public static final int TEXT = 0x01;
public static final int PIC = 0x02;
public static final int VIDEO = 0x03;
public static final int MIX = 0x04;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSize() {
return size;
}
public void setSize(String size) {
this.size = size;
}
public int getType() {
return type;
}
public void setType(int type) {
this.type = type;
}
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
/**
* 浅拷贝
JDK实现的默认浅拷贝实现
* @return
*/
@Override
public Message clone() {
try {
return (Message) super.clone();
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
/**
* 自定义的深拷贝实现
*/
public Message deepClone() throws CloneNotSupportedException,
IOException, ClassNotFoundException {
//把对象写入到字节流中
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(this);
//把字节流转化为对象
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
return (Message) ois.readObject();
}
@Override
public String toString() {
String tos = "name[" + name + "],size[" + size +
"],type[" + type + "],date[" + date + "]";
return tos;
}
}
// 测试方法:
public class ProtoTypeTest {
public static void main(String[] args) {
Message msg = new Message();
msg.setName("好友消息");
msg.setSize("123KB");
msg.setType(Message.TEXT);
msg.setDate(new Date());
System.out.println("msg:" + msg.toString());
try {
Message cloneMsg = msg.clone();
System.out.println("msg:" + msg.toString());
System.out.println("cloneMsg:" + cloneMsg.toString());
System.out.println(cloneMsg.getDate() == msg.getDate());
System.out.println(cloneMsg.getName() == msg.getName());
System.out.println(cloneMsg.getType() == msg.getType());
} catch (Exception e) {
e.printStackTrace();
}
}
}
运行结果:
在上图的代码示例中,通过调用实现Cloneable接口,java默认的实现方式是浅复制,而非深复制,可以看到Message对象中所有的引用类型 的地址指向的仍然是原来变量的地址
深拷贝
public class ProtoTypeTest {
public static void main(String[] args) {
Message msg = new Message();
msg.setName("好友消息");
msg.setSize("123KB");
msg.setType(Message.TEXT);
msg.setDate(new Date());
System.out.println("msg:" + msg.toString());
try {
//Message cloneMsg = msg.clone();
Message cloneMsg = msg.deepClone();
System.out.println("msg:" + msg.toString());
System.out.println("cloneMsg:" + cloneMsg.toString());
System.out.println(cloneMsg.getDate() == msg.getDate());
System.out.println(cloneMsg.getName() == msg.getName());
System.out.println(cloneMsg.getType() == msg.getType());
} catch (Exception e) {
e.printStackTrace();
}
}
}
运行结果:
JDK中原型模式的实现
2.4 单例模式
- 保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例。
- 适合场景:
- 想确保任何情况下都绝对只有一个实例
- 需要频繁的进行创建和销毁的对象;
- 创建对象时耗时过多或耗费资源过多,但又经常用到的对象;
- 工具类对象;
- 频繁访问数据库或文件的对象。
- 在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。这些应用都或多或少具有资源管理器的功能。每台计算机可以有若干个打印机,但只能有一个Printer Spooler,以避免两个打印作业同时输出到打印机中。每台计算机可以有若干通信端口,系统应当集中管理这些通信端口,以避免一个通信端口同时被两个请求同时调用。总之,选择单例模式就是为了避免不一致状态,避免政出多头。
- 单例模式的特点:
- 单例类只能有一个实例
- 单例类必须自己创建自己的唯一实例。
- 单例类必须给所有其他对象提供这一实例。
- 保证了全局对象的唯一性,比如系统启动读取配置文件就需要单例保证配置的一致性。
- 优点:
- 在内存中只有一个实例,减少了内存开销
- 可以避免对资源的多重占用
- 设置全局访问点,严格控制访问
- 缺点:
- 没有接口,扩展困难
- 重点:
- 私有构造器
- 线程安全
- 延迟加载
- 序列化和反序列化安全
- 反射
- 实现单例模式的八种方式
1.饿汉式【静态常量】
-
优点:
这种写法比较简单,就是在类装载的时候就完成实例化。避免了线程同步问题。
-
缺点:
在类装载的时候就完成实例化,没有达到Lazy Loading的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费。
/**
* @author vvirster@163.com
* @date 2022/5/1 13:23
* 静态常量
**/
public class StaticConstantSingle {
private static StaticConstantSingle INSTANCE = new StaticConstantSingle();
private StaticConstantSingle(){}
public static StaticConstantSingle getInstance(){
return INSTANCE;
}
}
2.饿汉式【静态代码块】
- 与上面的静态常量类型,优缺点也是一样。
/**
* @author vvirster@163.com
* @date 2022/5/1 13:27
* 静态代码块实现单例模式
**/
public class StaticCodeSingle {
private static StaticCodeSingle INSTANCE ;
static{
INSTANCE = new StaticCodeSingle();
}
private StaticCodeSingle(){
}
public static StaticCodeSingle getInstance(){
return INSTANCE;
}
}
3.懒汉式【线程不安全】
这种写法起到了懒加载的效果,但是只能在单线程下使用。如果在多线程下,一个线程进入了if (INSTANCE== null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。所以在多线程环境下不可使用这种方式。
/**
* @author vvirster@163.com
* @date 2022/5/1 13:31
* 懒汉式-【线程不安全】
**/
public class LazyUnsafeSingle {
private static LazyUnsafeSingle INSTANCE ;
private LazyUnsafeSingle(){
}
public static LazyUnsafeSingle getInstance(){
if(INSTANCE == null){
INSTANCE = new LazyUnsafeSingle();
}
return INSTANCE;
}
}
4.懒汉式【线程安全-同步方法】
为解决上面第三种单例模式创建方式 在多线程环境下,单例模式失效的问题,我们可以在方法中加入synchronized关键字设置为同步方法
- 优点:
多线程状态下是线程安全的
-
缺点:
由于是在static方法中加入的synchronized关键字,每次多线程进入的时候是锁住整个对象,效率太低下
/**
* @author vvirster@163.com
* @date 2022/5/1 13:38
* 懒汉式-线程安全 使用同步方法
**/
public class LazySafeSynMethodSingle {
private static LazySafeSynMethodSingle INSTANCE ;
private LazySafeSynMethodSingle(){
}
public synchronized static LazySafeSynMethodSingle getInstance(){
if(INSTANCE == null){
INSTANCE = new LazySafeSynMethodSingle();
}
return INSTANCE;
}
}
5.懒汉式【线程不安全-同步代码块】
由于第四种实现方式同步效率太低,所以摒弃同步方法,改为同步产生实例化的的代码块。但是这种同步并不能起到线程同步的作用。跟第3种实现方式遇到的情形一致,假如一个线程进入了if (INSTANCE== null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。
/**
* @author vvirster@163.com
* @date 2022/5/1 13:38
* 懒汉式-线程安全 使用同步代码块
* **/
public class LazySafeSynCodeBlockSingle {
private static LazySafeSynCodeBlockSingle INSTANCE ;
private LazySafeSynCodeBlockSingle(){
}
public synchronized static LazySafeSynCodeBlockSingle getInstance(){
if(INSTANCE == null){
synchronized(LazySafeSynCodeBlockSingle.class){
INSTANCE = new LazySafeSynCodeBlockSingle();
}
}
return INSTANCE;
}
}
6.懒汉式【线程安全-双重检查-推荐使用】
如代码中所示,我们进行了两次if (INSTANCE== null)检查,这样就可以保证线程安全了。这样,实例化代码只用执行一次,后面再次访问时,判断if(INSTANCE== null),直接return实例化对象。
/**
* @author vvirster@163.com
* @date 2022/5/1 13:55
* 懒汉式-双重检查 线程安全
**/
public class LazyDoubleCheckSingle {
private static LazyDoubleCheckSingle INSTANCE ;
private LazyDoubleCheckSingle(){
}
public synchronized static LazyDoubleCheckSingle getInstance(){
if(INSTANCE == null){
synchronized(LazyDoubleCheckSingle.class){
if(INSTANCE == null){
INSTANCE = new LazyDoubleCheckSingle();
}
}
}
return INSTANCE;
}
}
7.静态内部类【推荐使用】
这种方式跟饿汉式方式采用的机制类似,但又有不同。两者都是采用了类装载的机制来保证初始化实例时只有一个线程。不同的地方在饿汉式方式是只要Singleton类被装载就会实例化,没有Lazy-Loading的作用,而静态内部类方式在Singleton类被装载时并不会立即实例化,而是在需要实例化时,调用getInstance方法,才会装载StaticInnerClassSingleInstance类,从而完成StaticInnerClassSingle的实例化。
类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。
/**
* @author vvirster@163.com
* @date 2022/5/1 13:59
* 静态内部类
**/
public class StaticInnerClassSingle {
private StaticInnerClassSingle(){
}
public static class StaticInnerClassSingleInstance{
private static final StaticInnerClassSingle INSTANCE = new StaticInnerClassSingle();
}
public static StaticInnerClassSingle getInstance(){
return StaticInnerClassSingleInstance.INSTANCE;
}
}
8.枚举
借助JDK1.5中添加的枚举来实现单例模式。不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。
/**
* @author vvirster@163.com
* @date 2022/5/1 14:03
* 枚举实现单例模式
**/
public enum EnumSingle {
EARTH("地球");
private final String NAME;
EnumSingle(String NAME){
this.NAME = NAME;
}
public String getNAME() {
return NAME;
}
@Override
public String toString() {
return "Single{" +
"NAME='" + NAME + '\'' +
'}';
}
}
2.5 建造者模式
- 将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示,用户只需指定需要建造的类型就可以得到它们,建造过程及细节不需要知道
- 适用场景:
- 如果一个对象有非常复杂的内部结构(很多属性)
- 想把复杂对象的创建和使用分离
- 优点:
- 产品的建造和表示分离,实现了解耦,可以使用相同的创建过程得到不同的产品。
- 将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰。(良好的封装性,不必知道内部组成的细节)
- 增加新的具体建造者无需修改原有类库的代码,易于拓展,符合“开闭原则“。
- 缺点
- 产生多余的Builder对象
- 产品内部发送变化,建造者都要修改,成本较大
代码示例:
// 创建用户对象
/**
* @author vvirster@163.com
* @date 2022/5/1 16:04
**/
public class User {
String UId;
String name;
String password ;
String phone;
public String getUId() {
return UId;
}
public void setUId(String UId) {
this.UId = UId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
@Override
public String toString() {
return "User{" +
"UId='" + UId + '\'' +
", name='" + name + '\'' +
", password='" + password + '\'' +
", phone='" + phone + '\'' +
'}';
}
}
// 抽象构造器
/**
* @author vvirster@163.com
* @date 2022/5/1 16:06
**/
public abstract class UserBuilder {
/**
* 用户ID
* @param UId
*/
public abstract UserBuilder buildUserUId(String UId);
/**
* 用户名
* @param name
*/
public abstract UserBuilder buildUserName(String name);
/**
* 密码
* @param password
*/
public abstract UserBuilder buildUserPassword(String password);
/**
* 手机号
* @param phone
*/
public abstract UserBuilder buildUserPhone(String phone);
/**
* 构建用户
* @return
*/
public abstract User buildUser();
}
// 管理员用户构造器
/**
* @author vvirster@163.com
* @date 2022/5/1 16:09
**/
public class AdminUserBuilder extends UserBuilder {
User user = new User();
@Override
public UserBuilder buildUserUId(String UId) {
user.setUId(UId);
return this;
}
@Override
public UserBuilder buildUserName(String name) {
user.setName(name);
return this;
}
@Override
public UserBuilder buildUserPassword(String password) {
user.setPassword(password);
return this;
}
@Override
public UserBuilder buildUserPhone(String phone) {
user.setPhone(phone);
return this;
}
@Override
public User buildUser() {
return user;
}
}
// 测试
/**
* @author vvirster@163.com
* @date 2022/5/1 16:12
**/
public class UserBuilderTest {
public static void main(String[] args) {
UserBuilder userBuilder = new AdminUserBuilder();
User user = userBuilder.buildUserUId("UID124214").buildUserName("张三").
buildUserPassword("fsafffs").buildUserPhone("12324214214").buildUser();
System.out.println(user.toString());
}
}
运行结果:
(二)结构型
2.6 代理模式
- 为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。
- 适用场景:
- 保护目标对象
- 增强目标对象
- 优点:
- 代理模式能将代理对象与真实被调用的目标对象分离,一定程度上降低了系统的耦合度
- 保护目标对象,增强目标对象
- 缺点
- 代理模式会造成系统设计中类的数目增加
- 在客户端和目标对象增加一个代理对象,会造成请求处理速度变慢
- 增加系统的复杂度
代理模式可分为静态代理跟动态代理两种
- 静态代理
- 由程序员创建或工具生成代理类的源码,再编译代理类。所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。
- 动态代理
- 在实现阶段不用关心代理类,而在运行阶段才指定哪一个对象
- 常见的动态代理中有JDK的动态代理以及CGlib代理
- spring中的代理扩展
- 当Bean有实现接口时,Spring就会用JDK的动态代理
- 当Bean没有实现接口时,Spring使用CGlib
- spring中的代理扩展
(1)静态代理
代码示例:
创建接口以及其实现类、代理类、测试类
/**
* @author vvirster@163.com
* @date 2022/5/2 0:29
**/
public interface BuyHouse {
/**
* 买房
* @param money
*/
public void buyHouse(Integer money);
}
/**
* @author vvirster@163.com
* @date 2022/5/2 0:29
**/
public class BuyHouseImpl implements BuyHouse{
@Override
public void buyHouse(Integer money) {
System.out.println("buy house with "+ money);
}
}
/**
* @author vvirster@163.com
* @date 2022/5/2 0:30
**/
public class StaticBuyHouseProxy {
private BuyHouse buyHouse;
public BuyHouseProxy(BuyHouse buyHouse) {
this.buyHouse = buyHouse;
}
public void buyHouse(Integer money){
System.out.println("买房前手续");
//买房付钱
buyHouse.buyHouse( money);
System.out.println("买房后过户");
}
}
/**
* @author vvirster@163.com
* @date 2022/5/2 0:30
**/
public class StaticProxyTest {
public static void main(String[] args) {
System.out.println("被代理前");
BuyHouse buyHouse = new BuyHouseImpl();
buyHouse.buyHouse(1000);
System.out.println("--------");
System.out.println("代理后");
StaticBuyHouseProxy buyHouseProxy = new StaticBuyHouseProxy(buyHouse);
buyHouseProxy.buyHouse(1000);
}
}
从以上的代码示例中可以看出,使用静态代理BuyHouseProxy是对BuyHouseImpl的一个增强,原先买房是拿着钱去,但是不知道买房前需要准备什么买房后需要干嘛,通过代理则是将这些步骤都进行了一个增强优化。
(2)动态代理
2.1 JDK动态代理
在静态代理的接口、实现类基础上创建动态代理处理器、测试类
/**
* @author vvirster@163.com
* @date 2022/5/2 0:51
**/
public class DynamicBuyHouseProxyProxyHandler implements InvocationHandler {
private Object target;
public DynamicBuyHouseProxyProxyHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("买房前准备"+"通过JDK动态代理调用:"+method.getName());
Object result = method.invoke(target,args);
System.out.println("买房后准备"+"通过JDK动态代理调用:"+method.getName());
return result;
}
}
/**
* @author vvirster@163.com
* @date 2022/5/2 0:56
**/
public class DynamicProxyTest {
public static void main(String[] args) {
BuyHouse buyHouse = new BuyHouseImpl();
BuyHouse proxyBuyHouse = (BuyHouse) Proxy.newProxyInstance(BuyHouse.class.getClassLoader(),
new Class[]{BuyHouse.class}, new DynamicBuyHouseProxyProxyHandler(buyHouse));
proxyBuyHouse.buyHouse(1000);
}
}
运行结果:
2.2 Cglib动态代理
Cglib动态代理需要添加第三方依赖,在maven项目中加入以下下来
<dependencies>
<!-- https://mvnrepository.com/artifact/cglib/cglib -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
</dependencies>
其他构建模式下的项目同理,在项目的依赖环境中加入Cglib的jar包
代码示例:
在静态代理的接口、实现类基础上创建Cglib动态代理类、测试类
/**
* @author vvirster@163.com
* @date 2022/5/2 1:11
**/
public class CglibBuyHouseProxy implements MethodInterceptor {
private Object target;
public Object getInstance (final Object object) {
this.target = object;
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(this.target.getClass());
enhancer.setCallback(this);
return enhancer.create();
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("买房前准备");
Object result = method.invoke(o,objects);
System.out.println("买房后准备");
return result;
}
}
这是由于 JDK 8 中有关反射相关的功能自从 JDK 9 开始就已经被限制了,为了兼容原先的版本,需要在运行项目时添加 --add-opens =java.base/java.lang=ALL-UNNAMED 选项来开启这种默认不被允许的行为。
PS:可能是JDK版本原因导致,此处运行会一直报错,暂未运行成功,也暂时未找到解决办法。
2.7 适配器模式
-
适配器模式是作为两个不兼容的接口之间的桥梁。
-
将一个类的接口转换成客户期望的另一个接口,让原本接口不兼容的类可以一起工作
-
例如:USB网线转换器:一端连接电脑,另一端连接网线
-
适用场景:
- 系统需要使用一些现有的类,而这些类的接口(如方法名)不符合系统的需要,甚至没有这些类的源
代码。 - 想创建一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引
进的类一起工作。
- 系统需要使用一些现有的类,而这些类的接口(如方法名)不符合系统的需要,甚至没有这些类的源
-
优点:
- 一个对象适配器可以把多个不同的适配者适配到同一个目标
- 可以适配一个适配者的子类,由于适配器和适配者之间是关联关系,根据
“里氏代换原则”
,适配者
的子类也可通过该适配器进行适配。 - 能提高类的透明性和复用,现有的类复用但不需要改变
- 目标类和适配器类解耦,提高程序扩展性
- 符合开闭原则
-
缺点:
- 对于Java、C#等不支持多重类继承的语言,一次最多只能适配一个适配者类,不能同时适配多个适配者;
- 在Java、C#等语言中,类适配器模式中的目标抽象类只能为接口,不能为类,其使用有一定的局限性。
- 适配器编写过程需要全面考虑,可能会增加系统的复杂性
- 增加系统代码可读的难度
-
适配器模式中涉及到的角色
Client
:用户类,使用新接口Target来完成某些特定的需求。Target
:新的接口类,开放特定接口request来完成某些特定操作,与Client协作。Adaptee
:原有的类,即需要适配的类或被适配者类。Adapter
:适配器类,将Adaptee中的接口封装成Target中的新接口,来满足新的需求。
-
适配器可分为两种模式:
- 对象适配器
- 类适配器
-
适配器模式常见的应用
- MyBatis中的日志适配,比如其中的日志模块,其中适配了
slf4j
、Apache Commons Logging
、Log4j2
和JDK logging
等的日志类型
- MyBatis中的日志适配,比如其中的日志模块,其中适配了
-
代码示例;
类适配器:
主要是通过继承适配者类并且实现目标接口
/**
* @author vvirster@163.com
* @date 2022/5/6 14:42
* 适配者类
**/
public class Adaptor {
public void specificRequest(){
System.out.println("我是适配者类");
}
}
/**
* @author vvirster@163.com
* @date 2022/5/6 14:42
* 目标接口
**/
public interface Target {
public void request();
}
/**
* @author vvirster@163.com
* @date 2022/5/6 14:43
* 适配器类
**/
public class Adapter extends Adaptor implements Target{
@Override
public void request() {
specificRequest();
}
}
/**
* @author vvirster@163.com
* @date 2022/5/6 14:43
* 客户端
**/
public class Client {
public static void main(String[] args) {
Target target = new Adapter();
target.request();
}
}
运行结果:
对象适配器:
创建ObjectAdapter类实现Target接口,和上面的adapter唯一的区别在于它不是通过继承来调用方法,而是在内部new一个对象,调用对象的方法
/**
* @author vvirster@163.com
* @date 2022/5/6 14:51
* 适配者类
**/
public class Adaptor {
public void specificRequest(){
System.out.println("我是适配者类");
}
}
/**
* @author vvirster@163.com
* @date 2022/5/6 14:42
* 目标接口
**/
public interface Target {
public void request();
}
/**
* @author vvirster@163.com
* @date 2022/5/6 14:53
* //对象适配器类
**/
public class ObjectAdapter implements Target{
private Adaptor adaptee;
public ObjectAdapter(Adaptor adaptee) {
this.adaptee = adaptee;
}
@Override
public void request() {
adaptee.specificRequest();
}
}
/**
* @author vvirster@163.com
* @date 2022/5/6 14:53
* 客户端类
**/
public class Client {
public static void main(String[] args) {
Adaptor adaptee = new Adaptor();
Target target = new ObjectAdapter(adaptee);
target.request();
}
}
运行结果:
2.8 桥接模式
-
定义:
- 将抽象部分与他的具体实现部分分离,使它们都可以独立地变化
- 通过组合的方式建立两个类之间联系,而不是继承
-
适用场景:
- 抽象和具体实现之间增加更多的灵活性
- 一个类存在两个(或多个)独立变化的维度,且这两个(或多个)维度都需要独立进行扩展
- 不希望使用继承,或因为多层继承导致系统类的个数剧增
-
优点:
- 分离抽象部分及其具体实现部分
- 提高了系统的可扩展性
- 符合开闭原则
- 符合合成复用原则
-
缺点:
- 增加了系统的理解与设计难度
- 需要正确地识别出系统中两个独立变化的维度
-
代码示例:
模拟电子产品销售场景
不使用桥接模式的情况:
/** * @author vvirster@163.com * @date 2022/5/6 15:59 * 电脑接口 **/ public interface Computer { /** * 销售 */ void sale(); }
/** * @author vvirster@163.com * @date 2022/5/6 16:00 * 台式机 **/ public class Desktop implements Computer { @Override public void sale() { System.out.println("销售台式机"); } }
/** * @author vvirster@163.com * @date 2022/5/6 16:00 * 笔记本 **/ public class Laptop implements Computer { @Override public void sale() { System.out.println("销售笔记本"); } }
/** * @author vvirster@163.com * @date 2022/5/6 16:00 **/ public class Pad implements Computer{ @Override public void sale() { System.out.println("销售平板"); } }
/** * @author vvirster@163.com * @date 2022/5/6 16:00 **/ public class LenovoDesktop extends Desktop{ @Override public void sale() { System.out.println("销售联想台式机"); } }
/** * @author vvirster@163.com * @date 2022/5/6 16:00 **/ public class LenovoLaptop extends Laptop{ @Override public void sale() { System.out.println("销售联想笔记本"); } }
/** * @author vvirster@163.com * @date 2022/5/6 16:01 **/ public class LenovoPad extends Pad{ @Override public void sale() { System.out.println("销售联想平板"); } }
/** * @author vvirster@163.com * @date 2022/5/6 16:05 **/ public class ShenzhouDesktop extends Desktop{ @Override public void sale() { System.out.println("销售神舟台式机"); } }
/** * @author vvirster@163.com * @date 2022/5/6 16:05 **/ public class ShenzhouLaptop extends Laptop{ @Override public void sale() { System.out.println("销售神舟笔记本"); } }
/** * @author vvirster@163.com * @date 2022/5/6 16:05 **/ public class ShenzhouPad extends Pad{ @Override public void sale() { System.out.println("销售神舟平板"); } }
/** * @author vvirster@163.com * @date 2022/5/6 16:16 **/ public class SaleClient { public static void main(String[] args) { //Shenzhou Computer comp1 = new ShenzhouDesktop(); comp1.sale(); Computer comp2 = new ShenzhouLaptop(); comp2.sale(); Computer comp3 = new ShenzhouPad(); comp3.sale(); //lenovo Computer comp4 = new LenovoDesktop(); comp4.sale(); Computer comp5 = new LenovoLaptop(); comp5.sale(); Computer comp6 = new LenovoPad(); comp6.sale(); } }
运行结果:
从上述代码中可看出: 如果增加新的电脑类型pad,需要创建Pad类实现电脑接口,接着每个电脑品牌继承它,重写销售相应的方法(被修改的地方众多,类爆炸)。
IDEAUML图
使用桥接模式:
使用桥接模式,将各个品牌不同类型的电脑拆分成两个维度, 品牌—电脑
/** * @author vvirster@163.com * @date 2022/5/6 16:22 **/ public interface Brand { void sale(); }
/** * @author vvirster@163.com * @date 2022/5/6 16:22 **/ public class Dell implements Brand{ @Override public void sale() { System.out.print("销售戴尔"); } }
/** * @author vvirster@163.com * @date 2022/5/6 16:22 **/ public class Lenovo implements Brand{ @Override public void sale() { System.out.print("销售联想"); } }
/** * @author vvirster@163.com * @date 2022/5/6 16:23 **/ public class Shenzhou implements Brand{ @Override public void sale() { System.out.print("销售神舟"); } }
/** * @author vvirster@163.com * @date 2022/5/6 15:59 * 电脑类型维度 **/ public abstract class Computer { Brand brand = null; public Computer(Brand brand) { this.brand = brand; } public void sale() { brand.sale(); } }
/** * @author vvirster@163.com * @date 2022/5/6 16:00 * 台式机 **/ public class Desktop extends Computer { public Desktop(Brand brand) { super(brand); } @Override public void sale() { super.sale(); System.out.println("台式机"); } }
/** * @author vvirster@163.com * @date 2022/5/6 16:00 * 笔记本 **/ public class Laptop extends Computer { public Laptop(Brand brand) { super(brand); } @Override public void sale() { super.sale(); System.out.println("笔记本电脑"); } }
/** * @author vvirster@163.com * @date 2022/5/6 16:25 **/ public class Pad extends Computer{ public Pad(Brand brand) { super(brand); } @Override public void sale() { super.sale(); System.out.println("平板"); } }
/** * @author vvirster@163.com * @date 2022/5/6 16:26 **/ public class UserBridgeTest { public static void main(String[] args) { //销售联想笔记本电脑 Brand brand = new Lenovo(); Computer computer = new Laptop(brand); computer.sale(); //销售戴尔平板电脑 Computer computer2 = new Pad(new Dell()); computer2.sale(); //销售神舟台式机 Computer computer3 = new Desktop(new Shenzhou()); computer3.sale(); } }
运行结果:
注:以上的案例可以看出桥接模式是为了解决Java中的多继承问题,C++中有多继承,而Java中没有多继承这种方式,所以需要使用多层继承结构。
-
桥接模式实际中的使用场景:
- JDBC驱动程序
- OA系统中的消息处理:
- 业务类型:普通消息、加急消息、特急消息
- 发送消息方式:系统内消息、手机短信、邮件
2.9 装饰者模式
-
定义
动态地给一个对象添加一些额外的职责,就增加功能来说,装饰模式比生成子类更加灵活。
-
适用场景
扩展一个类的功能或给一个类添加附加职责,动态的给一个对象添加功能,这些功能可以再动态的撤销
-
优点
-
继承的有力补充,比继承灵活,不改变原有对象的情况下给一个对象扩展功能
-
通过使用不同装饰类以及这些装饰类的排列组合,可以实现不同效果
-
符合开闭原则
-
-
缺点:
会出现更多的代码,更多的类,增加程序的复杂性
动态装饰时,多层装饰时会更复杂
-
角色
- Component:可以是接口或者是抽象类,被装饰的最原始的对象。
- ConcreteComponent:组件具体实现类。Component的具体实现,被装饰的具体对象【即包含原有功能的对象】。
- Decorator:抽象装饰者,新增装饰类,用来扩展原有Component类的功能,对于Component来说无须知道Decorator的存在,所以在它的属性中必然有一个private变量指向Component抽象组件。
- ConcreteDecorator:装饰者的具体实现类。
-
代码示例:
一个普通的人通过不同教练学运动技能【新学会了游泳 + 拳击】的例子来说明。
// 定义抽象类 被装饰的原始对象 public abstract class Person { /** * 运动方法 */ public abstract void sport(); }
//抽象组件具体实现 /** * @author vvirster@163.com * @date 2022/5/25 10:53 **/ public class CommonPerson extends Person{ @Override public void sport() { System.out.println("普通人运动"); } }
//抽象装饰者 /** * @author vvirster@163.com * @date 2022/5/25 10:54 **/ public abstract class Coach extends Person{ private final Person mPerson; public Coach(Person person) { mPerson = person; } @Override public void sport() { mPerson.sport(); } }
// 装饰者的具体实现类。 /** * @author vvirster@163.com * @date 2022/5/25 10:55 **/ public class SwimmingCoach extends Coach { public SwimmingCoach(Person person) { super(person); } @Override public void sport() { super.sport(); teachSwimming(); } public void teachSwimming(){ System.out.println("游泳教练教会普通人游泳!"); } }
/** * @author vvirster@163.com * @date 2022/5/25 10:59 **/ public class BoxingCoach extends Coach{ public BoxingCoach(Person person) { super(person); } @Override public void sport() { super.sport(); teachBoxing(); } public void teachBoxing(){ System.out.println("拳击教练教会普通人拳击!"); } }
/** * @author vvirster@163.com * @date 2022/5/25 11:00 **/ public class DecoratorTest { public static void main(String[] args) { CommonPerson person = new CommonPerson(); person.sport(); BoxingCoach boxingCoach = new BoxingCoach(person); boxingCoach.sport(); SwimmingCoach swimmingCoach = new SwimmingCoach(person); swimmingCoach.sport(); } }
2.10 外观模式
-
定义
又叫门面模式,提供了一个统一的接口,用来访问子系统中的一群接口
外观模式定义了一个高层接口,让子系统更容易使用
-
适用场景:
- 子系统越来越复杂,增加外观模式提供简单调用接口
- 构建多层系统结构,利用外观对象作为每层的入口,简化层间调用
-
优点:
- 简化了调用过程,无需了解深入子系统,防止带来风险。
- 减少系统依赖、松散耦合
- 更好的划分访问层次
- 符合迪米特法则,即最少知道原则
-
缺点:
- 增加子系统、扩展子系统行为容易引入风险
- 不符合开闭原则
-
代码示例:
/** * @author vvirster@163.com * @date 2022/5/25 17:05 **/ public interface Shape { void draw(); }
/** * @author vvirster@163.com * @date 2022/5/25 17:07 **/ public class Circle implements Shape{ @Override public void draw() { System.out.println("Circle::draw()"); } }
/** * @author vvirster@163.com * @date 2022/5/25 17:06 **/ public class Rectangle implements Shape{ @Override public void draw() { System.out.println("Rectangle::draw()"); } }
/** * @author vvirster@163.com * @date 2022/5/25 17:07 **/ public class Square implements Shape{ @Override public void draw() { System.out.println("Square::draw()"); } }
----创建外观类 /** * @author vvirster@163.com * @date 2022/5/25 17:07 * 外观类 **/ public class ShapeMaker { private Shape circle; private Shape rectangle; private Shape square; public ShapeMaker() { circle = new Circle(); rectangle = new Rectangle(); square = new Square(); } public void drawCircle(){ circle.draw(); } public void drawRectangle(){ rectangle.draw(); } public void drawSquare(){ square.draw(); } }
/** * @author vvirster@163.com * @date 2022/5/25 17:09 **/ public class AppearanceTest { public static void main(String[] args) { ShapeMaker shapeMaker = new ShapeMaker(); shapeMaker.drawCircle(); shapeMaker.drawRectangle(); shapeMaker.drawSquare(); } }
运行结果:
2.11 享元模式
-
定义:
提供了减少对象数量从而改善应用所需的对象结构的方式,运用共享技术有效地支持大量细粒度的对象
-
抽象的享元角色, 他是产品的抽象类, 同时定义出对象的外部状态和内部状态的接口或实现 。
-
具体的享元角色,是具体的产品类,实现抽象角色定义相关业务。
-
内部状态跟外部状态:
享元模式提出了两个要求:细粒度和共享对象。这里就涉及到内部状态和外部状态 了,即将对象的信息分为两个部分:内部状态和外部状态 。
- 内部状态指对象共享出来的信息,存储在享元对象内部且不会随环境的改变而改变 。
- 外部状态指对象得以依赖的一个标记,是随环境改变而改变的、不可共享的状态。
-
-
适用场景
- 应用于系统底层的开发,以便解决系统的性能问题。
- 系统有大量相似对象、需要缓冲池的场景。
-
优点
- 减少对象的创建,降低内存中对象的数量,降低系统的内存,提高效率
- 减少内存之外的其他资源占用
-
缺点
- 关注内/外部状态、关注线程安全问题
- 使得系统、程序的逻辑复杂化
-
代码示例
/** * @author vvirster@163.com * @date 2022/5/25 14:48 * 抽象出享元对象接口 **/ public interface IToys { void setColor(String color); int getSize(); String getColor(); }
/** * @author vvirster@163.com * @date 2022/5/25 14:49 * 具体享元对象 **/ public class SpecificToys implements IToys{ private String color; private int size; public SpecificToys(int size) { this.size = size; } @Override public void setColor(String color) { this.color=color; } @Override public int getSize() { return size; } @Override public String getColor() { return color; } }
/** * @author vvirster@163.com * @date 2022/5/25 14:53 **/ public class ToysFactory { //存储共享对象 private Map<Integer, IToys> cacheMap = new HashMap<>(); private static ToysFactory instance = null; private ToysFactory(){} public static ToysFactory getInstance(){ if (null == instance){ synchronized (ToysFactory.class){ if (null == instance){ instance = new ToysFactory(); } } } return instance; } public IToys getIToysInstance(int size){ IToys iToys = cacheMap.get(size); if (null == iToys){ iToys = new SpecificToys(size); cacheMap.put(size, iToys); } return iToys; } }
/** * @author vvirster@163.com * @date 2022/5/25 14:54 **/ public class FlyweightTest { public static void main(String[] args) { IToys iToys1 = ToysFactory.getInstance().getIToysInstance(1); iToys1.setColor("red"); System.out.println(iToys1 + "---" + iToys1.getSize() + "---" + iToys1.getColor()); IToys iToys2 = ToysFactory.getInstance().getIToysInstance(1); iToys1.setColor("blue"); System.out.println(iToys2 + "---" + iToys2.getSize() + "---" + iToys2.getColor()); IToys iToys3 = ToysFactory.getInstance().getIToysInstance(1); iToys1.setColor("yellow"); System.out.println(iToys3 + "---" + iToys3.getSize() + "---" + iToys3.getColor()); } }
运行结果:
UML类图:
从运行结果中也可以看出,引用的IToys实例指向的地址都是同一个。标识该对象是属于共享的一个状态
2.12 组合模式
-
定义:
组合多个对象形成树形结构以表示有整体-部分关系层次结构,组合模式可以让客户端统一对待单个对象和组合对象
-
使用场景:
- 希望客户端可以忽略组合对象与单个对象的差异时
- 处理一个树型结构时
-
优点:
- 清楚地定义分层次的复杂对象,表示对象的全部或部分层次
- 让客户端忽略了层次的差异,方便对整个层次结构进行控制
- 简化客户端代码
- 符合开闭原则
-
缺点:
- 限制类型时较为复杂,使设计变得更加抽象
- 叶子和树枝的声明都是实现类,而不是接口,违反了依赖倒置原则。
-
代码示例
/** * @author vvirster@163.com * @date 2022/5/25 11:24 * 折扣接口 **/ public interface DiscountStrategy { /** * 获取折扣后的价格 * @param price 价格 * @return 折扣后的价格 */ double getTotal(double price); }
/** * @author vvirster@163.com * @date 2022/5/25 11:26 **/ public class ActivityDiscount implements DiscountStrategy{ // 活动打9折 @Override public double getTotal(double price) { return 0.9 * price; } }
/** * @author vvirster@163.com * @date 2022/5/25 11:26 **/ public class VipDiscount implements DiscountStrategy { /** * 会员优惠0.95折 * @param price 价格 */ @Override public double getTotal(double price) { return 0.95*price; } }
/** * @author vvirster@163.com * @date 2022/5/25 11:27 **/ public class StoreDiscount implements DiscountStrategy{ // 超过500部分打七折 @Override public double getTotal(double price) { double STORE_PRICE = 500; if(price> STORE_PRICE){ return 0.7*(price-500) + 500; }else{ return price; } } }
public abstract class CompositeDiscount implements DiscountStrategy{ /** * 组合模式 容器 */ protected ArrayList<DiscountStrategy> strategies =new ArrayList<DiscountStrategy>(); public void add(DiscountStrategy discountStrategy){ //添加叶子结点 strategies.add(discountStrategy); } @Override public double getTotal(double price) { return price; } }
public class SingleMinStrategy extends CompositeDiscount{ /** * 取最小折扣 * @param price 价格 * @return */ @Override public double getTotal(double price) { double rtn = price ; for(DiscountStrategy s :strategies){ rtn = Math.min(rtn,s.getTotal(price)); } return rtn; } }
public class MultipleStrategy extends CompositeDiscount{ // 折上折 @Override public double getTotal(double price) { double rtn = price ; for (DiscountStrategy s : strategies){ rtn = s.getTotal(price); } return rtn ; } }
public class DiscountFactory { public DiscountStrategy create(String type) { //工厂来创建相应策略 //单一折扣策略 if ("ynn".equals(type)) { return new VipDiscount(); } else if ("nyn".equals(type)) { return new StoreDiscount(); } else if ("nny".equals(type)) { return new ActivityDiscount(); } else { //多种折扣策略 CompositeDiscount compositeDiscount; System.out.println("请选择冲突解决方案:1.折上折 2.最低折"); Scanner scanner = new Scanner(System.in); int type2 = scanner.nextInt(); if (type2 == 1) { compositeDiscount = new MultipleStrategy(); } else { compositeDiscount = new SingleMinStrategy(); } if (type.charAt(1) == 'y') { compositeDiscount.add(new StoreDiscount()); } if (type.charAt(0) == 'y') { compositeDiscount.add(new VipDiscount()); } if (type.charAt(2) == 'y') { compositeDiscount.add(new ActivityDiscount()); } return compositeDiscount; } } }
public class Order { public double price; private String type; public DiscountStrategy discountStrategy; public Order(double price) { this.price=price; } public void display(){ System.out.println("总价:"+price); System.out.println("是否是VIP?y/n"); Scanner scanner=new Scanner(System.in); type=scanner.next(); System.out.println("是否超过500?y/n"); String tmp; tmp=scanner.next(); type+=tmp; System.out.println("是否满足活动价?y/n"); tmp=scanner.next(); type+=tmp; DiscountFactory discountFactory=new DiscountFactory(); double discountPrice=discountFactory.create(type).getTotal(price); System.out.println("优惠:"+(price-discountPrice)); System.out.println("应付:"+discountPrice); } }
public class CombinationTest { // 无论是单一折扣还是多种折扣,客户端使用时都是一个用法,不必区分和操心。 public static void main(String[] args) { Order order=new Order(620); order.display(); } }
(三) 行为型
2.13 策略模式
-
定义
它将对象和行为分开,将行为定义为
一个行为接口
和具体行为的实现
。策略模式最大的特点是行为的变化,行为之间可以相互替换。每个if判断都可以理解为就是一个策略。本模式使得算法可独立于使用它的用户而变化 -
使用场景
- 业务代码需要根据场景不同,切换不同的实现逻辑
- 代码中存在大量 if else 逻辑判断
-
优点:
-
符合开闭原则
-
避免使用多重条件转移语句
-
提高算法的保密性和安全性
-
-
缺点
- 客户端必须知道所有的策略类,并自行决定使用哪一个策略类。
- 产生很多策略类
-
包含角色
- Strategy: 抽象策略类:策略是一个接口,该接口定义若干个算法标识,即定义了若干个抽象方法
- Context: 环境类 /上下文类:
- 上下文是依赖于接口的类(是面向策略设计的类,如下图Context类),即上下文包含用策略(接口)声明的变量
- 上下文提供一个方法,该方法委托策略变量调用具体策略所实现的策略接口中的方法(实现接口的类重写策略(接口)中的方法,来完成具体功能)
- Concrete Strategy: 具体策略类:具体策略是实现策略接口的类。具体策略实现策略接口所定义的抽象方法,即给出算法标识的具体方法。(说白了就是重写策略类的方法!)
-
代码示例
电商网站购物,对于初级会员没有优惠,中级会员优惠5%,高级会员优惠15%。
- 传统方式
/** * @author vvirster@163.com * @date 2022/5/28 14:32 **/ public class NormalCalculationPrice { public Double calculationPrice(String type, Double originalPrice, int n) { //中级会员计费 if (type.equals("intermediateMember")) { return originalPrice * n - originalPrice * 0.05; } //高级会员计费 if (type.equals("advancePrimaryMember")) { return originalPrice * n - originalPrice * 0.15; } //普通会员计费 return originalPrice; } }
- 策略模式
----抽象类策略 /** * @author vvirster@163.com * @date 2022/5/28 14:34 **/ public interface MemberStrategy { /** * 一个计算价格的抽象方法 * @param price 商品的价格 * @param n 商品的个数 * @return */ public double calcPrice(double price, int n); }
----抽象类具体实现策略 /** * @author vvirster@163.com * @date 2022/5/28 14:36 * 普通会员——不打折 **/ public class PrimaryMemberStrategy implements MemberStrategy { /** * * @param price 商品的价格 * @param n 商品的个数 * @return */ @Override public double calcPrice(double price, int n) { return price * n; } }
/** * @author vvirster@163.com * @date 2022/5/28 14:36 * 中级会员5%的折扣 **/ public class IntermediateMemberStrategy implements MemberStrategy{ @Override public double calcPrice(double price, int n) { double money = (price * n) - price * n * 0.05; return money; } }
/** * @author vvirster@163.com * @date 2022/5/28 14:37 * 高级会员15%的折扣 **/ public class AdvanceMemberStrategy implements MemberStrategy{ @Override public double calcPrice(double price, int n) { double money = price * n - price * n * 0.15; return money; } }
-----上下文类 /** * @author vvirster@163.com * @date 2022/5/28 14:38 * 上下文类 * 负责和具体的策略类交互 **/ public class MemberContext { /** * 用户折扣策略接口 */ private final MemberStrategy memberStrategy; /** * 构造方法 * * @param memberStrategy */ public MemberContext(MemberStrategy memberStrategy) { this.memberStrategy = memberStrategy; } /** * 计算价格 * * @param goodsPrice * @param n * @return */ public double calculatePrice(double goodsPrice, int n) { // 通过接口变量调用对应的具体策略 return memberStrategy.calcPrice(goodsPrice, n); } }
/** * @author vvirster@163.com * @date 2022/5/28 14:40 **/ public class StrategyTest { public static void main(String[] args) { // 具体行为策略 MemberStrategy primaryMemberStrategy = new PrimaryMemberStrategy(); // 接口回调(向上转型) MemberStrategy intermediateMemberStrategy = new IntermediateMemberStrategy(); MemberStrategy advanceMemberStrategy = new AdvanceMemberStrategy(); // 用户选择不同策略 MemberContext primaryContext = new MemberContext(primaryMemberStrategy); MemberContext intermediateContext = new MemberContext(intermediateMemberStrategy); MemberContext advanceContext = new MemberContext(advanceMemberStrategy); //计算一本300块钱的书 System.out.println("普通会员的价格:"+ primaryContext.calculatePrice(300,1));// 普通会员:300 System.out.println("中级会员的价格:"+ intermediateContext.calculatePrice(300,1));// 中级会员 285 System.out.println("高级会员的价格:"+ advanceContext.calculatePrice(300,1));// 高级会员255 } }
运行结果:
上述代码还可以用工厂模式进行进一步优化。
2.14 命令模式
-
定义
将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。这样两者之间通过命令对象进行沟通,这样方便将命令对象进行存储、传递、调用、增加与管理。
-
适用场景
- 请求调用者和请求接收者需要解耦,使得调用者和接收者不直接交互, 需要抽象出等待执行的行为
- 系统需要在不同的时间指定请求、将请求排队和执行请求。
- 系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作。
-
优点
- 降低解耦
- 增加或删除命令非常方便。采用命令模式增加与删除命令不会影响其他类,它满足“开闭原则”,对扩展比较灵活。
- 可以实现宏命令。命令模式可以与组合模式结合,将多个命令装配成一个组合命令,即宏命令。
- 方便实现 Undo 和 Redo 操作。命令模式可以与备忘录模式结合,实现命令的撤销与恢复。
-
缺点
- 命令的无限扩展会增加类的数量,提高系统实现复杂度
-
包含角色
- 抽象命令类(Command)角色: 定义命令的接口,声明执行的方法。
- 具体命令(Concrete Command)角色:具体的命令,实现命令接口;通常会持有接收者,并调用接收者的功能来完成命令要执行的操作。
- 实现者/接收者(Receiver)角色: 接收者,真正执行命令的对象。任何类都可能成为一个接收者,只要它能够实现命令要求实现的相应功能。
- 调用者/请求者(Invoker)角色: 要求命令对象执行请求,通常会持有命令对象,可以持有很多的命令对象。这个是客户端真正触发命令并要求命令执行相应操作的地方,也就是说相当于使用命令对象的入口。
-
代码示例:
服务员: 就是调用者角色,由她来发起命令。
大厨: 就是接收者角色,真正命令执行的对象。
订单: 命令中包含订单。
/** * @author vvirster@163.com * @date 2022/5/28 15:02 * 订单类 **/ public class Order { /** * 餐桌号 */ private int diningTable; /** * 记录餐名和份数 */ private Map<String,Integer> foodDic = new HashMap<>(); public int getDiningTable() { return diningTable; } public void setDiningTable(int diningTable) { this.diningTable = diningTable; } public Map<String, Integer> getFoodDic() { return foodDic; } public void setFoodDic(String name,int num) { foodDic.put(name,num); } }
/** * @author vvirster@163.com * @date 2022/5/28 15:02 * 命令接口 **/ public interface Command { /** * 命令执行方法 */ void execute(); }
/** * @author vvirster@163.com * @date 2022/5/28 15:03 * 厨师类 **/ public class SeniorChef { /** * 模拟制作食物的方法 * @param foodName 食物名称 * @param num 份数 */ public void makeFood(String foodName,int num){ System.out.println(num + "份" + foodName); } }
/** * @author vvirster@163.com * @date 2022/5/28 15:03、 * 订单命令具体实现类 **/ public class OrderCommand implements Command{ /** * 持有订单对象 */ private Order order; /** * 持有厨师对象,即命令接收者对象 */ private SeniorChef seniorChef; public OrderCommand(Order order, SeniorChef seniorChef) { this.order = order; this.seniorChef = seniorChef; } @Override public void execute() { System.out.println(order.getDiningTable()+" 桌的订单:"); Map<String, Integer> foodDic = order.getFoodDic(); for (Map.Entry<String, Integer> entry : foodDic.entrySet()) { seniorChef.makeFood(entry.getKey(), entry.getValue()); } try { //停顿一下,模拟做饭过程 Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(order.getDiningTable() + " 桌的饭弄好了 "); } }
/** * @author vvirster@163.com * @date 2022/5/28 15:04 * 服务员类 **/ public class Waiter { /** * 可以持有多个命令 */ private List<Command> commandList; public Waiter(){ commandList = new ArrayList<>(); } public void setCommand(Command command){ commandList.add(command); } /** * 发出命令,给厨师下订单 */ public void orderUp(){ System.out.println("叮咚,大厨,新订单来了......."); for (Command command : commandList) { if (command != null){ command.execute(); } } } }
/** * @author vvirster@163.com * @date 2022/5/28 15:02 * 命令模式 **/ public class CommandTest { public static void main(String[] args) { //创建订单对象 Order order = new Order(); order.setDiningTable(1); order.setFoodDic("西红柿鸡蛋面",1); order.setFoodDic("小杯可乐",2); Order order1 = new Order(); order1.setDiningTable(2); order1.setFoodDic("尖椒肉丝盖饭",1); order1.setFoodDic("小杯雪碧",1); //创建接收者对象(厨师) SeniorChef receive = new SeniorChef(); //将订单和接收者封装成命令对象 OrderCommand orderCommand = new OrderCommand(order,receive); OrderCommand orderCommand1 = new OrderCommand(order1,receive); //创建调用者(服务员)并持有命令 Waiter waiter = new Waiter(); waiter.setCommand(orderCommand); waiter.setCommand(orderCommand1); //将订单命令带到柜台给厨师下订单 waiter.orderUp(); } }
运行结果:
2.15 解释器模式
-
定义:
给分析对象定义一个语言,并定义该语言的文法表示,再设计一个解析器来解释语言中的句子。
为了解释一种语言,而为语言创建的解释器
-
适用场景:
-
当问题重复出现,且可以用一种简单的语言来进行表达时
-
当一个语言需要解释执行,并且语言中的句子可以表示为一个抽象语法树的时候,如 XML 文档解释。
-
某个特定类型问题发生频率足够高
例如日志处理:
使用 脚本语言 或 编程语言 处理日志时 , 有很多服务 会产生 大量的日志 , 需要 对日志进行解析 , 生成报表 ;
各个服务的日志格式不同 , 数据中的要素相同 , 这种情况下 , 通过程序解决上述问题 , 主要的解决方案就是使用解释器模式 ;
-
-
优点
- 扩展性好。由于在解释器模式中使用类来表示语言的文法规则,因此可以通过继承等机制来改变或扩展文法。
- 容易实现。在语法树中的每个表达式节点类都是相似的,所以实现其文法较为容易。
-
缺点
- 当语法规则数目太多时,增加了系统复杂度
- 执行效率较低。解释器模式中通常使用大量的循环和递归调用,当要解释的句子较复杂时,其运行速度很慢,且代码的调试过程也比较麻烦。
- 可应用的场景比较少。在软件开发中,需要定义语言文法的应用实例非常少,所以这种模式很少被使用到。
-
涉及角色
- 抽象表达式角色:定义解释器的接口,约定解释器的解释操作,主要包含解释方法 interpret()。
- 终结符表达式角色:是抽象表达式的子类,用来实现文法中与终结符相关的操作,文法中的每一个终结符都有一个具体终结表达式与之相对应。
- 非终结符表达式角色:也是抽象表达式的子类,用来实现文法中与非终结符相关的操作,文法中的每条规则都对应于一个非终结符表达式。
- 环境角色:通常包含各个解释器需要的数据或是公共的功能,一般用来传递被所有解释器共享的数据,后面的解释器可以从这里获取这些值。
- 客户端:主要任务是将需要分析的句子或表达式转换成使用解释器对象描述的抽象语法树,然后调用解释器的解释方法,当然也可以通过环境角色间接访问解释器的解释方法。
-
代码示例
将字符串使用空格切割成数组
遇到数字直接入栈
遇到运算符 , 从栈中取出两个数据 , 进行计算 , 将计算结果再放入栈中
遍历完毕后 , 最终的栈内数据就是最终结果/** * @author vvirster@163.com * @date 2022/5/28 23:26 * 解释器接口 **/ public interface Interpreter { /** * 解释方法 * @return */ int interpret(); }
/** * @author vvirster@163.com * @date 2022/5/28 23:26 **/ public class AddInterpreter implements Interpreter { /** * 第一个表达式 */ private Interpreter firstInterpreter; /** * 第二个表达式 */ private Interpreter secondInterpreter; public AddInterpreter(Interpreter firstInterpreter, Interpreter secondInterpreter) { this.firstInterpreter = firstInterpreter; this.secondInterpreter = secondInterpreter; } @Override public int interpret() { return this.firstInterpreter.interpret() + this.secondInterpreter.interpret(); } @Override public String toString() { return "+"; } }
/** * @author vvirster@163.com * @date 2022/5/28 23:27 * 乘法解释器 **/ public class MultiInterpreter implements Interpreter { /** * 第一个表达式 */ private Interpreter firstInterpreter; /** * 第二个表达式 */ private Interpreter secondInterpreter; public MultiInterpreter(Interpreter firstInterpreter, Interpreter secondInterpreter) { this.firstInterpreter = firstInterpreter; this.secondInterpreter = secondInterpreter; } @Override public int interpret() { return this.firstInterpreter.interpret() * this.secondInterpreter.interpret(); } @Override public String toString() { return "*"; } }
/** * @author vvirster@163.com * @date 2022/5/28 23:27 **/ public class NumberInterpreter implements Interpreter { /** * 核心数字 * 需要将传入的数据转为数字 */ private int number; /** * 直接设置数字类型 * @param number */ public NumberInterpreter(int number) { this.number = number; } /** * 将字符串转为数字类型 * @param number */ public NumberInterpreter(String number) { this.number = Integer.parseInt(number); } @Override public int interpret() { return this.number; } @Override public String toString() { return "" + this.number; } }
/** * @author vvirster@163.com * @date 2022/5/28 23:28 * 语法解析类 **/ public class ExpressionParser { /** * 存放解释器的栈 * 栈的特点是先进后出 */ private Stack<Interpreter> stack = new Stack<>(); /** * 解析字符串语法 * @param str */ public void parse(String str) { // 通过空格分割字符串 String[] strArray = str.split(" "); // 遍历栈中的字符串 for (String symbol : strArray) { if (!OperatorUtils.isOperator(symbol)) { // 如果不是操作符 , 说明是数字 , 则直接入栈 Interpreter interpreter = new NumberInterpreter(symbol); stack.push(interpreter); System.out.println(symbol + " 入栈"); } else { // 如果是操作符 , 则数据出栈 , 处理是操作符运算的情况 // 取出两个需要计算的元素 Interpreter firstInterpreter = stack.pop(); System.out.println(firstInterpreter + " 出栈"); Interpreter secondInterpreter = stack.pop(); System.out.println(secondInterpreter + " 出栈"); // 获取 运算符号 对应的解释器 Interpreter operator = OperatorUtils. getExpressionInterpretor( symbol, firstInterpreter, secondInterpreter); System.out.println("运算符 " + operator + " 出栈"); // 计算 运算符 运算结果 int result = operator.interpret(); // 将计算结果你年入栈 NumberInterpreter numberInterpreter = new NumberInterpreter(result); stack.push(numberInterpreter); System.out.println("计算结果 " + result + " 入栈"); } } // 取出最终计算结果 , 计算完毕后 , 整个栈必然只剩下一个元素 int result = stack.pop().interpret(); System.out.println("最终计算结果 : " + result); } }
/** * @author vvirster@163.com * @date 2022/5/28 23:28 * 操作工具类 **/ public class OperatorUtils { /** * 判断传入的符号字符串是否是操作符 * @param symbol * @return */ public static boolean isOperator(String symbol) { return symbol.equals("+") || symbol.equals("*"); } public static Interpreter getExpressionInterpretor( String symbol, Interpreter firstInterpreter, Interpreter secondInterpreter) { Interpreter interpreter = null; if (symbol.equals("+")) { interpreter = new AddInterpreter(firstInterpreter, secondInterpreter); } else if (symbol.equals("*")) { interpreter = new MultiInterpreter(firstInterpreter, secondInterpreter); } return interpreter; } }
/** * @author vvirster@163.com * @date 2022/5/28 23:29 * 解释器测试类 **/ public class InterpreterTest { public static void main(String[] args) { // 将字符串使用空格切割成数组 // 遇到数字直接入栈 // 遇到运算符 , 从栈中取出两个数据 , 进行计算 , 将计算结果再放入栈中 // 遍历完毕后 , 最终的栈内数据就是最终结果 String text = "10 2 3 + *"; ExpressionParser parser = new ExpressionParser(); parser.parse(text); } }
运行结果:
2.16 职责链模式
-
定义
为请求创建一个接收此次请求对象的链,使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链, 并沿着这条链传递该请求,直到有一个对象处理它为止。 这一模式的想法是,给多个对象处理一个请求的机会,从而解耦发送者和接受者。
-
适用场景
- 一个请求的处理需要多个对象当中的一个或几个协作处理
- 在不明确指定接收者的情况下,向多个对象中的一个提交一个请求。
- 可处理一个请求的对象集合应被动态指定。
-
角色
- Handler 定义一个处理请求的接口。 (可选)实现后继链。
- Concrete Handler 处理它所负责的请求。 可访问它的后继者。 如果可处理该请求,就处理之;否则将该请求转发给它的后继者。
- Client 向链上的具体处理者(Concrete Handler)对象提交请求。
-
优点
- 请求的发送者和接收者(请求的处理)解耦
- 请求对象不需要具体知道哪个类去处理,只需要关注最后处理结果即可。
- 可以动态组合(新增一个处理类的时候,无需修改原有代码,只需要维护处理器链即可)
-
缺点
- 职责链太长或者处理时间太长,影响性能
- 职责链有可能过多
- 调用的时候,不明确一定能处理,可能会出现到最后都没处理(一般可以设计个兜底服务)
- 建立链条的时候,如果设置不当,可能形成循环调用。
-
代码示例
公司的报销流程,一般主管、经理、总经理都是不同的报销额度,当职员进行报销申请的时候,如果主管权限不足,会自动的再向上提交给经理,经理可以处理也可以再向上提交总经理。报销申请(请求事件)提交后,职员自己并不需要再关注谁能处理,只是关注到最后结果即可。
/**
* @author vvirster@163.com
* @date 2022/5/28 23:51
* 报销申请请求类
**/
public class ApplyFile {
private String name;
private Integer money;
static ApplyFile applyFile = new ApplyFile();
public ApplyFile() {
}
public ApplyFile(String name, Integer money) {
this.name = name;
this.money = money;
}
public static ApplyFile builder() {
return applyFile;
}
public ApplyFile name(String name) {
applyFile.name = name;
return this;
}
public ApplyFile money(Integer money) {
applyFile.money = money;
return this;
}
@Override
public String toString() {
return "name:"+name+",money:"+money;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getMoney() {
return money;
}
public void setMoney(Integer money) {
this.money = money;
}
public ApplyFile build() {
return applyFile;
}
}
/**
* @author vvirster@163.com
* @date 2022/5/28 23:52
* 抽象处理类:
**/
public abstract class HandleCls {
protected HandleCls nextCls;
//处理节点名
private String name;
public HandleCls getNextCls() {
return nextCls;
}
public void setNextCls(HandleCls nextCls) {
this.nextCls = nextCls;
}
public HandleCls(String name) {
this.name = name ;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public abstract void pro(ApplyFile applyFile);
}
/**
* @author vvirster@163.com
* @date 2022/5/28 23:53
* 主管类(直接审批,负责接收和向上申请)
**/
public class Director extends HandleCls{
public Director(String name) {
super(name);
}
@Override
public void pro(ApplyFile applyFile) {
if(applyFile.getMoney() < 100) {
System.out.println(this.getName() + "处理了申请:" + applyFile.toString());
}else {
this.nextCls.pro(applyFile);
}
}
}
/**
* @author vvirster@163.com
* @date 2022/5/28 23:56
* 经理处理类
**/
public class Manager extends HandleCls{
public Manager(String name) {
super(name);
}
@Override
public void pro(ApplyFile applyFile) {
if(applyFile.getMoney() < 200) {
System.out.println(this.getName() + "处理了申请:" + applyFile.toString());
}else{
this.nextCls.pro(applyFile);
}
}
}
/**
* @author vvirster@163.com
* @date 2022/5/28 23:57
* 总经理处理类
**/
public class GeneralManager extends HandleCls{
public GeneralManager(String name) {
super(name);
}
@Override
public void pro(ApplyFile applyFile) {
if(applyFile.getMoney() < 300) {
System.out.println(this.getName() + "处理了申请:" + applyFile.toString());
}else{
this.nextCls.pro(applyFile);
}
}
}
/**
* @author vvirster@163.com
* @date 2022/5/28 23:58
**/
public class ResponsibilityTest {
public static void main(String[] args) {
//创建职责链条
HandleCls p1 = new Director("主管");
HandleCls p2 = new Manager("经理");
HandleCls p3 = new GeneralManager("总经理");
p1.setNextCls(p2);
p2.setNextCls(p3);
//创建报销申请
ApplyFile applyFile = ApplyFile.builder().name("猪小屁").money(99).build();
p1.pro(applyFile);
applyFile = ApplyFile.builder().name("猪小屁").money(101).build();
p1.pro(applyFile);
applyFile = ApplyFile.builder().name("猪小屁").money(201).build();
p1.pro(applyFile);
}
}
运行结果:
UML类图
2.17 状态模式
-
定义
允许一个对象在其内部状态改变时,改变它的行为
-
适用场景:
- 一个对象存在多个状态(不同状态下行为不同),且状态可相互转换
-
优点
- 将不同的状态隔离
- 把各种状态的转换逻辑,分布到State的子类中,减少相互间依赖
- 增加新的状态非常简单
-
缺点
- 状态多的业务场景导致类数目增加,系统边复杂
-
涉及角色
- 使用环境(Context)角色:客户程序是通过它来满足自己的需求。它定义了客户程序需要的接口;并且维护一个具体状态角色的实例,这个实例来决定当前的状态。
- 状态(State)角色:定义一个接口以封装与使用环境角色的一个特定状态相关的行为。
- 具体状态(Concrete State)角色:实现状态角色定义的接口。
-
示例代码
切换视频课程的状态为例进行代码演示
/** * @author vvirster@163.com * @date 2022/5/29 0:26 * 定义一个抽象的课程视频状态类 **/ public abstract class CourseVideoState { protected CourseVideoContext courseVideoContext; public void setCourseVideoContext(CourseVideoContext courseVideoContext) { this.courseVideoContext = courseVideoContext; } /** * 播放功能方法 */ public abstract void play(); /** * 快进功能方法 */ public abstract void speed(); /** * 暂停功能方法 */ public abstract void pause(); /** * 停止功能方法 */ public abstract void stop(); }
/** * @author vvirster@163.com * @date 2022/5/29 0:26 * 定义一个课程视频上下文类 **/ public class CourseVideoContext { private CourseVideoState courseVideoState; public CourseVideoState getCourseVideoState() { return courseVideoState; } public void setCourseVideoState(CourseVideoState courseVideoState) { this.courseVideoState = courseVideoState; this.courseVideoState.setCourseVideoContext(this); } /** * 声明常量 */ public static final PlayState PLAY_STATE=new PlayState(); public static final SpeedState SPEED_STATE=new SpeedState(); public static final PauseSate PAUSE_SATE=new PauseSate(); public static final StopState STOP_STATE=new StopState(); public void play(){ this.courseVideoState.play(); } public void speed(){ this.courseVideoState.speed(); } public void pause(){ this.courseVideoState.pause(); } public void stop(){ this.courseVideoState.stop(); } }
/** * @author vvirster@163.com * @date 2022/5/29 0:29 * 定义一个播放功能状态类 **/ public class PlayState extends CourseVideoState{ @Override public void play() { System.out.println("正常播放课程视频状态"); } @Override public void speed() { super.courseVideoContext.setCourseVideoState(CourseVideoContext.SPEED_STATE); } @Override public void pause() { super.courseVideoContext.setCourseVideoState(CourseVideoContext.PAUSE_SATE); } @Override public void stop() { super.courseVideoContext.setCourseVideoState(CourseVideoContext.STOP_STATE); } }
/** * @author vvirster@163.com * @date 2022/5/29 0:30 * 定义一个快进功能状态类 **/ public class SpeedState extends CourseVideoState{ @Override public void play() { super.courseVideoContext.setCourseVideoState(CourseVideoContext.PLAY_STATE); } @Override public void speed() { System.out.println("快进播放课程视频状态"); } @Override public void pause() { super.courseVideoContext.setCourseVideoState(CourseVideoContext.PAUSE_SATE); } @Override public void stop() { super.courseVideoContext.setCourseVideoState(CourseVideoContext.STOP_STATE); } }
/** * @author vvirster@163.com * @date 2022/5/29 0:31 * 定义一个暂停功能状态类 **/ public class PauseSate extends CourseVideoState{ @Override public void play(){ super.courseVideoContext.setCourseVideoState(CourseVideoContext.PLAY_STATE); } @Override public void speed(){ super.courseVideoContext.setCourseVideoState(CourseVideoContext.SPEED_STATE); } @Override public void pause() { System.out.println("暂停播放课程视频状态"); } @Override public void stop() { super.courseVideoContext.setCourseVideoState(CourseVideoContext.STOP_STATE); } }
/** * @author vvirster@163.com * @date 2022/5/29 0:31 * 定义一个停止功能状态类 **/ public class StopState extends CourseVideoState{ @Override public void play() { super.courseVideoContext.setCourseVideoState(CourseVideoContext.PLAY_STATE); } @Override public void speed() { System.out.println("停止状态不能快进"); } @Override public void pause() { System.out.println("停止状态不能暂停"); } @Override public void stop() { System.out.println("停止播放课程视频状态"); } }
/** * @author vvirster@163.com * @date 2022/5/29 0:32 **/ public class StateModelTest { public static void main(String[] args) { //声明一个上下文 CourseVideoContext courseVideoContext=new CourseVideoContext(); //第一个设置播放视频功能 courseVideoContext.setCourseVideoState(new PlayState()); System.out.println("当前状态:"+courseVideoContext.getCourseVideoState().getClass().getSimpleName()); //设置暂停播放视频功能 courseVideoContext.pause(); System.out.println("当前状态:"+courseVideoContext.getCourseVideoState().getClass().getSimpleName()); //设置快进播放视频功能 courseVideoContext.speed(); System.out.println("当前状态:"+courseVideoContext.getCourseVideoState().getClass().getSimpleName()); //设置停止播放视频功能 courseVideoContext.stop(); System.out.println("当前状态:"+courseVideoContext.getCourseVideoState().getClass().getSimpleName()); //停止播放视频在调用快进功能 courseVideoContext.speed(); } }
运行结果:
UML类图
2.18 观察者模式
-
定义
又叫发布-订阅模式(Publish/Subscribe),定义对象间一种一对多的依赖关系(注册),使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并自动更新(通知)。说白了就是个注册,通知的过程。
-
适用场景:
- 关联行为场景,建立一套触发机制
-
涉及角色
- 抽象主题(Subject)角色:也叫抽象目标类,它提供了一个用于保存观察者对象的聚集类和增加、删除观察者对象的方法,以及通知所有观察者的抽象方法。
- 具体主题(Concrete Subject)角色:也叫具体目标类,它实现抽象目标中的通知方法,当具体主题的内部状态发生改变时,通知所有注册过的观察者对象。继承Subject类,在这里实现具体业务,在具体项目中,该类会有很多变种。
- 抽象观察者(Observer)角色:它是一个抽象类或接口,它包含了一个更新自己的抽象方法,当接到具体主题的更改通知时被调用。
- 具体观察者(Concrete Observer)角色:实现抽象观察者中定义的抽象方法,以便在得到目标的更改通知时更新自身的状态。
-
优点
- 降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系。符合依赖倒置原则。
- 目标与观察者之间建立了一套触发机制。
- 观察者模式支持广播通信
-
缺点
- 观察者之间有过多的细节依赖。太高时间消耗及程序复杂度
- 目标与观察者之间的依赖关系并没有完全解除,而且有可能出现循环引用。
-
代码示例:
/** * @author vvirster@163.com * @date 2022/5/29 1:05 * 定义一个主题 **/ public class Subject { /** * 观察者数组 */ private final Vector<Observer> oVector = new Vector<>(); /** * 增加一个观察者,相当于观察者注册 * * @param observer */ public void addObserver(Observer observer) { this.oVector.add(observer); } /** * 删除一个观察者 * * @param observer */ public void deleteObserver(Observer observer) { this.oVector.remove(observer); } /** * 通知所有观察者,主题有变化时通知观察者 */ public void notifyObserver() { for (Observer observer : this.oVector) { observer.response(); } } }
/** * @author vvirster@163.com * @date 2022/5/29 1:08 * 具体主题 * 继承Subject类,在这里实现具体业务,在具体项目中,该类会有很多变种。 **/ public class ConcreteSubject extends Subject { //具体业务 public void doSomething() { //... System.out.println("具体目标发生改变..."); System.out.println("--------------"); super.notifyObserver(); } }
/** * @author vvirster@163.com * @date 2022/5/29 1:06 * 抽象观察者Observer **/ public interface Observer { /** * 观察者响应 */ void response(); }
/** * @author vvirster@163.com * @date 2022/5/29 1:09 * 具体观察者1 **/ public class ConcreteObserver1 implements Observer { public void response() { System.out.println("具体观察者1作出反应!"); } }
/** * @author vvirster@163.com * @date 2022/5/29 1:09 * 具体观察者2 **/ public class ConcreteObserver2 implements Observer { public void response() { System.out.println("具体观察者2作出反应!"); } }
/** * @author vvirster@163.com * @date 2022/5/29 1:10 **/ public class ObserverTest { public static void main(String[] args) { //创建一个主题 ConcreteSubject subject = new ConcreteSubject(); //定义一个观察者 Observer observer = new ConcreteObserver1(); Observer observer2 = new ConcreteObserver2(); //注册观察者 subject.addObserver(observer); subject.addObserver(observer2); //开始活动 subject.doSomething(); } }
运行结果:
UML类图:
2.19 中介者模式
-
定义
又叫调停模式,定义一个中介角色来封装一系列对象之间的交互,使原有对象之间的耦合松散,且可以独立地改变它们之间的交互。
-
适用场景
系统中对象之间存在复杂的引用关系,产生的相互依赖关系结构混乱且难以复用时。
交互的公共行为,想创建一个运行于多个类之间的对象,又不想生成新的子类时。
-
涉及角色
- 抽象中介者(Mediator)角色:它是中介者的接口,提供了同事对象注册与转发同事对象信息的抽象方法。
- 具体中介者(Concrete Mediator)角色:实现中介者接口,定义一个 List 来管理同事对象,协调各个同事角色之间的交互关系,因此它依赖于同事角色。
- 抽象同事类(Colleague)角色:定义同事类的接口,保存中介者对象,提供同事对象交互的抽象方法,实现所有相互影响的同事类的公共功能。
- 具体同事类(Concrete Colleague)角色:是抽象同事类的实现者,当需要与其他同事对象交互时,由中介者对象负责后续的交互。
-
优点
- 类之间各司其职,符合迪米特法则。
- 降低了对象之间的耦合性,使得对象易于独立地被复用。
- 对象间的一对多关联转变为一对一的关联,提高系统的灵活性,使得系统易于维护和扩展。
-
缺点
- 将原本多个对象直接的相互依赖变成了中介者和多个同事类的依赖关系。当同事类越多时,中介者就会越臃肿,变得复杂且难以维护。
-
示例代码
现在租房基本都是通过房屋中介,房主将房屋托管给房屋中介,而租房者从房屋中介获取房屋信息。房屋中介充当租房者与房屋所有者之间的中介者。
/** * @author vvirster@163.com * @date 2022/5/29 15:52 * 抽象中介者 **/ public abstract class Mediator { /** * 声明一个联络的方法 * @param message * @param person */ protected abstract void contact(String message, Person person); }
/** * @author vvirster@163.com * @date 2022/5/29 15:55 * 具体中介者 **/ public class MediatorStructure extends Mediator { /** * 中介者需要知道房主是谁 */ private HouseOwner houseOwner; /** * 中介者需要知道承租者是谁 */ private Tenant tenant; public MediatorStructure() { } public HouseOwner getHouseOwner() { return houseOwner; } public void setHouseOwner(HouseOwner houseOwner) { this.houseOwner = houseOwner; } public Tenant getTenant() { return tenant; } public void setTenant(Tenant tenant) { this.tenant = tenant; } /** * 重写联络方法 * @param message * @param person */ @Override protected void contact(String message, Person person) { if (person == houseOwner){ //将房主的信息反馈给承租者 this.tenant.getMessage(message); }else { //将承租者的信息反馈给房主 this.houseOwner.getMessage(message); } } }
/** * @author vvirster@163.com * @date 2022/5/29 15:56 * 抽象Person类 **/ public abstract class Person { protected String name; protected Mediator mediator; protected Person(String name, Mediator mediator) { this.name = name; this.mediator = mediator; } }
/** * @author vvirster@163.com * @date 2022/5/29 15:57 * 具体房主类 **/ public class HouseOwner extends Person { protected HouseOwner(String name, Mediator mediator) { super(name, mediator); } /** * 与中介者联系的方法 * @param message */ public void contact(String message){ this.mediator.contact(message,this); } /** * 获取信息 * @param message */ public void getMessage(String message){ System.out.println("房主" + this.name + "获取到的信息是:"+message); } }
/** * @author vvirster@163.com * @date 2022/5/29 15:57 * 具体承租人 **/ public class Tenant extends Person{ protected Tenant(String name, Mediator mediator) { super(name, mediator); } /** * 与中介者联系的方法 * @param message */ public void contact(String message){ this.mediator.contact(message,this); } /** * 获取信息 * @param message */ public void getMessage(String message){ System.out.println("承租者" + this.name + "获取到的信息是:"+message); } }
/** * @author vvirster@163.com * @date 2022/5/29 16:00 * 中介者模式测试 **/ public class MediatorTest { public static void main(String[] args) { //创建中介者对象 MediatorStructure mediator = new MediatorStructure(); //创建房主对象,房主只需要知道中介 HouseOwner houseOwner = new HouseOwner("张三", mediator); //创建承租者对象,承租者也只需要知道中介 Tenant tenant = new Tenant("李四",mediator); //中介需要知道房主和承租者 mediator.setHouseOwner(houseOwner); mediator.setTenant(tenant); //承租者与中介联系 tenant.contact("我需要租三室一厅的房子"); //房主与中介联系 houseOwner.contact("我这里有三室一厅的房子,你要租吗?"); } }
运行结果:
UML类图:
2.10 迭代器模式
-
定义
提供一个对象来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示。
-
适用场景
当需要为聚合对象提供多种遍历方式时。
访问一个集合对象的内容而无需暴露它的内部表示
为遍历不同的集合结果提供一个统一的接口
-
涉及角色
- 抽象聚合(Aggregate)角色:定义存储、添加、删除聚合元素以及创建迭代器对象的接口。
- 具体聚合(Concrete Aggregate)角色:实现抽象聚合类,返回一个具体迭代器的实例。
- 抽象迭代器(Iterator)角色:定义访问和遍历聚合元素的接口,通常包含 hasNext()、next() 等方法。
- 具体迭代器(Concrete Iterator)角色:实现抽象迭代器接口中所定义的方法,完成对聚合对象的遍历,记录遍历的当前位置。
-
优点
- 访问一个聚合对象的内容而无须暴露它的内部表示。
- 遍历任务交由迭代器完成,这简化了聚合类。
- 它支持以不同方式遍历一个聚合,甚至可以自定义迭代器的子类以支持新的遍历。
- 增加新的聚合类和迭代器类都很方便,无须修改原有代码。
- 封装性良好,为遍历不同的聚合结构提供一个统一的接口。
-
缺点
- 增加了类的个数,这在一定程度上增加了系统的复杂性。
-
示例代码
定义一个可以存储学生对象的容器对象,将遍历该容器的功能交由迭代器实现
/** * @author vvirster@163.com * @date 2022/5/29 16:16 * 创建学生类 **/ public class Student { /** * 姓名 */ private String name ; /** * 班级编号 */ private String classNo; public Student() { } public Student(String name, String classNo) { this.name = name; this.classNo = classNo; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getClassNo() { return classNo; } public void setClassNo(String classNo) { this.classNo = classNo; } @Override public String toString() { return "Student{" + "name='" + name + '\'' + ", classNo='" + classNo + '\'' + '}'; } }
/** * @author vvirster@163.com * @date 2022/5/29 16:15 * 定义迭代器接口 **/ public interface StudentIterator { /** * 判断是否有下一个元素 * @return */ boolean hasNext(); /** * 遍历下一个学生对象 * @return */ Student next(); }
/** * @author vvirster@163.com * @date 2022/5/29 16:18 * 定义具体的迭代器类,重写所有的抽象方法 **/ public class StudentIteratorImpl implements StudentIterator{ private List<Student> list; private int position = 0; public StudentIteratorImpl(List<Student> list) { this.list = list; } @Override public boolean hasNext() { return position < list.size(); } @Override public Student next() { Student currentStudent = list.get(position); position ++; return currentStudent; } }
/** * @author vvirster@163.com * @date 2022/5/29 16:19 * 定义抽象容器类,包含添加元素,删除元素,获取迭代器对象的方法 **/ public interface StudentAggregate { /** * 添加学生 * @param student */ void addStudent(Student student); /** * 删除学生 * @param student */ void removeStudent(Student student); /** * 获取迭代器 * @return */ StudentIterator getStudentIterator(); }
/** * @author vvirster@163.com * @date 2022/5/29 16:20 * 定义具体的容器类,重写所有的方法 **/ public class StudentAggregateImpl implements StudentAggregate{ /** * 学生列表 */ private List<Student> list = new ArrayList<Student>(); @Override public void addStudent(Student student) { this.list.add(student); } @Override public void removeStudent(Student student) { this.list.remove(student); } @Override public StudentIterator getStudentIterator() { return new StudentIteratorImpl(list); } }
/** * @author vvirster@163.com * @date 2022/5/29 16:22 * 迭代器测试接口 **/ public class IteratorTest { public static void main(String[] args) { StudentAggregate studentAggregate = new StudentAggregateImpl(); for (int i = 0; i < 10; i++) { Student student = new Student("张三"+i,"A01"); studentAggregate.addStudent(student); } StudentIterator studentIterator = studentAggregate.getStudentIterator(); while (studentIterator.hasNext()) { System.out.println(studentIterator.next().toString()); } } }
运行结果:
UML类图:
2.21 访问者模式
-
定义
封装一些作用于某种数据结构的各元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的新的操作。
-
适用场景
- 一个数据结构如(List/Set/Map等)包含很多类型对象
- 数据结构与数据操作分离
-
涉及角色
- 抽象访问者(Visitor):定义了一个访问元素的具体接口。
- 具体访问者(Concrete Visitor):所有的具体访问者都必须实现抽象访问者,并实现访问操作。
- 抽象目标(target):声明了一个接收操作的接口。
- 具体目标(Concrete Target):实现了抽象目标和一些业务相关逻辑。
- 对象结构(Object Structure):包含了具体目标的容器。
-
优点
- 访问者模式符合设计模式原则之一的单一职责原则
- 访问者模式具有高可拓展性和高弹性
-
缺点
- 因为访问的具体目标对访问者类提供了具体的细节,违反了迪米特原则和依赖倒置原则
- 修改具体目标改动不易
- 增加新的数据结构困难
-
示例代码
/** * @author vvirster@163.com * @date 2022/5/29 22:36 * 定义目标接口 **/ public interface Hello { void say(Visitor visitor); }
定义具体目标
/** * @author vvirster@163.com * @date 2022/5/29 22:37 **/ public class EnglishHello implements Hello { @Override public void say(Visitor visitor) { visitor.visit(this); } }
/** * @author vvirster@163.com * @date 2022/5/29 22:36 **/ public class ChineseHello implements Hello{ @Override public void say(Visitor visitor) { visitor.visit(this); } }
定义对象结构
/** * @author vvirster@163.com * @date 2022/5/29 22:37 **/ public class StructureHello implements Hello { Hello[] hello; { hello = new Hello[]{new EnglishHello(), new ChineseHello()}; } @Override public void say(Visitor visitor) { Arrays.stream(hello).forEach(h -> h.say(visitor)); visitor.visit(this); } }
定义抽象访问者
/** * @author vvirster@163.com * @date 2022/5/29 22:35 * 定义抽象访问者 **/ public interface Visitor { void visit(EnglishHello hello); void visit(ChineseHello hello); void visit(StructureHello hello); }
定义具体访问者
/** * @author vvirster@163.com * @date 2022/5/29 22:39 **/ public class ConcreteVisitor implements Visitor { @Override public void visit(EnglishHello hello) { System.out.println("Hello world!"); } @Override public void visit(ChineseHello hello) { System.out.println("你好世界!"); } @Override public void visit(StructureHello hello) { System.out.println("我是对象容器hello"); } }
测试
/** * @author vvirster@163.com * @date 2022/5/29 22:40 **/ public class VisitorPatternTest { public static void main(String[] args) { Hello hello = new StructureHello(); hello.say(new ConcreteVisitor()); } }
运行结果:
UML类图
2.22 备忘录模式
-
定义
保存一个对象的某个状态,以便在适当的时候恢复对象。
-
适用场景
- 保存及恢复数据相关业务场景
- 后悔的时候,即想恢复到之前的状态
-
涉及角色
- originator : 对象(需要保存状态的对象) 。
- Memento :备忘录对象,负责保存好记录,即 Originator 内部状态 。
- Caretaker: 守护者对象,负责保存多个备忘录对象, 使用集合管理,提高效率 。
-
优点
- 给用户提供了一种可以恢复状态的机制,可以使用户能够比较方便地回到某个历史的状态。
- 实现了信息的封装,使得用户不需要关心状态的保存细节。
-
缺点
- 消耗资源。如果类的成员变量过多,势必会占用比较大的资源,而且每一次保存都会消耗一定的内存。
-
示例代码
备忘录对象
/** * @author vvirster@163.com * @date 2022/5/29 23:17 * 备忘录对象 **/ public class Memento { /** * 角色名称 */ private String name; /** * 攻击力 */ private int vit; /** * 防御力 */ private int def; public Memento() { } public Memento(String name, int vit, int def) { this.name = name; this.vit = vit; this.def = def; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getVit() { return vit; } public void setVit(int vit) { this.vit = vit; } public int getDef() { return def; } public void setDef(int def) { this.def = def; } }
守护者对象
/** * @author vvirster@163.com * @date 2022/5/29 23:19 * 守护者对象,负责保存多个备忘录对象, 使用集合管理,提高效率 。 **/ public class Caretaker { /** * 如果只保存一次状态 */ private Memento memento; //对 GameRole 保存多次状态 //private ArrayList<Memento> mementos; //对多个游戏角色保存多个状态 //private HashMap<String, List<Memento>> rolesMementos; public Memento getMemento() { return memento; } public void setMemento(Memento memento) { this.memento = memento; } }
游戏对象
/** * @author vvirster@163.com * @date 2022/5/29 23:21 * 游戏角色 **/ public class GameRole { /** * 角色名称 */ private String name; /** * 攻击力 */ private int vit; /** * 防御力 */ private int def; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getVit() { return vit; } public void setVit(int vit) { this.vit = vit; } public int getDef() { return def; } public void setDef(int def) { this.def = def; } /** * 创建Memento,即根据当前的状态得到 Memento * * @return */ public Memento createMemento() { return new Memento(name, vit, def); } /** * 从备忘录对象,恢复 gameRole 的状态 * * @param memento */ public void recoverGameRoleFromMemento(Memento memento) { this.name = memento.getName(); this.vit = memento.getVit(); this.def = memento.getDef(); } /** * 显示当前游戏角色的状态 */ public void display() { System.out.println("游戏角色:" + name + " 攻击力:" + this.vit + " 防御力:" + this.def); } }
测试
/** * @author vvirster@163.com * @date 2022/5/29 23:22 **/ public class MemorandumTest { public static void main(String[] args) { //创建对象 GameRole gameRole = new GameRole(); gameRole.setName("安琪拉"); gameRole.setVit(100); gameRole.setDef(100); System.out.print("【战前状态】"); gameRole.display(); //把当前状态保存到备忘录对象 Caretaker Caretaker caretaker = new Caretaker(); caretaker.setMemento(gameRole.createMemento()); //对象状态改变 gameRole.setName("安琪拉"); gameRole.setDef(50); gameRole.setVit(60); System.out.print("【战后状态】"); gameRole.display(); //备忘录对象恢复之前对象状态 gameRole.recoverGameRoleFromMemento(caretaker.getMemento()); System.out.print("【恢复状态】"); gameRole.display(); } }
运行结果:
UML类图
2.23 模板模式
-
定义
定义一个操作中的算法的框架,而将一些步骤延迟到子类中。使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
-
适用场景
一次性实现一个算法的不变的部分,并将可变的行为留给子类来实现。
各子类中公共的行为被提取出来并集中到一个公共父类中,从而避免代码重复。
-
涉及角色
- **抽象类:**实现了模板的方法,定义了算法的骨架。
- **具体实现类:**实现了抽象类的抽象方法,以完成完整的算法
-
优点
- 封装不变部分,扩展可变部分。把认为是不变部分的算法封装到父类实现,而可变部分的则可以通过继承来继续扩展。
- 提取公共部分代码,便于维护。
- 行为由父类控制,子类实现。基本方法是由子类实现的,因此子类可以通过扩展的方式增加相应的功能,符合开闭原 则。
-
缺点
- 类数目增加
- 增加了系统实现的复杂度
- 继承关系自身缺点,如果父类添加新的抽象方法,所有子类都要改一遍
-
示例代码
我们在玩游戏的时候,都需要初始化加载游戏,然后开始游戏,最后结束游戏,这像是一套模板一样的操作,但是具体的实现方法却不同,例如:LOL 游戏与 CF 游戏的加载资源肯定不同,游戏的内容也不同。
游戏抽象类
/** * @author vvirster@163.com * @date 2022/5/29 23:44 **/ public abstract class Game { /** * 初始化游戏 */ abstract void init(); /** *开始游戏 */ abstract void start(); /** * 结束游戏 */ abstract void end(); /** * 模板 */ public final void play() { // 初始化游戏 init(); // 开始游戏 start(); // 结束游戏 end(); } }
具体实现类
/** * @author vvirster@163.com * @date 2022/5/29 23:47 **/ public class CfGame extends Game { @Override void init() { System.out.println("初始化 CF 游戏"); } @Override void start() { System.out.println("CF 游戏开始"); } @Override void end() { System.out.println("CF 游戏结束"); } }
/** * @author vvirster@163.com * @date 2022/5/29 23:46 **/ public class LolGame extends Game { @Override void init() { System.out.println("初始化 LOL 游戏"); } @Override void start() { System.out.println("LOL 游戏开始"); } @Override void end() { System.out.println("LOL 游戏结束"); } }
/** * @author vvirster@163.com * @date 2022/5/29 23:51 **/ public class TemplateTest { public static void main(String[] args) { // LOL 游戏 Game lolGame = new LolGame(); lolGame.play(); System.out.println(); // CF 游戏 Game cfGame = new CfGame(); cfGame.play(); } }
运行结果
UML类图: