写在前头
建议浏览我的blog网站来获得更佳观感
本文主要是个人初学Java理解设计模式和面向对象设计,对项目的一次总结,大佬请绕步,比较适合新手食用…
背景
源自于一次简单的程序实践课程,构建了一个基于控制台的轮渡管理系统(非常常规非常滴水…)。好像是大二上做的,对于大二的学生来讲再写这种控制台管理系统有些无聊了,毕竟之前就在大一上学期学完c就写了个链表的学生管理系统,被自己的屎山狠狠恶心(绝望)。所以在这一次的实践,心想能不能弄点好玩的打发时间,毕竟几乎要在机房里面坐牢好几天…
所以,我在快速写完了几个基本任务就开始在拓展点有趣的内容了。记得原本项目内容是要写一个管理一个船载几种车过船的,按照一种装载策略对应种类对应数量排好队上船开过去,然后记录信息。我就拓展了一下装载策略的部分。
想法
刚开始看到装载策略的时候,是想着改合理一点的,比如小车按位置,大车按吨位这样,但是于此同时我需要先实现原本的组合排队装载策略,保证能水过去这个实践,能不能我先实现组合排队,但是又能直接切换自己的策略又不影响之前的代码呢的方法呢🤔?
诶!我有个想法🤓☝,我们能不能先预设在装载时候的代码,对于装载逻辑预设一个槽位,然后编写一个符合这个槽位规格的方法放进去,只要(你是否在寻找:Spring)
于是乎我问了一下AI来给我来几个点子,现在的大模型AI也是一个某种意义上的baidu,帮忙找东西也挺方便的。最后,我就得到几种设计模式:
-
核心模式:策略模式(动态切换算法)。
-
增强模式:
-
组合模式:实现策略的组合。
-
工厂模式:简化策略实例的创建。
-
装饰器模式:扩展策略的功能。
-
模板方法模式:统一管理通用逻辑。
思路
高拓展性——是我这个项目最想要实现的特性,所以基于这个目标我引入了这几种技术
控制反转 & 依赖注入,实现复杂的排队逻辑与执行类的解耦
策略模式,实现多种排队方法的切换,并且对方法与执行解耦,提高可维护性
实现&对比
对于这个简单的学习项目,我就不在这里过多讲述如何实现了,专注于文章的主题——设计模式的意义
策略模式
策略模式的核心思想是将算法封装成独立的类,并通过接口进行抽象。这样,客户端可以根据需要选择不同的算法实现,而无需修改客户端代码。
在这个项目中,对于多个方法来来说,他们都是一种排队方法,因此是可以实现一个抽象类的。但是对于排队方法来说,这个类更注重于执行方法中,因此应该是使用一个接口来实现这些排队方法的抽象。
接口LoadingRule代码
package rule;
import entity.vehicle.Vehicle;
import java.util.List;
public interface LoadingRule {
Vehicle selectNextVehicleToLoad(List selectionPool);
}
那么,如果一个方法想要被作为一种排队策略,那么这个方法类就需要实现接口LoadingRule中的接口方法selectNextVehicleToLoad 。通过这个接口,我们就实现了各个排队方法调用方式的统一,但是实现逻辑却是不一样的。
对于接口使用的碎碎念
举个栗子🌰,现在奶茶种类众多且名称复杂,你问店员有没有珍珠奶茶,他说“对不起先生,我们店内没有珍珠奶茶,只有QQㄋㄟㄋㄟ好喝到咩噗茶”,但是在如果你设置了一个珍珠奶茶的接口,规定把珍珠放进奶茶里面的就叫做珍珠奶茶,那么以后你去买珍珠奶茶就不用念千奇百怪的名字了(虽然视频店员回头下一秒就说了要一杯珍珠奶茶…)
控制反转&依赖注入
在通过LoadingRule接口解耦多个排队策略选择后,我们可以大概实现以下代码
private void loadVehicles() {
Ferry ferry = new Ferry(ferryList.size() + 1);
whpackage entity;
import entity.vehicle.Vehicle;
import log.Log;
import rule.LoadingRule;
import rule.SimpleLoadingRule; // 或者 AdvancedLoadingRule
import java.util.*;
public class FerryTerminal {
private PriorityQueue waitingQueue = new PriorityQueue<>(Comparator.comparingLong(Vehicle::getArrivalTime));
private List ferryList = new ArrayList<>();
public Log log = new Log();
private LoadingRule loadingRule;
// 构造函数,内部创建 LoadingRule 实例
public FerryTerminal() {
this.loadingRule = new SimpleLoadingRule(); // 或者 new AdvancedLoadingRule();
}
// 加入新车辆到等待队列
public void enqueueVehicle(Vehicle vehicle) {
waitingQueue.offer(vehicle);
}
/**
* 持续装载车辆到轮渡,直到所有车辆都被处理。
*/
public void startLoadingProcess() {
while (!waitingQueue.isEmpty()) {
loadVehicles();
}
}
/**
* 主要业务逻辑
* 装载车辆到轮渡,并将装载完毕的轮渡加入到轮渡列表中。
*/
private void loadVehicles() {
Ferry ferry = new Ferry(ferryList.size() + 1);
while (ferry.getCurrentSize() < Ferry.MAX_SIZE && !waitingQueue.isEmpty()) {
List<Vehicle> selectionPool = extractSelectionPool();
Vehicle selectedVehicle = loadingRule.selectNextVehicleToLoad(selectionPool);
if (selectedVehicle != null) {
ferry.loadVehicle(selectedVehicle);
log.recordVehicle(selectedVehicle, ferry.getId());
selectionPool.remove(selectedVehicle);
} else {
break;
}
for (Vehicle v : selectionPool) {
waitingQueue.offer(v);
}
}
ferry.depart();
ferryList.add(ferry);
}
private List<Vehicle> extractSelectionPool() {
List<Vehicle> selectionPool = new ArrayList<>();
int maxPoolSize = 6;
for (int i = 0; i < maxPoolSize && !waitingQueue.isEmpty(); i++) {
selectionPool.add(waitingQueue.poll());
}
return selectionPool;
}
}
对于上面使用LoadingRule接口实现策略模式,我们目前只解决了排队方法的切换和封装,但是还没有完全把方法实现内部解耦——换句话说,当我们需要选择不同策略时,我们需在执行类中编写对应的实现方法代码。
也就是说,目前我们通过策略模式,实现了方法代码与方法代码之间的解耦。
但是这里的执行者仍与策略实现代码有耦合,在设置具体实现方法中,我们是在执行者中通过new来添加的。如果我们要切换不同的策略,就需要重新打开这里的代码,改写new的类实例,也就是硬编码问题。
那博主博主,执行者中存在硬编码问题还是不够解耦,有没有更解耦一点的方式呢?有的兄弟有的(
为了让排队方法的创建硬编码解耦,我们可以把创建这件事情抛出到外面。即我们只管LoadingRule的使用,就像平常我们买一个肥皂,我们只负责使用肥皂,而肥皂怎么生产的我们却不需要管。根据项目需求设计,我们一次只用一个方法,因此可以在执行类的构造函数中,在new执行类时把实现LoadingRule接口的方法放进来。
于是乎,我们通过DI(依赖注入)的方式,实现了设计原则——IOC(控制反转)。把对象的创建和管理,从类内部转移到外部。
控制反转IOC与依赖注入DI的关系
IOC 是一种设计原则,强调将控制权从类内部转移到外部。
DI 是 IOC 的一种实现方式,通过构造函数、Setter 方法或接口等方式传递依赖项。
IOC 不一定需要 DI,还可以通过其他方式(如服务定位器模式)实现。
控制反转&依赖注入与策略模式
在我写这篇文章的时候,我也有一些对这两种方式的关系有点放分不开,因此在这里作为文章的重点也记录一下
(1) 策略模式与 IoC 的关系
策略模式关注的是算法的封装和互换,将不同的算法封装成独立的类。
IoC 关注的是对象的创建和管理,将对象的控制权反转给外部容器或框架。
虽然策略模式可以与 IoC 结合使用,但它们解决的问题不同:
策略模式 解决的是算法的封装和互换。
IoC 解决的是对象的创建和管理。
(2) 策略模式与 DI 的关系
策略模式 通过接口定义算法,具体算法由不同的实现类提供。
DI 通过外部注入依赖对象,使得依赖关系更加灵活。
在项目中,策略模式和 DI 可以结合使用,以实现更灵活的系统设计:
策略模式 定义了不同的装载规则。
8827

被折叠的 条评论
为什么被折叠?



